@workit-dev/codex 0.0.0-stage → 1.0.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/.codex-plugin/plugin.json +32 -0
- package/.mcp.json +9 -0
- package/README.md +29 -2
- package/agents/workit-implementer.toml +52 -0
- package/agents/workit-reviewer.toml +32 -0
- package/agents/workit-verifier.toml +38 -0
- package/bin/launch-mcp.mjs +42 -0
- package/bin/node-resolve.mjs +249 -0
- package/bin/workit-paths.mjs +242 -0
- package/dist/launch-mcp.js +31422 -0
- package/dist/workit-hook-2hj7pbaj.js +23598 -0
- package/dist/workit-hook-3g6cv3hd.js +10 -0
- package/dist/workit-hook-3qaf6adn.js +10 -0
- package/dist/workit-hook-6n3h8wh7.js +104 -0
- package/dist/workit-hook-am9gge2k.js +583 -0
- package/dist/workit-hook-avy6vsn6.js +88 -0
- package/dist/workit-hook-b8nrcfzr.js +151 -0
- package/dist/workit-hook-ehy7rps6.js +200 -0
- package/dist/workit-hook-jy7y0gdx.js +70 -0
- package/dist/workit-hook-mk64s4fk.js +19 -0
- package/dist/workit-hook-y0ga088a.js +9 -0
- package/dist/workit-hook-zyzsv436.js +43 -0
- package/dist/workit-hook.js +94 -0
- package/hooks/hooks.json +46 -0
- package/package.json +48 -4
- package/skills/workit-architecture/SKILL.md +72 -0
- package/skills/workit-architecture/agents/openai.yaml +3 -0
- package/skills/workit-architecture/references/instruction-files.md +52 -0
- package/skills/workit-architecture/references/vocabulary.md +53 -0
- package/skills/workit-bdd/SKILL.md +53 -0
- package/skills/workit-continue/SKILL.md +54 -0
- package/skills/workit-debug/SKILL.md +45 -0
- package/skills/workit-deslop/SKILL.md +58 -0
- package/skills/workit-fanout/SKILL.md +71 -0
- package/skills/workit-fanout/references/brief.md +141 -0
- package/skills/workit-fanout/references/orchestration.md +169 -0
- package/skills/workit-implement/SKILL.md +71 -0
- package/skills/workit-retro/SKILL.md +72 -0
- package/skills/workit-retro/agents/openai.yaml +3 -0
- package/skills/workit-retro/references/sources.md +35 -0
- package/skills/workit-retro/references/steering.md +39 -0
- package/skills/workit-review/SKILL.md +66 -0
- package/skills/workit-review/references/impact.md +24 -0
- package/skills/workit-shape/SKILL.md +71 -0
- package/skills/workit-shape/references/diagrams.md +17 -0
- package/skills/workit-shape/references/knowledge.md +61 -0
- package/skills/workit-shape/references/mockups.md +15 -0
- package/skills/workit-shape/references/slicing.md +44 -0
- package/skills/workit-ship/SKILL.md +71 -0
- package/skills/workit-ship/references/changelog.md +44 -0
- package/skills/workit-ship/references/pr-body.md +32 -0
- package/skills/workit-ship/references/release.md +39 -0
- package/skills/workit-test-audit/SKILL.md +54 -0
- package/skills/workit-verify-app/SKILL.md +68 -0
- package/skills/workit-verify-app/references/template.md +51 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
gitStoreDir2
|
|
4
|
+
} from "./workit-hook-jy7y0gdx.js";
|
|
5
|
+
|
|
6
|
+
// packages/workit-core/src/hooks/budget-signal.ts
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
var hash = (text) => createHash("sha256").update(text).digest("hex").slice(0, 32);
|
|
11
|
+
var budgetDir = (store) => path.join(store, "budget");
|
|
12
|
+
var agentBase = (store, session, agentId) => path.join(budgetDir(store), hash(`${session}\x00${agentId ?? ""}`));
|
|
13
|
+
var spawnsFile = (store, session) => path.join(budgetDir(store), `spawns-${hash(session)}.jsonl`);
|
|
14
|
+
var spawnBudget = (prompt) => {
|
|
15
|
+
const found = /^TIMEBOX:[^\n]*?\b(\d+)\s+tool calls\b/mu.exec(prompt);
|
|
16
|
+
const n = found ? Number(found[1]) : NaN;
|
|
17
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
18
|
+
};
|
|
19
|
+
var noteSpawnBudget = (cwd, session, type, prompt) => {
|
|
20
|
+
try {
|
|
21
|
+
const store = session ? gitStoreDir2(cwd) : null;
|
|
22
|
+
if (!store)
|
|
23
|
+
return;
|
|
24
|
+
fs.mkdirSync(budgetDir(store), { recursive: true });
|
|
25
|
+
fs.appendFileSync(spawnsFile(store, session), `${JSON.stringify({ type: type ?? "", n: spawnBudget(prompt) })}
|
|
26
|
+
`);
|
|
27
|
+
} catch {}
|
|
28
|
+
};
|
|
29
|
+
var readBudget = (file) => {
|
|
30
|
+
try {
|
|
31
|
+
const { limit, told } = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
32
|
+
return typeof limit === "number" && limit > 0 ? { limit, told: Number(told) || 0 } : null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var claimSpawnBudget = (cwd, session, agentId, agentType) => {
|
|
38
|
+
try {
|
|
39
|
+
const store = session ? gitStoreDir2(cwd) : null;
|
|
40
|
+
if (!store)
|
|
41
|
+
return null;
|
|
42
|
+
const file = spawnsFile(store, session);
|
|
43
|
+
const entries = fs.readFileSync(file, "utf8").split(`
|
|
44
|
+
`).filter(Boolean).map((line) => JSON.parse(line));
|
|
45
|
+
const at = entries.findIndex((entry) => entry.type === (agentType ?? ""));
|
|
46
|
+
if (at < 0)
|
|
47
|
+
return null;
|
|
48
|
+
const [{ n }] = entries.splice(at, 1);
|
|
49
|
+
fs.writeFileSync(file, entries.map((entry) => `${JSON.stringify(entry)}
|
|
50
|
+
`).join(""));
|
|
51
|
+
if (typeof n !== "number" || n <= 0)
|
|
52
|
+
return null;
|
|
53
|
+
startBudget(store, session, agentId, n);
|
|
54
|
+
return n;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var startBudget = (store, session, agentId, n) => {
|
|
60
|
+
const dir = budgetDir(store);
|
|
61
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
62
|
+
const cutoff = Date.now() - 604800000;
|
|
63
|
+
for (const name of fs.readdirSync(dir))
|
|
64
|
+
try {
|
|
65
|
+
if (fs.statSync(path.join(dir, name)).mtimeMs < cutoff)
|
|
66
|
+
fs.rmSync(path.join(dir, name));
|
|
67
|
+
} catch {}
|
|
68
|
+
const base = agentBase(store, session, agentId);
|
|
69
|
+
fs.writeFileSync(`${base}.json`, JSON.stringify({ limit: n, told: 0 }));
|
|
70
|
+
fs.rmSync(`${base}.calls`, { force: true });
|
|
71
|
+
};
|
|
72
|
+
var tickBudget = (cwd, session, agentId) => {
|
|
73
|
+
try {
|
|
74
|
+
const store = session ? gitStoreDir2(cwd) : null;
|
|
75
|
+
if (!store)
|
|
76
|
+
return false;
|
|
77
|
+
const base = agentBase(store, session, agentId);
|
|
78
|
+
const budget = readBudget(`${base}.json`);
|
|
79
|
+
if (!budget)
|
|
80
|
+
return false;
|
|
81
|
+
fs.appendFileSync(`${base}.calls`, ".");
|
|
82
|
+
return fs.statSync(`${base}.calls`).size >= budget.limit * (budget.told + 1);
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var lineFor = (calls) => `Tool-call budget reached (${calls}): commit your work, write your report, stop.`;
|
|
88
|
+
var reachedOf = (cwd, session, agentId) => {
|
|
89
|
+
const store = session ? gitStoreDir2(cwd) : null;
|
|
90
|
+
const base = store && agentBase(store, session, agentId);
|
|
91
|
+
const budget = base && readBudget(`${base}.json`);
|
|
92
|
+
if (!budget)
|
|
93
|
+
return null;
|
|
94
|
+
return { base, budget, reached: Math.floor(fs.statSync(`${base}.calls`).size / budget.limit) };
|
|
95
|
+
};
|
|
96
|
+
var dropBudget = (cwd, session, agentId) => {
|
|
97
|
+
const store = session ? gitStoreDir2(cwd) : null;
|
|
98
|
+
if (store)
|
|
99
|
+
for (const ext of [".json", ".calls"])
|
|
100
|
+
fs.rmSync(agentBase(store, session, agentId) + ext, { force: true });
|
|
101
|
+
};
|
|
102
|
+
var budgetLine = (cwd, session, agentId) => {
|
|
103
|
+
try {
|
|
104
|
+
const at = reachedOf(cwd, session, agentId);
|
|
105
|
+
if (!at || at.reached <= at.budget.told)
|
|
106
|
+
return null;
|
|
107
|
+
fs.writeFileSync(`${at.base}.json`, JSON.stringify({ ...at.budget, told: at.reached }));
|
|
108
|
+
return lineFor(at.budget.limit * at.reached);
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var CODEX_GATED = new Set(["bash", "unifiedexec", "shell", "applypatch", "edit", "write"]);
|
|
114
|
+
var observeHookBudget2 = (payload) => {
|
|
115
|
+
try {
|
|
116
|
+
const value = JSON.parse(payload);
|
|
117
|
+
if (value.hook_event_name !== "PreToolUse" || typeof value.session_id !== "string")
|
|
118
|
+
return false;
|
|
119
|
+
const cwd = typeof value.cwd === "string" ? value.cwd : ".";
|
|
120
|
+
const agentId = typeof value.agent_id === "string" && value.agent_id.trim() ? value.agent_id : null;
|
|
121
|
+
if (agentId)
|
|
122
|
+
return tickBudget(cwd, value.session_id, agentId);
|
|
123
|
+
const tool = typeof value.tool_name === "string" ? value.tool_name : "";
|
|
124
|
+
const spawn = value.tool_input;
|
|
125
|
+
if (CODEX_GATED.has(tool.toLowerCase().replace(/[_-]/g, "")) || !spawn || typeof spawn !== "object")
|
|
126
|
+
return false;
|
|
127
|
+
const fields = spawn;
|
|
128
|
+
const prompt = Object.values(fields).find((field) => typeof field === "string" && field.includes("TIMEBOX:"));
|
|
129
|
+
const type = [fields.agent_type, fields.subagent_type].find((field) => typeof field === "string");
|
|
130
|
+
if (prompt !== undefined || type !== undefined)
|
|
131
|
+
noteSpawnBudget(cwd, value.session_id, type ?? null, prompt ?? "");
|
|
132
|
+
return false;
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
var budgetHooks2 = {
|
|
138
|
+
line: (input) => budgetLine(input.cwd, input.session.id ?? "", input.session.agentId),
|
|
139
|
+
start: (input, agentId, agentType) => void claimSpawnBudget(input.cwd, input.session.id ?? "", agentId, agentType),
|
|
140
|
+
spawn: (input, spawn) => {
|
|
141
|
+
if (!input.session.agentId)
|
|
142
|
+
noteSpawnBudget(input.cwd, input.session.id ?? "", String(spawn.subagent_type || "general-purpose"), String(spawn.prompt ?? ""));
|
|
143
|
+
},
|
|
144
|
+
stop: (input, agentId) => {
|
|
145
|
+
try {
|
|
146
|
+
dropBudget(input.cwd, input.session.id ?? "", agentId);
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export { observeHookBudget2, budgetHooks2 };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import"./workit-hook-am9gge2k.js";
|
|
3
|
+
import {
|
|
4
|
+
recordHookFailure2
|
|
5
|
+
} from "./workit-hook-zyzsv436.js";
|
|
6
|
+
import {
|
|
7
|
+
stateDir3
|
|
8
|
+
} from "./workit-hook-6n3h8wh7.js";
|
|
9
|
+
|
|
10
|
+
// packages/workit-core/src/core/node-resolve.mjs
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
var HOOK_NODE_REEXEC_ENV = "WORKIT_HOOK_REEXEC";
|
|
16
|
+
var MAX_ENTRIES_PER_ROOT = 200;
|
|
17
|
+
var parseNodeVersion = (text) => {
|
|
18
|
+
const found = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(String(text).trim());
|
|
19
|
+
return found ? [Number(found[1]), Number(found[2] || 0), Number(found[3] || 0)] : null;
|
|
20
|
+
};
|
|
21
|
+
var compareVersions = (a, b) => {
|
|
22
|
+
for (let i = 0;i < 3; i++)
|
|
23
|
+
if (a[i] !== b[i])
|
|
24
|
+
return a[i] - b[i];
|
|
25
|
+
return 0;
|
|
26
|
+
};
|
|
27
|
+
var hookStateDir = (env) => stateDir3(env);
|
|
28
|
+
var isFile = (file) => {
|
|
29
|
+
try {
|
|
30
|
+
return statSync(file).isFile();
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var readJson = (file) => {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var scanRoots = (env, platform) => {
|
|
43
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
44
|
+
const win = platform === "win32";
|
|
45
|
+
const exe = win ? "node.exe" : "node";
|
|
46
|
+
const bin = win ? exe : path.join("bin", exe);
|
|
47
|
+
const roots = [];
|
|
48
|
+
const add = (dir, rel) => {
|
|
49
|
+
if (dir)
|
|
50
|
+
roots.push({ dir, bin: rel });
|
|
51
|
+
};
|
|
52
|
+
const fnmDirs = [
|
|
53
|
+
env.FNM_DIR,
|
|
54
|
+
win ? path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "fnm") : "",
|
|
55
|
+
win ? path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "fnm") : "",
|
|
56
|
+
env.XDG_DATA_HOME ? path.join(env.XDG_DATA_HOME, "fnm") : "",
|
|
57
|
+
path.join(home, ".local", "share", "fnm"),
|
|
58
|
+
path.join(home, "Library", "Application Support", "fnm"),
|
|
59
|
+
path.join(home, ".fnm")
|
|
60
|
+
];
|
|
61
|
+
for (const dir of fnmDirs)
|
|
62
|
+
if (dir)
|
|
63
|
+
add(path.join(dir, "node-versions"), path.join("installation", bin));
|
|
64
|
+
for (const dir of [env.NVM_DIR, path.join(home, ".nvm")])
|
|
65
|
+
if (dir)
|
|
66
|
+
add(path.join(dir, "versions", "node"), bin);
|
|
67
|
+
if (env.NVM_HOME)
|
|
68
|
+
add(env.NVM_HOME, exe);
|
|
69
|
+
const volta = env.VOLTA_HOME || (win ? "" : path.join(home, ".volta"));
|
|
70
|
+
if (volta)
|
|
71
|
+
add(path.join(volta, "tools", "image", "node"), bin);
|
|
72
|
+
if (win)
|
|
73
|
+
add(path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "Volta", "tools", "image", "node"), bin);
|
|
74
|
+
const mise = env.MISE_DATA_DIR || path.join(home, ".local", "share", "mise");
|
|
75
|
+
add(path.join(mise, "installs", "node"), bin);
|
|
76
|
+
const asdf = env.ASDF_DATA_DIR || path.join(home, ".asdf");
|
|
77
|
+
add(path.join(asdf, "installs", "nodejs"), bin);
|
|
78
|
+
return roots;
|
|
79
|
+
};
|
|
80
|
+
var scanInstalledNodes = (env, platform = process.platform) => {
|
|
81
|
+
const found = [];
|
|
82
|
+
for (const root of scanRoots(env, platform)) {
|
|
83
|
+
let names;
|
|
84
|
+
try {
|
|
85
|
+
names = readdirSync(root.dir).slice(0, MAX_ENTRIES_PER_ROOT);
|
|
86
|
+
} catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
const version = parseNodeVersion(name);
|
|
91
|
+
if (!version || !/^v?\d/.test(name))
|
|
92
|
+
continue;
|
|
93
|
+
const candidate = path.join(root.dir, name, root.bin);
|
|
94
|
+
if (isFile(candidate))
|
|
95
|
+
found.push({ path: candidate, version, source: root.dir });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return found;
|
|
99
|
+
};
|
|
100
|
+
var resolveHookNode = ({ env, floor, platform = process.platform }) => {
|
|
101
|
+
if (env.WORKIT_NODE && isFile(env.WORKIT_NODE))
|
|
102
|
+
return { path: env.WORKIT_NODE, version: null, source: "env" };
|
|
103
|
+
const recorded = readJson(path.join(hookStateDir(env), "node.json"));
|
|
104
|
+
if (recorded && typeof recorded.path === "string" && isFile(recorded.path)) {
|
|
105
|
+
const version = parseNodeVersion(recorded.version);
|
|
106
|
+
if (version && compareVersions(version, floor) >= 0)
|
|
107
|
+
return { path: recorded.path, version: String(recorded.version), source: "recorded" };
|
|
108
|
+
}
|
|
109
|
+
let best = null;
|
|
110
|
+
for (const entry of scanInstalledNodes(env, platform))
|
|
111
|
+
if (compareVersions(entry.version, floor) >= 0 && (!best || compareVersions(entry.version, best.version) > 0))
|
|
112
|
+
best = entry;
|
|
113
|
+
return best ? { path: best.path, version: best.version.join("."), source: "scan" } : null;
|
|
114
|
+
};
|
|
115
|
+
var nodeFloorNotice = (found, range) => `Workit hooks are off: the Node on PATH is ${String(found).replace(/^v/, "")}, below the required ${range}, and no Node 24+ was found. ` + "Install Node 24 (fnm install 24, nvm install 24) or run `workit setup` from a Node 24 shell so Workit can record it; " + "`workit doctor` (check hook_node) shows what the hooks run on.";
|
|
116
|
+
var reexecOnNode = (nodePath, script, args, env) => {
|
|
117
|
+
const child = Object.assign({}, env, { [HOOK_NODE_REEXEC_ENV]: "1" });
|
|
118
|
+
delete child.WORKIT_HOOK_NODE_VERSION;
|
|
119
|
+
const run = spawnSync(nodePath, [script].concat(args), {
|
|
120
|
+
stdio: "inherit",
|
|
121
|
+
env: child,
|
|
122
|
+
windowsHide: true
|
|
123
|
+
});
|
|
124
|
+
if (run.error)
|
|
125
|
+
return null;
|
|
126
|
+
return run.status === null ? 1 : run.status;
|
|
127
|
+
};
|
|
128
|
+
var handleBelowFloor = async (options) => {
|
|
129
|
+
if (!options.env[HOOK_NODE_REEXEC_ENV]) {
|
|
130
|
+
const hit = resolveHookNode({ env: options.env, floor: options.floor });
|
|
131
|
+
const code = hit ? reexecOnNode(hit.path, options.script, options.argv, options.env) : null;
|
|
132
|
+
if (code !== null)
|
|
133
|
+
return code;
|
|
134
|
+
}
|
|
135
|
+
process.stderr.write(`[workit] ${options.host} hook unavailable: needs Node ${options.range}, found ${options.found}
|
|
136
|
+
`);
|
|
137
|
+
let early = "";
|
|
138
|
+
for await (const chunk of process.stdin)
|
|
139
|
+
early += String(chunk);
|
|
140
|
+
let event;
|
|
141
|
+
try {
|
|
142
|
+
event = JSON.parse(early).hook_event_name;
|
|
143
|
+
} catch {
|
|
144
|
+
event = undefined;
|
|
145
|
+
}
|
|
146
|
+
process.stdout.write(options.isStart(event) ? `${JSON.stringify(options.notice(nodeFloorNotice(options.found, options.range)))}
|
|
147
|
+
` : `{}
|
|
148
|
+
`);
|
|
149
|
+
return 0;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// packages/workit-codex/hooks/fail-loud.ts
|
|
153
|
+
var LOUD_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
|
|
154
|
+
var SUBAGENT_NOTE = " This subagent got no Workit session: a verifier or reviewer cannot record a verdict (it fails with no_session) until the hook runs; fix the hook, run `workit doctor`, then spawn a fresh one.";
|
|
155
|
+
var isLoudEvent = (event) => typeof event === "string" && LOUD_EVENTS.has(event);
|
|
156
|
+
var failedAnswer = (event, text) => {
|
|
157
|
+
if (typeof event !== "string" || !LOUD_EVENTS.has(event))
|
|
158
|
+
return {};
|
|
159
|
+
const message = event === "SubagentStart" ? `${text}${SUBAGENT_NOTE}` : text;
|
|
160
|
+
return {
|
|
161
|
+
systemMessage: message,
|
|
162
|
+
hookSpecificOutput: { hookEventName: event, additionalContext: message }
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
var recordFailure = (host, event, cause) => recordHookFailure2(host, typeof event === "string" ? event : null, cause);
|
|
166
|
+
var answerRuntimeFailure = (host, payload, error) => {
|
|
167
|
+
const reason = `could not run (${error instanceof Error ? error.message : String(error)})`;
|
|
168
|
+
process.stderr.write(`[workit] Codex hook unavailable: ${reason}
|
|
169
|
+
`);
|
|
170
|
+
let event;
|
|
171
|
+
try {
|
|
172
|
+
event = JSON.parse(payload).hook_event_name;
|
|
173
|
+
} catch {
|
|
174
|
+
event = undefined;
|
|
175
|
+
}
|
|
176
|
+
recordFailure(host, event, reason);
|
|
177
|
+
process.stdout.write(`${JSON.stringify(failedAnswer(event, `Workit hooks are off: the Codex hook ${reason}. Branch policy, task context and verifier sessions are off until it does. Run \`workit upgrade\` to reinstall the plugin, then \`workit doctor\` (check hook_node runs each installed hook).`))}
|
|
178
|
+
`);
|
|
179
|
+
};
|
|
180
|
+
var belowFloor = async (host, node, script) => {
|
|
181
|
+
const cause = `needs Node ${node.range}, found ${node.found}`;
|
|
182
|
+
let floorEvent;
|
|
183
|
+
return handleBelowFloor({
|
|
184
|
+
host: "Codex",
|
|
185
|
+
...node,
|
|
186
|
+
script,
|
|
187
|
+
argv: process.argv.slice(2),
|
|
188
|
+
env: process.env,
|
|
189
|
+
isStart: (event) => {
|
|
190
|
+
floorEvent = event;
|
|
191
|
+
recordFailure(host, event, cause);
|
|
192
|
+
return isLoudEvent(event);
|
|
193
|
+
},
|
|
194
|
+
notice: (text) => failedAnswer(floorEvent, text)
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
export {
|
|
198
|
+
answerRuntimeFailure,
|
|
199
|
+
belowFloor
|
|
200
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// packages/workit-core/src/hooks/standing-signal.ts
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
var gitStoreDir2 = (cwd) => {
|
|
8
|
+
try {
|
|
9
|
+
for (let dir = path.resolve(cwd);; dir = path.dirname(dir)) {
|
|
10
|
+
const dotGit = path.join(dir, ".git");
|
|
11
|
+
if (fs.existsSync(dotGit)) {
|
|
12
|
+
let gitDir = dotGit;
|
|
13
|
+
if (fs.statSync(dotGit).isFile()) {
|
|
14
|
+
const target = /^gitdir:\s*(.+)$/mu.exec(fs.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
15
|
+
if (!target)
|
|
16
|
+
return null;
|
|
17
|
+
gitDir = path.resolve(dir, target);
|
|
18
|
+
const common = path.join(gitDir, "commondir");
|
|
19
|
+
if (fs.existsSync(common))
|
|
20
|
+
gitDir = path.resolve(gitDir, fs.readFileSync(common, "utf8").trim());
|
|
21
|
+
}
|
|
22
|
+
return path.join(gitDir, "workit");
|
|
23
|
+
}
|
|
24
|
+
if (path.dirname(dir) === dir)
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var standingRevFile = (store) => path.join(store, "standing", "rev");
|
|
32
|
+
var standingSeenFile2 = (store, session, agentId) => path.join(store, "standing", "seen", `${createHash("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
33
|
+
var readStandingRev2 = (store) => {
|
|
34
|
+
try {
|
|
35
|
+
return fs.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var readStandingSeen2 = (file) => {
|
|
41
|
+
try {
|
|
42
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
43
|
+
return {
|
|
44
|
+
rev: typeof value.rev === "string" ? value.rev : null,
|
|
45
|
+
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
46
|
+
};
|
|
47
|
+
} catch {
|
|
48
|
+
return { rev: null, ids: [] };
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var writeStandingSeen2 = (file, seen) => {
|
|
52
|
+
try {
|
|
53
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
54
|
+
fs.writeFileSync(file, JSON.stringify(seen));
|
|
55
|
+
} catch {}
|
|
56
|
+
};
|
|
57
|
+
var bumpStandingRev2 = (store, rev) => {
|
|
58
|
+
try {
|
|
59
|
+
fs.mkdirSync(path.join(store, "standing"), { recursive: true });
|
|
60
|
+
fs.writeFileSync(standingRevFile(store), `${rev}
|
|
61
|
+
`);
|
|
62
|
+
} catch {}
|
|
63
|
+
};
|
|
64
|
+
var standingPending2 = (cwd, session, agentId) => {
|
|
65
|
+
const store = gitStoreDir2(cwd);
|
|
66
|
+
const rev = store ? readStandingRev2(store) : null;
|
|
67
|
+
return rev !== null && readStandingSeen2(standingSeenFile2(store, session, agentId)).rev !== rev;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export { gitStoreDir2, standingSeenFile2, readStandingRev2, readStandingSeen2, writeStandingSeen2, bumpStandingRev2, standingPending2 };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
gitStoreDir2,
|
|
4
|
+
standingSeenFile2,
|
|
5
|
+
readStandingRev2,
|
|
6
|
+
readStandingSeen2,
|
|
7
|
+
writeStandingSeen2,
|
|
8
|
+
bumpStandingRev2,
|
|
9
|
+
standingPending2
|
|
10
|
+
} from "./workit-hook-jy7y0gdx.js";
|
|
11
|
+
export {
|
|
12
|
+
bumpStandingRev2 as bumpStandingRev,
|
|
13
|
+
gitStoreDir2 as gitStoreDir,
|
|
14
|
+
readStandingRev2 as readStandingRev,
|
|
15
|
+
readStandingSeen2 as readStandingSeen,
|
|
16
|
+
standingPending2 as standingPending,
|
|
17
|
+
standingSeenFile2 as standingSeenFile,
|
|
18
|
+
writeStandingSeen2 as writeStandingSeen
|
|
19
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
stateDir2
|
|
4
|
+
} from "./workit-hook-am9gge2k.js";
|
|
5
|
+
|
|
6
|
+
// packages/workit-core/src/hooks/hook-runs.ts
|
|
7
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
var HOOK_RUNS_DIR = "hook-runs";
|
|
10
|
+
var HOOK_FAILURE_SUFFIX = "__failed";
|
|
11
|
+
var safe = (part) => part.replace(/[^A-Za-z0-9_.-]/g, "-").slice(0, 64);
|
|
12
|
+
var runFile = (dir, host, event, failed = false) => path.join(dir, HOOK_RUNS_DIR, `${safe(host)}__${safe(event)}${failed ? HOOK_FAILURE_SUFFIX : ""}.json`);
|
|
13
|
+
var recordHookRun2 = (host, event, env = process.env, now = Date.now) => {
|
|
14
|
+
if (event)
|
|
15
|
+
writeRun(host, event, undefined, env, now);
|
|
16
|
+
};
|
|
17
|
+
var recordHookFailure2 = (host, event, cause, env = process.env, now = Date.now) => writeRun(host, event || "unknown", cause.slice(0, 500), env, now);
|
|
18
|
+
var writeRun = (host, event, failure, env, now) => {
|
|
19
|
+
try {
|
|
20
|
+
const dir = stateDir2(env);
|
|
21
|
+
const file = runFile(dir, host, event, failure !== undefined);
|
|
22
|
+
let count = 0;
|
|
23
|
+
try {
|
|
24
|
+
const previous = JSON.parse(readFileSync(file, "utf8"));
|
|
25
|
+
if (typeof previous.count === "number" && Number.isFinite(previous.count))
|
|
26
|
+
count = previous.count;
|
|
27
|
+
} catch {}
|
|
28
|
+
const row = {
|
|
29
|
+
host,
|
|
30
|
+
event,
|
|
31
|
+
at: new Date(now()).toISOString(),
|
|
32
|
+
count: count + 1,
|
|
33
|
+
...failure === undefined ? {} : { failure }
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
36
|
+
writeFileSync(file, JSON.stringify(row));
|
|
37
|
+
} catch {
|
|
38
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
39
|
+
writeFileSync(file, JSON.stringify(row));
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
};
|
|
43
|
+
export { recordHookRun2, recordHookFailure2 };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// packages/workit-codex/hooks/launcher.ts
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
var foundNode = process.env.WORKIT_HOOK_NODE_VERSION || process.versions.node;
|
|
8
|
+
var nodeFloor = () => {
|
|
9
|
+
try {
|
|
10
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const range = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8")).engines?.node;
|
|
12
|
+
const min = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(String(range ?? ""));
|
|
13
|
+
if (!min || !range)
|
|
14
|
+
return null;
|
|
15
|
+
const floor = [min[1], min[2] ?? 0, min[3] ?? 0].map(Number);
|
|
16
|
+
const have = String(foundNode).replace(/^v/, "").split(".").map(Number);
|
|
17
|
+
for (let i = 0;i < 3; i++) {
|
|
18
|
+
const a = have[i] ?? 0;
|
|
19
|
+
if (Number.isNaN(a))
|
|
20
|
+
return null;
|
|
21
|
+
if (a !== floor[i])
|
|
22
|
+
return a < floor[i] ? { range, floor } : null;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var codexHost = () => process.env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || process.env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli";
|
|
30
|
+
var tooOld = nodeFloor();
|
|
31
|
+
if (tooOld) {
|
|
32
|
+
const { belowFloor } = await import("./workit-hook-ehy7rps6.js");
|
|
33
|
+
process.exit(await belowFloor(codexHost(), { ...tooOld, found: foundNode }, fileURLToPath(import.meta.url)));
|
|
34
|
+
}
|
|
35
|
+
var { useOwnedWorkitCli } = await import("./workit-hook-avy6vsn6.js");
|
|
36
|
+
useOwnedWorkitCli();
|
|
37
|
+
var { maybeHeavy, stateDir } = await import("./workit-hook-y0ga088a.js");
|
|
38
|
+
var { standingPending } = await import("./workit-hook-mk64s4fk.js");
|
|
39
|
+
var { recordHookRun } = await import("./workit-hook-3qaf6adn.js");
|
|
40
|
+
var { observeHookBudget } = await import("./workit-hook-3g6cv3hd.js");
|
|
41
|
+
var payload = "";
|
|
42
|
+
for await (const chunk of process.stdin)
|
|
43
|
+
payload += String(chunk);
|
|
44
|
+
var SHELL_TOOLS = new Set(["bash", "unified-exec"]);
|
|
45
|
+
var WRITE_TOOLS = new Set(["applypatch", "edit", "write"]);
|
|
46
|
+
var mentionsIssued = (text) => {
|
|
47
|
+
const flat = (value) => value.replaceAll("\\", "/").toLowerCase();
|
|
48
|
+
return /issued/i.test(text) || flat(text).includes(flat(stateDir(process.env)));
|
|
49
|
+
};
|
|
50
|
+
var quietEvent = () => {
|
|
51
|
+
try {
|
|
52
|
+
const value = JSON.parse(payload);
|
|
53
|
+
const event = value.hook_event_name;
|
|
54
|
+
if (event !== "PreToolUse" && event !== "PostToolUse")
|
|
55
|
+
return null;
|
|
56
|
+
if (typeof value.cwd !== "string" || typeof value.tool_name !== "string")
|
|
57
|
+
return null;
|
|
58
|
+
if (event === "PreToolUse" && typeof value.session_id === "string" && standingPending(value.cwd, value.session_id, typeof value.agent_id === "string" && value.agent_id.trim() ? value.agent_id : null))
|
|
59
|
+
return null;
|
|
60
|
+
const tool = value.tool_name.toLowerCase();
|
|
61
|
+
if (event === "PreToolUse" && WRITE_TOOLS.has(tool.replace(/[_-]/g, "")))
|
|
62
|
+
return null;
|
|
63
|
+
if (event === "PreToolUse" && value.agent_type === "workit-implementer")
|
|
64
|
+
return null;
|
|
65
|
+
const shell = SHELL_TOOLS.has(tool);
|
|
66
|
+
const command = value.tool_input?.command;
|
|
67
|
+
const text = Array.isArray(command) ? command.join(" ") : String(command ?? "");
|
|
68
|
+
if (!shell)
|
|
69
|
+
return event;
|
|
70
|
+
if (!text.trim())
|
|
71
|
+
return null;
|
|
72
|
+
if (event === "PostToolUse")
|
|
73
|
+
return /\bcommit\b/.test(text) ? null : event;
|
|
74
|
+
return /\b(?:git|gh|glab)\b/.test(text) || mentionsIssued(text) || maybeHeavy(text, value.cwd) ? null : event;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var quiet = observeHookBudget(payload) ? null : quietEvent();
|
|
80
|
+
if (quiet) {
|
|
81
|
+
if (quiet === "PreToolUse")
|
|
82
|
+
recordHookRun(codexHost(), quiet);
|
|
83
|
+
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
|
|
84
|
+
`);
|
|
85
|
+
} else {
|
|
86
|
+
try {
|
|
87
|
+
const { runCodexHook } = await import("./workit-hook-2hj7pbaj.js");
|
|
88
|
+
await runCodexHook([payload]);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const { answerRuntimeFailure } = await import("./workit-hook-ehy7rps6.js");
|
|
91
|
+
answerRuntimeFailure(codexHost(), payload, error);
|
|
92
|
+
process.exitCode = 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "*",
|
|
6
|
+
"hooks": [{ "type": "command", "command": "node ${PLUGIN_ROOT}/dist/workit-hook.js" }]
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"UserPromptSubmit": [
|
|
10
|
+
{
|
|
11
|
+
"hooks": [{ "type": "command", "command": "node ${PLUGIN_ROOT}/dist/workit-hook.js" }]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PreToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "*",
|
|
17
|
+
"hooks": [{ "type": "command", "command": "node ${PLUGIN_ROOT}/dist/workit-hook.js" }]
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"PostToolUse": [
|
|
21
|
+
{
|
|
22
|
+
"matcher": "Bash",
|
|
23
|
+
"hooks": [{ "type": "command", "command": "node ${PLUGIN_ROOT}/dist/workit-hook.js" }]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"SubagentStart": [
|
|
27
|
+
{
|
|
28
|
+
"matcher": "*",
|
|
29
|
+
"hooks": [{ "type": "command", "command": "node ${PLUGIN_ROOT}/dist/workit-hook.js" }]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"Stop": [
|
|
33
|
+
{
|
|
34
|
+
"hooks": [
|
|
35
|
+
{ "type": "command", "command": "node ${PLUGIN_ROOT}/dist/workit-hook.js", "timeout": 20 }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"SubagentStop": [
|
|
40
|
+
{
|
|
41
|
+
"matcher": "*",
|
|
42
|
+
"hooks": [{ "type": "command", "command": "node ${PLUGIN_ROOT}/dist/workit-hook.js" }]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|