@brainervirus/workit-cli 8.12.3 → 8.13.1
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 +1549 -319
- package/package.json +5 -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.13.1",
|
|
69
69
|
private: false,
|
|
70
70
|
description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
71
71
|
keywords: [
|
|
@@ -103,13 +103,14 @@ var init_package = __esm(() => {
|
|
|
103
103
|
},
|
|
104
104
|
dependencies: {
|
|
105
105
|
"@babel/parser": "7.29.9",
|
|
106
|
-
"@brainervirus/workit-
|
|
107
|
-
"@brainervirus/workit-cursor": "^8.12.3",
|
|
108
|
-
"@brainervirus/workit-opencode": "^8.12.3",
|
|
106
|
+
"@brainervirus/workit-cursor": "^8.13.1",
|
|
109
107
|
"@inkjs/ui": "2.0.0",
|
|
110
108
|
ink: "7.1.1",
|
|
111
109
|
react: "19.2.8"
|
|
112
110
|
},
|
|
111
|
+
devDependencies: {
|
|
112
|
+
"@brainervirus/workit-core": "^8.13.1"
|
|
113
|
+
},
|
|
113
114
|
engines: {
|
|
114
115
|
node: ">=24"
|
|
115
116
|
}
|
|
@@ -10429,7 +10430,7 @@ var init_paths = __esm(() => {
|
|
|
10429
10430
|
});
|
|
10430
10431
|
|
|
10431
10432
|
// packages/workit-core/src/host-session.ts
|
|
10432
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
10433
|
+
import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
10433
10434
|
import fs6 from "node:fs";
|
|
10434
10435
|
import path6 from "node:path";
|
|
10435
10436
|
function hostSessionFromEnv(env) {
|
|
@@ -10482,7 +10483,12 @@ function migrateRepoIssued(cwd) {
|
|
|
10482
10483
|
const to = path6.join(userIssuedRoot(), lead);
|
|
10483
10484
|
fs6.mkdirSync(to, { recursive: true, mode: 448 });
|
|
10484
10485
|
for (const id of fs6.readdirSync(from)) {
|
|
10485
|
-
|
|
10486
|
+
try {
|
|
10487
|
+
fs6.copyFileSync(path6.join(from, id), path6.join(to, id), fs6.constants.COPYFILE_EXCL);
|
|
10488
|
+
} catch (error) {
|
|
10489
|
+
if (error.code !== "EEXIST")
|
|
10490
|
+
throw error;
|
|
10491
|
+
}
|
|
10486
10492
|
fs6.rmSync(path6.join(from, id), { force: true });
|
|
10487
10493
|
}
|
|
10488
10494
|
removeIfEmpty(from);
|
|
@@ -10490,7 +10496,25 @@ function migrateRepoIssued(cwd) {
|
|
|
10490
10496
|
}
|
|
10491
10497
|
removeIfEmpty(old);
|
|
10492
10498
|
}
|
|
10493
|
-
function
|
|
10499
|
+
function issuedBinding(cwd, claimed) {
|
|
10500
|
+
const record = issuedRecord(cwd, claimed);
|
|
10501
|
+
const { stored, leadBound } = issuedToken(claimed);
|
|
10502
|
+
if (stored)
|
|
10503
|
+
return "token";
|
|
10504
|
+
if (record === null)
|
|
10505
|
+
return null;
|
|
10506
|
+
return leadBound ? "token" : "legacy";
|
|
10507
|
+
}
|
|
10508
|
+
function issuedTokenMatches(cwd, claimed, token) {
|
|
10509
|
+
migrateRepoIssued(cwd);
|
|
10510
|
+
const { stored } = issuedToken(claimed);
|
|
10511
|
+
if (!stored || !token)
|
|
10512
|
+
return false;
|
|
10513
|
+
const given = Buffer.from(tokenHash(token.trim()));
|
|
10514
|
+
const want = Buffer.from(stored);
|
|
10515
|
+
return given.length === want.length && timingSafeEqual(given, want);
|
|
10516
|
+
}
|
|
10517
|
+
function sessionClaimRefusal(env, claimed, cwd, options = {}) {
|
|
10494
10518
|
const real = realHostSession(env);
|
|
10495
10519
|
const host = family(real.host);
|
|
10496
10520
|
if (real.kind !== "derived" && host !== null && HOST_SET_VARIABLE[host])
|
|
@@ -10500,18 +10524,22 @@ function sessionClaimRefusal(env, claimed, cwd) {
|
|
|
10500
10524
|
};
|
|
10501
10525
|
if (real.kind === "none" || !real.session)
|
|
10502
10526
|
return null;
|
|
10503
|
-
|
|
10504
|
-
if (
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
if (
|
|
10527
|
+
const ownShell = claimed === real.session && !(real.kind === "declared" && claimed.includes(":")) || real.kind === "derived" && claimed.endsWith(`:${real.session}`);
|
|
10528
|
+
if (ownShell)
|
|
10529
|
+
return options.flag ? {
|
|
10530
|
+
error: `session_flag: --session ${claimed} names this shell's own ${real.host} session; on a host only an issued subagent id is passed as --session`,
|
|
10531
|
+
unblock: "drop --session: the shell's own session is already the acting one"
|
|
10532
|
+
} : null;
|
|
10533
|
+
const issuedClaim = claimed === real.session || claimed.startsWith(`${real.session}:`);
|
|
10534
|
+
if (issuedClaim) {
|
|
10535
|
+
const binding = issuedBinding(cwd, claimed);
|
|
10536
|
+
if (binding === "legacy")
|
|
10513
10537
|
return null;
|
|
10514
|
-
|
|
10538
|
+
if (binding === "token")
|
|
10539
|
+
return issuedTokenMatches(cwd, claimed, options.token) ? null : {
|
|
10540
|
+
error: `unbound_session: ${claimed} was issued to a subagent, and this call did not present the token its SubagentStart hook gave it`,
|
|
10541
|
+
unblock: `only that subagent acts as ${claimed}: it passes --token exactly as its SubagentStart context names it (or WORKIT_SESSION_TOKEN); anyone else spawns their own verifier`
|
|
10542
|
+
};
|
|
10515
10543
|
}
|
|
10516
10544
|
const error = `forged_session: ${claimed} is neither this shell's session (${real.session}) nor an id its host issued to a child of it`;
|
|
10517
10545
|
if (issuedClaim && noIssuedRecord(cwd, leadOf(claimed)) && host !== null && PLUGIN_UPDATE[host])
|
|
@@ -10522,10 +10550,10 @@ function sessionClaimRefusal(env, claimed, cwd) {
|
|
|
10522
10550
|
const ownChildShell = host !== null && OWN_CHILD_SHELL.has(host);
|
|
10523
10551
|
return {
|
|
10524
10552
|
error,
|
|
10525
|
-
unblock: ownChildShell ? `record the verdict from the verifier's own ${host === "pi" ? "Pi worker" : "OpenCode child session"} shell, where WORKIT_SESSION_ID is already set; never set WORKIT_SESSION_ID or --session by hand` : "never name a session yourself: spawn a verifier or reviewer subagent and pass exactly the --session its SubagentStart context names, or pass --as verifier"
|
|
10553
|
+
unblock: ownChildShell ? `record the verdict from the verifier's own ${host === "pi" ? "Pi worker" : "OpenCode child session"} shell, where WORKIT_SESSION_ID is already set; never set WORKIT_SESSION_ID or --session by hand` : "never name a session yourself: spawn a verifier or reviewer subagent and pass exactly the --session and --token its SubagentStart context names, or pass --as verifier"
|
|
10526
10554
|
};
|
|
10527
10555
|
}
|
|
10528
|
-
var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, hash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path6.join(resolveStateDir(), "issued"), safeList = (dir) => {
|
|
10556
|
+
var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, hash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path6.join(resolveStateDir(), "issued"), userTokenRoot = () => path6.join(resolveStateDir(), "issued-tokens"), safeList = (dir) => {
|
|
10529
10557
|
try {
|
|
10530
10558
|
return fs6.readdirSync(dir);
|
|
10531
10559
|
} catch {
|
|
@@ -10544,12 +10572,26 @@ var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "")
|
|
|
10544
10572
|
} catch {
|
|
10545
10573
|
return false;
|
|
10546
10574
|
}
|
|
10547
|
-
}, PLUGIN_UPDATE,
|
|
10575
|
+
}, PLUGIN_UPDATE, tokenHash = (token) => createHash3("sha256").update(token).digest("hex"), issuedRecord = (cwd, claimed) => {
|
|
10576
|
+
const cut = claimed.lastIndexOf(":");
|
|
10577
|
+
if (cut <= 0)
|
|
10578
|
+
return null;
|
|
10548
10579
|
migrateRepoIssued(cwd);
|
|
10549
10580
|
try {
|
|
10550
|
-
return fs6.
|
|
10581
|
+
return fs6.readFileSync(path6.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
10551
10582
|
} catch {
|
|
10552
|
-
return
|
|
10583
|
+
return null;
|
|
10584
|
+
}
|
|
10585
|
+
}, TOKEN_HASH, issuedToken = (claimed) => {
|
|
10586
|
+
const cut = claimed.lastIndexOf(":");
|
|
10587
|
+
if (cut <= 0)
|
|
10588
|
+
return { stored: null, leadBound: false };
|
|
10589
|
+
const dir = path6.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
10590
|
+
try {
|
|
10591
|
+
const text = fs6.readFileSync(path6.join(dir, hash(claimed)), "utf8");
|
|
10592
|
+
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
10593
|
+
} catch {
|
|
10594
|
+
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
10553
10595
|
}
|
|
10554
10596
|
}, leadOf = (session) => session.slice(0, session.lastIndexOf(":")), OWN_CHILD_SHELL, HOST_SET_VARIABLE;
|
|
10555
10597
|
var init_host_session = __esm(() => {
|
|
@@ -10567,6 +10609,7 @@ var init_host_session = __esm(() => {
|
|
|
10567
10609
|
claude_code: "claude plugin marketplace update workit && claude plugin update workit@workit",
|
|
10568
10610
|
codex: "codex plugin remove workit@workit && codex plugin add workit@workit"
|
|
10569
10611
|
};
|
|
10612
|
+
TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
10570
10613
|
OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
10571
10614
|
HOST_SET_VARIABLE = {
|
|
10572
10615
|
opencode: "OPENCODE_SESSION_ID",
|
|
@@ -10599,7 +10642,7 @@ import { spawnSync as spawnSync3 } from "node:child_process";
|
|
|
10599
10642
|
import fs7 from "node:fs";
|
|
10600
10643
|
import { hostname } from "node:os";
|
|
10601
10644
|
import path8 from "node:path";
|
|
10602
|
-
var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, RECLAIM_GUARD_TTL_MS = 30000,
|
|
10645
|
+
var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, RECLAIM_GUARD_TTL_MS = 30000, IN_PROCESS_LOCK_TIMEOUT_MS = 250, defaultLockTimeoutMs, defaultLockTimeout = () => defaultLockTimeoutMs, hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS, setDefaultLockTimeout = (ms) => {
|
|
10603
10646
|
defaultLockTimeoutMs = ms;
|
|
10604
10647
|
}, parseMetadataLock = (raw) => {
|
|
10605
10648
|
let value;
|
|
@@ -10789,6 +10832,7 @@ var init_store_lock = __esm(() => {
|
|
|
10789
10832
|
externalAction: literal(true).optional()
|
|
10790
10833
|
}).strict();
|
|
10791
10834
|
FOREIGN_LOCK_TTL_MS = 10 * 60000;
|
|
10835
|
+
defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
|
|
10792
10836
|
TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
|
|
10793
10837
|
});
|
|
10794
10838
|
|
|
@@ -13697,6 +13741,14 @@ var PREFIXES, branchNames = (workspaceRoot) => {
|
|
|
13697
13741
|
}, repoBranchPreset = (workspaceRoot) => {
|
|
13698
13742
|
const detected = detectBranchPolicy(workspaceRoot);
|
|
13699
13743
|
return detected.developBranch || detected.allowed.length ? detected.preset : null;
|
|
13744
|
+
}, releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
|
|
13745
|
+
if (typeof policyPrefix === "string")
|
|
13746
|
+
return policyPrefix.trim();
|
|
13747
|
+
const r = spawnSync5("git", ["config", "--get", "gitflow.prefix.versiontag"], {
|
|
13748
|
+
cwd: workspaceRoot,
|
|
13749
|
+
encoding: "utf8"
|
|
13750
|
+
});
|
|
13751
|
+
return r.status === 0 ? (r.stdout ?? "").trim() : "v";
|
|
13700
13752
|
};
|
|
13701
13753
|
var init_branch_policy = __esm(() => {
|
|
13702
13754
|
init_config();
|
|
@@ -14781,7 +14833,7 @@ var init_vcs_config = __esm(() => {
|
|
|
14781
14833
|
|
|
14782
14834
|
// packages/workit-core/src/ledger.ts
|
|
14783
14835
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
14784
|
-
import { createHash as createHash9, randomBytes as
|
|
14836
|
+
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
14785
14837
|
import fs12 from "node:fs";
|
|
14786
14838
|
import path15 from "node:path";
|
|
14787
14839
|
function resolveCommit(cwd, ref) {
|
|
@@ -14870,7 +14922,7 @@ function actorFromEnv(env) {
|
|
|
14870
14922
|
agentId: value("WORKIT_AGENT_ID")
|
|
14871
14923
|
};
|
|
14872
14924
|
}
|
|
14873
|
-
function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
14925
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
14874
14926
|
const lock = `${file}.lock`;
|
|
14875
14927
|
const deadline = Date.now() + waitMs;
|
|
14876
14928
|
let token = null;
|
|
@@ -14894,8 +14946,10 @@ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
|
14894
14946
|
}
|
|
14895
14947
|
function appendLine(file, build, options) {
|
|
14896
14948
|
const write = () => {
|
|
14897
|
-
const fd = fs12.openSync(file, "a+");
|
|
14949
|
+
const fd = fs12.openSync(file, "a+", 384);
|
|
14898
14950
|
try {
|
|
14951
|
+
if (process.platform !== "win32")
|
|
14952
|
+
tighten(fd);
|
|
14899
14953
|
const size = fs12.fstatSync(fd).size;
|
|
14900
14954
|
let fence = "";
|
|
14901
14955
|
if (size > 0) {
|
|
@@ -14974,7 +15028,9 @@ function readLedger(cwd, options = {}) {
|
|
|
14974
15028
|
if (!file.ok)
|
|
14975
15029
|
return file;
|
|
14976
15030
|
let text = "";
|
|
15031
|
+
let stat = null;
|
|
14977
15032
|
try {
|
|
15033
|
+
stat = fs12.statSync(file.value);
|
|
14978
15034
|
text = fs12.readFileSync(file.value, "utf8");
|
|
14979
15035
|
} catch (error) {
|
|
14980
15036
|
if (error.code === "ENOENT")
|
|
@@ -14994,6 +15050,7 @@ function readLedger(cwd, options = {}) {
|
|
|
14994
15050
|
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
14995
15051
|
const earlier = new Set([null]);
|
|
14996
15052
|
let chainStarted = false;
|
|
15053
|
+
const ids = new Set;
|
|
14997
15054
|
for (const raw of text.split(`
|
|
14998
15055
|
`)) {
|
|
14999
15056
|
const trimmed = raw.trim();
|
|
@@ -15027,6 +15084,10 @@ function readLedger(cwd, options = {}) {
|
|
|
15027
15084
|
reason = "unsigned";
|
|
15028
15085
|
else
|
|
15029
15086
|
rowIntegrity = "legacy";
|
|
15087
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
15088
|
+
reason = "duplicate_id";
|
|
15089
|
+
if (options.integrity)
|
|
15090
|
+
ids.add(parsed.id);
|
|
15030
15091
|
if (reason)
|
|
15031
15092
|
rowIntegrity = "unverified";
|
|
15032
15093
|
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
@@ -15050,7 +15111,8 @@ function readLedger(cwd, options = {}) {
|
|
|
15050
15111
|
seq: rows.length + 1,
|
|
15051
15112
|
superseded: false,
|
|
15052
15113
|
supersedeIgnored: false,
|
|
15053
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {}
|
|
15114
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
15115
|
+
...reason ? { integrityReason: reason } : {}
|
|
15054
15116
|
};
|
|
15055
15117
|
rows.push(row);
|
|
15056
15118
|
if (!options.integrity)
|
|
@@ -15078,18 +15140,44 @@ function readLedger(cwd, options = {}) {
|
|
|
15078
15140
|
const byId = new Map;
|
|
15079
15141
|
for (const row of rows) {
|
|
15080
15142
|
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
15081
|
-
if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
15143
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
15082
15144
|
target.superseded = true;
|
|
15083
15145
|
else if (row.supersedes)
|
|
15084
15146
|
row.supersedeIgnored = true;
|
|
15085
15147
|
if (!byId.has(row.id))
|
|
15086
15148
|
byId.set(row.id, row);
|
|
15087
15149
|
}
|
|
15150
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
15151
|
+
tamperCache.set(file.value, {
|
|
15152
|
+
size: stat.size,
|
|
15153
|
+
stamp: fileStamp(stat),
|
|
15154
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
15155
|
+
});
|
|
15088
15156
|
return {
|
|
15089
15157
|
ok: true,
|
|
15090
15158
|
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
15091
15159
|
};
|
|
15092
15160
|
}
|
|
15161
|
+
function ledgerTampering(cwd) {
|
|
15162
|
+
const file = ledgerPath(cwd);
|
|
15163
|
+
if (!file.ok)
|
|
15164
|
+
return [];
|
|
15165
|
+
try {
|
|
15166
|
+
const stat = fs12.statSync(file.value);
|
|
15167
|
+
const cached = tamperCache.get(file.value);
|
|
15168
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
15169
|
+
return cached.breaks;
|
|
15170
|
+
} catch {
|
|
15171
|
+
return [];
|
|
15172
|
+
}
|
|
15173
|
+
const read = readLedger(cwd, { integrity: true });
|
|
15174
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
15175
|
+
}
|
|
15176
|
+
function tamperedUnblock(cwd) {
|
|
15177
|
+
const file = ledgerPath(cwd);
|
|
15178
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
15179
|
+
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
15180
|
+
}
|
|
15093
15181
|
function filterRows(rows, filter) {
|
|
15094
15182
|
const matched = rows.filter((row) => (filter.branch === undefined || row.branch === filter.branch) && (filter.pr === undefined || row.pr === filter.pr) && (filter.type === undefined || row.type === filter.type));
|
|
15095
15183
|
return filter.last === undefined ? matched : matched.slice(Math.max(0, matched.length - filter.last));
|
|
@@ -15205,15 +15293,51 @@ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, s
|
|
|
15205
15293
|
sessions.add(session);
|
|
15206
15294
|
return sessions;
|
|
15207
15295
|
}
|
|
15208
|
-
function
|
|
15296
|
+
function sessionTrust(cwd, row) {
|
|
15297
|
+
const session = row.actor.session;
|
|
15298
|
+
if (!session || row.self === true)
|
|
15299
|
+
return null;
|
|
15300
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
15301
|
+
const host = row.actor.host;
|
|
15302
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
15303
|
+
if (!issuedOnly)
|
|
15304
|
+
return null;
|
|
15305
|
+
const binding = issuedBinding(cwd, session);
|
|
15306
|
+
if (binding === null)
|
|
15307
|
+
return "unissued_session";
|
|
15308
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
15309
|
+
}
|
|
15310
|
+
function trustReasons(cwd, row) {
|
|
15311
|
+
if (row.integrity === "unverified")
|
|
15312
|
+
return [row.integrityReason ?? "unsigned"];
|
|
15313
|
+
if (row.integrity !== "verified")
|
|
15314
|
+
return ["unsigned"];
|
|
15315
|
+
const session = sessionTrust(cwd, row);
|
|
15316
|
+
return session ? [session] : [];
|
|
15317
|
+
}
|
|
15318
|
+
function withIntegrity(cwd, rows) {
|
|
15319
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
15320
|
+
return rows;
|
|
15321
|
+
const read = readLedger(cwd, { integrity: true });
|
|
15322
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
15323
|
+
return rows.map((row) => {
|
|
15324
|
+
if (row.integrity !== undefined)
|
|
15325
|
+
return row;
|
|
15326
|
+
const fresh = bySeq.get(row.seq);
|
|
15327
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
15328
|
+
});
|
|
15329
|
+
}
|
|
15330
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
15209
15331
|
let effective = null;
|
|
15210
15332
|
for (const row of rows) {
|
|
15211
15333
|
if (!effective) {
|
|
15212
15334
|
effective = row;
|
|
15213
15335
|
continue;
|
|
15214
15336
|
}
|
|
15215
|
-
|
|
15216
|
-
|
|
15337
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
15338
|
+
continue;
|
|
15339
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
15340
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
15217
15341
|
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
15218
15342
|
continue;
|
|
15219
15343
|
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
@@ -15227,7 +15351,7 @@ function restackCarries(rows, branch, from, to) {
|
|
|
15227
15351
|
return false;
|
|
15228
15352
|
const edges = new Map;
|
|
15229
15353
|
for (const row of rows)
|
|
15230
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
15354
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
15231
15355
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
15232
15356
|
const seen = new Set([from]);
|
|
15233
15357
|
const queue = [from];
|
|
@@ -15243,7 +15367,8 @@ function restackCarries(rows, branch, from, to) {
|
|
|
15243
15367
|
}
|
|
15244
15368
|
return false;
|
|
15245
15369
|
}
|
|
15246
|
-
function checkVerdicts(cwd, branch,
|
|
15370
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
15371
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
15247
15372
|
const head = branchHead(cwd, branch);
|
|
15248
15373
|
const fallbackBase = defaultBase(cwd);
|
|
15249
15374
|
const scope = branchScope(cwd, branch);
|
|
@@ -15273,21 +15398,47 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
15273
15398
|
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
15274
15399
|
};
|
|
15275
15400
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
15401
|
+
const failureAuthorCache = new Map;
|
|
15402
|
+
const failureAuthors = (row) => {
|
|
15403
|
+
let set = failureAuthorCache.get(row.seq);
|
|
15404
|
+
if (!set) {
|
|
15405
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
15406
|
+
failureAuthorCache.set(row.seq, set);
|
|
15407
|
+
}
|
|
15408
|
+
return set;
|
|
15409
|
+
};
|
|
15410
|
+
const reasonCache = new Map;
|
|
15411
|
+
const reasonsOf = (row) => {
|
|
15412
|
+
let reasons = reasonCache.get(row);
|
|
15413
|
+
if (!reasons) {
|
|
15414
|
+
const failing = FAILING.has(String(row.result));
|
|
15415
|
+
reasons = [
|
|
15416
|
+
...trustReasons(cwd, row),
|
|
15417
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
15418
|
+
];
|
|
15419
|
+
reasonCache.set(row, reasons);
|
|
15420
|
+
}
|
|
15421
|
+
return reasons;
|
|
15422
|
+
};
|
|
15423
|
+
const retired = new Set;
|
|
15424
|
+
for (const row of rows)
|
|
15425
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
15426
|
+
retired.add(row.supersedes);
|
|
15276
15427
|
const byKind = new Map;
|
|
15277
15428
|
for (const row of rows)
|
|
15278
|
-
if (isLiveVerdict(row) && scope(row)) {
|
|
15429
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
15279
15430
|
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
15280
15431
|
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
15281
15432
|
}
|
|
15282
15433
|
const verdicts = [];
|
|
15283
15434
|
const warnings = [];
|
|
15284
15435
|
for (const [kind, list] of byKind) {
|
|
15285
|
-
const row = effectiveOf(list,
|
|
15436
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
15286
15437
|
if (!row)
|
|
15287
15438
|
continue;
|
|
15288
15439
|
const basis = basisOf(row);
|
|
15289
15440
|
const current = basis !== "stale";
|
|
15290
|
-
const independence =
|
|
15441
|
+
const independence = reasonsOf(row);
|
|
15291
15442
|
const reasons = [
|
|
15292
15443
|
...current ? [] : ["stale"],
|
|
15293
15444
|
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
@@ -15304,23 +15455,31 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
15304
15455
|
});
|
|
15305
15456
|
if (reasons.length === 0) {
|
|
15306
15457
|
for (const later of list)
|
|
15307
|
-
if (later.seq > row.seq &&
|
|
15458
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
15308
15459
|
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
15309
15460
|
}
|
|
15310
15461
|
}
|
|
15311
15462
|
for (const row of rows)
|
|
15312
15463
|
if (row.integrity === "unverified" && scope(row))
|
|
15313
15464
|
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
15465
|
+
const tampered = ledgerTampering(cwd);
|
|
15466
|
+
if (tampered.length) {
|
|
15467
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
15468
|
+
for (const entry of verdicts) {
|
|
15469
|
+
entry.accepted = false;
|
|
15470
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
15471
|
+
}
|
|
15472
|
+
}
|
|
15314
15473
|
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
15315
15474
|
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
15316
15475
|
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
15317
15476
|
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
15318
|
-
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
15477
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
15319
15478
|
accepted: false,
|
|
15320
15479
|
verdict: failing?.verdict ?? null,
|
|
15321
15480
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
15322
15481
|
};
|
|
15323
|
-
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
15482
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
15324
15483
|
return {
|
|
15325
15484
|
branch,
|
|
15326
15485
|
head,
|
|
@@ -15515,6 +15674,7 @@ function recordVerdict(context, input) {
|
|
|
15515
15674
|
self: self2,
|
|
15516
15675
|
selfReason,
|
|
15517
15676
|
sessionSource: context.sessionSource ?? "env",
|
|
15677
|
+
...context.sessionBound === true ? { sessionBound: true } : {},
|
|
15518
15678
|
evidenceRefs: input.evidenceRefs ?? []
|
|
15519
15679
|
}, { now: context.now });
|
|
15520
15680
|
}
|
|
@@ -15743,7 +15903,7 @@ var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, VERDICT_RESULTS, PASSING, FAILING
|
|
|
15743
15903
|
} catch {
|
|
15744
15904
|
return null;
|
|
15745
15905
|
}
|
|
15746
|
-
}, newId2 = () => `${Date.now().toString(36)}-${
|
|
15906
|
+
}, newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
|
|
15747
15907
|
if (process.platform !== "linux")
|
|
15748
15908
|
return false;
|
|
15749
15909
|
try {
|
|
@@ -15796,6 +15956,11 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
|
|
|
15796
15956
|
return text || null;
|
|
15797
15957
|
}
|
|
15798
15958
|
return null;
|
|
15959
|
+
}, tighten = (fd) => {
|
|
15960
|
+
try {
|
|
15961
|
+
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
15962
|
+
fs12.fchmodSync(fd, 384);
|
|
15963
|
+
} catch {}
|
|
15799
15964
|
}, sealed = (row, id, at, previous) => {
|
|
15800
15965
|
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
15801
15966
|
const prevHash = previous === null ? null : sha2562(previous);
|
|
@@ -15810,7 +15975,7 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
|
|
|
15810
15975
|
session: str2(actor.session),
|
|
15811
15976
|
agentId: str2(actor.agentId)
|
|
15812
15977
|
};
|
|
15813
|
-
}, isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.
|
|
15978
|
+
}, TAMPER_REASONS, tamperCache, fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`, tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`, isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id), RENAMED, renameAwareBranch = (scopeOf, branch) => {
|
|
15814
15979
|
return (target) => {
|
|
15815
15980
|
const own = typeof branch === "string" ? branch : null;
|
|
15816
15981
|
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
@@ -15827,7 +15992,7 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
|
|
|
15827
15992
|
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
15828
15993
|
return true;
|
|
15829
15994
|
return false;
|
|
15830
|
-
}, independenceReasons = (row, authors) => {
|
|
15995
|
+
}, TRUST_REASONS, SHELL_VOUCHED_HOSTS, untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason)), independenceReasons = (row, authors) => {
|
|
15831
15996
|
const reasons = [];
|
|
15832
15997
|
if (!row.actor.session)
|
|
15833
15998
|
reasons.push("no_session");
|
|
@@ -15889,6 +16054,7 @@ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
|
|
|
15889
16054
|
return brief.pr === null ? "workit pr create" : `workit pr status --pr ${brief.pr}`;
|
|
15890
16055
|
};
|
|
15891
16056
|
var init_ledger = __esm(() => {
|
|
16057
|
+
init_store_lock();
|
|
15892
16058
|
init_vcs_config();
|
|
15893
16059
|
init_rev();
|
|
15894
16060
|
init_host_session();
|
|
@@ -15927,14 +16093,14 @@ var init_ledger = __esm(() => {
|
|
|
15927
16093
|
return null;
|
|
15928
16094
|
throw error;
|
|
15929
16095
|
}
|
|
15930
|
-
const token = `${process.pid}-${
|
|
16096
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
15931
16097
|
fs12.writeFileSync(path15.join(lock, LOCK_OWNER), token);
|
|
15932
16098
|
return token;
|
|
15933
16099
|
},
|
|
15934
16100
|
release(lock, token) {
|
|
15935
16101
|
if (lockToken(lock) !== token)
|
|
15936
16102
|
return false;
|
|
15937
|
-
const tomb = `${lock}.release-${
|
|
16103
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
15938
16104
|
try {
|
|
15939
16105
|
fs12.renameSync(lock, tomb);
|
|
15940
16106
|
} catch {
|
|
@@ -15951,7 +16117,7 @@ var init_ledger = __esm(() => {
|
|
|
15951
16117
|
},
|
|
15952
16118
|
token: lockToken,
|
|
15953
16119
|
reap(lock, observed) {
|
|
15954
|
-
const tomb = `${lock}.reap-${
|
|
16120
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
15955
16121
|
try {
|
|
15956
16122
|
fs12.renameSync(lock, tomb);
|
|
15957
16123
|
} catch {
|
|
@@ -15970,7 +16136,24 @@ var init_ledger = __esm(() => {
|
|
|
15970
16136
|
}
|
|
15971
16137
|
};
|
|
15972
16138
|
TAIL_BYTES = 64 * 1024;
|
|
16139
|
+
TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
16140
|
+
tamperCache = new Map;
|
|
15973
16141
|
RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
16142
|
+
TRUST_REASONS = new Set([
|
|
16143
|
+
"unsigned",
|
|
16144
|
+
"hash_mismatch",
|
|
16145
|
+
"broken_link",
|
|
16146
|
+
"duplicate_id",
|
|
16147
|
+
"unissued_session",
|
|
16148
|
+
"unbound_session"
|
|
16149
|
+
]);
|
|
16150
|
+
SHELL_VOUCHED_HOSTS = new Set([
|
|
16151
|
+
"cli",
|
|
16152
|
+
"opencode",
|
|
16153
|
+
"pi",
|
|
16154
|
+
"codex_cli",
|
|
16155
|
+
"codex_desktop"
|
|
16156
|
+
]);
|
|
15974
16157
|
OBSERVED_ONLY = new Set([
|
|
15975
16158
|
"check",
|
|
15976
16159
|
"merge.unverified",
|
|
@@ -17311,6 +17494,7 @@ var init_shell_words = () => {};
|
|
|
17311
17494
|
var SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, XARGS_VALUES, PARALLEL_VALUES, RECENT, LAUNCHERS, PUSH_VALUE_OPTIONS, VALUE_OPTIONS, API_VALUES, API_FIELDS, GIT_COMMANDS;
|
|
17312
17495
|
var init_raw_git = __esm(() => {
|
|
17313
17496
|
init_branch();
|
|
17497
|
+
init_branch_policy();
|
|
17314
17498
|
init_workspaces();
|
|
17315
17499
|
init_ledger();
|
|
17316
17500
|
init_paths();
|
|
@@ -63704,7 +63888,8 @@ var init_changelog = __esm(() => {
|
|
|
63704
63888
|
});
|
|
63705
63889
|
|
|
63706
63890
|
// packages/workit-core/src/core/hygiene.ts
|
|
63707
|
-
import {
|
|
63891
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
63892
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
63708
63893
|
import path26 from "node:path";
|
|
63709
63894
|
var repoRoot2, templatesDir = () => path26.join(repoRoot2, "templates", "hygiene"), packageJson = (root) => {
|
|
63710
63895
|
if (!existsSync9(path26.join(root, "package.json")))
|
|
@@ -63734,6 +63919,23 @@ var repoRoot2, templatesDir = () => path26.join(repoRoot2, "templates", "hygiene
|
|
|
63734
63919
|
}
|
|
63735
63920
|
}
|
|
63736
63921
|
return "";
|
|
63922
|
+
}, projectTarget = (dir) => {
|
|
63923
|
+
const start = path26.resolve(dir);
|
|
63924
|
+
const top = spawnSync12("git", ["rev-parse", "--show-toplevel"], { cwd: start, encoding: "utf8" });
|
|
63925
|
+
const root = top.status === 0 ? (top.stdout ?? "").trim() : "";
|
|
63926
|
+
if (root)
|
|
63927
|
+
return { ok: true, root: path26.resolve(root) };
|
|
63928
|
+
let children = [];
|
|
63929
|
+
try {
|
|
63930
|
+
children = readdirSync5(start, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync9(path26.join(start, entry.name, ".git"))).map((entry) => path26.join(start, entry.name)).toSorted();
|
|
63931
|
+
} catch {
|
|
63932
|
+
children = [];
|
|
63933
|
+
}
|
|
63934
|
+
const where = children.length > 0 ? `run it inside one of its repositories: ${children.slice(0, 5).map((child) => `cd ${child}`).join(", ")}${children.length > 5 ? ` (+${children.length - 5} more)` : ""}` : "cd <repo> (or git init here) and run workit init again";
|
|
63935
|
+
return {
|
|
63936
|
+
ok: false,
|
|
63937
|
+
refused: `${start} is not a git repository, so no project files were added there; ${where}`
|
|
63938
|
+
};
|
|
63737
63939
|
}, planHygieneFiles = (root, opts = {}) => {
|
|
63738
63940
|
const openSource = opts.includeOpenSource ?? isOpenSource(root);
|
|
63739
63941
|
const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"];
|
|
@@ -63958,13 +64160,13 @@ var init_support_matrix = __esm(() => {
|
|
|
63958
64160
|
});
|
|
63959
64161
|
|
|
63960
64162
|
// packages/workit-cli/src/admin/host-install.ts
|
|
63961
|
-
import { spawnSync as
|
|
64163
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
63962
64164
|
import {
|
|
63963
64165
|
accessSync,
|
|
63964
64166
|
constants as constants2,
|
|
63965
64167
|
existsSync as existsSync11,
|
|
63966
64168
|
readFileSync as readFileSync11,
|
|
63967
|
-
readdirSync as
|
|
64169
|
+
readdirSync as readdirSync6,
|
|
63968
64170
|
realpathSync as realpathSync6,
|
|
63969
64171
|
statSync as statSync2
|
|
63970
64172
|
} from "node:fs";
|
|
@@ -64007,7 +64209,7 @@ function findHostExecutable(name, options = {}) {
|
|
|
64007
64209
|
[path29.join(home, ".nvm", "versions", "node"), "bin"]
|
|
64008
64210
|
]) {
|
|
64009
64211
|
try {
|
|
64010
|
-
for (const entry of
|
|
64212
|
+
for (const entry of readdirSync6(base, { withFileTypes: true })) {
|
|
64011
64213
|
if (entry.isDirectory())
|
|
64012
64214
|
dirs.push(path29.join(base, entry.name, tail));
|
|
64013
64215
|
}
|
|
@@ -64229,7 +64431,7 @@ function runHostCommand(step, context = {}) {
|
|
|
64229
64431
|
stderr: `Cannot safely execute batch shim ${step.command}; resolve its Node.js CLI entrypoint and retry`
|
|
64230
64432
|
};
|
|
64231
64433
|
}
|
|
64232
|
-
const result =
|
|
64434
|
+
const result = spawnSync13(step.command, step.args, {
|
|
64233
64435
|
cwd: step.cwd,
|
|
64234
64436
|
env: (() => {
|
|
64235
64437
|
const home = context.home ?? context.env?.HOME ?? os7.homedir();
|
|
@@ -64283,6 +64485,40 @@ var resolveWindowsShimScript = (shim) => {
|
|
|
64283
64485
|
} catch {
|
|
64284
64486
|
return false;
|
|
64285
64487
|
}
|
|
64488
|
+
}, OPENCODE_PLUGIN_SCOPE = "@brainervirus", OPENCODE_PLUGIN_NAME = "workit-opencode", packageVersionAt = (root, name) => {
|
|
64489
|
+
try {
|
|
64490
|
+
const pkg = JSON.parse(readFileSync11(path29.join(root, "package.json"), "utf8"));
|
|
64491
|
+
return pkg.name === name && typeof pkg.version === "string" ? pkg.version : null;
|
|
64492
|
+
} catch {
|
|
64493
|
+
return null;
|
|
64494
|
+
}
|
|
64495
|
+
}, compareVersions = (a, b) => {
|
|
64496
|
+
const pa = a.split(/[.+-]/).map((n) => Number(n) || 0);
|
|
64497
|
+
const pb = b.split(/[.+-]/).map((n) => Number(n) || 0);
|
|
64498
|
+
for (let i = 0;i < Math.max(pa.length, pb.length); i++) {
|
|
64499
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
64500
|
+
if (d !== 0)
|
|
64501
|
+
return d;
|
|
64502
|
+
}
|
|
64503
|
+
return 0;
|
|
64504
|
+
}, openCodeCacheDir = (home, env = process.env) => path29.join(env.XDG_CACHE_HOME || path29.join(home, ".cache"), "opencode"), subdirs = (dir) => {
|
|
64505
|
+
try {
|
|
64506
|
+
return readdirSync6(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path29.join(dir, entry.name));
|
|
64507
|
+
} catch {
|
|
64508
|
+
return [];
|
|
64509
|
+
}
|
|
64510
|
+
}, openCodeInstalledPlugin = (home, env = process.env) => {
|
|
64511
|
+
const name = `${OPENCODE_PLUGIN_SCOPE}/${OPENCODE_PLUGIN_NAME}`;
|
|
64512
|
+
const cache = openCodeCacheDir(home, env);
|
|
64513
|
+
const specDirs = (layout) => subdirs(path29.join(cache, layout, OPENCODE_PLUGIN_SCOPE)).filter((dir) => path29.basename(dir) === `${OPENCODE_PLUGIN_NAME}@latest`);
|
|
64514
|
+
const installs = [...specDirs("npm").flatMap(subdirs), ...specDirs("packages")].map((dir) => path29.join(dir, "node_modules", OPENCODE_PLUGIN_SCOPE, OPENCODE_PLUGIN_NAME));
|
|
64515
|
+
let best = null;
|
|
64516
|
+
for (const root of installs) {
|
|
64517
|
+
const version = packageVersionAt(root, name);
|
|
64518
|
+
if (version && (!best || compareVersions(version, best.version) > 0))
|
|
64519
|
+
best = { root, version };
|
|
64520
|
+
}
|
|
64521
|
+
return best;
|
|
64286
64522
|
}, isCanonicalCodexPackage = (root) => {
|
|
64287
64523
|
for (const file of [
|
|
64288
64524
|
path29.join(root, ".codex-plugin", "plugin.json"),
|
|
@@ -64302,7 +64538,7 @@ var resolveWindowsShimScript = (shim) => {
|
|
|
64302
64538
|
if (isCanonicalCodexPackage(root))
|
|
64303
64539
|
return true;
|
|
64304
64540
|
try {
|
|
64305
|
-
return
|
|
64541
|
+
return readdirSync6(root, { withFileTypes: true }).some((entry) => entry.isDirectory() && isCanonicalCodexPackage(path29.join(root, entry.name)));
|
|
64306
64542
|
} catch {
|
|
64307
64543
|
return false;
|
|
64308
64544
|
}
|
|
@@ -64311,7 +64547,7 @@ var resolveWindowsShimScript = (shim) => {
|
|
|
64311
64547
|
return true;
|
|
64312
64548
|
const root = path29.join(codexHome, "plugins", "cache");
|
|
64313
64549
|
try {
|
|
64314
|
-
for (const marketplace of
|
|
64550
|
+
for (const marketplace of readdirSync6(root, { withFileTypes: true })) {
|
|
64315
64551
|
if (!marketplace.isDirectory())
|
|
64316
64552
|
continue;
|
|
64317
64553
|
if (isPackageOrVersionDirectory(path29.join(root, marketplace.name, "workit")))
|
|
@@ -64389,9 +64625,9 @@ var init_host_install = __esm(() => {
|
|
|
64389
64625
|
});
|
|
64390
64626
|
|
|
64391
64627
|
// packages/workit-core/src/core/skill-manifests.ts
|
|
64392
|
-
import { cpSync as cpSync3, existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as
|
|
64628
|
+
import { cpSync as cpSync3, existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
64393
64629
|
import path30 from "node:path";
|
|
64394
|
-
var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ?
|
|
64630
|
+
var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? readdirSync7(root).filter((name) => existsSync12(path30.join(root, name, "SKILL.md"))).toSorted() : [], validateSkillManifests = (root, expected, label) => {
|
|
64395
64631
|
const actual = skillManifestNames(root);
|
|
64396
64632
|
const missing = expected.filter((name) => !actual.includes(name));
|
|
64397
64633
|
const extra = actual.filter((name) => !expected.includes(name));
|
|
@@ -64403,7 +64639,7 @@ var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? re
|
|
|
64403
64639
|
const pending = [path30.join(pluginDir, "skills")];
|
|
64404
64640
|
while (pending.length > 0) {
|
|
64405
64641
|
const dir = pending.pop();
|
|
64406
|
-
for (const entry of
|
|
64642
|
+
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
64407
64643
|
const file = path30.join(dir, entry.name);
|
|
64408
64644
|
if (entry.isDirectory())
|
|
64409
64645
|
pending.push(file);
|
|
@@ -64434,7 +64670,7 @@ var init_skill_manifests = __esm(() => {
|
|
|
64434
64670
|
});
|
|
64435
64671
|
|
|
64436
64672
|
// packages/workit-core/src/git/session-hook.ts
|
|
64437
|
-
import { spawnSync as
|
|
64673
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
64438
64674
|
import {
|
|
64439
64675
|
chmodSync,
|
|
64440
64676
|
existsSync as existsSync13,
|
|
@@ -64629,7 +64865,7 @@ add_trailer "$(git rev-parse --git-path COMMIT_EDITMSG)"
|
|
|
64629
64865
|
[ "$2" != merge ] || add_trailer "$(git rev-parse --git-path MERGE_MSG)"
|
|
64630
64866
|
exit 0
|
|
64631
64867
|
`, manualTrailerLine = () => `h="$(git rev-parse --git-common-dir 2>/dev/null)/${HELPER.join("/")}"; [ ! -f "$h" ] || sh "$h" "$@" || true`, lefthookTrailerRun = () => `set -f; IFS=; h=$(git rev-parse --git-common-dir 2>/dev/null)/${HELPER.join("/")}; [ ! -f $h ] || sh $h '' {2} {3} || true`, HELPER_REF, SESSION_HOOK_SCRIPT, run2 = (cwd, args, env) => {
|
|
64632
|
-
const r =
|
|
64868
|
+
const r = spawnSync14("git", args, {
|
|
64633
64869
|
cwd,
|
|
64634
64870
|
encoding: "utf8",
|
|
64635
64871
|
timeout: GIT_TIMEOUTS.local,
|
|
@@ -64790,14 +65026,14 @@ ${manualTrailerLine()}
|
|
|
64790
65026
|
});
|
|
64791
65027
|
|
|
64792
65028
|
// packages/workit-cli/src/admin/doctor.ts
|
|
64793
|
-
import { spawnSync as
|
|
65029
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
64794
65030
|
import { createHash as createHash14 } from "node:crypto";
|
|
64795
65031
|
import {
|
|
64796
65032
|
existsSync as existsSync14,
|
|
64797
65033
|
lstatSync as lstatSync5,
|
|
64798
65034
|
mkdirSync as mkdirSync6,
|
|
64799
65035
|
mkdtempSync,
|
|
64800
|
-
readdirSync as
|
|
65036
|
+
readdirSync as readdirSync8,
|
|
64801
65037
|
readFileSync as readFileSync14,
|
|
64802
65038
|
readlinkSync as readlinkSync2,
|
|
64803
65039
|
rmSync as rmSync3,
|
|
@@ -64823,7 +65059,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
64823
65059
|
const configDir = options.configDir ?? env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path32.join(home, ".config", "workit");
|
|
64824
65060
|
const stateDir = options.stateDir ?? env.WORKFLOW_TOOLKIT_STATE ?? path32.join(home, ".local", "state", "workit");
|
|
64825
65061
|
const cwd = options.cwd ?? process.cwd();
|
|
64826
|
-
const dev = options.dev
|
|
65062
|
+
const dev = options.dev?.trim() || env.WORKFLOW_TOOLKIT_DEV?.trim() || findDevFromCwd(cwd);
|
|
64827
65063
|
return {
|
|
64828
65064
|
host: options.host ?? "cli",
|
|
64829
65065
|
home,
|
|
@@ -64833,7 +65069,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
64833
65069
|
workspaceRoot: options.workspaceRoot ?? env.WORKFLOW_WORKSPACE_ROOT ?? cwd,
|
|
64834
65070
|
dev,
|
|
64835
65071
|
opencodeConfig: options.opencodeConfig ?? path32.join(home, ".config", "opencode", "opencode.json"),
|
|
64836
|
-
opencodePackageCacheDir: options.opencodePackageCacheDir ??
|
|
65072
|
+
opencodePackageCacheDir: options.opencodePackageCacheDir ?? null,
|
|
64837
65073
|
cursorSettings: options.cursorSettings ?? path32.join(home, ".cursor", "settings.json"),
|
|
64838
65074
|
cursorMcp: options.cursorMcp ?? path32.join(home, ".cursor", "mcp.json"),
|
|
64839
65075
|
cursorPluginDir: options.cursorPluginDir ?? path32.join(home, ".cursor", "plugins", "local", "workit"),
|
|
@@ -64879,7 +65115,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
64879
65115
|
}
|
|
64880
65116
|
return null;
|
|
64881
65117
|
}, versionOf = (bin, env, timeout) => {
|
|
64882
|
-
const r =
|
|
65118
|
+
const r = spawnSync15(bin, ["--version"], { encoding: "utf8", env, timeout });
|
|
64883
65119
|
if (r.error)
|
|
64884
65120
|
return null;
|
|
64885
65121
|
return (r.stdout ?? "").trim();
|
|
@@ -64897,7 +65133,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
64897
65133
|
if (env.BUN && existsSync14(env.BUN))
|
|
64898
65134
|
return env.BUN;
|
|
64899
65135
|
for (const candidate of [
|
|
64900
|
-
path32.join(os8.homedir(), ".bun/bin/bun"),
|
|
65136
|
+
path32.join(env.HOME || os8.homedir(), ".bun/bin/bun"),
|
|
64901
65137
|
"/usr/local/bin/bun",
|
|
64902
65138
|
"/usr/bin/bun"
|
|
64903
65139
|
]) {
|
|
@@ -64906,47 +65142,50 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
64906
65142
|
}
|
|
64907
65143
|
return commandOnPath("bun", env) ? "bun" : null;
|
|
64908
65144
|
}, checkRuntime = (res) => {
|
|
64909
|
-
const
|
|
64910
|
-
const
|
|
65145
|
+
const running = `v${process.versions.node}`;
|
|
65146
|
+
const runningOk = semverAtLeast(running, SUPPORT_MATRIX.node.minimum);
|
|
65147
|
+
const pathNode = versionOf("node", res.env);
|
|
64911
65148
|
const bun = resolveBun(res.env);
|
|
64912
65149
|
const bunVersion = bun ? versionOf(bun, res.env) : null;
|
|
64913
65150
|
const bunOk = bunVersion !== null;
|
|
64914
65151
|
const needsBun = res.dev !== null;
|
|
64915
|
-
if (!
|
|
65152
|
+
if (!runningOk) {
|
|
64916
65153
|
return {
|
|
64917
65154
|
id: "runtime",
|
|
64918
65155
|
status: "fail",
|
|
64919
|
-
detail: `node ${SUPPORT_MATRIX.node.minimum}+ required (
|
|
65156
|
+
detail: `node ${SUPPORT_MATRIX.node.minimum}+ required (workit runs under ${running} at ${process.execPath})`,
|
|
64920
65157
|
fix: `Install Node ${SUPPORT_MATRIX.node.minimum}+ (declared support: ${SUPPORT_MATRIX.node.minimum}/current)`
|
|
64921
65158
|
};
|
|
64922
65159
|
}
|
|
64923
|
-
|
|
65160
|
+
const pathNote = pathNode !== null && pathNode !== running ? `; node on PATH is ${pathNode}${semverAtLeast(pathNode, SUPPORT_MATRIX.node.minimum) ? "" : ` (below ${SUPPORT_MATRIX.node.minimum}; it does not run this CLI)`}` : "";
|
|
65161
|
+
if (pathNode === null) {
|
|
64924
65162
|
return {
|
|
64925
65163
|
id: "runtime",
|
|
64926
|
-
status: "
|
|
64927
|
-
detail: `
|
|
64928
|
-
fix: `
|
|
65164
|
+
status: "warn",
|
|
65165
|
+
detail: `workit runs under node ${running}, but no node is on PATH for host hooks`,
|
|
65166
|
+
fix: `Put Node ${SUPPORT_MATRIX.node.minimum}+ on PATH (host hooks launch \`node\`)`
|
|
64929
65167
|
};
|
|
64930
65168
|
}
|
|
64931
|
-
if (!bunOk) {
|
|
65169
|
+
if (!bunOk && needsBun) {
|
|
64932
65170
|
return {
|
|
64933
65171
|
id: "runtime",
|
|
64934
|
-
status: "
|
|
64935
|
-
detail:
|
|
64936
|
-
fix:
|
|
65172
|
+
status: "fail",
|
|
65173
|
+
detail: `bun not found on PATH (dev checkout requires the pinned toolchain ${SUPPORT_MATRIX.bun})`,
|
|
65174
|
+
fix: `Install bun ${SUPPORT_MATRIX.bun} (curl -fsSL https://bun.sh/install | bash)`
|
|
64937
65175
|
};
|
|
64938
65176
|
}
|
|
65177
|
+
const pathOld = !semverAtLeast(pathNode, SUPPORT_MATRIX.node.minimum);
|
|
64939
65178
|
return {
|
|
64940
65179
|
id: "runtime",
|
|
64941
|
-
status: "pass",
|
|
64942
|
-
detail: `node ${
|
|
65180
|
+
status: bunOk && !pathOld ? "pass" : "info",
|
|
65181
|
+
detail: `node ${running} (>=${SUPPORT_MATRIX.node.minimum})${pathNote}, ${bunOk ? `bun ${bunVersion}` : "no bun (needed only for a dev checkout)"}`
|
|
64943
65182
|
};
|
|
64944
65183
|
}, checkVersions = (res) => {
|
|
64945
65184
|
if (!res.dev) {
|
|
64946
65185
|
return {
|
|
64947
65186
|
id: "versions",
|
|
64948
|
-
status: "
|
|
64949
|
-
detail: "
|
|
65187
|
+
status: "info",
|
|
65188
|
+
detail: "installed package, not a dev checkout — version parity applies to checkouts only"
|
|
64950
65189
|
};
|
|
64951
65190
|
}
|
|
64952
65191
|
const corePkg = readJson(path32.join(res.dev, "packages/workit-core/package.json"));
|
|
@@ -64960,7 +65199,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
64960
65199
|
const refs = new Set;
|
|
64961
65200
|
for (const name of ["workit-opencode", "workit-cursor", "workit-cli"]) {
|
|
64962
65201
|
const pkg = readJson(path32.join(res.dev, "packages", name, "package.json"));
|
|
64963
|
-
const dep = pkg?.dependencies?.["@brainervirus/workit-core"];
|
|
65202
|
+
const dep = pkg?.dependencies?.["@brainervirus/workit-core"] ?? pkg?.devDependencies?.["@brainervirus/workit-core"];
|
|
64964
65203
|
if (typeof dep === "string")
|
|
64965
65204
|
refs.add(dep);
|
|
64966
65205
|
}
|
|
@@ -65057,10 +65296,16 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65057
65296
|
status: "pass",
|
|
65058
65297
|
detail: `codex CLI ${installed} matches the qualified pin`
|
|
65059
65298
|
};
|
|
65299
|
+
if (semverAtLeast(installed, qualified))
|
|
65300
|
+
return {
|
|
65301
|
+
id: "codex_pin",
|
|
65302
|
+
status: "info",
|
|
65303
|
+
detail: `codex CLI ${installed} is newer than the qualified ${qualified}`
|
|
65304
|
+
};
|
|
65060
65305
|
return {
|
|
65061
65306
|
id: "codex_pin",
|
|
65062
65307
|
status: "warn",
|
|
65063
|
-
detail: `codex CLI ${installed}
|
|
65308
|
+
detail: `codex CLI ${installed} is older than the qualified ${qualified}`,
|
|
65064
65309
|
fix: `Reinstall the qualified Codex CLI ${qualified} or record fresh qualification evidence`
|
|
65065
65310
|
};
|
|
65066
65311
|
}, readTomlTables = (file) => {
|
|
@@ -65117,7 +65362,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65117
65362
|
const dir = path32.join(codexHome, "plugins", "cache", marketplace, "workit");
|
|
65118
65363
|
let versions = [];
|
|
65119
65364
|
try {
|
|
65120
|
-
versions =
|
|
65365
|
+
versions = readdirSync8(dir).filter((v) => existsSync14(path32.join(dir, v, ".codex-plugin")));
|
|
65121
65366
|
} catch {
|
|
65122
65367
|
versions = [];
|
|
65123
65368
|
}
|
|
@@ -65226,7 +65471,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65226
65471
|
const installed = [];
|
|
65227
65472
|
for (const install of installs) {
|
|
65228
65473
|
const source = install.root ? path32.join(install.root, "agents") : null;
|
|
65229
|
-
const files = source && existsSync14(source) ?
|
|
65474
|
+
const files = source && existsSync14(source) ? readdirSync8(source).filter((f) => f.endsWith(".toml")).toSorted() : [];
|
|
65230
65475
|
if (files.length === 0) {
|
|
65231
65476
|
problems.push(`${install.id} ${install.version ?? "?"} bundles no agents`);
|
|
65232
65477
|
fixes.push(`codex plugin remove ${install.id} && codex plugin add ${install.id}`);
|
|
@@ -65454,8 +65699,8 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65454
65699
|
if (!dev && res.host === "opencode") {
|
|
65455
65700
|
return {
|
|
65456
65701
|
id: "assets",
|
|
65457
|
-
status: "
|
|
65458
|
-
detail: "
|
|
65702
|
+
status: "info",
|
|
65703
|
+
detail: "installed package — OpenCode loads the published assets, no checkout to compare"
|
|
65459
65704
|
};
|
|
65460
65705
|
}
|
|
65461
65706
|
return {
|
|
@@ -65490,7 +65735,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65490
65735
|
`)) {
|
|
65491
65736
|
return false;
|
|
65492
65737
|
}
|
|
65493
|
-
return
|
|
65738
|
+
return spawnSync15(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
|
|
65494
65739
|
} catch {
|
|
65495
65740
|
return false;
|
|
65496
65741
|
}
|
|
@@ -65580,8 +65825,8 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65580
65825
|
if (!dev && res.host !== "cursor") {
|
|
65581
65826
|
return {
|
|
65582
65827
|
id: "launcher",
|
|
65583
|
-
status: "
|
|
65584
|
-
detail: "
|
|
65828
|
+
status: "info",
|
|
65829
|
+
detail: "installed package — non-Cursor launchers ship inside each host plugin, no checkout to check"
|
|
65585
65830
|
};
|
|
65586
65831
|
}
|
|
65587
65832
|
return {
|
|
@@ -65621,7 +65866,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65621
65866
|
fix: "Re-run install-cursor-plugin.sh (or `workit init` with Cursor) to rewrite the hook entries"
|
|
65622
65867
|
};
|
|
65623
65868
|
const started = performance.now();
|
|
65624
|
-
const probe =
|
|
65869
|
+
const probe = spawnSync15("node", [launcher, "--probe", "workit-cursor-hook"], {
|
|
65625
65870
|
encoding: "utf8",
|
|
65626
65871
|
env: { ...res.env, WORKIT_CURSOR_HOOK_NPX_OFFLINE: "1" },
|
|
65627
65872
|
timeout: 40000,
|
|
@@ -65690,18 +65935,18 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65690
65935
|
fix: "Install git and ensure it is on PATH"
|
|
65691
65936
|
};
|
|
65692
65937
|
}
|
|
65693
|
-
if (process.platform !== "win32" && !commandOnPath("flock", res.env)) {
|
|
65938
|
+
if (res.dev && process.platform !== "win32" && !commandOnPath("flock", res.env)) {
|
|
65694
65939
|
return {
|
|
65695
65940
|
id: "utility",
|
|
65696
65941
|
status: "warn",
|
|
65697
|
-
detail: "flock
|
|
65698
|
-
fix: "Install util-linux (flock)"
|
|
65942
|
+
detail: "flock not found — the dev checkout's sync-runtime requires it for locking",
|
|
65943
|
+
fix: process.platform === "darwin" ? "brew install flock (Homebrew's util-linux is keg-only and leaves flock off PATH)" : "Install util-linux (flock)"
|
|
65699
65944
|
};
|
|
65700
65945
|
}
|
|
65701
65946
|
return {
|
|
65702
65947
|
id: "utility",
|
|
65703
65948
|
status: "pass",
|
|
65704
|
-
detail: "git
|
|
65949
|
+
detail: res.dev ? "git and flock on PATH" : "git on PATH"
|
|
65705
65950
|
};
|
|
65706
65951
|
}, isFragileCachePin = (entry) => {
|
|
65707
65952
|
const n = entry.replaceAll("\\", "/").toLowerCase();
|
|
@@ -65795,7 +66040,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65795
66040
|
if (!cmd)
|
|
65796
66041
|
return null;
|
|
65797
66042
|
try {
|
|
65798
|
-
const r =
|
|
66043
|
+
const r = spawnSync15(cmd, ["view", pkgName, "version"], {
|
|
65799
66044
|
encoding: "utf8",
|
|
65800
66045
|
timeout: 20000,
|
|
65801
66046
|
env: res.env
|
|
@@ -65808,9 +66053,18 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65808
66053
|
}, installedPluginVersion = (res) => {
|
|
65809
66054
|
const pkg = readJson(path32.join(res.cursorPluginDir, "package.json"));
|
|
65810
66055
|
return typeof pkg?.version === "string" && pkg.version ? pkg.version : null;
|
|
65811
|
-
},
|
|
65812
|
-
|
|
65813
|
-
|
|
66056
|
+
}, installedOpenCodeCache = (res) => {
|
|
66057
|
+
if (res.opencodePackageCacheDir !== null) {
|
|
66058
|
+
const pkg = readJson(path32.join(res.opencodePackageCacheDir, "node_modules", "@brainervirus", "workit-opencode", "package.json"));
|
|
66059
|
+
return typeof pkg?.version === "string" && pkg.version ? { version: pkg.version, dir: res.opencodePackageCacheDir } : null;
|
|
66060
|
+
}
|
|
66061
|
+
const found = openCodeInstalledPlugin(res.home, res.env);
|
|
66062
|
+
if (!found)
|
|
66063
|
+
return null;
|
|
66064
|
+
let dir = path32.dirname(path32.dirname(path32.dirname(found.root)));
|
|
66065
|
+
if (path32.basename(dir) !== "workit-opencode@latest")
|
|
66066
|
+
dir = path32.dirname(dir);
|
|
66067
|
+
return { version: found.version, dir };
|
|
65814
66068
|
}, checkOpenCodePackageCache = (res) => {
|
|
65815
66069
|
if (!existsSync14(res.opencodeConfig)) {
|
|
65816
66070
|
return {
|
|
@@ -65842,8 +66096,9 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65842
66096
|
detail: `opencode pin ${pin} is an exact version — not compared to registry latest`
|
|
65843
66097
|
};
|
|
65844
66098
|
}
|
|
65845
|
-
const
|
|
65846
|
-
|
|
66099
|
+
const cache = installedOpenCodeCache(res);
|
|
66100
|
+
const installed = cache?.version ?? null;
|
|
66101
|
+
if (cache === null || installed === null) {
|
|
65847
66102
|
return {
|
|
65848
66103
|
id: "stale_install",
|
|
65849
66104
|
status: "pass",
|
|
@@ -65857,7 +66112,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65857
66112
|
status: "warn",
|
|
65858
66113
|
registryProbed: true,
|
|
65859
66114
|
detail: "registry_unreachable: cannot compare OpenCode @latest cache against published workit-opencode",
|
|
65860
|
-
fix: `Retry when npm is reachable, or remove ${
|
|
66115
|
+
fix: `Retry when npm is reachable, or remove ${cache.dir} and restart OpenCode to re-resolve`
|
|
65861
66116
|
};
|
|
65862
66117
|
}
|
|
65863
66118
|
if (!semverAtLeast(installed, expected)) {
|
|
@@ -65866,7 +66121,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
65866
66121
|
status: "fail",
|
|
65867
66122
|
registryProbed: true,
|
|
65868
66123
|
detail: `stale_install: OpenCode @latest cache has workit-opencode ${installed} behind published ${expected}`,
|
|
65869
|
-
fix: `Remove ${
|
|
66124
|
+
fix: `Remove ${cache.dir} and restart OpenCode — it re-resolves @latest on the next launch`
|
|
65870
66125
|
};
|
|
65871
66126
|
}
|
|
65872
66127
|
return {
|
|
@@ -66335,7 +66590,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66335
66590
|
}, defaultIdentityProbes = (env) => ({
|
|
66336
66591
|
origin: (cwd) => {
|
|
66337
66592
|
try {
|
|
66338
|
-
const out =
|
|
66593
|
+
const out = spawnSync15("git", ["remote", "get-url", "origin"], {
|
|
66339
66594
|
cwd,
|
|
66340
66595
|
encoding: "utf8",
|
|
66341
66596
|
env
|
|
@@ -66347,7 +66602,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66347
66602
|
},
|
|
66348
66603
|
ghLogin: (e) => {
|
|
66349
66604
|
try {
|
|
66350
|
-
const out =
|
|
66605
|
+
const out = spawnSync15("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
|
|
66351
66606
|
const login = (out.stdout ?? "").trim();
|
|
66352
66607
|
return out.status === 0 && login ? login : null;
|
|
66353
66608
|
} catch {
|
|
@@ -66356,7 +66611,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66356
66611
|
},
|
|
66357
66612
|
glabLogin: (e) => {
|
|
66358
66613
|
try {
|
|
66359
|
-
const out =
|
|
66614
|
+
const out = spawnSync15("glab", ["api", "user", "--jq", ".username"], {
|
|
66360
66615
|
encoding: "utf8",
|
|
66361
66616
|
env: e
|
|
66362
66617
|
});
|
|
@@ -66368,7 +66623,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66368
66623
|
},
|
|
66369
66624
|
sshLogin: (host) => {
|
|
66370
66625
|
try {
|
|
66371
|
-
const out =
|
|
66626
|
+
const out = spawnSync15("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
|
|
66372
66627
|
encoding: "utf8",
|
|
66373
66628
|
env
|
|
66374
66629
|
});
|
|
@@ -66525,10 +66780,10 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66525
66780
|
fix: repair,
|
|
66526
66781
|
registryProbed
|
|
66527
66782
|
};
|
|
66528
|
-
}, HOOK_ISSUED_MARKER = "workit-issued-sessions/
|
|
66783
|
+
}, HOOK_ISSUED_MARKER = "workit-issued-sessions/3", hookRecordsIssued = (root) => {
|
|
66529
66784
|
let files;
|
|
66530
66785
|
try {
|
|
66531
|
-
files =
|
|
66786
|
+
files = readdirSync8(path32.join(root, "dist")).filter((f) => f.endsWith(".js"));
|
|
66532
66787
|
} catch {
|
|
66533
66788
|
return null;
|
|
66534
66789
|
}
|
|
@@ -66587,12 +66842,12 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66587
66842
|
if (cached)
|
|
66588
66843
|
return cached;
|
|
66589
66844
|
const viaShell = process.platform === "win32" && /\.(?:cmd|bat)$/i.test(bin);
|
|
66590
|
-
const r = viaShell ?
|
|
66845
|
+
const r = viaShell ? spawnSync15(`"${bin}" --version`, {
|
|
66591
66846
|
encoding: "utf8",
|
|
66592
66847
|
env,
|
|
66593
66848
|
timeout: WORKIT_PROBE_TIMEOUT_MS,
|
|
66594
66849
|
shell: true
|
|
66595
|
-
}) :
|
|
66850
|
+
}) : spawnSync15(bin, ["--version"], { encoding: "utf8", env, timeout: WORKIT_PROBE_TIMEOUT_MS });
|
|
66596
66851
|
const timedOut = r.error !== undefined && "code" in r.error && r.error.code === "ETIMEDOUT";
|
|
66597
66852
|
const version = r.status === 0 ? (r.stdout ?? "").match(SEMVER)?.[0] ?? null : null;
|
|
66598
66853
|
const probe = { version: timedOut ? null : version, timedOut };
|
|
@@ -66605,7 +66860,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66605
66860
|
version: i.version
|
|
66606
66861
|
})),
|
|
66607
66862
|
{ label: "Cursor plugin", version: installedPluginVersion(res) },
|
|
66608
|
-
{ label: "OpenCode plugin", version:
|
|
66863
|
+
{ label: "OpenCode plugin", version: installedOpenCodeCache(res)?.version ?? null }
|
|
66609
66864
|
].filter((p) => p.version !== null);
|
|
66610
66865
|
const newest = plugins.reduce((best, p) => best && semverAtLeast(best.version, p.version) ? best : p, null);
|
|
66611
66866
|
const target = newest?.version ?? "latest";
|
|
@@ -66646,7 +66901,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
66646
66901
|
} : c) : raw;
|
|
66647
66902
|
const failed = checks.filter((c) => c.status === "fail").length;
|
|
66648
66903
|
const warned = checks.filter((c) => c.status === "warn").length;
|
|
66649
|
-
const passed = checks.filter((c) => c.status === "pass").length;
|
|
66904
|
+
const passed = checks.filter((c) => c.status === "pass" || c.status === "info").length;
|
|
66650
66905
|
const exitCode = failed > 0 ? 1 : 0;
|
|
66651
66906
|
const offline = !raw.some((c) => ("registryProbed" in c) && c.registryProbed);
|
|
66652
66907
|
const report = {
|
|
@@ -66737,13 +66992,14 @@ import {
|
|
|
66737
66992
|
mkdirSync as mkdirSync7,
|
|
66738
66993
|
mkdtempSync as mkdtempSync2,
|
|
66739
66994
|
readFileSync as readFileSync15,
|
|
66740
|
-
readdirSync as
|
|
66995
|
+
readdirSync as readdirSync9,
|
|
66741
66996
|
realpathSync as realpathSync7,
|
|
66742
66997
|
renameSync as renameSync4,
|
|
66743
66998
|
rmSync as rmSync4,
|
|
66744
66999
|
statSync as statSync6,
|
|
66745
67000
|
writeFileSync as writeFileSync8
|
|
66746
67001
|
} from "node:fs";
|
|
67002
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
66747
67003
|
import os9 from "node:os";
|
|
66748
67004
|
import path33 from "node:path";
|
|
66749
67005
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
@@ -66780,6 +67036,7 @@ function buildSetupPreview(values, opts = {}) {
|
|
|
66780
67036
|
const blocked = [];
|
|
66781
67037
|
const mutations = [];
|
|
66782
67038
|
const preserved = [];
|
|
67039
|
+
let projectRefused;
|
|
66783
67040
|
const overrides = activeSetupOverrides(env);
|
|
66784
67041
|
for (const entry of [state.config, state.youtrack, state.vcs, state.workspaces]) {
|
|
66785
67042
|
if (entry.status === "malformed")
|
|
@@ -66902,8 +67159,11 @@ function buildSetupPreview(values, opts = {}) {
|
|
|
66902
67159
|
}
|
|
66903
67160
|
}
|
|
66904
67161
|
}
|
|
66905
|
-
|
|
66906
|
-
|
|
67162
|
+
const target = values.applyProject ? projectTarget(opts.cwd ?? process.cwd()) : null;
|
|
67163
|
+
if (target && !target.ok)
|
|
67164
|
+
projectRefused = target.refused;
|
|
67165
|
+
if (target?.ok) {
|
|
67166
|
+
const root = target.root;
|
|
66907
67167
|
const gitignorePath = path33.join(root, ".gitignore");
|
|
66908
67168
|
const existing = existsSync15(gitignorePath) ? readFileSync15(gitignorePath, "utf8") : "";
|
|
66909
67169
|
const existingLines = new Set(existing.split(`
|
|
@@ -66922,7 +67182,8 @@ function buildSetupPreview(values, opts = {}) {
|
|
|
66922
67182
|
overrides,
|
|
66923
67183
|
preserved,
|
|
66924
67184
|
platforms: values.platforms ?? [],
|
|
66925
|
-
state
|
|
67185
|
+
state,
|
|
67186
|
+
...projectRefused === undefined ? {} : { projectRefused }
|
|
66926
67187
|
};
|
|
66927
67188
|
}
|
|
66928
67189
|
function resolveApply(preview, options) {
|
|
@@ -67105,7 +67366,14 @@ function adapterRoot(platform, res, preferInstalledAdapter = false) {
|
|
|
67105
67366
|
if (res.dev) {
|
|
67106
67367
|
candidates.push(path33.join(res.dev, "packages", `workit-${platform}`));
|
|
67107
67368
|
} else {
|
|
67108
|
-
candidates.push(
|
|
67369
|
+
candidates.push(...nodeResolvedAdapters(platform));
|
|
67370
|
+
if (platform === "cursor")
|
|
67371
|
+
candidates.push(stagedCursor);
|
|
67372
|
+
if (platform === "opencode") {
|
|
67373
|
+
const cached = openCodeInstalledPlugin(res.home, res.env);
|
|
67374
|
+
if (cached)
|
|
67375
|
+
candidates.push(cached.root);
|
|
67376
|
+
}
|
|
67109
67377
|
let dir = path33.resolve(res.cwd);
|
|
67110
67378
|
while (true) {
|
|
67111
67379
|
candidates.push(path33.join(dir, "packages", `workit-${platform}`));
|
|
@@ -67115,17 +67383,14 @@ function adapterRoot(platform, res, preferInstalledAdapter = false) {
|
|
|
67115
67383
|
dir = parent;
|
|
67116
67384
|
}
|
|
67117
67385
|
}
|
|
67118
|
-
candidates.push(path33.join(res.home, ".local", "share", "workit", "packages", `workit-${platform}`));
|
|
67119
|
-
if (platform === "cursor")
|
|
67120
|
-
candidates.push(stagedCursor);
|
|
67121
67386
|
for (const candidate of candidates) {
|
|
67122
67387
|
if (isAdapter(candidate, platform))
|
|
67123
|
-
return candidate;
|
|
67388
|
+
return realRoot(candidate);
|
|
67124
67389
|
}
|
|
67125
67390
|
return null;
|
|
67126
67391
|
}
|
|
67127
67392
|
function applyOpenCode(root, res) {
|
|
67128
|
-
const resolvedPin = opencodePin(root, res);
|
|
67393
|
+
const resolvedPin = root === null ? OPENCODE_NPM_PIN : opencodePin(root, res);
|
|
67129
67394
|
if (!resolvedPin) {
|
|
67130
67395
|
return {
|
|
67131
67396
|
platform: "opencode",
|
|
@@ -67424,6 +67689,10 @@ function applySetupPreview(preview, options = {}) {
|
|
|
67424
67689
|
root = adapterRoot(platform, res, options.preferInstalledAdapter);
|
|
67425
67690
|
rootFor.set(platform, root);
|
|
67426
67691
|
}
|
|
67692
|
+
if (root === null && registersNpmPin(platform, res)) {
|
|
67693
|
+
entries.push(applyOpenCode(null, res));
|
|
67694
|
+
continue;
|
|
67695
|
+
}
|
|
67427
67696
|
if (root === null) {
|
|
67428
67697
|
entries.push({
|
|
67429
67698
|
platform,
|
|
@@ -67465,12 +67734,20 @@ function applySetupPreview(preview, options = {}) {
|
|
|
67465
67734
|
detail: "existing credential preserved"
|
|
67466
67735
|
});
|
|
67467
67736
|
}
|
|
67737
|
+
if (preview.projectRefused) {
|
|
67738
|
+
entries.push({
|
|
67739
|
+
platform: "core",
|
|
67740
|
+
file: path33.resolve(res.cwd),
|
|
67741
|
+
status: "Skipped",
|
|
67742
|
+
detail: preview.projectRefused
|
|
67743
|
+
});
|
|
67744
|
+
}
|
|
67468
67745
|
const doctor = [];
|
|
67469
67746
|
for (const platform of preview.platforms) {
|
|
67470
67747
|
if (platform !== "opencode" && platform !== "cursor")
|
|
67471
67748
|
continue;
|
|
67472
67749
|
const root = rootFor.get(platform);
|
|
67473
|
-
if (root ===
|
|
67750
|
+
if (root === undefined || root === null && !registersNpmPin(platform, res))
|
|
67474
67751
|
continue;
|
|
67475
67752
|
if (!entries.some((e) => e.platform === platform && e.status !== "Failed"))
|
|
67476
67753
|
continue;
|
|
@@ -67599,6 +67876,16 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
|
|
|
67599
67876
|
} catch {
|
|
67600
67877
|
return false;
|
|
67601
67878
|
}
|
|
67879
|
+
}, nodeResolvedAdapters = (platform) => {
|
|
67880
|
+
const name = `@brainervirus/workit-${platform}`;
|
|
67881
|
+
const require2 = createRequire2(path33.join(packageRoot(), "package.json"));
|
|
67882
|
+
return (require2.resolve.paths(name) ?? []).map((dir) => path33.join(dir, name));
|
|
67883
|
+
}, realRoot = (root) => {
|
|
67884
|
+
try {
|
|
67885
|
+
return realpathSync7(root);
|
|
67886
|
+
} catch {
|
|
67887
|
+
return root;
|
|
67888
|
+
}
|
|
67602
67889
|
}, isDevOpenCodeAdapterRoot = (root, dev) => {
|
|
67603
67890
|
if (dev)
|
|
67604
67891
|
return true;
|
|
@@ -67613,7 +67900,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
|
|
|
67613
67900
|
return `file://${entry}`;
|
|
67614
67901
|
}
|
|
67615
67902
|
return null;
|
|
67616
|
-
}, opencodePin = (root, res) => resolveOpenCodePin(root, { dev: res.dev }), CURSOR_HOOKS_FILE, localizedCursorHooks = (shipped, pluginDir) => {
|
|
67903
|
+
}, opencodePin = (root, res) => resolveOpenCodePin(root, { dev: res.dev }), registersNpmPin = (platform, res) => platform === "opencode" && !res.dev, CURSOR_HOOKS_FILE, localizedCursorHooks = (shipped, pluginDir) => {
|
|
67617
67904
|
try {
|
|
67618
67905
|
const merged = mergeCursorHooks(JSON.parse(shipped.toString("utf8")), { command: cursorHooksEntry(pluginDir).command }, pluginDir);
|
|
67619
67906
|
return Buffer.from(`${JSON.stringify(merged.config, null, 2)}
|
|
@@ -67623,7 +67910,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
|
|
|
67623
67910
|
}
|
|
67624
67911
|
}, samePluginContent = (src, dest, pluginDir, relative = "") => {
|
|
67625
67912
|
try {
|
|
67626
|
-
for (const entry of
|
|
67913
|
+
for (const entry of readdirSync9(src, { withFileTypes: true })) {
|
|
67627
67914
|
if (entry.name === "node_modules")
|
|
67628
67915
|
continue;
|
|
67629
67916
|
const source = path33.join(src, entry.name);
|
|
@@ -67638,7 +67925,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
|
|
|
67638
67925
|
return false;
|
|
67639
67926
|
}
|
|
67640
67927
|
}
|
|
67641
|
-
for (const entry of
|
|
67928
|
+
for (const entry of readdirSync9(dest, { withFileTypes: true })) {
|
|
67642
67929
|
if (existsSync15(path33.join(src, entry.name)))
|
|
67643
67930
|
continue;
|
|
67644
67931
|
const rel = path33.join(relative, entry.name);
|
|
@@ -67657,7 +67944,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
|
|
|
67657
67944
|
const sourceRules = path33.join(src, "rules");
|
|
67658
67945
|
const installedRules = path33.join(dest, "rules");
|
|
67659
67946
|
try {
|
|
67660
|
-
for (const entry of
|
|
67947
|
+
for (const entry of readdirSync9(installedRules, { withFileTypes: true })) {
|
|
67661
67948
|
if (!entry.isFile() || !entry.name.endsWith(".mdc"))
|
|
67662
67949
|
continue;
|
|
67663
67950
|
if (existsSync15(path33.join(sourceRules, entry.name)))
|
|
@@ -67674,7 +67961,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
|
|
|
67674
67961
|
if (existsSync15(legacyRules)) {
|
|
67675
67962
|
try {
|
|
67676
67963
|
mkdirSync7(path33.join(res.cursorPluginDir, "rules"), { recursive: true });
|
|
67677
|
-
for (const entry of
|
|
67964
|
+
for (const entry of readdirSync9(legacyRules, { withFileTypes: true })) {
|
|
67678
67965
|
if (!entry.isFile() || !entry.name.endsWith(".mdc"))
|
|
67679
67966
|
continue;
|
|
67680
67967
|
const target = path33.join(res.cursorPluginDir, "rules", entry.name);
|
|
@@ -67684,7 +67971,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
|
|
|
67684
67971
|
} catch {}
|
|
67685
67972
|
}
|
|
67686
67973
|
rmSync4(legacy, { recursive: true, force: true });
|
|
67687
|
-
}, failedDetail = (report) => report.checks.filter((c) => c.status === "fail").map((c) => `${c.id}: ${c.detail}`).join("; "), setupCompletionGuidance = () => [
|
|
67974
|
+
}, failedDetail = (report) => report.checks.filter((c) => c.status === "fail").map((c) => c.detail.startsWith(`${c.id}:`) ? c.detail : `${c.id}: ${c.detail}`).join("; "), setupCompletionGuidance = () => [
|
|
67688
67975
|
"Run `workit doctor` to verify your installation.",
|
|
67689
67976
|
"Run /wk-status in OpenCode to review token setup steps."
|
|
67690
67977
|
];
|
|
@@ -70753,7 +71040,43 @@ function Screen({
|
|
|
70753
71040
|
}, undefined, false, undefined, this)
|
|
70754
71041
|
]
|
|
70755
71042
|
}, undefined, true, undefined, this);
|
|
70756
|
-
case "project":
|
|
71043
|
+
case "project": {
|
|
71044
|
+
const target = projectTargetFor(resolveBasePath(draft.values));
|
|
71045
|
+
if (!target.ok) {
|
|
71046
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
71047
|
+
flexDirection: "column",
|
|
71048
|
+
gap: 1,
|
|
71049
|
+
children: [
|
|
71050
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
71051
|
+
bold: true,
|
|
71052
|
+
children: "Step 6 — Project setup"
|
|
71053
|
+
}, undefined, false, undefined, this),
|
|
71054
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
71055
|
+
color: "yellow",
|
|
71056
|
+
children: [
|
|
71057
|
+
"Skipped: ",
|
|
71058
|
+
target.refused
|
|
71059
|
+
]
|
|
71060
|
+
}, undefined, true, undefined, this),
|
|
71061
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(ConfirmInput, {
|
|
71062
|
+
defaultChoice: "confirm",
|
|
71063
|
+
submitOnEnter: false,
|
|
71064
|
+
onConfirm: () => {
|
|
71065
|
+
dispatch({ type: "set", field: "applyProject", value: true });
|
|
71066
|
+
dispatch({ type: "next" });
|
|
71067
|
+
},
|
|
71068
|
+
onCancel: () => {
|
|
71069
|
+
dispatch({ type: "set", field: "applyProject", value: false });
|
|
71070
|
+
dispatch({ type: "next" });
|
|
71071
|
+
}
|
|
71072
|
+
}, undefined, false, undefined, this),
|
|
71073
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
71074
|
+
dimColor: true,
|
|
71075
|
+
children: "y to keep this note in the summary · n to skip it · either adds no project files · b Back · Esc Cancel"
|
|
71076
|
+
}, undefined, false, undefined, this)
|
|
71077
|
+
]
|
|
71078
|
+
}, undefined, true, undefined, this);
|
|
71079
|
+
}
|
|
70757
71080
|
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
70758
71081
|
flexDirection: "column",
|
|
70759
71082
|
gap: 1,
|
|
@@ -70766,7 +71089,7 @@ function Screen({
|
|
|
70766
71089
|
dimColor: true,
|
|
70767
71090
|
children: [
|
|
70768
71091
|
"Apply gitignore + hygiene in ",
|
|
70769
|
-
|
|
71092
|
+
target.root,
|
|
70770
71093
|
"? (Existing files are never overwritten.)"
|
|
70771
71094
|
]
|
|
70772
71095
|
}, undefined, true, undefined, this),
|
|
@@ -70788,6 +71111,7 @@ function Screen({
|
|
|
70788
71111
|
}, undefined, false, undefined, this)
|
|
70789
71112
|
]
|
|
70790
71113
|
}, undefined, true, undefined, this);
|
|
71114
|
+
}
|
|
70791
71115
|
case "summary": {
|
|
70792
71116
|
const policy = effectivePolicy(draft.values);
|
|
70793
71117
|
const preview = buildSetupPreview(draft.values, { cwd: resolveBasePath(draft.values) });
|
|
@@ -70929,8 +71253,15 @@ function Screen({
|
|
|
70929
71253
|
}, undefined, true, undefined, this),
|
|
70930
71254
|
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
70931
71255
|
children: [
|
|
70932
|
-
"Project hygiene:
|
|
70933
|
-
|
|
71256
|
+
"Project hygiene:",
|
|
71257
|
+
" ",
|
|
71258
|
+
preview.projectRefused ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
71259
|
+
color: "yellow",
|
|
71260
|
+
children: [
|
|
71261
|
+
"skipped — ",
|
|
71262
|
+
preview.projectRefused
|
|
71263
|
+
]
|
|
71264
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
70934
71265
|
color: "green",
|
|
70935
71266
|
children: draft.values.applyProject ? "yes" : "no"
|
|
70936
71267
|
}, undefined, false, undefined, this)
|
|
@@ -71019,7 +71350,11 @@ function Screen({
|
|
|
71019
71350
|
}, undefined, false, undefined, this);
|
|
71020
71351
|
}
|
|
71021
71352
|
}
|
|
71022
|
-
var import_react66, jsx_dev_runtime2,
|
|
71353
|
+
var import_react66, jsx_dev_runtime2, lastProjectTarget = null, projectTargetFor = (base) => {
|
|
71354
|
+
if (lastProjectTarget?.base !== base)
|
|
71355
|
+
lastProjectTarget = { base, target: projectTarget(base) };
|
|
71356
|
+
return lastProjectTarget.target;
|
|
71357
|
+
}, PLATFORM_LABELS, BRANCH_PRESETS, BRANCH_PRESET_DESCRIPTIONS, SCREEN_PLACEHOLDERS, VCS_PROVIDERS, LOCALE_PICKER_OPTIONS, ISSUE_TRACKERS, WORKSPACE_ADVANCED_FIELDS, PROFILE_FIELDS, TRACK_FIELDS, enumFieldOptions = (field) => {
|
|
71023
71358
|
if (field === "commitPolicy.preset")
|
|
71024
71359
|
return [{ label: "Inherit global", value: "inherit" }, ...commitPresetOptions];
|
|
71025
71360
|
if (field === "branchPolicy.preset")
|
|
@@ -71061,6 +71396,7 @@ var init_steps = __esm(async () => {
|
|
|
71061
71396
|
init_logic();
|
|
71062
71397
|
init_workspaces();
|
|
71063
71398
|
init_branch_policy();
|
|
71399
|
+
init_hygiene();
|
|
71064
71400
|
init_wizard_state();
|
|
71065
71401
|
init_detect_hosts();
|
|
71066
71402
|
await __promiseAll([
|
|
@@ -71452,7 +71788,7 @@ var init_init = __esm(() => {
|
|
|
71452
71788
|
});
|
|
71453
71789
|
|
|
71454
71790
|
// packages/workit-cli/src/upgrade.ts
|
|
71455
|
-
import { spawnSync as
|
|
71791
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
71456
71792
|
import {
|
|
71457
71793
|
existsSync as existsSync18,
|
|
71458
71794
|
mkdtempSync as mkdtempSync3,
|
|
@@ -71808,7 +72144,7 @@ async function runLaunchCommand(argv, deps = {}) {
|
|
|
71808
72144
|
}
|
|
71809
72145
|
if (deps.run)
|
|
71810
72146
|
return deps.run(host, hostArgs).status ?? 1;
|
|
71811
|
-
return
|
|
72147
|
+
return spawnSync16(launch.command, launch.args, {
|
|
71812
72148
|
stdio: "inherit",
|
|
71813
72149
|
env
|
|
71814
72150
|
}).status ?? 1;
|
|
@@ -71990,7 +72326,7 @@ var init_launch = __esm(() => {
|
|
|
71990
72326
|
});
|
|
71991
72327
|
|
|
71992
72328
|
// packages/workit-core/src/knowledge.ts
|
|
71993
|
-
import { spawnSync as
|
|
72329
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
71994
72330
|
import fs18 from "node:fs";
|
|
71995
72331
|
import path38 from "node:path";
|
|
71996
72332
|
function lintKnowledge(root) {
|
|
@@ -72111,7 +72447,7 @@ var AGENTS_BYTE_BUDGET, STEERING_FILES, NEED_BASED_FILES, STANDARDS_FILE = "CODI
|
|
|
72111
72447
|
return "unreadable";
|
|
72112
72448
|
}
|
|
72113
72449
|
}, trackedTopLevel = (root) => {
|
|
72114
|
-
const run =
|
|
72450
|
+
const run = spawnSync17("git", ["ls-files", "-z", "--cached"], {
|
|
72115
72451
|
cwd: root,
|
|
72116
72452
|
encoding: "utf8",
|
|
72117
72453
|
maxBuffer: 256 * 1024 * 1024,
|
|
@@ -72479,7 +72815,8 @@ var R = "[REDACTED]", TOKEN_PATTERNS, WEBHOOK, URL_USERINFO, CURL_USER, KEY, ASS
|
|
|
72479
72815
|
var init_redact = __esm(() => {
|
|
72480
72816
|
TOKEN_PATTERNS = [
|
|
72481
72817
|
/\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})/gu,
|
|
72482
|
-
/\
|
|
72818
|
+
/\b(?:glpat|gloas|gldt|glrtr|glrt|glcbt|glptt|glft|glimt|glagent|glwt|glsoat|glffct)-[A-Za-z0-9_-]{20,}/gu,
|
|
72819
|
+
/\b_gitlab_session=[A-Za-z0-9%_-]{16,}/gu,
|
|
72483
72820
|
/\bnpm_[A-Za-z0-9]{30,}/gu,
|
|
72484
72821
|
/\bxox[abposr]-[A-Za-z0-9-]{10,}/gu,
|
|
72485
72822
|
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu,
|
|
@@ -72513,7 +72850,7 @@ var init_redact = __esm(() => {
|
|
|
72513
72850
|
});
|
|
72514
72851
|
|
|
72515
72852
|
// packages/workit-core/src/checks.ts
|
|
72516
|
-
import { spawn, spawnSync as
|
|
72853
|
+
import { spawn, spawnSync as spawnSync18 } from "node:child_process";
|
|
72517
72854
|
import { createHash as createHash16 } from "node:crypto";
|
|
72518
72855
|
import fs19 from "node:fs";
|
|
72519
72856
|
import os13 from "node:os";
|
|
@@ -72695,7 +73032,7 @@ function runCheckCommand(argv, options) {
|
|
|
72695
73032
|
if (!child?.pid)
|
|
72696
73033
|
return;
|
|
72697
73034
|
if (windows)
|
|
72698
|
-
|
|
73035
|
+
spawnSync18("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
72699
73036
|
stdio: "ignore",
|
|
72700
73037
|
windowsHide: true
|
|
72701
73038
|
});
|
|
@@ -74031,7 +74368,7 @@ var init_check = __esm(() => {
|
|
|
74031
74368
|
var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
|
|
74032
74369
|
|
|
74033
74370
|
// packages/workit-core/src/git/ops.ts
|
|
74034
|
-
import { spawnSync as
|
|
74371
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
74035
74372
|
import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
|
|
74036
74373
|
import path42 from "node:path";
|
|
74037
74374
|
function operationInProgress(cwd) {
|
|
@@ -74346,6 +74683,14 @@ function gitCommit(cwd, input) {
|
|
|
74346
74683
|
const session = input.actor.session;
|
|
74347
74684
|
if (session && !SESSION_SAFE.test(session))
|
|
74348
74685
|
return failure3("invalid_input", "WORKIT_SESSION_ID must be 1-128 characters of [A-Za-z0-9_.:@/+-]");
|
|
74686
|
+
if (session) {
|
|
74687
|
+
const env = input.env ?? process.env;
|
|
74688
|
+
if (issuedBinding(cwd, session) !== null)
|
|
74689
|
+
return failure3("blocked", `judge_session: ${session} was issued to a verifier or reviewer subagent; it records verdicts and never authors commits`, "commit from the implementer's or the lead's own session (unset WORKIT_SESSION_ID back to the shell's)");
|
|
74690
|
+
const refusal = sessionClaimRefusal(env, session, cwd);
|
|
74691
|
+
if (refusal)
|
|
74692
|
+
return failure3("blocked", refusal.error, "commit from the session this shell really is: never set WORKIT_SESSION_ID by hand for a commit (unset it, or use the id the host put in this shell)");
|
|
74693
|
+
}
|
|
74349
74694
|
const changeRequired = !input.amend && !input.allowEmpty;
|
|
74350
74695
|
const emptyAmend = previous !== null && git5(cwd, ["rev-parse", "--verify", "-q", `${previous}^{tree}`]).stdout.trim() === git5(cwd, ["rev-parse", "--verify", "-q", `${previous}~1^{tree}`]).stdout.trim();
|
|
74351
74696
|
const args = ["commit", "--no-edit"];
|
|
@@ -74628,6 +74973,51 @@ ${gitOutput(run)}`);
|
|
|
74628
74973
|
recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
|
|
74629
74974
|
});
|
|
74630
74975
|
}
|
|
74976
|
+
function releaseLinesOf(cwd) {
|
|
74977
|
+
const { preset, branches, production } = releaseBranchesOf(cwd);
|
|
74978
|
+
let policyPrefix;
|
|
74979
|
+
try {
|
|
74980
|
+
const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
74981
|
+
policyPrefix = selected?.releaseTagPrefix;
|
|
74982
|
+
} catch {
|
|
74983
|
+
policyPrefix = undefined;
|
|
74984
|
+
}
|
|
74985
|
+
const prefix = releaseTagPrefixOf(cwd, policyPrefix);
|
|
74986
|
+
const trunk = production ?? "main";
|
|
74987
|
+
const lines = [
|
|
74988
|
+
{
|
|
74989
|
+
track: null,
|
|
74990
|
+
production: branches[0] ?? trunk,
|
|
74991
|
+
integration: branches[1] ?? trunk,
|
|
74992
|
+
mergeBack: branches[1] ? [branches[1]] : [],
|
|
74993
|
+
tagPrefix: prefix,
|
|
74994
|
+
gitflow: branches.length > 0,
|
|
74995
|
+
naming: {}
|
|
74996
|
+
}
|
|
74997
|
+
];
|
|
74998
|
+
let raw;
|
|
74999
|
+
try {
|
|
75000
|
+
raw = resolveRuntimeReleaseTracks(cwd)?.releaseTracks;
|
|
75001
|
+
} catch {
|
|
75002
|
+
return lines;
|
|
75003
|
+
}
|
|
75004
|
+
for (const track of readReleaseTracks(raw).tracks) {
|
|
75005
|
+
const value = isRecord8(raw) ? raw[track.name] : undefined;
|
|
75006
|
+
const entry = isRecord8(value) ? value : {};
|
|
75007
|
+
const strategy = typeof entry.strategy === "string" ? entry.strategy : preset;
|
|
75008
|
+
const namespace = typeof entry.tagNamespace === "string" ? entry.tagNamespace.trim() : "";
|
|
75009
|
+
lines.push({
|
|
75010
|
+
track: track.name,
|
|
75011
|
+
production: track.productionBranch,
|
|
75012
|
+
integration: track.integrationBranch,
|
|
75013
|
+
mergeBack: track.mergeBackBranches.length ? track.mergeBackBranches : [track.integrationBranch],
|
|
75014
|
+
tagPrefix: `${namespace}${prefix}`,
|
|
75015
|
+
gitflow: strategy === "gitflow",
|
|
75016
|
+
naming: { release: track.naming.release, hotfix: track.naming.hotfix }
|
|
75017
|
+
});
|
|
75018
|
+
}
|
|
75019
|
+
return lines;
|
|
75020
|
+
}
|
|
74631
75021
|
function releasePushPreflight(cwd, names) {
|
|
74632
75022
|
const progress = operationInProgress(cwd);
|
|
74633
75023
|
if (progress)
|
|
@@ -74635,7 +75025,16 @@ function releasePushPreflight(cwd, names) {
|
|
|
74635
75025
|
const wanted = [...new Set(names)];
|
|
74636
75026
|
if (!wanted.length)
|
|
74637
75027
|
return failure3("invalid_input", "name the release refs to push");
|
|
74638
|
-
const { preset,
|
|
75028
|
+
const { preset, production } = releaseBranchesOf(cwd);
|
|
75029
|
+
const lines = releaseLinesOf(cwd);
|
|
75030
|
+
const tracks = lines.filter((line) => line.track !== null && line.gitflow);
|
|
75031
|
+
const branches = [
|
|
75032
|
+
...new Set([
|
|
75033
|
+
...releaseBranchesOf(cwd).branches,
|
|
75034
|
+
...tracks.flatMap((line) => [line.production, line.integration, ...line.mergeBack])
|
|
75035
|
+
])
|
|
75036
|
+
];
|
|
75037
|
+
const tagShape = `${lines[0].tagPrefix}<version>`;
|
|
74639
75038
|
const refs = [];
|
|
74640
75039
|
for (const raw of wanted) {
|
|
74641
75040
|
if (raw.startsWith("-") || raw.includes(":"))
|
|
@@ -74647,16 +75046,22 @@ function releasePushPreflight(cwd, names) {
|
|
|
74647
75046
|
if (tag && branch)
|
|
74648
75047
|
return failure3("invalid_input", `${raw} names both a branch and a tag`, `workit git push --release refs/heads/${raw} # or refs/tags/${raw}`);
|
|
74649
75048
|
if (tag) {
|
|
74650
|
-
const
|
|
74651
|
-
if (!
|
|
74652
|
-
return failure3("blocked", `release_push_refused: tag ${tagName} is not a release tag (${
|
|
74653
|
-
refs.push({
|
|
75049
|
+
const line = lineOfTag(tagName, lines);
|
|
75050
|
+
if (!line)
|
|
75051
|
+
return failure3("blocked", `release_push_refused: tag ${tagName} is not a release tag (${tagShape})`, `git tag -a ${tagShape} -m <message> # then: workit git push --release ${tagShape}`);
|
|
75052
|
+
refs.push({
|
|
75053
|
+
kind: "tag",
|
|
75054
|
+
name: tagName,
|
|
75055
|
+
ref: `refs/tags/${tagName}`,
|
|
75056
|
+
sha: tag,
|
|
75057
|
+
production: line.track === null ? production : line.production
|
|
75058
|
+
});
|
|
74654
75059
|
continue;
|
|
74655
75060
|
}
|
|
74656
75061
|
if (!branch)
|
|
74657
75062
|
return failure3("not_found", `${raw} is not a local branch or tag`);
|
|
74658
75063
|
if (!branches.some((name) => name.toLowerCase() === branchName.toLowerCase()))
|
|
74659
|
-
return failure3("blocked", branches.length ? `release_push_refused: ${branchName} is not a git-flow release branch (${branches.join(", ")})` : `release_push_refused: the ${preset} policy lands ${branchName} only through a reviewed PR; workit git push --release pushes
|
|
75064
|
+
return failure3("blocked", branches.length ? `release_push_refused: ${branchName} is not a git-flow release branch (${branches.join(", ")})` : `release_push_refused: the ${preset} policy lands ${branchName} only through a reviewed PR; workit git push --release pushes ${tagShape} tags there, not branches`, branches.length ? "workit git push # a feature, bugfix, release or hotfix branch, then: workit pr create" : `workit pr create --base ${branchName} # then tag the merged commit and: workit git push --release ${tagShape}`);
|
|
74660
75065
|
refs.push({ kind: "branch", name: branchName, ref: `refs/heads/${branchName}`, sha: branch });
|
|
74661
75066
|
}
|
|
74662
75067
|
const remote = pushRemoteName(cwd, refs.find((ref) => ref.kind === "branch")?.name ?? null);
|
|
@@ -74668,7 +75073,14 @@ function releasePushPreflight(cwd, names) {
|
|
|
74668
75073
|
const local = parseRemoteUrl(raw)?.protocol === "file";
|
|
74669
75074
|
if (!local && !pushTargetIsStable(cwd, raw))
|
|
74670
75075
|
return failure3("blocked", `push_target_rewritten: the push URL of "${remote}" is rewritten by a url.*.insteadOf rule, so the destination is not the one checked`, "git config --get-regexp '^url\\.' # remove the rewrite for this remote");
|
|
74671
|
-
return success2({
|
|
75076
|
+
return success2({
|
|
75077
|
+
refs,
|
|
75078
|
+
remote,
|
|
75079
|
+
url: redactRemote(raw),
|
|
75080
|
+
rawUrl: raw,
|
|
75081
|
+
local,
|
|
75082
|
+
production
|
|
75083
|
+
});
|
|
74672
75084
|
}
|
|
74673
75085
|
function executeReleasePush(cwd, plan, input) {
|
|
74674
75086
|
const previous = new Map;
|
|
@@ -74685,20 +75097,20 @@ function executeReleasePush(cwd, plan, input) {
|
|
|
74685
75097
|
return failure3("failed", `non_fast_forward: ${plan.remote}/${ref.name} (${before.sha.slice(0, 12)}) is not part of local ${ref.name}; a release push never rewrites it`, `git fetch ${plan.remote} ${ref.name} && git switch ${ref.name} && git merge --ff-only ${plan.remote}/${ref.name} # then: workit git push --release ${ref.name}`);
|
|
74686
75098
|
}
|
|
74687
75099
|
const tags = plan.refs.filter((ref) => ref.kind === "tag");
|
|
74688
|
-
|
|
74689
|
-
|
|
74690
|
-
|
|
74691
|
-
|
|
75100
|
+
const contains = (tip, sha) => git5(cwd, ["merge-base", "--is-ancestor", sha, tip]).ok;
|
|
75101
|
+
for (const tag of tags) {
|
|
75102
|
+
const production = tag.production ?? plan.production;
|
|
75103
|
+
let remoteTip = null;
|
|
75104
|
+
if (production) {
|
|
75105
|
+
const tip = remoteRefTip(cwd, plan.rawUrl, `refs/heads/${production}`);
|
|
74692
75106
|
if (!tip.ok)
|
|
74693
75107
|
return failure3(tip.code, tip.error);
|
|
74694
|
-
|
|
74695
|
-
}
|
|
74696
|
-
const contains = (tip, sha) => git5(cwd, ["merge-base", "--is-ancestor", sha, tip]).ok;
|
|
74697
|
-
for (const tag of tags) {
|
|
74698
|
-
const released = productionTip !== null && contains(productionTip, tag.sha) || plan.refs.some((ref) => ref.kind === "branch" && contains(ref.sha, tag.sha));
|
|
74699
|
-
if (!released)
|
|
74700
|
-
return failure3("blocked", `tag_unreleased: ${tag.name} (${tag.sha.slice(0, 12)}) is not on ${plan.remote}/${plan.production ?? "the production branch"}${plan.refs.some((ref) => ref.kind === "branch") ? " or a branch in this push" : ""}; a release tag marks a commit that already landed`, `land it through a PR first (workit pr create), git fetch ${plan.remote}, then tag the merged commit`);
|
|
75108
|
+
remoteTip = tip.sha;
|
|
74701
75109
|
}
|
|
75110
|
+
const pushed = plan.refs.find((ref) => ref.kind === "branch" && ref.name.toLowerCase() === production?.toLowerCase());
|
|
75111
|
+
const released = remoteTip !== null && contains(remoteTip, tag.sha) || pushed !== undefined && contains(pushed.sha, tag.sha);
|
|
75112
|
+
if (!released)
|
|
75113
|
+
return failure3("blocked", `tag_unreleased: ${tag.name} (${tag.sha.slice(0, 12)}) is not on ${plan.remote}/${production ?? "the production branch"}${pushed ? ` or the ${production} in this push` : ""}; a release tag marks a commit that already landed on its line's production branch`, `land it through a PR first (workit pr create), git fetch ${plan.remote}, then tag the merged commit`);
|
|
74702
75114
|
}
|
|
74703
75115
|
const pending = plan.refs.filter((ref) => previous.get(ref.ref) !== ref.sha);
|
|
74704
75116
|
if (pending.length) {
|
|
@@ -74777,7 +75189,7 @@ ${run.stdout}`;
|
|
|
74777
75189
|
} : { ok: false, lease: false, error: gitError(run) };
|
|
74778
75190
|
}
|
|
74779
75191
|
var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}) => {
|
|
74780
|
-
const result =
|
|
75192
|
+
const result = spawnSync19("git", args, {
|
|
74781
75193
|
cwd,
|
|
74782
75194
|
encoding: "utf8",
|
|
74783
75195
|
env: { ...options.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
@@ -74812,18 +75224,17 @@ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((va
|
|
|
74812
75224
|
...lines.slice(-half)
|
|
74813
75225
|
].join(`
|
|
74814
75226
|
`);
|
|
74815
|
-
}, 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,
|
|
74816
|
-
const configured = git5(cwd, ["config", "--get", "gitflow.prefix.versiontag"]);
|
|
74817
|
-
return configured.ok ? configured.stdout.trim() : "v";
|
|
74818
|
-
}, isReleaseTag = (name, prefix) => name.startsWith(prefix) && /^\d/u.test(name.slice(prefix.length));
|
|
75227
|
+
}, 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, isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), lineOfTag = (tag, lines) => lines.filter((line) => tag.startsWith(line.tagPrefix) && /^\d/u.test(tag.slice(line.tagPrefix.length))).toSorted((a, b) => b.tagPrefix.length - a.tagPrefix.length)[0] ?? null;
|
|
74819
75228
|
var init_ops = __esm(() => {
|
|
74820
75229
|
init_branch();
|
|
75230
|
+
init_branch_policy();
|
|
74821
75231
|
init_commit_flavors();
|
|
74822
75232
|
init_pr_create();
|
|
74823
75233
|
init_release_tracks();
|
|
74824
75234
|
init_vcs_config();
|
|
74825
75235
|
init_workspaces();
|
|
74826
75236
|
init_redact();
|
|
75237
|
+
init_host_session();
|
|
74827
75238
|
init_ledger();
|
|
74828
75239
|
init_rev();
|
|
74829
75240
|
COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
@@ -75312,7 +75723,7 @@ var init_report = __esm(() => {
|
|
|
75312
75723
|
});
|
|
75313
75724
|
|
|
75314
75725
|
// packages/workit-core/src/forge/pr-ops.ts
|
|
75315
|
-
import { spawnSync as
|
|
75726
|
+
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
75316
75727
|
function fillFromCommits(cwd, resolved, base) {
|
|
75317
75728
|
const tracking = `refs/remotes/${resolved.baseRemote}/${base}`;
|
|
75318
75729
|
if (!base.startsWith("-"))
|
|
@@ -75320,7 +75731,7 @@ function fillFromCommits(cwd, resolved, base) {
|
|
|
75320
75731
|
const from = resolveRef(cwd, tracking) ?? resolveRef(cwd, `refs/heads/${base}`);
|
|
75321
75732
|
if (!from)
|
|
75322
75733
|
return failure3("not_found", `base ${base} is not available locally`, `git fetch ${resolved.baseRemote} ${base}`);
|
|
75323
|
-
const log =
|
|
75734
|
+
const log = spawnSync20("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
|
|
75324
75735
|
cwd,
|
|
75325
75736
|
encoding: "utf8",
|
|
75326
75737
|
timeout: GIT_TIMEOUTS.local,
|
|
@@ -75517,7 +75928,7 @@ function squashCommitList(cwd, resolved, base, head) {
|
|
|
75517
75928
|
const from = resolveRef(cwd, `refs/remotes/${resolved.baseRemote}/${base}`) ?? resolveRef(cwd, `refs/heads/${base}`);
|
|
75518
75929
|
if (!from)
|
|
75519
75930
|
return "";
|
|
75520
|
-
const log =
|
|
75931
|
+
const log = spawnSync20("git", ["log", "--reverse", "--no-merges", "--format=%B%x1e", `${from}..${head}`], {
|
|
75521
75932
|
cwd,
|
|
75522
75933
|
encoding: "utf8",
|
|
75523
75934
|
timeout: GIT_TIMEOUTS.local,
|
|
@@ -75590,7 +76001,7 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
75590
76001
|
if (reason)
|
|
75591
76002
|
return refuse(`protected_branch: --delete-branch would delete ${branch}, which ${reason}; workit never deletes it`, `workit pr merge${input.pr ? ` --pr ${doc.number}` : ""} --method ${input.method} # without --delete-branch`, { reason: "protected_branch" });
|
|
75592
76003
|
}
|
|
75593
|
-
const ledger = readLedger(cwd);
|
|
76004
|
+
const ledger = readLedger(cwd, { integrity: true });
|
|
75594
76005
|
if (!ledger.ok)
|
|
75595
76006
|
return ledger;
|
|
75596
76007
|
const check = checkVerdicts(cwd, branch, ledger.value.rows);
|
|
@@ -75622,18 +76033,54 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
75622
76033
|
unverified = { reason, recorded: String(recorded.value.id) };
|
|
75623
76034
|
} else if (check.head !== head)
|
|
75624
76035
|
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 });
|
|
76036
|
+
else if (!covered && check.accepted.reasons.includes("ledger_tampered"))
|
|
76037
|
+
return refuse(`ledger_tampered: a ledger row was edited, removed or moved, so no verdict for ${label} is accepted until it is repaired`, tamperedUnblock(cwd), { reason: "ledger_tampered", verdict: summary });
|
|
75625
76038
|
else if (!covered)
|
|
75626
76039
|
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 });
|
|
75627
76040
|
const squashMessage = squashMessageFor(cwd, resolved, input, doc);
|
|
75628
76041
|
if (squashMessage && !squashMessage.ok)
|
|
75629
76042
|
return squashMessage;
|
|
76043
|
+
const intent = appendObserved(cwd, {
|
|
76044
|
+
type: "pr.merge.intent",
|
|
76045
|
+
actor: input.actor,
|
|
76046
|
+
branch,
|
|
76047
|
+
head,
|
|
76048
|
+
pr: doc.number,
|
|
76049
|
+
base: doc.base,
|
|
76050
|
+
method: input.method,
|
|
76051
|
+
verdictId: verdict.verdictId,
|
|
76052
|
+
...unverified ? { unverified: unverified.recorded } : {},
|
|
76053
|
+
grant: grant.source
|
|
76054
|
+
});
|
|
76055
|
+
if (!intent.ok)
|
|
76056
|
+
return refuse(`intent_unrecorded: the merge intent for ${label} could not be recorded (${intent.error}); nothing was merged`, "fix the ledger (workit ledger check), then merge again", { reason: "intent_unrecorded" });
|
|
76057
|
+
const intentId = String(intent.value.id);
|
|
75630
76058
|
const merged = resolved.forge.merge(doc.number, {
|
|
75631
76059
|
sha: head,
|
|
75632
76060
|
method: input.method,
|
|
75633
76061
|
...squashMessage ? { squashMessage: squashMessage.data } : {}
|
|
75634
76062
|
});
|
|
75635
76063
|
if (!merged.ok)
|
|
75636
|
-
return
|
|
76064
|
+
return {
|
|
76065
|
+
...merged,
|
|
76066
|
+
error: `${merged.error} (merge intent ${intentId} is recorded: if the forge merged anyway, workit pr status reads it)`
|
|
76067
|
+
};
|
|
76068
|
+
const row = appendObserved(cwd, {
|
|
76069
|
+
type: "pr.merged",
|
|
76070
|
+
actor: input.actor,
|
|
76071
|
+
branch,
|
|
76072
|
+
head,
|
|
76073
|
+
pr: doc.number,
|
|
76074
|
+
base: doc.base,
|
|
76075
|
+
mergeSha: merged.data.mergeSha,
|
|
76076
|
+
method: input.method,
|
|
76077
|
+
verdictId: verdict.verdictId,
|
|
76078
|
+
...unverified ? { unverified: unverified.recorded } : {},
|
|
76079
|
+
grant: grant.source,
|
|
76080
|
+
intent: intentId,
|
|
76081
|
+
verified: false
|
|
76082
|
+
});
|
|
76083
|
+
const recorded = row.ok ? { id: String(row.value.id) } : { error: row.error };
|
|
75637
76084
|
let state = "open";
|
|
75638
76085
|
for (const delay of [0, ...VERIFY_DELAYS_MS]) {
|
|
75639
76086
|
if (delay)
|
|
@@ -75648,9 +76095,21 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
75648
76095
|
return {
|
|
75649
76096
|
ok: false,
|
|
75650
76097
|
code: "failed",
|
|
75651
|
-
error: `merge_unverified: the forge accepted the merge of ${label} but still reports it ${state}`,
|
|
75652
|
-
unblock: `workit verify-delivery merged --pr ${doc.number}
|
|
76098
|
+
error: `merge_unverified: the forge accepted the merge of ${label} but still reports it ${state}${"id" in recorded ? `; recorded as pr.merged ${recorded.id} (verified: false)` : `; the pr.merged row could not be written (${recorded.error})`}`,
|
|
76099
|
+
unblock: `workit verify-delivery merged --pr ${doc.number}`,
|
|
76100
|
+
recorded
|
|
75653
76101
|
};
|
|
76102
|
+
const confirmation = "id" in recorded ? appendObserved(cwd, {
|
|
76103
|
+
type: "pr.merge.verified",
|
|
76104
|
+
actor: input.actor,
|
|
76105
|
+
branch,
|
|
76106
|
+
head,
|
|
76107
|
+
pr: doc.number,
|
|
76108
|
+
base: doc.base,
|
|
76109
|
+
merged: recorded.id,
|
|
76110
|
+
mergeSha: merged.data.mergeSha
|
|
76111
|
+
}) : null;
|
|
76112
|
+
const confirmed = confirmation === null ? { error: "no pr.merged row to confirm" } : confirmation.ok ? { id: String(confirmation.value.id) } : { error: confirmation.error };
|
|
75654
76113
|
let deletedBranch = false;
|
|
75655
76114
|
if (input.deleteBranch) {
|
|
75656
76115
|
const deleted = deleteRemoteBranch(cwd, resolved.remote, branch, head);
|
|
@@ -75658,19 +76117,6 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
75658
76117
|
deletedBranch = deleted.ok || gone && gone.ok && gone.sha === null ? true : { error: deleted.error };
|
|
75659
76118
|
}
|
|
75660
76119
|
const mergeBack = mergeBackFor(cwd, doc.base, branch);
|
|
75661
|
-
const row = appendObserved(cwd, {
|
|
75662
|
-
type: "pr.merged",
|
|
75663
|
-
actor: input.actor,
|
|
75664
|
-
branch,
|
|
75665
|
-
head,
|
|
75666
|
-
pr: doc.number,
|
|
75667
|
-
base: doc.base,
|
|
75668
|
-
mergeSha: merged.data.mergeSha,
|
|
75669
|
-
method: input.method,
|
|
75670
|
-
verdictId: verdict.verdictId,
|
|
75671
|
-
...unverified ? { unverified: unverified.recorded } : {},
|
|
75672
|
-
grant: grant.source
|
|
75673
|
-
});
|
|
75674
76120
|
return {
|
|
75675
76121
|
ok: true,
|
|
75676
76122
|
data: {
|
|
@@ -75685,7 +76131,8 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
|
|
|
75685
76131
|
unverified,
|
|
75686
76132
|
grant: { source: grant.source },
|
|
75687
76133
|
deletedBranch,
|
|
75688
|
-
recorded
|
|
76134
|
+
recorded,
|
|
76135
|
+
confirmed,
|
|
75689
76136
|
mergeBack: mergeBack.branches,
|
|
75690
76137
|
mergeBackTrack: mergeBack.track
|
|
75691
76138
|
}
|
|
@@ -75743,8 +76190,8 @@ var init_pr_ops = __esm(() => {
|
|
|
75743
76190
|
});
|
|
75744
76191
|
|
|
75745
76192
|
// packages/workit-core/src/stack.ts
|
|
75746
|
-
import { spawnSync as
|
|
75747
|
-
import { createHash as createHash17, randomBytes as
|
|
76193
|
+
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
76194
|
+
import { createHash as createHash17, randomBytes as randomBytes4 } from "node:crypto";
|
|
75748
76195
|
import fs22 from "node:fs";
|
|
75749
76196
|
import path44 from "node:path";
|
|
75750
76197
|
function worktreeOf(cwd, branch) {
|
|
@@ -75790,7 +76237,7 @@ function gitRestack(cwd, input) {
|
|
|
75790
76237
|
if (!scratch)
|
|
75791
76238
|
return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
|
|
75792
76239
|
fs22.mkdirSync(scratch, { recursive: true });
|
|
75793
|
-
dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${
|
|
76240
|
+
dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes4(4).toString("hex")}`);
|
|
75794
76241
|
const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
|
|
75795
76242
|
if (!added.ok)
|
|
75796
76243
|
return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
|
|
@@ -75844,7 +76291,7 @@ function stacksDir(cwd) {
|
|
|
75844
76291
|
return { ok: true, data: path44.join(root.value.root, "stacks") };
|
|
75845
76292
|
}
|
|
75846
76293
|
function parseStack(raw) {
|
|
75847
|
-
if (!
|
|
76294
|
+
if (!isRecord9(raw) || !Array.isArray(raw.branches))
|
|
75848
76295
|
return null;
|
|
75849
76296
|
const name = str3(raw.name);
|
|
75850
76297
|
const trunk = str3(raw.trunk);
|
|
@@ -75852,12 +76299,12 @@ function parseStack(raw) {
|
|
|
75852
76299
|
return null;
|
|
75853
76300
|
const branches = [];
|
|
75854
76301
|
for (const item of raw.branches) {
|
|
75855
|
-
if (!
|
|
76302
|
+
if (!isRecord9(item))
|
|
75856
76303
|
return null;
|
|
75857
76304
|
const branch = str3(item.branch);
|
|
75858
76305
|
if (!branch)
|
|
75859
76306
|
return null;
|
|
75860
|
-
const merged =
|
|
76307
|
+
const merged = isRecord9(item.merged) ? {
|
|
75861
76308
|
pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
|
|
75862
76309
|
mergeSha: str3(item.merged.mergeSha),
|
|
75863
76310
|
at: str3(item.merged.at) ?? ""
|
|
@@ -75870,7 +76317,7 @@ function parseStack(raw) {
|
|
|
75870
76317
|
lastParentHead: str3(item.lastParentHead),
|
|
75871
76318
|
patchId: str3(item.patchId),
|
|
75872
76319
|
merged,
|
|
75873
|
-
...
|
|
76320
|
+
...isRecord9(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
|
|
75874
76321
|
pendingRestack: {
|
|
75875
76322
|
from: str3(item.pendingRestack.from),
|
|
75876
76323
|
tip: str3(item.pendingRestack.tip),
|
|
@@ -75999,13 +76446,13 @@ function takeLock(lock) {
|
|
|
75999
76446
|
pid: process.pid,
|
|
76000
76447
|
processStart: processStartOf(process.pid),
|
|
76001
76448
|
host: localLockHost(),
|
|
76002
|
-
nonce:
|
|
76449
|
+
nonce: randomBytes4(8).toString("hex")
|
|
76003
76450
|
};
|
|
76004
76451
|
fs22.writeFileSync(path44.join(lock, OWNER), JSON.stringify(owner));
|
|
76005
76452
|
return owner;
|
|
76006
76453
|
}
|
|
76007
76454
|
function reapLock(lock, observed) {
|
|
76008
|
-
const tomb = `${lock}.reap-${
|
|
76455
|
+
const tomb = `${lock}.reap-${randomBytes4(6).toString("hex")}`;
|
|
76009
76456
|
try {
|
|
76010
76457
|
fs22.renameSync(lock, tomb);
|
|
76011
76458
|
} catch {
|
|
@@ -76026,7 +76473,7 @@ function releaseLock(lock, owner) {
|
|
|
76026
76473
|
const { raw } = readOwner(lock);
|
|
76027
76474
|
if (raw !== JSON.stringify(owner))
|
|
76028
76475
|
return;
|
|
76029
|
-
const tomb = `${lock}.release-${
|
|
76476
|
+
const tomb = `${lock}.release-${randomBytes4(6).toString("hex")}`;
|
|
76030
76477
|
try {
|
|
76031
76478
|
fs22.renameSync(lock, tomb);
|
|
76032
76479
|
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
@@ -76256,7 +76703,7 @@ function commitKeys(cwd, range) {
|
|
|
76256
76703
|
const patches = new Map;
|
|
76257
76704
|
const diff = gitRun3(cwd, ["log", "--no-merges", "-p", "--format=commit %H", range]);
|
|
76258
76705
|
if (diff.ok && diff.stdout.trim()) {
|
|
76259
|
-
const ids =
|
|
76706
|
+
const ids = spawnSync21("git", ["patch-id", "--stable"], {
|
|
76260
76707
|
cwd,
|
|
76261
76708
|
input: diff.stdout,
|
|
76262
76709
|
encoding: "utf8",
|
|
@@ -76349,7 +76796,7 @@ function stackStatus(ctx, stack) {
|
|
|
76349
76796
|
const mismatch = repoMismatch(ctx, stack);
|
|
76350
76797
|
if (mismatch)
|
|
76351
76798
|
return mismatch;
|
|
76352
|
-
const ledger = readLedger(ctx.cwd);
|
|
76799
|
+
const ledger = readLedger(ctx.cwd, { integrity: true });
|
|
76353
76800
|
if (!ledger.ok)
|
|
76354
76801
|
return stackFail(ledger.code, ledger.error, ledger.unblock);
|
|
76355
76802
|
const remote = remoteFor(ctx.cwd, ctx.resolved, stack.trunk);
|
|
@@ -76773,7 +77220,7 @@ async function landStack(ctx, stack, options) {
|
|
|
76773
77220
|
absorb(synced.data);
|
|
76774
77221
|
}
|
|
76775
77222
|
const ledgerRows = () => {
|
|
76776
|
-
const ledger = readLedger(ctx.cwd);
|
|
77223
|
+
const ledger = readLedger(ctx.cwd, { integrity: true });
|
|
76777
77224
|
return ledger.ok ? ledger.value.rows : [];
|
|
76778
77225
|
};
|
|
76779
77226
|
const simulated = new Set;
|
|
@@ -76880,7 +77327,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
|
|
|
76880
77327
|
...unblock ? { unblock } : {},
|
|
76881
77328
|
...data ? { data } : {}
|
|
76882
77329
|
}), fromForge = (result) => stackFail(result.code, result.error, result.unblock), stackDeps, gitRun3 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
76883
|
-
const run =
|
|
77330
|
+
const run = spawnSync21("git", args, {
|
|
76884
77331
|
cwd,
|
|
76885
77332
|
encoding: "utf8",
|
|
76886
77333
|
timeout: timeoutMs,
|
|
@@ -76920,7 +77367,7 @@ ${run.stdout}`.split(`
|
|
|
76920
77367
|
return false;
|
|
76921
77368
|
}
|
|
76922
77369
|
return true;
|
|
76923
|
-
},
|
|
77370
|
+
}, isRecord9 = (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) => {
|
|
76924
77371
|
let raw = null;
|
|
76925
77372
|
try {
|
|
76926
77373
|
raw = fs22.readFileSync(path44.join(lock, OWNER), "utf8");
|
|
@@ -76961,7 +77408,7 @@ var init_stack = __esm(() => {
|
|
|
76961
77408
|
});
|
|
76962
77409
|
|
|
76963
77410
|
// packages/workit-core/src/forge/pr-edit.ts
|
|
76964
|
-
import { spawnSync as
|
|
77411
|
+
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
76965
77412
|
function openPr(cwd, resolved, pr) {
|
|
76966
77413
|
const grant = requireGrant(cwd, "pr");
|
|
76967
77414
|
if (!grant.allowed)
|
|
@@ -77126,7 +77573,7 @@ function replyToThread(cwd, resolved, input) {
|
|
|
77126
77573
|
});
|
|
77127
77574
|
}
|
|
77128
77575
|
var label = (resolved, number) => `${resolved.forge.kind === "github" ? "PR #" : "MR !"}${number}`, git6 = (cwd, args) => {
|
|
77129
|
-
const run =
|
|
77576
|
+
const run = spawnSync22("git", args, {
|
|
77130
77577
|
cwd,
|
|
77131
77578
|
encoding: "utf8",
|
|
77132
77579
|
timeout: GIT_TIMEOUTS.local,
|
|
@@ -77146,7 +77593,7 @@ var init_pr_edit = __esm(() => {
|
|
|
77146
77593
|
});
|
|
77147
77594
|
|
|
77148
77595
|
// packages/workit-core/src/forge/exec.ts
|
|
77149
|
-
import { spawnSync as
|
|
77596
|
+
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
77150
77597
|
import fs23 from "node:fs";
|
|
77151
77598
|
import path45 from "node:path";
|
|
77152
77599
|
function findExecutable(bin, env) {
|
|
@@ -77181,7 +77628,7 @@ function systemRunner(kind, apiHost, env = process.env) {
|
|
|
77181
77628
|
if (!exe)
|
|
77182
77629
|
return { status: null, stdout: "", stderr: "", timedOut: false, missing: true };
|
|
77183
77630
|
const token = options.token ? kind === "github" ? { GH_TOKEN: options.token, GH_ENTERPRISE_TOKEN: options.token } : { GITLAB_TOKEN: options.token } : {};
|
|
77184
|
-
const result =
|
|
77631
|
+
const result = spawnSync23(exe, [...args], {
|
|
77185
77632
|
encoding: "utf8",
|
|
77186
77633
|
env: { ...childEnv, ...token },
|
|
77187
77634
|
maxBuffer: MAX_BUFFER2,
|
|
@@ -77678,6 +78125,24 @@ function createGitHubForge(options) {
|
|
|
77678
78125
|
if (node?.pullRequest?.number !== number)
|
|
77679
78126
|
return success2(null);
|
|
77680
78127
|
return success2(node.isResolved ? "resolved" : "open");
|
|
78128
|
+
},
|
|
78129
|
+
findRelease(tag) {
|
|
78130
|
+
const found = rest(`repos/${repo}/releases/tags/${encodeRef(tag)}`, `release ${tag}`);
|
|
78131
|
+
if (!found.ok)
|
|
78132
|
+
return found.code === "not_found" ? success2(null) : found;
|
|
78133
|
+
return success2({
|
|
78134
|
+
tag: found.data.tag_name ?? tag,
|
|
78135
|
+
url: safeUrl(found.data.html_url ?? null)
|
|
78136
|
+
});
|
|
78137
|
+
},
|
|
78138
|
+
createRelease(input) {
|
|
78139
|
+
const created = apiWrite(runner, "gh", apiHost, ["api", "-X", "POST", `repos/${repo}/releases`, "--input", "-"], `release ${input.tag}`, undefined, JSON.stringify({ tag_name: input.tag, name: input.name, body: input.body }));
|
|
78140
|
+
if (!created.ok)
|
|
78141
|
+
return created;
|
|
78142
|
+
return success2({
|
|
78143
|
+
tag: created.data.tag_name ?? input.tag,
|
|
78144
|
+
url: safeUrl(created.data.html_url ?? null)
|
|
78145
|
+
});
|
|
77681
78146
|
}
|
|
77682
78147
|
};
|
|
77683
78148
|
}
|
|
@@ -78063,6 +78528,24 @@ ${squash.body}` : squash.subject
|
|
|
78063
78528
|
if (!notes.some((note) => note.resolvable === true))
|
|
78064
78529
|
return success2(null);
|
|
78065
78530
|
return success2(notes.some((note) => note.resolvable === true && note.resolved !== true) ? "open" : "resolved");
|
|
78531
|
+
},
|
|
78532
|
+
findRelease(tag) {
|
|
78533
|
+
const found = apiJson(runner, "glab", apiHost, api(`${project}/releases/${encodeURIComponent(tag)}`), `release ${tag}`);
|
|
78534
|
+
if (!found.ok)
|
|
78535
|
+
return found.code === "not_found" ? success2(null) : found;
|
|
78536
|
+
return success2({
|
|
78537
|
+
tag: found.data.tag_name ?? tag,
|
|
78538
|
+
url: safeUrl2(found.data["_links"]?.self ?? null)
|
|
78539
|
+
});
|
|
78540
|
+
},
|
|
78541
|
+
createRelease(input) {
|
|
78542
|
+
const created = apiWrite(runner, "glab", apiHost, ["api", "-X", "POST", `${project}/releases`, "--input", "-"], `release ${input.tag}`, undefined, JSON.stringify({ tag_name: input.tag, name: input.name, description: input.body }));
|
|
78543
|
+
if (!created.ok)
|
|
78544
|
+
return created;
|
|
78545
|
+
return success2({
|
|
78546
|
+
tag: created.data.tag_name ?? input.tag,
|
|
78547
|
+
url: safeUrl2(created.data["_links"]?.self ?? null)
|
|
78548
|
+
});
|
|
78066
78549
|
}
|
|
78067
78550
|
};
|
|
78068
78551
|
}
|
|
@@ -78145,6 +78628,14 @@ function resolveCredential(workspace, derived, run, scope) {
|
|
|
78145
78628
|
const explicit = typeof workspaceHost === "string" && workspaceHost.trim().toLowerCase().replace(/^https?:\/\//u, "").replace(/\/.*$/u, "") === apiHost;
|
|
78146
78629
|
if (derived.via === "host_name" && !explicit)
|
|
78147
78630
|
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`);
|
|
78631
|
+
if (process.platform !== "win32") {
|
|
78632
|
+
let mode = 0;
|
|
78633
|
+
try {
|
|
78634
|
+
mode = fs24.statSync(tokenFile.trim()).mode;
|
|
78635
|
+
} catch {}
|
|
78636
|
+
if ((mode & 63) !== 0)
|
|
78637
|
+
return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile is readable by group or other (mode ${(mode & 511).toString(8).padStart(4, "0")})`, `chmod 600 ${tokenFile.trim()}`);
|
|
78638
|
+
}
|
|
78148
78639
|
let token = "";
|
|
78149
78640
|
try {
|
|
78150
78641
|
token = fs24.readFileSync(tokenFile.trim(), "utf8").trim();
|
|
@@ -78568,17 +79059,20 @@ async function status(argv, io) {
|
|
|
78568
79059
|
});
|
|
78569
79060
|
}
|
|
78570
79061
|
function verdictBlock(cwd, branch, doc) {
|
|
78571
|
-
const ledger = readLedger(cwd);
|
|
79062
|
+
const ledger = readLedger(cwd, { integrity: true });
|
|
78572
79063
|
if (!ledger.ok)
|
|
78573
79064
|
return null;
|
|
78574
79065
|
const check = checkVerdicts(cwd, branch, ledger.value.rows);
|
|
78575
79066
|
let landed = null;
|
|
78576
79067
|
if (doc.state === "merged") {
|
|
78577
|
-
const
|
|
79068
|
+
const mine = (type) => ledger.value.rows.findLast((candidate) => candidate.type === type && candidate.observer === "workit_cli" && candidate.integrity !== "unverified" && candidate.pr === doc.number && candidate.base === doc.base);
|
|
79069
|
+
const merged = mine("pr.merged");
|
|
79070
|
+
const row = merged ?? mine("pr.merge.intent");
|
|
78578
79071
|
landed = row ? {
|
|
78579
79072
|
head: typeof row.head === "string" ? row.head : null,
|
|
78580
79073
|
verdictId: typeof row.verdictId === "string" ? row.verdictId : null,
|
|
78581
|
-
unverified: row.unverified !== undefined && row.unverified !== null
|
|
79074
|
+
unverified: row.unverified !== undefined && row.unverified !== null,
|
|
79075
|
+
...merged ? {} : { intentOnly: true }
|
|
78582
79076
|
} : "untracked";
|
|
78583
79077
|
}
|
|
78584
79078
|
return {
|
|
@@ -78749,7 +79243,7 @@ async function merge3(argv, io) {
|
|
|
78749
79243
|
if (!result.ok)
|
|
78750
79244
|
return emit(io, fail(result.code, result.error, {
|
|
78751
79245
|
unblock: result.unblock,
|
|
78752
|
-
data: "refusal" in result && result.refusal ? result.refusal : {}
|
|
79246
|
+
data: "refusal" in result && result.refusal ? result.refusal : ("recorded" in result) && result.recorded ? { recorded: result.recorded } : {}
|
|
78753
79247
|
}));
|
|
78754
79248
|
return emit(io, ok(result.data), (data) => [
|
|
78755
79249
|
`merged ${connected.data.forge.kind === "github" ? "PR #" : "MR !"}${data.number} (${data.method}) at head ${data.head.slice(0, 12)}${data.mergeSha ? ` -> ${data.mergeSha.slice(0, 12)}` : ""}`,
|
|
@@ -78758,7 +79252,8 @@ async function merge3(argv, io) {
|
|
|
78758
79252
|
`merge back: ${data.base} is the ${data.mergeBackTrack} production branch; bring it into ${data.mergeBack.join(", ")}`
|
|
78759
79253
|
] : [],
|
|
78760
79254
|
...data.deletedBranch === true ? [`deleted ${data.branch}`] : typeof data.deletedBranch === "object" ? [`branch not deleted: ${data.deletedBranch.error}`] : [],
|
|
78761
|
-
..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
|
|
79255
|
+
..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : [],
|
|
79256
|
+
..."error" in data.confirmed && !("error" in data.recorded) ? [`ledger: merge not confirmed in the ledger (${data.confirmed.error})`] : []
|
|
78762
79257
|
]);
|
|
78763
79258
|
}
|
|
78764
79259
|
async function ready(argv, io) {
|
|
@@ -78908,7 +79403,7 @@ async function run11(argv, io) {
|
|
|
78908
79403
|
return reply(rest, io);
|
|
78909
79404
|
return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE4);
|
|
78910
79405
|
}
|
|
78911
|
-
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;
|
|
79406
|
+
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)}${landed.intentOnly ? " (merge intent recorded; the merge call itself failed)" : ""}`, METHODS2;
|
|
78912
79407
|
var init_pr = __esm(() => {
|
|
78913
79408
|
init_pr_body();
|
|
78914
79409
|
init_pr_edit();
|
|
@@ -79433,7 +79928,7 @@ var init_git2 = __esm(() => {
|
|
|
79433
79928
|
});
|
|
79434
79929
|
|
|
79435
79930
|
// packages/workit-core/src/forge/verify.ts
|
|
79436
|
-
import { spawnSync as
|
|
79931
|
+
import { spawnSync as spawnSync24 } from "node:child_process";
|
|
79437
79932
|
function finish(cwd, report, actor) {
|
|
79438
79933
|
const delivered = report.observations.length > 0 && report.observations.every((entry) => entry.ok);
|
|
79439
79934
|
const out = { ...report, delivered };
|
|
@@ -79646,7 +80141,7 @@ var NPM_NAME, SEMVER2, SAFE_ARG, systemNpm = (args) => {
|
|
|
79646
80141
|
};
|
|
79647
80142
|
const found = process.platform === "win32" ? findExecutable("npm.cmd", process.env) : null;
|
|
79648
80143
|
const npmCmd = found && !/["%!^]/u.test(found) ? `"${found}"` : "npm.cmd";
|
|
79649
|
-
const result = process.platform === "win32" ?
|
|
80144
|
+
const result = process.platform === "win32" ? spawnSync24(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync24("npm", [...args], options);
|
|
79650
80145
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
79651
80146
|
}, short3 = (sha) => sha ? sha.slice(0, 12) : "(absent)", npmView = (stdout) => {
|
|
79652
80147
|
try {
|
|
@@ -79668,12 +80163,700 @@ var init_verify = __esm(() => {
|
|
|
79668
80163
|
SAFE_ARG = /^[0-9A-Za-z@/._~+-]+$/u;
|
|
79669
80164
|
});
|
|
79670
80165
|
|
|
80166
|
+
// packages/workit-core/src/git/release.ts
|
|
80167
|
+
function lineFor(cwd, track) {
|
|
80168
|
+
const lines = releaseLinesOf(cwd);
|
|
80169
|
+
let name = track;
|
|
80170
|
+
if (name === null && lines.length > 1) {
|
|
80171
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
80172
|
+
if (resolved.ok === false)
|
|
80173
|
+
return failure3("blocked", String(resolved.error));
|
|
80174
|
+
if (resolved.releaseTrack?.blocking)
|
|
80175
|
+
return failure3("blocked", String(resolved.releaseTrack.blocking), "pass --track <name>");
|
|
80176
|
+
name = resolved.releaseTrack?.track?.name ?? null;
|
|
80177
|
+
}
|
|
80178
|
+
const line = lines.find((entry) => entry.track === name);
|
|
80179
|
+
if (!line)
|
|
80180
|
+
return failure3("invalid_input", `no release track ${JSON.stringify(name)} (configured: ${lines.map((entry) => entry.track).filter(Boolean).join(", ") || "none"})`);
|
|
80181
|
+
return success2(line);
|
|
80182
|
+
}
|
|
80183
|
+
function releaseStart(cwd, input) {
|
|
80184
|
+
const version = input.version.trim();
|
|
80185
|
+
if (!validVersion(version))
|
|
80186
|
+
return failure3("invalid_input", `${JSON.stringify(version)} is not a version (e.g. 1.4.0)`);
|
|
80187
|
+
const kind = input.hotfix ? "hotfix" : "release";
|
|
80188
|
+
const lineResult = lineFor(cwd, input.track ?? null);
|
|
80189
|
+
if (!lineResult.ok)
|
|
80190
|
+
return lineResult;
|
|
80191
|
+
const line = lineResult.data;
|
|
80192
|
+
const template = templateOf(line, kind);
|
|
80193
|
+
const existing = git5(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]).stdout.split(`
|
|
80194
|
+
`).filter((name) => name && versionIn(template, name) !== null);
|
|
80195
|
+
if (existing.length)
|
|
80196
|
+
return failure3("blocked", `release_exists: ${existing[0]} is still open; git-flow finishes one ${kind} before starting the next`, `workit release finish # on ${existing[0]}`);
|
|
80197
|
+
const tag = `${line.tagPrefix}${version}`;
|
|
80198
|
+
if (resolveRef(cwd, `refs/tags/${tag}`))
|
|
80199
|
+
return failure3("blocked", `tag_exists: ${tag} already exists`, "start the next version instead");
|
|
80200
|
+
const base = kind === "hotfix" ? line.production : line.integration;
|
|
80201
|
+
const remote = pushRemoteName(cwd, base);
|
|
80202
|
+
if (remote) {
|
|
80203
|
+
fetchRefs(cwd, remote, [`+refs/heads/${base}:refs/remotes/${remote}/${base}`]);
|
|
80204
|
+
const local = resolveRef(cwd, `refs/heads/${base}`);
|
|
80205
|
+
const tracked = resolveRef(cwd, `refs/remotes/${remote}/${base}`);
|
|
80206
|
+
if (local && tracked && local !== tracked && !git5(cwd, ["merge-base", "--is-ancestor", local, tracked]).ok)
|
|
80207
|
+
return failure3("blocked", `base_unpushed: local ${base} has commits ${remote}/${base} lacks; the ${kind} would start without them`, `land them on ${remote}/${base} first, or: git switch ${base} && git reset --keep ${remote}/${base}`);
|
|
80208
|
+
}
|
|
80209
|
+
const branched = gitBranch(cwd, {
|
|
80210
|
+
name: branchFor(line, kind, version),
|
|
80211
|
+
base,
|
|
80212
|
+
...line.track ? { track: line.track } : {}
|
|
80213
|
+
});
|
|
80214
|
+
if (!branched.ok)
|
|
80215
|
+
return branched;
|
|
80216
|
+
record2(cwd, input.actor, {
|
|
80217
|
+
type: "release.started",
|
|
80218
|
+
version,
|
|
80219
|
+
kind,
|
|
80220
|
+
branch: branched.data.branch,
|
|
80221
|
+
head: branched.data.baseSha,
|
|
80222
|
+
base,
|
|
80223
|
+
track: line.track,
|
|
80224
|
+
tag
|
|
80225
|
+
});
|
|
80226
|
+
return success2({
|
|
80227
|
+
version,
|
|
80228
|
+
kind,
|
|
80229
|
+
branch: branched.data.branch,
|
|
80230
|
+
base,
|
|
80231
|
+
baseSha: branched.data.baseSha,
|
|
80232
|
+
track: line.track,
|
|
80233
|
+
tag,
|
|
80234
|
+
notes: branched.data.notes
|
|
80235
|
+
});
|
|
80236
|
+
}
|
|
80237
|
+
function locate(cwd, version, track) {
|
|
80238
|
+
const lines = releaseLinesOf(cwd).filter((line) => track === null || line.track === track);
|
|
80239
|
+
if (!lines.length)
|
|
80240
|
+
return failure3("invalid_input", `no release track ${JSON.stringify(track)}`);
|
|
80241
|
+
const rows = releaseRows(cwd);
|
|
80242
|
+
const started = rows.filter((row) => row.type === "release.started");
|
|
80243
|
+
const finished = new Set(rows.filter((row) => row.type === "release.finished").map((row) => String(row.branch)));
|
|
80244
|
+
const startedRow = (branch) => started.findLast((row) => row.branch === branch && !finished.has(branch));
|
|
80245
|
+
const lineOf = (branch, candidates) => {
|
|
80246
|
+
const row = startedRow(branch);
|
|
80247
|
+
const byRow = row ? candidates.find((line) => line.track === (row.track ?? null)) : undefined;
|
|
80248
|
+
if (byRow)
|
|
80249
|
+
return byRow;
|
|
80250
|
+
const resolved = vcsConfig("resolve", cwd, { branch });
|
|
80251
|
+
const name = resolved.ok === false ? null : resolved.releaseTrack?.track?.name ?? null;
|
|
80252
|
+
return candidates.find((line) => line.track === name) ?? candidates[0];
|
|
80253
|
+
};
|
|
80254
|
+
const match = (branch) => {
|
|
80255
|
+
const hits = [];
|
|
80256
|
+
for (const line of lines)
|
|
80257
|
+
for (const kind of KINDS) {
|
|
80258
|
+
const found = versionIn(templateOf(line, kind), branch);
|
|
80259
|
+
if (found)
|
|
80260
|
+
hits.push({ line, kind, version: found });
|
|
80261
|
+
}
|
|
80262
|
+
if (!hits.length)
|
|
80263
|
+
return null;
|
|
80264
|
+
const line = lineOf(branch, hits.map((hit) => hit.line));
|
|
80265
|
+
const hit = hits.find((entry) => entry.line === line) ?? hits[0];
|
|
80266
|
+
return { ...hit, branch };
|
|
80267
|
+
};
|
|
80268
|
+
if (version !== null) {
|
|
80269
|
+
if (!validVersion(version))
|
|
80270
|
+
return failure3("invalid_input", `${JSON.stringify(version)} is not a version`);
|
|
80271
|
+
for (const line of lines)
|
|
80272
|
+
for (const kind of KINDS) {
|
|
80273
|
+
const branch = branchFor(line, kind, version);
|
|
80274
|
+
if (resolveRef(cwd, `refs/heads/${branch}`) || startedRow(branch)) {
|
|
80275
|
+
const located = match(branch);
|
|
80276
|
+
if (located)
|
|
80277
|
+
return success2(located);
|
|
80278
|
+
}
|
|
80279
|
+
}
|
|
80280
|
+
return failure3("not_found", `no release or hotfix branch for ${version} here, and none this ledger started`);
|
|
80281
|
+
}
|
|
80282
|
+
const current = currentBranch(cwd);
|
|
80283
|
+
const here = current ? match(current) : null;
|
|
80284
|
+
if (here)
|
|
80285
|
+
return success2(here);
|
|
80286
|
+
const open = started.findLast((row) => !finished.has(String(row.branch)));
|
|
80287
|
+
if (open) {
|
|
80288
|
+
const located = match(String(open.branch));
|
|
80289
|
+
if (located)
|
|
80290
|
+
return success2(located);
|
|
80291
|
+
}
|
|
80292
|
+
const branches = git5(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]).stdout.split(`
|
|
80293
|
+
`).map((name) => name ? match(name) : null).filter((entry) => entry !== null);
|
|
80294
|
+
if (branches.length === 1)
|
|
80295
|
+
return success2(branches[0]);
|
|
80296
|
+
return failure3(branches.length ? "invalid_input" : "not_found", branches.length ? `several releases are open (${branches.map((entry) => entry.branch).join(", ")}); name one` : "no release or hotfix branch to finish", branches.length ? `workit release finish ${branches[0].version}` : "workit release start <version>");
|
|
80297
|
+
}
|
|
80298
|
+
function changelogSection(cwd, ref, version) {
|
|
80299
|
+
const shown = git5(cwd, ["show", `${ref}:CHANGELOG.md`]);
|
|
80300
|
+
if (!shown.ok)
|
|
80301
|
+
return null;
|
|
80302
|
+
const lines = shown.stdout.split(/\r?\n/u);
|
|
80303
|
+
const escaped = version.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
80304
|
+
const heading = new RegExp(`^(#{1,3})\\s+\\[?v?${escaped}\\]?(?:[\\s(]|$)`, "u");
|
|
80305
|
+
const start = lines.findIndex((line) => heading.test(line));
|
|
80306
|
+
if (start < 0)
|
|
80307
|
+
return null;
|
|
80308
|
+
const level = heading.exec(lines[start])?.[1].length ?? 2;
|
|
80309
|
+
let end = lines.length;
|
|
80310
|
+
for (let index = start + 1;index < lines.length; index += 1) {
|
|
80311
|
+
const hashes = /^(#+)\s/u.exec(lines[index])?.[1].length;
|
|
80312
|
+
if (hashes !== undefined && hashes <= level) {
|
|
80313
|
+
end = index;
|
|
80314
|
+
break;
|
|
80315
|
+
}
|
|
80316
|
+
}
|
|
80317
|
+
const body = lines.slice(start + 1, end).join(`
|
|
80318
|
+
`).trim();
|
|
80319
|
+
return body || null;
|
|
80320
|
+
}
|
|
80321
|
+
function planReleaseFinish(cwd, input) {
|
|
80322
|
+
const progress = operationInProgress(cwd);
|
|
80323
|
+
if (progress)
|
|
80324
|
+
return inProgressFailure(progress.operation, "finish the release");
|
|
80325
|
+
const located = locate(cwd, input.version?.trim() || null, input.track ?? null);
|
|
80326
|
+
if (!located.ok)
|
|
80327
|
+
return located;
|
|
80328
|
+
const { line, kind, version, branch } = located.data;
|
|
80329
|
+
const notes = [];
|
|
80330
|
+
const dirty = git5(cwd, ["status", "--porcelain", "--untracked-files=no"]).stdout.trim();
|
|
80331
|
+
if (dirty)
|
|
80332
|
+
return failure3("blocked", "dirty_worktree: tracked files have uncommitted changes; a release finish merges on a clean tree", "workit git commit -m '<msg>' -- <paths> # on the release branch, or stash them");
|
|
80333
|
+
const tag = `${line.tagPrefix}${version}`;
|
|
80334
|
+
const rows = releaseRows(cwd).filter((row) => row.branch === branch || row.release === branch);
|
|
80335
|
+
const recordedOpen = rows.findLast((row) => row.type === "release.hotfix_target");
|
|
80336
|
+
const openRelease = kind !== "hotfix" ? null : typeof recordedOpen?.target === "string" ? recordedOpen.target : git5(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads"]).stdout.split(`
|
|
80337
|
+
`).find((name) => name && versionIn(templateOf(line, "release"), name) !== null) ?? null;
|
|
80338
|
+
const mergeBack = openRelease ? [...new Set(line.mergeBack.map((name) => name === line.integration ? openRelease : name))] : line.mergeBack;
|
|
80339
|
+
if (openRelease)
|
|
80340
|
+
notes.push(`${openRelease} is open: the hotfix merges back into it instead of ${line.integration}`);
|
|
80341
|
+
const remote = pushRemoteName(cwd, line.production);
|
|
80342
|
+
if (remote) {
|
|
80343
|
+
const wanted = [line.production, ...mergeBack, branch];
|
|
80344
|
+
for (const name of wanted)
|
|
80345
|
+
fetchRefs(cwd, remote, [`+refs/heads/${name}:refs/remotes/${remote}/${name}`]);
|
|
80346
|
+
}
|
|
80347
|
+
const branchTip = resolveRef(cwd, `refs/heads/${branch}`);
|
|
80348
|
+
const remoteBranch = remote ? resolveRef(cwd, `refs/remotes/${remote}/${branch}`) : null;
|
|
80349
|
+
const mergedRow = rows.findLast((row) => row.type === "release.merged");
|
|
80350
|
+
const deletedRow = rows.findLast((row) => row.type === "release.branch_deleted");
|
|
80351
|
+
const recordedTip = [mergedRow?.releaseTip, deletedRow?.head].find((value) => typeof value === "string");
|
|
80352
|
+
const releaseTip = branchTip ?? recordedTip ?? null;
|
|
80353
|
+
if (!releaseTip)
|
|
80354
|
+
return failure3("not_found", `${branch} is gone and this ledger did not record its merge; nothing to resume`);
|
|
80355
|
+
for (const name of [line.production, ...mergeBack]) {
|
|
80356
|
+
const local = resolveRef(cwd, `refs/heads/${name}`);
|
|
80357
|
+
const tracked = remote ? resolveRef(cwd, `refs/remotes/${remote}/${name}`) : null;
|
|
80358
|
+
if (!local && tracked) {
|
|
80359
|
+
const created = git5(cwd, ["branch", "--no-track", name, tracked]);
|
|
80360
|
+
if (!created.ok)
|
|
80361
|
+
return failure3("failed", `could not create local ${name}: ${gitError(created)}`);
|
|
80362
|
+
notes.push(`created local ${name} from ${remote}/${name}`);
|
|
80363
|
+
continue;
|
|
80364
|
+
}
|
|
80365
|
+
if (!local)
|
|
80366
|
+
return failure3("not_found", `${name} exists neither locally nor on ${remote ?? "a remote"}`);
|
|
80367
|
+
if (!tracked || name === openRelease)
|
|
80368
|
+
continue;
|
|
80369
|
+
if (!isAncestor3(cwd, tracked, local))
|
|
80370
|
+
return failure3("blocked", `branch_behind: local ${name} is behind or diverged from ${remote}/${name}; git-flow finishes only onto up-to-date branches`, `git switch ${name} && git merge --ff-only ${remote}/${name} # then: workit release finish ${version}`);
|
|
80371
|
+
const productionTracked = remote ? resolveRef(cwd, `refs/remotes/${remote}/${line.production}`) : null;
|
|
80372
|
+
const foreign = git5(cwd, [
|
|
80373
|
+
"rev-list",
|
|
80374
|
+
local,
|
|
80375
|
+
`^${tracked}`,
|
|
80376
|
+
`^${releaseTip}`,
|
|
80377
|
+
...productionTracked ? [`^${productionTracked}`] : []
|
|
80378
|
+
]).stdout.split(`
|
|
80379
|
+
`).filter(Boolean).filter((sha) => !(git5(cwd, ["rev-parse", "-q", "--verify", `${sha}^2`]).ok && isAncestor3(cwd, releaseTip, sha)));
|
|
80380
|
+
if (foreign.length)
|
|
80381
|
+
return failure3("blocked", `branch_ahead: local ${name} has ${foreign.length} commit(s) ${remote}/${name} lacks that are not part of ${branch}; the finish push would publish them`, `land them through review first, or: git switch ${name} && git reset --keep ${remote}/${name} # then: workit release finish ${version}`);
|
|
80382
|
+
}
|
|
80383
|
+
if (branchTip && remoteBranch && !isAncestor3(cwd, remoteBranch, branchTip))
|
|
80384
|
+
return failure3("blocked", `release_behind: ${remote}/${branch} has commits local ${branch} lacks`, `git switch ${branch} && git merge --ff-only ${remote}/${branch}`);
|
|
80385
|
+
const production = resolveRef(cwd, `refs/heads/${line.production}`);
|
|
80386
|
+
const merged = isAncestor3(cwd, releaseTip, production);
|
|
80387
|
+
const localTag = resolveRef(cwd, `refs/tags/${tag}`);
|
|
80388
|
+
if (localTag && !(isAncestor3(cwd, localTag, production) && isAncestor3(cwd, releaseTip, localTag)))
|
|
80389
|
+
return failure3("blocked", `tag_exists: ${tag} (${localTag.slice(0, 12)}) is not this release's merge on ${line.production}`, "a release tag never moves; finish under the next version");
|
|
80390
|
+
if (remote) {
|
|
80391
|
+
const remoteTag = remoteRefTip(cwd, remote, `refs/tags/${tag}`);
|
|
80392
|
+
if (remoteTag.ok && remoteTag.sha && remoteTag.sha !== localTag)
|
|
80393
|
+
return failure3("blocked", `tag_exists: ${remote} already has ${tag} at ${remoteTag.sha.slice(0, 12)}`, "a release tag never moves; finish under the next version");
|
|
80394
|
+
}
|
|
80395
|
+
const recordedMerge = typeof mergedRow?.merge === "string" ? mergedRow.merge : null;
|
|
80396
|
+
const tagTarget = localTag ?? (merged ? recordedMerge ?? production : null);
|
|
80397
|
+
const state = {
|
|
80398
|
+
merged,
|
|
80399
|
+
tagged: localTag !== null,
|
|
80400
|
+
mergedBack: Object.fromEntries(mergeBack.map((name) => [
|
|
80401
|
+
name,
|
|
80402
|
+
localTag !== null && isAncestor3(cwd, localTag, resolveRef(cwd, `refs/heads/${name}`))
|
|
80403
|
+
])),
|
|
80404
|
+
branchDeleted: branchTip === null
|
|
80405
|
+
};
|
|
80406
|
+
return success2({
|
|
80407
|
+
version,
|
|
80408
|
+
kind,
|
|
80409
|
+
branch,
|
|
80410
|
+
track: line.track,
|
|
80411
|
+
tag,
|
|
80412
|
+
production: line.production,
|
|
80413
|
+
mergeBack,
|
|
80414
|
+
pushBranches: [line.production, ...mergeBack.filter((name) => name !== openRelease)],
|
|
80415
|
+
openRelease,
|
|
80416
|
+
gitflow: line.gitflow,
|
|
80417
|
+
remote,
|
|
80418
|
+
releaseTip,
|
|
80419
|
+
published: remoteBranch,
|
|
80420
|
+
tagTarget,
|
|
80421
|
+
mergeRecorded: mergedRow !== undefined,
|
|
80422
|
+
state,
|
|
80423
|
+
changelog: changelogSection(cwd, branchTip ?? production, version),
|
|
80424
|
+
notes,
|
|
80425
|
+
warnings: integrationPrWarning(cwd, line.production)
|
|
80426
|
+
});
|
|
80427
|
+
}
|
|
80428
|
+
function integrationPrWarning(cwd, production) {
|
|
80429
|
+
try {
|
|
80430
|
+
const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
80431
|
+
return selected?.integration === "pr" ? [
|
|
80432
|
+
`the workspace integration mode is pr: the remote may require pull requests on ${production}, which would reject the finish push`
|
|
80433
|
+
] : [];
|
|
80434
|
+
} catch {
|
|
80435
|
+
return [];
|
|
80436
|
+
}
|
|
80437
|
+
}
|
|
80438
|
+
function finishLocally(cwd, plan, actor) {
|
|
80439
|
+
const ran = [];
|
|
80440
|
+
const base = { version: plan.version, release: plan.branch, tag: plan.tag, track: plan.track };
|
|
80441
|
+
let tagTarget = plan.tagTarget;
|
|
80442
|
+
if (plan.openRelease && !releaseRows(cwd).some((row) => row.type === "release.hotfix_target" && row.release === plan.branch))
|
|
80443
|
+
record2(cwd, actor, {
|
|
80444
|
+
type: "release.hotfix_target",
|
|
80445
|
+
...base,
|
|
80446
|
+
branch: plan.branch,
|
|
80447
|
+
target: plan.openRelease
|
|
80448
|
+
});
|
|
80449
|
+
if (plan.state.merged && !plan.mergeRecorded && tagTarget)
|
|
80450
|
+
record2(cwd, actor, {
|
|
80451
|
+
type: "release.merged",
|
|
80452
|
+
...base,
|
|
80453
|
+
branch: plan.production,
|
|
80454
|
+
head: tagTarget,
|
|
80455
|
+
merge: tagTarget,
|
|
80456
|
+
releaseTip: plan.releaseTip,
|
|
80457
|
+
observed: true
|
|
80458
|
+
});
|
|
80459
|
+
if (!plan.state.merged) {
|
|
80460
|
+
const on = switchTo(cwd, plan.production);
|
|
80461
|
+
if (!on.ok)
|
|
80462
|
+
return on;
|
|
80463
|
+
const merged = mergeNoFf(cwd, plan.branch, plan.production, plan.version);
|
|
80464
|
+
if (!merged.ok)
|
|
80465
|
+
return merged;
|
|
80466
|
+
tagTarget = merged.data;
|
|
80467
|
+
ran.push("merge");
|
|
80468
|
+
record2(cwd, actor, {
|
|
80469
|
+
type: "release.merged",
|
|
80470
|
+
...base,
|
|
80471
|
+
branch: plan.production,
|
|
80472
|
+
head: merged.data,
|
|
80473
|
+
merge: merged.data,
|
|
80474
|
+
releaseTip: plan.releaseTip
|
|
80475
|
+
});
|
|
80476
|
+
}
|
|
80477
|
+
if (!tagTarget)
|
|
80478
|
+
return failure3("failed", `cannot tell which ${plan.production} commit to tag`);
|
|
80479
|
+
if (!plan.state.tagged) {
|
|
80480
|
+
const message = plan.changelog ?? `Release ${plan.version}`;
|
|
80481
|
+
const tagged = git5(cwd, [
|
|
80482
|
+
"tag",
|
|
80483
|
+
"-a",
|
|
80484
|
+
"--cleanup=verbatim",
|
|
80485
|
+
plan.tag,
|
|
80486
|
+
"-m",
|
|
80487
|
+
message,
|
|
80488
|
+
tagTarget
|
|
80489
|
+
]);
|
|
80490
|
+
if (!tagged.ok)
|
|
80491
|
+
return failure3("failed", `git tag ${plan.tag} failed: ${gitError(tagged)}`);
|
|
80492
|
+
ran.push("tag");
|
|
80493
|
+
record2(cwd, actor, {
|
|
80494
|
+
type: "release.tagged",
|
|
80495
|
+
...base,
|
|
80496
|
+
branch: plan.production,
|
|
80497
|
+
head: tagTarget
|
|
80498
|
+
});
|
|
80499
|
+
}
|
|
80500
|
+
for (const name of plan.mergeBack) {
|
|
80501
|
+
if (plan.state.mergedBack[name])
|
|
80502
|
+
continue;
|
|
80503
|
+
const on = switchTo(cwd, name);
|
|
80504
|
+
if (!on.ok)
|
|
80505
|
+
return on;
|
|
80506
|
+
const merged = mergeNoFf(cwd, plan.tag, name, plan.version);
|
|
80507
|
+
if (!merged.ok)
|
|
80508
|
+
return merged;
|
|
80509
|
+
ran.push(`merge-back ${name}`);
|
|
80510
|
+
record2(cwd, actor, { type: "release.merged_back", ...base, branch: name, head: merged.data });
|
|
80511
|
+
}
|
|
80512
|
+
if (!plan.state.branchDeleted) {
|
|
80513
|
+
if (currentBranch(cwd) === plan.branch) {
|
|
80514
|
+
const on = switchTo(cwd, plan.mergeBack.at(-1) ?? plan.production);
|
|
80515
|
+
if (!on.ok)
|
|
80516
|
+
return on;
|
|
80517
|
+
}
|
|
80518
|
+
const deleted = git5(cwd, ["branch", "-d", plan.branch]);
|
|
80519
|
+
if (!deleted.ok)
|
|
80520
|
+
return failure3("failed", `git branch -d ${plan.branch} failed: ${gitError(deleted)}`, plan.published ? `workit release publish ${plan.version} # its remote copy lacks commits; then: workit release finish ${plan.version}` : undefined);
|
|
80521
|
+
ran.push("delete-branch");
|
|
80522
|
+
record2(cwd, actor, {
|
|
80523
|
+
type: "release.branch_deleted",
|
|
80524
|
+
...base,
|
|
80525
|
+
branch: plan.branch,
|
|
80526
|
+
head: plan.releaseTip
|
|
80527
|
+
});
|
|
80528
|
+
}
|
|
80529
|
+
return success2({ ran, tagTarget });
|
|
80530
|
+
}
|
|
80531
|
+
function recordReleaseStep(cwd, actor, plan, row) {
|
|
80532
|
+
record2(cwd, actor, {
|
|
80533
|
+
version: plan.version,
|
|
80534
|
+
release: plan.branch,
|
|
80535
|
+
tag: plan.tag,
|
|
80536
|
+
track: plan.track,
|
|
80537
|
+
...row
|
|
80538
|
+
});
|
|
80539
|
+
}
|
|
80540
|
+
function releaseToPublish(cwd, version) {
|
|
80541
|
+
const located = locate(cwd, version, null);
|
|
80542
|
+
if (!located.ok)
|
|
80543
|
+
return located;
|
|
80544
|
+
if (!resolveRef(cwd, `refs/heads/${located.data.branch}`))
|
|
80545
|
+
return failure3("not_found", `${located.data.branch} is not a local branch`);
|
|
80546
|
+
return located;
|
|
80547
|
+
}
|
|
80548
|
+
var VERSION, PLACEHOLDER3, validVersion = (version) => VERSION.test(version) && !version.includes("..") && !version.endsWith(".lock"), templateOf = (line, kind) => {
|
|
80549
|
+
const template = line.naming[kind];
|
|
80550
|
+
if (!template)
|
|
80551
|
+
return `${kind}/{version}`;
|
|
80552
|
+
return /\{[^}]*\}/u.test(template) ? template : `${template.replace(/\/?\*?$/u, "")}/{version}`;
|
|
80553
|
+
}, branchFor = (line, kind, version) => trackBranchName({ naming: line.naming }, kind, version) ?? `${kind}/${version}`, versionIn = (template, branch) => {
|
|
80554
|
+
const pattern = template.split(PLACEHOLDER3).map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("(.+)");
|
|
80555
|
+
const match = new RegExp(`^${pattern}$`, "u").exec(branch);
|
|
80556
|
+
return match?.[1] && validVersion(match[1]) ? match[1] : null;
|
|
80557
|
+
}, releaseRows = (cwd) => {
|
|
80558
|
+
const ledger = readLedger(cwd);
|
|
80559
|
+
if (!ledger.ok)
|
|
80560
|
+
return [];
|
|
80561
|
+
return ledger.value.rows.filter((row) => row.type.startsWith("release."));
|
|
80562
|
+
}, record2 = (cwd, actor, row) => {
|
|
80563
|
+
appendObserved(cwd, { ...row, actor });
|
|
80564
|
+
}, KINDS, isAncestor3 = (cwd, ancestor, descendant) => git5(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok, switchTo = (cwd, branch) => {
|
|
80565
|
+
if (currentBranch(cwd) === branch)
|
|
80566
|
+
return success2(undefined);
|
|
80567
|
+
const switched = git5(cwd, ["switch", "-q", branch]);
|
|
80568
|
+
return switched.ok ? success2(undefined) : failure3("failed", `git switch ${branch} failed: ${gitError(switched)}`);
|
|
80569
|
+
}, mergeNoFf = (cwd, what, into, version) => {
|
|
80570
|
+
const merged = git5(cwd, ["merge", "--no-ff", "--no-edit", what], { timeoutMs: 10 * 60000 });
|
|
80571
|
+
if (!merged.ok) {
|
|
80572
|
+
const conflicted = git5(cwd, ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).ok;
|
|
80573
|
+
return failure3("failed", conflicted ? `merge_conflict: merging ${what} into ${into} conflicts` : `git merge ${what} into ${into} failed: ${gitError(merged)}`, conflicted ? `resolve the conflicts, git add <files>, git merge --continue, then: workit release finish ${version}` : undefined);
|
|
80574
|
+
}
|
|
80575
|
+
return success2(git5(cwd, ["rev-parse", "HEAD"]).stdout.trim());
|
|
80576
|
+
}, switchToRelease = (cwd, plan) => switchTo(cwd, plan.branch);
|
|
80577
|
+
var init_release = __esm(() => {
|
|
80578
|
+
init_ledger();
|
|
80579
|
+
init_vcs_config();
|
|
80580
|
+
init_release_tracks();
|
|
80581
|
+
init_workspaces();
|
|
80582
|
+
init_ops();
|
|
80583
|
+
init_rev();
|
|
80584
|
+
VERSION = /^[0-9A-Za-z][0-9A-Za-z._+-]{0,99}$/u;
|
|
80585
|
+
PLACEHOLDER3 = /\{[^}]*\}/gu;
|
|
80586
|
+
KINDS = ["release", "hotfix"];
|
|
80587
|
+
});
|
|
80588
|
+
|
|
80589
|
+
// packages/workit-cli/src/verbs/release.ts
|
|
80590
|
+
var exports_release = {};
|
|
80591
|
+
__export(exports_release, {
|
|
80592
|
+
run: () => run14
|
|
80593
|
+
});
|
|
80594
|
+
async function subVerb(load, argv, io) {
|
|
80595
|
+
let out = "";
|
|
80596
|
+
let err = "";
|
|
80597
|
+
const verb = await load();
|
|
80598
|
+
await verb.run(argv, {
|
|
80599
|
+
...io,
|
|
80600
|
+
json: true,
|
|
80601
|
+
stdout: (text) => void (out += text),
|
|
80602
|
+
stderr: (text) => void (err += text)
|
|
80603
|
+
});
|
|
80604
|
+
try {
|
|
80605
|
+
return JSON.parse(out);
|
|
80606
|
+
} catch {
|
|
80607
|
+
return fail("failed", (out + err).trim() || `${argv.join(" ")} printed nothing`);
|
|
80608
|
+
}
|
|
80609
|
+
}
|
|
80610
|
+
async function start(argv, io) {
|
|
80611
|
+
const flags = parseFlags(argv, { hotfix: "boolean", track: "value" });
|
|
80612
|
+
if (typeof flags === "string")
|
|
80613
|
+
return usage3(io, flags, START_USAGE);
|
|
80614
|
+
if (flags.positionals.length !== 1)
|
|
80615
|
+
return usage3(io, flags.positionals.length ? "pass one version" : "missing <version>", START_USAGE);
|
|
80616
|
+
const result = releaseStart(io.cwd, {
|
|
80617
|
+
version: flags.positionals[0],
|
|
80618
|
+
hotfix: flags.booleans.has("hotfix"),
|
|
80619
|
+
track: flags.values.track ?? null,
|
|
80620
|
+
actor: actorFromEnv(io.env)
|
|
80621
|
+
});
|
|
80622
|
+
if (!result.ok)
|
|
80623
|
+
return forgeFail(io, result);
|
|
80624
|
+
return emit(io, ok(result.data), (data) => [
|
|
80625
|
+
`started ${data.branch} from ${data.base} (${short4(data.baseSha)}), local only; it will be tagged ${data.tag}`,
|
|
80626
|
+
`next: commit the version bump, changelog and docs on ${data.branch} (workit git commit), then: workit release finish`,
|
|
80627
|
+
...data.notes.map((note) => `note: ${note}`)
|
|
80628
|
+
]);
|
|
80629
|
+
}
|
|
80630
|
+
async function releasePr(io, plan, dryRun) {
|
|
80631
|
+
const title = `chore(release): ${plan.version}`;
|
|
80632
|
+
if (dryRun)
|
|
80633
|
+
return emit(io, ok({
|
|
80634
|
+
...plan,
|
|
80635
|
+
steps: [`push ${plan.branch}`, `open a PR ${plan.branch} -> ${plan.production}: ${title}`]
|
|
80636
|
+
}), () => [
|
|
80637
|
+
`would push ${plan.branch} and open a release PR into ${plan.production} (not git-flow: the trunk moves only through review)`
|
|
80638
|
+
]);
|
|
80639
|
+
const switched = switchToRelease(io.cwd, plan);
|
|
80640
|
+
if (!switched.ok)
|
|
80641
|
+
return forgeFail(io, switched);
|
|
80642
|
+
const pushed = await subVerb(() => Promise.resolve().then(() => (init_git2(), exports_git)), ["push", "--set-upstream"], io);
|
|
80643
|
+
if (!pushed.ok)
|
|
80644
|
+
return emit(io, pushed);
|
|
80645
|
+
const pr = await subVerb(() => Promise.resolve().then(() => (init_pr(), exports_pr)), [
|
|
80646
|
+
"create",
|
|
80647
|
+
"--base",
|
|
80648
|
+
plan.production,
|
|
80649
|
+
"--title",
|
|
80650
|
+
title,
|
|
80651
|
+
`--body=${plan.changelog ?? `Release ${plan.version}`}`
|
|
80652
|
+
], io);
|
|
80653
|
+
if (!pr.ok)
|
|
80654
|
+
return emit(io, pr);
|
|
80655
|
+
recordReleaseStep(io.cwd, actorFromEnv(io.env), plan, {
|
|
80656
|
+
type: "release.pr_opened",
|
|
80657
|
+
branch: plan.branch,
|
|
80658
|
+
pr: pr.data.number ?? null
|
|
80659
|
+
});
|
|
80660
|
+
return emit(io, ok({ ...plan, pr: pr.data }), () => [
|
|
80661
|
+
`opened the release PR ${String(pr.data.url ?? "")} into ${plan.production}`,
|
|
80662
|
+
`next: once it merges, tag the merged commit ${plan.tag} and: workit git push --release ${plan.tag}`
|
|
80663
|
+
]);
|
|
80664
|
+
}
|
|
80665
|
+
async function finish2(argv, io) {
|
|
80666
|
+
const flags = parseFlags(argv, { track: "value", "dry-run": "boolean" });
|
|
80667
|
+
if (typeof flags === "string")
|
|
80668
|
+
return usage3(io, flags, FINISH_USAGE);
|
|
80669
|
+
if (flags.positionals.length > 1)
|
|
80670
|
+
return usage3(io, `unexpected argument ${flags.positionals[1]}`, FINISH_USAGE);
|
|
80671
|
+
const dryRun = flags.booleans.has("dry-run");
|
|
80672
|
+
const planned = planReleaseFinish(io.cwd, {
|
|
80673
|
+
version: flags.positionals[0] ?? null,
|
|
80674
|
+
track: flags.values.track ?? null
|
|
80675
|
+
});
|
|
80676
|
+
if (!planned.ok)
|
|
80677
|
+
return forgeFail(io, planned);
|
|
80678
|
+
const plan = planned.data;
|
|
80679
|
+
if (!plan.gitflow) {
|
|
80680
|
+
if (!dryRun && plan.state.branchDeleted)
|
|
80681
|
+
return emit(io, fail("not_found", `${plan.branch} is gone; nothing to open a PR from`));
|
|
80682
|
+
return releasePr(io, plan, dryRun);
|
|
80683
|
+
}
|
|
80684
|
+
const actor = actorFromEnv(io.env);
|
|
80685
|
+
const local = isLocalRemote(io.cwd, plan.remote);
|
|
80686
|
+
const release = requireGrant(io.cwd, "release");
|
|
80687
|
+
const push = requireGrant(io.cwd, "push", { forge: !local });
|
|
80688
|
+
const steps = stepsOf(plan);
|
|
80689
|
+
for (const warning of plan.warnings)
|
|
80690
|
+
io.stderr(`warning: ${warning}
|
|
80691
|
+
`);
|
|
80692
|
+
const warned = (envelope) => plan.warnings.length ? { ...envelope, warnings: plan.warnings } : envelope;
|
|
80693
|
+
if (dryRun) {
|
|
80694
|
+
const missing = [release, push].find((grant) => !grant.allowed);
|
|
80695
|
+
const data = { ...plan, steps, grants: { release: release.allowed, push: push.allowed } };
|
|
80696
|
+
if (missing && !missing.allowed)
|
|
80697
|
+
return emit(io, warned(fail("blocked", missing.error, { unblock: missing.unblock, data })));
|
|
80698
|
+
return emit(io, warned(ok(data)), () => [
|
|
80699
|
+
`finish ${plan.branch} -> ${plan.production} as ${plan.tag}:`,
|
|
80700
|
+
...steps.map((entry) => ` ${entry.done ? "done" : "todo"} ${entry.step}`)
|
|
80701
|
+
]);
|
|
80702
|
+
}
|
|
80703
|
+
if (!release.allowed)
|
|
80704
|
+
return blocked(io, release);
|
|
80705
|
+
if (!push.allowed)
|
|
80706
|
+
return blocked(io, push);
|
|
80707
|
+
const resume = `workit release finish ${plan.version}${plan.track ? ` --track ${plan.track}` : ""}`;
|
|
80708
|
+
const merged = finishLocally(io.cwd, plan, actor);
|
|
80709
|
+
if (!merged.ok)
|
|
80710
|
+
return forgeFail(io, merged, { step: "merge", resume });
|
|
80711
|
+
const notes = [...plan.notes];
|
|
80712
|
+
const pushed = await subVerb(() => Promise.resolve().then(() => (init_git2(), exports_git)), ["push", ...[...plan.pushBranches, plan.tag].flatMap((ref) => ["--release", ref])], io);
|
|
80713
|
+
if (!pushed.ok)
|
|
80714
|
+
return emit(io, { ...pushed, data: { ...pushed.data, step: "push", resume } });
|
|
80715
|
+
if (plan.published && plan.remote) {
|
|
80716
|
+
const removed = deleteRemoteBranch(io.cwd, plan.remote, plan.branch, plan.published);
|
|
80717
|
+
notes.push(removed.ok ? `deleted ${plan.remote}/${plan.branch}` : `kept ${plan.remote}/${plan.branch}: ${removed.error}`);
|
|
80718
|
+
if (removed.ok)
|
|
80719
|
+
recordReleaseStep(io.cwd, actor, plan, {
|
|
80720
|
+
type: "release.remote_branch_deleted",
|
|
80721
|
+
branch: plan.branch,
|
|
80722
|
+
head: plan.published
|
|
80723
|
+
});
|
|
80724
|
+
}
|
|
80725
|
+
let forgeRelease = null;
|
|
80726
|
+
if (local)
|
|
80727
|
+
notes.push(`${plan.remote ?? "the remote"} is a local path: no forge release`);
|
|
80728
|
+
else {
|
|
80729
|
+
const connected = connect(io);
|
|
80730
|
+
if (!connected.ok)
|
|
80731
|
+
return forgeFail(io, connected, { step: "forge-release", resume });
|
|
80732
|
+
const forge = connected.data.forge;
|
|
80733
|
+
const found = forge.findRelease(plan.tag);
|
|
80734
|
+
if (!found.ok)
|
|
80735
|
+
return forgeFail(io, found, { step: "forge-release", resume });
|
|
80736
|
+
if (found.data)
|
|
80737
|
+
forgeRelease = { url: found.data.url, created: false };
|
|
80738
|
+
else {
|
|
80739
|
+
const created = forge.createRelease({
|
|
80740
|
+
tag: plan.tag,
|
|
80741
|
+
name: plan.tag,
|
|
80742
|
+
body: plan.changelog ?? `Release ${plan.version}`
|
|
80743
|
+
});
|
|
80744
|
+
if (!created.ok)
|
|
80745
|
+
return forgeFail(io, created, { step: "forge-release", resume });
|
|
80746
|
+
forgeRelease = { url: created.data.url, created: true };
|
|
80747
|
+
}
|
|
80748
|
+
recordReleaseStep(io.cwd, actor, plan, {
|
|
80749
|
+
type: "release.forge_released",
|
|
80750
|
+
branch: plan.production,
|
|
80751
|
+
url: forgeRelease.url,
|
|
80752
|
+
created: forgeRelease.created
|
|
80753
|
+
});
|
|
80754
|
+
}
|
|
80755
|
+
const verified = verifyReleased(io.cwd, { tag: plan.tag, actor }, forgeDeps.npm);
|
|
80756
|
+
if (!verified.ok)
|
|
80757
|
+
return forgeFail(io, verified, { step: "verify", resume });
|
|
80758
|
+
if (!verified.data.delivered)
|
|
80759
|
+
return emit(io, fail("failed", `release_unverified: ${plan.tag} is not on ${plan.remote ?? "the remote"}`, {
|
|
80760
|
+
data: { step: "verify", resume, report: verified.data },
|
|
80761
|
+
unblock: resume
|
|
80762
|
+
}));
|
|
80763
|
+
recordReleaseStep(io.cwd, actor, plan, {
|
|
80764
|
+
type: "release.finished",
|
|
80765
|
+
branch: plan.branch,
|
|
80766
|
+
head: merged.data.tagTarget
|
|
80767
|
+
});
|
|
80768
|
+
return emit(io, warned(ok({ ...plan, ran: merged.data.ran, push: pushed.data, forgeRelease, notes })), () => [
|
|
80769
|
+
`finished ${plan.branch}: ${plan.production} and ${plan.mergeBack.join(", ")} merged, ${plan.tag} tagged on ${short4(merged.data.tagTarget)}`,
|
|
80770
|
+
`pushed ${[...plan.pushBranches, plan.tag].join(", ")} to ${plan.remote ?? "the remote"}; remote tips verified`,
|
|
80771
|
+
...forgeRelease ? [
|
|
80772
|
+
`forge release ${plan.tag}${forgeRelease.created ? " created" : " already there"}${forgeRelease.url ? `: ${forgeRelease.url}` : ""}`
|
|
80773
|
+
] : [],
|
|
80774
|
+
...notes.map((note) => `note: ${note}`)
|
|
80775
|
+
]);
|
|
80776
|
+
}
|
|
80777
|
+
async function publish(argv, io) {
|
|
80778
|
+
const flags = parseFlags(argv, {});
|
|
80779
|
+
if (typeof flags === "string")
|
|
80780
|
+
return usage3(io, flags, PUBLISH_USAGE);
|
|
80781
|
+
if (flags.positionals.length > 1)
|
|
80782
|
+
return usage3(io, `unexpected argument ${flags.positionals[1]}`, PUBLISH_USAGE);
|
|
80783
|
+
const located = releaseToPublish(io.cwd, flags.positionals[0] ?? null);
|
|
80784
|
+
if (!located.ok)
|
|
80785
|
+
return forgeFail(io, located);
|
|
80786
|
+
const plan = pushPreflight(io.cwd, { branch: located.data.branch });
|
|
80787
|
+
if (!plan.ok)
|
|
80788
|
+
return forgeFail(io, plan);
|
|
80789
|
+
const identity = plan.data.local ? null : pushIdentity(io, plan.data.branch);
|
|
80790
|
+
if (identity && !identity.ok)
|
|
80791
|
+
return forgeFail(io, identity);
|
|
80792
|
+
const checked = identity?.data ?? null;
|
|
80793
|
+
const grant = requireGrant(io.cwd, "push", { forge: checked?.forge != null });
|
|
80794
|
+
if (!grant.allowed)
|
|
80795
|
+
return blocked(io, grant);
|
|
80796
|
+
const actor = actorFromEnv(io.env);
|
|
80797
|
+
const result = executePush(io.cwd, plan.data, {
|
|
80798
|
+
setUpstream: true,
|
|
80799
|
+
actor,
|
|
80800
|
+
...checked ? { identity: { ...checked } } : {}
|
|
80801
|
+
});
|
|
80802
|
+
if (!result.ok)
|
|
80803
|
+
return forgeFail(io, result);
|
|
80804
|
+
appendObserved(io.cwd, {
|
|
80805
|
+
type: "release.published",
|
|
80806
|
+
actor,
|
|
80807
|
+
version: located.data.version,
|
|
80808
|
+
branch: located.data.branch,
|
|
80809
|
+
head: result.data.sha
|
|
80810
|
+
});
|
|
80811
|
+
return emit(io, ok({ ...result.data, version: located.data.version }), (data) => [
|
|
80812
|
+
`published ${data.branch} (${short4(data.sha)}) to ${data.remote}; remote tip verified`
|
|
80813
|
+
]);
|
|
80814
|
+
}
|
|
80815
|
+
async function run14(argv, io) {
|
|
80816
|
+
const [sub, ...rest] = argv;
|
|
80817
|
+
if (sub === "start")
|
|
80818
|
+
return start(rest, io);
|
|
80819
|
+
if (sub === "finish")
|
|
80820
|
+
return finish2(rest, io);
|
|
80821
|
+
if (sub === "publish")
|
|
80822
|
+
return publish(rest, io);
|
|
80823
|
+
return usage3(io, sub && !sub.startsWith("-") ? `unknown release subcommand "${sub}"` : "missing release subcommand", USAGE6);
|
|
80824
|
+
}
|
|
80825
|
+
var START_USAGE = "workit release start <version> [--hotfix] [--track <t>] [--json]", FINISH_USAGE = "workit release finish [<version>] [--track <t>] [--dry-run] [--json]", PUBLISH_USAGE = "workit release publish [<version>] [--json]", USAGE6 = "workit release start|finish|publish ... (workit help release)", short4 = (sha) => sha ? sha.slice(0, 12) : "(none)", blocked = (io, grant) => emit(io, fail("blocked", grant.error, { unblock: grant.unblock, data: { reason: grant.reason } })), isLocalRemote = (cwd, remote) => {
|
|
80826
|
+
const url = remote ? pushUrl(cwd, remote) : null;
|
|
80827
|
+
return !url || parseRemoteUrl(url)?.protocol === "file";
|
|
80828
|
+
}, stepsOf = (plan) => [
|
|
80829
|
+
{ step: `merge ${plan.branch} into ${plan.production} (--no-ff)`, done: plan.state.merged },
|
|
80830
|
+
{ step: `tag ${plan.tag} (annotated)`, done: plan.state.tagged },
|
|
80831
|
+
...plan.mergeBack.map((name) => ({
|
|
80832
|
+
step: `merge ${plan.tag} into ${name} (--no-ff)`,
|
|
80833
|
+
done: plan.state.mergedBack[name] === true
|
|
80834
|
+
})),
|
|
80835
|
+
{ step: `delete local ${plan.branch}`, done: plan.state.branchDeleted },
|
|
80836
|
+
{
|
|
80837
|
+
step: `push ${[...plan.pushBranches, plan.tag].join(", ")} (atomic, fast-forward only)`,
|
|
80838
|
+
done: false
|
|
80839
|
+
},
|
|
80840
|
+
{ step: `forge release ${plan.tag}`, done: false },
|
|
80841
|
+
{ step: `verify ${plan.tag} on the remote`, done: false }
|
|
80842
|
+
];
|
|
80843
|
+
var init_release2 = __esm(() => {
|
|
80844
|
+
init_autonomy();
|
|
80845
|
+
init_verify();
|
|
80846
|
+
init_ops();
|
|
80847
|
+
init_release();
|
|
80848
|
+
init_rev();
|
|
80849
|
+
init_ledger();
|
|
80850
|
+
init_output();
|
|
80851
|
+
init_forge_common();
|
|
80852
|
+
});
|
|
80853
|
+
|
|
79671
80854
|
// packages/workit-cli/src/verbs/verify-delivery.ts
|
|
79672
80855
|
var exports_verify_delivery = {};
|
|
79673
80856
|
__export(exports_verify_delivery, {
|
|
79674
|
-
run: () =>
|
|
80857
|
+
run: () => run15
|
|
79675
80858
|
});
|
|
79676
|
-
async function
|
|
80859
|
+
async function run15(argv, io) {
|
|
79677
80860
|
const flags = parseFlags(argv, {
|
|
79678
80861
|
expect: "value",
|
|
79679
80862
|
branch: "value",
|
|
@@ -79683,31 +80866,31 @@ async function run14(argv, io) {
|
|
|
79683
80866
|
package: "value"
|
|
79684
80867
|
});
|
|
79685
80868
|
if (typeof flags === "string")
|
|
79686
|
-
return usage3(io, flags,
|
|
80869
|
+
return usage3(io, flags, USAGE7);
|
|
79687
80870
|
if (flags.positionals.length > 1)
|
|
79688
|
-
return usage3(io, `unexpected argument ${flags.positionals[1]}`,
|
|
80871
|
+
return usage3(io, `unexpected argument ${flags.positionals[1]}`, USAGE7);
|
|
79689
80872
|
const named = flags.positionals[0];
|
|
79690
80873
|
if (named !== undefined && !ENDPOINTS[named])
|
|
79691
|
-
return usage3(io, `unknown endpoint "${named}"`,
|
|
80874
|
+
return usage3(io, `unknown endpoint "${named}"`, USAGE7);
|
|
79692
80875
|
if (flags.values.expect !== undefined && !ENDPOINTS[flags.values.expect])
|
|
79693
|
-
return usage3(io, "--expect must be pushed, pr, merged or released",
|
|
80876
|
+
return usage3(io, "--expect must be pushed, pr, merged or released", USAGE7);
|
|
79694
80877
|
if (named && flags.values.expect && ENDPOINTS[named] !== ENDPOINTS[flags.values.expect])
|
|
79695
|
-
return usage3(io, `${named} and --expect ${flags.values.expect} disagree`,
|
|
80878
|
+
return usage3(io, `${named} and --expect ${flags.values.expect} disagree`, USAGE7);
|
|
79696
80879
|
const expect = ENDPOINTS[named ?? ""] ?? ENDPOINTS[flags.values.expect ?? ""] ?? (flags.values.tag || flags.values.package ? "released" : flags.values.pr ? "pr" : "pushed");
|
|
79697
80880
|
const pr = positiveInt(flags.values.pr, "--pr");
|
|
79698
80881
|
if (typeof pr === "string")
|
|
79699
|
-
return usage3(io, pr,
|
|
80882
|
+
return usage3(io, pr, USAGE7);
|
|
79700
80883
|
if (pr && flags.values.branch)
|
|
79701
|
-
return usage3(io, "pass --pr or --branch, not both",
|
|
80884
|
+
return usage3(io, "pass --pr or --branch, not both", USAGE7);
|
|
79702
80885
|
const sha = flags.values.sha ?? null;
|
|
79703
80886
|
if (sha !== null && !/^[0-9a-f]{7,64}$/u.test(sha))
|
|
79704
|
-
return usage3(io, "--sha must be a commit sha (7-64 hex chars)",
|
|
80887
|
+
return usage3(io, "--sha must be a commit sha (7-64 hex chars)", USAGE7);
|
|
79705
80888
|
const actor = actorFromEnv(io.env);
|
|
79706
80889
|
const branch = flags.values.branch ?? null;
|
|
79707
80890
|
let result;
|
|
79708
80891
|
if (expect === "pushed") {
|
|
79709
80892
|
if (pr)
|
|
79710
|
-
return usage3(io, "push is checked by --branch, not --pr",
|
|
80893
|
+
return usage3(io, "push is checked by --branch, not --pr", USAGE7);
|
|
79711
80894
|
result = verifyPushed(io.cwd, { branch, sha, actor });
|
|
79712
80895
|
} else if (expect === "released") {
|
|
79713
80896
|
result = verifyReleased(io.cwd, { tag: flags.values.tag ?? null, pkg: flags.values.package ?? null, sha, actor }, forgeDeps.npm);
|
|
@@ -79732,7 +80915,7 @@ async function run14(argv, io) {
|
|
|
79732
80915
|
unblock: expect === "pushed" ? "workit git push" : expect === "pr" ? "workit git push && workit pr create --fill" : expect === "merged" ? "workit pr status # then workit pr merge when READY and granted" : "check the release workflow; the tag/package is not published"
|
|
79733
80916
|
}));
|
|
79734
80917
|
}
|
|
79735
|
-
var
|
|
80918
|
+
var USAGE7 = "workit verify-delivery [push|pr|merge|release] [--expect pushed|pr|merged|released] [--branch <b>] [--pr <n>] [--sha <sha>] [--tag <t>] [--package <name[@version]>] [--json]", ENDPOINTS, render2 = (report) => [
|
|
79736
80919
|
`${report.expect}: ${report.delivered ? "delivered" : "NOT delivered"}${report.branch ? ` (${report.branch}${report.sha ? ` @ ${report.sha.slice(0, 12)}` : ""})` : ""}${report.pr ? ` #${report.pr}` : ""}`,
|
|
79737
80920
|
...report.observations.map((entry) => ` ${entry.ok ? "ok" : "x "} ${entry.kind}: expected ${entry.expected ?? "-"}, observed ${entry.observed ?? "(none)"}${entry.note ? ` — ${entry.note}` : ""}`)
|
|
79738
80921
|
];
|
|
@@ -79755,7 +80938,7 @@ var init_verify_delivery = __esm(() => {
|
|
|
79755
80938
|
// packages/workit-cli/src/verbs/stack.ts
|
|
79756
80939
|
var exports_stack = {};
|
|
79757
80940
|
__export(exports_stack, {
|
|
79758
|
-
run: () =>
|
|
80941
|
+
run: () => run16
|
|
79759
80942
|
});
|
|
79760
80943
|
async function plan(argv, io) {
|
|
79761
80944
|
const flags = parseFlags(argv, { name: "value", trunk: "value", track: "value" });
|
|
@@ -79929,7 +81112,7 @@ async function land(argv, io) {
|
|
|
79929
81112
|
...data.dryRun ? [
|
|
79930
81113
|
data.wouldLand.length ? `would land: ${data.wouldLand.map((item) => `${label2(kind, item.pr)} (${item.branch})`).join(", ")}` : "would land nothing"
|
|
79931
81114
|
] : [
|
|
79932
|
-
data.landed.length ? `landed: ${data.landed.map((item) => `${label2(kind, item.pr)} (${item.branch}) -> ${
|
|
81115
|
+
data.landed.length ? `landed: ${data.landed.map((item) => `${label2(kind, item.pr)} (${item.branch}) -> ${short5(item.mergeSha)}${item.unverified ? ` UNVERIFIED (ledger ${item.unverified})` : ""}`).join(", ")}` : "landed nothing"
|
|
79933
81116
|
],
|
|
79934
81117
|
...data.retargeted.length ? [`retargeted: ${data.retargeted.map((pr) => label2(kind, pr)).join(", ")}`] : [],
|
|
79935
81118
|
...data.restacked.length ? [`restacked: ${data.restacked.join(", ")}`] : [],
|
|
@@ -79937,7 +81120,7 @@ async function land(argv, io) {
|
|
|
79937
81120
|
unblock: ${data.stoppedAt.unblock}` : data.complete ? "stack complete" : ""
|
|
79938
81121
|
].filter(Boolean));
|
|
79939
81122
|
}
|
|
79940
|
-
async function
|
|
81123
|
+
async function run16(argv, io) {
|
|
79941
81124
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
79942
81125
|
if (sub === "plan")
|
|
79943
81126
|
return plan(rest, io);
|
|
@@ -79947,13 +81130,13 @@ async function run15(argv, io) {
|
|
|
79947
81130
|
return sync(rest, io);
|
|
79948
81131
|
if (sub === "land")
|
|
79949
81132
|
return land(rest, io);
|
|
79950
|
-
return usage3(io, sub && !sub.startsWith("-") ? `unknown stack subcommand "${sub}"` : "missing stack subcommand",
|
|
81133
|
+
return usage3(io, sub && !sub.startsWith("-") ? `unknown stack subcommand "${sub}"` : "missing stack subcommand", USAGE8);
|
|
79951
81134
|
}
|
|
79952
|
-
var PLAN_USAGE = "workit stack plan [--name <n>] [--trunk <b> | --track <t>] [<bottom> … <top>] [--json]", STATUS_USAGE2 = "workit stack status [--name <n>] [--json]", SYNC_USAGE = "workit stack sync [--name <n>] [--local] [--dry-run] [--force <branch>]… [--json]", LAND_USAGE = "workit stack land [--name <n>] [--dry-run] [--max <n>] [--method squash|merge|rebase] [--unverified --reason <why>] [--timeout 20m] [--interval 30s] [--json]",
|
|
81135
|
+
var PLAN_USAGE = "workit stack plan [--name <n>] [--trunk <b> | --track <t>] [<bottom> … <top>] [--json]", STATUS_USAGE2 = "workit stack status [--name <n>] [--json]", SYNC_USAGE = "workit stack sync [--name <n>] [--local] [--dry-run] [--force <branch>]… [--json]", LAND_USAGE = "workit stack land [--name <n>] [--dry-run] [--max <n>] [--method squash|merge|rebase] [--unverified --reason <why>] [--timeout 20m] [--interval 30s] [--json]", USAGE8 = "workit stack plan|status|sync|land ... (workit help stack)", short5 = (sha) => sha ? sha.slice(0, 12) : "?", stackFailed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), label2 = (forge, pr) => pr === null ? "no PR" : `${forge === "gitlab" ? "MR !" : "PR #"}${pr}`, renderSync = (data) => [
|
|
79953
81136
|
`${data.dryRun ? "would sync" : "synced"} stack ${data.name}${data.merged.length ? `; merged: ${data.merged.map((item) => item.branch).join(", ")}` : ""}`,
|
|
79954
81137
|
...data.steps.map((step) => [
|
|
79955
81138
|
` ${step.branch} on ${step.parent}:`,
|
|
79956
|
-
step.restack ? `restacked ${
|
|
81139
|
+
step.restack ? `restacked ${short5(step.restack.from)} -> ${short5(step.restack.onto)}${step.restack.head ? ` = ${short5(step.restack.head)}` : ""}${step.restack.carried === true ? " (same change; verdict carries)" : step.restack.carried === false ? " (change differs; verdict stale)" : ""}` : "up to date",
|
|
79957
81140
|
step.push ? step.push.pushed ? `pushed${step.push.forced ? " (lease)" : ""}` : "remote current" : "",
|
|
79958
81141
|
step.retarget ? `retargeted ${step.retarget.from} -> ${step.retarget.to}` : ""
|
|
79959
81142
|
].filter(Boolean).join(" "))
|
|
@@ -79967,7 +81150,7 @@ var init_stack2 = __esm(() => {
|
|
|
79967
81150
|
});
|
|
79968
81151
|
|
|
79969
81152
|
// packages/workit-core/src/fanout.ts
|
|
79970
|
-
import { spawnSync as
|
|
81153
|
+
import { spawnSync as spawnSync25 } from "node:child_process";
|
|
79971
81154
|
import { createHash as createHash18 } from "node:crypto";
|
|
79972
81155
|
import fs25 from "node:fs";
|
|
79973
81156
|
import path48 from "node:path";
|
|
@@ -80116,7 +81299,7 @@ function inScope(plan, slice, file) {
|
|
|
80116
81299
|
}
|
|
80117
81300
|
function parsePlan(raw, defaults, name) {
|
|
80118
81301
|
const problems = [];
|
|
80119
|
-
if (!
|
|
81302
|
+
if (!isRecord10(raw))
|
|
80120
81303
|
return { ok: false, problems: ["the plan must be a JSON object"] };
|
|
80121
81304
|
const planName = name ?? (typeof raw.name === "string" ? raw.name.trim() : "");
|
|
80122
81305
|
if (!planName || planName.length > 200 || /\p{Cc}/u.test(planName))
|
|
@@ -80139,8 +81322,8 @@ function parsePlan(raw, defaults, name) {
|
|
|
80139
81322
|
const seenIds = new Set;
|
|
80140
81323
|
const seenBranches = new Map;
|
|
80141
81324
|
raw.slices.forEach((item, index) => {
|
|
80142
|
-
const where =
|
|
80143
|
-
if (!
|
|
81325
|
+
const where = isRecord10(item) && typeof item.id === "string" ? `slice ${item.id}` : `slice #${index + 1}`;
|
|
81326
|
+
if (!isRecord10(item)) {
|
|
80144
81327
|
problems.push(`${where}: must be an object`);
|
|
80145
81328
|
return;
|
|
80146
81329
|
}
|
|
@@ -80458,7 +81641,7 @@ function fanoutsDir(cwd) {
|
|
|
80458
81641
|
return { ok: true, data: path48.join(root.value.root, "fanouts") };
|
|
80459
81642
|
}
|
|
80460
81643
|
function parseStored(raw) {
|
|
80461
|
-
if (!
|
|
81644
|
+
if (!isRecord10(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
80462
81645
|
return null;
|
|
80463
81646
|
if (!Array.isArray(raw.slices))
|
|
80464
81647
|
return null;
|
|
@@ -80475,7 +81658,7 @@ function parseStored(raw) {
|
|
|
80475
81658
|
});
|
|
80476
81659
|
const slices = [];
|
|
80477
81660
|
for (const item of raw.slices) {
|
|
80478
|
-
if (!
|
|
81661
|
+
if (!isRecord10(item) || typeof item.id !== "string" || typeof item.branch !== "string")
|
|
80479
81662
|
return null;
|
|
80480
81663
|
slices.push({
|
|
80481
81664
|
id: item.id,
|
|
@@ -80598,7 +81781,7 @@ function planFanout(cwd, input, now = new Date) {
|
|
|
80598
81781
|
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");
|
|
80599
81782
|
if (input.takeLead && isDelegateSession(session))
|
|
80600
81783
|
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");
|
|
80601
|
-
const parsed = parsePlan(input.trunk &&
|
|
81784
|
+
const parsed = parsePlan(input.trunk && isRecord10(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
|
|
80602
81785
|
trunk: input.trunk ?? defaultTrunk(cwd),
|
|
80603
81786
|
root: mainCheckout(cwd),
|
|
80604
81787
|
worktreeRoot: worktreeRoot(cwd)
|
|
@@ -80721,7 +81904,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
|
|
|
80721
81904
|
error,
|
|
80722
81905
|
...unblock ? { unblock } : {},
|
|
80723
81906
|
...data ? { data } : {}
|
|
80724
|
-
}), 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)),
|
|
81907
|
+
}), 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)), isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), PLACEHOLDER4, filled = (text) => text.trim() !== "" && !PLACEHOLDER4.test(text.trim()), textList = (value) => {
|
|
80725
81908
|
if (typeof value === "string")
|
|
80726
81909
|
return [value];
|
|
80727
81910
|
if (Array.isArray(value) && value.every((item) => typeof item === "string"))
|
|
@@ -80740,7 +81923,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
|
|
|
80740
81923
|
text: edges.map((edge) => `add "${edge.dependsOn}" to "dependsOn" of slice ${edge.slice}`).join("; ")
|
|
80741
81924
|
};
|
|
80742
81925
|
}, gitRun4 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
80743
|
-
const run =
|
|
81926
|
+
const run = spawnSync25("git", args, {
|
|
80744
81927
|
cwd,
|
|
80745
81928
|
encoding: "utf8",
|
|
80746
81929
|
timeout: timeoutMs,
|
|
@@ -80773,12 +81956,12 @@ var init_fanout = __esm(() => {
|
|
|
80773
81956
|
"CHANGELOG.md"
|
|
80774
81957
|
];
|
|
80775
81958
|
compiled = new Map;
|
|
80776
|
-
|
|
81959
|
+
PLACEHOLDER4 = /^(?:<[^>]*>|tbd|todo|fixme|\?+|\.\.\.|…)$/iu;
|
|
80777
81960
|
ID = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
|
80778
81961
|
});
|
|
80779
81962
|
|
|
80780
81963
|
// packages/workit-core/src/fanout-worktree.ts
|
|
80781
|
-
import { randomBytes as
|
|
81964
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
80782
81965
|
import fs26 from "node:fs";
|
|
80783
81966
|
import path49 from "node:path";
|
|
80784
81967
|
function rowSample(lines) {
|
|
@@ -80862,7 +82045,7 @@ function markAdminDir(worktree) {
|
|
|
80862
82045
|
const admin = adminDir(worktree);
|
|
80863
82046
|
if (!admin)
|
|
80864
82047
|
return null;
|
|
80865
|
-
const token =
|
|
82048
|
+
const token = randomBytes5(16).toString("hex");
|
|
80866
82049
|
try {
|
|
80867
82050
|
fs26.writeFileSync(path49.join(admin.path, TOKEN_FILE), `${token}
|
|
80868
82051
|
`);
|
|
@@ -81287,7 +82470,7 @@ var init_fanout_brief = __esm(() => {
|
|
|
81287
82470
|
});
|
|
81288
82471
|
|
|
81289
82472
|
// packages/workit-core/src/fanout-landed.ts
|
|
81290
|
-
import { spawnSync as
|
|
82473
|
+
import { spawnSync as spawnSync26 } from "node:child_process";
|
|
81291
82474
|
function forgeBreaker(resolved) {
|
|
81292
82475
|
if (!resolved)
|
|
81293
82476
|
return { forge: null, down: () => null };
|
|
@@ -81330,9 +82513,9 @@ function hasOwnCommits2(cwd, branch, start, head) {
|
|
|
81330
82513
|
const onto = /^rebase(?: -i)? \(finish\): \S+ onto ([0-9a-f]+)/u.exec(subject)?.[1];
|
|
81331
82514
|
if (!made && !(onto && !sha.startsWith(onto) && !onto.startsWith(sha)))
|
|
81332
82515
|
continue;
|
|
81333
|
-
if (sha !== head && !
|
|
82516
|
+
if (sha !== head && !isAncestor4(cwd, sha, head))
|
|
81334
82517
|
continue;
|
|
81335
|
-
if (start && (sha === start ||
|
|
82518
|
+
if (start && (sha === start || isAncestor4(cwd, sha, start)))
|
|
81336
82519
|
continue;
|
|
81337
82520
|
return true;
|
|
81338
82521
|
}
|
|
@@ -81342,7 +82525,7 @@ function reverted(cwd, trunk, landedHead, start) {
|
|
|
81342
82525
|
const fork = mergeBase(cwd, landedHead, trunk);
|
|
81343
82526
|
if (fork && fork !== landedHead)
|
|
81344
82527
|
return unchangedSince(cwd, fork, landedHead, trunk);
|
|
81345
|
-
const before = start && start !== landedHead &&
|
|
82528
|
+
const before = start && start !== landedHead && isAncestor4(cwd, start, landedHead) ? start : gitRun4(cwd, ["rev-parse", "--verify", "-q", `${landedHead}^1`]).stdout.trim();
|
|
81346
82529
|
return before ? unchangedSince(cwd, before, landedHead, trunk) : false;
|
|
81347
82530
|
}
|
|
81348
82531
|
function unchangedSince(cwd, before, after, trunk) {
|
|
@@ -81378,7 +82561,7 @@ function trunkPatchIds(ctx, fork) {
|
|
|
81378
82561
|
"--"
|
|
81379
82562
|
]);
|
|
81380
82563
|
if (log.ok && log.stdout.trim()) {
|
|
81381
|
-
const run =
|
|
82564
|
+
const run = spawnSync26("git", ["patch-id", "--stable"], {
|
|
81382
82565
|
cwd: ctx.cwd,
|
|
81383
82566
|
input: log.stdout,
|
|
81384
82567
|
encoding: "utf8",
|
|
@@ -81450,9 +82633,9 @@ function detectLanding(ctx, slice, input) {
|
|
|
81450
82633
|
}
|
|
81451
82634
|
return merged ? landed("pr_merged", merged) : { ...out, landed: true, how: "pr_merged" };
|
|
81452
82635
|
}
|
|
81453
|
-
if (!merged || head === merged ||
|
|
82636
|
+
if (!merged || head === merged || isAncestor4(ctx.cwd, head, merged))
|
|
81454
82637
|
return landed("pr_merged", merged ?? head);
|
|
81455
|
-
out.note =
|
|
82638
|
+
out.note = isAncestor4(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)`;
|
|
81456
82639
|
return out;
|
|
81457
82640
|
}
|
|
81458
82641
|
}
|
|
@@ -81469,7 +82652,7 @@ function detectLanding(ctx, slice, input) {
|
|
|
81469
82652
|
if (trunkIds.size === 0)
|
|
81470
82653
|
return out;
|
|
81471
82654
|
const froms = [fork];
|
|
81472
|
-
if (input.parentHead && input.parentHead !== head &&
|
|
82655
|
+
if (input.parentHead && input.parentHead !== head && isAncestor4(ctx.cwd, input.parentHead, head))
|
|
81473
82656
|
froms.push(input.parentHead);
|
|
81474
82657
|
for (const from of froms) {
|
|
81475
82658
|
const id = patchId(ctx.cwd, from, head);
|
|
@@ -81485,7 +82668,7 @@ function sliceStart(cwd, rows, fanout, slice) {
|
|
|
81485
82668
|
const row = rows.findLast((candidate) => candidate.type === "fanout.worktree.created" && candidate.fanout === fanout && candidate.slice === slice.id && typeof candidate.baseSha === "string" && inSliceRun(candidate, slice));
|
|
81486
82669
|
return row?.baseSha ?? null;
|
|
81487
82670
|
}
|
|
81488
|
-
var
|
|
82671
|
+
var isAncestor4 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
|
|
81489
82672
|
var init_fanout_landed = __esm(() => {
|
|
81490
82673
|
init_rev();
|
|
81491
82674
|
init_fanout();
|
|
@@ -81542,7 +82725,7 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
81542
82725
|
const b = tips.get(second);
|
|
81543
82726
|
if (landings.get(first)?.landed || landings.get(second)?.landed)
|
|
81544
82727
|
continue;
|
|
81545
|
-
if (!a || !b ||
|
|
82728
|
+
if (!a || !b || isAncestor5(cwd, a, b) || isAncestor5(cwd, b, a))
|
|
81546
82729
|
continue;
|
|
81547
82730
|
const check = checks.get(second);
|
|
81548
82731
|
const theirs = new Map((changedBy.get(first) ?? []).map((file) => [file.toLowerCase(), file]));
|
|
@@ -81722,7 +82905,7 @@ function unblockFor(check, plan) {
|
|
|
81722
82905
|
return `unblock ${check.waitsFor.join(", ")} first`;
|
|
81723
82906
|
return check.reasons[0] ?? "workit fanout check";
|
|
81724
82907
|
}
|
|
81725
|
-
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"),
|
|
82908
|
+
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"), isAncestor5 = (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) => ({
|
|
81726
82909
|
id: slice.id,
|
|
81727
82910
|
branch: slice.branch,
|
|
81728
82911
|
head,
|
|
@@ -81939,7 +83122,7 @@ var init_fanout_status = __esm(() => {
|
|
|
81939
83122
|
// packages/workit-cli/src/verbs/fanout.ts
|
|
81940
83123
|
var exports_fanout = {};
|
|
81941
83124
|
__export(exports_fanout, {
|
|
81942
|
-
run: () =>
|
|
83125
|
+
run: () => run17
|
|
81943
83126
|
});
|
|
81944
83127
|
import fs27 from "node:fs";
|
|
81945
83128
|
import path50 from "node:path";
|
|
@@ -82135,7 +83318,7 @@ function worktree(argv, io) {
|
|
|
82135
83318
|
return failed(io, result);
|
|
82136
83319
|
return emit(io, ok(result.data), (data) => [
|
|
82137
83320
|
`${data.created ? "created" : "kept"} worktree for slice ${data.slice} (${data.mode}): ${data.path}`,
|
|
82138
|
-
` branch ${data.branch} <- ${data.base} at ${
|
|
83321
|
+
` branch ${data.branch} <- ${data.base} at ${short6(data.head)}`,
|
|
82139
83322
|
` scratch ${data.scratch} (put it in the worker brief: temp files go here)`,
|
|
82140
83323
|
...data.notes.map((note) => `note: ${note}`)
|
|
82141
83324
|
]);
|
|
@@ -82159,7 +83342,7 @@ function worktree(argv, io) {
|
|
|
82159
83342
|
...data.notes.map((note) => `note: ${note}`)
|
|
82160
83343
|
]);
|
|
82161
83344
|
}
|
|
82162
|
-
async function
|
|
83345
|
+
async function run17(argv, io) {
|
|
82163
83346
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
82164
83347
|
if (sub === "plan")
|
|
82165
83348
|
return plan2(rest, io);
|
|
@@ -82171,9 +83354,9 @@ async function run16(argv, io) {
|
|
|
82171
83354
|
return status3(rest, io);
|
|
82172
83355
|
if (sub === "worktree")
|
|
82173
83356
|
return worktree(rest, io);
|
|
82174
|
-
return usage3(io, sub && !sub.startsWith("-") ? `unknown fanout subcommand "${sub}"` : "missing fanout subcommand",
|
|
83357
|
+
return usage3(io, sub && !sub.startsWith("-") ? `unknown fanout subcommand "${sub}"` : "missing fanout subcommand", USAGE9);
|
|
82175
83358
|
}
|
|
82176
|
-
var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] [--take-lead] [--json]", BRIEF_USAGE = "workit fanout brief <slice> [--name <n>] [--mode new|resume] [--attempt <n>] [--json]", CHECK_USAGE = "workit fanout check [<slice>…] [--name <n>] [--base <ref>] [--offline] [--json]", STATUS_USAGE3 = "workit fanout status [--name <n>] [--stuck-after 30m] [--offline] [--json]", WORKTREE_USAGE = "workit fanout worktree create <slice> [--name <n>] | worktree release <slice> [--name <n>] [--force] [--json]",
|
|
83359
|
+
var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] [--take-lead] [--json]", BRIEF_USAGE = "workit fanout brief <slice> [--name <n>] [--mode new|resume] [--attempt <n>] [--json]", CHECK_USAGE = "workit fanout check [<slice>…] [--name <n>] [--base <ref>] [--offline] [--json]", STATUS_USAGE3 = "workit fanout status [--name <n>] [--stuck-after 30m] [--offline] [--json]", WORKTREE_USAGE = "workit fanout worktree create <slice> [--name <n>] | worktree release <slice> [--name <n>] [--force] [--json]", USAGE9 = "workit fanout plan|brief|check|status|worktree ... (workit help fanout)", failed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), renderCheck = (data) => [
|
|
82177
83360
|
`fanout ${data.name} against ${data.trunkRef}: ${data.ready ? "ready to land" : "blocked"}`,
|
|
82178
83361
|
...data.slices.map((slice) => slice.status === "landed" ? ` -- ${slice.id} (${slice.branch}) landed (${slice.landed?.how}${slice.landed?.pr ? ` #${slice.landed.pr}` : ""})` : ` ${slice.status === "ready" ? "ok " : "x "}${slice.id} (${slice.branch}) ${slice.changed} file${slice.changed === 1 ? "" : "s"} · verdict ${slice.verdict.accepted ? "accepted" : slice.verdict.basis}${slice.reasons.length ? `
|
|
82179
83362
|
${slice.reasons.join(`
|
|
@@ -82183,7 +83366,7 @@ var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --
|
|
|
82183
83366
|
`landing order: ${data.landingOrder.join(", ")}`,
|
|
82184
83367
|
`next: ${data.next}`,
|
|
82185
83368
|
...data.notes.map((note) => `note: ${note}`)
|
|
82186
|
-
],
|
|
83369
|
+
], short6 = (sha) => sha ? sha.slice(0, 7) : "-", renderStatus2 = (data) => {
|
|
82187
83370
|
const counts = new Map;
|
|
82188
83371
|
for (const slice of data.slices)
|
|
82189
83372
|
counts.set(slice.state, (counts.get(slice.state) ?? 0) + 1);
|
|
@@ -82191,7 +83374,7 @@ var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --
|
|
|
82191
83374
|
`fanout ${data.name} on ${data.trunk}: ${[...counts].map(([state, count]) => `${count} ${state.replace("_", " ")}`).join(", ")} · forge ${data.forge.available ? "on" : "off"}`,
|
|
82192
83375
|
...data.slices.map((slice) => {
|
|
82193
83376
|
const parts = [
|
|
82194
|
-
slice.branchExists ?
|
|
83377
|
+
slice.branchExists ? short6(slice.head) : "no branch",
|
|
82195
83378
|
slice.lastCommitAt && slice.idleMinutes !== null ? `idle ${slice.idleMinutes} min` : null,
|
|
82196
83379
|
slice.pr ? `PR #${slice.pr.number} ${slice.pr.state}` : null,
|
|
82197
83380
|
slice.ci === "unknown" ? null : `CI ${slice.ci}`,
|
|
@@ -82225,9 +83408,9 @@ var init_fanout2 = __esm(() => {
|
|
|
82225
83408
|
// packages/workit-cli/src/verbs/ledger.ts
|
|
82226
83409
|
var exports_ledger = {};
|
|
82227
83410
|
__export(exports_ledger, {
|
|
82228
|
-
run: () =>
|
|
83411
|
+
run: () => run18
|
|
82229
83412
|
});
|
|
82230
|
-
import { randomBytes as
|
|
83413
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
82231
83414
|
import { parseArgs } from "node:util";
|
|
82232
83415
|
function targetBranch(io, rows, branch, pr) {
|
|
82233
83416
|
if (pr !== undefined) {
|
|
@@ -82249,7 +83432,7 @@ function targetBranch(io, rows, branch, pr) {
|
|
|
82249
83432
|
}
|
|
82250
83433
|
return { ok: true, value: branch ?? null };
|
|
82251
83434
|
}
|
|
82252
|
-
async function
|
|
83435
|
+
async function run18(argv, io) {
|
|
82253
83436
|
let parsed;
|
|
82254
83437
|
try {
|
|
82255
83438
|
parsed = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
|
|
@@ -82365,10 +83548,12 @@ async function run17(argv, io) {
|
|
|
82365
83548
|
if (rest.length !== 1)
|
|
82366
83549
|
return usage4(io, `ledger verdict takes one result: ${VERDICT_RESULTS.join("|")}`);
|
|
82367
83550
|
const claimed = values.self === true ? null : claimedSession(acting);
|
|
82368
|
-
const
|
|
83551
|
+
const token = values.token ?? io.env.WORKIT_SESSION_TOKEN ?? null;
|
|
83552
|
+
const refusal = claimed === null ? null : sessionClaimRefusal(io.env, claimed, io.cwd, { token, flag: acting.source === "flag" });
|
|
82369
83553
|
if (refusal)
|
|
82370
83554
|
return emit(io, fail("blocked", refusal.error, { unblock: refusal.unblock }));
|
|
82371
|
-
const
|
|
83555
|
+
const bound = claimed !== null && issuedTokenMatches(io.cwd, claimed, token);
|
|
83556
|
+
const verdict = await recorded(recordVerdict({ ...context, ...bound ? { sessionBound: true } : {} }, {
|
|
82372
83557
|
result: rest[0],
|
|
82373
83558
|
kind: values.kind,
|
|
82374
83559
|
how: values.how,
|
|
@@ -82384,7 +83569,12 @@ async function run17(argv, io) {
|
|
|
82384
83569
|
if (shadowed)
|
|
82385
83570
|
io.stderr(`warning: ${shadowed}
|
|
82386
83571
|
`);
|
|
82387
|
-
|
|
83572
|
+
const withGate = verdict.ok ? { ok: true, value: { ...verdict.value, gate: gateReading(io.cwd, verdict.value) } } : verdict;
|
|
83573
|
+
return fromResult(io, withGate, (row) => [
|
|
83574
|
+
`recorded verdict ${row.id}: ${row.result} [${row.kind}] for ${row.branch} @ ${(row.head ?? "").slice(0, 12)} as ${actor.session ?? "no session"}${row.self ? ` (self${row.selfReason === "no_session" ? ": WORKIT_SESSION_ID unset" : ""}; never accepted)` : ""}`,
|
|
83575
|
+
row.gate ? `gate: ${row.branch} ${row.gate.accepted ? `accepted (verdict ${row.gate.verdictId ?? "?"})` : `not accepted (${row.gate.reasons.join(", ")})`}` : "gate: unknown (ledger unreadable)"
|
|
83576
|
+
].join(`
|
|
83577
|
+
`));
|
|
82388
83578
|
}
|
|
82389
83579
|
function standingFanout(io, values) {
|
|
82390
83580
|
if (values.fanout !== undefined) {
|
|
@@ -82441,7 +83631,7 @@ async function standing(io, values, args) {
|
|
|
82441
83631
|
return usage4(io, `unexpected argument: ${rest[1]}`);
|
|
82442
83632
|
return fromResult(io, clearStanding(context, { fanout, lead, leads, target: rest[0] ?? null }), (row) => row.target ? `cleared standing order ${row.target} for fanout ${fanout}` : `cleared every standing order for fanout ${fanout}`);
|
|
82443
83633
|
}
|
|
82444
|
-
var
|
|
83634
|
+
var USAGE10 = "workit ledger decision|ruling|verdict|standing|list|check ... (workit help ledger for the grammar)", STANDING_USAGE = 'workit ledger standing add "<order>" | list | clear [<id>] [--fanout <name>] [--json]', OPTIONS, SESSION_SAFE2, ROLE_SAFE, actorFor = (io, values) => {
|
|
82445
83635
|
const actor = actorFromEnv(io.env);
|
|
82446
83636
|
if (values.session !== undefined && values.as !== undefined)
|
|
82447
83637
|
return new Error("pass --session or --as, not both");
|
|
@@ -82455,7 +83645,7 @@ var USAGE9 = "workit ledger decision|ruling|verdict|standing|list|check ... (wor
|
|
|
82455
83645
|
return new Error("--as takes a lowercase role, e.g. verifier");
|
|
82456
83646
|
const prefix = (actor.session ?? actor.host).slice(0, 96);
|
|
82457
83647
|
return {
|
|
82458
|
-
actor: { ...actor, session: `${prefix}:${values.as}:${
|
|
83648
|
+
actor: { ...actor, session: `${prefix}:${values.as}:${randomBytes6(4).toString("hex")}` },
|
|
82459
83649
|
source: "as",
|
|
82460
83650
|
derivedFrom: actor.session
|
|
82461
83651
|
};
|
|
@@ -82466,7 +83656,7 @@ var USAGE9 = "workit ledger decision|ruling|verdict|standing|list|check ... (wor
|
|
|
82466
83656
|
return;
|
|
82467
83657
|
const parsed = Number(value);
|
|
82468
83658
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : new Error(`${flag} must be a positive integer`);
|
|
82469
|
-
}, failed2 = (io, result) => emit(io, fail(result.code, result.error, result.unblock ? { unblock: result.unblock } : {})), fromResult = (io, result, human) => result.ok ? emit(io, ok(result.value), human) : failed2(io, result), usage4 = (io, error) => emit(io, fail("invalid_input", error, { unblock:
|
|
83659
|
+
}, failed2 = (io, result) => emit(io, fail(result.code, result.error, result.unblock ? { unblock: result.unblock } : {})), fromResult = (io, result, human) => result.ok ? emit(io, ok(result.value), human) : failed2(io, result), usage4 = (io, error) => emit(io, fail("invalid_input", error, { unblock: USAGE10 })), passedTestOn = (cwd, verdict) => {
|
|
82470
83660
|
const ledger = readLedger(cwd);
|
|
82471
83661
|
return ledger.ok && ledger.value.rows.some((row) => row.type === "check" && row.observer === "workit_cli" && row.name === "test" && row.result === "passed" && row.dirty !== true && row.branch === verdict.branch && row.head === verdict.head);
|
|
82472
83662
|
}, verdictLines = (check) => {
|
|
@@ -82498,7 +83688,17 @@ var USAGE9 = "workit ledger decision|ruling|verdict|standing|list|check ... (wor
|
|
|
82498
83688
|
] : [
|
|
82499
83689
|
`${data.unverified_rows.length} unverified row(s) (not written by the workit CLI, or changed afterwards):`,
|
|
82500
83690
|
...data.unverified_rows.map((row) => ` #${row.seq} ${row.id} ${row.type}${row.branch ? ` [${row.branch}]` : ""}: ${row.reason}`)
|
|
82501
|
-
]
|
|
83691
|
+
], gateReading = (cwd, row) => {
|
|
83692
|
+
const ledger = readLedger(cwd, { integrity: true });
|
|
83693
|
+
if (!ledger.ok || !row.branch)
|
|
83694
|
+
return null;
|
|
83695
|
+
const { accepted } = checkVerdicts(cwd, row.branch, ledger.value.rows);
|
|
83696
|
+
return {
|
|
83697
|
+
accepted: accepted.accepted,
|
|
83698
|
+
reasons: accepted.reasons,
|
|
83699
|
+
verdictId: typeof accepted.verdict?.id === "string" ? accepted.verdict.id : null
|
|
83700
|
+
};
|
|
83701
|
+
};
|
|
82502
83702
|
var init_ledger2 = __esm(() => {
|
|
82503
83703
|
init_ledger();
|
|
82504
83704
|
init_fanout();
|
|
@@ -82522,6 +83722,7 @@ var init_ledger2 = __esm(() => {
|
|
|
82522
83722
|
last: { type: "string" },
|
|
82523
83723
|
supersedes: { type: "string" },
|
|
82524
83724
|
session: { type: "string" },
|
|
83725
|
+
token: { type: "string" },
|
|
82525
83726
|
as: { type: "string" },
|
|
82526
83727
|
fanout: { type: "string" },
|
|
82527
83728
|
json: { type: "boolean" }
|
|
@@ -98377,11 +99578,11 @@ var init_rules = __esm(() => {
|
|
|
98377
99578
|
});
|
|
98378
99579
|
|
|
98379
99580
|
// packages/workit-cli/src/test-audit/audit.ts
|
|
98380
|
-
import { spawnSync as
|
|
98381
|
-
import { existsSync as existsSync20, readdirSync as
|
|
99581
|
+
import { spawnSync as spawnSync27 } from "node:child_process";
|
|
99582
|
+
import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync23, statSync as statSync8 } from "node:fs";
|
|
98382
99583
|
import path52 from "node:path";
|
|
98383
99584
|
function git7(cwd, args) {
|
|
98384
|
-
const run =
|
|
99585
|
+
const run = spawnSync27("git", ["-c", "core.quotepath=off", ...args], {
|
|
98385
99586
|
cwd,
|
|
98386
99587
|
encoding: "utf8",
|
|
98387
99588
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -98389,7 +99590,7 @@ function git7(cwd, args) {
|
|
|
98389
99590
|
return { ok: run.status === 0, stdout: run.stdout ?? "" };
|
|
98390
99591
|
}
|
|
98391
99592
|
function walkTests(dir, out) {
|
|
98392
|
-
for (const entry of
|
|
99593
|
+
for (const entry of readdirSync10(dir, { withFileTypes: true })) {
|
|
98393
99594
|
if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
|
|
98394
99595
|
continue;
|
|
98395
99596
|
const full = path52.join(dir, entry.name);
|
|
@@ -98525,7 +99726,7 @@ import {
|
|
|
98525
99726
|
lstatSync as lstatSync8,
|
|
98526
99727
|
mkdirSync as mkdirSync10,
|
|
98527
99728
|
mkdtempSync as mkdtempSync4,
|
|
98528
|
-
readdirSync as
|
|
99729
|
+
readdirSync as readdirSync11,
|
|
98529
99730
|
readFileSync as readFileSync24,
|
|
98530
99731
|
readlinkSync as readlinkSync3,
|
|
98531
99732
|
realpathSync as realpathSync9,
|
|
@@ -98622,7 +99823,7 @@ function removeStaleCopies(tmp = os16.tmpdir()) {
|
|
|
98622
99823
|
let removed = 0;
|
|
98623
99824
|
let names = [];
|
|
98624
99825
|
try {
|
|
98625
|
-
names =
|
|
99826
|
+
names = readdirSync11(tmp);
|
|
98626
99827
|
} catch {
|
|
98627
99828
|
return 0;
|
|
98628
99829
|
}
|
|
@@ -98651,7 +99852,7 @@ function removeStaleCopies(tmp = os16.tmpdir()) {
|
|
|
98651
99852
|
}
|
|
98652
99853
|
function linkNodeModules(origin, target, repo, copy) {
|
|
98653
99854
|
mkdirSync10(target, { recursive: true });
|
|
98654
|
-
for (const entry of
|
|
99855
|
+
for (const entry of readdirSync11(origin, { withFileTypes: true })) {
|
|
98655
99856
|
const from = path53.join(origin, entry.name);
|
|
98656
99857
|
const to = path53.join(target, entry.name);
|
|
98657
99858
|
if (entry.name.startsWith("@") && entry.isDirectory()) {
|
|
@@ -98845,7 +100046,7 @@ async function runMutation(options) {
|
|
|
98845
100046
|
if (!runnable.includes(mutant))
|
|
98846
100047
|
record({ ...mutant, status: "no-tests", durationMs: 0, tests: [] });
|
|
98847
100048
|
if (runnable.length === 0)
|
|
98848
|
-
return
|
|
100049
|
+
return finish3(options, 0, results, counts, started);
|
|
98849
100050
|
removeStaleCopies();
|
|
98850
100051
|
const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
|
|
98851
100052
|
const copyCwd = path53.join(copy, cwdRelative);
|
|
@@ -98909,7 +100110,7 @@ async function runMutation(options) {
|
|
|
98909
100110
|
restore();
|
|
98910
100111
|
}
|
|
98911
100112
|
}
|
|
98912
|
-
return
|
|
100113
|
+
return finish3(options, baseline.durationMs, results, counts, started);
|
|
98913
100114
|
} finally {
|
|
98914
100115
|
process.off("SIGINT", onSignal);
|
|
98915
100116
|
process.off("SIGTERM", onSignal);
|
|
@@ -98917,7 +100118,7 @@ async function runMutation(options) {
|
|
|
98917
100118
|
cleanup();
|
|
98918
100119
|
}
|
|
98919
100120
|
}
|
|
98920
|
-
function
|
|
100121
|
+
function finish3(options, baselineMs, results, counts, started) {
|
|
98921
100122
|
const decided = counts.killed + counts.timeout + counts.survived;
|
|
98922
100123
|
return {
|
|
98923
100124
|
base: options.base,
|
|
@@ -98987,7 +100188,7 @@ var init_mutate = __esm(() => {
|
|
|
98987
100188
|
// packages/workit-cli/src/verbs/test-audit.ts
|
|
98988
100189
|
var exports_test_audit = {};
|
|
98989
100190
|
__export(exports_test_audit, {
|
|
98990
|
-
run: () =>
|
|
100191
|
+
run: () => run19
|
|
98991
100192
|
});
|
|
98992
100193
|
import { existsSync as existsSync22 } from "node:fs";
|
|
98993
100194
|
import path54 from "node:path";
|
|
@@ -99088,11 +100289,11 @@ function human(data) {
|
|
|
99088
100289
|
}
|
|
99089
100290
|
return lines;
|
|
99090
100291
|
}
|
|
99091
|
-
async function
|
|
100292
|
+
async function run19(argv, io) {
|
|
99092
100293
|
const options = parse7(argv);
|
|
99093
100294
|
if (typeof options === "string")
|
|
99094
100295
|
return emit(io, fail("invalid_input", `${options}
|
|
99095
|
-
usage: ${
|
|
100296
|
+
usage: ${USAGE11}`));
|
|
99096
100297
|
const top = git7(io.cwd, ["rev-parse", "--show-toplevel"]);
|
|
99097
100298
|
const repo = top.ok ? path54.resolve(top.stdout.trim()) : null;
|
|
99098
100299
|
const display = (file) => toPosix(path54.relative(io.cwd, file) || file);
|
|
@@ -99178,7 +100379,7 @@ usage: ${USAGE10}`));
|
|
|
99178
100379
|
}
|
|
99179
100380
|
return emit(io, ok(data), human);
|
|
99180
100381
|
}
|
|
99181
|
-
var
|
|
100382
|
+
var USAGE11 = "workit test-audit [paths…] [--diff [base]] [--rule <ids>] [--min-severity <level>] [--fail-on <level>] [--mutate [--test-cmd <cmd>] [--max-mutants <n>] [--budget <s>] [--timeout <s>]] [--json]", LEVELS, VALUE_FLAGS2, quote = (arg) => /^[\w./@:=+-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, "'\\''")}'`;
|
|
99182
100383
|
var init_test_audit = __esm(() => {
|
|
99183
100384
|
init_audit();
|
|
99184
100385
|
init_mutate();
|
|
@@ -99199,12 +100400,12 @@ var init_test_audit = __esm(() => {
|
|
|
99199
100400
|
// packages/workit-cli/src/verbs/knowledge.ts
|
|
99200
100401
|
var exports_knowledge = {};
|
|
99201
100402
|
__export(exports_knowledge, {
|
|
99202
|
-
run: () =>
|
|
100403
|
+
run: () => run20
|
|
99203
100404
|
});
|
|
99204
|
-
async function
|
|
100405
|
+
async function run20(argv, io) {
|
|
99205
100406
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
99206
100407
|
if (sub !== "lint" || rest.length > 0)
|
|
99207
|
-
return emit(io, fail("invalid_input", `usage: ${
|
|
100408
|
+
return emit(io, fail("invalid_input", `usage: ${USAGE12}`));
|
|
99208
100409
|
const report = lintKnowledge(checkRoot(io.cwd));
|
|
99209
100410
|
if (report.unreadable.length > 0)
|
|
99210
100411
|
return emit(io, fail("unavailable", `knowledge lint: cannot read ${report.unreadable.join(", ")}`, {
|
|
@@ -99221,7 +100422,7 @@ async function run19(argv, io) {
|
|
|
99221
100422
|
`);
|
|
99222
100423
|
return report.findings.length === 0 ? 0 : 1;
|
|
99223
100424
|
}
|
|
99224
|
-
var
|
|
100425
|
+
var USAGE12 = "workit knowledge lint [--json]";
|
|
99225
100426
|
var init_knowledge2 = __esm(() => {
|
|
99226
100427
|
init_check_config();
|
|
99227
100428
|
init_knowledge();
|
|
@@ -99335,14 +100536,14 @@ var init_youtrack_writes = __esm(() => {
|
|
|
99335
100536
|
// packages/workit-cli/src/verbs/youtrack.ts
|
|
99336
100537
|
var exports_youtrack = {};
|
|
99337
100538
|
__export(exports_youtrack, {
|
|
99338
|
-
run: () =>
|
|
100539
|
+
run: () => run21
|
|
99339
100540
|
});
|
|
99340
100541
|
import { readFileSync as readFileSync25 } from "node:fs";
|
|
99341
100542
|
import path55 from "node:path";
|
|
99342
|
-
async function
|
|
100543
|
+
async function run21(argv, io) {
|
|
99343
100544
|
const [sub, ...rest] = argv;
|
|
99344
100545
|
if (sub !== "note" && sub !== "time" && sub !== "meeting")
|
|
99345
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
100546
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE13);
|
|
99346
100547
|
const flags = parseFlags(rest, {
|
|
99347
100548
|
markdown: "value",
|
|
99348
100549
|
file: "value",
|
|
@@ -99351,18 +100552,18 @@ async function run20(argv, io) {
|
|
|
99351
100552
|
date: "value"
|
|
99352
100553
|
});
|
|
99353
100554
|
if (typeof flags === "string")
|
|
99354
|
-
return usage3(io, flags,
|
|
100555
|
+
return usage3(io, flags, USAGE13);
|
|
99355
100556
|
const [issueId, extra] = flags.positionals;
|
|
99356
100557
|
if (!issueId || !ISSUE_RE2.test(issueId))
|
|
99357
|
-
return usage3(io, "an issue id like ABC-123 is required",
|
|
100558
|
+
return usage3(io, "an issue id like ABC-123 is required", USAGE13);
|
|
99358
100559
|
if (extra !== undefined)
|
|
99359
|
-
return usage3(io, `unexpected argument ${extra}`,
|
|
100560
|
+
return usage3(io, `unexpected argument ${extra}`, USAGE13);
|
|
99360
100561
|
const minutes = positiveInt(flags.values.minutes, "--minutes");
|
|
99361
100562
|
if (typeof minutes === "string")
|
|
99362
|
-
return usage3(io, minutes,
|
|
100563
|
+
return usage3(io, minutes, USAGE13);
|
|
99363
100564
|
const date = youTrackWorkDateMs(flags.values.date ?? "auto");
|
|
99364
100565
|
if ("error" in date)
|
|
99365
|
-
return usage3(io, date.error,
|
|
100566
|
+
return usage3(io, date.error, USAGE13);
|
|
99366
100567
|
const dateMs = date.data.dateMs;
|
|
99367
100568
|
const markedDay = flags.values.date !== undefined && flags.values.date !== "auto" ? { dateMs } : {};
|
|
99368
100569
|
const root = io.cwd;
|
|
@@ -99380,24 +100581,24 @@ async function run20(argv, io) {
|
|
|
99380
100581
|
};
|
|
99381
100582
|
if (sub === "note") {
|
|
99382
100583
|
if (Boolean(flags.values.markdown) === Boolean(flags.values.file))
|
|
99383
|
-
return usage3(io, "pass exactly one of --markdown or --file",
|
|
100584
|
+
return usage3(io, "pass exactly one of --markdown or --file", USAGE13);
|
|
99384
100585
|
let markdown = flags.values.markdown ?? "";
|
|
99385
100586
|
if (flags.values.file)
|
|
99386
100587
|
try {
|
|
99387
100588
|
markdown = readFileSync25(path55.resolve(io.cwd, flags.values.file), "utf8");
|
|
99388
100589
|
} catch (error) {
|
|
99389
|
-
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`,
|
|
100590
|
+
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE13);
|
|
99390
100591
|
}
|
|
99391
100592
|
if (!markdown.trim())
|
|
99392
|
-
return usage3(io, "the note is empty",
|
|
100593
|
+
return usage3(io, "the note is empty", USAGE13);
|
|
99393
100594
|
const missing = step("comment", "comments", { operation: "note", issueId, markdown }, markdown, async (text) => commentResult(await postComment(issueId, text, root))) ?? (minutes ? step("time", "workItems", { operation: "note.time", issueId, markdown, minutes, ...markedDay }, "workit update", (text) => logTimeUpdate({ issueId, minutes, text, dateMs, workspace_root: root })) : null);
|
|
99394
100595
|
if (missing)
|
|
99395
100596
|
return emit(io, fail("unavailable", missing));
|
|
99396
100597
|
} else {
|
|
99397
100598
|
if (!minutes)
|
|
99398
|
-
return usage3(io, `${sub} needs --minutes <n>`,
|
|
100599
|
+
return usage3(io, `${sub} needs --minutes <n>`, USAGE13);
|
|
99399
100600
|
if (sub === "meeting" && !flags.values.text)
|
|
99400
|
-
return usage3(io, "meeting needs --text <what the meeting was>",
|
|
100601
|
+
return usage3(io, "meeting needs --text <what the meeting was>", USAGE13);
|
|
99401
100602
|
const text = flags.values.text ?? "";
|
|
99402
100603
|
const missing = step(sub, "workItems", { operation: sub, issueId, minutes, text, ...markedDay }, text, (marked) => logTimeUpdate({ issueId, minutes, text: marked, dateMs, workspace_root: root }));
|
|
99403
100604
|
if (missing)
|
|
@@ -99426,7 +100627,7 @@ async function run20(argv, io) {
|
|
|
99426
100627
|
...data.steps.flatMap((entry) => entry.note ? [` note (${entry.step}): ${entry.note}`] : [])
|
|
99427
100628
|
]);
|
|
99428
100629
|
}
|
|
99429
|
-
var
|
|
100630
|
+
var USAGE13 = "workit youtrack note <ISSUE> (--markdown <t> | --file <p>) [--minutes <n>] [--date auto|YYYY-MM-DD] | youtrack time <ISSUE> --minutes <n> [--text <t>] [--date …] | youtrack meeting <ISSUE> --minutes <n> --text <t> [--date …]", commentResult = (value) => {
|
|
99430
100631
|
try {
|
|
99431
100632
|
return { ok: true, data: unwrap2(value) };
|
|
99432
100633
|
} catch (error) {
|
|
@@ -99450,14 +100651,14 @@ var init_youtrack2 = __esm(() => {
|
|
|
99450
100651
|
// packages/workit-cli/src/verbs/changelog.ts
|
|
99451
100652
|
var exports_changelog = {};
|
|
99452
100653
|
__export(exports_changelog, {
|
|
99453
|
-
run: () =>
|
|
100654
|
+
run: () => run22
|
|
99454
100655
|
});
|
|
99455
100656
|
import { readFileSync as readFileSync26 } from "node:fs";
|
|
99456
100657
|
import path56 from "node:path";
|
|
99457
|
-
async function
|
|
100658
|
+
async function run22(argv, io) {
|
|
99458
100659
|
const [sub, ...rest] = argv;
|
|
99459
100660
|
if (sub !== "apply")
|
|
99460
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
100661
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE14);
|
|
99461
100662
|
const flags = parseFlags(rest, {
|
|
99462
100663
|
entries: "value",
|
|
99463
100664
|
path: "value",
|
|
@@ -99465,18 +100666,18 @@ async function run21(argv, io) {
|
|
|
99465
100666
|
preview: "boolean"
|
|
99466
100667
|
});
|
|
99467
100668
|
if (typeof flags === "string")
|
|
99468
|
-
return usage3(io, flags,
|
|
100669
|
+
return usage3(io, flags, USAGE14);
|
|
99469
100670
|
if (flags.positionals.length)
|
|
99470
|
-
return usage3(io, `unexpected argument ${flags.positionals[0]}`,
|
|
100671
|
+
return usage3(io, `unexpected argument ${flags.positionals[0]}`, USAGE14);
|
|
99471
100672
|
const normalizeOnly = flags.booleans.has("normalize-only");
|
|
99472
100673
|
if (!normalizeOnly && !flags.values.entries)
|
|
99473
|
-
return usage3(io, "pass --entries <JSON> or --normalize-only",
|
|
100674
|
+
return usage3(io, "pass --entries <JSON> or --normalize-only", USAGE14);
|
|
99474
100675
|
let entries;
|
|
99475
100676
|
if (flags.values.entries)
|
|
99476
100677
|
try {
|
|
99477
100678
|
entries = readEntries(flags.values.entries, io.cwd);
|
|
99478
100679
|
} catch (error) {
|
|
99479
|
-
return usage3(io, `--entries is not valid JSON: ${String(error)}`,
|
|
100680
|
+
return usage3(io, `--entries is not valid JSON: ${String(error)}`, USAGE14);
|
|
99480
100681
|
}
|
|
99481
100682
|
const input = {
|
|
99482
100683
|
entries,
|
|
@@ -99498,7 +100699,7 @@ async function run21(argv, io) {
|
|
|
99498
100699
|
`changelog updated: ${String(applied.path ?? "CHANGELOG.md")}`
|
|
99499
100700
|
]);
|
|
99500
100701
|
}
|
|
99501
|
-
var
|
|
100702
|
+
var USAGE14 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
|
|
99502
100703
|
const text = raw === "-" ? readFileSync26(0, "utf8") : raw.startsWith("@") ? readFileSync26(path56.resolve(cwd, raw.slice(1)), "utf8") : raw;
|
|
99503
100704
|
return JSON.parse(text);
|
|
99504
100705
|
};
|
|
@@ -99511,12 +100712,12 @@ var init_changelog2 = __esm(() => {
|
|
|
99511
100712
|
// packages/workit-cli/src/verbs/handoff.ts
|
|
99512
100713
|
var exports_handoff = {};
|
|
99513
100714
|
__export(exports_handoff, {
|
|
99514
|
-
run: () =>
|
|
100715
|
+
run: () => run23
|
|
99515
100716
|
});
|
|
99516
100717
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
99517
100718
|
function human2(brief) {
|
|
99518
100719
|
const lines = [
|
|
99519
|
-
`branch: ${brief.branch ?? "(detached)"} @ ${
|
|
100720
|
+
`branch: ${brief.branch ?? "(detached)"} @ ${short7(brief.head)}${brief.subject ? ` ${brief.subject}` : ""}`
|
|
99520
100721
|
];
|
|
99521
100722
|
if (brief.base)
|
|
99522
100723
|
lines.push(`base: ${brief.base}${brief.ahead === null ? "" : ` (ahead ${brief.ahead}, behind ${brief.behind})`}`);
|
|
@@ -99559,11 +100760,11 @@ async function resumeBrief(argv, io) {
|
|
|
99559
100760
|
strict: true
|
|
99560
100761
|
}));
|
|
99561
100762
|
} catch (error) {
|
|
99562
|
-
return emit(io, fail("invalid_input", error.message, { unblock:
|
|
100763
|
+
return emit(io, fail("invalid_input", error.message, { unblock: USAGE15 }));
|
|
99563
100764
|
}
|
|
99564
100765
|
const last = values.last === undefined ? 10 : Number(values.last);
|
|
99565
100766
|
if (!Number.isInteger(last) || last < 1)
|
|
99566
|
-
return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock:
|
|
100767
|
+
return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock: USAGE15 }));
|
|
99567
100768
|
const built = buildHandoff(io.cwd, {
|
|
99568
100769
|
last,
|
|
99569
100770
|
note: values.note ?? null,
|
|
@@ -99581,13 +100782,13 @@ async function resumeBrief(argv, io) {
|
|
|
99581
100782
|
}
|
|
99582
100783
|
return emit(io, ok({ ...data, recorded }), human2);
|
|
99583
100784
|
}
|
|
99584
|
-
async function
|
|
100785
|
+
async function run23(argv, io) {
|
|
99585
100786
|
if (!argv.some((arg) => arg === "--task" || arg.startsWith("--task=")))
|
|
99586
100787
|
return resumeBrief(argv, io);
|
|
99587
100788
|
await Promise.resolve().then(() => init_task());
|
|
99588
100789
|
return runTaskCommand(["handoff", ...argv]);
|
|
99589
100790
|
}
|
|
99590
|
-
var
|
|
100791
|
+
var USAGE15 = "workit handoff [--note <text>] [--next <text>] [--record] [--last <n>] [--json]", short7 = (sha) => sha ? sha.slice(0, 12) : "no commit";
|
|
99591
100792
|
var init_handoff = __esm(() => {
|
|
99592
100793
|
init_ledger();
|
|
99593
100794
|
init_output();
|
|
@@ -99788,7 +100989,7 @@ function withSubcommands(entry, options = {}) {
|
|
|
99788
100989
|
};
|
|
99789
100990
|
}
|
|
99790
100991
|
var VERIFY_OPTIONS = "[--expect pushed|pr|merged|released] [--pr <n> | --branch <b>] [--sha <sha>] [--tag <t>] [--package <name[@version]>]";
|
|
99791
|
-
var LEDGER_ACTOR = "[--branch <b> | --pr <n>] [--supersedes <id>] [--session <id> | --as <role>]";
|
|
100992
|
+
var LEDGER_ACTOR = "[--branch <b> | --pr <n>] [--supersedes <id>] [--session <id> [--token <t>] | --as <role>]";
|
|
99792
100993
|
var VERBS = [
|
|
99793
100994
|
{
|
|
99794
100995
|
name: "init",
|
|
@@ -99966,6 +101167,35 @@ var VERBS = [
|
|
|
99966
101167
|
],
|
|
99967
101168
|
load: () => Promise.resolve().then(() => (init_git2(), exports_git))
|
|
99968
101169
|
}),
|
|
101170
|
+
withSubcommands({
|
|
101171
|
+
name: "release",
|
|
101172
|
+
group: "delivery",
|
|
101173
|
+
summary: "git-flow releases without the git-flow binary: start a local release branch, finish it (merge, tag, merge back, push, forge release), publish it only when asked",
|
|
101174
|
+
subcommands: [
|
|
101175
|
+
{
|
|
101176
|
+
name: "start",
|
|
101177
|
+
usage: "workit release start <version> [--hotfix] [--track <t>]",
|
|
101178
|
+
summary: "Cut release/<version> (hotfix/<version> from production) locally; never pushes it"
|
|
101179
|
+
},
|
|
101180
|
+
{
|
|
101181
|
+
name: "finish",
|
|
101182
|
+
usage: "workit release finish [<version>] [--track <t>] [--dry-run]",
|
|
101183
|
+
summary: "Merge into production, tag, merge back, delete the branch, push production, merge-back branches and the tag, publish the forge release; resumes an interrupted finish"
|
|
101184
|
+
},
|
|
101185
|
+
{
|
|
101186
|
+
name: "publish",
|
|
101187
|
+
usage: "workit release publish [<version>]",
|
|
101188
|
+
summary: "Push the release branch (opt-in: others commit to it or CI runs on it)"
|
|
101189
|
+
}
|
|
101190
|
+
],
|
|
101191
|
+
examples: [
|
|
101192
|
+
"workit release start 1.4.0",
|
|
101193
|
+
'workit git commit -m "chore(release): 1.4.0" -- package.json CHANGELOG.md',
|
|
101194
|
+
"workit release finish --dry-run",
|
|
101195
|
+
"workit release finish"
|
|
101196
|
+
],
|
|
101197
|
+
load: () => Promise.resolve().then(() => (init_release2(), exports_release))
|
|
101198
|
+
}),
|
|
99969
101199
|
withSubcommands({
|
|
99970
101200
|
name: "verify-delivery",
|
|
99971
101201
|
group: "delivery",
|
|
@@ -100088,7 +101318,7 @@ var VERBS = [
|
|
|
100088
101318
|
},
|
|
100089
101319
|
{
|
|
100090
101320
|
name: "standing",
|
|
100091
|
-
usage: `workit ledger standing add "<order>" | standing list | standing clear [<id>] [--fanout <name>] [--supersedes <id>] [--session <id> | --as <role>]`,
|
|
101321
|
+
usage: `workit ledger standing add "<order>" | standing list | standing clear [<id>] [--fanout <name>] [--supersedes <id>] [--session <id> [--token <t>] | --as <role>]`,
|
|
100092
101322
|
summary: "The lead's standing orders for a fanout's workers"
|
|
100093
101323
|
},
|
|
100094
101324
|
{
|