@brainervirus/workit-codex 8.16.0 → 8.17.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 +134 -2
- package/dist/{workit-hook-71xrmyzh.js → workit-hook-23t8ttjv.js} +1430 -737
- package/dist/workit-hook-rbb0m9zp.js +282 -0
- package/dist/workit-hook.js +5 -3
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +7 -7
- package/skills/workit-fanout/references/brief.md +3 -1
- package/skills/workit-implement/SKILL.md +8 -1
- package/skills/workit-review/SKILL.md +2 -1
- package/skills/workit-verify-app/SKILL.md +2 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// packages/workit-core/src/core/heavy-words.ts
|
|
4
|
+
import fs2 from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path2 from "node:path";
|
|
7
|
+
|
|
8
|
+
// packages/workit-core/src/check-config.ts
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
13
|
+
var CHECKS_FILE = "workit.checks.json";
|
|
14
|
+
var checksConfigFile = (root) => path.join(root, CHECKS_FILE);
|
|
15
|
+
function repoHeavyCommands(root) {
|
|
16
|
+
try {
|
|
17
|
+
const value = JSON.parse(fs.readFileSync(checksConfigFile(root), "utf8"));
|
|
18
|
+
const resources = isRecord(value) ? value.resources : undefined;
|
|
19
|
+
const list = isRecord(resources) ? resources.heavyCommands : undefined;
|
|
20
|
+
return Array.isArray(list) ? list.filter((item) => typeof item === "string") : [];
|
|
21
|
+
} catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
27
|
+
function splitCommand(command) {
|
|
28
|
+
const argv = [];
|
|
29
|
+
let current = "";
|
|
30
|
+
let quote = null;
|
|
31
|
+
let started = false;
|
|
32
|
+
for (const char of command) {
|
|
33
|
+
if (quote) {
|
|
34
|
+
if (char === quote)
|
|
35
|
+
quote = null;
|
|
36
|
+
else
|
|
37
|
+
current += char;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (char === '"' || char === "'") {
|
|
41
|
+
quote = char;
|
|
42
|
+
started = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (/\s/u.test(char)) {
|
|
46
|
+
if (started)
|
|
47
|
+
argv.push(current);
|
|
48
|
+
current = "";
|
|
49
|
+
started = false;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (SHELL_META.test(char))
|
|
53
|
+
return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
|
|
54
|
+
current += char;
|
|
55
|
+
started = true;
|
|
56
|
+
}
|
|
57
|
+
if (quote)
|
|
58
|
+
return new Error(`"${command}" has an unterminated quote`);
|
|
59
|
+
if (started)
|
|
60
|
+
argv.push(current);
|
|
61
|
+
return argv.length ? argv : new Error("empty command");
|
|
62
|
+
}
|
|
63
|
+
var git = (cwd, args) => {
|
|
64
|
+
const run = spawnSync("git", args, {
|
|
65
|
+
cwd,
|
|
66
|
+
encoding: "utf8",
|
|
67
|
+
timeout: 20000,
|
|
68
|
+
killSignal: "SIGKILL",
|
|
69
|
+
windowsHide: true
|
|
70
|
+
});
|
|
71
|
+
const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
|
|
72
|
+
return value || null;
|
|
73
|
+
};
|
|
74
|
+
function checkRoot(cwd) {
|
|
75
|
+
return path.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
76
|
+
}
|
|
77
|
+
var packageManager = (root) => {
|
|
78
|
+
const has = (file) => fs.existsSync(path.join(root, file));
|
|
79
|
+
if (has("bun.lock") || has("bun.lockb"))
|
|
80
|
+
return "bun";
|
|
81
|
+
if (has("pnpm-lock.yaml"))
|
|
82
|
+
return "pnpm";
|
|
83
|
+
if (has("yarn.lock"))
|
|
84
|
+
return "yarn";
|
|
85
|
+
return "npm";
|
|
86
|
+
};
|
|
87
|
+
var scriptCheck = (pm, name) => {
|
|
88
|
+
const argv = [pm, "run", name];
|
|
89
|
+
const accepts = [argv];
|
|
90
|
+
if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
|
|
91
|
+
accepts.push([pm, name]);
|
|
92
|
+
return { name, argv, accepts };
|
|
93
|
+
};
|
|
94
|
+
var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
95
|
+
function detectedChecks(root) {
|
|
96
|
+
const read = (file) => {
|
|
97
|
+
try {
|
|
98
|
+
return fs.readFileSync(path.join(root, file), "utf8");
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const checks = [];
|
|
104
|
+
const add = (check) => {
|
|
105
|
+
if (!checks.some((item) => item.name === check.name))
|
|
106
|
+
checks.push(check);
|
|
107
|
+
};
|
|
108
|
+
const manifestText = read("package.json");
|
|
109
|
+
if (manifestText !== null) {
|
|
110
|
+
let manifest = null;
|
|
111
|
+
try {
|
|
112
|
+
manifest = JSON.parse(manifestText);
|
|
113
|
+
} catch {}
|
|
114
|
+
const scripts = isRecord(manifest) && isRecord(manifest.scripts) ? manifest.scripts : {};
|
|
115
|
+
const pm = packageManager(root);
|
|
116
|
+
for (const name of SCRIPT_CHECKS) {
|
|
117
|
+
const script = scripts[name];
|
|
118
|
+
if (typeof script === "string" && script.trim() !== "")
|
|
119
|
+
add(scriptCheck(pm, name));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const plain = (name, argv) => ({ name, argv, accepts: [argv] });
|
|
123
|
+
if (read("go.mod") !== null)
|
|
124
|
+
add(plain("test", ["go", "test", "./..."]));
|
|
125
|
+
if (read("Cargo.toml") !== null)
|
|
126
|
+
add(plain("test", ["cargo", "test"]));
|
|
127
|
+
if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
|
|
128
|
+
add(plain("test", ["pytest"]));
|
|
129
|
+
const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
|
|
130
|
+
if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
|
|
131
|
+
add(plain("test", ["make", "test"]));
|
|
132
|
+
return checks;
|
|
133
|
+
}
|
|
134
|
+
var GATES = ["testing", "verification"];
|
|
135
|
+
function loadCheckConfig(cwd) {
|
|
136
|
+
const root = checkRoot(cwd);
|
|
137
|
+
const file = checksConfigFile(root);
|
|
138
|
+
const none = (error = null) => ({
|
|
139
|
+
source: error ? CHECKS_FILE : "none",
|
|
140
|
+
root,
|
|
141
|
+
checks: [],
|
|
142
|
+
gates: {},
|
|
143
|
+
error
|
|
144
|
+
});
|
|
145
|
+
let text = null;
|
|
146
|
+
try {
|
|
147
|
+
text = fs.readFileSync(file, "utf8");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error.code !== "ENOENT")
|
|
150
|
+
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
if (text === null) {
|
|
153
|
+
const checks = detectedChecks(root);
|
|
154
|
+
return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
|
|
155
|
+
}
|
|
156
|
+
let parsed;
|
|
157
|
+
try {
|
|
158
|
+
parsed = JSON.parse(text);
|
|
159
|
+
} catch {
|
|
160
|
+
return none(`${CHECKS_FILE} is not valid JSON`);
|
|
161
|
+
}
|
|
162
|
+
if (!isRecord(parsed) || !isRecord(parsed.checks))
|
|
163
|
+
return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
164
|
+
const checks = [];
|
|
165
|
+
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
166
|
+
if (!CHECK_NAME.test(name))
|
|
167
|
+
return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
168
|
+
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
|
+
if (argv instanceof Error)
|
|
170
|
+
return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
|
|
171
|
+
checks.push({ name, argv, accepts: [argv] });
|
|
172
|
+
}
|
|
173
|
+
const gates = {};
|
|
174
|
+
if (parsed.gates !== undefined) {
|
|
175
|
+
if (!isRecord(parsed.gates))
|
|
176
|
+
return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
177
|
+
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
178
|
+
if (!GATES.includes(gate))
|
|
179
|
+
return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
180
|
+
const names = typeof value === "string" ? [value] : value;
|
|
181
|
+
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
182
|
+
return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
|
|
183
|
+
gates[gate] = names;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
|
|
187
|
+
}
|
|
188
|
+
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
189
|
+
function matchesNamedCheck(config, name, argv, cwd) {
|
|
190
|
+
if (config.error || name === null || cwd !== ".")
|
|
191
|
+
return false;
|
|
192
|
+
const check = config.checks.find((item) => item.name === name);
|
|
193
|
+
return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
|
|
194
|
+
}
|
|
195
|
+
function gateCheckNames(config, dimension) {
|
|
196
|
+
const explicit = config.gates[dimension];
|
|
197
|
+
if (explicit)
|
|
198
|
+
return explicit;
|
|
199
|
+
const names = config.checks.map((check) => check.name);
|
|
200
|
+
if (dimension === "testing")
|
|
201
|
+
return names.includes("test") ? ["test"] : [];
|
|
202
|
+
return names;
|
|
203
|
+
}
|
|
204
|
+
var workitOnPath;
|
|
205
|
+
function workitAvailable(env = process.env) {
|
|
206
|
+
if (workitOnPath !== undefined)
|
|
207
|
+
return workitOnPath;
|
|
208
|
+
const windows = process.platform === "win32";
|
|
209
|
+
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
210
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path.delimiter).filter(Boolean);
|
|
211
|
+
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
212
|
+
try {
|
|
213
|
+
return fs.statSync(path.join(dir, `workit${ext}`)).isFile();
|
|
214
|
+
} catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
218
|
+
return workitOnPath;
|
|
219
|
+
}
|
|
220
|
+
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
221
|
+
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// packages/workit-core/src/core/heavy-words.ts
|
|
225
|
+
var HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
|
|
226
|
+
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
227
|
+
var stateDir = (env) => {
|
|
228
|
+
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
229
|
+
return env.WORKFLOW_TOOLKIT_STATE;
|
|
230
|
+
if (env.XDG_STATE_HOME)
|
|
231
|
+
return path2.join(env.XDG_STATE_HOME, "workit");
|
|
232
|
+
const home = env.HOME || os.homedir();
|
|
233
|
+
if (process.platform === "darwin")
|
|
234
|
+
return path2.join(home, "Library", "Application Support", "workit");
|
|
235
|
+
if (process.platform === "win32")
|
|
236
|
+
return path2.join(env.LOCALAPPDATA ?? path2.join(home, "AppData", "Local"), "workit");
|
|
237
|
+
return path2.join(home, ".local", "state", "workit");
|
|
238
|
+
};
|
|
239
|
+
var readJson = (file) => {
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(fs2.readFileSync(file, "utf8"));
|
|
242
|
+
} catch {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
var patternWords = (cwd, env) => {
|
|
247
|
+
const patterns = [];
|
|
248
|
+
const machine = readJson(path2.join(stateDir(env), "heavy-commands.json"));
|
|
249
|
+
if (Array.isArray(machine?.patterns))
|
|
250
|
+
patterns.push(...machine.patterns);
|
|
251
|
+
for (let dir = path2.resolve(cwd);; dir = path2.dirname(dir)) {
|
|
252
|
+
if (fs2.existsSync(path2.join(dir, ".git"))) {
|
|
253
|
+
patterns.push(...repoHeavyCommands(dir));
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
if (path2.dirname(dir) === dir)
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
const words = new Set;
|
|
260
|
+
for (const pattern of patterns) {
|
|
261
|
+
if (typeof pattern !== "string")
|
|
262
|
+
continue;
|
|
263
|
+
const first = wordsOf(pattern)[0];
|
|
264
|
+
if (!first || first.includes("*"))
|
|
265
|
+
return null;
|
|
266
|
+
words.add(first);
|
|
267
|
+
}
|
|
268
|
+
return words;
|
|
269
|
+
};
|
|
270
|
+
function maybeHeavy(command, cwd, env = process.env) {
|
|
271
|
+
try {
|
|
272
|
+
const words = wordsOf(command);
|
|
273
|
+
if (words.some((word) => HEAVY_WORDS.has(word) || /^python[\d.]*$/u.test(word)))
|
|
274
|
+
return true;
|
|
275
|
+
const extra = patternWords(cwd, env);
|
|
276
|
+
return extra === null || words.some((word) => extra.has(word));
|
|
277
|
+
} catch {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export { CHECKS_FILE, repoHeavyCommands, loadCheckConfig, matchesNamedCheck, gateCheckNames, checkCommand, maybeHeavy };
|
package/dist/workit-hook.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
maybeHeavy
|
|
4
|
+
} from "./workit-hook-rbb0m9zp.js";
|
|
2
5
|
|
|
3
6
|
// packages/workit-codex/hooks/launcher.ts
|
|
4
|
-
import process from "node:process";
|
|
5
7
|
var payload = "";
|
|
6
8
|
for await (const chunk of process.stdin)
|
|
7
9
|
payload += String(chunk);
|
|
@@ -27,7 +29,7 @@ var quietEvent = () => {
|
|
|
27
29
|
return null;
|
|
28
30
|
if (event === "PostToolUse")
|
|
29
31
|
return /\bcommit\b/.test(text) ? null : event;
|
|
30
|
-
return /\b(?:git|gh|glab)\b/.test(text) ? null : event;
|
|
32
|
+
return /\b(?:git|gh|glab)\b/.test(text) || maybeHeavy(text, value.cwd) ? null : event;
|
|
31
33
|
} catch {
|
|
32
34
|
return null;
|
|
33
35
|
}
|
|
@@ -37,6 +39,6 @@ if (quiet)
|
|
|
37
39
|
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
|
|
38
40
|
`);
|
|
39
41
|
else {
|
|
40
|
-
const { runCodexHook } = await import("./workit-hook-
|
|
42
|
+
const { runCodexHook } = await import("./workit-hook-23t8ttjv.js");
|
|
41
43
|
await runCodexHook([payload]);
|
|
42
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-codex",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.17.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.17.0",
|
|
43
|
+
"@brainervirus/workit-mcp": "^8.17.0"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=24"
|
|
@@ -5,13 +5,13 @@ description: Run independent slices in parallel - one worker per isolated worktr
|
|
|
5
5
|
|
|
6
6
|
# Fan out parallel workers
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
Inline-first (workspace `delegation`): fan out only past a threshold, named as
|
|
9
|
+
each slice's `reason` (explore, parallel, backstop, high-risk-verify): an
|
|
10
|
+
explorer past ~3 tool calls or 10k tokens; parallel writers only for 2+ disjoint
|
|
11
|
+
units of ~100+ lines each. Coupled work stays with one owner. Workit never spawns.
|
|
12
12
|
|
|
13
13
|
1. **Plan the slices** (read the `workit-shape` skill's SKILL.md and follow it) in a plan file (`references/brief.md`):
|
|
14
|
-
per slice an id, branch, TIER, SCOPE globs, `owns` for shared files,
|
|
14
|
+
per slice an id, branch, REASON, TIER, SCOPE globs, `owns` for shared files,
|
|
15
15
|
`dependsOn` only for a real dependency, and the brief fields. `workit fanout
|
|
16
16
|
plan <plan.json>` refuses an empty or placeholder brief field (exit 2) and
|
|
17
17
|
two slices that may write one file (exit 3) with a fix; apply it and re-run:
|
|
@@ -21,8 +21,8 @@ ceremony; do it yourself. Workit never spawns agents; you do.
|
|
|
21
21
|
2. **Record standing orders** (`workit ledger standing add "<order>"`): every
|
|
22
22
|
user directive all workers share. Directives decay across resumes.
|
|
23
23
|
3. **Brief each worker verbatim** with `workit fanout brief <slice>`: plan
|
|
24
|
-
fields, standing orders, SCRATCH, session id, fan-in
|
|
25
|
-
it; change the plan or the standing orders and render again.
|
|
24
|
+
fields, standing orders, SCRATCH, session id, fan-in and heavy-lease rules.
|
|
25
|
+
Never hand-edit it; change the plan or the standing orders and render again.
|
|
26
26
|
4. **Spawn in a rolling window**, in the background, each in its own worktree:
|
|
27
27
|
keep 4-6 in flight and refill from `spawnable` in `workit fanout status` as
|
|
28
28
|
each one finishes. Model: the brief's `model:` line, else TIER
|
|
@@ -59,7 +59,8 @@ defaults to the trunk, or to the branch of a single `dependsOn` slice (a
|
|
|
59
59
|
stack); `worktree` defaults to `../<repo>-wt/<id>`. `owns` claims a shared
|
|
60
60
|
file another slice's glob also matches. Globs that match no file yet are compared
|
|
61
61
|
through a sample path. Use `/` as the separator; a backslash only escapes
|
|
62
|
-
literal brackets: `"app/\\[id\\]/page.tsx"`. A `
|
|
62
|
+
literal brackets: `"app/\\[id\\]/page.tsx"`. A `reason` (`explore`, `parallel`, `backstop` or `high-risk-verify`) is rendered as the brief's `REASON:` line and is
|
|
63
|
+
required unless the workspace sets `delegation` to `lead-orchestrates`. A `timebox` such as `45 minutes`
|
|
63
64
|
is the slice's STUCK threshold in `workit fanout status`. `"fanIn":
|
|
64
65
|
"integration"` (with an integration branch as `trunk`) is the one-PR mode in
|
|
65
66
|
`orchestration.md`; the default `"prs"` lands one PR per slice.
|
|
@@ -79,6 +80,7 @@ is the slice's STUCK threshold in `workit fanout status`. `"fanIn":
|
|
|
79
80
|
"acceptance": ["Given no auth header, When GET /v1/usage, Then the status is 401"],
|
|
80
81
|
"verify": ["workit check test"],
|
|
81
82
|
"forbidden": ["no edits outside SCOPE", "no rebase or force-push"],
|
|
83
|
+
"reason": "parallel",
|
|
82
84
|
"context": "docs/usage/spec.md Behavior",
|
|
83
85
|
"timebox": "45 minutes"
|
|
84
86
|
},
|
|
@@ -19,7 +19,9 @@ description: Build a requested change in small verified steps - follow local pat
|
|
|
19
19
|
Mechanical change: the existing checks are enough. No tautological tests:
|
|
20
20
|
a test must fail if the behavior breaks, never restate the implementation.
|
|
21
21
|
4. Run the real checks: `workit check test` (and `lint`, `typecheck` when the
|
|
22
|
-
repo has them).
|
|
22
|
+
repo has them). Heavy work runs under one machine-wide lease: an ad-hoc
|
|
23
|
+
suite is `workit check --heavy -- <cmd>`, never a hand-made flock; a busy
|
|
24
|
+
lease waits and names its holder (`workit lock status`).
|
|
23
25
|
5. Prove the feature on its real surface with the project's `verify-<app>`
|
|
24
26
|
skill; none yet? Write one (read the `workit-verify-app` skill's SKILL.md and follow it). Tests show branch behavior,
|
|
25
27
|
not that the feature works.
|
|
@@ -41,6 +43,11 @@ description: Build a requested change in small verified steps - follow local pat
|
|
|
41
43
|
|
|
42
44
|
Independent slices that could run in parallel: fan them out (read the `workit-fanout` skill's SKILL.md and follow it).
|
|
43
45
|
|
|
46
|
+
Stay inline by default (workspace `delegation: inline-first`). Hand work off
|
|
47
|
+
only past the thresholds: an explorer after about 3 tool calls or 10k tokens
|
|
48
|
+
of searching, parallel writers for 2 or more disjoint units of about 100+
|
|
49
|
+
lines each.
|
|
50
|
+
|
|
44
51
|
## Example
|
|
45
52
|
|
|
46
53
|
Bad: "Done - added the --since flag, tests pass." (no check ran this turn; the
|
|
@@ -24,7 +24,8 @@ independent; `type-check-only` never proves a behavior change.
|
|
|
24
24
|
- **Standards:** repo rules first (`CODING_STANDARDS.md` when present),
|
|
25
25
|
then a smell baseline (unclear name, long function, duplicated logic,
|
|
26
26
|
leaky abstraction). Judgment only; lint owns nits.
|
|
27
|
-
4. **Tests:**
|
|
27
|
+
4. **Tests:** run suites under the heavy-check lease (`workit check test`,
|
|
28
|
+
`workit check --heavy -- <cmd>`); `workit test-audit --diff`. Would each new test fail if the
|
|
28
29
|
behavior broke? Triage the findings (read the `workit-test-audit` skill's SKILL.md and follow it).
|
|
29
30
|
5. **Blast radius:** for each touched contract, caller, config or migration,
|
|
30
31
|
state the one fact it is safe because of and run the proof. Anything
|
|
@@ -21,6 +21,8 @@ way a user would.
|
|
|
21
21
|
`.cursor/skills/`; other hosts: the directory their docs name). If the repo
|
|
22
22
|
already has a project skills directory, use it:
|
|
23
23
|
- **Launch:** one command, its ready signal (log line, port, exit 0), a timeout.
|
|
24
|
+
Builds, installs and e2e suites run under the heavy-check lease
|
|
25
|
+
(`workit check --heavy -- <cmd>`); a long-running server does not.
|
|
24
26
|
- **Doctor:** each precondition and its fix (deps, env vars, ports, services).
|
|
25
27
|
- **Drive:** how to exercise it: CLI invocations, `curl` against routes, a
|
|
26
28
|
browser tool for UI, with fixture data.
|