@brainervirus/workit-cursor 8.6.1 → 8.8.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
|
@@ -17874,12 +17874,43 @@ var init_write_gate = __esm(() => {
|
|
|
17874
17874
|
cache2 = new Map;
|
|
17875
17875
|
});
|
|
17876
17876
|
|
|
17877
|
-
// packages/workit-core/src/hooks/
|
|
17878
|
-
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17877
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17879
17878
|
import { createHash as createHash12 } from "node:crypto";
|
|
17880
17879
|
import fs14 from "node:fs";
|
|
17881
|
-
import os4 from "node:os";
|
|
17882
17880
|
import path18 from "node:path";
|
|
17881
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
17882
|
+
let dir = store;
|
|
17883
|
+
if (dir === undefined) {
|
|
17884
|
+
const location = resolveStore(input.cwd);
|
|
17885
|
+
if (location instanceof Error || !location.shared)
|
|
17886
|
+
return null;
|
|
17887
|
+
dir = location.dir;
|
|
17888
|
+
}
|
|
17889
|
+
const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
17890
|
+
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
17891
|
+
}, list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [], readSessionMarker = (file) => {
|
|
17892
|
+
try {
|
|
17893
|
+
const value = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
17894
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
17895
|
+
} catch {
|
|
17896
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
17897
|
+
}
|
|
17898
|
+
}, writeSessionMarker = (file, marker) => {
|
|
17899
|
+
try {
|
|
17900
|
+
fs14.mkdirSync(path18.dirname(file), { recursive: true });
|
|
17901
|
+
fs14.writeFileSync(file, JSON.stringify(marker));
|
|
17902
|
+
} catch {}
|
|
17903
|
+
};
|
|
17904
|
+
var init_session_marker = __esm(() => {
|
|
17905
|
+
init_paths();
|
|
17906
|
+
});
|
|
17907
|
+
|
|
17908
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17909
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17910
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
17911
|
+
import fs15 from "node:fs";
|
|
17912
|
+
import os4 from "node:os";
|
|
17913
|
+
import path19 from "node:path";
|
|
17883
17914
|
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
17884
17915
|
const found = [];
|
|
17885
17916
|
let dir = null;
|
|
@@ -17945,6 +17976,23 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
17945
17976
|
}
|
|
17946
17977
|
return found;
|
|
17947
17978
|
}
|
|
17979
|
+
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
17980
|
+
const found = [];
|
|
17981
|
+
for (const { words } of segmentsOf(command, dialect)) {
|
|
17982
|
+
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
17983
|
+
if (at < 0)
|
|
17984
|
+
continue;
|
|
17985
|
+
if (WORKIT_BIN.test(words[at])) {
|
|
17986
|
+
found.push(words.slice(at + 1));
|
|
17987
|
+
continue;
|
|
17988
|
+
}
|
|
17989
|
+
const shell = SHELLS.has(commandName(words[at]));
|
|
17990
|
+
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
17991
|
+
if (depth < 3 && script !== undefined)
|
|
17992
|
+
found.push(...workitInvocations(script, "posix", depth + 1));
|
|
17993
|
+
}
|
|
17994
|
+
return found;
|
|
17995
|
+
}
|
|
17948
17996
|
function rawAction(invocation) {
|
|
17949
17997
|
const args = invocation.args;
|
|
17950
17998
|
if (asksHelp(invocation))
|
|
@@ -17969,12 +18017,15 @@ function rawAction(invocation) {
|
|
|
17969
18017
|
return { kind: "pr-read", forge };
|
|
17970
18018
|
return null;
|
|
17971
18019
|
}
|
|
17972
|
-
function rawGitPre(input, command) {
|
|
18020
|
+
function rawGitPre(input, command, options = {}) {
|
|
17973
18021
|
try {
|
|
17974
18022
|
if (!mentionsRawTool(command))
|
|
17975
18023
|
return NONE3;
|
|
17976
|
-
const
|
|
17977
|
-
|
|
18024
|
+
const dialect = dialectOf(input);
|
|
18025
|
+
const nudges = new Map;
|
|
18026
|
+
let store = null;
|
|
18027
|
+
for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
|
|
18028
|
+
store ??= repo.location.dir;
|
|
17978
18029
|
if (action.kind === "merge")
|
|
17979
18030
|
return deny(refusal(action, null));
|
|
17980
18031
|
if (action.kind === "push")
|
|
@@ -17984,10 +18035,15 @@ function rawGitPre(input, command) {
|
|
|
17984
18035
|
return deny(refusal(action, branch));
|
|
17985
18036
|
}
|
|
17986
18037
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
17987
|
-
if (nudge && !nudges.
|
|
17988
|
-
nudges.
|
|
18038
|
+
if (nudge && !nudges.has(nudgeKind(action)))
|
|
18039
|
+
nudges.set(nudgeKind(action), nudge);
|
|
17989
18040
|
}
|
|
17990
|
-
|
|
18041
|
+
if (options.nudge === false || !store || nudges.size === 0)
|
|
18042
|
+
return NONE3;
|
|
18043
|
+
if (workitInvocations(command, dialect).length > 0)
|
|
18044
|
+
return NONE3;
|
|
18045
|
+
const shown = unseenNudges(input, nudges, store);
|
|
18046
|
+
return shown.length ? { kind: "context", text: shown.join(`
|
|
17991
18047
|
`) } : NONE3;
|
|
17992
18048
|
} catch {
|
|
17993
18049
|
return NONE3;
|
|
@@ -18002,8 +18058,8 @@ function noteRawCommit(input, command, key) {
|
|
|
18002
18058
|
if (action.kind !== "commit")
|
|
18003
18059
|
continue;
|
|
18004
18060
|
const file = markerFile(repo, input.session.id, key);
|
|
18005
|
-
|
|
18006
|
-
|
|
18061
|
+
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18062
|
+
fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18007
18063
|
`);
|
|
18008
18064
|
}
|
|
18009
18065
|
} catch {}
|
|
@@ -18025,15 +18081,15 @@ function settlePendingCommit(input) {
|
|
|
18025
18081
|
if (!repo)
|
|
18026
18082
|
return;
|
|
18027
18083
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
18028
|
-
if (
|
|
18084
|
+
if (fs15.existsSync(file))
|
|
18029
18085
|
settle2(input, repo, file);
|
|
18030
18086
|
} catch {}
|
|
18031
18087
|
}
|
|
18032
|
-
var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, isAbsolute = (value) =>
|
|
18088
|
+
var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), joinDir = (base, next) => {
|
|
18033
18089
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18034
18090
|
return;
|
|
18035
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
18036
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18091
|
+
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
18092
|
+
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
18037
18093
|
}, cdTarget = (words) => {
|
|
18038
18094
|
const args = words.slice(1);
|
|
18039
18095
|
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
@@ -18060,7 +18116,7 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18060
18116
|
} else
|
|
18061
18117
|
return words;
|
|
18062
18118
|
}
|
|
18063
|
-
}, commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, ""), PUSH_VALUE_OPTIONS, pushAction = (args) => {
|
|
18119
|
+
}, commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, ""), LAUNCHERS, WORKIT_BIN, PUSH_VALUE_OPTIONS, pushAction = (args) => {
|
|
18064
18120
|
let force = false;
|
|
18065
18121
|
let lease = false;
|
|
18066
18122
|
let wholesale = false;
|
|
@@ -18131,7 +18187,7 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18131
18187
|
return null;
|
|
18132
18188
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
18133
18189
|
}, isWorkitWorkspace = (repo) => {
|
|
18134
|
-
if (
|
|
18190
|
+
if (fs15.existsSync(repo.location.dir))
|
|
18135
18191
|
return true;
|
|
18136
18192
|
try {
|
|
18137
18193
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -18159,17 +18215,17 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18159
18215
|
if (!git || git.reftable)
|
|
18160
18216
|
return null;
|
|
18161
18217
|
try {
|
|
18162
|
-
const head =
|
|
18218
|
+
const head = fs15.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18163
18219
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18164
18220
|
return head;
|
|
18165
18221
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18166
18222
|
if (!ref)
|
|
18167
18223
|
return null;
|
|
18168
|
-
const common =
|
|
18224
|
+
const common = path19.dirname(repo.location.dir);
|
|
18169
18225
|
try {
|
|
18170
|
-
return
|
|
18226
|
+
return fs15.readFileSync(path19.join(common, ref), "utf8").trim() || null;
|
|
18171
18227
|
} catch {}
|
|
18172
|
-
const packed =
|
|
18228
|
+
const packed = fs15.readFileSync(path19.join(common, "packed-refs"), "utf8");
|
|
18173
18229
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18174
18230
|
} catch {
|
|
18175
18231
|
return null;
|
|
@@ -18202,7 +18258,7 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18202
18258
|
const action = rawAction(invocation);
|
|
18203
18259
|
if (!action)
|
|
18204
18260
|
continue;
|
|
18205
|
-
const dir =
|
|
18261
|
+
const dir = path19.resolve(cwd, invocation.dir ?? ".");
|
|
18206
18262
|
if (!repos.has(dir)) {
|
|
18207
18263
|
const repo = repoAt(dir);
|
|
18208
18264
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18215,7 +18271,21 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18215
18271
|
}, inWorkitWorkspace = (dir) => {
|
|
18216
18272
|
const repo = repoAt(dir);
|
|
18217
18273
|
return repo !== null && isWorkitWorkspace(repo);
|
|
18218
|
-
}, rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read"), mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command), dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix",
|
|
18274
|
+
}, rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read"), mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command), dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix", nudgeKind = (action) => action.kind === "commit" && action.amend ? "commit-amend" : action.kind === "push" && action.blindForce ? "push-force" : action.kind, unseenNudges = (input, nudges, store) => {
|
|
18275
|
+
const session = input.session.id;
|
|
18276
|
+
const file = session ? sessionMarkerFile(input, session, store) : null;
|
|
18277
|
+
if (!file)
|
|
18278
|
+
return [...nudges.values()];
|
|
18279
|
+
const marker = readSessionMarker(file);
|
|
18280
|
+
const agent = input.session.agentId ? `${input.session.agentId}:` : "";
|
|
18281
|
+
const fresh = [...nudges.keys()].filter((kind) => !marker.raw.includes(agent + kind));
|
|
18282
|
+
if (fresh.length)
|
|
18283
|
+
writeSessionMarker(file, {
|
|
18284
|
+
...marker,
|
|
18285
|
+
raw: [...marker.raw, ...fresh.map((kind) => agent + kind)]
|
|
18286
|
+
});
|
|
18287
|
+
return fresh.map((kind) => nudges.get(kind));
|
|
18288
|
+
}, deny = (reason) => ({ kind: "deny", reason, unblock: null }), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash13("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`), callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash13("sha256").update(command).digest("hex")}`, headCommit = (dir) => {
|
|
18219
18289
|
const run = spawnSync11("git", [
|
|
18220
18290
|
"log",
|
|
18221
18291
|
"-1",
|
|
@@ -18232,11 +18302,11 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18232
18302
|
}, settle2 = (input, repo, file) => {
|
|
18233
18303
|
let marker;
|
|
18234
18304
|
try {
|
|
18235
|
-
marker = JSON.parse(
|
|
18305
|
+
marker = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
18236
18306
|
} catch {
|
|
18237
18307
|
return;
|
|
18238
18308
|
}
|
|
18239
|
-
|
|
18309
|
+
fs15.rmSync(file, { force: true });
|
|
18240
18310
|
const session = input.session.id;
|
|
18241
18311
|
const now = Date.now() / 1000;
|
|
18242
18312
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -18274,6 +18344,7 @@ var init_raw_git = __esm(() => {
|
|
|
18274
18344
|
init_rev();
|
|
18275
18345
|
init_ledger();
|
|
18276
18346
|
init_paths();
|
|
18347
|
+
init_session_marker();
|
|
18277
18348
|
NONE3 = { kind: "none" };
|
|
18278
18349
|
SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18279
18350
|
PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
@@ -18290,6 +18361,20 @@ var init_raw_git = __esm(() => {
|
|
|
18290
18361
|
"--config-env",
|
|
18291
18362
|
"--super-prefix"
|
|
18292
18363
|
]);
|
|
18364
|
+
LAUNCHERS = new Set([
|
|
18365
|
+
"npx",
|
|
18366
|
+
"bunx",
|
|
18367
|
+
"pnpx",
|
|
18368
|
+
"-y",
|
|
18369
|
+
"--yes",
|
|
18370
|
+
"pnpm",
|
|
18371
|
+
"dlx",
|
|
18372
|
+
"exec",
|
|
18373
|
+
"env",
|
|
18374
|
+
"sudo",
|
|
18375
|
+
"command"
|
|
18376
|
+
]);
|
|
18377
|
+
WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
18293
18378
|
PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
18294
18379
|
VALUE_OPTIONS = {
|
|
18295
18380
|
git: new Set([
|
|
@@ -18320,6 +18405,153 @@ var init_raw_git = __esm(() => {
|
|
|
18320
18405
|
};
|
|
18321
18406
|
});
|
|
18322
18407
|
|
|
18408
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
18409
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18410
|
+
function stopObligation(input, descriptor) {
|
|
18411
|
+
try {
|
|
18412
|
+
const session = input.session.id;
|
|
18413
|
+
if (!session || input.session.agentId || !inWorkitWorkspace(input.cwd))
|
|
18414
|
+
return null;
|
|
18415
|
+
const branch = currentBranch(input.cwd);
|
|
18416
|
+
if (!branch || guardedBranch(input.cwd, branch))
|
|
18417
|
+
return null;
|
|
18418
|
+
const ledger = readLedger(input.cwd);
|
|
18419
|
+
if (!ledger.ok)
|
|
18420
|
+
return null;
|
|
18421
|
+
const all = ledger.value.rows;
|
|
18422
|
+
const rows = all.filter((row) => row.branch === branch);
|
|
18423
|
+
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
18424
|
+
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"));
|
|
18425
|
+
const delivered = mine.some((row) => row.observer === "workit_cli" && (row.type === "pr.created" || row.type === "delivery.verified"));
|
|
18426
|
+
if (commits.size === 0 && !delivered)
|
|
18427
|
+
return null;
|
|
18428
|
+
const head = gitLine(input.cwd, ["rev-parse", "HEAD"]);
|
|
18429
|
+
if (landed(input.cwd, branch, rows, head, commits))
|
|
18430
|
+
return null;
|
|
18431
|
+
const endpoint = resolveAutonomy(input.cwd).effectiveEndpoint;
|
|
18432
|
+
if (endpoint !== "commit") {
|
|
18433
|
+
const left = unpushed(input.cwd, branch, endpoint, commits, mergedHeads(rows));
|
|
18434
|
+
if (left)
|
|
18435
|
+
return left;
|
|
18436
|
+
}
|
|
18437
|
+
const pr = openPr(rows);
|
|
18438
|
+
if ((endpoint === "green" || endpoint === "merged") && pr !== null && head) {
|
|
18439
|
+
const left = redChecks(rows, pr, head, endpoint);
|
|
18440
|
+
if (left)
|
|
18441
|
+
return left;
|
|
18442
|
+
}
|
|
18443
|
+
return delivered ? unverified(input.cwd, branch, all, descriptor) : null;
|
|
18444
|
+
} catch {
|
|
18445
|
+
return null;
|
|
18446
|
+
}
|
|
18447
|
+
}
|
|
18448
|
+
function stopDecision(input, descriptor) {
|
|
18449
|
+
const event = input.event;
|
|
18450
|
+
if (event.kind !== "stop" || event.stopHookActive)
|
|
18451
|
+
return NONE4;
|
|
18452
|
+
if (!event.lastMessage?.trim() || asksUser(event.lastMessage))
|
|
18453
|
+
return NONE4;
|
|
18454
|
+
const left = stopObligation(input, descriptor);
|
|
18455
|
+
return left ? {
|
|
18456
|
+
kind: "continue",
|
|
18457
|
+
reason: `Workit: before stopping, ${left.reason} If this cannot be done now, say so to the user and stop.`
|
|
18458
|
+
} : NONE4;
|
|
18459
|
+
}
|
|
18460
|
+
var NONE4, QUESTION, OFFER, YES_NO, LIST_ITEM, asksUser = (message) => {
|
|
18461
|
+
if (!message)
|
|
18462
|
+
return false;
|
|
18463
|
+
const paragraphs = message.replace(/```[\s\S]*?(?:```|$)/g, `
|
|
18464
|
+
|
|
18465
|
+
`).split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
18466
|
+
while (paragraphs.length > 1 && paragraphs.at(-1).split(`
|
|
18467
|
+
`).every((line) => LIST_ITEM.test(line)))
|
|
18468
|
+
paragraphs.pop();
|
|
18469
|
+
const tail = paragraphs.slice(-2).join(`
|
|
18470
|
+
`);
|
|
18471
|
+
return QUESTION.test(tail) || OFFER.test(tail) || YES_NO.test(tail);
|
|
18472
|
+
}, short = (sha) => (sha ?? "?").slice(0, 12), gitList = (cwd, args) => {
|
|
18473
|
+
const run = spawnSync12("git", args, {
|
|
18474
|
+
cwd,
|
|
18475
|
+
encoding: "utf8",
|
|
18476
|
+
timeout: 3000,
|
|
18477
|
+
windowsHide: true,
|
|
18478
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
|
|
18479
|
+
});
|
|
18480
|
+
return run.status === 0 ? run.stdout.split(`
|
|
18481
|
+
`).filter(Boolean) : null;
|
|
18482
|
+
}, gitLine = (cwd, args) => gitList(cwd, args)?.[0] ?? null, sessionOf = (row) => (typeof row.session === "string" ? row.session : null) ?? row.actor.session, prState = (row) => typeof row.state === "string" ? row.state : "open", ended = (row) => row.observer === "workit_cli" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) !== "open"), openPr = (rows) => {
|
|
18483
|
+
const seen = rows.findLast((row) => (row.type === "pr.created" || row.type === "pr.status") && row.observer === "workit_cli" && typeof row.pr === "number");
|
|
18484
|
+
if (!seen || ended(seen))
|
|
18485
|
+
return null;
|
|
18486
|
+
const closed = rows.some((row) => ended(row) && row.pr === seen.pr && row.seq > seen.seq);
|
|
18487
|
+
return closed ? null : seen.pr;
|
|
18488
|
+
}, mergedHeads = (rows) => [
|
|
18489
|
+
...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))
|
|
18490
|
+
].slice(-3), localCommit = (cwd, sha) => /^[0-9a-f]{7,64}$/.test(sha) && gitList(cwd, ["cat-file", "-e", `${sha}^{commit}`]) !== null, upstreamGone = (cwd, branch) => gitLine(cwd, ["for-each-ref", "--format=%(upstream:track)", `refs/heads/${branch}`]) === "[gone]", landed = (cwd, branch, rows, head, mine) => {
|
|
18491
|
+
if (!head)
|
|
18492
|
+
return false;
|
|
18493
|
+
const merged = mergedHeads(rows);
|
|
18494
|
+
if (merged.length === 0)
|
|
18495
|
+
return upstreamGone(cwd, branch);
|
|
18496
|
+
if (merged.includes(head))
|
|
18497
|
+
return true;
|
|
18498
|
+
if (mine.size === 0)
|
|
18499
|
+
return false;
|
|
18500
|
+
return merged.some((at) => {
|
|
18501
|
+
const after = gitList(cwd, ["rev-list", `--max-count=${UNPUSHED_SCAN}`, "HEAD", "--not", at]);
|
|
18502
|
+
return after !== null && !after.some((sha) => mine.has(sha));
|
|
18503
|
+
});
|
|
18504
|
+
}, UNPUSHED_SCAN = 500, unpushed = (cwd, branch, endpoint, mine, merged) => {
|
|
18505
|
+
const remote = pushRemoteName(cwd, branch);
|
|
18506
|
+
if (!remote || mine.size === 0)
|
|
18507
|
+
return null;
|
|
18508
|
+
const listed = gitList(cwd, [
|
|
18509
|
+
"rev-list",
|
|
18510
|
+
`--max-count=${UNPUSHED_SCAN}`,
|
|
18511
|
+
"HEAD",
|
|
18512
|
+
"--not",
|
|
18513
|
+
`--remotes=${remote}`,
|
|
18514
|
+
...merged.filter((sha) => localCommit(cwd, sha))
|
|
18515
|
+
]);
|
|
18516
|
+
const count = (listed ?? []).filter((sha) => mine.has(sha)).length;
|
|
18517
|
+
if (count === 0)
|
|
18518
|
+
return null;
|
|
18519
|
+
return {
|
|
18520
|
+
kind: "unpushed",
|
|
18521
|
+
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`)" : ""}.`
|
|
18522
|
+
};
|
|
18523
|
+
}, redChecks = (rows, pr, head, endpoint) => {
|
|
18524
|
+
const status = rows.findLast((row) => row.type === "pr.status" && row.observer === "workit_cli" && row.pr === pr);
|
|
18525
|
+
const state = status && status.head === head && prState(status) === "open" ? status.checks : null;
|
|
18526
|
+
if (state !== "failing" && state !== "pending")
|
|
18527
|
+
return null;
|
|
18528
|
+
return {
|
|
18529
|
+
kind: "checks",
|
|
18530
|
+
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)" : ""}.`
|
|
18531
|
+
};
|
|
18532
|
+
}, unverified = (cwd, branch, all, descriptor) => {
|
|
18533
|
+
const check = checkVerdicts(cwd, branch, all);
|
|
18534
|
+
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
18535
|
+
return null;
|
|
18536
|
+
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
18537
|
+
const verifier = `${descriptor.subagents.agentPrefix}verifier`;
|
|
18538
|
+
return {
|
|
18539
|
+
kind: "verdict",
|
|
18540
|
+
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.`
|
|
18541
|
+
};
|
|
18542
|
+
};
|
|
18543
|
+
var init_stop = __esm(() => {
|
|
18544
|
+
init_autonomy();
|
|
18545
|
+
init_rev();
|
|
18546
|
+
init_ledger();
|
|
18547
|
+
init_raw_git();
|
|
18548
|
+
NONE4 = { kind: "none" };
|
|
18549
|
+
QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
18550
|
+
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;
|
|
18551
|
+
YES_NO = /[[(]\s*y(?:es)?\s*\/\s*n(?:o)?\s*[\])]/i;
|
|
18552
|
+
LIST_ITEM = /^\s*(?:[-*+•]|\d+[.)]|[a-z][.)])\s+/i;
|
|
18553
|
+
});
|
|
18554
|
+
|
|
18323
18555
|
// packages/workit-core/src/core/skill-manifests.ts
|
|
18324
18556
|
var WORKIT_METHOD_SKILLS, WORKIT_SKILL_ALIASES, skillLoadWording = (host, skill) => {
|
|
18325
18557
|
switch (host) {
|
|
@@ -18366,9 +18598,6 @@ var init_skill_manifests = __esm(() => {
|
|
|
18366
18598
|
});
|
|
18367
18599
|
|
|
18368
18600
|
// packages/workit-core/src/hooks/skill-nudge.ts
|
|
18369
|
-
import { createHash as createHash13 } from "node:crypto";
|
|
18370
|
-
import fs15 from "node:fs";
|
|
18371
|
-
import path19 from "node:path";
|
|
18372
18601
|
function recordSkillLoad(input, name, via) {
|
|
18373
18602
|
try {
|
|
18374
18603
|
const skill = workitSkillOf(name);
|
|
@@ -18377,13 +18606,13 @@ function recordSkillLoad(input, name, via) {
|
|
|
18377
18606
|
return;
|
|
18378
18607
|
if (!inWorkitWorkspace(input.cwd))
|
|
18379
18608
|
return;
|
|
18380
|
-
const file =
|
|
18609
|
+
const file = sessionMarkerFile(input, session);
|
|
18381
18610
|
if (!file)
|
|
18382
18611
|
return;
|
|
18383
|
-
const marker =
|
|
18612
|
+
const marker = readSessionMarker(file);
|
|
18384
18613
|
if (marker.loaded.includes(skill))
|
|
18385
18614
|
return;
|
|
18386
|
-
|
|
18615
|
+
writeSessionMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
|
|
18387
18616
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
18388
18617
|
return;
|
|
18389
18618
|
appendHookObserved(input.cwd, {
|
|
@@ -18397,7 +18626,7 @@ function recordSkillLoad(input, name, via) {
|
|
|
18397
18626
|
function promptNudge(input, prompt) {
|
|
18398
18627
|
try {
|
|
18399
18628
|
const session = input.session.id;
|
|
18400
|
-
if (!prompt || !session || input.session.agentId)
|
|
18629
|
+
if (!prompt || !session || input.session.agentId || relayedMessage(prompt))
|
|
18401
18630
|
return null;
|
|
18402
18631
|
const loads = promptLoads(prompt);
|
|
18403
18632
|
if (loads) {
|
|
@@ -18440,24 +18669,6 @@ var USER_INVOKED, SKILL_HOST, PROMPT_INTENTS, workitSkillOf = (name) => {
|
|
|
18440
18669
|
return { skill, trigger: match[1] ?? match[0] };
|
|
18441
18670
|
}
|
|
18442
18671
|
return null;
|
|
18443
|
-
}, markerFile2 = (input, session) => {
|
|
18444
|
-
const location = resolveStore(input.cwd);
|
|
18445
|
-
if (location instanceof Error || !location.shared)
|
|
18446
|
-
return null;
|
|
18447
|
-
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18448
|
-
return path19.join(location.dir, "hooks", `skills-${name}.json`);
|
|
18449
|
-
}, readMarker = (file) => {
|
|
18450
|
-
try {
|
|
18451
|
-
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
18452
|
-
return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
|
|
18453
|
-
} catch {
|
|
18454
|
-
return { nudged: [], loaded: [] };
|
|
18455
|
-
}
|
|
18456
|
-
}, writeMarker = (file, marker) => {
|
|
18457
|
-
try {
|
|
18458
|
-
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18459
|
-
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
18460
|
-
} catch {}
|
|
18461
18672
|
}, loadedSkills = (cwd, session) => {
|
|
18462
18673
|
const ledger = readLedger(cwd);
|
|
18463
18674
|
const loaded = new Set;
|
|
@@ -18481,28 +18692,17 @@ var USER_INVOKED, SKILL_HOST, PROMPT_INTENTS, workitSkillOf = (name) => {
|
|
|
18481
18692
|
}
|
|
18482
18693
|
return null;
|
|
18483
18694
|
}, nudgeOnce = (input, session, skill, line) => {
|
|
18484
|
-
const file =
|
|
18695
|
+
const file = sessionMarkerFile(input, session);
|
|
18485
18696
|
if (!file)
|
|
18486
18697
|
return null;
|
|
18487
|
-
const marker =
|
|
18698
|
+
const marker = readSessionMarker(file);
|
|
18488
18699
|
if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
|
|
18489
18700
|
return null;
|
|
18490
18701
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
18491
18702
|
return null;
|
|
18492
|
-
|
|
18703
|
+
writeSessionMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
|
|
18493
18704
|
return line;
|
|
18494
|
-
}, 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.`, 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,
|
|
18495
|
-
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
18496
|
-
if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
|
|
18497
|
-
const args = words.slice(at + 1);
|
|
18498
|
-
if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
|
|
18499
|
-
return false;
|
|
18500
|
-
return WORKIT_DELIVERY.test(args.join(" "));
|
|
18501
|
-
}
|
|
18502
|
-
const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
|
|
18503
|
-
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
18504
|
-
return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
|
|
18505
|
-
}), isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd), withContextLine = (decision, line) => {
|
|
18705
|
+
}, 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.`, 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, relayedMessage = (prompt) => /^\s*(?:Another Claude session sent a message\b|<agent-message\s+from=|\[SYSTEM NOTIFICATION\b|<task-notification>)/.test(prompt), WORKIT_DELIVERY, runsWorkitDelivery = (command, dialect) => workitInvocations(command, dialect).some((args) => !args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run") && WORKIT_DELIVERY.test(args.join(" "))), isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd), withContextLine = (decision, line) => {
|
|
18506
18706
|
if (!line)
|
|
18507
18707
|
return decision;
|
|
18508
18708
|
if (decision.kind === "none")
|
|
@@ -18515,8 +18715,8 @@ ${line}` };
|
|
|
18515
18715
|
var init_skill_nudge = __esm(() => {
|
|
18516
18716
|
init_skill_manifests();
|
|
18517
18717
|
init_ledger();
|
|
18518
|
-
init_paths();
|
|
18519
18718
|
init_raw_git();
|
|
18719
|
+
init_session_marker();
|
|
18520
18720
|
USER_INVOKED = new Set(["workit-retro", "workit-architecture"]);
|
|
18521
18721
|
SKILL_HOST = {
|
|
18522
18722
|
claude_code: "claude-code",
|
|
@@ -18577,20 +18777,6 @@ var init_skill_nudge = __esm(() => {
|
|
|
18577
18777
|
"type"
|
|
18578
18778
|
]);
|
|
18579
18779
|
WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
|
|
18580
|
-
SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18581
|
-
LAUNCHERS = new Set([
|
|
18582
|
-
"npx",
|
|
18583
|
-
"bunx",
|
|
18584
|
-
"pnpx",
|
|
18585
|
-
"-y",
|
|
18586
|
-
"--yes",
|
|
18587
|
-
"pnpm",
|
|
18588
|
-
"dlx",
|
|
18589
|
-
"exec",
|
|
18590
|
-
"env",
|
|
18591
|
-
"sudo",
|
|
18592
|
-
"command"
|
|
18593
|
-
]);
|
|
18594
18780
|
});
|
|
18595
18781
|
|
|
18596
18782
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
@@ -18604,7 +18790,7 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
|
|
|
18604
18790
|
} catch {
|
|
18605
18791
|
return null;
|
|
18606
18792
|
}
|
|
18607
|
-
},
|
|
18793
|
+
}, SHELLS2, SCRIPT_FLAG2, quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg), commandText = (value) => {
|
|
18608
18794
|
if (nonEmpty2(value))
|
|
18609
18795
|
return value;
|
|
18610
18796
|
if (!Array.isArray(value) || value.length === 0)
|
|
@@ -18613,7 +18799,7 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
|
|
|
18613
18799
|
if (!argv.every((arg) => typeof arg === "string"))
|
|
18614
18800
|
return null;
|
|
18615
18801
|
const shell = argv[0].split(/[\\/]/).at(-1)?.replace(/\.exe$/i, "").toLowerCase() ?? "";
|
|
18616
|
-
if (
|
|
18802
|
+
if (SHELLS2.has(shell))
|
|
18617
18803
|
for (let index = 1;index < argv.length - 1; index++) {
|
|
18618
18804
|
if (SCRIPT_FLAG2.test(argv[index]))
|
|
18619
18805
|
return nonEmpty2(argv[index + 1]) ? argv[index + 1] : null;
|
|
@@ -18636,7 +18822,7 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
|
|
|
18636
18822
|
return paths.length > 0 ? paths : null;
|
|
18637
18823
|
};
|
|
18638
18824
|
var init_fields = __esm(() => {
|
|
18639
|
-
|
|
18825
|
+
SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
18640
18826
|
SCRIPT_FLAG2 = /^(?:-\w*c|-command|\/c)$/i;
|
|
18641
18827
|
WRITE_TOOLS = new Set([
|
|
18642
18828
|
"write",
|
|
@@ -18668,7 +18854,7 @@ function handleHook(input, deps) {
|
|
|
18668
18854
|
switch (event.kind) {
|
|
18669
18855
|
case "session.start": {
|
|
18670
18856
|
if (!usable2(descriptor.context.sessionStart))
|
|
18671
|
-
return
|
|
18857
|
+
return NONE5;
|
|
18672
18858
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
18673
18859
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
18674
18860
|
if (offer && key !== null)
|
|
@@ -18683,42 +18869,44 @@ function handleHook(input, deps) {
|
|
|
18683
18869
|
}
|
|
18684
18870
|
case "context.turn": {
|
|
18685
18871
|
if (!usable2(descriptor.context.perTurn))
|
|
18686
|
-
return
|
|
18872
|
+
return NONE5;
|
|
18687
18873
|
const current = turnContextText(input, descriptor);
|
|
18688
18874
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
18689
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
18875
|
+
return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
|
|
18690
18876
|
}
|
|
18691
18877
|
case "shell.pre": {
|
|
18692
18878
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
18693
18879
|
if (skillRead)
|
|
18694
18880
|
recordSkillLoad(input, skillRead, "read");
|
|
18695
18881
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
18696
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command) :
|
|
18882
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
|
|
18697
18883
|
if (policy.kind !== "none")
|
|
18698
18884
|
return policy;
|
|
18699
18885
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
18700
18886
|
if (!postTool)
|
|
18701
18887
|
settlePendingCommit(input);
|
|
18702
|
-
const raw = canDeny ? rawGitPre(input, event.command) :
|
|
18888
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
|
|
18703
18889
|
if (raw.kind === "deny")
|
|
18704
18890
|
return raw;
|
|
18705
18891
|
const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
|
|
18706
|
-
const gate = writes.writes ? writeGate(input.cwd, writes.targets) :
|
|
18892
|
+
const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
|
|
18707
18893
|
if (gate.kind === "deny")
|
|
18708
18894
|
return gate;
|
|
18709
18895
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
18710
|
-
|
|
18896
|
+
if (descriptor.transport !== "hook-process")
|
|
18897
|
+
return NONE5;
|
|
18898
|
+
return withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, shipNudge(input, event.command));
|
|
18711
18899
|
}
|
|
18712
18900
|
case "shell.post":
|
|
18713
18901
|
if (usable2(descriptor.events["shell.post"].support))
|
|
18714
18902
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
18715
|
-
return
|
|
18903
|
+
return NONE5;
|
|
18716
18904
|
case "write.pre":
|
|
18717
|
-
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) :
|
|
18905
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
|
|
18718
18906
|
case "subagent.start":
|
|
18719
18907
|
return {
|
|
18720
18908
|
kind: "context",
|
|
18721
|
-
text: subagentStartText(input
|
|
18909
|
+
text: subagentStartText(input, descriptor, event)
|
|
18722
18910
|
};
|
|
18723
18911
|
case "compact.pre":
|
|
18724
18912
|
return {
|
|
@@ -18728,24 +18916,30 @@ function handleHook(input, deps) {
|
|
|
18728
18916
|
case "tool.pre":
|
|
18729
18917
|
if (event.skill)
|
|
18730
18918
|
recordSkillLoad(input, event.skill, "tool");
|
|
18731
|
-
return
|
|
18919
|
+
return NONE5;
|
|
18920
|
+
case "stop":
|
|
18921
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
|
|
18732
18922
|
case "subagent.stop":
|
|
18733
18923
|
case "prompt.submit":
|
|
18734
|
-
|
|
18735
|
-
return NONE4;
|
|
18924
|
+
return NONE5;
|
|
18736
18925
|
}
|
|
18737
18926
|
}
|
|
18738
|
-
var usable2 = (value) => value === "native" || value === "partial",
|
|
18927
|
+
var usable2 = (value) => value === "native" || value === "partial", NONE5, resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn), offered, ROLES, workitRole = (descriptor, agentType) => {
|
|
18739
18928
|
const prefix = descriptor.subagents.agentPrefix;
|
|
18740
18929
|
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
18741
18930
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
18742
|
-
}, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128),
|
|
18931
|
+
}, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE, 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`).', subagentStartText = (input, descriptor, event) => {
|
|
18932
|
+
const session = input.session.id ?? null;
|
|
18743
18933
|
const role = workitRole(descriptor, event.agentType);
|
|
18744
18934
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
return `Workit observed ${who}
|
|
18748
|
-
|
|
18935
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
|
|
18936
|
+
if (role === "implementer" && isolated)
|
|
18937
|
+
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.`;
|
|
18938
|
+
if (isolated)
|
|
18939
|
+
return `${ISOLATED_GIT}
|
|
18940
|
+
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
18941
|
+
if (role === "implementer")
|
|
18942
|
+
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.`;
|
|
18749
18943
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
18750
18944
|
if (role !== "judge" || !event.agentId)
|
|
18751
18945
|
return readOnly;
|
|
@@ -18753,10 +18947,10 @@ var usable2 = (value) => value === "native" || value === "partial", NONE4, resen
|
|
|
18753
18947
|
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
|
|
18754
18948
|
}, failureDecision = (descriptor, event, error) => {
|
|
18755
18949
|
if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
|
|
18756
|
-
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } :
|
|
18950
|
+
return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE5;
|
|
18757
18951
|
if (event === "session.start" || event === "subagent.start")
|
|
18758
18952
|
return { kind: "context", text: `[workit diagnostic: ${error}]` };
|
|
18759
|
-
return
|
|
18953
|
+
return NONE5;
|
|
18760
18954
|
}, dispatchHook = (adapter, raw, env = process.env) => {
|
|
18761
18955
|
const parsed = adapter.parse(raw, env);
|
|
18762
18956
|
if (!parsed.ok)
|
|
@@ -18784,14 +18978,16 @@ var init_handle = __esm(() => {
|
|
|
18784
18978
|
init_policy();
|
|
18785
18979
|
init_raw_git();
|
|
18786
18980
|
init_skill_nudge();
|
|
18981
|
+
init_stop();
|
|
18787
18982
|
init_write_gate();
|
|
18788
|
-
|
|
18983
|
+
NONE5 = { kind: "none" };
|
|
18789
18984
|
offered = new Set;
|
|
18790
18985
|
ROLES = {
|
|
18791
18986
|
implementer: "implementer",
|
|
18792
18987
|
verifier: "judge",
|
|
18793
18988
|
reviewer: "judge"
|
|
18794
18989
|
};
|
|
18990
|
+
CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
18795
18991
|
});
|
|
18796
18992
|
|
|
18797
18993
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
@@ -18925,7 +19121,7 @@ var init_claude_code = __esm(() => {
|
|
|
18925
19121
|
"prompt.submit": { support: "native", native: "UserPromptSubmit" },
|
|
18926
19122
|
"subagent.stop": { support: "none", native: null },
|
|
18927
19123
|
"compact.pre": { support: "none", native: null },
|
|
18928
|
-
stop: { support: "
|
|
19124
|
+
stop: { support: "native", native: "Stop" }
|
|
18929
19125
|
},
|
|
18930
19126
|
shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
|
|
18931
19127
|
context: {
|
|
@@ -19095,6 +19291,10 @@ var CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS
|
|
|
19095
19291
|
...nonEmpty2(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {},
|
|
19096
19292
|
...nonEmpty2(value.agent_id) ? { agent_id: value.agent_id } : {},
|
|
19097
19293
|
...nonEmpty2(value.agent_type) ? { agent_type: value.agent_type } : {},
|
|
19294
|
+
...event === "Stop" ? {
|
|
19295
|
+
last_assistant_message: optionalText(value.last_assistant_message),
|
|
19296
|
+
stop_hook_active: value.stop_hook_active === true
|
|
19297
|
+
} : {},
|
|
19098
19298
|
...event === "SubagentStop" ? {
|
|
19099
19299
|
agent_transcript_path: optionalText(value.agent_transcript_path),
|
|
19100
19300
|
last_assistant_message: optionalText(value.last_assistant_message),
|
|
@@ -19146,11 +19346,19 @@ var CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS
|
|
|
19146
19346
|
lastMessage: input.last_assistant_message ?? null,
|
|
19147
19347
|
stopHookActive: input.stop_hook_active ?? false
|
|
19148
19348
|
};
|
|
19349
|
+
case "Stop":
|
|
19350
|
+
return {
|
|
19351
|
+
kind: "stop",
|
|
19352
|
+
lastMessage: input.last_assistant_message ?? null,
|
|
19353
|
+
stopHookActive: input.stop_hook_active ?? false
|
|
19354
|
+
};
|
|
19149
19355
|
}
|
|
19150
19356
|
}, output = (event, extra = {}) => ({
|
|
19151
19357
|
hookSpecificOutput: { hookEventName: event, ...extra }
|
|
19152
19358
|
}), render2 = (decision, native) => {
|
|
19153
|
-
if (native === "
|
|
19359
|
+
if (native === "Stop" && decision.kind === "continue")
|
|
19360
|
+
return { json: { decision: "block", reason: decision.reason }, exitCode: 0 };
|
|
19361
|
+
if (native === "SubagentStop" || native === "Stop" || native === null)
|
|
19154
19362
|
return { json: {}, exitCode: 0 };
|
|
19155
19363
|
if (decision.kind === "deny")
|
|
19156
19364
|
return {
|
|
@@ -19183,7 +19391,7 @@ var init_codex = __esm(() => {
|
|
|
19183
19391
|
"subagent.stop": { support: "native", native: "SubagentStop" },
|
|
19184
19392
|
"prompt.submit": { support: "native", native: "UserPromptSubmit" },
|
|
19185
19393
|
"compact.pre": { support: "none", native: null },
|
|
19186
|
-
stop: { support: "
|
|
19394
|
+
stop: { support: "native", native: "Stop" }
|
|
19187
19395
|
},
|
|
19188
19396
|
shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
|
|
19189
19397
|
context: {
|
|
@@ -19203,7 +19411,7 @@ var init_codex = __esm(() => {
|
|
|
19203
19411
|
},
|
|
19204
19412
|
provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
|
|
19205
19413
|
interaction: { questions: "none", writeBoundary: "partial" },
|
|
19206
|
-
stopControl: "
|
|
19414
|
+
stopControl: "native",
|
|
19207
19415
|
shellAvailable: "native",
|
|
19208
19416
|
perEventCost: "low",
|
|
19209
19417
|
capabilities: [
|
|
@@ -19270,7 +19478,8 @@ var init_codex = __esm(() => {
|
|
|
19270
19478
|
PreToolUse: "shell.pre",
|
|
19271
19479
|
PostToolUse: "shell.post",
|
|
19272
19480
|
SubagentStart: "subagent.start",
|
|
19273
|
-
SubagentStop: "subagent.stop"
|
|
19481
|
+
SubagentStop: "subagent.stop",
|
|
19482
|
+
Stop: "stop"
|
|
19274
19483
|
};
|
|
19275
19484
|
SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
19276
19485
|
SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
@@ -19308,6 +19517,7 @@ var init_codex = __esm(() => {
|
|
|
19308
19517
|
});
|
|
19309
19518
|
|
|
19310
19519
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
19520
|
+
import fs16 from "node:fs";
|
|
19311
19521
|
import path21 from "node:path";
|
|
19312
19522
|
var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
|
|
19313
19523
|
if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
@@ -19343,7 +19553,12 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
19343
19553
|
...event === "subagentStart" && nonEmpty2(value.subagent_id) ? { subagent_id: value.subagent_id } : {},
|
|
19344
19554
|
...event === "subagentStart" && nonEmpty2(value.subagent_type) ? { subagent_type: value.subagent_type } : {},
|
|
19345
19555
|
...event === "subagentStart" && nonEmpty2(value.parent_conversation_id) ? { parent_conversation_id: value.parent_conversation_id } : {},
|
|
19346
|
-
...event === "subagentStart" && nonEmpty2(value.task) ? { task: value.task } : {}
|
|
19556
|
+
...event === "subagentStart" && nonEmpty2(value.task) ? { task: value.task } : {},
|
|
19557
|
+
...event === "stop" ? {
|
|
19558
|
+
...nonEmpty2(value.status) ? { status: value.status } : {},
|
|
19559
|
+
...typeof value.loop_count === "number" ? { loop_count: value.loop_count } : {},
|
|
19560
|
+
...nonEmpty2(value.transcript_path) ? { transcript_path: value.transcript_path } : {}
|
|
19561
|
+
} : {}
|
|
19347
19562
|
}
|
|
19348
19563
|
};
|
|
19349
19564
|
}, protocolEvent2 = (input) => {
|
|
@@ -19376,6 +19591,49 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
19376
19591
|
};
|
|
19377
19592
|
case "preCompact":
|
|
19378
19593
|
return { kind: "compact.pre", trigger: "auto" };
|
|
19594
|
+
case "stop":
|
|
19595
|
+
return {
|
|
19596
|
+
kind: "stop",
|
|
19597
|
+
lastMessage: (input.status ?? "completed") === "completed" ? lastAssistantText(input.transcript_path) : null,
|
|
19598
|
+
stopHookActive: (input.loop_count ?? 0) > 0
|
|
19599
|
+
};
|
|
19600
|
+
}
|
|
19601
|
+
}, TRANSCRIPT_TAIL_BYTES, lastAssistantText = (file) => {
|
|
19602
|
+
if (!file || !path21.isAbsolute(file))
|
|
19603
|
+
return null;
|
|
19604
|
+
try {
|
|
19605
|
+
const size = fs16.statSync(file).size;
|
|
19606
|
+
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
19607
|
+
const buffer = Buffer.alloc(size - start);
|
|
19608
|
+
const fd = fs16.openSync(file, "r");
|
|
19609
|
+
try {
|
|
19610
|
+
fs16.readSync(fd, buffer, 0, buffer.length, start);
|
|
19611
|
+
} finally {
|
|
19612
|
+
fs16.closeSync(fd);
|
|
19613
|
+
}
|
|
19614
|
+
const lines = buffer.toString("utf8").split(`
|
|
19615
|
+
`);
|
|
19616
|
+
for (let index = lines.length - 1;index >= 0; index--) {
|
|
19617
|
+
let entry;
|
|
19618
|
+
try {
|
|
19619
|
+
entry = JSON.parse(lines[index]);
|
|
19620
|
+
} catch {
|
|
19621
|
+
continue;
|
|
19622
|
+
}
|
|
19623
|
+
if (!isRecord4(entry) || entry.role !== "assistant")
|
|
19624
|
+
continue;
|
|
19625
|
+
const content = isRecord4(entry.message) ? entry.message.content : entry.content;
|
|
19626
|
+
if (typeof content === "string")
|
|
19627
|
+
return content;
|
|
19628
|
+
if (!Array.isArray(content))
|
|
19629
|
+
return null;
|
|
19630
|
+
const text = content.filter((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
|
|
19631
|
+
`);
|
|
19632
|
+
return text || null;
|
|
19633
|
+
}
|
|
19634
|
+
return null;
|
|
19635
|
+
} catch {
|
|
19636
|
+
return null;
|
|
19379
19637
|
}
|
|
19380
19638
|
}, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path21.sep) ? root : `${root}${path21.sep}`), hookCwd = (input) => {
|
|
19381
19639
|
const cwd = existingDirectory(input.cwd);
|
|
@@ -19401,6 +19659,8 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
19401
19659
|
return { json: { additional_context: decision.text }, exitCode: 0 };
|
|
19402
19660
|
if (decision.kind === "notice")
|
|
19403
19661
|
return { json: { user_message: decision.userMessage }, exitCode: 0 };
|
|
19662
|
+
if (decision.kind === "continue" && native === "stop")
|
|
19663
|
+
return { json: { followup_message: decision.reason }, exitCode: 0 };
|
|
19404
19664
|
return {
|
|
19405
19665
|
json: native !== null && BLOCKING2.has(native) ? { permission: "allow" } : {},
|
|
19406
19666
|
exitCode: 0
|
|
@@ -19427,7 +19687,7 @@ var init_cursor = __esm(() => {
|
|
|
19427
19687
|
"subagent.stop": { support: "partial", native: "subagentStop" },
|
|
19428
19688
|
"prompt.submit": undocumented,
|
|
19429
19689
|
"compact.pre": { support: "partial", native: "preCompact" },
|
|
19430
|
-
stop:
|
|
19690
|
+
stop: { support: "native", native: "stop" }
|
|
19431
19691
|
},
|
|
19432
19692
|
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: true },
|
|
19433
19693
|
context: {
|
|
@@ -19451,7 +19711,7 @@ var init_cursor = __esm(() => {
|
|
|
19451
19711
|
postToolObserve: "undocumented"
|
|
19452
19712
|
},
|
|
19453
19713
|
interaction: { questions: "none", writeBoundary: "partial" },
|
|
19454
|
-
stopControl: "
|
|
19714
|
+
stopControl: "native",
|
|
19455
19715
|
shellAvailable: "native",
|
|
19456
19716
|
perEventCost: "low",
|
|
19457
19717
|
capabilities: [
|
|
@@ -19518,8 +19778,10 @@ var init_cursor = __esm(() => {
|
|
|
19518
19778
|
beforeShellExecution: "shell.pre",
|
|
19519
19779
|
subagentStart: "subagent.start",
|
|
19520
19780
|
subagentStop: "subagent.stop",
|
|
19521
|
-
preCompact: "compact.pre"
|
|
19781
|
+
preCompact: "compact.pre",
|
|
19782
|
+
stop: "stop"
|
|
19522
19783
|
};
|
|
19784
|
+
TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
19523
19785
|
BLOCKING2 = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
19524
19786
|
cursorAdapter = {
|
|
19525
19787
|
descriptor: CURSOR_DESCRIPTOR,
|
|
@@ -19599,6 +19861,7 @@ var init_hooks = __esm(async () => {
|
|
|
19599
19861
|
init_context();
|
|
19600
19862
|
init_policy();
|
|
19601
19863
|
init_write_gate();
|
|
19864
|
+
init_stop();
|
|
19602
19865
|
init_raw_git();
|
|
19603
19866
|
init_skill_nudge();
|
|
19604
19867
|
init_fields();
|