@brainervirus/workit-pi 8.4.1 → 8.5.2
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 +437 -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,294 @@ 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|regressed|(?:is|introduced) a regression|regression (?:in|after|since|from)|(?: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)(?=\s*$|\s+(?:for|to|on|with|and|against|into|from|after|once)\b|[.!,;])|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
|
+
[
|
|
20034
|
+
"workit-fanout",
|
|
20035
|
+
/\b(fan(?: |-)?out|parallelize|parallel agents|agent swarm|swarm of agents)\b/i
|
|
20036
|
+
],
|
|
20037
|
+
["workit-verify-app", /\b(verify the app|smoke test|prove it works)\b/i],
|
|
20038
|
+
["workit-retro", /\b((?:a|the) retro(?![-\w]|\.\w)|retrospective)/i],
|
|
20039
|
+
["workit-architecture", /\b(deepen (?:the )?modules|architecture (?:review|pass|audit))\b/i],
|
|
20040
|
+
[
|
|
20041
|
+
"workit-shape",
|
|
20042
|
+
/\b(brainstorm\w*|grill me|should we|let'?s plan|plan (?:out|how|the|a|for)|(?:write|draft) (?:a|the) spec)\b/i
|
|
20043
|
+
],
|
|
20044
|
+
[
|
|
20045
|
+
"workit-implement",
|
|
20046
|
+
/\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
|
|
20047
|
+
]
|
|
20048
|
+
];
|
|
20049
|
+
var workitSkillOf = (name) => {
|
|
20050
|
+
const bare = name.trim().replace(/^\//, "").toLowerCase();
|
|
20051
|
+
const alias = WORKIT_SKILL_ALIASES[bare];
|
|
20052
|
+
if (alias)
|
|
20053
|
+
return alias;
|
|
20054
|
+
const skill = bare.replace(/^workit:/, "workit-");
|
|
20055
|
+
return WORKIT_METHOD_SKILLS.includes(skill) ? skill : null;
|
|
20056
|
+
};
|
|
20057
|
+
var promptTrigger = (prompt) => {
|
|
20058
|
+
const text = prompt.replace(/```[\s\S]*?```/g, " ").slice(0, 4000);
|
|
20059
|
+
for (const [skill, pattern] of PROMPT_INTENTS) {
|
|
20060
|
+
const match = pattern.exec(text);
|
|
20061
|
+
if (match)
|
|
20062
|
+
return { skill, trigger: match[1] ?? match[0] };
|
|
20063
|
+
}
|
|
20064
|
+
return null;
|
|
20065
|
+
};
|
|
20066
|
+
var markerFile2 = (input, session) => {
|
|
20067
|
+
const location = resolveStore(input.cwd);
|
|
20068
|
+
if (location instanceof Error || !location.shared)
|
|
20069
|
+
return null;
|
|
20070
|
+
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
20071
|
+
return path23.join(location.dir, "hooks", `skills-${name}.json`);
|
|
20072
|
+
};
|
|
20073
|
+
var readMarker = (file) => {
|
|
20074
|
+
try {
|
|
20075
|
+
const value = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
20076
|
+
return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
|
|
20077
|
+
} catch {
|
|
20078
|
+
return { nudged: [], loaded: [] };
|
|
20079
|
+
}
|
|
20080
|
+
};
|
|
20081
|
+
var writeMarker = (file, marker) => {
|
|
20082
|
+
try {
|
|
20083
|
+
fs17.mkdirSync(path23.dirname(file), { recursive: true });
|
|
20084
|
+
fs17.writeFileSync(file, JSON.stringify(marker));
|
|
20085
|
+
} catch {}
|
|
20086
|
+
};
|
|
20087
|
+
var loadedSkills = (cwd, session) => {
|
|
20088
|
+
const ledger = readLedger(cwd);
|
|
20089
|
+
const loaded = new Set;
|
|
20090
|
+
if (!ledger.ok)
|
|
20091
|
+
return loaded;
|
|
20092
|
+
for (const row of ledger.value.rows)
|
|
20093
|
+
if (row.type === "skill.loaded" && row.actor.session === session && typeof row.skill === "string")
|
|
20094
|
+
loaded.add(row.skill);
|
|
20095
|
+
return loaded;
|
|
20096
|
+
};
|
|
20097
|
+
var SKILL_FILE = /(?:^|[\\/])(workit-[a-z-]+)[\\/]SKILL\.md$/;
|
|
20098
|
+
var skillFileIn = (file) => SKILL_FILE.exec(file)?.[1] ?? null;
|
|
20099
|
+
var READERS = new Set([
|
|
20100
|
+
"cat",
|
|
20101
|
+
"sed",
|
|
20102
|
+
"head",
|
|
20103
|
+
"tail",
|
|
20104
|
+
"less",
|
|
20105
|
+
"more",
|
|
20106
|
+
"bat",
|
|
20107
|
+
"nl",
|
|
20108
|
+
"get-content",
|
|
20109
|
+
"gc",
|
|
20110
|
+
"type"
|
|
20111
|
+
]);
|
|
20112
|
+
var skillReadIn = (command, dialect = "posix") => {
|
|
20113
|
+
for (const { words } of segmentsOf(command, dialect)) {
|
|
20114
|
+
if (!READERS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
|
|
20115
|
+
continue;
|
|
20116
|
+
if (words.some((word) => /^(?:-i(?:\.\w+)?|--in-place(?:=.*)?)$/.test(word)))
|
|
20117
|
+
continue;
|
|
20118
|
+
for (const word of words.slice(1)) {
|
|
20119
|
+
const skill = skillFileIn(word);
|
|
20120
|
+
if (skill)
|
|
20121
|
+
return skill;
|
|
20122
|
+
}
|
|
20123
|
+
}
|
|
20124
|
+
return null;
|
|
20125
|
+
};
|
|
20126
|
+
function recordSkillLoad(input, name, via) {
|
|
20127
|
+
try {
|
|
20128
|
+
const skill = workitSkillOf(name);
|
|
20129
|
+
const session = input.session.id;
|
|
20130
|
+
if (!skill || !session)
|
|
20131
|
+
return;
|
|
20132
|
+
if (!inWorkitWorkspace(input.cwd))
|
|
20133
|
+
return;
|
|
20134
|
+
const file = markerFile2(input, session);
|
|
20135
|
+
if (!file)
|
|
20136
|
+
return;
|
|
20137
|
+
const marker = readMarker(file);
|
|
20138
|
+
if (marker.loaded.includes(skill))
|
|
20139
|
+
return;
|
|
20140
|
+
writeMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
|
|
20141
|
+
if (loadedSkills(input.cwd, session).has(skill))
|
|
20142
|
+
return;
|
|
20143
|
+
appendHookObserved(input.cwd, {
|
|
20144
|
+
type: "skill.loaded",
|
|
20145
|
+
actor: { host: input.host, session, agentId: input.session.agentId },
|
|
20146
|
+
skill,
|
|
20147
|
+
via
|
|
20148
|
+
});
|
|
20149
|
+
} catch {}
|
|
20150
|
+
}
|
|
20151
|
+
var nudgeOnce = (input, session, skill, line) => {
|
|
20152
|
+
const file = markerFile2(input, session);
|
|
20153
|
+
if (!file)
|
|
20154
|
+
return null;
|
|
20155
|
+
const marker = readMarker(file);
|
|
20156
|
+
if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
|
|
20157
|
+
return null;
|
|
20158
|
+
if (loadedSkills(input.cwd, session).has(skill))
|
|
20159
|
+
return null;
|
|
20160
|
+
writeMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
|
|
20161
|
+
return line;
|
|
20162
|
+
};
|
|
20163
|
+
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.`;
|
|
20164
|
+
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;
|
|
20165
|
+
function promptNudge(input, prompt) {
|
|
20166
|
+
try {
|
|
20167
|
+
const session = input.session.id;
|
|
20168
|
+
if (!prompt || !session || input.session.agentId)
|
|
20169
|
+
return null;
|
|
20170
|
+
const loads = promptLoads(prompt);
|
|
20171
|
+
if (loads) {
|
|
20172
|
+
recordSkillLoad(input, loads, "slash");
|
|
20173
|
+
return null;
|
|
20174
|
+
}
|
|
20175
|
+
const hit = promptTrigger(prompt);
|
|
20176
|
+
if (!hit || !inWorkitWorkspace(input.cwd))
|
|
20177
|
+
return null;
|
|
20178
|
+
return nudgeOnce(input, session, hit.skill, nudgeText(input, hit.skill, `this request ("${hit.trigger}")`));
|
|
20179
|
+
} catch {
|
|
20180
|
+
return null;
|
|
20181
|
+
}
|
|
20182
|
+
}
|
|
20183
|
+
var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
|
|
20184
|
+
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
20185
|
+
var LAUNCHERS = new Set([
|
|
20186
|
+
"npx",
|
|
20187
|
+
"bunx",
|
|
20188
|
+
"pnpx",
|
|
20189
|
+
"-y",
|
|
20190
|
+
"--yes",
|
|
20191
|
+
"pnpm",
|
|
20192
|
+
"dlx",
|
|
20193
|
+
"exec",
|
|
20194
|
+
"env",
|
|
20195
|
+
"sudo",
|
|
20196
|
+
"command"
|
|
20197
|
+
]);
|
|
20198
|
+
var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
|
|
20199
|
+
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
20200
|
+
if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
|
|
20201
|
+
const args = words.slice(at + 1);
|
|
20202
|
+
if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
|
|
20203
|
+
return false;
|
|
20204
|
+
return WORKIT_DELIVERY.test(args.join(" "));
|
|
20205
|
+
}
|
|
20206
|
+
const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
|
|
20207
|
+
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
20208
|
+
return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
|
|
20209
|
+
});
|
|
20210
|
+
var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
|
|
20211
|
+
function shipNudge(input, command) {
|
|
20212
|
+
try {
|
|
20213
|
+
const session = input.session.id;
|
|
20214
|
+
if (!session || input.session.agentId)
|
|
20215
|
+
return null;
|
|
20216
|
+
const dialect = input.event.kind === "shell.pre" ? input.event.dialect ?? "posix" : "posix";
|
|
20217
|
+
if (!isDeliveryCommand(input.cwd, command, dialect))
|
|
20218
|
+
return null;
|
|
20219
|
+
return nudgeOnce(input, session, "workit-ship", nudgeText(input, "workit-ship", "this delivery step"));
|
|
20220
|
+
} catch {
|
|
20221
|
+
return null;
|
|
20222
|
+
}
|
|
20223
|
+
}
|
|
20224
|
+
var withContextLine = (decision, line) => {
|
|
20225
|
+
if (!line)
|
|
20226
|
+
return decision;
|
|
20227
|
+
if (decision.kind === "none")
|
|
20228
|
+
return { kind: "context", text: line };
|
|
20229
|
+
if (decision.kind === "context")
|
|
20230
|
+
return { kind: "context", text: `${decision.text}
|
|
20231
|
+
${line}` };
|
|
20232
|
+
return decision;
|
|
20233
|
+
};
|
|
20234
|
+
var shellNudge = (input, command) => withContextLine(rawGitPre(input, command), shipNudge(input, command));
|
|
19879
20235
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
19880
20236
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19881
20237
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
19882
|
-
var
|
|
20238
|
+
var SHELLS3 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
19883
20239
|
var WRITE_TOOLS = new Set([
|
|
19884
20240
|
"write",
|
|
19885
20241
|
"edit",
|
|
@@ -19900,30 +20256,45 @@ var WRITE_TOOLS = new Set([
|
|
|
19900
20256
|
"writefile"
|
|
19901
20257
|
]);
|
|
19902
20258
|
var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
|
|
20259
|
+
var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
|
|
19903
20260
|
var writePaths = (input) => {
|
|
19904
20261
|
if (!isRecord4(input))
|
|
19905
20262
|
return [];
|
|
19906
20263
|
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
19907
20264
|
for (const value of Object.values(input))
|
|
19908
20265
|
if (typeof value === "string")
|
|
19909
|
-
for (const match of value.matchAll(
|
|
20266
|
+
for (const match of value.matchAll(PATCH_HEADER))
|
|
19910
20267
|
paths.push(match[1].trim());
|
|
19911
20268
|
return [...new Set(paths)];
|
|
19912
20269
|
};
|
|
19913
20270
|
// packages/workit-core/src/hooks/handle.ts
|
|
19914
20271
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
19915
20272
|
var NONE4 = { kind: "none" };
|
|
20273
|
+
var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
|
|
19916
20274
|
var offered = new Set;
|
|
19917
|
-
var
|
|
19918
|
-
|
|
20275
|
+
var ROLES = {
|
|
20276
|
+
implementer: "implementer",
|
|
20277
|
+
verifier: "judge",
|
|
20278
|
+
reviewer: "judge"
|
|
20279
|
+
};
|
|
20280
|
+
var workitRole = (descriptor, agentType) => {
|
|
20281
|
+
const prefix = descriptor.subagents.agentPrefix;
|
|
20282
|
+
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
20283
|
+
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
20284
|
+
};
|
|
19919
20285
|
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
|
-
|
|
20286
|
+
var subagentStartText = (session, descriptor, event) => {
|
|
20287
|
+
const role = workitRole(descriptor, event.agentType);
|
|
20288
|
+
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
20289
|
+
if (role === "implementer") {
|
|
20290
|
+
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";
|
|
20291
|
+
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.`;
|
|
20292
|
+
}
|
|
20293
|
+
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
20294
|
+
if (role !== "judge" || !event.agentId)
|
|
20295
|
+
return readOnly;
|
|
20296
|
+
const own = subagentSession(session, event.agentId);
|
|
20297
|
+
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
20298
|
};
|
|
19928
20299
|
function handleHook(input, deps) {
|
|
19929
20300
|
const { descriptor } = deps;
|
|
@@ -19939,18 +20310,22 @@ function handleHook(input, deps) {
|
|
|
19939
20310
|
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
20311
|
const addendum = [deps.addendum, advisory].filter(Boolean).join(`
|
|
19941
20312
|
`) || null;
|
|
19942
|
-
|
|
19943
|
-
|
|
19944
|
-
|
|
19945
|
-
};
|
|
20313
|
+
const text = sessionContextText(input, descriptor, { offer, addendum });
|
|
20314
|
+
if (resendsOnChange(descriptor))
|
|
20315
|
+
seedTurnContext(input, descriptor);
|
|
20316
|
+
return { kind: "context", text };
|
|
19946
20317
|
}
|
|
19947
20318
|
case "context.turn": {
|
|
19948
20319
|
if (!usable2(descriptor.context.perTurn))
|
|
19949
20320
|
return NONE4;
|
|
19950
|
-
const
|
|
19951
|
-
|
|
20321
|
+
const current = turnContextText(input, descriptor);
|
|
20322
|
+
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
20323
|
+
return withContextLine(text ? { kind: "context", text } : NONE4, promptNudge(input, event.prompt));
|
|
19952
20324
|
}
|
|
19953
20325
|
case "shell.pre": {
|
|
20326
|
+
const skillRead = skillReadIn(event.command, event.dialect);
|
|
20327
|
+
if (skillRead)
|
|
20328
|
+
recordSkillLoad(input, skillRead, "read");
|
|
19954
20329
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
19955
20330
|
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE4;
|
|
19956
20331
|
if (policy.kind !== "none")
|
|
@@ -19966,7 +20341,7 @@ function handleHook(input, deps) {
|
|
|
19966
20341
|
if (gate.kind === "deny")
|
|
19967
20342
|
return gate;
|
|
19968
20343
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
19969
|
-
return raw;
|
|
20344
|
+
return withContextLine(raw, shipNudge(input, event.command));
|
|
19970
20345
|
}
|
|
19971
20346
|
case "shell.post":
|
|
19972
20347
|
if (usable2(descriptor.events["shell.post"].support))
|
|
@@ -19977,7 +20352,7 @@ function handleHook(input, deps) {
|
|
|
19977
20352
|
case "subagent.start":
|
|
19978
20353
|
return {
|
|
19979
20354
|
kind: "context",
|
|
19980
|
-
text: subagentStartText(input.
|
|
20355
|
+
text: subagentStartText(input.session.id ?? null, descriptor, event)
|
|
19981
20356
|
};
|
|
19982
20357
|
case "compact.pre":
|
|
19983
20358
|
return {
|
|
@@ -19985,6 +20360,9 @@ function handleHook(input, deps) {
|
|
|
19985
20360
|
userMessage: "Workit context may be stale after compaction; re-run inspection or resume before acting."
|
|
19986
20361
|
};
|
|
19987
20362
|
case "tool.pre":
|
|
20363
|
+
if (event.skill)
|
|
20364
|
+
recordSkillLoad(input, event.skill, "tool");
|
|
20365
|
+
return NONE4;
|
|
19988
20366
|
case "subagent.stop":
|
|
19989
20367
|
case "prompt.submit":
|
|
19990
20368
|
case "stop":
|
|
@@ -20004,7 +20382,7 @@ var CONTEXT_EVENTS = new Set([
|
|
|
20004
20382
|
|
|
20005
20383
|
// packages/workit-core/src/hooks/hosts/codex.ts
|
|
20006
20384
|
var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
20007
|
-
var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
|
|
20385
|
+
var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
20008
20386
|
|
|
20009
20387
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
20010
20388
|
var BLOCKING2 = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
@@ -20034,16 +20412,18 @@ var PI_DESCRIPTOR = {
|
|
|
20034
20412
|
shellPolicy: { deny: "native", channel: "block", failClosed: false },
|
|
20035
20413
|
context: {
|
|
20036
20414
|
sessionStart: "native",
|
|
20037
|
-
perTurn: "
|
|
20415
|
+
perTurn: "partial",
|
|
20038
20416
|
afterCompact: "native",
|
|
20039
|
-
task: "session-bound"
|
|
20417
|
+
task: "session-bound",
|
|
20418
|
+
turnResend: "per-session"
|
|
20040
20419
|
},
|
|
20041
20420
|
subagents: {
|
|
20042
20421
|
identity: "none",
|
|
20043
20422
|
parentBinding: "none",
|
|
20044
20423
|
blockStart: "none",
|
|
20045
20424
|
worktreeIsolation: "none",
|
|
20046
|
-
maxConcurrency: "undocumented"
|
|
20425
|
+
maxConcurrency: "undocumented",
|
|
20426
|
+
agentPrefix: "workit-"
|
|
20047
20427
|
},
|
|
20048
20428
|
provenance: { sessionId: "native", agentIdOnTool: "none", postToolObserve: "native" },
|
|
20049
20429
|
interaction: { questions: "native", writeBoundary: "native" },
|
|
@@ -20120,24 +20500,6 @@ var unfinishedTaskOffer2 = (ctx) => {
|
|
|
20120
20500
|
}
|
|
20121
20501
|
};
|
|
20122
20502
|
|
|
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
20503
|
// packages/workit-pi/src/tools.ts
|
|
20142
20504
|
var readOnlyActions = new Set(["list", "inspect", "preview", "explain", "export"]);
|
|
20143
20505
|
var output = (result) => ({
|
|
@@ -20227,6 +20589,13 @@ var enforceToolPolicy = (event, ctx) => {
|
|
|
20227
20589
|
const command = event.input?.command;
|
|
20228
20590
|
return typeof command === "string" ? gate({ kind: "shell.pre", command, toolUseId: event.toolCallId ?? null }) : undefined;
|
|
20229
20591
|
}
|
|
20592
|
+
if (event.toolName === "read") {
|
|
20593
|
+
const file = event.input?.path;
|
|
20594
|
+
const skill = typeof file === "string" ? skillFileIn(file) : null;
|
|
20595
|
+
if (skill)
|
|
20596
|
+
recordSkillLoad(hookInput(ctx, { kind: "tool.pre", tool: "read", toolUseId: null }), skill, "read");
|
|
20597
|
+
return;
|
|
20598
|
+
}
|
|
20230
20599
|
if (event.toolName !== "write" && event.toolName !== "edit")
|
|
20231
20600
|
return;
|
|
20232
20601
|
if (!ctx.isProjectTrusted())
|
|
@@ -20253,22 +20622,23 @@ var observeToolResult = (event, ctx) => {
|
|
|
20253
20622
|
toolUseId: event.toolCallId
|
|
20254
20623
|
});
|
|
20255
20624
|
handleHook(post, { descriptor: PI_DESCRIPTOR, addendum: null });
|
|
20256
|
-
const nudge = rawGitPre(post, command);
|
|
20625
|
+
const nudge = process.env.WORKIT_PI_WORKER_SESSION ? rawGitPre(post, command) : shellNudge(post, command);
|
|
20257
20626
|
return nudge.kind === "context" ? { content: [...event.content, { type: "text", text: nudge.text }] } : undefined;
|
|
20258
20627
|
} catch {
|
|
20259
20628
|
return;
|
|
20260
20629
|
}
|
|
20261
20630
|
};
|
|
20631
|
+
var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? null : promptNudge(hookInput(ctx, { kind: "context.turn", prompt }), prompt);
|
|
20262
20632
|
|
|
20263
20633
|
// packages/workit-pi/src/launches.ts
|
|
20264
20634
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
20265
20635
|
import os5 from "node:os";
|
|
20266
|
-
import
|
|
20636
|
+
import path24 from "node:path";
|
|
20267
20637
|
var launchDir = (root) => {
|
|
20268
20638
|
const location = resolveStore(root);
|
|
20269
|
-
return location instanceof Error ? null :
|
|
20639
|
+
return location instanceof Error ? null : path24.join(location.dir, "pi-launches");
|
|
20270
20640
|
};
|
|
20271
|
-
var fileFor = (dir, workerId) =>
|
|
20641
|
+
var fileFor = (dir, workerId) => path24.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
20272
20642
|
var recordLaunch = (root, attempt) => {
|
|
20273
20643
|
const dir = launchDir(root);
|
|
20274
20644
|
if (!dir)
|
|
@@ -20314,7 +20684,7 @@ var abandonedLaunches = (root) => {
|
|
|
20314
20684
|
const found = [];
|
|
20315
20685
|
for (const name of names) {
|
|
20316
20686
|
try {
|
|
20317
|
-
const attempt = JSON.parse(readFileSync6(
|
|
20687
|
+
const attempt = JSON.parse(readFileSync6(path24.join(dir, name), "utf8"));
|
|
20318
20688
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
20319
20689
|
if (!alive(attempt))
|
|
20320
20690
|
found.push(attempt);
|
|
@@ -21142,14 +21512,18 @@ function extension(pi) {
|
|
|
21142
21512
|
sessions.delete(ctx.sessionManager.getSessionId());
|
|
21143
21513
|
reconcileLostWorkers(ctx);
|
|
21144
21514
|
});
|
|
21145
|
-
pi.on("before_agent_start", (
|
|
21515
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
21516
|
+
const nudge = skillPromptNudge(event.prompt, ctx);
|
|
21146
21517
|
const id = ctx.sessionManager.getSessionId();
|
|
21147
21518
|
if (sessions.has(id))
|
|
21148
|
-
return;
|
|
21519
|
+
return nudge ? { message: { customType: "workit-skill", content: nudge, display: false } } : undefined;
|
|
21149
21520
|
sessions.add(id);
|
|
21150
21521
|
const offer = historyOfferSessions.has(id) ? null : unfinishedTaskOffer2(ctx);
|
|
21151
21522
|
historyOfferSessions.add(id);
|
|
21152
|
-
|
|
21523
|
+
const message = contextMessage(ctx, offer);
|
|
21524
|
+
return { message: nudge ? { ...message, content: `${message.content}
|
|
21525
|
+
|
|
21526
|
+
${nudge}` } : message };
|
|
21153
21527
|
});
|
|
21154
21528
|
pi.on("session_before_compact", () => {
|
|
21155
21529
|
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.
|