@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.
@@ -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 };
@@ -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-71xrmyzh.js");
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.16.0",
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.16.0",
43
- "@brainervirus/workit-mcp": "^8.16.0"
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
- Fan out only independent slices: disjoint files, no shared mutable state, each
9
- verifiable alone. Code-coupled work stays with one owner, who fans out after
10
- the blocking part lands. A worker whose whole job is re-running one command is
11
- ceremony; do it yourself. Workit never spawns agents; you do.
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 rule. Never hand-edit
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 `timebox` such as `45 minutes`
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:** `workit test-audit --diff`. Would each new test fail if the
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.