@brainervirus/workit-cursor 8.1.1 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +798 -84
- package/dist/mcp-server.js +83 -4
- package/dist/workit-hook.js +799 -58
- package/package.json +3 -3
- package/skills/workit-ship/SKILL.md +14 -14
package/dist/workit-hook.js
CHANGED
|
@@ -8527,9 +8527,28 @@ function patchId(cwd, base, head) {
|
|
|
8527
8527
|
const id = git(cwd, ["patch-id", "--stable"], { input: diff.stdout });
|
|
8528
8528
|
return line(id)?.split(/\s+/u)[0] ?? null;
|
|
8529
8529
|
}
|
|
8530
|
+
function remoteNames(cwd) {
|
|
8531
|
+
return (git(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
8532
|
+
}
|
|
8533
|
+
var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
8530
8534
|
function currentBranch(cwd) {
|
|
8531
8535
|
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
8532
8536
|
}
|
|
8537
|
+
function pushRemoteName(cwd, branch) {
|
|
8538
|
+
const name = branch ?? currentBranch(cwd);
|
|
8539
|
+
const remotes = remoteNames(cwd);
|
|
8540
|
+
const candidates = [
|
|
8541
|
+
name ? config2(cwd, `branch.${name}.pushRemote`) : null,
|
|
8542
|
+
config2(cwd, "remote.pushDefault"),
|
|
8543
|
+
name ? config2(cwd, `branch.${name}.remote`) : null
|
|
8544
|
+
];
|
|
8545
|
+
for (const candidate of candidates)
|
|
8546
|
+
if (candidate && candidate !== "." && remotes.includes(candidate))
|
|
8547
|
+
return candidate;
|
|
8548
|
+
if (remotes.includes("origin"))
|
|
8549
|
+
return "origin";
|
|
8550
|
+
return remotes.length === 1 ? remotes[0] : null;
|
|
8551
|
+
}
|
|
8533
8552
|
|
|
8534
8553
|
// packages/workit-core/src/ledger.ts
|
|
8535
8554
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -8537,6 +8556,35 @@ import { createHash as createHash4, randomBytes } from "node:crypto";
|
|
|
8537
8556
|
import fs3 from "node:fs";
|
|
8538
8557
|
import path3 from "node:path";
|
|
8539
8558
|
|
|
8559
|
+
// packages/workit-core/src/host-session.ts
|
|
8560
|
+
var HOST_SESSION_VARS = [
|
|
8561
|
+
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
8562
|
+
["PI_SESSION_ID", () => "pi"],
|
|
8563
|
+
[
|
|
8564
|
+
"CODEX_THREAD_ID",
|
|
8565
|
+
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
8566
|
+
]
|
|
8567
|
+
];
|
|
8568
|
+
var family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
|
|
8569
|
+
function hostSessionFromEnv(env) {
|
|
8570
|
+
const value = (key) => env[key]?.trim() || null;
|
|
8571
|
+
let inner = null;
|
|
8572
|
+
for (const [key, host] of HOST_SESSION_VARS) {
|
|
8573
|
+
const session = value(key);
|
|
8574
|
+
if (session) {
|
|
8575
|
+
inner = { host: host(env), session };
|
|
8576
|
+
break;
|
|
8577
|
+
}
|
|
8578
|
+
}
|
|
8579
|
+
const declared = value("WORKIT_HOST");
|
|
8580
|
+
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
8581
|
+
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
8582
|
+
if (!inherited)
|
|
8583
|
+
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
8584
|
+
}
|
|
8585
|
+
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
8586
|
+
}
|
|
8587
|
+
|
|
8540
8588
|
// packages/workit-core/src/store/paths.ts
|
|
8541
8589
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
8542
8590
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -8819,6 +8867,7 @@ var checkoutSlug = (root) => {
|
|
|
8819
8867
|
|
|
8820
8868
|
// packages/workit-core/src/ledger.ts
|
|
8821
8869
|
var LEDGER_VERSION = 1;
|
|
8870
|
+
var MAX_LINE_BYTES = 4096;
|
|
8822
8871
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
8823
8872
|
var FAILING = new Set(["failed", "blocked"]);
|
|
8824
8873
|
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
@@ -8918,6 +8967,7 @@ function ledgerPath(cwd) {
|
|
|
8918
8967
|
const root = storeRoot(cwd);
|
|
8919
8968
|
return root.ok ? { ok: true, value: path3.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
8920
8969
|
}
|
|
8970
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
|
|
8921
8971
|
var NETWORK_FS_TYPES = new Set([
|
|
8922
8972
|
26985,
|
|
8923
8973
|
20859,
|
|
@@ -8930,6 +8980,158 @@ var NETWORK_FS_TYPES = new Set([
|
|
|
8930
8980
|
1196443219,
|
|
8931
8981
|
198183888
|
|
8932
8982
|
]);
|
|
8983
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
8984
|
+
var onNetworkFs = (dir) => {
|
|
8985
|
+
if (process.platform !== "linux")
|
|
8986
|
+
return false;
|
|
8987
|
+
try {
|
|
8988
|
+
return isNetworkFsType(fs3.statfsSync(dir).type);
|
|
8989
|
+
} catch {
|
|
8990
|
+
return false;
|
|
8991
|
+
}
|
|
8992
|
+
};
|
|
8993
|
+
var LOCK_WAIT_MS = 5000;
|
|
8994
|
+
var LOCK_STALE_MS = 30000;
|
|
8995
|
+
var LOCK_OWNER = "owner";
|
|
8996
|
+
var lockToken = (lock) => {
|
|
8997
|
+
try {
|
|
8998
|
+
return fs3.readFileSync(path3.join(lock, LOCK_OWNER), "utf8");
|
|
8999
|
+
} catch {
|
|
9000
|
+
return null;
|
|
9001
|
+
}
|
|
9002
|
+
};
|
|
9003
|
+
var lockAgeMs = (lock) => {
|
|
9004
|
+
for (const target of [path3.join(lock, LOCK_OWNER), lock]) {
|
|
9005
|
+
try {
|
|
9006
|
+
return Date.now() - fs3.statSync(target).mtimeMs;
|
|
9007
|
+
} catch {}
|
|
9008
|
+
}
|
|
9009
|
+
return null;
|
|
9010
|
+
};
|
|
9011
|
+
var ledgerLock = {
|
|
9012
|
+
acquire(lock) {
|
|
9013
|
+
try {
|
|
9014
|
+
fs3.mkdirSync(lock);
|
|
9015
|
+
} catch (error) {
|
|
9016
|
+
if (error.code === "EEXIST")
|
|
9017
|
+
return null;
|
|
9018
|
+
throw error;
|
|
9019
|
+
}
|
|
9020
|
+
const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
9021
|
+
fs3.writeFileSync(path3.join(lock, LOCK_OWNER), token);
|
|
9022
|
+
return token;
|
|
9023
|
+
},
|
|
9024
|
+
release(lock, token) {
|
|
9025
|
+
if (lockToken(lock) !== token)
|
|
9026
|
+
return false;
|
|
9027
|
+
const tomb = `${lock}.release-${randomBytes(6).toString("hex")}`;
|
|
9028
|
+
try {
|
|
9029
|
+
fs3.renameSync(lock, tomb);
|
|
9030
|
+
} catch {
|
|
9031
|
+
return false;
|
|
9032
|
+
}
|
|
9033
|
+
if (lockToken(tomb) !== token) {
|
|
9034
|
+
try {
|
|
9035
|
+
fs3.renameSync(tomb, lock);
|
|
9036
|
+
} catch {}
|
|
9037
|
+
return false;
|
|
9038
|
+
}
|
|
9039
|
+
fs3.rmSync(tomb, { recursive: true, force: true });
|
|
9040
|
+
return true;
|
|
9041
|
+
},
|
|
9042
|
+
token: lockToken,
|
|
9043
|
+
reap(lock, observed) {
|
|
9044
|
+
const tomb = `${lock}.reap-${randomBytes(6).toString("hex")}`;
|
|
9045
|
+
try {
|
|
9046
|
+
fs3.renameSync(lock, tomb);
|
|
9047
|
+
} catch {
|
|
9048
|
+
return false;
|
|
9049
|
+
}
|
|
9050
|
+
if (lockToken(tomb) === observed) {
|
|
9051
|
+
fs3.rmSync(tomb, { recursive: true, force: true });
|
|
9052
|
+
return true;
|
|
9053
|
+
}
|
|
9054
|
+
try {
|
|
9055
|
+
fs3.renameSync(tomb, lock);
|
|
9056
|
+
} catch {
|
|
9057
|
+
fs3.rmSync(tomb, { recursive: true, force: true });
|
|
9058
|
+
}
|
|
9059
|
+
return false;
|
|
9060
|
+
}
|
|
9061
|
+
};
|
|
9062
|
+
function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
9063
|
+
const lock = `${file}.lock`;
|
|
9064
|
+
const deadline = Date.now() + waitMs;
|
|
9065
|
+
let token = null;
|
|
9066
|
+
for (;; ) {
|
|
9067
|
+
token = ledgerLock.acquire(lock);
|
|
9068
|
+
if (token)
|
|
9069
|
+
break;
|
|
9070
|
+
const observed = lockToken(lock);
|
|
9071
|
+
const age = lockAgeMs(lock);
|
|
9072
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
9073
|
+
continue;
|
|
9074
|
+
if (Date.now() >= deadline)
|
|
9075
|
+
return null;
|
|
9076
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
9077
|
+
}
|
|
9078
|
+
try {
|
|
9079
|
+
return fn();
|
|
9080
|
+
} finally {
|
|
9081
|
+
ledgerLock.release(lock, token);
|
|
9082
|
+
}
|
|
9083
|
+
}
|
|
9084
|
+
function appendLine(file, line, options) {
|
|
9085
|
+
const write = () => {
|
|
9086
|
+
const fd = fs3.openSync(file, "a+");
|
|
9087
|
+
try {
|
|
9088
|
+
const size = fs3.fstatSync(fd).size;
|
|
9089
|
+
let fence = "";
|
|
9090
|
+
if (size > 0) {
|
|
9091
|
+
const last = Buffer.alloc(1);
|
|
9092
|
+
fs3.readSync(fd, last, 0, 1, size - 1);
|
|
9093
|
+
if (last[0] !== 10)
|
|
9094
|
+
fence = `
|
|
9095
|
+
`;
|
|
9096
|
+
}
|
|
9097
|
+
const bytes = Buffer.from(fence + line);
|
|
9098
|
+
const written = fs3.writeSync(fd, bytes);
|
|
9099
|
+
if (written !== bytes.length)
|
|
9100
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
9101
|
+
return { ok: true, value: undefined };
|
|
9102
|
+
} finally {
|
|
9103
|
+
fs3.closeSync(fd);
|
|
9104
|
+
}
|
|
9105
|
+
};
|
|
9106
|
+
try {
|
|
9107
|
+
fs3.mkdirSync(path3.dirname(file), { recursive: true });
|
|
9108
|
+
if (!options.forceLock && !onNetworkFs(path3.dirname(file)))
|
|
9109
|
+
return write();
|
|
9110
|
+
const locked = withLedgerLock(file, write);
|
|
9111
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
9112
|
+
} catch (error) {
|
|
9113
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
9114
|
+
}
|
|
9115
|
+
}
|
|
9116
|
+
var serialize = (row, now) => {
|
|
9117
|
+
const full = { v: LEDGER_VERSION, id: newId2(), at: now.toISOString(), ...row };
|
|
9118
|
+
return { full, line: `${JSON.stringify(full)}
|
|
9119
|
+
` };
|
|
9120
|
+
};
|
|
9121
|
+
function appendRaw(cwd, row, options) {
|
|
9122
|
+
const { full, line } = serialize(row, options.now ?? new Date);
|
|
9123
|
+
const bytes = Buffer.byteLength(line);
|
|
9124
|
+
if (bytes > MAX_LINE_BYTES)
|
|
9125
|
+
return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
|
|
9126
|
+
const file = ledgerPath(cwd);
|
|
9127
|
+
if (!file.ok)
|
|
9128
|
+
return file;
|
|
9129
|
+
const written = appendLine(file.value, line, options);
|
|
9130
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
9131
|
+
}
|
|
9132
|
+
function appendHookObserved(cwd, row) {
|
|
9133
|
+
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
9134
|
+
}
|
|
8933
9135
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8934
9136
|
var str = (value) => typeof value === "string" ? value : null;
|
|
8935
9137
|
var normalizeActor = (value) => {
|
|
@@ -10524,6 +10726,7 @@ var runtimeWorkspacePolicySchema = (kind) => {
|
|
|
10524
10726
|
}).passthrough();
|
|
10525
10727
|
return entrySchema;
|
|
10526
10728
|
};
|
|
10729
|
+
var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
|
|
10527
10730
|
var runtimeWorkspaceIndexSchema = object({
|
|
10528
10731
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
10529
10732
|
}).passthrough();
|
|
@@ -12012,9 +12215,10 @@ var drop = (lock) => {
|
|
|
12012
12215
|
};
|
|
12013
12216
|
var envActor = () => {
|
|
12014
12217
|
const value = (key) => process.env[key]?.trim() || null;
|
|
12218
|
+
const acting = hostSessionFromEnv(process.env);
|
|
12015
12219
|
return {
|
|
12016
|
-
host:
|
|
12017
|
-
session:
|
|
12220
|
+
host: acting.host ?? "runtime",
|
|
12221
|
+
session: acting.session,
|
|
12018
12222
|
agentId: value("WORKIT_AGENT_ID")
|
|
12019
12223
|
};
|
|
12020
12224
|
};
|
|
@@ -16628,6 +16832,7 @@ var validateBranchNameFor = (workspaceRoot, name) => {
|
|
|
16628
16832
|
};
|
|
16629
16833
|
}
|
|
16630
16834
|
};
|
|
16835
|
+
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
16631
16836
|
|
|
16632
16837
|
// packages/workit-core/src/core/route-intent.ts
|
|
16633
16838
|
var shellBranchTarget = (command) => {
|
|
@@ -16653,36 +16858,43 @@ var shellPolicy = (cwd, command) => {
|
|
|
16653
16858
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
16654
16859
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
16655
16860
|
import path16 from "node:path";
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
var
|
|
16659
|
-
|
|
16660
|
-
for (let
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16861
|
+
|
|
16862
|
+
// packages/workit-core/src/hooks/shell-words.ts
|
|
16863
|
+
var closingQuote = (command, start, dialect) => {
|
|
16864
|
+
const quote = command[start];
|
|
16865
|
+
for (let index = start + 1;index < command.length; index++) {
|
|
16866
|
+
const char = command[index];
|
|
16867
|
+
if (quote === "'") {
|
|
16868
|
+
if (char !== "'")
|
|
16869
|
+
continue;
|
|
16870
|
+
if (dialect === "powershell" && command[index + 1] === "'") {
|
|
16871
|
+
index++;
|
|
16872
|
+
continue;
|
|
16873
|
+
}
|
|
16874
|
+
return index;
|
|
16875
|
+
}
|
|
16876
|
+
if (dialect === "posix" && char === "\\" || dialect === "powershell" && char === "`") {
|
|
16877
|
+
index++;
|
|
16878
|
+
continue;
|
|
16667
16879
|
}
|
|
16880
|
+
if (char === '"')
|
|
16881
|
+
return index;
|
|
16668
16882
|
}
|
|
16883
|
+
return -1;
|
|
16669
16884
|
};
|
|
16670
|
-
var
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
return true;
|
|
16675
|
-
const posix = relative.split(path16.sep).join("/");
|
|
16676
|
-
const segments = posix.split("/");
|
|
16677
|
-
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
16885
|
+
var unquote = (body, quote, dialect) => {
|
|
16886
|
+
if (quote === "'")
|
|
16887
|
+
return dialect === "powershell" ? body.replaceAll("''", "'") : body;
|
|
16888
|
+
return dialect === "posix" ? body.replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
|
|
16678
16889
|
};
|
|
16679
|
-
var segmentsOf = (command) => {
|
|
16890
|
+
var segmentsOf = (command, dialect = "posix") => {
|
|
16680
16891
|
const segments = [];
|
|
16681
16892
|
let current = { words: [], redirects: [] };
|
|
16682
16893
|
let word = "";
|
|
16683
16894
|
let inWord = false;
|
|
16684
16895
|
let pending = null;
|
|
16685
16896
|
const heredocs = [];
|
|
16897
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
16686
16898
|
const flushWord = () => {
|
|
16687
16899
|
if (!inWord)
|
|
16688
16900
|
return;
|
|
@@ -16706,11 +16918,11 @@ var segmentsOf = (command) => {
|
|
|
16706
16918
|
for (let index = 0;index < command.length; index++) {
|
|
16707
16919
|
const char = command[index];
|
|
16708
16920
|
if (char === "'" || char === '"') {
|
|
16709
|
-
const end = command
|
|
16710
|
-
word += command.slice(index + 1, end < 0 ? undefined : end);
|
|
16921
|
+
const end = closingQuote(command, index, dialect);
|
|
16922
|
+
word += unquote(command.slice(index + 1, end < 0 ? undefined : end), char, dialect);
|
|
16711
16923
|
inWord = true;
|
|
16712
16924
|
index = end < 0 ? command.length : end;
|
|
16713
|
-
} else if (char ===
|
|
16925
|
+
} else if (char === escape && index + 1 < command.length) {
|
|
16714
16926
|
word += command[++index];
|
|
16715
16927
|
inWord = true;
|
|
16716
16928
|
} else if (char === `
|
|
@@ -16727,7 +16939,14 @@ var segmentsOf = (command) => {
|
|
|
16727
16939
|
}
|
|
16728
16940
|
} else if (/\s/.test(char))
|
|
16729
16941
|
flushWord();
|
|
16730
|
-
else if ("
|
|
16942
|
+
else if (char === "(" || char === ")") {
|
|
16943
|
+
if (char === "(" && inWord && word.endsWith("$")) {
|
|
16944
|
+
word = word.slice(0, -1);
|
|
16945
|
+
inWord = word !== "";
|
|
16946
|
+
}
|
|
16947
|
+
flushSegment();
|
|
16948
|
+
segments.push({ words: [], redirects: [], group: char === "(" ? "open" : "close" });
|
|
16949
|
+
} else if (";&|".includes(char)) {
|
|
16731
16950
|
if (char === "&" && command[index + 1] === ">")
|
|
16732
16951
|
continue;
|
|
16733
16952
|
flushSegment();
|
|
@@ -16745,7 +16964,7 @@ var segmentsOf = (command) => {
|
|
|
16745
16964
|
inWord = true;
|
|
16746
16965
|
}
|
|
16747
16966
|
pending = "redirect";
|
|
16748
|
-
} else if (char === "<") {
|
|
16967
|
+
} else if (char === "<" && dialect === "posix") {
|
|
16749
16968
|
flushWord();
|
|
16750
16969
|
if (command[index + 1] === "<") {
|
|
16751
16970
|
index++;
|
|
@@ -16762,6 +16981,31 @@ var segmentsOf = (command) => {
|
|
|
16762
16981
|
flushSegment();
|
|
16763
16982
|
return segments;
|
|
16764
16983
|
};
|
|
16984
|
+
|
|
16985
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
16986
|
+
var NONE2 = { kind: "none" };
|
|
16987
|
+
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
16988
|
+
var canonical2 = (file) => {
|
|
16989
|
+
let rest = "";
|
|
16990
|
+
for (let dir = path16.resolve(file);; dir = path16.dirname(dir)) {
|
|
16991
|
+
try {
|
|
16992
|
+
return path16.join(realpathSync5(dir), rest);
|
|
16993
|
+
} catch {
|
|
16994
|
+
if (path16.dirname(dir) === dir)
|
|
16995
|
+
return path16.resolve(file);
|
|
16996
|
+
rest = rest ? path16.join(path16.basename(dir), rest) : path16.basename(dir);
|
|
16997
|
+
}
|
|
16998
|
+
}
|
|
16999
|
+
};
|
|
17000
|
+
var exempt = (cwd, root, file, plans) => {
|
|
17001
|
+
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
17002
|
+
const relative = path16.relative(fold(canonical2(root)), fold(canonical2(path16.resolve(cwd, file))));
|
|
17003
|
+
if (relative.startsWith("..") || path16.isAbsolute(relative))
|
|
17004
|
+
return true;
|
|
17005
|
+
const posix = relative.split(path16.sep).join("/");
|
|
17006
|
+
const segments = posix.split("/");
|
|
17007
|
+
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
17008
|
+
};
|
|
16765
17009
|
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
16766
17010
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
16767
17011
|
var editsOf = (input) => {
|
|
@@ -16911,14 +17155,473 @@ function writeGate(cwd, paths) {
|
|
|
16911
17155
|
return NONE2;
|
|
16912
17156
|
}
|
|
16913
17157
|
}
|
|
17158
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17159
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
17160
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
17161
|
+
import fs12 from "node:fs";
|
|
17162
|
+
import os4 from "node:os";
|
|
17163
|
+
import path17 from "node:path";
|
|
17164
|
+
var NONE3 = { kind: "none" };
|
|
17165
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
17166
|
+
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
17167
|
+
var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
17168
|
+
var CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
17169
|
+
var PUSHD = new Set(["pushd", "push-location"]);
|
|
17170
|
+
var POPD = new Set(["popd", "pop-location"]);
|
|
17171
|
+
var GIT_VALUE_OPTIONS = new Set([
|
|
17172
|
+
"-c",
|
|
17173
|
+
"--git-dir",
|
|
17174
|
+
"--work-tree",
|
|
17175
|
+
"--namespace",
|
|
17176
|
+
"--exec-path",
|
|
17177
|
+
"--config-env",
|
|
17178
|
+
"--super-prefix"
|
|
17179
|
+
]);
|
|
17180
|
+
var isAbsolute = (value) => path17.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
17181
|
+
var joinDir = (base, next) => {
|
|
17182
|
+
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
17183
|
+
return;
|
|
17184
|
+
const expanded = next === "~" || next.startsWith("~/") ? path17.join(os4.homedir(), next.slice(1)) : next;
|
|
17185
|
+
return base === null || isAbsolute(expanded) ? expanded : path17.join(base, expanded);
|
|
17186
|
+
};
|
|
17187
|
+
var cdTarget = (words) => {
|
|
17188
|
+
const args = words.slice(1);
|
|
17189
|
+
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
17190
|
+
if (flag >= 0)
|
|
17191
|
+
return args[flag + 1] ?? null;
|
|
17192
|
+
return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
|
|
17193
|
+
};
|
|
17194
|
+
var stripPrefixes = (input) => {
|
|
17195
|
+
let words = input;
|
|
17196
|
+
for (;; ) {
|
|
17197
|
+
const first = words[0];
|
|
17198
|
+
if (first === undefined)
|
|
17199
|
+
return words;
|
|
17200
|
+
if (/^\w+=/.test(first))
|
|
17201
|
+
words = words.slice(1);
|
|
17202
|
+
else if (PREFIXES3.has(first)) {
|
|
17203
|
+
words = words.slice(1);
|
|
17204
|
+
while (words[0]?.startsWith("-"))
|
|
17205
|
+
words = words.slice(words[0] === "-u" ? 2 : 1);
|
|
17206
|
+
} else if (first === "timeout") {
|
|
17207
|
+
words = words.slice(1);
|
|
17208
|
+
while (words[0]?.startsWith("-"))
|
|
17209
|
+
words = words.slice(1);
|
|
17210
|
+
words = words.slice(1);
|
|
17211
|
+
} else
|
|
17212
|
+
return words;
|
|
17213
|
+
}
|
|
17214
|
+
};
|
|
17215
|
+
var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
17216
|
+
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
17217
|
+
const found = [];
|
|
17218
|
+
let dir = null;
|
|
17219
|
+
const groups = [];
|
|
17220
|
+
let stack = [];
|
|
17221
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
17222
|
+
if (segment.group === "open") {
|
|
17223
|
+
groups.push({ dir, stack: [...stack] });
|
|
17224
|
+
continue;
|
|
17225
|
+
}
|
|
17226
|
+
if (segment.group === "close") {
|
|
17227
|
+
const saved = groups.pop();
|
|
17228
|
+
if (saved)
|
|
17229
|
+
({ dir, stack } = saved);
|
|
17230
|
+
continue;
|
|
17231
|
+
}
|
|
17232
|
+
const words = stripPrefixes(segment.words);
|
|
17233
|
+
const name = commandName(words[0]);
|
|
17234
|
+
if (CD.has(name)) {
|
|
17235
|
+
const target = cdTarget(words);
|
|
17236
|
+
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
17237
|
+
continue;
|
|
17238
|
+
}
|
|
17239
|
+
if (PUSHD.has(name)) {
|
|
17240
|
+
const target = cdTarget(words);
|
|
17241
|
+
stack.push(dir);
|
|
17242
|
+
dir = target === null || target === "-" ? undefined : joinDir(dir, target);
|
|
17243
|
+
continue;
|
|
17244
|
+
}
|
|
17245
|
+
if (POPD.has(name)) {
|
|
17246
|
+
dir = stack.length ? stack.pop() : undefined;
|
|
17247
|
+
continue;
|
|
17248
|
+
}
|
|
17249
|
+
if (SHELLS.has(name) && depth < 3) {
|
|
17250
|
+
const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
|
|
17251
|
+
const script = flag > 0 ? words[flag + 1] : undefined;
|
|
17252
|
+
if (script)
|
|
17253
|
+
for (const inner of rawInvocations(script, "posix", depth + 1))
|
|
17254
|
+
found.push({
|
|
17255
|
+
...inner,
|
|
17256
|
+
dir: inner.dir === null ? dir : inner.dir === undefined ? undefined : joinDir(dir, inner.dir)
|
|
17257
|
+
});
|
|
17258
|
+
continue;
|
|
17259
|
+
}
|
|
17260
|
+
if (name === "gh" || name === "glab") {
|
|
17261
|
+
found.push({ tool: name, dir, args: words.slice(1) });
|
|
17262
|
+
continue;
|
|
17263
|
+
}
|
|
17264
|
+
if (name !== "git")
|
|
17265
|
+
continue;
|
|
17266
|
+
let here = dir;
|
|
17267
|
+
let index = 1;
|
|
17268
|
+
for (;index < words.length; index++) {
|
|
17269
|
+
const word = words[index];
|
|
17270
|
+
if (word === "-C")
|
|
17271
|
+
here = words[index + 1] ? joinDir(here, words[++index]) : here;
|
|
17272
|
+
else if (GIT_VALUE_OPTIONS.has(word))
|
|
17273
|
+
index++;
|
|
17274
|
+
else if (!word.startsWith("-"))
|
|
17275
|
+
break;
|
|
17276
|
+
}
|
|
17277
|
+
found.push({ tool: "git", dir: here, args: words.slice(index) });
|
|
17278
|
+
}
|
|
17279
|
+
return found;
|
|
17280
|
+
}
|
|
17281
|
+
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17282
|
+
var pushAction = (args) => {
|
|
17283
|
+
let force = false;
|
|
17284
|
+
let lease = false;
|
|
17285
|
+
let wholesale = false;
|
|
17286
|
+
let remote = null;
|
|
17287
|
+
const positionals = [];
|
|
17288
|
+
for (let index = 1;index < args.length; index++) {
|
|
17289
|
+
const arg = args[index];
|
|
17290
|
+
if (arg === "--") {
|
|
17291
|
+
positionals.push(...args.slice(index + 1));
|
|
17292
|
+
break;
|
|
17293
|
+
}
|
|
17294
|
+
if (arg === "--dry-run" || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
|
|
17295
|
+
return null;
|
|
17296
|
+
if (arg === "--repo")
|
|
17297
|
+
remote = args[++index] ?? null;
|
|
17298
|
+
else if (arg.startsWith("--repo="))
|
|
17299
|
+
remote = arg.slice("--repo=".length);
|
|
17300
|
+
else if (PUSH_VALUE_OPTIONS.has(arg))
|
|
17301
|
+
index++;
|
|
17302
|
+
else if (arg === "--force" || /^-[a-zA-Z]*f[a-zA-Z]*$/.test(arg))
|
|
17303
|
+
force = true;
|
|
17304
|
+
else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
|
|
17305
|
+
lease = true;
|
|
17306
|
+
else if (["--all", "--mirror", "--branches", "--tags"].includes(arg))
|
|
17307
|
+
wholesale = true;
|
|
17308
|
+
else if (!arg.startsWith("-"))
|
|
17309
|
+
positionals.push(arg);
|
|
17310
|
+
}
|
|
17311
|
+
const specs = remote === null ? positionals.slice(1) : positionals;
|
|
17312
|
+
remote ??= positionals[0] ?? null;
|
|
17313
|
+
const targets = [];
|
|
17314
|
+
for (const raw of specs) {
|
|
17315
|
+
if (raw.startsWith("+"))
|
|
17316
|
+
force = true;
|
|
17317
|
+
const spec = raw.replace(/^\+/, "");
|
|
17318
|
+
const colon = spec.indexOf(":");
|
|
17319
|
+
const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
|
|
17320
|
+
if (!destination)
|
|
17321
|
+
continue;
|
|
17322
|
+
if (destination === "HEAD")
|
|
17323
|
+
targets.push(null);
|
|
17324
|
+
else if (destination.startsWith("refs/heads/"))
|
|
17325
|
+
targets.push(destination.slice(11));
|
|
17326
|
+
else if (!destination.startsWith("refs/"))
|
|
17327
|
+
targets.push(destination);
|
|
17328
|
+
}
|
|
17329
|
+
if (specs.length === 0 && !wholesale)
|
|
17330
|
+
targets.push(null);
|
|
17331
|
+
return { kind: "push", targets, blindForce: force && !lease, remote };
|
|
17332
|
+
};
|
|
17333
|
+
var VALUE_OPTIONS = {
|
|
17334
|
+
git: new Set([
|
|
17335
|
+
"-m",
|
|
17336
|
+
"--message",
|
|
17337
|
+
"-F",
|
|
17338
|
+
"--file",
|
|
17339
|
+
"-C",
|
|
17340
|
+
"-c",
|
|
17341
|
+
"--reuse-message",
|
|
17342
|
+
"--reedit-message",
|
|
17343
|
+
"--author",
|
|
17344
|
+
"--date",
|
|
17345
|
+
"--trailer",
|
|
17346
|
+
"-t",
|
|
17347
|
+
"--template",
|
|
17348
|
+
"--cleanup",
|
|
17349
|
+
"--fixup",
|
|
17350
|
+
"--squash",
|
|
17351
|
+
"-o",
|
|
17352
|
+
"--push-option",
|
|
17353
|
+
"--repo",
|
|
17354
|
+
"--receive-pack",
|
|
17355
|
+
"--exec"
|
|
17356
|
+
]),
|
|
17357
|
+
gh: new Set(["-b", "--body", "-F", "--body-file", "-t", "--title", "--subject", "-A", "--author-email"].concat(["-B", "--base", "-H", "--head", "-l", "--label", "-a", "--assignee"]).concat(["-r", "--reviewer", "-m", "--milestone", "-p", "--project", "-T", "--template"]).concat(["--match-head-commit", "-R", "--repo"])),
|
|
17358
|
+
glab: new Set(["-m", "--message", "--squash-message", "--sha", "-t", "--title", "-d", "--description"].concat(["-b", "--target-branch", "-s", "--source-branch", "-l", "--label"]).concat(["-a", "--assignee", "--reviewer", "-R", "--repo"]))
|
|
17359
|
+
};
|
|
17360
|
+
var asksHelp = (invocation) => {
|
|
17361
|
+
const { tool, args } = invocation;
|
|
17362
|
+
if (args[0] === "help" || tool !== "git" && args[1] === "help")
|
|
17363
|
+
return true;
|
|
17364
|
+
const values = VALUE_OPTIONS[tool];
|
|
17365
|
+
for (let index = 0;index < args.length; index++) {
|
|
17366
|
+
const arg = args[index];
|
|
17367
|
+
if (arg === "--")
|
|
17368
|
+
return false;
|
|
17369
|
+
if (arg === "--help" || arg === "-h")
|
|
17370
|
+
return true;
|
|
17371
|
+
if (values.has(arg))
|
|
17372
|
+
index++;
|
|
17373
|
+
}
|
|
17374
|
+
return false;
|
|
17375
|
+
};
|
|
17376
|
+
function rawAction(invocation) {
|
|
17377
|
+
const args = invocation.args;
|
|
17378
|
+
if (asksHelp(invocation))
|
|
17379
|
+
return null;
|
|
17380
|
+
const [first, second] = args;
|
|
17381
|
+
if (invocation.tool === "git") {
|
|
17382
|
+
if (first === "commit")
|
|
17383
|
+
return args.includes("--dry-run") ? null : { kind: "commit", amend: args.includes("--amend") };
|
|
17384
|
+
if (first === "push")
|
|
17385
|
+
return pushAction(args);
|
|
17386
|
+
return null;
|
|
17387
|
+
}
|
|
17388
|
+
const forge = invocation.tool;
|
|
17389
|
+
const noun = forge === "gh" ? "pr" : "mr";
|
|
17390
|
+
if (first !== noun)
|
|
17391
|
+
return null;
|
|
17392
|
+
if (second === "merge")
|
|
17393
|
+
return { kind: "merge", forge };
|
|
17394
|
+
if (second === "create")
|
|
17395
|
+
return { kind: "pr-create", forge };
|
|
17396
|
+
if (["view", "checks", "status", "ci", "list"].includes(second ?? ""))
|
|
17397
|
+
return { kind: "pr-read", forge };
|
|
17398
|
+
return null;
|
|
17399
|
+
}
|
|
17400
|
+
var repoAt = (dir) => {
|
|
17401
|
+
const location = resolveStore(dir);
|
|
17402
|
+
if (location instanceof Error || !location.shared)
|
|
17403
|
+
return null;
|
|
17404
|
+
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
17405
|
+
};
|
|
17406
|
+
var isWorkitWorkspace = (repo) => {
|
|
17407
|
+
if (fs12.existsSync(repo.location.dir))
|
|
17408
|
+
return true;
|
|
17409
|
+
try {
|
|
17410
|
+
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
17411
|
+
} catch {
|
|
17412
|
+
return false;
|
|
17413
|
+
}
|
|
17414
|
+
};
|
|
17415
|
+
var guardedBranch = (dir, branch) => {
|
|
17416
|
+
try {
|
|
17417
|
+
const policy = resolveBranchPolicyFor(dir);
|
|
17418
|
+
return policy.protected.has(branch.toLowerCase()) || policy.defaultTargetBranch.toLowerCase() === branch.toLowerCase();
|
|
17419
|
+
} catch {
|
|
17420
|
+
return false;
|
|
17421
|
+
}
|
|
17422
|
+
};
|
|
17423
|
+
var toPushRemote = (repo, remote) => {
|
|
17424
|
+
if (remote === null)
|
|
17425
|
+
return true;
|
|
17426
|
+
try {
|
|
17427
|
+
const pushRemote = pushRemoteName(repo.dir, repo.branch);
|
|
17428
|
+
return pushRemote === null || pushRemote === remote;
|
|
17429
|
+
} catch {
|
|
17430
|
+
return false;
|
|
17431
|
+
}
|
|
17432
|
+
};
|
|
17433
|
+
var headSha2 = (repo) => {
|
|
17434
|
+
const git = repo.location.git;
|
|
17435
|
+
if (!git || git.reftable)
|
|
17436
|
+
return null;
|
|
17437
|
+
try {
|
|
17438
|
+
const head = fs12.readFileSync(path17.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
17439
|
+
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
17440
|
+
return head;
|
|
17441
|
+
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
17442
|
+
if (!ref)
|
|
17443
|
+
return null;
|
|
17444
|
+
const common = path17.dirname(repo.location.dir);
|
|
17445
|
+
try {
|
|
17446
|
+
return fs12.readFileSync(path17.join(common, ref), "utf8").trim() || null;
|
|
17447
|
+
} catch {}
|
|
17448
|
+
const packed = fs12.readFileSync(path17.join(common, "packed-refs"), "utf8");
|
|
17449
|
+
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
17450
|
+
} catch {
|
|
17451
|
+
return null;
|
|
17452
|
+
}
|
|
17453
|
+
};
|
|
17454
|
+
var quote = (text) => `\`${text}\``;
|
|
17455
|
+
var refusal = (action, branch) => {
|
|
17456
|
+
if (action.kind === "merge")
|
|
17457
|
+
return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
|
|
17458
|
+
return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
|
|
17459
|
+
};
|
|
17460
|
+
var nudgeFor = (action, host, session) => {
|
|
17461
|
+
switch (action.kind) {
|
|
17462
|
+
case "commit": {
|
|
17463
|
+
const prefix = host === "cursor" && session ? `WORKIT_SESSION_ID=${session} ` : "";
|
|
17464
|
+
return action.amend ? `workit: raw ${quote("git commit --amend")} is recorded for this session; for new commits prefer ${quote(`${prefix}workit git commit -m '<msg>'`)} (adds the Workit-Session trailer and checks branch and commit policy).` : `workit: prefer ${quote(`${prefix}workit git commit -m '<msg>' [--all | -- <paths>]`)}: it adds the Workit-Session trailer and checks branch and commit policy.`;
|
|
17465
|
+
}
|
|
17466
|
+
case "push":
|
|
17467
|
+
return action.blindForce ? `workit: ${quote("git push --force")} overwrites without a lease; use ${quote("workit git push --force-with-lease")}.` : `workit: prefer ${quote("workit git push")}: it refuses protected branches and verifies the remote tip.`;
|
|
17468
|
+
case "pr-create":
|
|
17469
|
+
return `workit: prefer ${quote("workit pr create --fill")} (or --title/--body): it records the PR the merge gate checks.`;
|
|
17470
|
+
case "pr-read":
|
|
17471
|
+
return `workit: ${quote("workit pr status")} shows the PR, its checks and the verdict state; ${quote("workit ci wait")} waits for checks.`;
|
|
17472
|
+
case "merge":
|
|
17473
|
+
return null;
|
|
17474
|
+
}
|
|
17475
|
+
};
|
|
17476
|
+
var actionsIn = (cwd, command, dialect) => {
|
|
17477
|
+
const out = [];
|
|
17478
|
+
const repos = new Map;
|
|
17479
|
+
for (const invocation of rawInvocations(command, dialect)) {
|
|
17480
|
+
if (invocation.dir === undefined)
|
|
17481
|
+
continue;
|
|
17482
|
+
const action = rawAction(invocation);
|
|
17483
|
+
if (!action)
|
|
17484
|
+
continue;
|
|
17485
|
+
const dir = path17.resolve(cwd, invocation.dir ?? ".");
|
|
17486
|
+
if (!repos.has(dir)) {
|
|
17487
|
+
const repo = repoAt(dir);
|
|
17488
|
+
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
17489
|
+
}
|
|
17490
|
+
const repo = repos.get(dir);
|
|
17491
|
+
if (repo)
|
|
17492
|
+
out.push({ action, repo });
|
|
17493
|
+
}
|
|
17494
|
+
return out;
|
|
17495
|
+
};
|
|
17496
|
+
var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
|
|
17497
|
+
var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
|
|
17498
|
+
function rawGitPre(input, command) {
|
|
17499
|
+
try {
|
|
17500
|
+
if (!mentionsRawTool(command))
|
|
17501
|
+
return NONE3;
|
|
17502
|
+
const nudges = [];
|
|
17503
|
+
for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input))) {
|
|
17504
|
+
if (action.kind === "merge")
|
|
17505
|
+
return deny(refusal(action, null));
|
|
17506
|
+
if (action.kind === "push")
|
|
17507
|
+
for (const target of action.targets) {
|
|
17508
|
+
const branch = target ?? repo.branch;
|
|
17509
|
+
if (branch && guardedBranch(repo.dir, branch) && toPushRemote(repo, action.remote))
|
|
17510
|
+
return deny(refusal(action, branch));
|
|
17511
|
+
}
|
|
17512
|
+
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
17513
|
+
if (nudge && !nudges.includes(nudge))
|
|
17514
|
+
nudges.push(nudge);
|
|
17515
|
+
}
|
|
17516
|
+
return nudges.length ? { kind: "context", text: nudges.join(`
|
|
17517
|
+
`) } : NONE3;
|
|
17518
|
+
} catch {
|
|
17519
|
+
return NONE3;
|
|
17520
|
+
}
|
|
17521
|
+
}
|
|
17522
|
+
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
17523
|
+
var NEXT_COMMAND = "next";
|
|
17524
|
+
var MARKER_TTL_SECONDS = 3600;
|
|
17525
|
+
var markerFile = (repo, session, key) => path17.join(repo.location.dir, "hooks", `commit-${createHash10("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
17526
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash10("sha256").update(command).digest("hex")}`;
|
|
17527
|
+
function noteRawCommit(input, command, key) {
|
|
17528
|
+
try {
|
|
17529
|
+
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
17530
|
+
return;
|
|
17531
|
+
const at = Math.floor(Date.now() / 1000);
|
|
17532
|
+
for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input))) {
|
|
17533
|
+
if (action.kind !== "commit")
|
|
17534
|
+
continue;
|
|
17535
|
+
const file = markerFile(repo, input.session.id, key);
|
|
17536
|
+
fs12.mkdirSync(path17.dirname(file), { recursive: true });
|
|
17537
|
+
fs12.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
17538
|
+
`);
|
|
17539
|
+
}
|
|
17540
|
+
} catch {}
|
|
17541
|
+
}
|
|
17542
|
+
var headCommit = (dir) => {
|
|
17543
|
+
const run = spawnSync9("git", [
|
|
17544
|
+
"log",
|
|
17545
|
+
"-1",
|
|
17546
|
+
"--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
|
|
17547
|
+
], {
|
|
17548
|
+
cwd: dir,
|
|
17549
|
+
encoding: "utf8",
|
|
17550
|
+
timeout: 5000,
|
|
17551
|
+
windowsHide: true,
|
|
17552
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
|
|
17553
|
+
});
|
|
17554
|
+
const [sha, committed, subject = "", trailer = ""] = (run.status === 0 ? run.stdout.trim() : "").split("\x1F");
|
|
17555
|
+
return sha && /^[0-9a-f]{40,64}$/.test(sha) ? { sha, committed: Number(committed), subject, trailer: trailer.trim() || null } : null;
|
|
17556
|
+
};
|
|
17557
|
+
var settle2 = (input, repo, file) => {
|
|
17558
|
+
let marker;
|
|
17559
|
+
try {
|
|
17560
|
+
marker = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
17561
|
+
} catch {
|
|
17562
|
+
return;
|
|
17563
|
+
}
|
|
17564
|
+
fs12.rmSync(file, { force: true });
|
|
17565
|
+
const session = input.session.id;
|
|
17566
|
+
const now = Date.now() / 1000;
|
|
17567
|
+
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
17568
|
+
return;
|
|
17569
|
+
if (!session || !repo.branch)
|
|
17570
|
+
return;
|
|
17571
|
+
const head = headCommit(repo.dir);
|
|
17572
|
+
if (!head || head.sha === marker.head || head.committed < marker.at)
|
|
17573
|
+
return;
|
|
17574
|
+
if (head.trailer && head.trailer !== session)
|
|
17575
|
+
return;
|
|
17576
|
+
const ledger = readLedger(repo.dir);
|
|
17577
|
+
if (!ledger.ok)
|
|
17578
|
+
return;
|
|
17579
|
+
const claimed = ledger.value.rows.some((row) => row.type === "commit.recorded" && (row.sha === head.sha || row.head === head.sha));
|
|
17580
|
+
if (claimed)
|
|
17581
|
+
return;
|
|
17582
|
+
appendHookObserved(repo.dir, {
|
|
17583
|
+
type: "commit.recorded",
|
|
17584
|
+
actor: { host: input.host, session, agentId: input.session.agentId },
|
|
17585
|
+
branch: repo.branch,
|
|
17586
|
+
head: head.sha,
|
|
17587
|
+
sha: head.sha,
|
|
17588
|
+
session,
|
|
17589
|
+
agentId: input.session.agentId,
|
|
17590
|
+
subject: head.subject.slice(0, 200),
|
|
17591
|
+
files: [],
|
|
17592
|
+
fileCount: null,
|
|
17593
|
+
via: "raw_shell"
|
|
17594
|
+
});
|
|
17595
|
+
};
|
|
17596
|
+
function rawGitPost(input, command, key) {
|
|
17597
|
+
try {
|
|
17598
|
+
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
17599
|
+
return;
|
|
17600
|
+
for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input)))
|
|
17601
|
+
if (action.kind === "commit")
|
|
17602
|
+
settle2(input, repo, markerFile(repo, input.session.id, key));
|
|
17603
|
+
} catch {}
|
|
17604
|
+
}
|
|
17605
|
+
function settlePendingCommit(input) {
|
|
17606
|
+
try {
|
|
17607
|
+
if (!input.session.id)
|
|
17608
|
+
return;
|
|
17609
|
+
const repo = repoAt(input.cwd);
|
|
17610
|
+
if (!repo)
|
|
17611
|
+
return;
|
|
17612
|
+
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
17613
|
+
if (fs12.existsSync(file))
|
|
17614
|
+
settle2(input, repo, file);
|
|
17615
|
+
} catch {}
|
|
17616
|
+
}
|
|
16914
17617
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
16915
17618
|
import { existsSync as existsSync6, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
16916
|
-
import
|
|
17619
|
+
import path18 from "node:path";
|
|
16917
17620
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16918
17621
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
16919
17622
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
16920
17623
|
var existingDirectory = (value) => {
|
|
16921
|
-
if (!nonEmpty2(value) || !
|
|
17624
|
+
if (!nonEmpty2(value) || !path18.isAbsolute(value) || !existsSync6(value))
|
|
16922
17625
|
return null;
|
|
16923
17626
|
try {
|
|
16924
17627
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -16926,8 +17629,8 @@ var existingDirectory = (value) => {
|
|
|
16926
17629
|
return null;
|
|
16927
17630
|
}
|
|
16928
17631
|
};
|
|
16929
|
-
var
|
|
16930
|
-
var
|
|
17632
|
+
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
17633
|
+
var SCRIPT_FLAG2 = /^(?:-\w*c|-command|\/c)$/i;
|
|
16931
17634
|
var quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
|
|
16932
17635
|
var commandText = (value) => {
|
|
16933
17636
|
if (nonEmpty2(value))
|
|
@@ -16938,9 +17641,9 @@ var commandText = (value) => {
|
|
|
16938
17641
|
if (!argv.every((arg) => typeof arg === "string"))
|
|
16939
17642
|
return null;
|
|
16940
17643
|
const shell = argv[0].split(/[\\/]/).at(-1)?.replace(/\.exe$/i, "").toLowerCase() ?? "";
|
|
16941
|
-
if (
|
|
17644
|
+
if (SHELLS2.has(shell))
|
|
16942
17645
|
for (let index = 1;index < argv.length - 1; index++) {
|
|
16943
|
-
if (
|
|
17646
|
+
if (SCRIPT_FLAG2.test(argv[index]))
|
|
16944
17647
|
return nonEmpty2(argv[index + 1]) ? argv[index + 1] : null;
|
|
16945
17648
|
if (!/^[-/]/.test(argv[index]))
|
|
16946
17649
|
break;
|
|
@@ -16981,7 +17684,7 @@ var writePaths = (input) => {
|
|
|
16981
17684
|
};
|
|
16982
17685
|
// packages/workit-core/src/hooks/handle.ts
|
|
16983
17686
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
16984
|
-
var
|
|
17687
|
+
var NONE4 = { kind: "none" };
|
|
16985
17688
|
var offered = new Set;
|
|
16986
17689
|
var CLAUDE_WORKTREE_IMPLEMENTER = "workit:implementer";
|
|
16987
17690
|
var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
|
|
@@ -17000,7 +17703,7 @@ function handleHook(input, deps) {
|
|
|
17000
17703
|
switch (event.kind) {
|
|
17001
17704
|
case "session.start": {
|
|
17002
17705
|
if (!usable2(descriptor.context.sessionStart))
|
|
17003
|
-
return
|
|
17706
|
+
return NONE4;
|
|
17004
17707
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
17005
17708
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
17006
17709
|
if (offer && key !== null)
|
|
@@ -17015,19 +17718,34 @@ function handleHook(input, deps) {
|
|
|
17015
17718
|
}
|
|
17016
17719
|
case "context.turn": {
|
|
17017
17720
|
if (!usable2(descriptor.context.perTurn))
|
|
17018
|
-
return
|
|
17721
|
+
return NONE4;
|
|
17019
17722
|
const text = turnContextText(input, descriptor);
|
|
17020
|
-
return text ? { kind: "context", text } :
|
|
17723
|
+
return text ? { kind: "context", text } : NONE4;
|
|
17021
17724
|
}
|
|
17022
17725
|
case "shell.pre": {
|
|
17023
|
-
const
|
|
17024
|
-
|
|
17726
|
+
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
17727
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE4;
|
|
17728
|
+
if (policy.kind !== "none")
|
|
17025
17729
|
return policy;
|
|
17026
|
-
const
|
|
17027
|
-
|
|
17730
|
+
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
17731
|
+
if (!postTool)
|
|
17732
|
+
settlePendingCommit(input);
|
|
17733
|
+
const raw = canDeny ? rawGitPre(input, event.command) : NONE4;
|
|
17734
|
+
if (raw.kind === "deny")
|
|
17735
|
+
return raw;
|
|
17736
|
+
const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
|
|
17737
|
+
const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE4;
|
|
17738
|
+
if (gate.kind === "deny")
|
|
17739
|
+
return gate;
|
|
17740
|
+
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
17741
|
+
return raw;
|
|
17028
17742
|
}
|
|
17743
|
+
case "shell.post":
|
|
17744
|
+
if (usable2(descriptor.events["shell.post"].support))
|
|
17745
|
+
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
17746
|
+
return NONE4;
|
|
17029
17747
|
case "write.pre":
|
|
17030
|
-
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) :
|
|
17748
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE4;
|
|
17031
17749
|
case "subagent.start":
|
|
17032
17750
|
return {
|
|
17033
17751
|
kind: "context",
|
|
@@ -17039,19 +17757,18 @@ function handleHook(input, deps) {
|
|
|
17039
17757
|
userMessage: "Workit context may be stale after compaction; re-run inspection or resume before acting."
|
|
17040
17758
|
};
|
|
17041
17759
|
case "tool.pre":
|
|
17042
|
-
case "shell.post":
|
|
17043
17760
|
case "subagent.stop":
|
|
17044
17761
|
case "prompt.submit":
|
|
17045
17762
|
case "stop":
|
|
17046
|
-
return
|
|
17763
|
+
return NONE4;
|
|
17047
17764
|
}
|
|
17048
17765
|
}
|
|
17049
17766
|
var failureDecision = (descriptor, event, error) => {
|
|
17050
17767
|
if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
|
|
17051
|
-
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } :
|
|
17768
|
+
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE4;
|
|
17052
17769
|
if (event === "session.start" || event === "subagent.start")
|
|
17053
17770
|
return { kind: "context", text: `[workit diagnostic: ${error}]` };
|
|
17054
|
-
return
|
|
17771
|
+
return NONE4;
|
|
17055
17772
|
};
|
|
17056
17773
|
var dispatchHook = (adapter, raw, env = process.env) => {
|
|
17057
17774
|
const parsed = adapter.parse(raw, env);
|
|
@@ -17174,6 +17891,7 @@ var EVENTS = {
|
|
|
17174
17891
|
};
|
|
17175
17892
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
17176
17893
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
17894
|
+
var dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix";
|
|
17177
17895
|
var toolCommand = (value) => isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
17178
17896
|
var eventOf = (name, value) => {
|
|
17179
17897
|
const toolUseId = optionalText(value.tool_use_id);
|
|
@@ -17198,7 +17916,7 @@ var eventOf = (name, value) => {
|
|
|
17198
17916
|
if (!SHELL_TOOLS.has(value.tool_name))
|
|
17199
17917
|
return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
|
|
17200
17918
|
const command = toolCommand(value);
|
|
17201
|
-
return command ? { ok: true, event: { kind: "shell.pre", command, toolUseId } } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
|
|
17919
|
+
return command ? { ok: true, event: { kind: "shell.pre", command, toolUseId, dialect: dialectOf2(value) } } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
|
|
17202
17920
|
}
|
|
17203
17921
|
case "PostToolUse": {
|
|
17204
17922
|
const command = toolCommand(value);
|
|
@@ -17212,7 +17930,8 @@ var eventOf = (name, value) => {
|
|
|
17212
17930
|
command,
|
|
17213
17931
|
stdout: optionalText(response.stdout) ?? "",
|
|
17214
17932
|
exitCode: typeof response.exit_code === "number" ? response.exit_code : null,
|
|
17215
|
-
toolUseId
|
|
17933
|
+
toolUseId,
|
|
17934
|
+
dialect: dialectOf2(value)
|
|
17216
17935
|
}
|
|
17217
17936
|
};
|
|
17218
17937
|
}
|
|
@@ -17253,7 +17972,13 @@ var eventOf = (name, value) => {
|
|
|
17253
17972
|
};
|
|
17254
17973
|
}
|
|
17255
17974
|
};
|
|
17256
|
-
var CONTEXT_EVENTS = new Set([
|
|
17975
|
+
var CONTEXT_EVENTS = new Set([
|
|
17976
|
+
"SessionStart",
|
|
17977
|
+
"UserPromptSubmit",
|
|
17978
|
+
"SubagentStart",
|
|
17979
|
+
"PreToolUse",
|
|
17980
|
+
"PostToolUse"
|
|
17981
|
+
]);
|
|
17257
17982
|
var render = (decision, native) => {
|
|
17258
17983
|
if (decision.kind === "deny" && native === "PreToolUse")
|
|
17259
17984
|
return {
|
|
@@ -17335,7 +18060,7 @@ var CODEX_DESCRIPTOR = {
|
|
|
17335
18060
|
"shell.pre": { support: "native", native: "PreToolUse" },
|
|
17336
18061
|
"tool.pre": { support: "native", native: "PreToolUse" },
|
|
17337
18062
|
"write.pre": { support: "undocumented", native: null },
|
|
17338
|
-
"shell.post":
|
|
18063
|
+
"shell.post": { support: "native", native: "PostToolUse" },
|
|
17339
18064
|
"subagent.start": { support: "native", native: "SubagentStart" },
|
|
17340
18065
|
"subagent.stop": { support: "native", native: "SubagentStop" },
|
|
17341
18066
|
"prompt.submit": undocumented,
|
|
@@ -17356,7 +18081,7 @@ var CODEX_DESCRIPTOR = {
|
|
|
17356
18081
|
worktreeIsolation: "undocumented",
|
|
17357
18082
|
maxConcurrency: "undocumented"
|
|
17358
18083
|
},
|
|
17359
|
-
provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "
|
|
18084
|
+
provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
|
|
17360
18085
|
interaction: { questions: "none", writeBoundary: "partial" },
|
|
17361
18086
|
stopControl: "undocumented",
|
|
17362
18087
|
shellAvailable: "native",
|
|
@@ -17422,6 +18147,7 @@ var CODEX_DESCRIPTOR = {
|
|
|
17422
18147
|
var EVENTS2 = {
|
|
17423
18148
|
SessionStart: "session.start",
|
|
17424
18149
|
PreToolUse: "shell.pre",
|
|
18150
|
+
PostToolUse: "shell.post",
|
|
17425
18151
|
SubagentStart: "subagent.start",
|
|
17426
18152
|
SubagentStop: "subagent.stop"
|
|
17427
18153
|
};
|
|
@@ -17435,10 +18161,11 @@ var parseCodexHookInput = (value) => {
|
|
|
17435
18161
|
const cwd = existingDirectory(value.cwd);
|
|
17436
18162
|
if (!cwd)
|
|
17437
18163
|
return { ok: false, error: "cwd must be an existing absolute directory" };
|
|
17438
|
-
|
|
18164
|
+
const toolEvent = event === "PreToolUse" || event === "PostToolUse";
|
|
18165
|
+
if (toolEvent && !nonEmpty2(value.tool_name))
|
|
17439
18166
|
return { ok: false, error: "tool_name is required" };
|
|
17440
18167
|
const command = isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
17441
|
-
if (
|
|
18168
|
+
if (toolEvent && isShellTool(value.tool_name) && !command)
|
|
17442
18169
|
return { ok: false, error: "tool_input.command is required for shell tools" };
|
|
17443
18170
|
if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
|
|
17444
18171
|
return { ok: false, error: "agent_id is required" };
|
|
@@ -17454,7 +18181,8 @@ var parseCodexHookInput = (value) => {
|
|
|
17454
18181
|
...event === "SessionStart" ? { source: SOURCES2.has(String(value.source)) ? value.source : "resume" } : {},
|
|
17455
18182
|
...nonEmpty2(value.turn_id) ? { turn_id: value.turn_id } : {},
|
|
17456
18183
|
...nonEmpty2(value.tool_name) ? { tool_name: value.tool_name } : {},
|
|
17457
|
-
...
|
|
18184
|
+
...toolEvent ? { tool_input: value.tool_input } : {},
|
|
18185
|
+
...event === "PostToolUse" && typeof value.tool_response === "string" ? { tool_response: value.tool_response } : {},
|
|
17458
18186
|
...command ? { command } : {},
|
|
17459
18187
|
...nonEmpty2(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {},
|
|
17460
18188
|
...nonEmpty2(value.agent_id) ? { agent_id: value.agent_id } : {},
|
|
@@ -17479,6 +18207,16 @@ var protocolEvent = (input) => {
|
|
|
17479
18207
|
toolUseId
|
|
17480
18208
|
} : { kind: "tool.pre", tool: input.tool_name, toolUseId };
|
|
17481
18209
|
}
|
|
18210
|
+
case "PostToolUse": {
|
|
18211
|
+
const toolUseId = input.tool_use_id ?? null;
|
|
18212
|
+
return isShellTool(input.tool_name) ? {
|
|
18213
|
+
kind: "shell.post",
|
|
18214
|
+
command: input.command ?? "",
|
|
18215
|
+
stdout: input.tool_response ?? "",
|
|
18216
|
+
exitCode: null,
|
|
18217
|
+
toolUseId
|
|
18218
|
+
} : { kind: "tool.pre", tool: input.tool_name, toolUseId };
|
|
18219
|
+
}
|
|
17482
18220
|
case "SubagentStart":
|
|
17483
18221
|
return {
|
|
17484
18222
|
kind: "subagent.start",
|
|
@@ -17543,11 +18281,11 @@ var codexAdapter = {
|
|
|
17543
18281
|
};
|
|
17544
18282
|
},
|
|
17545
18283
|
render: render2,
|
|
17546
|
-
addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. A verifier or reviewer records workit ledger verdict under a session the lead assigns (WORKIT_SESSION_ID=<lead>-v<n>), never the author's. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
|
|
18284
|
+
addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under a session the lead assigns (WORKIT_HOST=codex_cli WORKIT_SESSION_ID=<lead>-v<n>), never the author's. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
|
|
17547
18285
|
};
|
|
17548
18286
|
|
|
17549
18287
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
17550
|
-
import
|
|
18288
|
+
import path19 from "node:path";
|
|
17551
18289
|
var undocumented2 = { support: "undocumented", native: null };
|
|
17552
18290
|
var none = { support: "none", native: null };
|
|
17553
18291
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -17728,7 +18466,7 @@ var protocolEvent2 = (input) => {
|
|
|
17728
18466
|
return { kind: "compact.pre", trigger: "auto" };
|
|
17729
18467
|
}
|
|
17730
18468
|
};
|
|
17731
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
18469
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path19.sep) ? root : `${root}${path19.sep}`);
|
|
17732
18470
|
var hookCwd = (input) => {
|
|
17733
18471
|
const cwd = existingDirectory(input.cwd);
|
|
17734
18472
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
@@ -17751,6 +18489,8 @@ var render3 = (decision, native) => {
|
|
|
17751
18489
|
json: cursorDeny(decision.reason),
|
|
17752
18490
|
exitCode: native !== null && BLOCKING.has(native) ? 2 : 0
|
|
17753
18491
|
};
|
|
18492
|
+
if (decision.kind === "context" && native === "beforeShellExecution")
|
|
18493
|
+
return { json: { permission: "allow", agent_message: decision.text }, exitCode: 0 };
|
|
17754
18494
|
if (decision.kind === "context")
|
|
17755
18495
|
return { json: { additional_context: decision.text }, exitCode: 0 };
|
|
17756
18496
|
if (decision.kind === "notice")
|
|
@@ -17787,7 +18527,8 @@ var cursorAdapter = {
|
|
|
17787
18527
|
}
|
|
17788
18528
|
};
|
|
17789
18529
|
},
|
|
17790
|
-
render: render3
|
|
18530
|
+
render: render3,
|
|
18531
|
+
addendum: (input) => input.session.id ? `<workit-session>Cursor puts no session id in the shell: prefix workit commands that record authorship with WORKIT_SESSION_ID=${input.session.id} (e.g. \`WORKIT_SESSION_ID=${input.session.id} workit git commit -m …\`). A verifier uses its own id, never this one.</workit-session>` : null
|
|
17791
18532
|
};
|
|
17792
18533
|
|
|
17793
18534
|
// packages/workit-core/src/hooks/run.ts
|