@brainervirus/workit-opencode 8.7.0 → 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/dist/plugin.js +339 -85
- package/package.json +2 -2
package/dist/plugin.js
CHANGED
|
@@ -19463,12 +19463,48 @@ function writeGate(cwd, paths) {
|
|
|
19463
19463
|
return NONE2;
|
|
19464
19464
|
}
|
|
19465
19465
|
}
|
|
19466
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
19467
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
19468
|
+
|
|
19466
19469
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
19467
19470
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
19471
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
19472
|
+
import fs16 from "node:fs";
|
|
19473
|
+
import os4 from "node:os";
|
|
19474
|
+
import path23 from "node:path";
|
|
19475
|
+
|
|
19476
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
19468
19477
|
import { createHash as createHash11 } from "node:crypto";
|
|
19469
19478
|
import fs15 from "node:fs";
|
|
19470
|
-
import os4 from "node:os";
|
|
19471
19479
|
import path22 from "node:path";
|
|
19480
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
19481
|
+
let dir = store;
|
|
19482
|
+
if (dir === undefined) {
|
|
19483
|
+
const location = resolveStore(input.cwd);
|
|
19484
|
+
if (location instanceof Error || !location.shared)
|
|
19485
|
+
return null;
|
|
19486
|
+
dir = location.dir;
|
|
19487
|
+
}
|
|
19488
|
+
const name = createHash11("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
19489
|
+
return path22.join(dir, "hooks", `skills-${name}.json`);
|
|
19490
|
+
};
|
|
19491
|
+
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
19492
|
+
var readSessionMarker = (file) => {
|
|
19493
|
+
try {
|
|
19494
|
+
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
19495
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
19496
|
+
} catch {
|
|
19497
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
19498
|
+
}
|
|
19499
|
+
};
|
|
19500
|
+
var writeSessionMarker = (file, marker) => {
|
|
19501
|
+
try {
|
|
19502
|
+
fs15.mkdirSync(path22.dirname(file), { recursive: true });
|
|
19503
|
+
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
19504
|
+
} catch {}
|
|
19505
|
+
};
|
|
19506
|
+
|
|
19507
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
19472
19508
|
var NONE3 = { kind: "none" };
|
|
19473
19509
|
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
19474
19510
|
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
@@ -19485,12 +19521,12 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
19485
19521
|
"--config-env",
|
|
19486
19522
|
"--super-prefix"
|
|
19487
19523
|
]);
|
|
19488
|
-
var isAbsolute = (value) =>
|
|
19524
|
+
var isAbsolute = (value) => path23.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
19489
19525
|
var joinDir = (base, next) => {
|
|
19490
19526
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
19491
19527
|
return;
|
|
19492
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
19493
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
19528
|
+
const expanded = next === "~" || next.startsWith("~/") ? path23.join(os4.homedir(), next.slice(1)) : next;
|
|
19529
|
+
return base === null || isAbsolute(expanded) ? expanded : path23.join(base, expanded);
|
|
19494
19530
|
};
|
|
19495
19531
|
var cdTarget = (words) => {
|
|
19496
19532
|
const args = words.slice(1);
|
|
@@ -19586,6 +19622,37 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
19586
19622
|
}
|
|
19587
19623
|
return found;
|
|
19588
19624
|
}
|
|
19625
|
+
var LAUNCHERS = new Set([
|
|
19626
|
+
"npx",
|
|
19627
|
+
"bunx",
|
|
19628
|
+
"pnpx",
|
|
19629
|
+
"-y",
|
|
19630
|
+
"--yes",
|
|
19631
|
+
"pnpm",
|
|
19632
|
+
"dlx",
|
|
19633
|
+
"exec",
|
|
19634
|
+
"env",
|
|
19635
|
+
"sudo",
|
|
19636
|
+
"command"
|
|
19637
|
+
]);
|
|
19638
|
+
var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
19639
|
+
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
19640
|
+
const found = [];
|
|
19641
|
+
for (const { words } of segmentsOf(command, dialect)) {
|
|
19642
|
+
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
19643
|
+
if (at < 0)
|
|
19644
|
+
continue;
|
|
19645
|
+
if (WORKIT_BIN.test(words[at])) {
|
|
19646
|
+
found.push(words.slice(at + 1));
|
|
19647
|
+
continue;
|
|
19648
|
+
}
|
|
19649
|
+
const shell = SHELLS.has(commandName(words[at]));
|
|
19650
|
+
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
19651
|
+
if (depth < 3 && script !== undefined)
|
|
19652
|
+
found.push(...workitInvocations(script, "posix", depth + 1));
|
|
19653
|
+
}
|
|
19654
|
+
return found;
|
|
19655
|
+
}
|
|
19589
19656
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
19590
19657
|
var pushAction = (args) => {
|
|
19591
19658
|
let force = false;
|
|
@@ -19712,7 +19779,7 @@ var repoAt = (dir) => {
|
|
|
19712
19779
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
19713
19780
|
};
|
|
19714
19781
|
var isWorkitWorkspace = (repo) => {
|
|
19715
|
-
if (
|
|
19782
|
+
if (fs16.existsSync(repo.location.dir))
|
|
19716
19783
|
return true;
|
|
19717
19784
|
try {
|
|
19718
19785
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -19743,17 +19810,17 @@ var headSha2 = (repo) => {
|
|
|
19743
19810
|
if (!git || git.reftable)
|
|
19744
19811
|
return null;
|
|
19745
19812
|
try {
|
|
19746
|
-
const head =
|
|
19813
|
+
const head = fs16.readFileSync(path23.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
19747
19814
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
19748
19815
|
return head;
|
|
19749
19816
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
19750
19817
|
if (!ref)
|
|
19751
19818
|
return null;
|
|
19752
|
-
const common =
|
|
19819
|
+
const common = path23.dirname(repo.location.dir);
|
|
19753
19820
|
try {
|
|
19754
|
-
return
|
|
19821
|
+
return fs16.readFileSync(path23.join(common, ref), "utf8").trim() || null;
|
|
19755
19822
|
} catch {}
|
|
19756
|
-
const packed =
|
|
19823
|
+
const packed = fs16.readFileSync(path23.join(common, "packed-refs"), "utf8");
|
|
19757
19824
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
19758
19825
|
} catch {
|
|
19759
19826
|
return null;
|
|
@@ -19790,7 +19857,7 @@ var actionsIn = (cwd, command, dialect) => {
|
|
|
19790
19857
|
const action = rawAction(invocation);
|
|
19791
19858
|
if (!action)
|
|
19792
19859
|
continue;
|
|
19793
|
-
const dir =
|
|
19860
|
+
const dir = path23.resolve(cwd, invocation.dir ?? ".");
|
|
19794
19861
|
if (!repos.has(dir)) {
|
|
19795
19862
|
const repo = repoAt(dir);
|
|
19796
19863
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -19808,12 +19875,31 @@ var inWorkitWorkspace = (dir) => {
|
|
|
19808
19875
|
var rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read");
|
|
19809
19876
|
var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
|
|
19810
19877
|
var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
|
|
19811
|
-
|
|
19878
|
+
var nudgeKind = (action) => action.kind === "commit" && action.amend ? "commit-amend" : action.kind === "push" && action.blindForce ? "push-force" : action.kind;
|
|
19879
|
+
var unseenNudges = (input, nudges, store) => {
|
|
19880
|
+
const session = input.session.id;
|
|
19881
|
+
const file = session ? sessionMarkerFile(input, session, store) : null;
|
|
19882
|
+
if (!file)
|
|
19883
|
+
return [...nudges.values()];
|
|
19884
|
+
const marker = readSessionMarker(file);
|
|
19885
|
+
const agent = input.session.agentId ? `${input.session.agentId}:` : "";
|
|
19886
|
+
const fresh = [...nudges.keys()].filter((kind) => !marker.raw.includes(agent + kind));
|
|
19887
|
+
if (fresh.length)
|
|
19888
|
+
writeSessionMarker(file, {
|
|
19889
|
+
...marker,
|
|
19890
|
+
raw: [...marker.raw, ...fresh.map((kind) => agent + kind)]
|
|
19891
|
+
});
|
|
19892
|
+
return fresh.map((kind) => nudges.get(kind));
|
|
19893
|
+
};
|
|
19894
|
+
function rawGitPre(input, command, options = {}) {
|
|
19812
19895
|
try {
|
|
19813
19896
|
if (!mentionsRawTool(command))
|
|
19814
19897
|
return NONE3;
|
|
19815
|
-
const
|
|
19816
|
-
|
|
19898
|
+
const dialect = dialectOf(input);
|
|
19899
|
+
const nudges = new Map;
|
|
19900
|
+
let store = null;
|
|
19901
|
+
for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
|
|
19902
|
+
store ??= repo.location.dir;
|
|
19817
19903
|
if (action.kind === "merge")
|
|
19818
19904
|
return deny(refusal(action, null));
|
|
19819
19905
|
if (action.kind === "push")
|
|
@@ -19823,10 +19909,15 @@ function rawGitPre(input, command) {
|
|
|
19823
19909
|
return deny(refusal(action, branch));
|
|
19824
19910
|
}
|
|
19825
19911
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
19826
|
-
if (nudge && !nudges.
|
|
19827
|
-
nudges.
|
|
19912
|
+
if (nudge && !nudges.has(nudgeKind(action)))
|
|
19913
|
+
nudges.set(nudgeKind(action), nudge);
|
|
19828
19914
|
}
|
|
19829
|
-
|
|
19915
|
+
if (options.nudge === false || !store || nudges.size === 0)
|
|
19916
|
+
return NONE3;
|
|
19917
|
+
if (workitInvocations(command, dialect).length > 0)
|
|
19918
|
+
return NONE3;
|
|
19919
|
+
const shown = unseenNudges(input, nudges, store);
|
|
19920
|
+
return shown.length ? { kind: "context", text: shown.join(`
|
|
19830
19921
|
`) } : NONE3;
|
|
19831
19922
|
} catch {
|
|
19832
19923
|
return NONE3;
|
|
@@ -19834,8 +19925,8 @@ function rawGitPre(input, command) {
|
|
|
19834
19925
|
}
|
|
19835
19926
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
19836
19927
|
var MARKER_TTL_SECONDS = 3600;
|
|
19837
|
-
var markerFile = (repo, session, key) =>
|
|
19838
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
19928
|
+
var markerFile = (repo, session, key) => path23.join(repo.location.dir, "hooks", `commit-${createHash12("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
19929
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash12("sha256").update(command).digest("hex")}`;
|
|
19839
19930
|
function noteRawCommit(input, command, key) {
|
|
19840
19931
|
try {
|
|
19841
19932
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -19845,8 +19936,8 @@ function noteRawCommit(input, command, key) {
|
|
|
19845
19936
|
if (action.kind !== "commit")
|
|
19846
19937
|
continue;
|
|
19847
19938
|
const file = markerFile(repo, input.session.id, key);
|
|
19848
|
-
|
|
19849
|
-
|
|
19939
|
+
fs16.mkdirSync(path23.dirname(file), { recursive: true });
|
|
19940
|
+
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
19850
19941
|
`);
|
|
19851
19942
|
}
|
|
19852
19943
|
} catch {}
|
|
@@ -19869,11 +19960,11 @@ var headCommit = (dir) => {
|
|
|
19869
19960
|
var settle2 = (input, repo, file) => {
|
|
19870
19961
|
let marker;
|
|
19871
19962
|
try {
|
|
19872
|
-
marker = JSON.parse(
|
|
19963
|
+
marker = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
19873
19964
|
} catch {
|
|
19874
19965
|
return;
|
|
19875
19966
|
}
|
|
19876
|
-
|
|
19967
|
+
fs16.rmSync(file, { force: true });
|
|
19877
19968
|
const session = input.session.id;
|
|
19878
19969
|
const now = Date.now() / 1000;
|
|
19879
19970
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -19914,11 +20005,161 @@ function rawGitPost(input, command, key) {
|
|
|
19914
20005
|
settle2(input, repo, markerFile(repo, input.session.id, key));
|
|
19915
20006
|
} catch {}
|
|
19916
20007
|
}
|
|
19917
|
-
// packages/workit-core/src/hooks/skill-nudge.ts
|
|
19918
|
-
import { createHash as createHash12 } from "node:crypto";
|
|
19919
|
-
import fs16 from "node:fs";
|
|
19920
|
-
import path23 from "node:path";
|
|
19921
20008
|
|
|
20009
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
20010
|
+
var NONE4 = { kind: "none" };
|
|
20011
|
+
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20012
|
+
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;
|
|
20013
|
+
var YES_NO = /[[(]\s*y(?:es)?\s*\/\s*n(?:o)?\s*[\])]/i;
|
|
20014
|
+
var LIST_ITEM = /^\s*(?:[-*+•]|\d+[.)]|[a-z][.)])\s+/i;
|
|
20015
|
+
var asksUser = (message) => {
|
|
20016
|
+
if (!message)
|
|
20017
|
+
return false;
|
|
20018
|
+
const paragraphs = message.replace(/```[\s\S]*?(?:```|$)/g, `
|
|
20019
|
+
|
|
20020
|
+
`).split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
20021
|
+
while (paragraphs.length > 1 && paragraphs.at(-1).split(`
|
|
20022
|
+
`).every((line) => LIST_ITEM.test(line)))
|
|
20023
|
+
paragraphs.pop();
|
|
20024
|
+
const tail = paragraphs.slice(-2).join(`
|
|
20025
|
+
`);
|
|
20026
|
+
return QUESTION.test(tail) || OFFER.test(tail) || YES_NO.test(tail);
|
|
20027
|
+
};
|
|
20028
|
+
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
20029
|
+
var gitList = (cwd, args) => {
|
|
20030
|
+
const run = spawnSync16("git", args, {
|
|
20031
|
+
cwd,
|
|
20032
|
+
encoding: "utf8",
|
|
20033
|
+
timeout: 3000,
|
|
20034
|
+
windowsHide: true,
|
|
20035
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
|
|
20036
|
+
});
|
|
20037
|
+
return run.status === 0 ? run.stdout.split(`
|
|
20038
|
+
`).filter(Boolean) : null;
|
|
20039
|
+
};
|
|
20040
|
+
var gitLine = (cwd, args) => gitList(cwd, args)?.[0] ?? null;
|
|
20041
|
+
var sessionOf = (row) => (typeof row.session === "string" ? row.session : null) ?? row.actor.session;
|
|
20042
|
+
var prState = (row) => typeof row.state === "string" ? row.state : "open";
|
|
20043
|
+
var ended = (row) => row.observer === "workit_cli" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) !== "open");
|
|
20044
|
+
var openPr = (rows) => {
|
|
20045
|
+
const seen = rows.findLast((row) => (row.type === "pr.created" || row.type === "pr.status") && row.observer === "workit_cli" && typeof row.pr === "number");
|
|
20046
|
+
if (!seen || ended(seen))
|
|
20047
|
+
return null;
|
|
20048
|
+
const closed = rows.some((row) => ended(row) && row.pr === seen.pr && row.seq > seen.seq);
|
|
20049
|
+
return closed ? null : seen.pr;
|
|
20050
|
+
};
|
|
20051
|
+
var mergedHeads = (rows) => [
|
|
20052
|
+
...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))
|
|
20053
|
+
].slice(-3);
|
|
20054
|
+
var localCommit = (cwd, sha) => /^[0-9a-f]{7,64}$/.test(sha) && gitList(cwd, ["cat-file", "-e", `${sha}^{commit}`]) !== null;
|
|
20055
|
+
var upstreamGone = (cwd, branch) => gitLine(cwd, ["for-each-ref", "--format=%(upstream:track)", `refs/heads/${branch}`]) === "[gone]";
|
|
20056
|
+
var landed = (cwd, branch, rows, head, mine) => {
|
|
20057
|
+
if (!head)
|
|
20058
|
+
return false;
|
|
20059
|
+
const merged = mergedHeads(rows);
|
|
20060
|
+
if (merged.length === 0)
|
|
20061
|
+
return upstreamGone(cwd, branch);
|
|
20062
|
+
if (merged.includes(head))
|
|
20063
|
+
return true;
|
|
20064
|
+
if (mine.size === 0)
|
|
20065
|
+
return false;
|
|
20066
|
+
return merged.some((at) => {
|
|
20067
|
+
const after = gitList(cwd, ["rev-list", `--max-count=${UNPUSHED_SCAN}`, "HEAD", "--not", at]);
|
|
20068
|
+
return after !== null && !after.some((sha) => mine.has(sha));
|
|
20069
|
+
});
|
|
20070
|
+
};
|
|
20071
|
+
var UNPUSHED_SCAN = 500;
|
|
20072
|
+
var unpushed = (cwd, branch, endpoint, mine, merged) => {
|
|
20073
|
+
const remote = pushRemoteName(cwd, branch);
|
|
20074
|
+
if (!remote || mine.size === 0)
|
|
20075
|
+
return null;
|
|
20076
|
+
const listed = gitList(cwd, [
|
|
20077
|
+
"rev-list",
|
|
20078
|
+
`--max-count=${UNPUSHED_SCAN}`,
|
|
20079
|
+
"HEAD",
|
|
20080
|
+
"--not",
|
|
20081
|
+
`--remotes=${remote}`,
|
|
20082
|
+
...merged.filter((sha) => localCommit(cwd, sha))
|
|
20083
|
+
]);
|
|
20084
|
+
const count = (listed ?? []).filter((sha) => mine.has(sha)).length;
|
|
20085
|
+
if (count === 0)
|
|
20086
|
+
return null;
|
|
20087
|
+
return {
|
|
20088
|
+
kind: "unpushed",
|
|
20089
|
+
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`)" : ""}.`
|
|
20090
|
+
};
|
|
20091
|
+
};
|
|
20092
|
+
var redChecks = (rows, pr, head, endpoint) => {
|
|
20093
|
+
const status = rows.findLast((row) => row.type === "pr.status" && row.observer === "workit_cli" && row.pr === pr);
|
|
20094
|
+
const state = status && status.head === head && prState(status) === "open" ? status.checks : null;
|
|
20095
|
+
if (state !== "failing" && state !== "pending")
|
|
20096
|
+
return null;
|
|
20097
|
+
return {
|
|
20098
|
+
kind: "checks",
|
|
20099
|
+
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)" : ""}.`
|
|
20100
|
+
};
|
|
20101
|
+
};
|
|
20102
|
+
var unverified = (cwd, branch, all, descriptor) => {
|
|
20103
|
+
const check = checkVerdicts(cwd, branch, all);
|
|
20104
|
+
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
20105
|
+
return null;
|
|
20106
|
+
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
20107
|
+
const verifier = `${descriptor.subagents.agentPrefix}verifier`;
|
|
20108
|
+
return {
|
|
20109
|
+
kind: "verdict",
|
|
20110
|
+
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.`
|
|
20111
|
+
};
|
|
20112
|
+
};
|
|
20113
|
+
function stopObligation(input, descriptor) {
|
|
20114
|
+
try {
|
|
20115
|
+
const session = input.session.id;
|
|
20116
|
+
if (!session || input.session.agentId || !inWorkitWorkspace(input.cwd))
|
|
20117
|
+
return null;
|
|
20118
|
+
const branch = currentBranch(input.cwd);
|
|
20119
|
+
if (!branch || guardedBranch(input.cwd, branch))
|
|
20120
|
+
return null;
|
|
20121
|
+
const ledger = readLedger(input.cwd);
|
|
20122
|
+
if (!ledger.ok)
|
|
20123
|
+
return null;
|
|
20124
|
+
const all = ledger.value.rows;
|
|
20125
|
+
const rows = all.filter((row) => row.branch === branch);
|
|
20126
|
+
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
20127
|
+
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"));
|
|
20128
|
+
const delivered = mine.some((row) => row.observer === "workit_cli" && (row.type === "pr.created" || row.type === "delivery.verified"));
|
|
20129
|
+
if (commits.size === 0 && !delivered)
|
|
20130
|
+
return null;
|
|
20131
|
+
const head = gitLine(input.cwd, ["rev-parse", "HEAD"]);
|
|
20132
|
+
if (landed(input.cwd, branch, rows, head, commits))
|
|
20133
|
+
return null;
|
|
20134
|
+
const endpoint = resolveAutonomy(input.cwd).effectiveEndpoint;
|
|
20135
|
+
if (endpoint !== "commit") {
|
|
20136
|
+
const left = unpushed(input.cwd, branch, endpoint, commits, mergedHeads(rows));
|
|
20137
|
+
if (left)
|
|
20138
|
+
return left;
|
|
20139
|
+
}
|
|
20140
|
+
const pr = openPr(rows);
|
|
20141
|
+
if ((endpoint === "green" || endpoint === "merged") && pr !== null && head) {
|
|
20142
|
+
const left = redChecks(rows, pr, head, endpoint);
|
|
20143
|
+
if (left)
|
|
20144
|
+
return left;
|
|
20145
|
+
}
|
|
20146
|
+
return delivered ? unverified(input.cwd, branch, all, descriptor) : null;
|
|
20147
|
+
} catch {
|
|
20148
|
+
return null;
|
|
20149
|
+
}
|
|
20150
|
+
}
|
|
20151
|
+
function stopDecision(input, descriptor) {
|
|
20152
|
+
const event = input.event;
|
|
20153
|
+
if (event.kind !== "stop" || event.stopHookActive)
|
|
20154
|
+
return NONE4;
|
|
20155
|
+
if (!event.lastMessage?.trim() || asksUser(event.lastMessage))
|
|
20156
|
+
return NONE4;
|
|
20157
|
+
const left = stopObligation(input, descriptor);
|
|
20158
|
+
return left ? {
|
|
20159
|
+
kind: "continue",
|
|
20160
|
+
reason: `Workit: before stopping, ${left.reason} If this cannot be done now, say so to the user and stop.`
|
|
20161
|
+
} : NONE4;
|
|
20162
|
+
}
|
|
19922
20163
|
// packages/workit-core/src/core/skill-manifests.ts
|
|
19923
20164
|
var WORKIT_METHOD_SKILLS = [
|
|
19924
20165
|
"workit-shape",
|
|
@@ -20027,27 +20268,6 @@ var promptTrigger = (prompt) => {
|
|
|
20027
20268
|
}
|
|
20028
20269
|
return null;
|
|
20029
20270
|
};
|
|
20030
|
-
var markerFile2 = (input, session) => {
|
|
20031
|
-
const location = resolveStore(input.cwd);
|
|
20032
|
-
if (location instanceof Error || !location.shared)
|
|
20033
|
-
return null;
|
|
20034
|
-
const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
20035
|
-
return path23.join(location.dir, "hooks", `skills-${name}.json`);
|
|
20036
|
-
};
|
|
20037
|
-
var readMarker = (file) => {
|
|
20038
|
-
try {
|
|
20039
|
-
const value = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
20040
|
-
return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
|
|
20041
|
-
} catch {
|
|
20042
|
-
return { nudged: [], loaded: [] };
|
|
20043
|
-
}
|
|
20044
|
-
};
|
|
20045
|
-
var writeMarker = (file, marker) => {
|
|
20046
|
-
try {
|
|
20047
|
-
fs16.mkdirSync(path23.dirname(file), { recursive: true });
|
|
20048
|
-
fs16.writeFileSync(file, JSON.stringify(marker));
|
|
20049
|
-
} catch {}
|
|
20050
|
-
};
|
|
20051
20271
|
var loadedSkills = (cwd, session) => {
|
|
20052
20272
|
const ledger = readLedger(cwd);
|
|
20053
20273
|
const loaded = new Set;
|
|
@@ -20079,13 +20299,13 @@ function recordSkillLoad(input, name, via) {
|
|
|
20079
20299
|
return;
|
|
20080
20300
|
if (!inWorkitWorkspace(input.cwd))
|
|
20081
20301
|
return;
|
|
20082
|
-
const file =
|
|
20302
|
+
const file = sessionMarkerFile(input, session);
|
|
20083
20303
|
if (!file)
|
|
20084
20304
|
return;
|
|
20085
|
-
const marker =
|
|
20305
|
+
const marker = readSessionMarker(file);
|
|
20086
20306
|
if (marker.loaded.includes(skill))
|
|
20087
20307
|
return;
|
|
20088
|
-
|
|
20308
|
+
writeSessionMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
|
|
20089
20309
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
20090
20310
|
return;
|
|
20091
20311
|
appendHookObserved(input.cwd, {
|
|
@@ -20097,23 +20317,24 @@ function recordSkillLoad(input, name, via) {
|
|
|
20097
20317
|
} catch {}
|
|
20098
20318
|
}
|
|
20099
20319
|
var nudgeOnce = (input, session, skill, line) => {
|
|
20100
|
-
const file =
|
|
20320
|
+
const file = sessionMarkerFile(input, session);
|
|
20101
20321
|
if (!file)
|
|
20102
20322
|
return null;
|
|
20103
|
-
const marker =
|
|
20323
|
+
const marker = readSessionMarker(file);
|
|
20104
20324
|
if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
|
|
20105
20325
|
return null;
|
|
20106
20326
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
20107
20327
|
return null;
|
|
20108
|
-
|
|
20328
|
+
writeSessionMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
|
|
20109
20329
|
return line;
|
|
20110
20330
|
};
|
|
20111
20331
|
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.`;
|
|
20112
20332
|
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;
|
|
20333
|
+
var relayedMessage = (prompt) => /^\s*(?:Another Claude session sent a message\b|<agent-message\s+from=|\[SYSTEM NOTIFICATION\b|<task-notification>)/.test(prompt);
|
|
20113
20334
|
function promptNudge(input, prompt) {
|
|
20114
20335
|
try {
|
|
20115
20336
|
const session = input.session.id;
|
|
20116
|
-
if (!prompt || !session || input.session.agentId)
|
|
20337
|
+
if (!prompt || !session || input.session.agentId || relayedMessage(prompt))
|
|
20117
20338
|
return null;
|
|
20118
20339
|
const loads = promptLoads(prompt);
|
|
20119
20340
|
if (loads) {
|
|
@@ -20129,32 +20350,7 @@ function promptNudge(input, prompt) {
|
|
|
20129
20350
|
}
|
|
20130
20351
|
}
|
|
20131
20352
|
var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
|
|
20132
|
-
var
|
|
20133
|
-
var LAUNCHERS = new Set([
|
|
20134
|
-
"npx",
|
|
20135
|
-
"bunx",
|
|
20136
|
-
"pnpx",
|
|
20137
|
-
"-y",
|
|
20138
|
-
"--yes",
|
|
20139
|
-
"pnpm",
|
|
20140
|
-
"dlx",
|
|
20141
|
-
"exec",
|
|
20142
|
-
"env",
|
|
20143
|
-
"sudo",
|
|
20144
|
-
"command"
|
|
20145
|
-
]);
|
|
20146
|
-
var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
|
|
20147
|
-
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
20148
|
-
if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
|
|
20149
|
-
const args = words.slice(at + 1);
|
|
20150
|
-
if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
|
|
20151
|
-
return false;
|
|
20152
|
-
return WORKIT_DELIVERY.test(args.join(" "));
|
|
20153
|
-
}
|
|
20154
|
-
const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
|
|
20155
|
-
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
20156
|
-
return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
|
|
20157
|
-
});
|
|
20353
|
+
var runsWorkitDelivery = (command, dialect) => workitInvocations(command, dialect).some((args) => !args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run") && WORKIT_DELIVERY.test(args.join(" ")));
|
|
20158
20354
|
var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
|
|
20159
20355
|
function shipNudge(input, command) {
|
|
20160
20356
|
try {
|
|
@@ -20181,7 +20377,7 @@ ${line}` };
|
|
|
20181
20377
|
};
|
|
20182
20378
|
var shellNudge = (input, command) => withContextLine(rawGitPre(input, command), shipNudge(input, command));
|
|
20183
20379
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20184
|
-
var
|
|
20380
|
+
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
20185
20381
|
var WRITE_TOOLS = new Set([
|
|
20186
20382
|
"write",
|
|
20187
20383
|
"edit",
|
|
@@ -20219,6 +20415,7 @@ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
|
20219
20415
|
var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
20220
20416
|
|
|
20221
20417
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
20418
|
+
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
20222
20419
|
var BLOCKING2 = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
20223
20420
|
|
|
20224
20421
|
// packages/workit-core/src/hooks/run.ts
|
|
@@ -20241,7 +20438,7 @@ var OPENCODE_DESCRIPTOR = {
|
|
|
20241
20438
|
"subagent.stop": { support: "native", native: 'tool.hook("execute.after") subagent' },
|
|
20242
20439
|
"prompt.submit": { support: "undocumented", native: null },
|
|
20243
20440
|
"compact.pre": { support: "native", native: 'session.hook("compaction")' },
|
|
20244
|
-
stop: { support: "
|
|
20441
|
+
stop: { support: "partial", native: 'event("session.idle")' }
|
|
20245
20442
|
},
|
|
20246
20443
|
shellPolicy: { deny: "native", channel: "effect", failClosed: false },
|
|
20247
20444
|
context: {
|
|
@@ -20409,7 +20606,7 @@ var ensureHygieneFiles = (root, opts) => {
|
|
|
20409
20606
|
// packages/workit-core/src/core/init.ts
|
|
20410
20607
|
import fs17 from "node:fs";
|
|
20411
20608
|
import path27 from "node:path";
|
|
20412
|
-
import { spawnSync as
|
|
20609
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
20413
20610
|
|
|
20414
20611
|
// packages/workit-core/src/core/workspace-branch-policy.ts
|
|
20415
20612
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
@@ -20532,7 +20729,7 @@ var resolveInitProvider = (cwd) => {
|
|
|
20532
20729
|
return env.toLowerCase();
|
|
20533
20730
|
const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
20534
20731
|
try {
|
|
20535
|
-
const out =
|
|
20732
|
+
const out = spawnSync17("git", ["remote", "get-url", "origin"], {
|
|
20536
20733
|
cwd: root,
|
|
20537
20734
|
encoding: "utf8"
|
|
20538
20735
|
});
|
|
@@ -21442,7 +21639,7 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
|
|
|
21442
21639
|
permissionMode: null,
|
|
21443
21640
|
transcriptPath: null,
|
|
21444
21641
|
event: { kind: "shell.pre", command: resource, toolUseId: null }
|
|
21445
|
-
}, resource);
|
|
21642
|
+
}, resource, { nudge: false });
|
|
21446
21643
|
if (raw.kind === "deny")
|
|
21447
21644
|
return deny(raw.reason);
|
|
21448
21645
|
const writes = shellWrites(resource);
|
|
@@ -21547,6 +21744,56 @@ var injectSkillNudge = (root, session, messages, system) => {
|
|
|
21547
21744
|
} catch {}
|
|
21548
21745
|
};
|
|
21549
21746
|
|
|
21747
|
+
// packages/workit-opencode/src/v2/stop.ts
|
|
21748
|
+
var record3 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
21749
|
+
var lastAssistantText = (messages) => {
|
|
21750
|
+
for (const message of messages.toReversed()) {
|
|
21751
|
+
if (record3(message).type !== "assistant")
|
|
21752
|
+
continue;
|
|
21753
|
+
const content = record3(message).content;
|
|
21754
|
+
if (!Array.isArray(content))
|
|
21755
|
+
return null;
|
|
21756
|
+
const text = content.filter((part) => record3(part).type === "text" && typeof record3(part).text === "string").map((part) => record3(part).text).join(`
|
|
21757
|
+
`);
|
|
21758
|
+
return text || null;
|
|
21759
|
+
}
|
|
21760
|
+
return null;
|
|
21761
|
+
};
|
|
21762
|
+
var createStopControl = (root, port) => {
|
|
21763
|
+
const continued = new Set;
|
|
21764
|
+
return {
|
|
21765
|
+
onIdle: async (sessionID) => {
|
|
21766
|
+
try {
|
|
21767
|
+
if (continued.delete(sessionID))
|
|
21768
|
+
return false;
|
|
21769
|
+
const session = await port.getSession(sessionID);
|
|
21770
|
+
if (!session || session.parentID !== undefined)
|
|
21771
|
+
return false;
|
|
21772
|
+
if (session.outcome !== "succeeded")
|
|
21773
|
+
return false;
|
|
21774
|
+
if (!sameWorkspace(root, session.directory))
|
|
21775
|
+
return false;
|
|
21776
|
+
const lastMessage = lastAssistantText(await port.messages(sessionID));
|
|
21777
|
+
const decision = stopDecision({
|
|
21778
|
+
host: "opencode",
|
|
21779
|
+
cwd: root,
|
|
21780
|
+
session: { id: sessionID, agentId: null, agentType: null, parentId: null },
|
|
21781
|
+
permissionMode: null,
|
|
21782
|
+
transcriptPath: null,
|
|
21783
|
+
event: { kind: "stop", lastMessage, stopHookActive: false }
|
|
21784
|
+
}, OPENCODE_DESCRIPTOR);
|
|
21785
|
+
if (decision.kind !== "continue")
|
|
21786
|
+
return false;
|
|
21787
|
+
continued.add(sessionID);
|
|
21788
|
+
await port.resume(sessionID, decision.reason);
|
|
21789
|
+
return true;
|
|
21790
|
+
} catch {
|
|
21791
|
+
return false;
|
|
21792
|
+
}
|
|
21793
|
+
}
|
|
21794
|
+
};
|
|
21795
|
+
};
|
|
21796
|
+
|
|
21550
21797
|
// packages/workit-opencode/src/v2/registry.ts
|
|
21551
21798
|
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
21552
21799
|
import path33 from "node:path";
|
|
@@ -21827,11 +22074,18 @@ var setup = async (ctx) => {
|
|
|
21827
22074
|
});
|
|
21828
22075
|
const registeredSkills = await registerSkills(ctx);
|
|
21829
22076
|
await registerCommands(ctx, registeredSkills);
|
|
22077
|
+
const stopControl = createStopControl(root, {
|
|
22078
|
+
getSession: (sessionID) => sessionFacts(ctx, sessionID),
|
|
22079
|
+
messages: async (sessionID) => await ctx.session.context({ sessionID }),
|
|
22080
|
+
resume: (sessionID, text) => ctx.session.synthetic({ sessionID, text, delivery: "queue", resume: true })
|
|
22081
|
+
});
|
|
21830
22082
|
const subscription = new AbortController;
|
|
21831
22083
|
(async () => {
|
|
21832
22084
|
try {
|
|
21833
22085
|
for await (const event of ctx.event.subscribe({ signal: subscription.signal })) {
|
|
21834
22086
|
await lifecycle.handleEvent(event);
|
|
22087
|
+
if (event.type === "session.idle")
|
|
22088
|
+
stopControl.onIdle(String(event.data.sessionID));
|
|
21835
22089
|
}
|
|
21836
22090
|
} catch {}
|
|
21837
22091
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-opencode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit OpenCode plugin — native tools, adaptive policy, and sixteen method skills",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"typecheck": "tsc --noEmit"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@brainervirus/workit-core": "^8.
|
|
42
|
+
"@brainervirus/workit-core": "^8.8.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@opencode/plugin": "2.0.18"
|