@brainervirus/workit-pi 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/dist/workit.js CHANGED
@@ -12429,8 +12429,13 @@ import { spawnSync as spawnSync3 } from "node:child_process";
12429
12429
  import fs9 from "node:fs";
12430
12430
  import path9 from "node:path";
12431
12431
  var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
12432
- var CHECKS_FILE = "workit.checks.json";
12433
- var checksConfigFile = (root) => path9.join(root, CHECKS_FILE);
12432
+ var CHECKS_FILE = ".workit/checks.json";
12433
+ var LEGACY_CHECKS_FILE = "workit.checks.json";
12434
+ function checksConfigFile(root) {
12435
+ const current = path9.join(root, CHECKS_FILE);
12436
+ const legacy = path9.join(root, LEGACY_CHECKS_FILE);
12437
+ return !fs9.existsSync(current) && fs9.existsSync(legacy) ? legacy : current;
12438
+ }
12434
12439
  function repoHeavyCommands(root) {
12435
12440
  try {
12436
12441
  const value = JSON.parse(fs9.readFileSync(checksConfigFile(root), "utf8"));
@@ -12554,19 +12559,25 @@ var GATES = ["testing", "verification"];
12554
12559
  function loadCheckConfig(cwd) {
12555
12560
  const root = checkRoot(cwd);
12556
12561
  const file = checksConfigFile(root);
12562
+ const legacy = path9.basename(file) === LEGACY_CHECKS_FILE;
12563
+ const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
12564
+ const note = legacy ? {
12565
+ note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`
12566
+ } : {};
12557
12567
  const none = (error = null) => ({
12558
- source: error ? CHECKS_FILE : "none",
12568
+ source: error ? label : "none",
12559
12569
  root,
12560
12570
  checks: [],
12561
12571
  gates: {},
12562
- error
12572
+ error,
12573
+ ...note
12563
12574
  });
12564
12575
  let text = null;
12565
12576
  try {
12566
12577
  text = fs9.readFileSync(file, "utf8");
12567
12578
  } catch (error) {
12568
12579
  if (error.code !== "ENOENT")
12569
- return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
12580
+ return none(`cannot read ${label}: ${error.message}`);
12570
12581
  }
12571
12582
  if (text === null) {
12572
12583
  const checks = detectedChecks(root);
@@ -12576,33 +12587,33 @@ function loadCheckConfig(cwd) {
12576
12587
  try {
12577
12588
  parsed = JSON.parse(text);
12578
12589
  } catch {
12579
- return none(`${CHECKS_FILE} is not valid JSON`);
12590
+ return none(`${label} is not valid JSON`);
12580
12591
  }
12581
12592
  if (!isRecord(parsed) || !isRecord(parsed.checks))
12582
- return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
12593
+ return none(`${label} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
12583
12594
  const checks = [];
12584
12595
  for (const [name, value] of Object.entries(parsed.checks)) {
12585
12596
  if (!CHECK_NAME.test(name))
12586
- return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
12597
+ return none(`${label}: check name "${name}" must match ${CHECK_NAME.source}`);
12587
12598
  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");
12588
12599
  if (argv instanceof Error)
12589
- return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
12600
+ return none(`${label}: check "${name}": ${argv.message}`);
12590
12601
  checks.push({ name, argv, accepts: [argv] });
12591
12602
  }
12592
12603
  const gates = {};
12593
12604
  if (parsed.gates !== undefined) {
12594
12605
  if (!isRecord(parsed.gates))
12595
- return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
12606
+ return none(`${label}: gates must be {"testing":"<name>"|["<name>",…]}`);
12596
12607
  for (const [gate, value] of Object.entries(parsed.gates)) {
12597
12608
  if (!GATES.includes(gate))
12598
- return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
12609
+ return none(`${label}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
12599
12610
  const names = typeof value === "string" ? [value] : value;
12600
12611
  if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
12601
- return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
12612
+ return none(`${label}: gates.${gate} must name configured checks`);
12602
12613
  gates[gate] = names;
12603
12614
  }
12604
12615
  }
12605
- return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
12616
+ return { source: label, root, checks, gates, error: null, ...note, resources: parsed.resources };
12606
12617
  }
12607
12618
  var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
12608
12619
  function matchesNamedCheck(config, name, argv, cwd) {
@@ -15304,7 +15315,7 @@ var productDecision = (task, ledger) => {
15304
15315
  };
15305
15316
  const since = Date.parse(task.createdAt);
15306
15317
  const branch = ledger.branch();
15307
- const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
15318
+ const row = ledger.rows().findLast((item) => item.type === "decision" && item.by !== "decider" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
15308
15319
  return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
15309
15320
  met: false,
15310
15321
  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>"`)',
@@ -15799,7 +15810,7 @@ function evaluateObservedGate(task, requirement, statuses, config) {
15799
15810
  return {
15800
15811
  satisfied: [],
15801
15812
  considered,
15802
- 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} (committed, so it shows in the diff), e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`
15813
+ 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())}`
15803
15814
  };
15804
15815
  const satisfied = fresh.filter(named);
15805
15816
  if (satisfied.length)
@@ -24019,6 +24030,18 @@ function handleHook(input, deps) {
24019
24030
  return NONE5;
24020
24031
  }
24021
24032
  }
24033
+ // packages/workit-core/src/hooks/deadline.ts
24034
+ var QUIET = new Set([
24035
+ "stop",
24036
+ "subagent.stop",
24037
+ "shell.post",
24038
+ "compact.pre",
24039
+ "prompt.submit",
24040
+ null
24041
+ ]);
24042
+ var TIMED_OUT = Symbol("timed out");
24043
+ var PROCESS_START = performance.timeOrigin;
24044
+
24022
24045
  // packages/workit-core/src/hooks/hosts/claude-code.ts
24023
24046
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
24024
24047
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-pi",
3
- "version": "8.17.0",
3
+ "version": "8.18.0",
4
4
  "description": "Workit native extension for Pi — tools, method skills, and session continuity",
5
5
  "keywords": [
6
6
  "agentic-coding",
@@ -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 `workit.checks.json`, copying
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 hard slice may race instead: N attempts, keep the best
41
- verdict (`references/orchestration.md`).
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
- (`workit.checks.json`, `workit check <name>`) or CI job > test >
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 `workit.checks.json`. Enforcer: AGENTS.md
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. **Harness note** (optional): friction caused by the repo or harness, not
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 `workit.checks.json`; if
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