@brainervirus/workit-cursor 8.27.0 → 8.28.1

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.27.0",
5
+ "version": "8.28.1",
6
6
  "author": {
7
7
  "name": "Cristhofer Pincetti"
8
8
  },
@@ -8122,7 +8122,33 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
8122
8122
  workspace: found?.workspace ?? null,
8123
8123
  from: found?.source?.name ?? null
8124
8124
  };
8125
- }, workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false, TIER_KEYS, TIER_HOSTS, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
8125
+ }, workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false, modelSources = (cwd, dir) => {
8126
+ const out = [];
8127
+ try {
8128
+ const matched = matchRuntimeWorkspace(cwd, dir);
8129
+ if (matched) {
8130
+ const enclosing = matched.matches.filter((entry) => entry !== matched.selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
8131
+ for (const entry of [matched.selected, ...enclosing])
8132
+ out.push({
8133
+ name: entry.name,
8134
+ global: false,
8135
+ models: entry.models,
8136
+ tierModels: entry.fanout?.tierModels
8137
+ });
8138
+ }
8139
+ } catch {}
8140
+ try {
8141
+ const document = JSON.parse(readFileSync2(path4.join(dir ?? configDir(), "workspaces.json"), "utf8"));
8142
+ if (document && typeof document === "object" && !Array.isArray(document))
8143
+ out.push({
8144
+ name: "global",
8145
+ global: true,
8146
+ models: document.models,
8147
+ tierModels: undefined
8148
+ });
8149
+ } catch {}
8150
+ return out;
8151
+ }, hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, ""), TIER_KEYS, TIER_HOSTS, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
8126
8152
  const matched = matchRuntimeWorkspace(cwd);
8127
8153
  if (!matched)
8128
8154
  return null;
@@ -9713,11 +9739,15 @@ var withReleaseTracks = (workspaceRoot, policy) => {
9713
9739
  source,
9714
9740
  fingerprint
9715
9741
  };
9742
+ }, rethrowRuntimeBug = (error) => {
9743
+ if (error instanceof TypeError || error instanceof ReferenceError)
9744
+ throw error;
9716
9745
  }, validateBranchNameFor = (workspaceRoot, name) => {
9717
9746
  try {
9718
9747
  const resolved = branchPolicySnapshotFor(workspaceRoot);
9719
9748
  return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
9720
9749
  } catch (error) {
9750
+ rethrowRuntimeBug(error);
9721
9751
  const correction = "repair or select a valid workspace policy before creating a branch";
9722
9752
  return {
9723
9753
  ok: false,
@@ -11497,7 +11527,7 @@ var str2 = (d, values) => ({
11497
11527
  description: typeof description === "string" ? description : "",
11498
11528
  paths: ["."],
11499
11529
  exclusions: []
11500
- }), LISTS, ENVELOPE;
11530
+ }), LISTS, ENVELOPE, NOT_FLAT;
11501
11531
  var init_operation_input = __esm(() => {
11502
11532
  init_task_contract();
11503
11533
  scopeFields = {
@@ -11619,6 +11649,7 @@ var init_operation_input = __esm(() => {
11619
11649
  "expectedWorkspaceRevision",
11620
11650
  "judgment"
11621
11651
  ]);
11652
+ NOT_FLAT = new Set(["schemaVersion", "action", "workspaceId"]);
11622
11653
  });
11623
11654
 
11624
11655
  // packages/workit-core/src/core/policy/judgment.ts
@@ -22037,6 +22068,131 @@ var init_standing = __esm(() => {
22037
22068
  sinceStart = new Map;
22038
22069
  });
22039
22070
 
22071
+ // packages/workit-core/src/core/role-models.ts
22072
+ var MODEL_ROLES, MODEL_TIERS, HOST_KEYS, BUILT_IN_CLAUDE_CODE, isObject7 = (value) => !!value && typeof value === "object" && !Array.isArray(value), name = (value) => typeof value === "string" && value.trim() ? value.trim() : null, leaf = (value) => {
22073
+ const text = name(value);
22074
+ if (text)
22075
+ return { model: text, effort: null };
22076
+ if (!isObject7(value))
22077
+ return null;
22078
+ const model = name(value.model);
22079
+ return model ? { model, effort: name(value.effort) } : null;
22080
+ }, isFlat = (value) => typeof value === "string" || isObject7(value) && Object.hasOwn(value, "model"), forHost = (value, host) => {
22081
+ if (isFlat(value))
22082
+ return leaf(value);
22083
+ if (!isObject7(value))
22084
+ return null;
22085
+ const family = host ? hostFamily(host) : null;
22086
+ for (const key of [family, host, "default"])
22087
+ if (key && Object.hasOwn(value, key)) {
22088
+ const found = leaf(value[key]);
22089
+ if (found)
22090
+ return found;
22091
+ }
22092
+ return null;
22093
+ }, rolesOf = (models) => isObject7(models) && isObject7(models.roles) ? models.roles : null, resolveRoleModel = (cwd, selector, host, dir) => {
22094
+ const { role, tier } = selector;
22095
+ for (const source of modelSources(cwd, dir)) {
22096
+ const where = source.global ? "the global default" : `workspace ${source.name}`;
22097
+ const roles = rolesOf(source.models);
22098
+ const configured = roles && Object.hasOwn(roles, role) ? roles[role] : undefined;
22099
+ const fromRoles = role === "implementer" ? tier && isObject7(configured) && Object.hasOwn(configured, tier) ? forHost(configured[tier], host) : null : forHost(configured, host);
22100
+ if (fromRoles)
22101
+ return { ...fromRoles, source: `models.roles in ${where}` };
22102
+ if (role === "implementer" && tier && isObject7(source.tierModels)) {
22103
+ const fromTiers = Object.hasOwn(source.tierModels, tier) ? forHost(source.tierModels[tier], host) : null;
22104
+ if (fromTiers)
22105
+ return { model: fromTiers.model, effort: null, source: `fanout.tierModels in ${where}` };
22106
+ }
22107
+ }
22108
+ if (host && hostFamily(host) === "claude_code") {
22109
+ const builtIn = BUILT_IN_CLAUDE_CODE[tier ? `${role}.${tier}` : role];
22110
+ if (builtIn)
22111
+ return { model: builtIn.model, effort: builtIn.effort ?? null, source: "built-in" };
22112
+ }
22113
+ return null;
22114
+ }, ROLE_SELECTORS, roleForAgentType = (agentType, prompt) => {
22115
+ switch (agentType) {
22116
+ case "workit:implementer": {
22117
+ const tier = typeof prompt === "string" ? /^[ \t]*TIER:[ \t]*(mundane|standard|hard)\b/im.exec(prompt)?.[1] : undefined;
22118
+ return { role: "implementer", tier: tier?.toLowerCase() ?? "standard" };
22119
+ }
22120
+ case "workit:verifier":
22121
+ return { role: "verifier" };
22122
+ case "workit:reviewer":
22123
+ return { role: "reviewer" };
22124
+ case "Explore":
22125
+ return { role: "explore" };
22126
+ default:
22127
+ return null;
22128
+ }
22129
+ };
22130
+ var init_role_models = __esm(() => {
22131
+ init_workspaces();
22132
+ MODEL_ROLES = [
22133
+ "decider",
22134
+ "implementer",
22135
+ "verifier",
22136
+ "reviewer",
22137
+ "explore",
22138
+ "babysit"
22139
+ ];
22140
+ MODEL_TIERS = ["mundane", "standard", "hard"];
22141
+ HOST_KEYS = new Set([
22142
+ "claude_code",
22143
+ "opencode",
22144
+ "codex",
22145
+ "codex_cli",
22146
+ "codex_desktop",
22147
+ "cursor",
22148
+ "pi",
22149
+ "default"
22150
+ ]);
22151
+ BUILT_IN_CLAUDE_CODE = {
22152
+ decider: { model: "fable", effort: "high" },
22153
+ "implementer.hard": { model: "opus" },
22154
+ "implementer.standard": { model: "sonnet" },
22155
+ "implementer.mundane": { model: "haiku" },
22156
+ verifier: { model: "sonnet" },
22157
+ reviewer: { model: "opus" },
22158
+ explore: { model: "haiku" },
22159
+ babysit: { model: "haiku" }
22160
+ };
22161
+ ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
22162
+ });
22163
+
22164
+ // packages/workit-core/src/hooks/agent-model.ts
22165
+ var MODELS, EFFORTS, given = (value) => typeof value === "string" && value.trim() !== "", agentModelDecision = (cwd, tool, env = process.env) => {
22166
+ if (given(tool.model) || env.CLAUDE_CODE_SUBAGENT_MODEL_FORCE)
22167
+ return { kind: "none" };
22168
+ const selector = roleForAgentType(tool.subagent_type, tool.prompt);
22169
+ if (!selector)
22170
+ return { kind: "none" };
22171
+ const resolved = resolveRoleModel(cwd, selector, "claude_code");
22172
+ if (!resolved)
22173
+ return { kind: "none" };
22174
+ const model = resolved.model.toLowerCase();
22175
+ const effort = resolved.effort?.toLowerCase() ?? null;
22176
+ const input = { ...tool };
22177
+ if (MODELS.has(model))
22178
+ input.model = model;
22179
+ if (effort && EFFORTS.has(effort) && !given(tool.effort))
22180
+ input.effort = effort;
22181
+ if (input.model === undefined && input.effort === tool.effort)
22182
+ return { kind: "none" };
22183
+ const label = selector.tier ? `${selector.role} ${selector.tier}` : selector.role;
22184
+ return {
22185
+ kind: "update-input",
22186
+ input,
22187
+ text: `Workit set ${String(input.model ?? "no model")}${input.effort !== tool.effort ? ` (effort ${String(input.effort)})` : ""} on this ${String(tool.subagent_type)} spawn: role ${label}, ${resolved.source}. Name a model to choose another.`
22188
+ };
22189
+ };
22190
+ var init_agent_model = __esm(() => {
22191
+ init_role_models();
22192
+ MODELS = new Set(["sonnet", "opus", "haiku", "fable"]);
22193
+ EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
22194
+ });
22195
+
22040
22196
  // packages/workit-core/src/hooks/cwd-normalize.ts
22041
22197
  var DIR, COMPOUND, GLOBALS_FIRST, VERB_FIRST, VERBS, FILTERS, clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length, stdinOnly = (words) => {
22042
22198
  const [name, ...args] = words;
@@ -22345,7 +22501,7 @@ function handleHook(input, deps) {
22345
22501
  const delta = standingDelta(input);
22346
22502
  if (!delta)
22347
22503
  return decision;
22348
- return decision.kind === "rewrite" ? { ...decision, text: [decision.text, delta].filter(Boolean).join(`
22504
+ return decision.kind === "rewrite" || decision.kind === "update-input" ? { ...decision, text: [decision.text, delta].filter(Boolean).join(`
22349
22505
  `) } : withContextLine(decision, delta);
22350
22506
  }
22351
22507
  function decide(input, deps) {
@@ -22458,6 +22614,8 @@ function decide(input, deps) {
22458
22614
  case "tool.pre":
22459
22615
  if (event.skill)
22460
22616
  recordSkillLoad(input, event.skill, "tool");
22617
+ if (event.agentInput && input.host === "claude_code")
22618
+ return agentModelDecision(input.cwd, event.agentInput);
22461
22619
  return NONE6;
22462
22620
  case "stop":
22463
22621
  return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
@@ -22530,6 +22688,7 @@ var init_handle = __esm(() => {
22530
22688
  init_branch();
22531
22689
  init_workspaces();
22532
22690
  init_host_session();
22691
+ init_agent_model();
22533
22692
  init_cwd_normalize();
22534
22693
  init_delivery_verbs();
22535
22694
  init_heavy_gate();
@@ -22680,6 +22839,16 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
22680
22839
  skill: isRecord6(value.tool_input) ? optionalText(value.tool_input.skill) : null
22681
22840
  }
22682
22841
  };
22842
+ if (value.tool_name === "Agent" || value.tool_name === "Task")
22843
+ return {
22844
+ ok: true,
22845
+ event: {
22846
+ kind: "tool.pre",
22847
+ tool: value.tool_name,
22848
+ toolUseId,
22849
+ ...isRecord6(value.tool_input) ? { agentInput: value.tool_input } : {}
22850
+ }
22851
+ };
22683
22852
  if (!SHELL_TOOLS.has(value.tool_name))
22684
22853
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
22685
22854
  const command = toolCommand(value);
@@ -22748,7 +22917,7 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
22748
22917
  };
22749
22918
  }
22750
22919
  }, CONTEXT_EVENTS, render = (decision, native) => {
22751
- if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
22920
+ if ((decision.kind === "rewrite" || decision.kind === "update-input") && native === "PreToolUse" && decision.input)
22752
22921
  return {
22753
22922
  json: {
22754
22923
  hookSpecificOutput: {
@@ -15951,8 +15951,9 @@ function flatOperationJsonSchema(family, options = {}) {
15951
15951
  };
15952
15952
  const properties = { action: { type: "string", enum: actions } };
15953
15953
  const readField = (name) => name === "taskId" || (family === "task" ? READ_TASK_FIELDS.has(name) : family === "policy");
15954
+ const taken = (name) => family === "policy" || actions.some((action) => actionKeys(family, action)?.accepted.has(name));
15954
15955
  for (const [name, field] of Object.entries({ ...COMMON, ...spec.fields }))
15955
- if (options.readOnly ? readField(name) : !(family === "state" && name === "expectedRevision"))
15956
+ if (options.readOnly ? readField(name) : taken(name))
15956
15957
  properties[name] = property(field);
15957
15958
  return {
15958
15959
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -16194,6 +16195,98 @@ function normalizeOperationInput(family, input, host) {
16194
16195
  request[key] = strings(request[key]);
16195
16196
  return request;
16196
16197
  }
16198
+ var defOf = (node) => node._zod.def;
16199
+ var unwrap = (node) => {
16200
+ let inner = node;
16201
+ while (["optional", "nullable"].includes(defOf(inner).type) && defOf(inner).innerType)
16202
+ inner = defOf(inner).innerType;
16203
+ return inner;
16204
+ };
16205
+ var FLAT_NAME = {
16206
+ authorityRefs: "refs",
16207
+ contentRefs: "refs",
16208
+ statedChoice: "choice",
16209
+ reviewContext: "reviewSession"
16210
+ };
16211
+ var NOT_FLAT = new Set(["schemaVersion", "action", "workspaceId"]);
16212
+ function actionKeys(family, action) {
16213
+ const options = advertisedOperationSchemas[family].options;
16214
+ const option = options.find((item) => {
16215
+ const literal = defOf(item).shape?.action;
16216
+ return literal._zod.def.values.includes(action);
16217
+ });
16218
+ if (!option)
16219
+ return null;
16220
+ const out = { accepted: new Set, required: [] };
16221
+ const walk = (shape, nested) => {
16222
+ for (const [key, node] of Object.entries(shape)) {
16223
+ if (NOT_FLAT.has(key) || nested && key === "taskId")
16224
+ continue;
16225
+ const inner = unwrap(node);
16226
+ const type = defOf(inner).type;
16227
+ const needed = !["optional", "nullable"].includes(defOf(node).type);
16228
+ if (key === "scope")
16229
+ for (const name of ["scope", "paths", "exclusions"])
16230
+ out.accepted.add(name);
16231
+ else if (type === "object" && key !== "bundle" && !(key in FLAT_NAME)) {
16232
+ out.accepted.add(key);
16233
+ walk(defOf(inner).shape, true);
16234
+ } else {
16235
+ const name = FLAT_NAME[key] ?? key;
16236
+ out.accepted.add(name);
16237
+ if (needed && type !== "array" && type !== "record" && !out.required.includes(name))
16238
+ out.required.push(name);
16239
+ }
16240
+ }
16241
+ };
16242
+ walk(defOf(option).shape, false);
16243
+ return out;
16244
+ }
16245
+ function exampleOperation(family, action) {
16246
+ const keys = actionKeys(family, action);
16247
+ const example = { action };
16248
+ for (const name of keys?.required ?? [])
16249
+ example[name] = FLAT[family].fields[name]?.enum?.[0] ?? `<${name}>`;
16250
+ if (family === "policy" && !keys?.required.length && action !== "explain")
16251
+ example.riskTier = "normal";
16252
+ return example;
16253
+ }
16254
+ var oneOf = (values) => values.join(" | ");
16255
+ function invalidOperation(family, input, error) {
16256
+ const request = isObject2(input) ? input : {};
16257
+ const action = typeof request.action === "string" ? request.action : "";
16258
+ const actions = OPERATION_ACTIONS[family];
16259
+ if (!actions.includes(action))
16260
+ return failure2("invalid_input", `${family}: action must be one of ${oneOf(actions)}`, {
16261
+ fields: [{ path: "action", reason: `got ${JSON.stringify(request.action ?? null)}` }]
16262
+ });
16263
+ const own = actionKeys(family, action);
16264
+ const fields = [];
16265
+ const sentence = [];
16266
+ for (const issue of error.issues) {
16267
+ if (issue.code === "unrecognized_keys") {
16268
+ for (const key of issue.keys) {
16269
+ const home = actions.find((other) => other !== action && actionKeys(family, other).accepted.has(key));
16270
+ const reason = home ? `${key} belongs to action: ${home}` : `${key} is not a field of ${family} ${action}; it takes ${[...own.accepted].join(", ")}`;
16271
+ fields.push({ path: key, reason });
16272
+ sentence.push(reason);
16273
+ }
16274
+ continue;
16275
+ }
16276
+ const last = String(issue.path.at(-1) ?? "");
16277
+ const name = Object.entries(FLAT_NAME).find(([contract]) => contract === last)?.[1] ?? last;
16278
+ const missing = issue.code === "invalid_type" && /received undefined/.test(issue.message);
16279
+ const absent = own.required.filter((key) => !(key in request));
16280
+ const needs = missing && issue.path.length === 1 && absent.length ? absent.join(", ") : name;
16281
+ const reason = missing ? `${family} ${action} requires ${needs}` : `${name}: ${issue.message}`;
16282
+ fields.push({ path: issue.path.join("."), reason });
16283
+ sentence.push(reason);
16284
+ }
16285
+ const example = JSON.stringify(exampleOperation(family, action));
16286
+ return failure2("invalid_input", `${[...new Set(sentence)].join("; ")}. Example: ${example}`, {
16287
+ fields
16288
+ });
16289
+ }
16197
16290
  function parseAdvertisedOperation(family, input, host) {
16198
16291
  const request = normalizeOperationInput(family, input, host);
16199
16292
  if (isObject2(request) && request.schemaVersion !== SCHEMA_VERSION)
@@ -16201,12 +16294,7 @@ function parseAdvertisedOperation(family, input, host) {
16201
16294
  const parsed = advertisedOperationSchemas[family].safeParse(request);
16202
16295
  if (parsed.success)
16203
16296
  return success(null, null, request);
16204
- return failure2("invalid_input", "operation input is invalid", {
16205
- fields: parsed.error.issues.map((issue) => ({
16206
- path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
16207
- reason: issue.message
16208
- }))
16209
- });
16297
+ return invalidOperation(family, request, parsed.error);
16210
16298
  }
16211
16299
  // packages/workit-core/src/core/task-store.ts
16212
16300
  import { createHash as createHash8, randomUUID as randomUUID3 } from "node:crypto";
@@ -28185,6 +28273,32 @@ var WRITE_TOOLS = new Set([
28185
28273
  ]);
28186
28274
  // packages/workit-core/src/hooks/standing.ts
28187
28275
  var sinceStart = new Map;
28276
+ // packages/workit-core/src/core/role-models.ts
28277
+ var MODEL_ROLES = [
28278
+ "decider",
28279
+ "implementer",
28280
+ "verifier",
28281
+ "reviewer",
28282
+ "explore",
28283
+ "babysit"
28284
+ ];
28285
+ var MODEL_TIERS = ["mundane", "standard", "hard"];
28286
+ var HOST_KEYS = new Set([
28287
+ "claude_code",
28288
+ "opencode",
28289
+ "codex",
28290
+ "codex_cli",
28291
+ "codex_desktop",
28292
+ "cursor",
28293
+ "pi",
28294
+ "default"
28295
+ ]);
28296
+ var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
28297
+
28298
+ // packages/workit-core/src/hooks/agent-model.ts
28299
+ var MODELS = new Set(["sonnet", "opus", "haiku", "fable"]);
28300
+ var EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
28301
+
28188
28302
  // packages/workit-core/src/hooks/cwd-normalize.ts
28189
28303
  var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
28190
28304
  var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
@@ -8205,6 +8205,34 @@ var workspaceTrailers = (cwd, dir) => {
8205
8205
  };
8206
8206
  };
8207
8207
  var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
8208
+ var modelSources = (cwd, dir) => {
8209
+ const out = [];
8210
+ try {
8211
+ const matched = matchRuntimeWorkspace(cwd, dir);
8212
+ if (matched) {
8213
+ const enclosing = matched.matches.filter((entry) => entry !== matched.selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
8214
+ for (const entry of [matched.selected, ...enclosing])
8215
+ out.push({
8216
+ name: entry.name,
8217
+ global: false,
8218
+ models: entry.models,
8219
+ tierModels: entry.fanout?.tierModels
8220
+ });
8221
+ }
8222
+ } catch {}
8223
+ try {
8224
+ const document = JSON.parse(readFileSync2(path4.join(dir ?? configDir(), "workspaces.json"), "utf8"));
8225
+ if (document && typeof document === "object" && !Array.isArray(document))
8226
+ out.push({
8227
+ name: "global",
8228
+ global: true,
8229
+ models: document.models,
8230
+ tierModels: undefined
8231
+ });
8232
+ } catch {}
8233
+ return out;
8234
+ };
8235
+ var hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, "");
8208
8236
  var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
8209
8237
  var TIER_HOSTS = new Set([
8210
8238
  "claude_code",
@@ -9700,11 +9728,16 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
9700
9728
  fingerprint
9701
9729
  };
9702
9730
  };
9731
+ var rethrowRuntimeBug = (error) => {
9732
+ if (error instanceof TypeError || error instanceof ReferenceError)
9733
+ throw error;
9734
+ };
9703
9735
  var validateBranchNameFor = (workspaceRoot, name) => {
9704
9736
  try {
9705
9737
  const resolved = branchPolicySnapshotFor(workspaceRoot);
9706
9738
  return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
9707
9739
  } catch (error) {
9740
+ rethrowRuntimeBug(error);
9708
9741
  const correction = "repair or select a valid workspace policy before creating a branch";
9709
9742
  return {
9710
9743
  ok: false,
@@ -11608,6 +11641,7 @@ function normalizeOperationInput(family, input, host) {
11608
11641
  request[key] = strings(request[key]);
11609
11642
  return request;
11610
11643
  }
11644
+ var NOT_FLAT = new Set(["schemaVersion", "action", "workspaceId"]);
11611
11645
 
11612
11646
  // packages/workit-core/src/core/policy/judgment.ts
11613
11647
  var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -22250,6 +22284,133 @@ var standingDelta = (input) => {
22250
22284
  }
22251
22285
  };
22252
22286
  var sinceStart = new Map;
22287
+ // packages/workit-core/src/core/role-models.ts
22288
+ var MODEL_ROLES = [
22289
+ "decider",
22290
+ "implementer",
22291
+ "verifier",
22292
+ "reviewer",
22293
+ "explore",
22294
+ "babysit"
22295
+ ];
22296
+ var MODEL_TIERS = ["mundane", "standard", "hard"];
22297
+ var HOST_KEYS = new Set([
22298
+ "claude_code",
22299
+ "opencode",
22300
+ "codex",
22301
+ "codex_cli",
22302
+ "codex_desktop",
22303
+ "cursor",
22304
+ "pi",
22305
+ "default"
22306
+ ]);
22307
+ var BUILT_IN_CLAUDE_CODE = {
22308
+ decider: { model: "fable", effort: "high" },
22309
+ "implementer.hard": { model: "opus" },
22310
+ "implementer.standard": { model: "sonnet" },
22311
+ "implementer.mundane": { model: "haiku" },
22312
+ verifier: { model: "sonnet" },
22313
+ reviewer: { model: "opus" },
22314
+ explore: { model: "haiku" },
22315
+ babysit: { model: "haiku" }
22316
+ };
22317
+ var isObject7 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
22318
+ var name = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
22319
+ var leaf = (value) => {
22320
+ const text = name(value);
22321
+ if (text)
22322
+ return { model: text, effort: null };
22323
+ if (!isObject7(value))
22324
+ return null;
22325
+ const model = name(value.model);
22326
+ return model ? { model, effort: name(value.effort) } : null;
22327
+ };
22328
+ var isFlat = (value) => typeof value === "string" || isObject7(value) && Object.hasOwn(value, "model");
22329
+ var forHost = (value, host) => {
22330
+ if (isFlat(value))
22331
+ return leaf(value);
22332
+ if (!isObject7(value))
22333
+ return null;
22334
+ const family = host ? hostFamily(host) : null;
22335
+ for (const key of [family, host, "default"])
22336
+ if (key && Object.hasOwn(value, key)) {
22337
+ const found = leaf(value[key]);
22338
+ if (found)
22339
+ return found;
22340
+ }
22341
+ return null;
22342
+ };
22343
+ var rolesOf = (models) => isObject7(models) && isObject7(models.roles) ? models.roles : null;
22344
+ var resolveRoleModel = (cwd, selector, host, dir) => {
22345
+ const { role, tier } = selector;
22346
+ for (const source of modelSources(cwd, dir)) {
22347
+ const where = source.global ? "the global default" : `workspace ${source.name}`;
22348
+ const roles = rolesOf(source.models);
22349
+ const configured = roles && Object.hasOwn(roles, role) ? roles[role] : undefined;
22350
+ const fromRoles = role === "implementer" ? tier && isObject7(configured) && Object.hasOwn(configured, tier) ? forHost(configured[tier], host) : null : forHost(configured, host);
22351
+ if (fromRoles)
22352
+ return { ...fromRoles, source: `models.roles in ${where}` };
22353
+ if (role === "implementer" && tier && isObject7(source.tierModels)) {
22354
+ const fromTiers = Object.hasOwn(source.tierModels, tier) ? forHost(source.tierModels[tier], host) : null;
22355
+ if (fromTiers)
22356
+ return { model: fromTiers.model, effort: null, source: `fanout.tierModels in ${where}` };
22357
+ }
22358
+ }
22359
+ if (host && hostFamily(host) === "claude_code") {
22360
+ const builtIn = BUILT_IN_CLAUDE_CODE[tier ? `${role}.${tier}` : role];
22361
+ if (builtIn)
22362
+ return { model: builtIn.model, effort: builtIn.effort ?? null, source: "built-in" };
22363
+ }
22364
+ return null;
22365
+ };
22366
+ var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
22367
+ var roleForAgentType = (agentType, prompt) => {
22368
+ switch (agentType) {
22369
+ case "workit:implementer": {
22370
+ const tier = typeof prompt === "string" ? /^[ \t]*TIER:[ \t]*(mundane|standard|hard)\b/im.exec(prompt)?.[1] : undefined;
22371
+ return { role: "implementer", tier: tier?.toLowerCase() ?? "standard" };
22372
+ }
22373
+ case "workit:verifier":
22374
+ return { role: "verifier" };
22375
+ case "workit:reviewer":
22376
+ return { role: "reviewer" };
22377
+ case "Explore":
22378
+ return { role: "explore" };
22379
+ default:
22380
+ return null;
22381
+ }
22382
+ };
22383
+
22384
+ // packages/workit-core/src/hooks/agent-model.ts
22385
+ var MODELS = new Set(["sonnet", "opus", "haiku", "fable"]);
22386
+ var EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
22387
+ var given = (value) => typeof value === "string" && value.trim() !== "";
22388
+ var agentModelDecision = (cwd, tool, env = process.env) => {
22389
+ if (given(tool.model) || env.CLAUDE_CODE_SUBAGENT_MODEL_FORCE)
22390
+ return { kind: "none" };
22391
+ const selector = roleForAgentType(tool.subagent_type, tool.prompt);
22392
+ if (!selector)
22393
+ return { kind: "none" };
22394
+ const resolved = resolveRoleModel(cwd, selector, "claude_code");
22395
+ if (!resolved)
22396
+ return { kind: "none" };
22397
+ const model = resolved.model.toLowerCase();
22398
+ const effort = resolved.effort?.toLowerCase() ?? null;
22399
+ const input = { ...tool };
22400
+ if (MODELS.has(model))
22401
+ input.model = model;
22402
+ if (effort && EFFORTS.has(effort) && !given(tool.effort))
22403
+ input.effort = effort;
22404
+ if (input.model === undefined && input.effort === tool.effort)
22405
+ return { kind: "none" };
22406
+ const label = selector.tier ? `${selector.role} ${selector.tier}` : selector.role;
22407
+ return {
22408
+ kind: "update-input",
22409
+ input,
22410
+ text: `Workit set ${String(input.model ?? "no model")}${input.effort !== tool.effort ? ` (effort ${String(input.effort)})` : ""} on this ${String(tool.subagent_type)} spawn: role ${label}, ${resolved.source}. Name a model to choose another.`
22411
+ };
22412
+ };
22413
+
22253
22414
  // packages/workit-core/src/hooks/cwd-normalize.ts
22254
22415
  var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
22255
22416
  var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
@@ -22613,7 +22774,7 @@ function handleHook(input, deps) {
22613
22774
  const delta = standingDelta(input);
22614
22775
  if (!delta)
22615
22776
  return decision;
22616
- return decision.kind === "rewrite" ? { ...decision, text: [decision.text, delta].filter(Boolean).join(`
22777
+ return decision.kind === "rewrite" || decision.kind === "update-input" ? { ...decision, text: [decision.text, delta].filter(Boolean).join(`
22617
22778
  `) } : withContextLine(decision, delta);
22618
22779
  }
22619
22780
  function decide(input, deps) {
@@ -22726,6 +22887,8 @@ function decide(input, deps) {
22726
22887
  case "tool.pre":
22727
22888
  if (event.skill)
22728
22889
  recordSkillLoad(input, event.skill, "tool");
22890
+ if (event.agentInput && input.host === "claude_code")
22891
+ return agentModelDecision(input.cwd, event.agentInput);
22729
22892
  return NONE6;
22730
22893
  case "stop":
22731
22894
  return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
@@ -23009,6 +23172,16 @@ var eventOf = (name, value) => {
23009
23172
  skill: isRecord6(value.tool_input) ? optionalText(value.tool_input.skill) : null
23010
23173
  }
23011
23174
  };
23175
+ if (value.tool_name === "Agent" || value.tool_name === "Task")
23176
+ return {
23177
+ ok: true,
23178
+ event: {
23179
+ kind: "tool.pre",
23180
+ tool: value.tool_name,
23181
+ toolUseId,
23182
+ ...isRecord6(value.tool_input) ? { agentInput: value.tool_input } : {}
23183
+ }
23184
+ };
23012
23185
  if (!SHELL_TOOLS.has(value.tool_name))
23013
23186
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
23014
23187
  const command = toolCommand(value);
@@ -23085,7 +23258,7 @@ var CONTEXT_EVENTS = new Set([
23085
23258
  "PostToolUse"
23086
23259
  ]);
23087
23260
  var render = (decision, native) => {
23088
- if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
23261
+ if ((decision.kind === "rewrite" || decision.kind === "update-input") && native === "PreToolUse" && decision.input)
23089
23262
  return {
23090
23263
  json: {
23091
23264
  hookSpecificOutput: {
package/hooks/launch.mjs CHANGED
@@ -15,6 +15,7 @@
15
15
  // failure, npx network error, timeout, a crash) prints `{}`, exits 0 and
16
16
  // writes one `[workit] Cursor hook unavailable: …` line on stderr. Only a
17
17
  // policy deny from the hook itself (exit 2 with Cursor's deny JSON) blocks.
18
+ import { readFileSync } from "node:fs";
18
19
  import path from "node:path";
19
20
  import { fileURLToPath } from "node:url";
20
21
  import {
@@ -25,6 +26,35 @@ import {
25
26
 
26
27
  const pluginRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
27
28
 
29
+ // A Node below this package's own engines minimum cannot run the bundled hook:
30
+ // fail open with one clear line. WORKIT_HOOK_NODE_VERSION stands in for
31
+ // process.versions.node (tests). An unreadable minimum never blocks.
32
+ const nodeFloorMessage = () => {
33
+ try {
34
+ const found = process.env.WORKIT_HOOK_NODE_VERSION || process.versions.node;
35
+ const range = JSON.parse(readFileSync(path.join(pluginRoot, "package.json"), "utf8")).engines
36
+ ?.node;
37
+ const min = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(String(range ?? ""));
38
+ if (!min) return null;
39
+ const need = [min[1], min[2] ?? 0, min[3] ?? 0].map(Number);
40
+ const have = String(found).replace(/^v/, "").split(".").map(Number);
41
+ for (let i = 0; i < 3; i++) {
42
+ const a = have[i] ?? 0;
43
+ if (Number.isNaN(a)) return null;
44
+ if (a !== need[i]) return a < need[i] ? `needs Node ${range}, found ${found}` : null;
45
+ }
46
+ return null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ };
51
+ const tooOld = nodeFloorMessage();
52
+ if (tooOld && process.argv[2] !== "--probe") {
53
+ process.stderr.write(`[workit] Cursor hook unavailable: ${tooOld}\n`);
54
+ process.stdout.write("{}\n");
55
+ process.exit(0);
56
+ }
57
+
28
58
  // `--probe <bin>` (for `workit doctor`): run one no-op preCompact through
29
59
  // the candidate Cursor would use and print which one it was and how it went.
30
60
  if (process.argv[2] === "--probe") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cursor",
3
- "version": "8.27.0",
3
+ "version": "8.28.1",
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.27.0",
50
- "@brainervirus/workit-mcp": "^8.27.0"
49
+ "@brainervirus/workit-core": "^8.28.1",
50
+ "@brainervirus/workit-mcp": "^8.28.1"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=24"