@brainervirus/workit-pi 8.4.0 → 8.5.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/workit.js +432 -63
- package/package.json +1 -1
- package/skills/workit-retro/SKILL.md +2 -2
package/dist/workit.js
CHANGED
|
@@ -18965,6 +18965,8 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
18965
18965
|
};
|
|
18966
18966
|
});
|
|
18967
18967
|
// packages/workit-core/src/hooks/context.ts
|
|
18968
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
18969
|
+
import fs15 from "node:fs";
|
|
18968
18970
|
import path20 from "node:path";
|
|
18969
18971
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
18970
18972
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
@@ -19112,6 +19114,75 @@ var turnContextText = (input, descriptor) => {
|
|
|
19112
19114
|
return null;
|
|
19113
19115
|
}
|
|
19114
19116
|
};
|
|
19117
|
+
var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
19118
|
+
var digestOf = (text) => createHash11("sha256").update(text).digest("hex");
|
|
19119
|
+
var turnMarker = (input) => {
|
|
19120
|
+
if (!input.session.id)
|
|
19121
|
+
return null;
|
|
19122
|
+
try {
|
|
19123
|
+
const location = resolveStore(input.cwd);
|
|
19124
|
+
if (location instanceof Error)
|
|
19125
|
+
return null;
|
|
19126
|
+
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
19127
|
+
return path20.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
19128
|
+
} catch {
|
|
19129
|
+
return null;
|
|
19130
|
+
}
|
|
19131
|
+
};
|
|
19132
|
+
var writeTurnDigest = (file, digest) => {
|
|
19133
|
+
try {
|
|
19134
|
+
fs15.mkdirSync(path20.dirname(file), { recursive: true });
|
|
19135
|
+
fs15.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
19136
|
+
`);
|
|
19137
|
+
} catch {}
|
|
19138
|
+
};
|
|
19139
|
+
var clearTurnDigest = (file) => {
|
|
19140
|
+
try {
|
|
19141
|
+
fs15.rmSync(file, { force: true });
|
|
19142
|
+
} catch {}
|
|
19143
|
+
};
|
|
19144
|
+
var pruneTurnDigests = (dir, now) => {
|
|
19145
|
+
try {
|
|
19146
|
+
for (const name of fs15.readdirSync(dir)) {
|
|
19147
|
+
if (!name.startsWith("turn-"))
|
|
19148
|
+
continue;
|
|
19149
|
+
const file = path20.join(dir, name);
|
|
19150
|
+
try {
|
|
19151
|
+
if (now - fs15.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
19152
|
+
fs15.rmSync(file, { force: true });
|
|
19153
|
+
} catch {}
|
|
19154
|
+
}
|
|
19155
|
+
} catch {}
|
|
19156
|
+
};
|
|
19157
|
+
var changedTurnContext = (input, text) => {
|
|
19158
|
+
const file = turnMarker(input);
|
|
19159
|
+
if (!text) {
|
|
19160
|
+
if (file)
|
|
19161
|
+
clearTurnDigest(file);
|
|
19162
|
+
return null;
|
|
19163
|
+
}
|
|
19164
|
+
if (!file)
|
|
19165
|
+
return text;
|
|
19166
|
+
const digest = digestOf(text);
|
|
19167
|
+
try {
|
|
19168
|
+
const previous = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
19169
|
+
if (previous.digest === digest)
|
|
19170
|
+
return null;
|
|
19171
|
+
} catch {}
|
|
19172
|
+
writeTurnDigest(file, digest);
|
|
19173
|
+
return text;
|
|
19174
|
+
};
|
|
19175
|
+
var seedTurnContext = (input, descriptor) => {
|
|
19176
|
+
const file = turnMarker(input);
|
|
19177
|
+
if (!file)
|
|
19178
|
+
return;
|
|
19179
|
+
pruneTurnDigests(path20.dirname(file), Date.now());
|
|
19180
|
+
const text = turnContextText(input, descriptor);
|
|
19181
|
+
if (text)
|
|
19182
|
+
writeTurnDigest(file, digestOf(text));
|
|
19183
|
+
else
|
|
19184
|
+
clearTurnDigest(file);
|
|
19185
|
+
};
|
|
19115
19186
|
// packages/workit-core/src/hooks/policy.ts
|
|
19116
19187
|
var shellPolicy = (cwd, command) => {
|
|
19117
19188
|
const policy = shellBranchPolicyViolation(cwd, command);
|
|
@@ -19405,7 +19476,7 @@ function writeGate(cwd, paths) {
|
|
|
19405
19476
|
const gate = gateFor(cwd);
|
|
19406
19477
|
if (!gate || gate.blockers.length === 0)
|
|
19407
19478
|
return NONE2;
|
|
19408
|
-
if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
19479
|
+
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
19409
19480
|
return NONE2;
|
|
19410
19481
|
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
19411
19482
|
return {
|
|
@@ -19419,8 +19490,8 @@ function writeGate(cwd, paths) {
|
|
|
19419
19490
|
}
|
|
19420
19491
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
19421
19492
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
19422
|
-
import { createHash as
|
|
19423
|
-
import
|
|
19493
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
19494
|
+
import fs16 from "node:fs";
|
|
19424
19495
|
import os4 from "node:os";
|
|
19425
19496
|
import path22 from "node:path";
|
|
19426
19497
|
var NONE3 = { kind: "none" };
|
|
@@ -19666,7 +19737,7 @@ var repoAt = (dir) => {
|
|
|
19666
19737
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
19667
19738
|
};
|
|
19668
19739
|
var isWorkitWorkspace = (repo) => {
|
|
19669
|
-
if (
|
|
19740
|
+
if (fs16.existsSync(repo.location.dir))
|
|
19670
19741
|
return true;
|
|
19671
19742
|
try {
|
|
19672
19743
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -19697,7 +19768,7 @@ var headSha2 = (repo) => {
|
|
|
19697
19768
|
if (!git || git.reftable)
|
|
19698
19769
|
return null;
|
|
19699
19770
|
try {
|
|
19700
|
-
const head =
|
|
19771
|
+
const head = fs16.readFileSync(path22.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
19701
19772
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
19702
19773
|
return head;
|
|
19703
19774
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
@@ -19705,9 +19776,9 @@ var headSha2 = (repo) => {
|
|
|
19705
19776
|
return null;
|
|
19706
19777
|
const common = path22.dirname(repo.location.dir);
|
|
19707
19778
|
try {
|
|
19708
|
-
return
|
|
19779
|
+
return fs16.readFileSync(path22.join(common, ref), "utf8").trim() || null;
|
|
19709
19780
|
} catch {}
|
|
19710
|
-
const packed =
|
|
19781
|
+
const packed = fs16.readFileSync(path22.join(common, "packed-refs"), "utf8");
|
|
19711
19782
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
19712
19783
|
} catch {
|
|
19713
19784
|
return null;
|
|
@@ -19755,6 +19826,11 @@ var actionsIn = (cwd, command, dialect) => {
|
|
|
19755
19826
|
}
|
|
19756
19827
|
return out;
|
|
19757
19828
|
};
|
|
19829
|
+
var inWorkitWorkspace = (dir) => {
|
|
19830
|
+
const repo = repoAt(dir);
|
|
19831
|
+
return repo !== null && isWorkitWorkspace(repo);
|
|
19832
|
+
};
|
|
19833
|
+
var rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read");
|
|
19758
19834
|
var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
|
|
19759
19835
|
var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
|
|
19760
19836
|
function rawGitPre(input, command) {
|
|
@@ -19784,8 +19860,8 @@ function rawGitPre(input, command) {
|
|
|
19784
19860
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
19785
19861
|
var NEXT_COMMAND = "next";
|
|
19786
19862
|
var MARKER_TTL_SECONDS = 3600;
|
|
19787
|
-
var markerFile = (repo, session, key) => path22.join(repo.location.dir, "hooks", `commit-${
|
|
19788
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
19863
|
+
var markerFile = (repo, session, key) => path22.join(repo.location.dir, "hooks", `commit-${createHash12("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
19864
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash12("sha256").update(command).digest("hex")}`;
|
|
19789
19865
|
function noteRawCommit(input, command, key) {
|
|
19790
19866
|
try {
|
|
19791
19867
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -19795,8 +19871,8 @@ function noteRawCommit(input, command, key) {
|
|
|
19795
19871
|
if (action.kind !== "commit")
|
|
19796
19872
|
continue;
|
|
19797
19873
|
const file = markerFile(repo, input.session.id, key);
|
|
19798
|
-
|
|
19799
|
-
|
|
19874
|
+
fs16.mkdirSync(path22.dirname(file), { recursive: true });
|
|
19875
|
+
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
19800
19876
|
`);
|
|
19801
19877
|
}
|
|
19802
19878
|
} catch {}
|
|
@@ -19819,11 +19895,11 @@ var headCommit = (dir) => {
|
|
|
19819
19895
|
var settle2 = (input, repo, file) => {
|
|
19820
19896
|
let marker;
|
|
19821
19897
|
try {
|
|
19822
|
-
marker = JSON.parse(
|
|
19898
|
+
marker = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
19823
19899
|
} catch {
|
|
19824
19900
|
return;
|
|
19825
19901
|
}
|
|
19826
|
-
|
|
19902
|
+
fs16.rmSync(file, { force: true });
|
|
19827
19903
|
const session = input.session.id;
|
|
19828
19904
|
const now = Date.now() / 1000;
|
|
19829
19905
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -19872,14 +19948,289 @@ function settlePendingCommit(input) {
|
|
|
19872
19948
|
if (!repo)
|
|
19873
19949
|
return;
|
|
19874
19950
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
19875
|
-
if (
|
|
19951
|
+
if (fs16.existsSync(file))
|
|
19876
19952
|
settle2(input, repo, file);
|
|
19877
19953
|
} catch {}
|
|
19878
19954
|
}
|
|
19955
|
+
// packages/workit-core/src/hooks/skill-nudge.ts
|
|
19956
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
19957
|
+
import fs17 from "node:fs";
|
|
19958
|
+
import path23 from "node:path";
|
|
19959
|
+
|
|
19960
|
+
// packages/workit-core/src/core/skill-manifests.ts
|
|
19961
|
+
var WORKIT_METHOD_SKILLS = [
|
|
19962
|
+
"workit-shape",
|
|
19963
|
+
"workit-implement",
|
|
19964
|
+
"workit-review",
|
|
19965
|
+
"workit-debug",
|
|
19966
|
+
"workit-ship",
|
|
19967
|
+
"workit-continue",
|
|
19968
|
+
"workit-bdd",
|
|
19969
|
+
"workit-test-audit",
|
|
19970
|
+
"workit-deslop",
|
|
19971
|
+
"workit-fanout",
|
|
19972
|
+
"workit-verify-app",
|
|
19973
|
+
"workit-retro",
|
|
19974
|
+
"workit-architecture"
|
|
19975
|
+
];
|
|
19976
|
+
var WORKIT_SKILL_ALIASES = {
|
|
19977
|
+
"wk-shape": "workit-shape",
|
|
19978
|
+
"wk-implement": "workit-implement",
|
|
19979
|
+
"wk-review": "workit-review",
|
|
19980
|
+
"wk-debug": "workit-debug",
|
|
19981
|
+
"wk-ship": "workit-ship",
|
|
19982
|
+
"wk-continue": "workit-continue",
|
|
19983
|
+
"wk-bdd": "workit-bdd",
|
|
19984
|
+
"wk-test-audit": "workit-test-audit",
|
|
19985
|
+
"wk-deslop": "workit-deslop",
|
|
19986
|
+
"wk-fanout": "workit-fanout",
|
|
19987
|
+
"wk-verify-app": "workit-verify-app",
|
|
19988
|
+
"wk-retro": "workit-retro",
|
|
19989
|
+
"wk-architecture": "workit-architecture"
|
|
19990
|
+
};
|
|
19991
|
+
var skillLoadWording = (host, skill) => {
|
|
19992
|
+
switch (host) {
|
|
19993
|
+
case "claude-code":
|
|
19994
|
+
return `call the Skill tool with \`workit:${skill.replace(/^workit-/, "")}\``;
|
|
19995
|
+
case "opencode":
|
|
19996
|
+
return `call the skill tool with \`${skill}\``;
|
|
19997
|
+
default:
|
|
19998
|
+
return `read the \`${skill}\` skill's SKILL.md and follow it`;
|
|
19999
|
+
}
|
|
20000
|
+
};
|
|
20001
|
+
if (false) {}
|
|
20002
|
+
|
|
20003
|
+
// packages/workit-core/src/hooks/skill-nudge.ts
|
|
20004
|
+
var USER_INVOKED = new Set(["workit-retro", "workit-architecture"]);
|
|
20005
|
+
var SKILL_HOST = {
|
|
20006
|
+
claude_code: "claude-code",
|
|
20007
|
+
opencode: "opencode",
|
|
20008
|
+
codex_cli: "codex",
|
|
20009
|
+
codex_desktop: "codex",
|
|
20010
|
+
cursor: "cursor",
|
|
20011
|
+
pi: "pi"
|
|
20012
|
+
};
|
|
20013
|
+
var PROMPT_INTENTS = [
|
|
20014
|
+
[
|
|
20015
|
+
"workit-debug",
|
|
20016
|
+
/\b(flaky|regression|(?:is|are|got|looks) broken|debug (?:this|it|the|why)|fix (?:the|this|a) bug)\b/i
|
|
20017
|
+
],
|
|
20018
|
+
[
|
|
20019
|
+
"workit-continue",
|
|
20020
|
+
/\b(pick up where|where (?:were|did) we|resume (?:the|this|my|our|work|where)|hand ?off)\b/i
|
|
20021
|
+
],
|
|
20022
|
+
[
|
|
20023
|
+
"workit-review",
|
|
20024
|
+
/\b(blast radius|code review|review (?:the|this|my|PR|pr|#\d+|branch|diff|changes))\b/i
|
|
20025
|
+
],
|
|
20026
|
+
[
|
|
20027
|
+
"workit-ship",
|
|
20028
|
+
/\b(ship (?:it|this|the)|babysit|open (?:a|the) (?:PR|pr|MR|mr)|merge (?:the|this|my) (?:PR|pr|MR|mr|branch)|(?:get|until|make) CI (?:is )?green)\b/i
|
|
20029
|
+
],
|
|
20030
|
+
["workit-bdd", /\b(BDD|TDD|acceptance criteria|Given\/When\/Then)(?![\w/])/],
|
|
20031
|
+
["workit-test-audit", /\b(test audit|audit the tests|tautolog\w*|weak tests)\b/i],
|
|
20032
|
+
["workit-deslop", /\b(deslop|slop|dead code)\b/i],
|
|
20033
|
+
["workit-fanout", /\b(fan(?: |-)?out|parallelize|parallel agents|swarm)\b/i],
|
|
20034
|
+
["workit-verify-app", /\b(verify the app|smoke test|prove it works)\b/i],
|
|
20035
|
+
["workit-retro", /\b(retro|retrospective)\b(?!\.\w)/i],
|
|
20036
|
+
["workit-architecture", /\b(deepen (?:the )?modules|architecture (?:review|pass|audit))\b/i],
|
|
20037
|
+
[
|
|
20038
|
+
"workit-shape",
|
|
20039
|
+
/\b(brainstorm\w*|grill me|should we|let'?s plan|plan (?:out|how|the|a|for)|(?:write|draft) (?:a|the) spec)\b/i
|
|
20040
|
+
],
|
|
20041
|
+
[
|
|
20042
|
+
"workit-implement",
|
|
20043
|
+
/\b(implement (?:the|this|a|it)|add (?:a|the) feature|build (?:a|an|the) (?:new )?(?:feature|command|verb|page|endpoint|component|cli|tool))\b/i
|
|
20044
|
+
]
|
|
20045
|
+
];
|
|
20046
|
+
var workitSkillOf = (name) => {
|
|
20047
|
+
const bare = name.trim().replace(/^\//, "").toLowerCase();
|
|
20048
|
+
const alias = WORKIT_SKILL_ALIASES[bare];
|
|
20049
|
+
if (alias)
|
|
20050
|
+
return alias;
|
|
20051
|
+
const skill = bare.replace(/^workit:/, "workit-");
|
|
20052
|
+
return WORKIT_METHOD_SKILLS.includes(skill) ? skill : null;
|
|
20053
|
+
};
|
|
20054
|
+
var promptTrigger = (prompt) => {
|
|
20055
|
+
const text = prompt.replace(/```[\s\S]*?```/g, " ").slice(0, 4000);
|
|
20056
|
+
for (const [skill, pattern] of PROMPT_INTENTS) {
|
|
20057
|
+
const match = pattern.exec(text);
|
|
20058
|
+
if (match)
|
|
20059
|
+
return { skill, trigger: match[1] ?? match[0] };
|
|
20060
|
+
}
|
|
20061
|
+
return null;
|
|
20062
|
+
};
|
|
20063
|
+
var markerFile2 = (input, session) => {
|
|
20064
|
+
const location = resolveStore(input.cwd);
|
|
20065
|
+
if (location instanceof Error || !location.shared)
|
|
20066
|
+
return null;
|
|
20067
|
+
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
20068
|
+
return path23.join(location.dir, "hooks", `skills-${name}.json`);
|
|
20069
|
+
};
|
|
20070
|
+
var readMarker = (file) => {
|
|
20071
|
+
try {
|
|
20072
|
+
const value = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
20073
|
+
return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
|
|
20074
|
+
} catch {
|
|
20075
|
+
return { nudged: [], loaded: [] };
|
|
20076
|
+
}
|
|
20077
|
+
};
|
|
20078
|
+
var writeMarker = (file, marker) => {
|
|
20079
|
+
try {
|
|
20080
|
+
fs17.mkdirSync(path23.dirname(file), { recursive: true });
|
|
20081
|
+
fs17.writeFileSync(file, JSON.stringify(marker));
|
|
20082
|
+
} catch {}
|
|
20083
|
+
};
|
|
20084
|
+
var loadedSkills = (cwd, session) => {
|
|
20085
|
+
const ledger = readLedger(cwd);
|
|
20086
|
+
const loaded = new Set;
|
|
20087
|
+
if (!ledger.ok)
|
|
20088
|
+
return loaded;
|
|
20089
|
+
for (const row of ledger.value.rows)
|
|
20090
|
+
if (row.type === "skill.loaded" && row.actor.session === session && typeof row.skill === "string")
|
|
20091
|
+
loaded.add(row.skill);
|
|
20092
|
+
return loaded;
|
|
20093
|
+
};
|
|
20094
|
+
var SKILL_FILE = /(?:^|[\\/])(workit-[a-z-]+)[\\/]SKILL\.md$/;
|
|
20095
|
+
var skillFileIn = (file) => SKILL_FILE.exec(file)?.[1] ?? null;
|
|
20096
|
+
var READERS = new Set([
|
|
20097
|
+
"cat",
|
|
20098
|
+
"sed",
|
|
20099
|
+
"head",
|
|
20100
|
+
"tail",
|
|
20101
|
+
"less",
|
|
20102
|
+
"more",
|
|
20103
|
+
"bat",
|
|
20104
|
+
"nl",
|
|
20105
|
+
"get-content",
|
|
20106
|
+
"gc",
|
|
20107
|
+
"type"
|
|
20108
|
+
]);
|
|
20109
|
+
var skillReadIn = (command, dialect = "posix") => {
|
|
20110
|
+
for (const { words } of segmentsOf(command, dialect)) {
|
|
20111
|
+
if (!READERS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
|
|
20112
|
+
continue;
|
|
20113
|
+
for (const word of words.slice(1)) {
|
|
20114
|
+
const skill = skillFileIn(word);
|
|
20115
|
+
if (skill)
|
|
20116
|
+
return skill;
|
|
20117
|
+
}
|
|
20118
|
+
}
|
|
20119
|
+
return null;
|
|
20120
|
+
};
|
|
20121
|
+
function recordSkillLoad(input, name, via) {
|
|
20122
|
+
try {
|
|
20123
|
+
const skill = workitSkillOf(name);
|
|
20124
|
+
const session = input.session.id;
|
|
20125
|
+
if (!skill || !session)
|
|
20126
|
+
return;
|
|
20127
|
+
if (!inWorkitWorkspace(input.cwd))
|
|
20128
|
+
return;
|
|
20129
|
+
const file = markerFile2(input, session);
|
|
20130
|
+
if (!file)
|
|
20131
|
+
return;
|
|
20132
|
+
const marker = readMarker(file);
|
|
20133
|
+
if (marker.loaded.includes(skill))
|
|
20134
|
+
return;
|
|
20135
|
+
writeMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
|
|
20136
|
+
if (loadedSkills(input.cwd, session).has(skill))
|
|
20137
|
+
return;
|
|
20138
|
+
appendHookObserved(input.cwd, {
|
|
20139
|
+
type: "skill.loaded",
|
|
20140
|
+
actor: { host: input.host, session, agentId: input.session.agentId },
|
|
20141
|
+
skill,
|
|
20142
|
+
via
|
|
20143
|
+
});
|
|
20144
|
+
} catch {}
|
|
20145
|
+
}
|
|
20146
|
+
var nudgeOnce = (input, session, skill, line) => {
|
|
20147
|
+
const file = markerFile2(input, session);
|
|
20148
|
+
if (!file)
|
|
20149
|
+
return null;
|
|
20150
|
+
const marker = readMarker(file);
|
|
20151
|
+
if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
|
|
20152
|
+
return null;
|
|
20153
|
+
if (loadedSkills(input.cwd, session).has(skill))
|
|
20154
|
+
return null;
|
|
20155
|
+
writeMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
|
|
20156
|
+
return line;
|
|
20157
|
+
};
|
|
20158
|
+
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.`;
|
|
20159
|
+
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;
|
|
20160
|
+
function promptNudge(input, prompt) {
|
|
20161
|
+
try {
|
|
20162
|
+
const session = input.session.id;
|
|
20163
|
+
if (!prompt || !session || input.session.agentId)
|
|
20164
|
+
return null;
|
|
20165
|
+
const loads = promptLoads(prompt);
|
|
20166
|
+
if (loads) {
|
|
20167
|
+
recordSkillLoad(input, loads, "slash");
|
|
20168
|
+
return null;
|
|
20169
|
+
}
|
|
20170
|
+
const hit = promptTrigger(prompt);
|
|
20171
|
+
if (!hit || !inWorkitWorkspace(input.cwd))
|
|
20172
|
+
return null;
|
|
20173
|
+
return nudgeOnce(input, session, hit.skill, nudgeText(input, hit.skill, `this request ("${hit.trigger}")`));
|
|
20174
|
+
} catch {
|
|
20175
|
+
return null;
|
|
20176
|
+
}
|
|
20177
|
+
}
|
|
20178
|
+
var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
|
|
20179
|
+
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
20180
|
+
var LAUNCHERS = new Set([
|
|
20181
|
+
"npx",
|
|
20182
|
+
"bunx",
|
|
20183
|
+
"pnpx",
|
|
20184
|
+
"-y",
|
|
20185
|
+
"--yes",
|
|
20186
|
+
"pnpm",
|
|
20187
|
+
"dlx",
|
|
20188
|
+
"exec",
|
|
20189
|
+
"env",
|
|
20190
|
+
"sudo",
|
|
20191
|
+
"command"
|
|
20192
|
+
]);
|
|
20193
|
+
var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
|
|
20194
|
+
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
20195
|
+
if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
|
|
20196
|
+
const args = words.slice(at + 1);
|
|
20197
|
+
if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
|
|
20198
|
+
return false;
|
|
20199
|
+
return WORKIT_DELIVERY.test(args.join(" "));
|
|
20200
|
+
}
|
|
20201
|
+
const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
|
|
20202
|
+
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
20203
|
+
return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
|
|
20204
|
+
});
|
|
20205
|
+
var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
|
|
20206
|
+
function shipNudge(input, command) {
|
|
20207
|
+
try {
|
|
20208
|
+
const session = input.session.id;
|
|
20209
|
+
if (!session || input.session.agentId)
|
|
20210
|
+
return null;
|
|
20211
|
+
const dialect = input.event.kind === "shell.pre" ? input.event.dialect ?? "posix" : "posix";
|
|
20212
|
+
if (!isDeliveryCommand(input.cwd, command, dialect))
|
|
20213
|
+
return null;
|
|
20214
|
+
return nudgeOnce(input, session, "workit-ship", nudgeText(input, "workit-ship", "this delivery step"));
|
|
20215
|
+
} catch {
|
|
20216
|
+
return null;
|
|
20217
|
+
}
|
|
20218
|
+
}
|
|
20219
|
+
var withContextLine = (decision, line) => {
|
|
20220
|
+
if (!line)
|
|
20221
|
+
return decision;
|
|
20222
|
+
if (decision.kind === "none")
|
|
20223
|
+
return { kind: "context", text: line };
|
|
20224
|
+
if (decision.kind === "context")
|
|
20225
|
+
return { kind: "context", text: `${decision.text}
|
|
20226
|
+
${line}` };
|
|
20227
|
+
return decision;
|
|
20228
|
+
};
|
|
20229
|
+
var shellNudge = (input, command) => withContextLine(rawGitPre(input, command), shipNudge(input, command));
|
|
19879
20230
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
19880
20231
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19881
20232
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
19882
|
-
var
|
|
20233
|
+
var SHELLS3 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
19883
20234
|
var WRITE_TOOLS = new Set([
|
|
19884
20235
|
"write",
|
|
19885
20236
|
"edit",
|
|
@@ -19900,30 +20251,45 @@ var WRITE_TOOLS = new Set([
|
|
|
19900
20251
|
"writefile"
|
|
19901
20252
|
]);
|
|
19902
20253
|
var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
|
|
20254
|
+
var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
|
|
19903
20255
|
var writePaths = (input) => {
|
|
19904
20256
|
if (!isRecord4(input))
|
|
19905
20257
|
return [];
|
|
19906
20258
|
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
19907
20259
|
for (const value of Object.values(input))
|
|
19908
20260
|
if (typeof value === "string")
|
|
19909
|
-
for (const match of value.matchAll(
|
|
20261
|
+
for (const match of value.matchAll(PATCH_HEADER))
|
|
19910
20262
|
paths.push(match[1].trim());
|
|
19911
20263
|
return [...new Set(paths)];
|
|
19912
20264
|
};
|
|
19913
20265
|
// packages/workit-core/src/hooks/handle.ts
|
|
19914
20266
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
19915
20267
|
var NONE4 = { kind: "none" };
|
|
20268
|
+
var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
|
|
19916
20269
|
var offered = new Set;
|
|
19917
|
-
var
|
|
19918
|
-
|
|
20270
|
+
var ROLES = {
|
|
20271
|
+
implementer: "implementer",
|
|
20272
|
+
verifier: "judge",
|
|
20273
|
+
reviewer: "judge"
|
|
20274
|
+
};
|
|
20275
|
+
var workitRole = (descriptor, agentType) => {
|
|
20276
|
+
const prefix = descriptor.subagents.agentPrefix;
|
|
20277
|
+
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
20278
|
+
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
20279
|
+
};
|
|
19919
20280
|
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
19920
|
-
var subagentStartText = (
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
19924
|
-
|
|
19925
|
-
return
|
|
19926
|
-
|
|
20281
|
+
var subagentStartText = (session, descriptor, event) => {
|
|
20282
|
+
const role = workitRole(descriptor, event.agentType);
|
|
20283
|
+
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
20284
|
+
if (role === "implementer") {
|
|
20285
|
+
const where = descriptor.subagents.worktreeIsolation === "native" ? "working in its own git worktree: it may edit and commit there" : "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";
|
|
20286
|
+
return `Workit observed ${who} ${where}, 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.`;
|
|
20287
|
+
}
|
|
20288
|
+
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
20289
|
+
if (role !== "judge" || !event.agentId)
|
|
20290
|
+
return readOnly;
|
|
20291
|
+
const own = subagentSession(session, event.agentId);
|
|
20292
|
+
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.`;
|
|
19927
20293
|
};
|
|
19928
20294
|
function handleHook(input, deps) {
|
|
19929
20295
|
const { descriptor } = deps;
|
|
@@ -19939,18 +20305,22 @@ function handleHook(input, deps) {
|
|
|
19939
20305
|
const advisory = usable2(descriptor.events["write.pre"].support) ? null : `Workit cannot gate file writes on ${descriptor.label} (no pre-write hook): before-write requirements (an open product choice, a missing plan) are advisory here, so settle them before editing.`;
|
|
19940
20306
|
const addendum = [deps.addendum, advisory].filter(Boolean).join(`
|
|
19941
20307
|
`) || null;
|
|
19942
|
-
|
|
19943
|
-
|
|
19944
|
-
|
|
19945
|
-
};
|
|
20308
|
+
const text = sessionContextText(input, descriptor, { offer, addendum });
|
|
20309
|
+
if (resendsOnChange(descriptor))
|
|
20310
|
+
seedTurnContext(input, descriptor);
|
|
20311
|
+
return { kind: "context", text };
|
|
19946
20312
|
}
|
|
19947
20313
|
case "context.turn": {
|
|
19948
20314
|
if (!usable2(descriptor.context.perTurn))
|
|
19949
20315
|
return NONE4;
|
|
19950
|
-
const
|
|
19951
|
-
|
|
20316
|
+
const current = turnContextText(input, descriptor);
|
|
20317
|
+
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
20318
|
+
return withContextLine(text ? { kind: "context", text } : NONE4, promptNudge(input, event.prompt));
|
|
19952
20319
|
}
|
|
19953
20320
|
case "shell.pre": {
|
|
20321
|
+
const skillRead = skillReadIn(event.command, event.dialect);
|
|
20322
|
+
if (skillRead)
|
|
20323
|
+
recordSkillLoad(input, skillRead, "read");
|
|
19954
20324
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
19955
20325
|
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE4;
|
|
19956
20326
|
if (policy.kind !== "none")
|
|
@@ -19966,7 +20336,7 @@ function handleHook(input, deps) {
|
|
|
19966
20336
|
if (gate.kind === "deny")
|
|
19967
20337
|
return gate;
|
|
19968
20338
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
19969
|
-
return raw;
|
|
20339
|
+
return withContextLine(raw, shipNudge(input, event.command));
|
|
19970
20340
|
}
|
|
19971
20341
|
case "shell.post":
|
|
19972
20342
|
if (usable2(descriptor.events["shell.post"].support))
|
|
@@ -19977,7 +20347,7 @@ function handleHook(input, deps) {
|
|
|
19977
20347
|
case "subagent.start":
|
|
19978
20348
|
return {
|
|
19979
20349
|
kind: "context",
|
|
19980
|
-
text: subagentStartText(input.
|
|
20350
|
+
text: subagentStartText(input.session.id ?? null, descriptor, event)
|
|
19981
20351
|
};
|
|
19982
20352
|
case "compact.pre":
|
|
19983
20353
|
return {
|
|
@@ -19985,6 +20355,9 @@ function handleHook(input, deps) {
|
|
|
19985
20355
|
userMessage: "Workit context may be stale after compaction; re-run inspection or resume before acting."
|
|
19986
20356
|
};
|
|
19987
20357
|
case "tool.pre":
|
|
20358
|
+
if (event.skill)
|
|
20359
|
+
recordSkillLoad(input, event.skill, "tool");
|
|
20360
|
+
return NONE4;
|
|
19988
20361
|
case "subagent.stop":
|
|
19989
20362
|
case "prompt.submit":
|
|
19990
20363
|
case "stop":
|
|
@@ -20004,7 +20377,7 @@ var CONTEXT_EVENTS = new Set([
|
|
|
20004
20377
|
|
|
20005
20378
|
// packages/workit-core/src/hooks/hosts/codex.ts
|
|
20006
20379
|
var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
20007
|
-
var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
|
|
20380
|
+
var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
20008
20381
|
|
|
20009
20382
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
20010
20383
|
var BLOCKING2 = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
@@ -20034,16 +20407,18 @@ var PI_DESCRIPTOR = {
|
|
|
20034
20407
|
shellPolicy: { deny: "native", channel: "block", failClosed: false },
|
|
20035
20408
|
context: {
|
|
20036
20409
|
sessionStart: "native",
|
|
20037
|
-
perTurn: "
|
|
20410
|
+
perTurn: "partial",
|
|
20038
20411
|
afterCompact: "native",
|
|
20039
|
-
task: "session-bound"
|
|
20412
|
+
task: "session-bound",
|
|
20413
|
+
turnResend: "per-session"
|
|
20040
20414
|
},
|
|
20041
20415
|
subagents: {
|
|
20042
20416
|
identity: "none",
|
|
20043
20417
|
parentBinding: "none",
|
|
20044
20418
|
blockStart: "none",
|
|
20045
20419
|
worktreeIsolation: "none",
|
|
20046
|
-
maxConcurrency: "undocumented"
|
|
20420
|
+
maxConcurrency: "undocumented",
|
|
20421
|
+
agentPrefix: "workit-"
|
|
20047
20422
|
},
|
|
20048
20423
|
provenance: { sessionId: "native", agentIdOnTool: "none", postToolObserve: "native" },
|
|
20049
20424
|
interaction: { questions: "native", writeBoundary: "native" },
|
|
@@ -20120,24 +20495,6 @@ var unfinishedTaskOffer2 = (ctx) => {
|
|
|
20120
20495
|
}
|
|
20121
20496
|
};
|
|
20122
20497
|
|
|
20123
|
-
// packages/workit-core/src/core/skill-manifests.ts
|
|
20124
|
-
var WORKIT_SKILL_ALIASES = {
|
|
20125
|
-
"wk-shape": "workit-shape",
|
|
20126
|
-
"wk-implement": "workit-implement",
|
|
20127
|
-
"wk-review": "workit-review",
|
|
20128
|
-
"wk-debug": "workit-debug",
|
|
20129
|
-
"wk-ship": "workit-ship",
|
|
20130
|
-
"wk-continue": "workit-continue",
|
|
20131
|
-
"wk-bdd": "workit-bdd",
|
|
20132
|
-
"wk-test-audit": "workit-test-audit",
|
|
20133
|
-
"wk-deslop": "workit-deslop",
|
|
20134
|
-
"wk-fanout": "workit-fanout",
|
|
20135
|
-
"wk-verify-app": "workit-verify-app",
|
|
20136
|
-
"wk-retro": "workit-retro",
|
|
20137
|
-
"wk-architecture": "workit-architecture"
|
|
20138
|
-
};
|
|
20139
|
-
if (false) {}
|
|
20140
|
-
|
|
20141
20498
|
// packages/workit-pi/src/tools.ts
|
|
20142
20499
|
var readOnlyActions = new Set(["list", "inspect", "preview", "explain", "export"]);
|
|
20143
20500
|
var output = (result) => ({
|
|
@@ -20227,6 +20584,13 @@ var enforceToolPolicy = (event, ctx) => {
|
|
|
20227
20584
|
const command = event.input?.command;
|
|
20228
20585
|
return typeof command === "string" ? gate({ kind: "shell.pre", command, toolUseId: event.toolCallId ?? null }) : undefined;
|
|
20229
20586
|
}
|
|
20587
|
+
if (event.toolName === "read") {
|
|
20588
|
+
const file = event.input?.path;
|
|
20589
|
+
const skill = typeof file === "string" ? skillFileIn(file) : null;
|
|
20590
|
+
if (skill)
|
|
20591
|
+
recordSkillLoad(hookInput(ctx, { kind: "tool.pre", tool: "read", toolUseId: null }), skill, "read");
|
|
20592
|
+
return;
|
|
20593
|
+
}
|
|
20230
20594
|
if (event.toolName !== "write" && event.toolName !== "edit")
|
|
20231
20595
|
return;
|
|
20232
20596
|
if (!ctx.isProjectTrusted())
|
|
@@ -20253,22 +20617,23 @@ var observeToolResult = (event, ctx) => {
|
|
|
20253
20617
|
toolUseId: event.toolCallId
|
|
20254
20618
|
});
|
|
20255
20619
|
handleHook(post, { descriptor: PI_DESCRIPTOR, addendum: null });
|
|
20256
|
-
const nudge = rawGitPre(post, command);
|
|
20620
|
+
const nudge = process.env.WORKIT_PI_WORKER_SESSION ? rawGitPre(post, command) : shellNudge(post, command);
|
|
20257
20621
|
return nudge.kind === "context" ? { content: [...event.content, { type: "text", text: nudge.text }] } : undefined;
|
|
20258
20622
|
} catch {
|
|
20259
20623
|
return;
|
|
20260
20624
|
}
|
|
20261
20625
|
};
|
|
20626
|
+
var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? null : promptNudge(hookInput(ctx, { kind: "context.turn", prompt }), prompt);
|
|
20262
20627
|
|
|
20263
20628
|
// packages/workit-pi/src/launches.ts
|
|
20264
20629
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
20265
20630
|
import os5 from "node:os";
|
|
20266
|
-
import
|
|
20631
|
+
import path24 from "node:path";
|
|
20267
20632
|
var launchDir = (root) => {
|
|
20268
20633
|
const location = resolveStore(root);
|
|
20269
|
-
return location instanceof Error ? null :
|
|
20634
|
+
return location instanceof Error ? null : path24.join(location.dir, "pi-launches");
|
|
20270
20635
|
};
|
|
20271
|
-
var fileFor = (dir, workerId) =>
|
|
20636
|
+
var fileFor = (dir, workerId) => path24.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
20272
20637
|
var recordLaunch = (root, attempt) => {
|
|
20273
20638
|
const dir = launchDir(root);
|
|
20274
20639
|
if (!dir)
|
|
@@ -20314,7 +20679,7 @@ var abandonedLaunches = (root) => {
|
|
|
20314
20679
|
const found = [];
|
|
20315
20680
|
for (const name of names) {
|
|
20316
20681
|
try {
|
|
20317
|
-
const attempt = JSON.parse(readFileSync6(
|
|
20682
|
+
const attempt = JSON.parse(readFileSync6(path24.join(dir, name), "utf8"));
|
|
20318
20683
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
20319
20684
|
if (!alive(attempt))
|
|
20320
20685
|
found.push(attempt);
|
|
@@ -21142,14 +21507,18 @@ function extension(pi) {
|
|
|
21142
21507
|
sessions.delete(ctx.sessionManager.getSessionId());
|
|
21143
21508
|
reconcileLostWorkers(ctx);
|
|
21144
21509
|
});
|
|
21145
|
-
pi.on("before_agent_start", (
|
|
21510
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
21511
|
+
const nudge = skillPromptNudge(event.prompt, ctx);
|
|
21146
21512
|
const id = ctx.sessionManager.getSessionId();
|
|
21147
21513
|
if (sessions.has(id))
|
|
21148
|
-
return;
|
|
21514
|
+
return nudge ? { message: { customType: "workit-skill", content: nudge, display: false } } : undefined;
|
|
21149
21515
|
sessions.add(id);
|
|
21150
21516
|
const offer = historyOfferSessions.has(id) ? null : unfinishedTaskOffer2(ctx);
|
|
21151
21517
|
historyOfferSessions.add(id);
|
|
21152
|
-
|
|
21518
|
+
const message = contextMessage(ctx, offer);
|
|
21519
|
+
return { message: nudge ? { ...message, content: `${message.content}
|
|
21520
|
+
|
|
21521
|
+
${nudge}` } : message };
|
|
21153
21522
|
});
|
|
21154
21523
|
pi.on("session_before_compact", () => {
|
|
21155
21524
|
return;
|
package/package.json
CHANGED
|
@@ -15,8 +15,8 @@ proposes and stops. Nothing changes until the user approves.
|
|
|
15
15
|
`retro:` row in `workit ledger list --type decision`), else the last ~10
|
|
16
16
|
sessions. State the window in one line.
|
|
17
17
|
2. **Read through workit, cheapest first.** Every finding cites these:
|
|
18
|
-
- `workit ledger list --last 200`: rulings
|
|
19
|
-
|
|
18
|
+
- `workit ledger list --last 200`: rulings, failed/self verdicts, handoffs,
|
|
19
|
+
check runs, CI reruns, `skill.loaded` rows (skipped skills: routing).
|
|
20
20
|
- `workit pr status --pr <n>` for each recent PR: threads, failing checks.
|
|
21
21
|
- `git log --oneline -50`, plus reverts and fixups after review.
|
|
22
22
|
- `workit knowledge lint`: today's AGENTS.md and need-based files.
|