@brainervirus/workit-cursor 8.7.0 → 8.9.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 +377 -114
- package/dist/mcp-server.js +17 -17
- package/dist/workit-hook.js +386 -113
- package/hooks/hooks-cursor.json +7 -0
- package/package.json +3 -3
package/dist/workit-hook.js
CHANGED
|
@@ -17906,12 +17906,48 @@ function writeGate(cwd, paths) {
|
|
|
17906
17906
|
return NONE2;
|
|
17907
17907
|
}
|
|
17908
17908
|
}
|
|
17909
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
17910
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
17911
|
+
|
|
17909
17912
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
17910
17913
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17914
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
17915
|
+
import fs15 from "node:fs";
|
|
17916
|
+
import os4 from "node:os";
|
|
17917
|
+
import path19 from "node:path";
|
|
17918
|
+
|
|
17919
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17911
17920
|
import { createHash as createHash12 } from "node:crypto";
|
|
17912
17921
|
import fs14 from "node:fs";
|
|
17913
|
-
import os4 from "node:os";
|
|
17914
17922
|
import path18 from "node:path";
|
|
17923
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
17924
|
+
let dir = store;
|
|
17925
|
+
if (dir === undefined) {
|
|
17926
|
+
const location = resolveStore(input.cwd);
|
|
17927
|
+
if (location instanceof Error || !location.shared)
|
|
17928
|
+
return null;
|
|
17929
|
+
dir = location.dir;
|
|
17930
|
+
}
|
|
17931
|
+
const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
17932
|
+
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
17933
|
+
};
|
|
17934
|
+
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
17935
|
+
var readSessionMarker = (file) => {
|
|
17936
|
+
try {
|
|
17937
|
+
const value = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
17938
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
17939
|
+
} catch {
|
|
17940
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
17941
|
+
}
|
|
17942
|
+
};
|
|
17943
|
+
var writeSessionMarker = (file, marker) => {
|
|
17944
|
+
try {
|
|
17945
|
+
fs14.mkdirSync(path18.dirname(file), { recursive: true });
|
|
17946
|
+
fs14.writeFileSync(file, JSON.stringify(marker));
|
|
17947
|
+
} catch {}
|
|
17948
|
+
};
|
|
17949
|
+
|
|
17950
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17915
17951
|
var NONE3 = { kind: "none" };
|
|
17916
17952
|
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
17917
17953
|
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
@@ -17928,12 +17964,12 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
17928
17964
|
"--config-env",
|
|
17929
17965
|
"--super-prefix"
|
|
17930
17966
|
]);
|
|
17931
|
-
var isAbsolute = (value) =>
|
|
17967
|
+
var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
17932
17968
|
var joinDir = (base, next) => {
|
|
17933
17969
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
17934
17970
|
return;
|
|
17935
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
17936
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
17971
|
+
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
17972
|
+
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
17937
17973
|
};
|
|
17938
17974
|
var cdTarget = (words) => {
|
|
17939
17975
|
const args = words.slice(1);
|
|
@@ -18029,6 +18065,37 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
18029
18065
|
}
|
|
18030
18066
|
return found;
|
|
18031
18067
|
}
|
|
18068
|
+
var LAUNCHERS = new Set([
|
|
18069
|
+
"npx",
|
|
18070
|
+
"bunx",
|
|
18071
|
+
"pnpx",
|
|
18072
|
+
"-y",
|
|
18073
|
+
"--yes",
|
|
18074
|
+
"pnpm",
|
|
18075
|
+
"dlx",
|
|
18076
|
+
"exec",
|
|
18077
|
+
"env",
|
|
18078
|
+
"sudo",
|
|
18079
|
+
"command"
|
|
18080
|
+
]);
|
|
18081
|
+
var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
18082
|
+
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
18083
|
+
const found = [];
|
|
18084
|
+
for (const { words } of segmentsOf(command, dialect)) {
|
|
18085
|
+
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
18086
|
+
if (at < 0)
|
|
18087
|
+
continue;
|
|
18088
|
+
if (WORKIT_BIN.test(words[at])) {
|
|
18089
|
+
found.push(words.slice(at + 1));
|
|
18090
|
+
continue;
|
|
18091
|
+
}
|
|
18092
|
+
const shell = SHELLS.has(commandName(words[at]));
|
|
18093
|
+
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
18094
|
+
if (depth < 3 && script !== undefined)
|
|
18095
|
+
found.push(...workitInvocations(script, "posix", depth + 1));
|
|
18096
|
+
}
|
|
18097
|
+
return found;
|
|
18098
|
+
}
|
|
18032
18099
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
18033
18100
|
var pushAction = (args) => {
|
|
18034
18101
|
let force = false;
|
|
@@ -18155,7 +18222,7 @@ var repoAt = (dir) => {
|
|
|
18155
18222
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
18156
18223
|
};
|
|
18157
18224
|
var isWorkitWorkspace = (repo) => {
|
|
18158
|
-
if (
|
|
18225
|
+
if (fs15.existsSync(repo.location.dir))
|
|
18159
18226
|
return true;
|
|
18160
18227
|
try {
|
|
18161
18228
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -18186,17 +18253,17 @@ var headSha2 = (repo) => {
|
|
|
18186
18253
|
if (!git || git.reftable)
|
|
18187
18254
|
return null;
|
|
18188
18255
|
try {
|
|
18189
|
-
const head =
|
|
18256
|
+
const head = fs15.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18190
18257
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18191
18258
|
return head;
|
|
18192
18259
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18193
18260
|
if (!ref)
|
|
18194
18261
|
return null;
|
|
18195
|
-
const common =
|
|
18262
|
+
const common = path19.dirname(repo.location.dir);
|
|
18196
18263
|
try {
|
|
18197
|
-
return
|
|
18264
|
+
return fs15.readFileSync(path19.join(common, ref), "utf8").trim() || null;
|
|
18198
18265
|
} catch {}
|
|
18199
|
-
const packed =
|
|
18266
|
+
const packed = fs15.readFileSync(path19.join(common, "packed-refs"), "utf8");
|
|
18200
18267
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18201
18268
|
} catch {
|
|
18202
18269
|
return null;
|
|
@@ -18233,7 +18300,7 @@ var actionsIn = (cwd, command, dialect) => {
|
|
|
18233
18300
|
const action = rawAction(invocation);
|
|
18234
18301
|
if (!action)
|
|
18235
18302
|
continue;
|
|
18236
|
-
const dir =
|
|
18303
|
+
const dir = path19.resolve(cwd, invocation.dir ?? ".");
|
|
18237
18304
|
if (!repos.has(dir)) {
|
|
18238
18305
|
const repo = repoAt(dir);
|
|
18239
18306
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18251,12 +18318,31 @@ var inWorkitWorkspace = (dir) => {
|
|
|
18251
18318
|
var rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read");
|
|
18252
18319
|
var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
|
|
18253
18320
|
var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
|
|
18254
|
-
|
|
18321
|
+
var nudgeKind = (action) => action.kind === "commit" && action.amend ? "commit-amend" : action.kind === "push" && action.blindForce ? "push-force" : action.kind;
|
|
18322
|
+
var unseenNudges = (input, nudges, store) => {
|
|
18323
|
+
const session = input.session.id;
|
|
18324
|
+
const file = session ? sessionMarkerFile(input, session, store) : null;
|
|
18325
|
+
if (!file)
|
|
18326
|
+
return [...nudges.values()];
|
|
18327
|
+
const marker = readSessionMarker(file);
|
|
18328
|
+
const agent = input.session.agentId ? `${input.session.agentId}:` : "";
|
|
18329
|
+
const fresh = [...nudges.keys()].filter((kind) => !marker.raw.includes(agent + kind));
|
|
18330
|
+
if (fresh.length)
|
|
18331
|
+
writeSessionMarker(file, {
|
|
18332
|
+
...marker,
|
|
18333
|
+
raw: [...marker.raw, ...fresh.map((kind) => agent + kind)]
|
|
18334
|
+
});
|
|
18335
|
+
return fresh.map((kind) => nudges.get(kind));
|
|
18336
|
+
};
|
|
18337
|
+
function rawGitPre(input, command, options = {}) {
|
|
18255
18338
|
try {
|
|
18256
18339
|
if (!mentionsRawTool(command))
|
|
18257
18340
|
return NONE3;
|
|
18258
|
-
const
|
|
18259
|
-
|
|
18341
|
+
const dialect = dialectOf(input);
|
|
18342
|
+
const nudges = new Map;
|
|
18343
|
+
let store = null;
|
|
18344
|
+
for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
|
|
18345
|
+
store ??= repo.location.dir;
|
|
18260
18346
|
if (action.kind === "merge")
|
|
18261
18347
|
return deny(refusal(action, null));
|
|
18262
18348
|
if (action.kind === "push")
|
|
@@ -18266,10 +18352,15 @@ function rawGitPre(input, command) {
|
|
|
18266
18352
|
return deny(refusal(action, branch));
|
|
18267
18353
|
}
|
|
18268
18354
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
18269
|
-
if (nudge && !nudges.
|
|
18270
|
-
nudges.
|
|
18355
|
+
if (nudge && !nudges.has(nudgeKind(action)))
|
|
18356
|
+
nudges.set(nudgeKind(action), nudge);
|
|
18271
18357
|
}
|
|
18272
|
-
|
|
18358
|
+
if (options.nudge === false || !store || nudges.size === 0)
|
|
18359
|
+
return NONE3;
|
|
18360
|
+
if (workitInvocations(command, dialect).length > 0)
|
|
18361
|
+
return NONE3;
|
|
18362
|
+
const shown = unseenNudges(input, nudges, store);
|
|
18363
|
+
return shown.length ? { kind: "context", text: shown.join(`
|
|
18273
18364
|
`) } : NONE3;
|
|
18274
18365
|
} catch {
|
|
18275
18366
|
return NONE3;
|
|
@@ -18278,8 +18369,8 @@ function rawGitPre(input, command) {
|
|
|
18278
18369
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
18279
18370
|
var NEXT_COMMAND = "next";
|
|
18280
18371
|
var MARKER_TTL_SECONDS = 3600;
|
|
18281
|
-
var markerFile = (repo, session, key) =>
|
|
18282
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
18372
|
+
var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash13("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18373
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash13("sha256").update(command).digest("hex")}`;
|
|
18283
18374
|
function noteRawCommit(input, command, key) {
|
|
18284
18375
|
try {
|
|
18285
18376
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -18289,8 +18380,8 @@ function noteRawCommit(input, command, key) {
|
|
|
18289
18380
|
if (action.kind !== "commit")
|
|
18290
18381
|
continue;
|
|
18291
18382
|
const file = markerFile(repo, input.session.id, key);
|
|
18292
|
-
|
|
18293
|
-
|
|
18383
|
+
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18384
|
+
fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18294
18385
|
`);
|
|
18295
18386
|
}
|
|
18296
18387
|
} catch {}
|
|
@@ -18313,11 +18404,11 @@ var headCommit = (dir) => {
|
|
|
18313
18404
|
var settle2 = (input, repo, file) => {
|
|
18314
18405
|
let marker;
|
|
18315
18406
|
try {
|
|
18316
|
-
marker = JSON.parse(
|
|
18407
|
+
marker = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
18317
18408
|
} catch {
|
|
18318
18409
|
return;
|
|
18319
18410
|
}
|
|
18320
|
-
|
|
18411
|
+
fs15.rmSync(file, { force: true });
|
|
18321
18412
|
const session = input.session.id;
|
|
18322
18413
|
const now = Date.now() / 1000;
|
|
18323
18414
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -18366,15 +18457,165 @@ function settlePendingCommit(input) {
|
|
|
18366
18457
|
if (!repo)
|
|
18367
18458
|
return;
|
|
18368
18459
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
18369
|
-
if (
|
|
18460
|
+
if (fs15.existsSync(file))
|
|
18370
18461
|
settle2(input, repo, file);
|
|
18371
18462
|
} catch {}
|
|
18372
18463
|
}
|
|
18373
|
-
// packages/workit-core/src/hooks/skill-nudge.ts
|
|
18374
|
-
import { createHash as createHash13 } from "node:crypto";
|
|
18375
|
-
import fs15 from "node:fs";
|
|
18376
|
-
import path19 from "node:path";
|
|
18377
18464
|
|
|
18465
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
18466
|
+
var NONE4 = { kind: "none" };
|
|
18467
|
+
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
18468
|
+
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
18469
|
+
var YES_NO = /[[(]\s*y(?:es)?\s*\/\s*n(?:o)?\s*[\])]/i;
|
|
18470
|
+
var LIST_ITEM = /^\s*(?:[-*+•]|\d+[.)]|[a-z][.)])\s+/i;
|
|
18471
|
+
var asksUser = (message) => {
|
|
18472
|
+
if (!message)
|
|
18473
|
+
return false;
|
|
18474
|
+
const paragraphs = message.replace(/```[\s\S]*?(?:```|$)/g, `
|
|
18475
|
+
|
|
18476
|
+
`).split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
18477
|
+
while (paragraphs.length > 1 && paragraphs.at(-1).split(`
|
|
18478
|
+
`).every((line) => LIST_ITEM.test(line)))
|
|
18479
|
+
paragraphs.pop();
|
|
18480
|
+
const tail = paragraphs.slice(-2).join(`
|
|
18481
|
+
`);
|
|
18482
|
+
return QUESTION.test(tail) || OFFER.test(tail) || YES_NO.test(tail);
|
|
18483
|
+
};
|
|
18484
|
+
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
18485
|
+
var gitList = (cwd, args) => {
|
|
18486
|
+
const run = spawnSync12("git", args, {
|
|
18487
|
+
cwd,
|
|
18488
|
+
encoding: "utf8",
|
|
18489
|
+
timeout: 3000,
|
|
18490
|
+
windowsHide: true,
|
|
18491
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
|
|
18492
|
+
});
|
|
18493
|
+
return run.status === 0 ? run.stdout.split(`
|
|
18494
|
+
`).filter(Boolean) : null;
|
|
18495
|
+
};
|
|
18496
|
+
var gitLine = (cwd, args) => gitList(cwd, args)?.[0] ?? null;
|
|
18497
|
+
var sessionOf = (row) => (typeof row.session === "string" ? row.session : null) ?? row.actor.session;
|
|
18498
|
+
var prState = (row) => typeof row.state === "string" ? row.state : "open";
|
|
18499
|
+
var ended = (row) => row.observer === "workit_cli" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) !== "open");
|
|
18500
|
+
var openPr = (rows) => {
|
|
18501
|
+
const seen = rows.findLast((row) => (row.type === "pr.created" || row.type === "pr.status") && row.observer === "workit_cli" && typeof row.pr === "number");
|
|
18502
|
+
if (!seen || ended(seen))
|
|
18503
|
+
return null;
|
|
18504
|
+
const closed = rows.some((row) => ended(row) && row.pr === seen.pr && row.seq > seen.seq);
|
|
18505
|
+
return closed ? null : seen.pr;
|
|
18506
|
+
};
|
|
18507
|
+
var mergedHeads = (rows) => [
|
|
18508
|
+
...new Set(rows.filter((row) => row.observer === "workit_cli" && typeof row.head === "string" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) === "merged")).map((row) => row.head))
|
|
18509
|
+
].slice(-3);
|
|
18510
|
+
var localCommit = (cwd, sha) => /^[0-9a-f]{7,64}$/.test(sha) && gitList(cwd, ["cat-file", "-e", `${sha}^{commit}`]) !== null;
|
|
18511
|
+
var upstreamGone = (cwd, branch) => gitLine(cwd, ["for-each-ref", "--format=%(upstream:track)", `refs/heads/${branch}`]) === "[gone]";
|
|
18512
|
+
var landed = (cwd, branch, rows, head, mine) => {
|
|
18513
|
+
if (!head)
|
|
18514
|
+
return false;
|
|
18515
|
+
const merged = mergedHeads(rows);
|
|
18516
|
+
if (merged.length === 0)
|
|
18517
|
+
return upstreamGone(cwd, branch);
|
|
18518
|
+
if (merged.includes(head))
|
|
18519
|
+
return true;
|
|
18520
|
+
if (mine.size === 0)
|
|
18521
|
+
return false;
|
|
18522
|
+
return merged.some((at) => {
|
|
18523
|
+
const after = gitList(cwd, ["rev-list", `--max-count=${UNPUSHED_SCAN}`, "HEAD", "--not", at]);
|
|
18524
|
+
return after !== null && !after.some((sha) => mine.has(sha));
|
|
18525
|
+
});
|
|
18526
|
+
};
|
|
18527
|
+
var UNPUSHED_SCAN = 500;
|
|
18528
|
+
var unpushed = (cwd, branch, endpoint, mine, merged) => {
|
|
18529
|
+
const remote = pushRemoteName(cwd, branch);
|
|
18530
|
+
if (!remote || mine.size === 0)
|
|
18531
|
+
return null;
|
|
18532
|
+
const listed = gitList(cwd, [
|
|
18533
|
+
"rev-list",
|
|
18534
|
+
`--max-count=${UNPUSHED_SCAN}`,
|
|
18535
|
+
"HEAD",
|
|
18536
|
+
"--not",
|
|
18537
|
+
`--remotes=${remote}`,
|
|
18538
|
+
...merged.filter((sha) => localCommit(cwd, sha))
|
|
18539
|
+
]);
|
|
18540
|
+
const count = (listed ?? []).filter((sha) => mine.has(sha)).length;
|
|
18541
|
+
if (count === 0)
|
|
18542
|
+
return null;
|
|
18543
|
+
return {
|
|
18544
|
+
kind: "unpushed",
|
|
18545
|
+
reason: `${branch} has ${count} commit(s) of this session not on ${remote}, and this workspace's endpoint is \`${endpoint}\` (\`workit grant show\`): push them with \`workit git push\`${endpoint === "pr" ? ", then open or update the PR (`workit pr create --fill`)" : ""}.`
|
|
18546
|
+
};
|
|
18547
|
+
};
|
|
18548
|
+
var redChecks = (rows, pr, head, endpoint) => {
|
|
18549
|
+
const status = rows.findLast((row) => row.type === "pr.status" && row.observer === "workit_cli" && row.pr === pr);
|
|
18550
|
+
const state = status && status.head === head && prState(status) === "open" ? status.checks : null;
|
|
18551
|
+
if (state !== "failing" && state !== "pending")
|
|
18552
|
+
return null;
|
|
18553
|
+
return {
|
|
18554
|
+
kind: "checks",
|
|
18555
|
+
reason: `PR #${pr}'s checks were last seen ${state} at ${short(head)}, and this workspace's endpoint is \`${endpoint}\`: run \`workit ci wait --pr ${pr}\`${state === "failing" ? " after fixing the failures `workit pr status` lists (then push)" : ""}.`
|
|
18556
|
+
};
|
|
18557
|
+
};
|
|
18558
|
+
var unverified = (cwd, branch, all, descriptor) => {
|
|
18559
|
+
const check = checkVerdicts(cwd, branch, all);
|
|
18560
|
+
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
18561
|
+
return null;
|
|
18562
|
+
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
18563
|
+
const verifier = `${descriptor.subagents.agentPrefix}verifier`;
|
|
18564
|
+
return {
|
|
18565
|
+
kind: "verdict",
|
|
18566
|
+
reason: `the head ${short(check.head)} of ${branch}, which this session delivered, has ${has}: if no verifier is already running on it, hand it to a session that did not author it (the ${verifier} agent records \`workit ledger verdict <result> --branch ${branch} --how "<evidence>"\`), or tell the user it is unverified. Never record a verdict on your own work.`
|
|
18567
|
+
};
|
|
18568
|
+
};
|
|
18569
|
+
function stopObligation(input, descriptor) {
|
|
18570
|
+
try {
|
|
18571
|
+
const session = input.session.id;
|
|
18572
|
+
if (!session || input.session.agentId || !inWorkitWorkspace(input.cwd))
|
|
18573
|
+
return null;
|
|
18574
|
+
const branch = currentBranch(input.cwd);
|
|
18575
|
+
if (!branch || guardedBranch(input.cwd, branch))
|
|
18576
|
+
return null;
|
|
18577
|
+
const ledger = readLedger(input.cwd);
|
|
18578
|
+
if (!ledger.ok)
|
|
18579
|
+
return null;
|
|
18580
|
+
const all = ledger.value.rows;
|
|
18581
|
+
const rows = all.filter((row) => row.branch === branch);
|
|
18582
|
+
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
18583
|
+
const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
|
|
18584
|
+
const delivered = mine.some((row) => row.observer === "workit_cli" && (row.type === "pr.created" || row.type === "delivery.verified"));
|
|
18585
|
+
if (commits.size === 0 && !delivered)
|
|
18586
|
+
return null;
|
|
18587
|
+
const head = gitLine(input.cwd, ["rev-parse", "HEAD"]);
|
|
18588
|
+
if (landed(input.cwd, branch, rows, head, commits))
|
|
18589
|
+
return null;
|
|
18590
|
+
const endpoint = resolveAutonomy(input.cwd).effectiveEndpoint;
|
|
18591
|
+
if (endpoint !== "commit") {
|
|
18592
|
+
const left = unpushed(input.cwd, branch, endpoint, commits, mergedHeads(rows));
|
|
18593
|
+
if (left)
|
|
18594
|
+
return left;
|
|
18595
|
+
}
|
|
18596
|
+
const pr = openPr(rows);
|
|
18597
|
+
if ((endpoint === "green" || endpoint === "merged") && pr !== null && head) {
|
|
18598
|
+
const left = redChecks(rows, pr, head, endpoint);
|
|
18599
|
+
if (left)
|
|
18600
|
+
return left;
|
|
18601
|
+
}
|
|
18602
|
+
return delivered ? unverified(input.cwd, branch, all, descriptor) : null;
|
|
18603
|
+
} catch {
|
|
18604
|
+
return null;
|
|
18605
|
+
}
|
|
18606
|
+
}
|
|
18607
|
+
function stopDecision(input, descriptor) {
|
|
18608
|
+
const event = input.event;
|
|
18609
|
+
if (event.kind !== "stop" || event.stopHookActive)
|
|
18610
|
+
return NONE4;
|
|
18611
|
+
if (!event.lastMessage?.trim() || asksUser(event.lastMessage))
|
|
18612
|
+
return NONE4;
|
|
18613
|
+
const left = stopObligation(input, descriptor);
|
|
18614
|
+
return left ? {
|
|
18615
|
+
kind: "continue",
|
|
18616
|
+
reason: `Workit: before stopping, ${left.reason} If this cannot be done now, say so to the user and stop.`
|
|
18617
|
+
} : NONE4;
|
|
18618
|
+
}
|
|
18378
18619
|
// packages/workit-core/src/core/skill-manifests.ts
|
|
18379
18620
|
var WORKIT_METHOD_SKILLS = [
|
|
18380
18621
|
"workit-shape",
|
|
@@ -18481,27 +18722,6 @@ var promptTrigger = (prompt) => {
|
|
|
18481
18722
|
}
|
|
18482
18723
|
return null;
|
|
18483
18724
|
};
|
|
18484
|
-
var markerFile2 = (input, session) => {
|
|
18485
|
-
const location = resolveStore(input.cwd);
|
|
18486
|
-
if (location instanceof Error || !location.shared)
|
|
18487
|
-
return null;
|
|
18488
|
-
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18489
|
-
return path19.join(location.dir, "hooks", `skills-${name}.json`);
|
|
18490
|
-
};
|
|
18491
|
-
var readMarker = (file) => {
|
|
18492
|
-
try {
|
|
18493
|
-
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
18494
|
-
return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
|
|
18495
|
-
} catch {
|
|
18496
|
-
return { nudged: [], loaded: [] };
|
|
18497
|
-
}
|
|
18498
|
-
};
|
|
18499
|
-
var writeMarker = (file, marker) => {
|
|
18500
|
-
try {
|
|
18501
|
-
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18502
|
-
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
18503
|
-
} catch {}
|
|
18504
|
-
};
|
|
18505
18725
|
var loadedSkills = (cwd, session) => {
|
|
18506
18726
|
const ledger = readLedger(cwd);
|
|
18507
18727
|
const loaded = new Set;
|
|
@@ -18549,13 +18769,13 @@ function recordSkillLoad(input, name, via) {
|
|
|
18549
18769
|
return;
|
|
18550
18770
|
if (!inWorkitWorkspace(input.cwd))
|
|
18551
18771
|
return;
|
|
18552
|
-
const file =
|
|
18772
|
+
const file = sessionMarkerFile(input, session);
|
|
18553
18773
|
if (!file)
|
|
18554
18774
|
return;
|
|
18555
|
-
const marker =
|
|
18775
|
+
const marker = readSessionMarker(file);
|
|
18556
18776
|
if (marker.loaded.includes(skill))
|
|
18557
18777
|
return;
|
|
18558
|
-
|
|
18778
|
+
writeSessionMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
|
|
18559
18779
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
18560
18780
|
return;
|
|
18561
18781
|
appendHookObserved(input.cwd, {
|
|
@@ -18567,23 +18787,24 @@ function recordSkillLoad(input, name, via) {
|
|
|
18567
18787
|
} catch {}
|
|
18568
18788
|
}
|
|
18569
18789
|
var nudgeOnce = (input, session, skill, line) => {
|
|
18570
|
-
const file =
|
|
18790
|
+
const file = sessionMarkerFile(input, session);
|
|
18571
18791
|
if (!file)
|
|
18572
18792
|
return null;
|
|
18573
|
-
const marker =
|
|
18793
|
+
const marker = readSessionMarker(file);
|
|
18574
18794
|
if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
|
|
18575
18795
|
return null;
|
|
18576
18796
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
18577
18797
|
return null;
|
|
18578
|
-
|
|
18798
|
+
writeSessionMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
|
|
18579
18799
|
return line;
|
|
18580
18800
|
};
|
|
18581
18801
|
var nudgeText = (input, skill, why) => USER_INVOKED.has(skill) ? `Workit: ${why} looks like ${skill} work, which the user starts; offer it (/${skill.replace(/^workit-/, "wk-")}) rather than loading it.` : `Workit: ${why} looks like ${skill} work; if it is, ${skillLoadWording(SKILL_HOST[input.host], skill)} first.`;
|
|
18582
18802
|
var promptLoads = (prompt) => /^\s*\/(?:skill:)?([\w:-]+)/.exec(prompt)?.[1] ?? /^\s*<skill name="([\w:-]+)"/.exec(prompt)?.[1] ?? /^\s*Load the (workit-[a-z-]+) skill with the skill tool\b/.exec(prompt)?.[1] ?? null;
|
|
18803
|
+
var relayedMessage = (prompt) => /^\s*(?:Another Claude session sent a message\b|<agent-message\s+from=|\[SYSTEM NOTIFICATION\b|<task-notification>)/.test(prompt);
|
|
18583
18804
|
function promptNudge(input, prompt) {
|
|
18584
18805
|
try {
|
|
18585
18806
|
const session = input.session.id;
|
|
18586
|
-
if (!prompt || !session || input.session.agentId)
|
|
18807
|
+
if (!prompt || !session || input.session.agentId || relayedMessage(prompt))
|
|
18587
18808
|
return null;
|
|
18588
18809
|
const loads = promptLoads(prompt);
|
|
18589
18810
|
if (loads) {
|
|
@@ -18599,32 +18820,7 @@ function promptNudge(input, prompt) {
|
|
|
18599
18820
|
}
|
|
18600
18821
|
}
|
|
18601
18822
|
var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
|
|
18602
|
-
var
|
|
18603
|
-
var LAUNCHERS = new Set([
|
|
18604
|
-
"npx",
|
|
18605
|
-
"bunx",
|
|
18606
|
-
"pnpx",
|
|
18607
|
-
"-y",
|
|
18608
|
-
"--yes",
|
|
18609
|
-
"pnpm",
|
|
18610
|
-
"dlx",
|
|
18611
|
-
"exec",
|
|
18612
|
-
"env",
|
|
18613
|
-
"sudo",
|
|
18614
|
-
"command"
|
|
18615
|
-
]);
|
|
18616
|
-
var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
|
|
18617
|
-
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
18618
|
-
if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
|
|
18619
|
-
const args = words.slice(at + 1);
|
|
18620
|
-
if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
|
|
18621
|
-
return false;
|
|
18622
|
-
return WORKIT_DELIVERY.test(args.join(" "));
|
|
18623
|
-
}
|
|
18624
|
-
const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
|
|
18625
|
-
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
18626
|
-
return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
|
|
18627
|
-
});
|
|
18823
|
+
var runsWorkitDelivery = (command, dialect) => workitInvocations(command, dialect).some((args) => !args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run") && WORKIT_DELIVERY.test(args.join(" ")));
|
|
18628
18824
|
var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
|
|
18629
18825
|
function shipNudge(input, command) {
|
|
18630
18826
|
try {
|
|
@@ -18664,7 +18860,7 @@ var existingDirectory = (value) => {
|
|
|
18664
18860
|
return null;
|
|
18665
18861
|
}
|
|
18666
18862
|
};
|
|
18667
|
-
var
|
|
18863
|
+
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
18668
18864
|
var SCRIPT_FLAG2 = /^(?:-\w*c|-command|\/c)$/i;
|
|
18669
18865
|
var quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
|
|
18670
18866
|
var commandText = (value) => {
|
|
@@ -18676,7 +18872,7 @@ var commandText = (value) => {
|
|
|
18676
18872
|
if (!argv.every((arg) => typeof arg === "string"))
|
|
18677
18873
|
return null;
|
|
18678
18874
|
const shell = argv[0].split(/[\\/]/).at(-1)?.replace(/\.exe$/i, "").toLowerCase() ?? "";
|
|
18679
|
-
if (
|
|
18875
|
+
if (SHELLS2.has(shell))
|
|
18680
18876
|
for (let index = 1;index < argv.length - 1; index++) {
|
|
18681
18877
|
if (SCRIPT_FLAG2.test(argv[index]))
|
|
18682
18878
|
return nonEmpty2(argv[index + 1]) ? argv[index + 1] : null;
|
|
@@ -18724,7 +18920,7 @@ var writeTargets = (input) => {
|
|
|
18724
18920
|
};
|
|
18725
18921
|
// packages/workit-core/src/hooks/handle.ts
|
|
18726
18922
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
18727
|
-
var
|
|
18923
|
+
var NONE5 = { kind: "none" };
|
|
18728
18924
|
var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
|
|
18729
18925
|
var offered = new Set;
|
|
18730
18926
|
var ROLES = {
|
|
@@ -18738,13 +18934,20 @@ var workitRole = (descriptor, agentType) => {
|
|
|
18738
18934
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
18739
18935
|
};
|
|
18740
18936
|
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
18741
|
-
var
|
|
18937
|
+
var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
18938
|
+
var ISOLATED_GIT = 'Run git as plain, separate commands inside your worktree (`git switch -c <branch> <base>`, `git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a commit-msg hook that adds the trailer), never `workit git …`: Claude Code\'s worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (`workit check`, `workit ledger`, `workit pr`).';
|
|
18939
|
+
var subagentStartText = (input, descriptor, event) => {
|
|
18940
|
+
const session = input.session.id ?? null;
|
|
18742
18941
|
const role = workitRole(descriptor, event.agentType);
|
|
18743
18942
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
return `Workit observed ${who}
|
|
18747
|
-
|
|
18943
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
|
|
18944
|
+
if (role === "implementer" && isolated)
|
|
18945
|
+
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Use a policy-compliant branch (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
18946
|
+
if (isolated)
|
|
18947
|
+
return `${ISOLATED_GIT}
|
|
18948
|
+
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
18949
|
+
if (role === "implementer")
|
|
18950
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
18748
18951
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
18749
18952
|
if (role !== "judge" || !event.agentId)
|
|
18750
18953
|
return readOnly;
|
|
@@ -18757,7 +18960,7 @@ function handleHook(input, deps) {
|
|
|
18757
18960
|
switch (event.kind) {
|
|
18758
18961
|
case "session.start": {
|
|
18759
18962
|
if (!usable2(descriptor.context.sessionStart))
|
|
18760
|
-
return
|
|
18963
|
+
return NONE5;
|
|
18761
18964
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
18762
18965
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
18763
18966
|
if (offer && key !== null)
|
|
@@ -18772,42 +18975,44 @@ function handleHook(input, deps) {
|
|
|
18772
18975
|
}
|
|
18773
18976
|
case "context.turn": {
|
|
18774
18977
|
if (!usable2(descriptor.context.perTurn))
|
|
18775
|
-
return
|
|
18978
|
+
return NONE5;
|
|
18776
18979
|
const current = turnContextText(input, descriptor);
|
|
18777
18980
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
18778
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
18981
|
+
return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
|
|
18779
18982
|
}
|
|
18780
18983
|
case "shell.pre": {
|
|
18781
18984
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
18782
18985
|
if (skillRead)
|
|
18783
18986
|
recordSkillLoad(input, skillRead, "read");
|
|
18784
18987
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
18785
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command) :
|
|
18988
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
|
|
18786
18989
|
if (policy.kind !== "none")
|
|
18787
18990
|
return policy;
|
|
18788
18991
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
18789
18992
|
if (!postTool)
|
|
18790
18993
|
settlePendingCommit(input);
|
|
18791
|
-
const raw = canDeny ? rawGitPre(input, event.command) :
|
|
18994
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
|
|
18792
18995
|
if (raw.kind === "deny")
|
|
18793
18996
|
return raw;
|
|
18794
18997
|
const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
|
|
18795
|
-
const gate = writes.writes ? writeGate(input.cwd, writes.targets) :
|
|
18998
|
+
const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
|
|
18796
18999
|
if (gate.kind === "deny")
|
|
18797
19000
|
return gate;
|
|
18798
19001
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
18799
|
-
|
|
19002
|
+
if (descriptor.transport !== "hook-process")
|
|
19003
|
+
return NONE5;
|
|
19004
|
+
return withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, shipNudge(input, event.command));
|
|
18800
19005
|
}
|
|
18801
19006
|
case "shell.post":
|
|
18802
19007
|
if (usable2(descriptor.events["shell.post"].support))
|
|
18803
19008
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
18804
|
-
return
|
|
19009
|
+
return NONE5;
|
|
18805
19010
|
case "write.pre":
|
|
18806
|
-
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) :
|
|
19011
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
|
|
18807
19012
|
case "subagent.start":
|
|
18808
19013
|
return {
|
|
18809
19014
|
kind: "context",
|
|
18810
|
-
text: subagentStartText(input
|
|
19015
|
+
text: subagentStartText(input, descriptor, event)
|
|
18811
19016
|
};
|
|
18812
19017
|
case "compact.pre":
|
|
18813
19018
|
return {
|
|
@@ -18817,19 +19022,20 @@ function handleHook(input, deps) {
|
|
|
18817
19022
|
case "tool.pre":
|
|
18818
19023
|
if (event.skill)
|
|
18819
19024
|
recordSkillLoad(input, event.skill, "tool");
|
|
18820
|
-
return
|
|
19025
|
+
return NONE5;
|
|
19026
|
+
case "stop":
|
|
19027
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
|
|
18821
19028
|
case "subagent.stop":
|
|
18822
19029
|
case "prompt.submit":
|
|
18823
|
-
|
|
18824
|
-
return NONE4;
|
|
19030
|
+
return NONE5;
|
|
18825
19031
|
}
|
|
18826
19032
|
}
|
|
18827
19033
|
var failureDecision = (descriptor, event, error) => {
|
|
18828
19034
|
if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
|
|
18829
|
-
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } :
|
|
19035
|
+
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE5;
|
|
18830
19036
|
if (event === "session.start" || event === "subagent.start")
|
|
18831
19037
|
return { kind: "context", text: `[workit diagnostic: ${error}]` };
|
|
18832
|
-
return
|
|
19038
|
+
return NONE5;
|
|
18833
19039
|
};
|
|
18834
19040
|
var dispatchHook = (adapter, raw, env = process.env) => {
|
|
18835
19041
|
const parsed = adapter.parse(raw, env);
|
|
@@ -18871,7 +19077,7 @@ var CLAUDE_CODE_DESCRIPTOR = {
|
|
|
18871
19077
|
"prompt.submit": { support: "native", native: "UserPromptSubmit" },
|
|
18872
19078
|
"subagent.stop": { support: "none", native: null },
|
|
18873
19079
|
"compact.pre": { support: "none", native: null },
|
|
18874
|
-
stop: { support: "
|
|
19080
|
+
stop: { support: "native", native: "Stop" }
|
|
18875
19081
|
},
|
|
18876
19082
|
shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
|
|
18877
19083
|
context: {
|
|
@@ -19137,7 +19343,7 @@ var CODEX_DESCRIPTOR = {
|
|
|
19137
19343
|
"subagent.stop": { support: "native", native: "SubagentStop" },
|
|
19138
19344
|
"prompt.submit": { support: "native", native: "UserPromptSubmit" },
|
|
19139
19345
|
"compact.pre": { support: "none", native: null },
|
|
19140
|
-
stop: { support: "
|
|
19346
|
+
stop: { support: "native", native: "Stop" }
|
|
19141
19347
|
},
|
|
19142
19348
|
shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
|
|
19143
19349
|
context: {
|
|
@@ -19157,7 +19363,7 @@ var CODEX_DESCRIPTOR = {
|
|
|
19157
19363
|
},
|
|
19158
19364
|
provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
|
|
19159
19365
|
interaction: { questions: "none", writeBoundary: "partial" },
|
|
19160
|
-
stopControl: "
|
|
19366
|
+
stopControl: "native",
|
|
19161
19367
|
shellAvailable: "native",
|
|
19162
19368
|
perEventCost: "low",
|
|
19163
19369
|
capabilities: [
|
|
@@ -19224,7 +19430,8 @@ var EVENTS2 = {
|
|
|
19224
19430
|
PreToolUse: "shell.pre",
|
|
19225
19431
|
PostToolUse: "shell.post",
|
|
19226
19432
|
SubagentStart: "subagent.start",
|
|
19227
|
-
SubagentStop: "subagent.stop"
|
|
19433
|
+
SubagentStop: "subagent.stop",
|
|
19434
|
+
Stop: "stop"
|
|
19228
19435
|
};
|
|
19229
19436
|
var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
19230
19437
|
var isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase());
|
|
@@ -19263,6 +19470,10 @@ var parseCodexHookInput = (value) => {
|
|
|
19263
19470
|
...nonEmpty2(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {},
|
|
19264
19471
|
...nonEmpty2(value.agent_id) ? { agent_id: value.agent_id } : {},
|
|
19265
19472
|
...nonEmpty2(value.agent_type) ? { agent_type: value.agent_type } : {},
|
|
19473
|
+
...event === "Stop" ? {
|
|
19474
|
+
last_assistant_message: optionalText(value.last_assistant_message),
|
|
19475
|
+
stop_hook_active: value.stop_hook_active === true
|
|
19476
|
+
} : {},
|
|
19266
19477
|
...event === "SubagentStop" ? {
|
|
19267
19478
|
agent_transcript_path: optionalText(value.agent_transcript_path),
|
|
19268
19479
|
last_assistant_message: optionalText(value.last_assistant_message),
|
|
@@ -19315,13 +19526,21 @@ var protocolEvent = (input) => {
|
|
|
19315
19526
|
lastMessage: input.last_assistant_message ?? null,
|
|
19316
19527
|
stopHookActive: input.stop_hook_active ?? false
|
|
19317
19528
|
};
|
|
19529
|
+
case "Stop":
|
|
19530
|
+
return {
|
|
19531
|
+
kind: "stop",
|
|
19532
|
+
lastMessage: input.last_assistant_message ?? null,
|
|
19533
|
+
stopHookActive: input.stop_hook_active ?? false
|
|
19534
|
+
};
|
|
19318
19535
|
}
|
|
19319
19536
|
};
|
|
19320
19537
|
var output = (event, extra = {}) => ({
|
|
19321
19538
|
hookSpecificOutput: { hookEventName: event, ...extra }
|
|
19322
19539
|
});
|
|
19323
19540
|
var render2 = (decision, native) => {
|
|
19324
|
-
if (native === "
|
|
19541
|
+
if (native === "Stop" && decision.kind === "continue")
|
|
19542
|
+
return { json: { decision: "block", reason: decision.reason }, exitCode: 0 };
|
|
19543
|
+
if (native === "SubagentStop" || native === "Stop" || native === null)
|
|
19325
19544
|
return { json: {}, exitCode: 0 };
|
|
19326
19545
|
if (decision.kind === "deny")
|
|
19327
19546
|
return {
|
|
@@ -19368,6 +19587,7 @@ var codexAdapter = {
|
|
|
19368
19587
|
};
|
|
19369
19588
|
|
|
19370
19589
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
19590
|
+
import fs16 from "node:fs";
|
|
19371
19591
|
import path21 from "node:path";
|
|
19372
19592
|
var undocumented = { support: "undocumented", native: null };
|
|
19373
19593
|
var none = { support: "none", native: null };
|
|
@@ -19388,7 +19608,7 @@ var CURSOR_DESCRIPTOR = {
|
|
|
19388
19608
|
"subagent.stop": { support: "partial", native: "subagentStop" },
|
|
19389
19609
|
"prompt.submit": undocumented,
|
|
19390
19610
|
"compact.pre": { support: "partial", native: "preCompact" },
|
|
19391
|
-
stop:
|
|
19611
|
+
stop: { support: "native", native: "stop" }
|
|
19392
19612
|
},
|
|
19393
19613
|
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: true },
|
|
19394
19614
|
context: {
|
|
@@ -19412,7 +19632,7 @@ var CURSOR_DESCRIPTOR = {
|
|
|
19412
19632
|
postToolObserve: "undocumented"
|
|
19413
19633
|
},
|
|
19414
19634
|
interaction: { questions: "none", writeBoundary: "partial" },
|
|
19415
|
-
stopControl: "
|
|
19635
|
+
stopControl: "native",
|
|
19416
19636
|
shellAvailable: "native",
|
|
19417
19637
|
perEventCost: "low",
|
|
19418
19638
|
capabilities: [
|
|
@@ -19479,7 +19699,8 @@ var EVENTS3 = {
|
|
|
19479
19699
|
beforeShellExecution: "shell.pre",
|
|
19480
19700
|
subagentStart: "subagent.start",
|
|
19481
19701
|
subagentStop: "subagent.stop",
|
|
19482
|
-
preCompact: "compact.pre"
|
|
19702
|
+
preCompact: "compact.pre",
|
|
19703
|
+
stop: "stop"
|
|
19483
19704
|
};
|
|
19484
19705
|
var parseCursorHookInput = (value) => {
|
|
19485
19706
|
if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
@@ -19515,7 +19736,12 @@ var parseCursorHookInput = (value) => {
|
|
|
19515
19736
|
...event === "subagentStart" && nonEmpty2(value.subagent_id) ? { subagent_id: value.subagent_id } : {},
|
|
19516
19737
|
...event === "subagentStart" && nonEmpty2(value.subagent_type) ? { subagent_type: value.subagent_type } : {},
|
|
19517
19738
|
...event === "subagentStart" && nonEmpty2(value.parent_conversation_id) ? { parent_conversation_id: value.parent_conversation_id } : {},
|
|
19518
|
-
...event === "subagentStart" && nonEmpty2(value.task) ? { task: value.task } : {}
|
|
19739
|
+
...event === "subagentStart" && nonEmpty2(value.task) ? { task: value.task } : {},
|
|
19740
|
+
...event === "stop" ? {
|
|
19741
|
+
...nonEmpty2(value.status) ? { status: value.status } : {},
|
|
19742
|
+
...typeof value.loop_count === "number" ? { loop_count: value.loop_count } : {},
|
|
19743
|
+
...nonEmpty2(value.transcript_path) ? { transcript_path: value.transcript_path } : {}
|
|
19744
|
+
} : {}
|
|
19519
19745
|
}
|
|
19520
19746
|
};
|
|
19521
19747
|
};
|
|
@@ -19549,6 +19775,51 @@ var protocolEvent2 = (input) => {
|
|
|
19549
19775
|
};
|
|
19550
19776
|
case "preCompact":
|
|
19551
19777
|
return { kind: "compact.pre", trigger: "auto" };
|
|
19778
|
+
case "stop":
|
|
19779
|
+
return {
|
|
19780
|
+
kind: "stop",
|
|
19781
|
+
lastMessage: (input.status ?? "completed") === "completed" ? lastAssistantText(input.transcript_path) : null,
|
|
19782
|
+
stopHookActive: (input.loop_count ?? 0) > 0
|
|
19783
|
+
};
|
|
19784
|
+
}
|
|
19785
|
+
};
|
|
19786
|
+
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
19787
|
+
var lastAssistantText = (file) => {
|
|
19788
|
+
if (!file || !path21.isAbsolute(file))
|
|
19789
|
+
return null;
|
|
19790
|
+
try {
|
|
19791
|
+
const size = fs16.statSync(file).size;
|
|
19792
|
+
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
19793
|
+
const buffer = Buffer.alloc(size - start);
|
|
19794
|
+
const fd = fs16.openSync(file, "r");
|
|
19795
|
+
try {
|
|
19796
|
+
fs16.readSync(fd, buffer, 0, buffer.length, start);
|
|
19797
|
+
} finally {
|
|
19798
|
+
fs16.closeSync(fd);
|
|
19799
|
+
}
|
|
19800
|
+
const lines = buffer.toString("utf8").split(`
|
|
19801
|
+
`);
|
|
19802
|
+
for (let index = lines.length - 1;index >= 0; index--) {
|
|
19803
|
+
let entry;
|
|
19804
|
+
try {
|
|
19805
|
+
entry = JSON.parse(lines[index]);
|
|
19806
|
+
} catch {
|
|
19807
|
+
continue;
|
|
19808
|
+
}
|
|
19809
|
+
if (!isRecord4(entry) || entry.role !== "assistant")
|
|
19810
|
+
continue;
|
|
19811
|
+
const content = isRecord4(entry.message) ? entry.message.content : entry.content;
|
|
19812
|
+
if (typeof content === "string")
|
|
19813
|
+
return content;
|
|
19814
|
+
if (!Array.isArray(content))
|
|
19815
|
+
return null;
|
|
19816
|
+
const text = content.filter((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
|
|
19817
|
+
`);
|
|
19818
|
+
return text || null;
|
|
19819
|
+
}
|
|
19820
|
+
return null;
|
|
19821
|
+
} catch {
|
|
19822
|
+
return null;
|
|
19552
19823
|
}
|
|
19553
19824
|
};
|
|
19554
19825
|
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path21.sep) ? root : `${root}${path21.sep}`);
|
|
@@ -19580,6 +19851,8 @@ var render3 = (decision, native) => {
|
|
|
19580
19851
|
return { json: { additional_context: decision.text }, exitCode: 0 };
|
|
19581
19852
|
if (decision.kind === "notice")
|
|
19582
19853
|
return { json: { user_message: decision.userMessage }, exitCode: 0 };
|
|
19854
|
+
if (decision.kind === "continue" && native === "stop")
|
|
19855
|
+
return { json: { followup_message: decision.reason }, exitCode: 0 };
|
|
19583
19856
|
return {
|
|
19584
19857
|
json: native !== null && BLOCKING2.has(native) ? { permission: "allow" } : {},
|
|
19585
19858
|
exitCode: 0
|