@brainervirus/workit-cursor 8.29.0 → 8.29.2

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.
@@ -2,7 +2,7 @@
2
2
  "name": "workit",
3
3
  "displayName": "Workit",
4
4
  "description": "Adaptive Workit workflow with shared MCP tools and native Cursor hooks",
5
- "version": "8.29.0",
5
+ "version": "8.29.2",
6
6
  "author": {
7
7
  "name": "Cristhofer Pincetti"
8
8
  },
@@ -15689,13 +15689,13 @@ Autonomy contract:
15689
15689
  or an unresolved blocker; report open gaps and uncertain workers.
15690
15690
  - No endpoint named: stop at a local commit on a policy-compliant branch and
15691
15691
  state the next command. If the request implies delivery (fix, implement,
15692
- ship), go to the effective endpoint in \`workit grant show\`: \`pr\` opens
15693
- the PR; \`green\` babysits it without asking (workit-ship) to merge-ready,
15694
- never merging; \`merged\` also lands it with \`workit pr merge\`.
15695
- Merge and release need a workspace grant only the user raises from their
15696
- own terminal (a blocked verb names how); never edit workspaces.json or route
15697
- around grant_required. Release: \`workit release start <v>\`, commits,
15698
- \`finish\`; \`publish\` if asked.
15692
+ ship), go to the effective endpoint in \`workit grant show\`: \`pr\` stops
15693
+ at the PR even with merge granted; \`green\` babysits it (workit-ship) to
15694
+ merge-ready, never merging; \`merged\` lands it with \`workit pr merge\`.
15695
+ Only the user raises a grant or the endpoint, in a terminal outside the agent
15696
+ host (\`workit grant set <ws> defaultEndpoint=merged\`); never edit
15697
+ workspaces.json or route around grant_required. Release: \`workit release
15698
+ start <v>\`, commits, \`finish\`; \`publish\` if asked.
15699
15699
  - Ask only for a product or preference choice, or for authority you lack, with
15700
15700
  your recommended answer. Facts are yours: read, run or prototype.
15701
15701
  - Label claims measured (run and seen this session), inferred, or guess.
@@ -15709,9 +15709,8 @@ Autonomy contract:
15709
15709
  is not evidence of a requested remote push. Before reporting delivery,
15710
15710
  reconcile every requested item and observe it (\`workit verify-delivery\`).
15711
15711
 
15712
- CLI first: if a step has one right answer, use the \`workit\` verb instead of
15713
- hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
15714
- else \`npx -y @brainervirus/workit-cli\`).
15712
+ Read-only git and gh run raw; mutations go through workit verbs
15713
+ (\`workit git|pr|ci|check|help\`; else \`npx -y @brainervirus/workit-cli\`).
15715
15714
  Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
15716
15715
  verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
15717
15716
  (\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
@@ -17699,12 +17698,36 @@ ${invariantBootstrap()}${compact}${offer ? `
17699
17698
  ${offer}` : ""}${options.addendum ? `
17700
17699
  ${options.addendum}` : ""}
17701
17700
  </workit-contract>`;
17701
+ }, checkoutAt = (cwd) => {
17702
+ for (let dir = path20.resolve(cwd);; dir = path20.dirname(dir)) {
17703
+ const dotGit = path20.join(dir, ".git");
17704
+ if (fs15.existsSync(dotGit)) {
17705
+ const admin = fs15.statSync(dotGit).isFile() ? path20.resolve(dir, /^gitdir:\s*(.+)$/mu.exec(fs15.readFileSync(dotGit, "utf8"))?.[1]?.trim() ?? "") : dotGit;
17706
+ const head = fs15.readFileSync(path20.join(admin, "HEAD"), "utf8").trim();
17707
+ return { root: dir, branch: /^ref: refs\/heads\/(.+)$/u.exec(head)?.[1] ?? null };
17708
+ }
17709
+ if (path20.dirname(dir) === dir)
17710
+ return null;
17711
+ }
17712
+ }, whereLine = (cwd) => {
17713
+ try {
17714
+ const checkout = checkoutAt(cwd);
17715
+ if (!checkout)
17716
+ return null;
17717
+ const allowed = branchPolicySnapshotFor(cwd).namePolicy.allowed;
17718
+ const kinds = allowed.some((pattern) => pattern.source === "^.*$") ? "any" : allowedKinds(allowed).join(", ") || "see `workit help git`";
17719
+ const place = checkout.root === path20.resolve(cwd) ? "" : ` in ${checkout.root}`;
17720
+ return `cwd ${cwd}${place} on ${checkout.branch ?? "detached HEAD"}; branch kinds: ${kinds}`;
17721
+ } catch {
17722
+ return null;
17723
+ }
17702
17724
  }, turnContextText = (input, descriptor) => {
17703
17725
  try {
17704
17726
  const text = sessionCompactContext(new TaskStore(input.cwd), { host: input.host, handle: input.session.id }, hookOperationContext(input, descriptor), descriptor.context.task);
17705
17727
  if (text === MIGRATION_PENDING_NOTE)
17706
17728
  return text;
17707
- return text ? `<workit-task-context>${text}</workit-task-context>` : null;
17729
+ return text ? `<workit-task-context>${[whereLine(input.cwd), text].filter(Boolean).join(`
17730
+ `)}</workit-task-context>` : null;
17708
17731
  } catch {
17709
17732
  return null;
17710
17733
  }
@@ -17770,6 +17793,7 @@ ${options.addendum}` : ""}
17770
17793
  clearTurnDigest(file);
17771
17794
  };
17772
17795
  var init_context = __esm(() => {
17796
+ init_branch();
17773
17797
  init_methods();
17774
17798
  init_paths();
17775
17799
  init_task_contract();
@@ -18477,17 +18501,21 @@ function workitCheckTail(words) {
18477
18501
  return null;
18478
18502
  return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
18479
18503
  }
18480
- function simpleCommand(command, dialect = "posix", depth = 0) {
18504
+ function simpleCommand(command, dialect = "posix", depth = 0, shaped = false) {
18481
18505
  let segments;
18482
18506
  try {
18483
18507
  segments = segmentsOf(command, dialect);
18484
18508
  } catch {
18485
18509
  return null;
18486
18510
  }
18487
- if (segments.length !== 1)
18511
+ const filter = shaped && segments.length === 2 ? segments[1] : null;
18512
+ if (filter) {
18513
+ if (!segments[0].pipe || !OUTPUT_FILTERS.has(commandName(filter.words[0])) || filter.group || filter.pipe || filter.unanalyzable || filter.subs?.length || filter.stdin?.length)
18514
+ return null;
18515
+ } else if (segments.length !== 1)
18488
18516
  return null;
18489
18517
  const [segment] = segments;
18490
- if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
18518
+ if (segment.group || segment.pipe && !filter || segment.unanalyzable || segment.redirects.length && !shaped || segment.subs?.length || segment.stdin?.length)
18491
18519
  return null;
18492
18520
  const { words } = stripPrefixes(segment.words);
18493
18521
  const name = commandName(words[0]);
@@ -18950,7 +18978,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
18950
18978
  ...Object.keys(env).length ? { env } : {},
18951
18979
  ...stdin === undefined ? {} : { stdin }
18952
18980
  };
18953
- }, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, WORKIT_GLOBAL, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
18981
+ }, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, OUTPUT_FILTERS, WORKIT_GLOBAL, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
18954
18982
  if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
18955
18983
  return { name: null };
18956
18984
  if (destination.startsWith("refs/heads/"))
@@ -19436,7 +19464,6 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
19436
19464
  case "pr-create":
19437
19465
  return `workit: prefer ${quote("workit pr create --fill")} (or --title/--body): it records the PR the merge gate checks.`;
19438
19466
  case "pr-read":
19439
- return `workit: ${quote("workit pr status")} shows the PR, its checks and the verdict state; ${quote("workit ci wait")} waits for checks.`;
19440
19467
  case "merge":
19441
19468
  return null;
19442
19469
  }
@@ -19644,6 +19671,7 @@ var init_raw_git = __esm(() => {
19644
19671
  WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
19645
19672
  WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
19646
19673
  PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
19674
+ OUTPUT_FILTERS = new Set(["tail", "head", "grep"]);
19647
19675
  WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
19648
19676
  PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
19649
19677
  CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
@@ -20427,7 +20455,7 @@ function heavyPlan(command, options) {
20427
20455
  const heavy = heavyLines(lines, options);
20428
20456
  if (!heavy.length)
20429
20457
  return { kind: "none" };
20430
- const simple = simpleCommand(command, dialect);
20458
+ const simple = simpleCommand(command, dialect, 0, true);
20431
20459
  const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
20432
20460
  const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
20433
20461
  words: match.line,
@@ -20439,7 +20467,7 @@ function heavyPlan(command, options) {
20439
20467
  if (!verdict.ok)
20440
20468
  return {
20441
20469
  kind: "passthrough",
20442
- note: `workit: \`${command.trim()}\` is heavy but runs outside the heavy-check lease because a host permission rule names it (or \`workit\` is allowlisted); the host decides.`
20470
+ note: ""
20443
20471
  };
20444
20472
  }
20445
20473
  if (single) {
@@ -20449,7 +20477,7 @@ function heavyPlan(command, options) {
20449
20477
  const parts = heavy.map((match) => leaseCommand(match.line));
20450
20478
  return {
20451
20479
  kind: "split",
20452
- reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run each heavy part alone as \`workit check --heavy -- <segment>\` (here: ${parts.map((part) => `\`${part}\``).join("; ")}); run the other parts as their own commands.`,
20480
+ reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run the heavy part alone, now: \`${parts[0]}\`; then run the other parts as their own commands${parts.length > 1 ? ", each heavy one the same way" : ""}.`,
20453
20481
  unblock: parts[0] ?? null
20454
20482
  };
20455
20483
  }
@@ -22506,6 +22534,31 @@ var init_worktree_confine = __esm(() => {
22506
22534
  CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
22507
22535
  });
22508
22536
 
22537
+ // packages/workit-core/src/hooks/steering.ts
22538
+ var ROLES, workitRole = (descriptor, agentType) => {
22539
+ const prefix = descriptor.subagents.agentPrefix;
22540
+ const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
22541
+ return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
22542
+ }, CLAUDE_WORKTREE, roleSteering = (role, isolated) => ({
22543
+ role,
22544
+ isolated,
22545
+ git: role === "implementer" ? isolated ? "plain" : "workit" : null,
22546
+ nudges: role === null
22547
+ }), agentSteering = (descriptor, cwd, agent) => {
22548
+ const role = agent.type ? workitRole(descriptor, agent.type) : null;
22549
+ const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(cwd, agent.id) : CLAUDE_WORKTREE.test(cwd));
22550
+ return roleSteering(role, isolated);
22551
+ };
22552
+ var init_steering = __esm(() => {
22553
+ init_worktree_confine();
22554
+ ROLES = {
22555
+ implementer: "implementer",
22556
+ verifier: "judge",
22557
+ reviewer: "judge"
22558
+ };
22559
+ CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
22560
+ });
22561
+
22509
22562
  // packages/workit-core/src/hooks/handle.ts
22510
22563
  function handleHook(input, deps) {
22511
22564
  const decision = decide(input, deps);
@@ -22591,7 +22644,12 @@ function decide(input, deps) {
22591
22644
  } : NONE6;
22592
22645
  if (descriptor.transport !== "hook-process")
22593
22646
  return rewritten(null);
22594
- const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
22647
+ const { nudges } = agentSteering(descriptor, input.cwd, {
22648
+ type: input.session.agentType,
22649
+ id: input.session.agentId
22650
+ });
22651
+ const nudge = canDeny ? rawGitPre(input, event.command, { nudge: nudges }) : NONE6;
22652
+ const nudged = withContextLine(withContextLine(nudge, aside), shipNudge(input, event.command));
22595
22653
  if (!lease)
22596
22654
  return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
22597
22655
  return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
@@ -22639,34 +22697,35 @@ function decide(input, deps) {
22639
22697
  return NONE6;
22640
22698
  }
22641
22699
  }
22642
- var usable2 = (value) => value === "native" || value === "partial", NONE6, resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn), offered, ROLES, workitRole = (descriptor, agentType) => {
22643
- const prefix = descriptor.subagents.agentPrefix;
22644
- const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
22645
- return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
22646
- }, implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer", subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE, isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`, subagentStartText = (input, descriptor, event) => {
22700
+ var usable2 = (value) => value === "native" || value === "partial", NONE6, resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn), offered, implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer", subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`, subagentStartText = (input, descriptor, event) => {
22647
22701
  const session = input.session.id ?? null;
22648
- const role = workitRole(descriptor, event.agentType);
22702
+ const { role, isolated, git } = agentSteering(descriptor, input.cwd, {
22703
+ type: event.agentType,
22704
+ id: event.agentId
22705
+ });
22649
22706
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
22650
- const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
22651
22707
  const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
22652
- if (role === "implementer" && isolated)
22708
+ if (git === "plain")
22653
22709
  return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
22654
22710
  if (isolated)
22655
22711
  return `${isolatedGit(input.cwd)}
22656
22712
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
22657
- if (role === "implementer")
22713
+ if (git === "workit")
22658
22714
  return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit git --cwd <worktree> commit -m "<message>" -- <paths>\`, \`workit check --cwd <worktree> <name>\` (verb first, so host allow rules match). The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit git --cwd <worktree> branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
22659
22715
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
22660
22716
  if (role !== "judge" || !event.agentId)
22661
22717
  return readOnly;
22662
22718
  const own = subagentSession(session, event.agentId);
22663
22719
  const cwd = input.cwd || process.cwd();
22720
+ const judge = event.agentType.endsWith("reviewer") ? "reviewer" : "verifier";
22721
+ const pick = judge === "verifier" ? ", --kind live when you drove the real surface" : "";
22722
+ const branch = judgeBranch(cwd);
22723
+ if (descriptor.host === "codex_cli" || descriptor.host === "codex_desktop")
22724
+ return `${readOnly} Its shell already acts as its own Workit session, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${verdictCommand({ branch, judge })}\`. Add no session or token option to it, and never set WORKIT_SESSION_ID by hand.`;
22664
22725
  const token = session ? recordIssuedSession(cwd, session, own) : null;
22665
22726
  if (session && !token)
22666
22727
  return `${readOnly} ${own} was already issued to an earlier subagent, so no token is handed out again and a verdict under it would be refused (unbound_session). Record no verdict as ${own}: spawn a fresh verifier or reviewer, which gets its own session and token.`;
22667
- const judge = event.agentType.endsWith("reviewer") ? "reviewer" : "verifier";
22668
- const line = verdictCommand({ branch: judgeBranch(cwd), judge, session: own, token });
22669
- const pick = judge === "verifier" ? ", --kind live when you drove the real surface" : "";
22728
+ const line = verdictCommand({ branch, judge, session: own, token });
22670
22729
  return `${readOnly} Its own Workit session is ${own}, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${line}\`.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
22671
22730
  }, PRE_TOOL, failureDecision = (descriptor, event, error) => {
22672
22731
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
@@ -22714,14 +22773,9 @@ var init_handle = __esm(() => {
22714
22773
  init_live_session();
22715
22774
  init_write_gate();
22716
22775
  init_worktree_confine();
22776
+ init_steering();
22717
22777
  NONE6 = { kind: "none" };
22718
22778
  offered = new Set;
22719
- ROLES = {
22720
- implementer: "implementer",
22721
- verifier: "judge",
22722
- reviewer: "judge"
22723
- };
22724
- CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
22725
22779
  PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
22726
22780
  });
22727
22781
 
@@ -24574,6 +24574,7 @@ var LAUNCHERS = new Set([
24574
24574
  "command"
24575
24575
  ]);
24576
24576
  var PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
24577
+ var OUTPUT_FILTERS = new Set(["tail", "head", "grep"]);
24577
24578
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
24578
24579
  var VALUE_OPTIONS = {
24579
24580
  git: new Set([
@@ -15735,13 +15735,13 @@ Autonomy contract:
15735
15735
  or an unresolved blocker; report open gaps and uncertain workers.
15736
15736
  - No endpoint named: stop at a local commit on a policy-compliant branch and
15737
15737
  state the next command. If the request implies delivery (fix, implement,
15738
- ship), go to the effective endpoint in \`workit grant show\`: \`pr\` opens
15739
- the PR; \`green\` babysits it without asking (workit-ship) to merge-ready,
15740
- never merging; \`merged\` also lands it with \`workit pr merge\`.
15741
- Merge and release need a workspace grant only the user raises from their
15742
- own terminal (a blocked verb names how); never edit workspaces.json or route
15743
- around grant_required. Release: \`workit release start <v>\`, commits,
15744
- \`finish\`; \`publish\` if asked.
15738
+ ship), go to the effective endpoint in \`workit grant show\`: \`pr\` stops
15739
+ at the PR even with merge granted; \`green\` babysits it (workit-ship) to
15740
+ merge-ready, never merging; \`merged\` lands it with \`workit pr merge\`.
15741
+ Only the user raises a grant or the endpoint, in a terminal outside the agent
15742
+ host (\`workit grant set <ws> defaultEndpoint=merged\`); never edit
15743
+ workspaces.json or route around grant_required. Release: \`workit release
15744
+ start <v>\`, commits, \`finish\`; \`publish\` if asked.
15745
15745
  - Ask only for a product or preference choice, or for authority you lack, with
15746
15746
  your recommended answer. Facts are yours: read, run or prototype.
15747
15747
  - Label claims measured (run and seen this session), inferred, or guess.
@@ -15755,9 +15755,8 @@ Autonomy contract:
15755
15755
  is not evidence of a requested remote push. Before reporting delivery,
15756
15756
  reconcile every requested item and observe it (\`workit verify-delivery\`).
15757
15757
 
15758
- CLI first: if a step has one right answer, use the \`workit\` verb instead of
15759
- hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
15760
- else \`npx -y @brainervirus/workit-cli\`).
15758
+ Read-only git and gh run raw; mutations go through workit verbs
15759
+ (\`workit git|pr|ci|check|help\`; else \`npx -y @brainervirus/workit-cli\`).
15761
15760
  Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
15762
15761
  verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
15763
15762
  (\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
@@ -17759,12 +17758,38 @@ ${offer}` : ""}${options.addendum ? `
17759
17758
  ${options.addendum}` : ""}
17760
17759
  </workit-contract>`;
17761
17760
  };
17761
+ var checkoutAt = (cwd) => {
17762
+ for (let dir = path20.resolve(cwd);; dir = path20.dirname(dir)) {
17763
+ const dotGit = path20.join(dir, ".git");
17764
+ if (fs15.existsSync(dotGit)) {
17765
+ const admin = fs15.statSync(dotGit).isFile() ? path20.resolve(dir, /^gitdir:\s*(.+)$/mu.exec(fs15.readFileSync(dotGit, "utf8"))?.[1]?.trim() ?? "") : dotGit;
17766
+ const head = fs15.readFileSync(path20.join(admin, "HEAD"), "utf8").trim();
17767
+ return { root: dir, branch: /^ref: refs\/heads\/(.+)$/u.exec(head)?.[1] ?? null };
17768
+ }
17769
+ if (path20.dirname(dir) === dir)
17770
+ return null;
17771
+ }
17772
+ };
17773
+ var whereLine = (cwd) => {
17774
+ try {
17775
+ const checkout = checkoutAt(cwd);
17776
+ if (!checkout)
17777
+ return null;
17778
+ const allowed = branchPolicySnapshotFor(cwd).namePolicy.allowed;
17779
+ const kinds = allowed.some((pattern) => pattern.source === "^.*$") ? "any" : allowedKinds(allowed).join(", ") || "see `workit help git`";
17780
+ const place = checkout.root === path20.resolve(cwd) ? "" : ` in ${checkout.root}`;
17781
+ return `cwd ${cwd}${place} on ${checkout.branch ?? "detached HEAD"}; branch kinds: ${kinds}`;
17782
+ } catch {
17783
+ return null;
17784
+ }
17785
+ };
17762
17786
  var turnContextText = (input, descriptor) => {
17763
17787
  try {
17764
17788
  const text = sessionCompactContext(new TaskStore(input.cwd), { host: input.host, handle: input.session.id }, hookOperationContext(input, descriptor), descriptor.context.task);
17765
17789
  if (text === MIGRATION_PENDING_NOTE)
17766
17790
  return text;
17767
- return text ? `<workit-task-context>${text}</workit-task-context>` : null;
17791
+ return text ? `<workit-task-context>${[whereLine(input.cwd), text].filter(Boolean).join(`
17792
+ `)}</workit-task-context>` : null;
17768
17793
  } catch {
17769
17794
  return null;
17770
17795
  }
@@ -18954,17 +18979,22 @@ function workitCheckTail(words) {
18954
18979
  return null;
18955
18980
  return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
18956
18981
  }
18957
- function simpleCommand(command, dialect = "posix", depth = 0) {
18982
+ var OUTPUT_FILTERS = new Set(["tail", "head", "grep"]);
18983
+ function simpleCommand(command, dialect = "posix", depth = 0, shaped = false) {
18958
18984
  let segments;
18959
18985
  try {
18960
18986
  segments = segmentsOf(command, dialect);
18961
18987
  } catch {
18962
18988
  return null;
18963
18989
  }
18964
- if (segments.length !== 1)
18990
+ const filter = shaped && segments.length === 2 ? segments[1] : null;
18991
+ if (filter) {
18992
+ if (!segments[0].pipe || !OUTPUT_FILTERS.has(commandName(filter.words[0])) || filter.group || filter.pipe || filter.unanalyzable || filter.subs?.length || filter.stdin?.length)
18993
+ return null;
18994
+ } else if (segments.length !== 1)
18965
18995
  return null;
18966
18996
  const [segment] = segments;
18967
- if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
18997
+ if (segment.group || segment.pipe && !filter || segment.unanalyzable || segment.redirects.length && !shaped || segment.subs?.length || segment.stdin?.length)
18968
18998
  return null;
18969
18999
  const { words } = stripPrefixes(segment.words);
18970
19000
  const name = commandName(words[0]);
@@ -19649,7 +19679,6 @@ var nudgeFor = (action, host, session) => {
19649
19679
  case "pr-create":
19650
19680
  return `workit: prefer ${quote("workit pr create --fill")} (or --title/--body): it records the PR the merge gate checks.`;
19651
19681
  case "pr-read":
19652
- return `workit: ${quote("workit pr status")} shows the PR, its checks and the verdict state; ${quote("workit ci wait")} waits for checks.`;
19653
19682
  case "merge":
19654
19683
  return null;
19655
19684
  }
@@ -20739,7 +20768,7 @@ function heavyPlan(command, options) {
20739
20768
  const heavy = heavyLines(lines, options);
20740
20769
  if (!heavy.length)
20741
20770
  return { kind: "none" };
20742
- const simple = simpleCommand(command, dialect);
20771
+ const simple = simpleCommand(command, dialect, 0, true);
20743
20772
  const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
20744
20773
  const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
20745
20774
  words: match.line,
@@ -20751,7 +20780,7 @@ function heavyPlan(command, options) {
20751
20780
  if (!verdict.ok)
20752
20781
  return {
20753
20782
  kind: "passthrough",
20754
- note: `workit: \`${command.trim()}\` is heavy but runs outside the heavy-check lease because a host permission rule names it (or \`workit\` is allowlisted); the host decides.`
20783
+ note: ""
20755
20784
  };
20756
20785
  }
20757
20786
  if (single) {
@@ -20761,7 +20790,7 @@ function heavyPlan(command, options) {
20761
20790
  const parts = heavy.map((match) => leaseCommand(match.line));
20762
20791
  return {
20763
20792
  kind: "split",
20764
- reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run each heavy part alone as \`workit check --heavy -- <segment>\` (here: ${parts.map((part) => `\`${part}\``).join("; ")}); run the other parts as their own commands.`,
20793
+ reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run the heavy part alone, now: \`${parts[0]}\`; then run the other parts as their own commands${parts.length > 1 ? ", each heavy one the same way" : ""}.`,
20765
20794
  unblock: parts[0] ?? null
20766
20795
  };
20767
20796
  }
@@ -22734,11 +22763,7 @@ function confineImplementerShell(input, command, dialect = "posix") {
22734
22763
  return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
22735
22764
  }
22736
22765
 
22737
- // packages/workit-core/src/hooks/handle.ts
22738
- var usable2 = (value) => value === "native" || value === "partial";
22739
- var NONE6 = { kind: "none" };
22740
- var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
22741
- var offered = new Set;
22766
+ // packages/workit-core/src/hooks/steering.ts
22742
22767
  var ROLES = {
22743
22768
  implementer: "implementer",
22744
22769
  verifier: "judge",
@@ -22749,34 +22774,56 @@ var workitRole = (descriptor, agentType) => {
22749
22774
  const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
22750
22775
  return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
22751
22776
  };
22777
+ var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
22778
+ var roleSteering = (role, isolated) => ({
22779
+ role,
22780
+ isolated,
22781
+ git: role === "implementer" ? isolated ? "plain" : "workit" : null,
22782
+ nudges: role === null
22783
+ });
22784
+ var agentSteering = (descriptor, cwd, agent) => {
22785
+ const role = agent.type ? workitRole(descriptor, agent.type) : null;
22786
+ const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(cwd, agent.id) : CLAUDE_WORKTREE.test(cwd));
22787
+ return roleSteering(role, isolated);
22788
+ };
22789
+
22790
+ // packages/workit-core/src/hooks/handle.ts
22791
+ var usable2 = (value) => value === "native" || value === "partial";
22792
+ var NONE6 = { kind: "none" };
22793
+ var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
22794
+ var offered = new Set;
22752
22795
  var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
22753
22796
  var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
22754
- var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
22755
22797
  var isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`;
22756
22798
  var subagentStartText = (input, descriptor, event) => {
22757
22799
  const session = input.session.id ?? null;
22758
- const role = workitRole(descriptor, event.agentType);
22800
+ const { role, isolated, git } = agentSteering(descriptor, input.cwd, {
22801
+ type: event.agentType,
22802
+ id: event.agentId
22803
+ });
22759
22804
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
22760
- const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
22761
22805
  const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
22762
- if (role === "implementer" && isolated)
22806
+ if (git === "plain")
22763
22807
  return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
22764
22808
  if (isolated)
22765
22809
  return `${isolatedGit(input.cwd)}
22766
22810
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
22767
- if (role === "implementer")
22811
+ if (git === "workit")
22768
22812
  return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit git --cwd <worktree> commit -m "<message>" -- <paths>\`, \`workit check --cwd <worktree> <name>\` (verb first, so host allow rules match). The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit git --cwd <worktree> branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
22769
22813
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
22770
22814
  if (role !== "judge" || !event.agentId)
22771
22815
  return readOnly;
22772
22816
  const own = subagentSession(session, event.agentId);
22773
22817
  const cwd = input.cwd || process.cwd();
22818
+ const judge = event.agentType.endsWith("reviewer") ? "reviewer" : "verifier";
22819
+ const pick = judge === "verifier" ? ", --kind live when you drove the real surface" : "";
22820
+ const branch = judgeBranch(cwd);
22821
+ if (descriptor.host === "codex_cli" || descriptor.host === "codex_desktop")
22822
+ return `${readOnly} Its shell already acts as its own Workit session, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${verdictCommand({ branch, judge })}\`. Add no session or token option to it, and never set WORKIT_SESSION_ID by hand.`;
22774
22823
  const token = session ? recordIssuedSession(cwd, session, own) : null;
22775
22824
  if (session && !token)
22776
22825
  return `${readOnly} ${own} was already issued to an earlier subagent, so no token is handed out again and a verdict under it would be refused (unbound_session). Record no verdict as ${own}: spawn a fresh verifier or reviewer, which gets its own session and token.`;
22777
- const judge = event.agentType.endsWith("reviewer") ? "reviewer" : "verifier";
22778
- const line = verdictCommand({ branch: judgeBranch(cwd), judge, session: own, token });
22779
- const pick = judge === "verifier" ? ", --kind live when you drove the real surface" : "";
22826
+ const line = verdictCommand({ branch, judge, session: own, token });
22780
22827
  return `${readOnly} Its own Workit session is ${own}, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${line}\`.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
22781
22828
  };
22782
22829
  var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
@@ -22864,7 +22911,12 @@ function decide(input, deps) {
22864
22911
  } : NONE6;
22865
22912
  if (descriptor.transport !== "hook-process")
22866
22913
  return rewritten(null);
22867
- const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
22914
+ const { nudges } = agentSteering(descriptor, input.cwd, {
22915
+ type: input.session.agentType,
22916
+ id: input.session.agentId
22917
+ });
22918
+ const nudge = canDeny ? rawGitPre(input, event.command, { nudge: nudges }) : NONE6;
22919
+ const nudged = withContextLine(withContextLine(nudge, aside), shipNudge(input, event.command));
22868
22920
  if (!lease)
22869
22921
  return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
22870
22922
  return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cursor",
3
- "version": "8.29.0",
3
+ "version": "8.29.2",
4
4
  "private": false,
5
5
  "description": "Workit Cursor plugin — shared MCP transport, native hooks, and sixteen method skills",
6
6
  "keywords": [
@@ -46,8 +46,8 @@
46
46
  "build": "bun scripts/build.ts"
47
47
  },
48
48
  "devDependencies": {
49
- "@brainervirus/workit-core": "^8.29.0",
50
- "@brainervirus/workit-mcp": "^8.29.0"
49
+ "@brainervirus/workit-core": "^8.29.2",
50
+ "@brainervirus/workit-mcp": "^8.29.2"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=24"