@brainervirus/workit-claude-code 8.17.1 → 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/.claude-plugin/plugin.json +1 -1
- package/bin/workit-hook.mjs +6 -4
- package/dist/workit-hook.js +69 -69
- package/dist/workit.js +126 -37
- package/package.json +3 -3
- package/skills/bdd/SKILL.md +1 -1
- package/skills/fanout/SKILL.md +2 -2
- package/skills/fanout/references/orchestration.md +21 -0
- package/skills/retro/SKILL.md +2 -2
- package/skills/review/SKILL.md +4 -1
- package/skills/ship/SKILL.md +1 -1
- package/skills/verify-app/SKILL.md +1 -1
package/dist/workit.js
CHANGED
|
@@ -65,7 +65,7 @@ var package_default;
|
|
|
65
65
|
var init_package = __esm(() => {
|
|
66
66
|
package_default = {
|
|
67
67
|
name: "@brainervirus/workit-cli",
|
|
68
|
-
version: "8.
|
|
68
|
+
version: "8.18.0",
|
|
69
69
|
private: false,
|
|
70
70
|
description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
71
71
|
keywords: [
|
|
@@ -103,13 +103,13 @@ var init_package = __esm(() => {
|
|
|
103
103
|
},
|
|
104
104
|
dependencies: {
|
|
105
105
|
"@babel/parser": "7.29.9",
|
|
106
|
-
"@brainervirus/workit-cursor": "^8.
|
|
106
|
+
"@brainervirus/workit-cursor": "^8.18.0",
|
|
107
107
|
"@inkjs/ui": "2.0.0",
|
|
108
108
|
ink: "7.1.1",
|
|
109
109
|
react: "19.2.8"
|
|
110
110
|
},
|
|
111
111
|
devDependencies: {
|
|
112
|
-
"@brainervirus/workit-core": "^8.
|
|
112
|
+
"@brainervirus/workit-core": "^8.18.0"
|
|
113
113
|
},
|
|
114
114
|
engines: {
|
|
115
115
|
node: ">=24"
|
|
@@ -170,6 +170,20 @@ var init_output = __esm(() => {
|
|
|
170
170
|
import { spawnSync } from "node:child_process";
|
|
171
171
|
import fs from "node:fs";
|
|
172
172
|
import path from "node:path";
|
|
173
|
+
function checksConfigFile(root) {
|
|
174
|
+
const current = path.join(root, CHECKS_FILE);
|
|
175
|
+
const legacy = path.join(root, LEGACY_CHECKS_FILE);
|
|
176
|
+
return !fs.existsSync(current) && fs.existsSync(legacy) ? legacy : current;
|
|
177
|
+
}
|
|
178
|
+
function ensureWorkitDir(root) {
|
|
179
|
+
const dir = path.join(root, ".workit");
|
|
180
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
181
|
+
const ignore = path.join(dir, ".gitignore");
|
|
182
|
+
if (!fs.existsSync(ignore))
|
|
183
|
+
fs.writeFileSync(ignore, `*
|
|
184
|
+
`);
|
|
185
|
+
return dir;
|
|
186
|
+
}
|
|
173
187
|
function repoHeavyCommands(root) {
|
|
174
188
|
try {
|
|
175
189
|
const value = JSON.parse(fs.readFileSync(checksConfigFile(root), "utf8"));
|
|
@@ -261,19 +275,25 @@ function detectedChecks(root) {
|
|
|
261
275
|
function loadCheckConfig(cwd) {
|
|
262
276
|
const root = checkRoot(cwd);
|
|
263
277
|
const file = checksConfigFile(root);
|
|
278
|
+
const legacy = path.basename(file) === LEGACY_CHECKS_FILE;
|
|
279
|
+
const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
|
|
280
|
+
const note = legacy ? {
|
|
281
|
+
note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`
|
|
282
|
+
} : {};
|
|
264
283
|
const none = (error = null) => ({
|
|
265
|
-
source: error ?
|
|
284
|
+
source: error ? label : "none",
|
|
266
285
|
root,
|
|
267
286
|
checks: [],
|
|
268
287
|
gates: {},
|
|
269
|
-
error
|
|
288
|
+
error,
|
|
289
|
+
...note
|
|
270
290
|
});
|
|
271
291
|
let text = null;
|
|
272
292
|
try {
|
|
273
293
|
text = fs.readFileSync(file, "utf8");
|
|
274
294
|
} catch (error) {
|
|
275
295
|
if (error.code !== "ENOENT")
|
|
276
|
-
return none(`cannot read ${
|
|
296
|
+
return none(`cannot read ${label}: ${error.message}`);
|
|
277
297
|
}
|
|
278
298
|
if (text === null) {
|
|
279
299
|
const checks = detectedChecks(root);
|
|
@@ -283,33 +303,33 @@ function loadCheckConfig(cwd) {
|
|
|
283
303
|
try {
|
|
284
304
|
parsed = JSON.parse(text);
|
|
285
305
|
} catch {
|
|
286
|
-
return none(`${
|
|
306
|
+
return none(`${label} is not valid JSON`);
|
|
287
307
|
}
|
|
288
308
|
if (!isRecord(parsed) || !isRecord(parsed.checks))
|
|
289
|
-
return none(`${
|
|
309
|
+
return none(`${label} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
290
310
|
const checks = [];
|
|
291
311
|
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
292
312
|
if (!CHECK_NAME.test(name))
|
|
293
|
-
return none(`${
|
|
313
|
+
return none(`${label}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
294
314
|
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");
|
|
295
315
|
if (argv instanceof Error)
|
|
296
|
-
return none(`${
|
|
316
|
+
return none(`${label}: check "${name}": ${argv.message}`);
|
|
297
317
|
checks.push({ name, argv, accepts: [argv] });
|
|
298
318
|
}
|
|
299
319
|
const gates = {};
|
|
300
320
|
if (parsed.gates !== undefined) {
|
|
301
321
|
if (!isRecord(parsed.gates))
|
|
302
|
-
return none(`${
|
|
322
|
+
return none(`${label}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
303
323
|
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
304
324
|
if (!GATES.includes(gate))
|
|
305
|
-
return none(`${
|
|
325
|
+
return none(`${label}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
306
326
|
const names = typeof value === "string" ? [value] : value;
|
|
307
327
|
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
308
|
-
return none(`${
|
|
328
|
+
return none(`${label}: gates.${gate} must name configured checks`);
|
|
309
329
|
gates[gate] = names;
|
|
310
330
|
}
|
|
311
331
|
}
|
|
312
|
-
return { source:
|
|
332
|
+
return { source: label, root, checks, gates, error: null, ...note, resources: parsed.resources };
|
|
313
333
|
}
|
|
314
334
|
function matchesNamedCheck(config, name, argv, cwd) {
|
|
315
335
|
if (config.error || name === null || cwd !== ".")
|
|
@@ -344,7 +364,7 @@ function workitAvailable(env = process.env) {
|
|
|
344
364
|
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
345
365
|
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
346
366
|
}
|
|
347
|
-
var SCRIPT_CHECKS, CHECKS_FILE = "workit
|
|
367
|
+
var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git = (cwd, args) => {
|
|
348
368
|
const run = spawnSync("git", args, {
|
|
349
369
|
cwd,
|
|
350
370
|
encoding: "utf8",
|
|
@@ -15647,6 +15667,10 @@ function checkSupersede(context, type, row = {}) {
|
|
|
15647
15667
|
return problem ? err("invalid_input", `--supersedes: ${problem}`) : { ok: true, value: undefined };
|
|
15648
15668
|
}
|
|
15649
15669
|
function recordDecision(context, input) {
|
|
15670
|
+
if (input.by !== undefined && input.by !== "decider")
|
|
15671
|
+
return err("invalid_input", `--by takes "decider", got "${input.by}"`);
|
|
15672
|
+
if (input.by === undefined && input.views?.length)
|
|
15673
|
+
return err("invalid_input", "--view needs --by decider");
|
|
15650
15674
|
const what = required2(input.what, "<what>");
|
|
15651
15675
|
if (!what.ok)
|
|
15652
15676
|
return what;
|
|
@@ -15661,7 +15685,9 @@ function recordDecision(context, input) {
|
|
|
15661
15685
|
type: "decision",
|
|
15662
15686
|
what: what.value,
|
|
15663
15687
|
why: why.value,
|
|
15664
|
-
refs: input.refs ?? []
|
|
15688
|
+
refs: input.refs ?? [],
|
|
15689
|
+
...input.by === "decider" ? { by: "decider" } : {},
|
|
15690
|
+
...input.views?.length ? { views: input.views } : {}
|
|
15665
15691
|
}, { now: context.now });
|
|
15666
15692
|
}
|
|
15667
15693
|
function recordRuling(context, input) {
|
|
@@ -15828,15 +15854,19 @@ function rowLabels(row) {
|
|
|
15828
15854
|
labels.push("ignored");
|
|
15829
15855
|
if (row.type === "verdict" && row.self === true)
|
|
15830
15856
|
labels.push("self");
|
|
15857
|
+
if (row.type === "decision" && row.by === "decider")
|
|
15858
|
+
labels.push("decider");
|
|
15831
15859
|
return labels;
|
|
15832
15860
|
}
|
|
15833
15861
|
function summarizeRow(row) {
|
|
15834
15862
|
const text = (key) => str2(row[key]) ?? "";
|
|
15835
15863
|
let summary;
|
|
15836
15864
|
switch (row.type) {
|
|
15837
|
-
case "decision":
|
|
15838
|
-
|
|
15865
|
+
case "decision": {
|
|
15866
|
+
const views = Array.isArray(row.views) ? row.views.map(String) : [];
|
|
15867
|
+
summary = `${row.by === "decider" ? "[decider] " : ""}${text("what")} (why: ${text("why")}${views.length ? `; views: ${views.join(" | ")}` : ""})`;
|
|
15839
15868
|
break;
|
|
15869
|
+
}
|
|
15840
15870
|
case "ruling":
|
|
15841
15871
|
summary = `${text("what")} (why: ${text("why")}; cost if wrong: ${text("costIfWrong")})`;
|
|
15842
15872
|
break;
|
|
@@ -16445,7 +16475,7 @@ var RULES, derived2 = (spec) => ({
|
|
|
16445
16475
|
};
|
|
16446
16476
|
const since = Date.parse(task.createdAt);
|
|
16447
16477
|
const branch = ledger.branch();
|
|
16448
|
-
const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
16478
|
+
const row = ledger.rows().findLast((item) => item.type === "decision" && item.by !== "decider" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
16449
16479
|
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
16450
16480
|
met: false,
|
|
16451
16481
|
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>"`)',
|
|
@@ -16680,7 +16710,7 @@ function evaluateObservedGate(task, requirement, statuses, config) {
|
|
|
16680
16710
|
return {
|
|
16681
16711
|
satisfied: [],
|
|
16682
16712
|
considered,
|
|
16683
|
-
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}
|
|
16713
|
+
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())}`
|
|
16684
16714
|
};
|
|
16685
16715
|
const satisfied = fresh.filter(named);
|
|
16686
16716
|
if (satisfied.length)
|
|
@@ -66219,6 +66249,7 @@ import {
|
|
|
66219
66249
|
readdirSync as readdirSync8,
|
|
66220
66250
|
readFileSync as readFileSync14,
|
|
66221
66251
|
readlinkSync as readlinkSync2,
|
|
66252
|
+
renameSync as renameSync4,
|
|
66222
66253
|
rmSync as rmSync3,
|
|
66223
66254
|
statSync as statSync5,
|
|
66224
66255
|
unlinkSync as unlinkSync3,
|
|
@@ -67995,6 +68026,36 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
|
67995
68026
|
status: "pass",
|
|
67996
68027
|
detail: "no hook passed its deadline in the last day"
|
|
67997
68028
|
};
|
|
68029
|
+
}, checkChecksConfig = (res) => {
|
|
68030
|
+
const root = checkRoot(res.cwd);
|
|
68031
|
+
const current = existsSync14(path34.join(root, CHECKS_FILE));
|
|
68032
|
+
const legacy = existsSync14(path34.join(root, LEGACY_CHECKS_FILE));
|
|
68033
|
+
if (!legacy)
|
|
68034
|
+
return {
|
|
68035
|
+
id: "checks_config",
|
|
68036
|
+
status: "pass",
|
|
68037
|
+
detail: current ? `${CHECKS_FILE} is the checks config` : "no per-repo checks config at the root"
|
|
68038
|
+
};
|
|
68039
|
+
return {
|
|
68040
|
+
id: "checks_config",
|
|
68041
|
+
status: "warn",
|
|
68042
|
+
detail: current ? `${LEGACY_CHECKS_FILE} at the repo root is ignored: ${CHECKS_FILE} wins` : `${LEGACY_CHECKS_FILE} at the repo root is a legacy location, still read`,
|
|
68043
|
+
fix: current ? `delete ${LEGACY_CHECKS_FILE} once ${CHECKS_FILE} has what you need` : "workit doctor --fix (moves it to .workit/checks.json)"
|
|
68044
|
+
};
|
|
68045
|
+
}, fixChecksConfig = (options = {}) => {
|
|
68046
|
+
const root = checkRoot(resolve(options).cwd);
|
|
68047
|
+
const from = path34.join(root, LEGACY_CHECKS_FILE);
|
|
68048
|
+
const to = path34.join(root, CHECKS_FILE);
|
|
68049
|
+
if (!existsSync14(from))
|
|
68050
|
+
return { action: "none", detail: `no ${LEGACY_CHECKS_FILE} at ${root}` };
|
|
68051
|
+
if (existsSync14(to))
|
|
68052
|
+
return {
|
|
68053
|
+
action: "skipped",
|
|
68054
|
+
detail: `${CHECKS_FILE} already exists; ${LEGACY_CHECKS_FILE} left in place`
|
|
68055
|
+
};
|
|
68056
|
+
ensureWorkitDir(root);
|
|
68057
|
+
renameSync4(from, to);
|
|
68058
|
+
return { action: "moved", detail: `${LEGACY_CHECKS_FILE} -> ${CHECKS_FILE}` };
|
|
67998
68059
|
}, CLAUDE_PLUGIN_PACKAGE = "@brainervirus/workit-claude-code", versionBehind = (version, latest) => version !== latest && semverAtLeast(latest, version), checkClaudePlugin = (res) => {
|
|
67999
68060
|
const installs = claudeWorkitInstalls(res.home, res.env, res.cwd);
|
|
68000
68061
|
if (installs.length === 0)
|
|
@@ -68181,6 +68242,7 @@ var init_doctor = __esm(() => {
|
|
|
68181
68242
|
init_branch_policy();
|
|
68182
68243
|
init_config();
|
|
68183
68244
|
init_logger();
|
|
68245
|
+
init_check_config();
|
|
68184
68246
|
init_heavy_scope();
|
|
68185
68247
|
init_package_root();
|
|
68186
68248
|
init_registration();
|
|
@@ -68222,7 +68284,8 @@ var init_doctor = __esm(() => {
|
|
|
68222
68284
|
checkGitLabIdentity,
|
|
68223
68285
|
checkLogWritable,
|
|
68224
68286
|
checkHeavyScope,
|
|
68225
|
-
checkHookDeadline
|
|
68287
|
+
checkHookDeadline,
|
|
68288
|
+
checkChecksConfig
|
|
68226
68289
|
];
|
|
68227
68290
|
INSTALLER_REQUIRED = new Set([
|
|
68228
68291
|
"runtime",
|
|
@@ -68247,7 +68310,7 @@ import {
|
|
|
68247
68310
|
readFileSync as readFileSync15,
|
|
68248
68311
|
readdirSync as readdirSync9,
|
|
68249
68312
|
realpathSync as realpathSync7,
|
|
68250
|
-
renameSync as
|
|
68313
|
+
renameSync as renameSync5,
|
|
68251
68314
|
rmSync as rmSync4,
|
|
68252
68315
|
statSync as statSync6,
|
|
68253
68316
|
writeFileSync as writeFileSync8
|
|
@@ -68536,7 +68599,7 @@ function applyMutation(m) {
|
|
|
68536
68599
|
detail: "workspace config changed while applying the preview; rebuild and review it"
|
|
68537
68600
|
};
|
|
68538
68601
|
}
|
|
68539
|
-
|
|
68602
|
+
renameSync5(tmp, m.path);
|
|
68540
68603
|
} catch (error) {
|
|
68541
68604
|
rmSync4(tmp, { force: true });
|
|
68542
68605
|
return {
|
|
@@ -68729,12 +68792,12 @@ function copyPluginDir(src, dest) {
|
|
|
68729
68792
|
throw new Error("staged adapter resolved to a symlink — refusing fragile cache install");
|
|
68730
68793
|
}
|
|
68731
68794
|
if (hadDir)
|
|
68732
|
-
|
|
68795
|
+
renameSync5(dest, backup);
|
|
68733
68796
|
try {
|
|
68734
|
-
|
|
68797
|
+
renameSync5(stage, dest);
|
|
68735
68798
|
} catch (error) {
|
|
68736
68799
|
if (hadDir && !existsSync15(dest))
|
|
68737
|
-
|
|
68800
|
+
renameSync5(backup, dest);
|
|
68738
68801
|
throw error;
|
|
68739
68802
|
}
|
|
68740
68803
|
rmSync4(backup, { recursive: true, force: true });
|
|
@@ -69257,7 +69320,7 @@ var init_setup = __esm(() => {
|
|
|
69257
69320
|
});
|
|
69258
69321
|
|
|
69259
69322
|
// packages/workit-core/src/core/workspace-branch-policy.ts
|
|
69260
|
-
import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, renameSync as
|
|
69323
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
69261
69324
|
import path36 from "node:path";
|
|
69262
69325
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
|
|
69263
69326
|
function applyWorkspaceBranchPolicy(opts) {
|
|
@@ -69335,7 +69398,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
69335
69398
|
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
69336
69399
|
};
|
|
69337
69400
|
}
|
|
69338
|
-
|
|
69401
|
+
renameSync6(tmp, wsPath);
|
|
69339
69402
|
} catch (error) {
|
|
69340
69403
|
try {
|
|
69341
69404
|
rmSync5(tmp, { force: true });
|
|
@@ -73936,6 +73999,17 @@ async function run6(argv, io) {
|
|
|
73936
73999
|
};
|
|
73937
74000
|
}
|
|
73938
74001
|
}
|
|
74002
|
+
let checksConfig = null;
|
|
74003
|
+
if (argv.includes("--fix")) {
|
|
74004
|
+
try {
|
|
74005
|
+
checksConfig = fixChecksConfig({ host: "cli", cwd: io.cwd });
|
|
74006
|
+
} catch (error) {
|
|
74007
|
+
checksConfig = {
|
|
74008
|
+
action: "failed",
|
|
74009
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
74010
|
+
};
|
|
74011
|
+
}
|
|
74012
|
+
}
|
|
73939
74013
|
const report = runDoctor({
|
|
73940
74014
|
host: "cli",
|
|
73941
74015
|
cwd: io.cwd,
|
|
@@ -73958,7 +74032,7 @@ async function run6(argv, io) {
|
|
|
73958
74032
|
budget: knowledge.budget,
|
|
73959
74033
|
findings: knowledge.findings.length
|
|
73960
74034
|
} : { unavailable: true };
|
|
73961
|
-
io.stdout(`${JSON.stringify({ ...report, ...fixLock ? { fixLock } : {}, ...sessionHook ? { sessionHook } : {}, knowledge: summary }, null, 2)}
|
|
74035
|
+
io.stdout(`${JSON.stringify({ ...report, ...fixLock ? { fixLock } : {}, ...sessionHook ? { sessionHook } : {}, ...checksConfig ? { checksConfig } : {}, knowledge: summary }, null, 2)}
|
|
73962
74036
|
`);
|
|
73963
74037
|
return report.exitCode;
|
|
73964
74038
|
}
|
|
@@ -73969,6 +74043,9 @@ async function run6(argv, io) {
|
|
|
73969
74043
|
}
|
|
73970
74044
|
if (sessionHook)
|
|
73971
74045
|
io.stdout(`fix: session hook ${sessionHook.action}: ${sessionHook.detail}
|
|
74046
|
+
`);
|
|
74047
|
+
if (checksConfig && checksConfig.action !== "none")
|
|
74048
|
+
io.stdout(`fix: checks config ${checksConfig.action}: ${checksConfig.detail}
|
|
73972
74049
|
`);
|
|
73973
74050
|
io.stdout(`workit doctor — ${report.ok ? "healthy" : "problems found"} (${report.offline ? "offline" : "online"})
|
|
73974
74051
|
`);
|
|
@@ -75824,6 +75901,9 @@ async function run10(argv, io) {
|
|
|
75824
75901
|
if (options instanceof Error)
|
|
75825
75902
|
return emit(io, fail("invalid_input", options.message, { unblock: USAGE3 }));
|
|
75826
75903
|
const config = loadCheckConfig(io.cwd);
|
|
75904
|
+
if (config.note)
|
|
75905
|
+
io.stderr(`note: ${config.note}
|
|
75906
|
+
`);
|
|
75827
75907
|
const top = checkRoot(io.cwd);
|
|
75828
75908
|
let command;
|
|
75829
75909
|
let runCwd = io.cwd;
|
|
@@ -75838,7 +75918,7 @@ async function run10(argv, io) {
|
|
|
75838
75918
|
if (!check)
|
|
75839
75919
|
return emit(io, fail("not_found", `no configured check named "${name}"`, {
|
|
75840
75920
|
data: { configured: config.checks.map((item) => item.name), source: config.source },
|
|
75841
|
-
unblock: config.checks.length ? `workit check ${config.checks.map((item) => item.name).join("|")}` : `add {"checks":{"${name}":"<command>"}} to workit
|
|
75921
|
+
unblock: config.checks.length ? `workit check ${config.checks.map((item) => item.name).join("|")}` : `add {"checks":{"${name}":"<command>"}} to .workit/checks.json, or run workit check --name ${name} -- <cmd…>`
|
|
75842
75922
|
}));
|
|
75843
75923
|
command = check.argv;
|
|
75844
75924
|
runCwd = config.root;
|
|
@@ -84795,7 +84875,8 @@ var DEFAULT_TIMEBOX = "30 minutes", REPORT, identityOf = (host) => {
|
|
|
84795
84875
|
' 2. Decide ambiguities yourself: `workit ledger ruling "<what>" --why "<why>" --cost-if-wrong "<cost>"`; stop only for an irreversible, security-sensitive or out-of-worktree action.',
|
|
84796
84876
|
plain ? ' 3. Commit with plain `git commit -m <msg> --trailer "Workit-Session: <the WORKIT_SESSION_ID in your env>" --trailer "<your host attribution trailer>" -- <paths in SCOPE>`; push only if this brief says so.' : " 3. Commit with `workit git commit -m <msg> -- <paths in SCOPE>`; push only if this brief says so.",
|
|
84797
84877
|
" 4. Never record a verdict on your own work.",
|
|
84798
|
-
" 5. Run tests, lint, typecheck and builds only through `workit check` (a named check, or `workit check --heavy -- <cmd>`): one machine-wide lease queues every agent, so never a hand-made flock; `workit lock status` shows who holds it."
|
|
84878
|
+
" 5. Run tests, lint, typecheck and builds only through `workit check` (a named check, or `workit check --heavy -- <cmd>`): one machine-wide lease queues every agent, so never a hand-made flock; `workit lock status` shows who holds it.",
|
|
84879
|
+
" 6. A failed review gets one fix round and one recheck. Still failing: stop and report the open finding, never a third round (the lead spawns a decider). A product choice is the user's: report it, do not decide it."
|
|
84799
84880
|
];
|
|
84800
84881
|
}, sessionSafe = (text) => text.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-"), SESSION_MAX = 128;
|
|
84801
84882
|
var init_fanout_brief = __esm(() => {
|
|
@@ -85889,7 +85970,13 @@ async function run19(argv, io) {
|
|
|
85889
85970
|
return result;
|
|
85890
85971
|
};
|
|
85891
85972
|
if (sub === "decision")
|
|
85892
|
-
return fromResult(io, await recorded(recordDecision(context, {
|
|
85973
|
+
return fromResult(io, await recorded(recordDecision(context, {
|
|
85974
|
+
what: text,
|
|
85975
|
+
why: values.why,
|
|
85976
|
+
refs: values.ref,
|
|
85977
|
+
by: values.by,
|
|
85978
|
+
views: values.view
|
|
85979
|
+
})), (row) => `recorded decision ${row.id}`);
|
|
85893
85980
|
if (sub === "ruling")
|
|
85894
85981
|
return fromResult(io, await recorded(recordRuling(context, {
|
|
85895
85982
|
what: text,
|
|
@@ -86062,6 +86149,8 @@ var init_ledger2 = __esm(() => {
|
|
|
86062
86149
|
why: { type: "string" },
|
|
86063
86150
|
"cost-if-wrong": { type: "string" },
|
|
86064
86151
|
ref: { type: "string", multiple: true },
|
|
86152
|
+
by: { type: "string" },
|
|
86153
|
+
view: { type: "string", multiple: true },
|
|
86065
86154
|
how: { type: "string" },
|
|
86066
86155
|
kind: { type: "string" },
|
|
86067
86156
|
surface: { type: "string" },
|
|
@@ -102082,7 +102171,7 @@ import {
|
|
|
102082
102171
|
readFileSync as readFileSync24,
|
|
102083
102172
|
readlinkSync as readlinkSync3,
|
|
102084
102173
|
realpathSync as realpathSync9,
|
|
102085
|
-
renameSync as
|
|
102174
|
+
renameSync as renameSync7,
|
|
102086
102175
|
rmSync as rmSync8,
|
|
102087
102176
|
statSync as statSync9,
|
|
102088
102177
|
symlinkSync,
|
|
@@ -102259,7 +102348,7 @@ function writeInside(copy, file, content) {
|
|
|
102259
102348
|
}
|
|
102260
102349
|
const temp = `${target}.workit-mutant-${process.pid}`;
|
|
102261
102350
|
writeFileSync11(temp, content);
|
|
102262
|
-
|
|
102351
|
+
renameSync7(temp, target);
|
|
102263
102352
|
return true;
|
|
102264
102353
|
}
|
|
102265
102354
|
function copyWorkingTree(repo) {
|
|
@@ -102688,7 +102777,7 @@ usage: ${USAGE12}`));
|
|
|
102688
102777
|
const runner = testCommand(repo, io.cwd, options.testCmd);
|
|
102689
102778
|
if (!runner)
|
|
102690
102779
|
return emit(io, fail("unavailable", "no test command for --mutate", {
|
|
102691
|
-
unblock: 'workit test-audit --mutate --test-cmd "<runner> {files}" (or configure a `test` check in workit
|
|
102780
|
+
unblock: 'workit test-audit --mutate --test-cmd "<runner> {files}" (or configure a `test` check in .workit/checks.json)'
|
|
102692
102781
|
}));
|
|
102693
102782
|
try {
|
|
102694
102783
|
mutation = await runMutation({
|
|
@@ -103586,8 +103675,8 @@ var VERBS = [
|
|
|
103586
103675
|
subcommands: [
|
|
103587
103676
|
{
|
|
103588
103677
|
name: "decision",
|
|
103589
|
-
usage: `workit ledger decision "<what>" --why "<why>" [--ref <path|url>]… ${LEDGER_ACTOR}`,
|
|
103590
|
-
summary: "Record a decision and why"
|
|
103678
|
+
usage: `workit ledger decision "<what>" --why "<why>" [--by decider [--view "<view>"]…] [--ref <path|url>]… ${LEDGER_ACTOR}`,
|
|
103679
|
+
summary: "Record a decision and why; --by decider marks a tie-breaker agent's call (not the user's answer, not a verdict)"
|
|
103591
103680
|
},
|
|
103592
103681
|
{
|
|
103593
103682
|
name: "ruling",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-claude-code",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.18.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit Claude Code plugin — session and per-turn task context, branch policy on git shell commands, workit 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-cli": "^8.
|
|
43
|
-
"@brainervirus/workit-core": "^8.
|
|
42
|
+
"@brainervirus/workit-cli": "^8.18.0",
|
|
43
|
+
"@brainervirus/workit-core": "^8.18.0"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=24"
|
package/skills/bdd/SKILL.md
CHANGED
|
@@ -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
|
package/skills/fanout/SKILL.md
CHANGED
|
@@ -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
|
package/skills/retro/SKILL.md
CHANGED
|
@@ -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?"
|
package/skills/review/SKILL.md
CHANGED
|
@@ -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 (call the Skill tool with `workit:fanout`). 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.
|
package/skills/ship/SKILL.md
CHANGED
|
@@ -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 (call the Skill tool with `workit:fanout`).
|
|
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
|