@brainervirus/workit-codex 8.17.0 → 8.18.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 +1 -1
- package/dist/launch-mcp.js +38 -15
- package/dist/{workit-hook-rbb0m9zp.js → workit-hook-8yfjygc1.js} +24 -13
- package/dist/{workit-hook-23t8ttjv.js → workit-hook-q9ypsecg.js} +123 -15
- package/dist/workit-hook.js +2 -2
- package/hooks/hooks.json +1 -1
- package/package.json +3 -3
- package/skills/workit-bdd/SKILL.md +1 -1
- package/skills/workit-fanout/SKILL.md +2 -2
- package/skills/workit-fanout/references/orchestration.md +21 -0
- package/skills/workit-retro/SKILL.md +2 -2
- package/skills/workit-review/SKILL.md +4 -1
- package/skills/workit-ship/SKILL.md +1 -1
- package/skills/workit-verify-app/SKILL.md +1 -1
package/dist/launch-mcp.js
CHANGED
|
@@ -18869,8 +18869,13 @@ import { spawnSync as spawnSync3 } from "node:child_process";
|
|
|
18869
18869
|
import fs9 from "node:fs";
|
|
18870
18870
|
import path9 from "node:path";
|
|
18871
18871
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
18872
|
-
var CHECKS_FILE = "workit
|
|
18873
|
-
var
|
|
18872
|
+
var CHECKS_FILE = ".workit/checks.json";
|
|
18873
|
+
var LEGACY_CHECKS_FILE = "workit.checks.json";
|
|
18874
|
+
function checksConfigFile(root) {
|
|
18875
|
+
const current = path9.join(root, CHECKS_FILE);
|
|
18876
|
+
const legacy = path9.join(root, LEGACY_CHECKS_FILE);
|
|
18877
|
+
return !fs9.existsSync(current) && fs9.existsSync(legacy) ? legacy : current;
|
|
18878
|
+
}
|
|
18874
18879
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18875
18880
|
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
18876
18881
|
function splitCommand(command) {
|
|
@@ -18984,19 +18989,25 @@ var GATES = ["testing", "verification"];
|
|
|
18984
18989
|
function loadCheckConfig(cwd) {
|
|
18985
18990
|
const root = checkRoot(cwd);
|
|
18986
18991
|
const file = checksConfigFile(root);
|
|
18992
|
+
const legacy = path9.basename(file) === LEGACY_CHECKS_FILE;
|
|
18993
|
+
const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
|
|
18994
|
+
const note = legacy ? {
|
|
18995
|
+
note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`
|
|
18996
|
+
} : {};
|
|
18987
18997
|
const none = (error = null) => ({
|
|
18988
|
-
source: error ?
|
|
18998
|
+
source: error ? label : "none",
|
|
18989
18999
|
root,
|
|
18990
19000
|
checks: [],
|
|
18991
19001
|
gates: {},
|
|
18992
|
-
error
|
|
19002
|
+
error,
|
|
19003
|
+
...note
|
|
18993
19004
|
});
|
|
18994
19005
|
let text = null;
|
|
18995
19006
|
try {
|
|
18996
19007
|
text = fs9.readFileSync(file, "utf8");
|
|
18997
19008
|
} catch (error) {
|
|
18998
19009
|
if (error.code !== "ENOENT")
|
|
18999
|
-
return none(`cannot read ${
|
|
19010
|
+
return none(`cannot read ${label}: ${error.message}`);
|
|
19000
19011
|
}
|
|
19001
19012
|
if (text === null) {
|
|
19002
19013
|
const checks = detectedChecks(root);
|
|
@@ -19006,33 +19017,33 @@ function loadCheckConfig(cwd) {
|
|
|
19006
19017
|
try {
|
|
19007
19018
|
parsed = JSON.parse(text);
|
|
19008
19019
|
} catch {
|
|
19009
|
-
return none(`${
|
|
19020
|
+
return none(`${label} is not valid JSON`);
|
|
19010
19021
|
}
|
|
19011
19022
|
if (!isRecord(parsed) || !isRecord(parsed.checks))
|
|
19012
|
-
return none(`${
|
|
19023
|
+
return none(`${label} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
19013
19024
|
const checks = [];
|
|
19014
19025
|
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
19015
19026
|
if (!CHECK_NAME.test(name))
|
|
19016
|
-
return none(`${
|
|
19027
|
+
return none(`${label}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
19017
19028
|
const argv = typeof value === "string" ? splitCommand(value) : Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0) ? value : new Error("expected a command string or a non-empty argv array");
|
|
19018
19029
|
if (argv instanceof Error)
|
|
19019
|
-
return none(`${
|
|
19030
|
+
return none(`${label}: check "${name}": ${argv.message}`);
|
|
19020
19031
|
checks.push({ name, argv, accepts: [argv] });
|
|
19021
19032
|
}
|
|
19022
19033
|
const gates = {};
|
|
19023
19034
|
if (parsed.gates !== undefined) {
|
|
19024
19035
|
if (!isRecord(parsed.gates))
|
|
19025
|
-
return none(`${
|
|
19036
|
+
return none(`${label}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
19026
19037
|
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
19027
19038
|
if (!GATES.includes(gate))
|
|
19028
|
-
return none(`${
|
|
19039
|
+
return none(`${label}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
19029
19040
|
const names = typeof value === "string" ? [value] : value;
|
|
19030
19041
|
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
19031
|
-
return none(`${
|
|
19042
|
+
return none(`${label}: gates.${gate} must name configured checks`);
|
|
19032
19043
|
gates[gate] = names;
|
|
19033
19044
|
}
|
|
19034
19045
|
}
|
|
19035
|
-
return { source:
|
|
19046
|
+
return { source: label, root, checks, gates, error: null, ...note, resources: parsed.resources };
|
|
19036
19047
|
}
|
|
19037
19048
|
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
19038
19049
|
function matchesNamedCheck(config, name, argv, cwd) {
|
|
@@ -21691,7 +21702,7 @@ var productDecision = (task, ledger) => {
|
|
|
21691
21702
|
};
|
|
21692
21703
|
const since = Date.parse(task.createdAt);
|
|
21693
21704
|
const branch = ledger.branch();
|
|
21694
|
-
const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
21705
|
+
const row = ledger.rows().findLast((item) => item.type === "decision" && item.by !== "decider" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
21695
21706
|
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
21696
21707
|
met: false,
|
|
21697
21708
|
reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or, if it is not open, `workit policy assess --judge product-choice=no --why "<reason>"`)',
|
|
@@ -22175,7 +22186,7 @@ function evaluateObservedGate(task, requirement, statuses, config) {
|
|
|
22175
22186
|
return {
|
|
22176
22187
|
satisfied: [],
|
|
22177
22188
|
considered,
|
|
22178
|
-
reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE}
|
|
22189
|
+
reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE}, e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`
|
|
22179
22190
|
};
|
|
22180
22191
|
const satisfied = fresh.filter(named);
|
|
22181
22192
|
if (satisfied.length)
|
|
@@ -28817,6 +28828,18 @@ var WRITE_TOOLS = new Set([
|
|
|
28817
28828
|
]);
|
|
28818
28829
|
// packages/workit-core/src/hooks/handle.ts
|
|
28819
28830
|
var offered = new Set;
|
|
28831
|
+
// packages/workit-core/src/hooks/deadline.ts
|
|
28832
|
+
var QUIET = new Set([
|
|
28833
|
+
"stop",
|
|
28834
|
+
"subagent.stop",
|
|
28835
|
+
"shell.post",
|
|
28836
|
+
"compact.pre",
|
|
28837
|
+
"prompt.submit",
|
|
28838
|
+
null
|
|
28839
|
+
]);
|
|
28840
|
+
var TIMED_OUT = Symbol("timed out");
|
|
28841
|
+
var PROCESS_START = performance.timeOrigin;
|
|
28842
|
+
|
|
28820
28843
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
28821
28844
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
28822
28845
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
@@ -10,8 +10,13 @@ import { spawnSync } from "node:child_process";
|
|
|
10
10
|
import fs from "node:fs";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
13
|
-
var CHECKS_FILE = "workit
|
|
14
|
-
var
|
|
13
|
+
var CHECKS_FILE = ".workit/checks.json";
|
|
14
|
+
var LEGACY_CHECKS_FILE = "workit.checks.json";
|
|
15
|
+
function checksConfigFile(root) {
|
|
16
|
+
const current = path.join(root, CHECKS_FILE);
|
|
17
|
+
const legacy = path.join(root, LEGACY_CHECKS_FILE);
|
|
18
|
+
return !fs.existsSync(current) && fs.existsSync(legacy) ? legacy : current;
|
|
19
|
+
}
|
|
15
20
|
function repoHeavyCommands(root) {
|
|
16
21
|
try {
|
|
17
22
|
const value = JSON.parse(fs.readFileSync(checksConfigFile(root), "utf8"));
|
|
@@ -135,19 +140,25 @@ var GATES = ["testing", "verification"];
|
|
|
135
140
|
function loadCheckConfig(cwd) {
|
|
136
141
|
const root = checkRoot(cwd);
|
|
137
142
|
const file = checksConfigFile(root);
|
|
143
|
+
const legacy = path.basename(file) === LEGACY_CHECKS_FILE;
|
|
144
|
+
const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
|
|
145
|
+
const note = legacy ? {
|
|
146
|
+
note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`
|
|
147
|
+
} : {};
|
|
138
148
|
const none = (error = null) => ({
|
|
139
|
-
source: error ?
|
|
149
|
+
source: error ? label : "none",
|
|
140
150
|
root,
|
|
141
151
|
checks: [],
|
|
142
152
|
gates: {},
|
|
143
|
-
error
|
|
153
|
+
error,
|
|
154
|
+
...note
|
|
144
155
|
});
|
|
145
156
|
let text = null;
|
|
146
157
|
try {
|
|
147
158
|
text = fs.readFileSync(file, "utf8");
|
|
148
159
|
} catch (error) {
|
|
149
160
|
if (error.code !== "ENOENT")
|
|
150
|
-
return none(`cannot read ${
|
|
161
|
+
return none(`cannot read ${label}: ${error.message}`);
|
|
151
162
|
}
|
|
152
163
|
if (text === null) {
|
|
153
164
|
const checks = detectedChecks(root);
|
|
@@ -157,33 +168,33 @@ function loadCheckConfig(cwd) {
|
|
|
157
168
|
try {
|
|
158
169
|
parsed = JSON.parse(text);
|
|
159
170
|
} catch {
|
|
160
|
-
return none(`${
|
|
171
|
+
return none(`${label} is not valid JSON`);
|
|
161
172
|
}
|
|
162
173
|
if (!isRecord(parsed) || !isRecord(parsed.checks))
|
|
163
|
-
return none(`${
|
|
174
|
+
return none(`${label} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
164
175
|
const checks = [];
|
|
165
176
|
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
166
177
|
if (!CHECK_NAME.test(name))
|
|
167
|
-
return none(`${
|
|
178
|
+
return none(`${label}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
168
179
|
const argv = typeof value === "string" ? splitCommand(value) : Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0) ? value : new Error("expected a command string or a non-empty argv array");
|
|
169
180
|
if (argv instanceof Error)
|
|
170
|
-
return none(`${
|
|
181
|
+
return none(`${label}: check "${name}": ${argv.message}`);
|
|
171
182
|
checks.push({ name, argv, accepts: [argv] });
|
|
172
183
|
}
|
|
173
184
|
const gates = {};
|
|
174
185
|
if (parsed.gates !== undefined) {
|
|
175
186
|
if (!isRecord(parsed.gates))
|
|
176
|
-
return none(`${
|
|
187
|
+
return none(`${label}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
177
188
|
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
178
189
|
if (!GATES.includes(gate))
|
|
179
|
-
return none(`${
|
|
190
|
+
return none(`${label}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
180
191
|
const names = typeof value === "string" ? [value] : value;
|
|
181
192
|
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
182
|
-
return none(`${
|
|
193
|
+
return none(`${label}: gates.${gate} must name configured checks`);
|
|
183
194
|
gates[gate] = names;
|
|
184
195
|
}
|
|
185
196
|
}
|
|
186
|
-
return { source:
|
|
197
|
+
return { source: label, root, checks, gates, error: null, ...note, resources: parsed.resources };
|
|
187
198
|
}
|
|
188
199
|
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
189
200
|
function matchesNamedCheck(config, name, argv, cwd) {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
gateCheckNames,
|
|
8
8
|
checkCommand,
|
|
9
9
|
maybeHeavy
|
|
10
|
-
} from "./workit-hook-
|
|
10
|
+
} from "./workit-hook-8yfjygc1.js";
|
|
11
11
|
// packages/workit-core/src/hooks/descriptor.ts
|
|
12
12
|
var support = (descriptor, axis) => {
|
|
13
13
|
if (axis.startsWith("event:"))
|
|
@@ -14444,7 +14444,7 @@ var productDecision = (task, ledger) => {
|
|
|
14444
14444
|
};
|
|
14445
14445
|
const since = Date.parse(task.createdAt);
|
|
14446
14446
|
const branch = ledger.branch();
|
|
14447
|
-
const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
14447
|
+
const row = ledger.rows().findLast((item) => item.type === "decision" && item.by !== "decider" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
14448
14448
|
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
14449
14449
|
met: false,
|
|
14450
14450
|
reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or, if it is not open, `workit policy assess --judge product-choice=no --why "<reason>"`)',
|
|
@@ -14939,7 +14939,7 @@ function evaluateObservedGate(task, requirement, statuses, config) {
|
|
|
14939
14939
|
return {
|
|
14940
14940
|
satisfied: [],
|
|
14941
14941
|
considered,
|
|
14942
|
-
reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE}
|
|
14942
|
+
reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE}, e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`
|
|
14943
14943
|
};
|
|
14944
14944
|
const satisfied = fresh.filter(named);
|
|
14945
14945
|
if (satisfied.length)
|
|
@@ -22228,6 +22228,112 @@ var dispatchHook = (adapter, raw, env = process.env) => {
|
|
|
22228
22228
|
};
|
|
22229
22229
|
}
|
|
22230
22230
|
};
|
|
22231
|
+
// packages/workit-core/src/hooks/deadline.ts
|
|
22232
|
+
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
22233
|
+
import path26 from "node:path";
|
|
22234
|
+
var HOOK_SKIP_TEXT = "workit context skipped: host under load";
|
|
22235
|
+
var HOOK_DEADLINE_MS = 8000;
|
|
22236
|
+
var HOST_DEADLINES = {
|
|
22237
|
+
claude_code: { "": 8000, SessionStart: 16000, Stop: 16000 },
|
|
22238
|
+
codex_cli: { "": 45000, Stop: 16000 },
|
|
22239
|
+
codex_desktop: { "": 45000, Stop: 16000 },
|
|
22240
|
+
cursor: { "": 45000 }
|
|
22241
|
+
};
|
|
22242
|
+
var HOOK_SKIPS_FILE = "hook-skips.jsonl";
|
|
22243
|
+
var override = (env) => {
|
|
22244
|
+
const value = Number(env.WORKIT_HOOK_DEADLINE_MS);
|
|
22245
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
22246
|
+
};
|
|
22247
|
+
var hookDeadlineMs = (env, host = "", native) => {
|
|
22248
|
+
const row = HOST_DEADLINES[host];
|
|
22249
|
+
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
22250
|
+
};
|
|
22251
|
+
var skipsFile = (stateDir) => path26.join(stateDir, HOOK_SKIPS_FILE);
|
|
22252
|
+
var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
22253
|
+
try {
|
|
22254
|
+
mkdirSync3(stateDir, { recursive: true });
|
|
22255
|
+
appendFileSync(skipsFile(stateDir), `${JSON.stringify(skip)}
|
|
22256
|
+
`);
|
|
22257
|
+
} catch {}
|
|
22258
|
+
};
|
|
22259
|
+
var QUIET = new Set([
|
|
22260
|
+
"stop",
|
|
22261
|
+
"subagent.stop",
|
|
22262
|
+
"shell.post",
|
|
22263
|
+
"compact.pre",
|
|
22264
|
+
"prompt.submit",
|
|
22265
|
+
null
|
|
22266
|
+
]);
|
|
22267
|
+
var skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT };
|
|
22268
|
+
var TIMED_OUT = Symbol("timed out");
|
|
22269
|
+
var within = async (work, ms) => {
|
|
22270
|
+
let timer;
|
|
22271
|
+
const expired = new Promise((resolve) => {
|
|
22272
|
+
timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
|
|
22273
|
+
timer.unref?.();
|
|
22274
|
+
});
|
|
22275
|
+
try {
|
|
22276
|
+
return await Promise.race([work, expired]);
|
|
22277
|
+
} finally {
|
|
22278
|
+
clearTimeout(timer);
|
|
22279
|
+
}
|
|
22280
|
+
};
|
|
22281
|
+
var isTimedOut = (value) => value === TIMED_OUT;
|
|
22282
|
+
var PROCESS_START = performance.timeOrigin;
|
|
22283
|
+
var hookBudget = (env, options = {}, host = "") => {
|
|
22284
|
+
const now = options.now ?? Date.now;
|
|
22285
|
+
return {
|
|
22286
|
+
now,
|
|
22287
|
+
record: options.record ?? ((skip) => recordHookSkip(skip)),
|
|
22288
|
+
startedAt: options.startedAt ?? now(),
|
|
22289
|
+
deadlineMs: options.deadlineMs ?? hookDeadlineMs(env, host),
|
|
22290
|
+
host: options.deadlineMs === undefined ? host : null
|
|
22291
|
+
};
|
|
22292
|
+
};
|
|
22293
|
+
var readWithin = async (stdin, budget) => {
|
|
22294
|
+
const read = (async () => {
|
|
22295
|
+
let text = "";
|
|
22296
|
+
for await (const chunk of stdin)
|
|
22297
|
+
text += String(chunk);
|
|
22298
|
+
return text;
|
|
22299
|
+
})();
|
|
22300
|
+
const text = await within(read, budget.startedAt + budget.deadlineMs - budget.now());
|
|
22301
|
+
return isTimedOut(text) ? null : text;
|
|
22302
|
+
};
|
|
22303
|
+
var note = (adapter, raw, env, budget, outcome, stage) => {
|
|
22304
|
+
const parsed = raw === undefined ? null : adapter.parse(raw, env);
|
|
22305
|
+
const event = parsed ? parsed.ok ? parsed.input.event.kind : parsed.event : null;
|
|
22306
|
+
budget.record({
|
|
22307
|
+
at: new Date(budget.now()).toISOString(),
|
|
22308
|
+
host: adapter.descriptor.host,
|
|
22309
|
+
event,
|
|
22310
|
+
outcome,
|
|
22311
|
+
stage,
|
|
22312
|
+
elapsedMs: Math.round(budget.now() - budget.startedAt)
|
|
22313
|
+
});
|
|
22314
|
+
return { native: parsed?.native ?? null, event };
|
|
22315
|
+
};
|
|
22316
|
+
var skippedHook = (adapter, raw, env, budget, stage) => {
|
|
22317
|
+
const { native, event } = note(adapter, raw, env, budget, "skip", stage);
|
|
22318
|
+
return {
|
|
22319
|
+
...adapter.render(skipDecision(event), native),
|
|
22320
|
+
error: `${HOOK_SKIP_TEXT} (past the ${budget.deadlineMs} ms hook deadline)`,
|
|
22321
|
+
skipped: true
|
|
22322
|
+
};
|
|
22323
|
+
};
|
|
22324
|
+
var dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook) => {
|
|
22325
|
+
const native = raw?.hook_event_name;
|
|
22326
|
+
if (budget.host !== null && typeof native === "string")
|
|
22327
|
+
budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
|
|
22328
|
+
const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
|
|
22329
|
+
const result = remaining() > 0 ? await within(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
|
|
22330
|
+
if (isTimedOut(result))
|
|
22331
|
+
return skippedHook(adapter, raw, env, budget, "dispatch");
|
|
22332
|
+
if (remaining() < 0)
|
|
22333
|
+
note(adapter, raw, env, budget, "late", "done");
|
|
22334
|
+
return { ...result, skipped: false };
|
|
22335
|
+
};
|
|
22336
|
+
|
|
22231
22337
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
22232
22338
|
var CLAUDE_CODE_DESCRIPTOR = {
|
|
22233
22339
|
host: "claude_code",
|
|
@@ -22787,7 +22893,7 @@ var codexAdapter = {
|
|
|
22787
22893
|
|
|
22788
22894
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
22789
22895
|
import fs19 from "node:fs";
|
|
22790
|
-
import
|
|
22896
|
+
import path27 from "node:path";
|
|
22791
22897
|
var undocumented = { support: "undocumented", native: null };
|
|
22792
22898
|
var none = { support: "none", native: null };
|
|
22793
22899
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -22984,7 +23090,7 @@ var protocolEvent2 = (input) => {
|
|
|
22984
23090
|
};
|
|
22985
23091
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
22986
23092
|
var lastAssistantText = (file) => {
|
|
22987
|
-
if (!file || !
|
|
23093
|
+
if (!file || !path27.isAbsolute(file))
|
|
22988
23094
|
return null;
|
|
22989
23095
|
try {
|
|
22990
23096
|
const size = fs19.statSync(file).size;
|
|
@@ -23021,7 +23127,7 @@ var lastAssistantText = (file) => {
|
|
|
23021
23127
|
return null;
|
|
23022
23128
|
}
|
|
23023
23129
|
};
|
|
23024
|
-
var
|
|
23130
|
+
var within2 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path27.sep) ? root : `${root}${path27.sep}`);
|
|
23025
23131
|
var hookCwd = (input) => {
|
|
23026
23132
|
const cwd = existingDirectory(input.cwd);
|
|
23027
23133
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
@@ -23030,7 +23136,7 @@ var hookCwd = (input) => {
|
|
|
23030
23136
|
};
|
|
23031
23137
|
var cursorWorkspaceRoot = (input) => {
|
|
23032
23138
|
const cwd = existingDirectory(input.cwd);
|
|
23033
|
-
return cwd && input.workspace_roots.find((root) =>
|
|
23139
|
+
return cwd && input.workspace_roots.find((root) => within2(root, cwd)) || input.workspace_roots[0];
|
|
23034
23140
|
};
|
|
23035
23141
|
var cursorDeny = (reason) => ({
|
|
23036
23142
|
permission: "deny",
|
|
@@ -23095,19 +23201,21 @@ var HOOK_ADAPTERS = {
|
|
|
23095
23201
|
codex_desktop: codexAdapter,
|
|
23096
23202
|
cursor: cursorAdapter
|
|
23097
23203
|
};
|
|
23098
|
-
async function runHookProcess(host, stdin, stdout, stderr = process.stderr, env = process.env) {
|
|
23204
|
+
async function runHookProcess(host, stdin, stdout, stderr = process.stderr, env = process.env, deadline = {}) {
|
|
23099
23205
|
const adapter = typeof host === "string" ? HOOK_ADAPTERS[host] : host;
|
|
23100
|
-
|
|
23101
|
-
|
|
23102
|
-
text += String(chunk);
|
|
23206
|
+
const budget = hookBudget(env, deadline, adapter.descriptor.host);
|
|
23207
|
+
const text = await readWithin(stdin, budget);
|
|
23103
23208
|
let raw;
|
|
23104
23209
|
try {
|
|
23105
|
-
raw = JSON.parse(text || "{}");
|
|
23210
|
+
raw = text === null ? undefined : JSON.parse(text || "{}");
|
|
23106
23211
|
} catch {
|
|
23107
23212
|
raw = undefined;
|
|
23108
23213
|
}
|
|
23109
|
-
const result =
|
|
23110
|
-
if (
|
|
23214
|
+
const result = text === null ? skippedHook(adapter, undefined, env, budget, "input") : await dispatchWithin(adapter, raw, env, budget, deadline.dispatch);
|
|
23215
|
+
if (result.skipped)
|
|
23216
|
+
stderr.write(`[workit] ${result.error}
|
|
23217
|
+
`);
|
|
23218
|
+
else if (raw === undefined || result.error)
|
|
23111
23219
|
stderr.write(`[workit] hook input rejected: ${raw === undefined ? "invalid JSON hook input" : result.error}
|
|
23112
23220
|
`);
|
|
23113
23221
|
stdout.write(`${JSON.stringify(result.json)}
|
|
@@ -23124,7 +23232,7 @@ function warnOnSurfaceFallback(env = process.env) {
|
|
|
23124
23232
|
}
|
|
23125
23233
|
var runCodexHook = async (stdin = process.stdin) => {
|
|
23126
23234
|
warnOnSurfaceFallback();
|
|
23127
|
-
process.exitCode = await runHookProcess(detectCodexSurface(process.env), stdin, process.stdout);
|
|
23235
|
+
process.exitCode = await runHookProcess(detectCodexSurface(process.env), stdin, process.stdout, process.stderr, process.env, { startedAt: PROCESS_START });
|
|
23128
23236
|
};
|
|
23129
23237
|
if (false)
|
|
23130
23238
|
;
|
package/dist/workit-hook.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
maybeHeavy
|
|
4
|
-
} from "./workit-hook-
|
|
4
|
+
} from "./workit-hook-8yfjygc1.js";
|
|
5
5
|
|
|
6
6
|
// packages/workit-codex/hooks/launcher.ts
|
|
7
7
|
var payload = "";
|
|
@@ -39,6 +39,6 @@ if (quiet)
|
|
|
39
39
|
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
|
|
40
40
|
`);
|
|
41
41
|
else {
|
|
42
|
-
const { runCodexHook } = await import("./workit-hook-
|
|
42
|
+
const { runCodexHook } = await import("./workit-hook-q9ypsecg.js");
|
|
43
43
|
await runCodexHook([payload]);
|
|
44
44
|
}
|
package/hooks/hooks.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-codex",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.18.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit Codex CLI/desktop plugin — shared MCP transport, hooks, method skills, and verifier/reviewer/implementer agents",
|
|
6
6
|
"keywords": [
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"build": "bun scripts/build.ts"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@brainervirus/workit-core": "^8.
|
|
43
|
-
"@brainervirus/workit-mcp": "^8.
|
|
42
|
+
"@brainervirus/workit-core": "^8.18.0",
|
|
43
|
+
"@brainervirus/workit-mcp": "^8.18.0"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=24"
|
|
@@ -22,7 +22,7 @@ description: Turn requirements into Given/When/Then scenarios, agree the test se
|
|
|
22
22
|
then take the next scenario. Any edit makes the observation stale; re-run
|
|
23
23
|
before you claim it. A recorded "tests pass" is a note, and an ad-hoc
|
|
24
24
|
`workit check -- <cmd>` never satisfies the gate: only the configured
|
|
25
|
-
`test` check does. No `test` detected? Create
|
|
25
|
+
`test` check does. No `test` detected? Create `.workit/checks.json`, copying
|
|
26
26
|
in every check the repo already runs: once it exists it replaces the
|
|
27
27
|
detected defaults.
|
|
28
28
|
5. **Mock only at system boundaries:** network, clock, randomness, other
|
|
@@ -37,8 +37,8 @@ units of ~100+ lines each. Coupled work stays with one owner. Workit never spawn
|
|
|
37
37
|
6. **Retry once, then escalate.** A stuck or failed slice gets one retry with
|
|
38
38
|
a fresh brief (`workit fanout brief <slice> --mode resume`, same branch);
|
|
39
39
|
if that fails too, re-slice it, take it over, or report the gap. Never
|
|
40
|
-
chain resumes. A
|
|
41
|
-
|
|
40
|
+
chain resumes. A fix round and recheck that both fail: a decider, not a
|
|
41
|
+
third round; product choices go to the user (`references/orchestration.md`).
|
|
42
42
|
7. **Verify by the workspace `verification` setting** (`workit grant show`).
|
|
43
43
|
A report is a pointer, never evidence. `self`: a verifier that wrote none
|
|
44
44
|
of the slices, or you if you wrote none. `independent` or high risk: a
|
|
@@ -43,6 +43,27 @@ rulings). If the retry fails too, escalate instead of a third worker:
|
|
|
43
43
|
re-slice it smaller (`workit fanout plan` again), take it over yourself, or
|
|
44
44
|
report the gap to the user.
|
|
45
45
|
|
|
46
|
+
## Stuck calls: the decider
|
|
47
|
+
|
|
48
|
+
A failed review gets one fix round and one recheck (the changed hunks only).
|
|
49
|
+
If the recheck fails, or a worker and a reviewer disagree, spawn a decider
|
|
50
|
+
instead of a third round. A decider is a fresh read-only agent, not the author
|
|
51
|
+
or the reviewer. It returns three things: the options, a pick by
|
|
52
|
+
reversibility (the option cheapest to undo wins a tie), and the exact change
|
|
53
|
+
list. The fixer applies that list once, then one focused recheck follows.
|
|
54
|
+
|
|
55
|
+
- Hard call (shared contract, security, migration): two deciders on different
|
|
56
|
+
models (Claude Code: a different `model` on the Agent call; OpenCode: a
|
|
57
|
+
child session on another configured agent; Codex, Cursor and Pi: one
|
|
58
|
+
decider, since the host picks no model). If they disagree, you take the
|
|
59
|
+
more reversible option.
|
|
60
|
+
- Record it: `workit ledger decision "<pick>" --by decider --why "<why it is
|
|
61
|
+
the more reversible>" --view "<decider A: view>" --view "<decider B: view>"`.
|
|
62
|
+
The row is listed as a decider call; it is never the user's answer to a
|
|
63
|
+
product choice and never a verdict.
|
|
64
|
+
- A product choice, or authority nobody here has, is not a decider's call:
|
|
65
|
+
ask the user with your recommendation.
|
|
66
|
+
|
|
46
67
|
## Race a hard slice
|
|
47
68
|
|
|
48
69
|
When a hard slice has an uncertain approach and a cheap VERIFY, run 2-3
|
|
@@ -32,7 +32,7 @@ proposes and stops. Nothing changes until the user approves.
|
|
|
32
32
|
more cited occurrences**; a one-off is not a learning.
|
|
33
33
|
4. **Name the strongest enforcer that works**, strongest first: architecture
|
|
34
34
|
or types (the mistake cannot be written) > lint rule > configured check
|
|
35
|
-
(
|
|
35
|
+
(`.workit/checks.json`, `workit check <name>`) or CI job > test >
|
|
36
36
|
`CODING_STANDARDS.md` (judgment the reviewer reads) > AGENTS.md pointer
|
|
37
37
|
(navigation only). A mechanical rule gets a check, not prose: a check can
|
|
38
38
|
fail, a sentence cannot. From types to test, the proof is that the new
|
|
@@ -60,7 +60,7 @@ Bad: "Agents seem lost in the build. Added 'read the build docs carefully' to
|
|
|
60
60
|
AGENTS.md." One vague occurrence, no citation, a no-op line, auto-applied.
|
|
61
61
|
|
|
62
62
|
Good: "Navigation, 3 occurrences (rulings 12, 19; PR #88 thread): agents sought
|
|
63
|
-
check names in package.json, missing
|
|
63
|
+
check names in package.json, missing `.workit/checks.json`. Enforcer: AGENTS.md
|
|
64
64
|
pointer, +74 bytes, minus the stale Commands paragraph (-210). Unstable check,
|
|
65
65
|
2 occurrences (ledger rows 31, 44: `e2e` red then green on one SHA): a debug
|
|
66
66
|
slice to fix the check. Approve?"
|
|
@@ -36,7 +36,10 @@ independent; `type-check-only` never proves a behavior change.
|
|
|
36
36
|
7. Record the verdict:
|
|
37
37
|
`workit ledger verdict verified|failed|blocked --kind review --branch <b> --how "<what you ran and read>"`
|
|
38
38
|
under your own session (the one the lead or the hook gave you).
|
|
39
|
-
8. **
|
|
39
|
+
8. **Bound the loop.** A failed verdict gets one fix round and one recheck of
|
|
40
|
+
the changed hunks. Still failing, or stuck: no third round; the lead spawns
|
|
41
|
+
a decider (read the `workit-fanout` skill's SKILL.md and follow it). A product choice goes to the user.
|
|
42
|
+
9. **Harness note** (optional): friction caused by the repo or harness, not
|
|
40
43
|
the change (a flaky fixture, a missing check, a misleading doc or skill
|
|
41
44
|
line), goes on its own `Harness note:` line in the report and any PR
|
|
42
45
|
comment, never as a finding or in the verdict, so a later retro finds it.
|
|
@@ -30,7 +30,7 @@ once the verdict is accepted. `merged`: step 7. `null` (closed, not merged):
|
|
|
30
30
|
stop and report. Stop early only for a new consequential choice, a host
|
|
31
31
|
denial, a review comment that needs a product decision, a required update
|
|
32
32
|
that repeats because the base keeps moving, or after 3 failed fix attempts on
|
|
33
|
-
the same check.
|
|
33
|
+
the same check. Fix round and recheck both failed: a decider (read the `workit-fanout` skill's SKILL.md and follow it).
|
|
34
34
|
|
|
35
35
|
1. **Open.** `workit git push`, then `workit pr create --title "<title>"
|
|
36
36
|
--body-file <f|->` plus `--issue <#n|ID>` per issue (`--refs`: no close; idempotent;
|
|
@@ -35,7 +35,7 @@ way a user would.
|
|
|
35
35
|
never run is a guess; do not hand it off.
|
|
36
36
|
4. **Make the driver a named check** when it is cheap and deterministic. A
|
|
37
37
|
named check is one command without shell syntax (put pipes and setup in a
|
|
38
|
-
script). Add `"verify": "<driver>"` to `checks` in
|
|
38
|
+
script). Add `"verify": "<driver>"` to `checks` in `.workit/checks.json`; if
|
|
39
39
|
the file does not exist yet, first copy in every check the repo already runs
|
|
40
40
|
(test, lint, typecheck), because the file replaces the detected defaults.
|
|
41
41
|
`workit check verify` then records it, and it joins the verification gate
|