@brainervirus/workit-cli 8.13.1 → 8.14.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.
Files changed (2) hide show
  1. package/dist/index.js +103 -12
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@brainervirus/workit-cli",
68
- version: "8.13.1",
68
+ version: "8.14.0",
69
69
  private: false,
70
70
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
71
71
  keywords: [
@@ -103,13 +103,13 @@ var init_package = __esm(() => {
103
103
  },
104
104
  dependencies: {
105
105
  "@babel/parser": "7.29.9",
106
- "@brainervirus/workit-cursor": "^8.13.1",
106
+ "@brainervirus/workit-cursor": "^8.14.0",
107
107
  "@inkjs/ui": "2.0.0",
108
108
  ink: "7.1.1",
109
109
  react: "19.2.8"
110
110
  },
111
111
  devDependencies: {
112
- "@brainervirus/workit-core": "^8.13.1"
112
+ "@brainervirus/workit-core": "^8.14.0"
113
113
  },
114
114
  engines: {
115
115
  node: ">=24"
@@ -14022,23 +14022,79 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
14022
14022
  }, resolveRuntimeReleaseTracks = (cwd) => {
14023
14023
  const matched = matchRuntimeWorkspace(cwd);
14024
14024
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
14025
- }, workspaceTrailers = (cwd, dir) => {
14025
+ }, inheritedSetting = (cwd, dir, has) => {
14026
14026
  let matched;
14027
14027
  try {
14028
14028
  matched = matchRuntimeWorkspace(cwd, dir);
14029
14029
  } catch {
14030
- return { enabled: true, workspace: null, from: null };
14030
+ return null;
14031
14031
  }
14032
14032
  if (!matched)
14033
- return { enabled: true, workspace: null, from: null };
14033
+ return null;
14034
14034
  const { selected, matches } = matched;
14035
14035
  const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
14036
- const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
14036
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
14037
+ }, workspaceTrailers = (cwd, dir) => {
14038
+ const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
14037
14039
  return {
14038
- enabled: source?.trailers !== false,
14039
- workspace: selected.name,
14040
- from: source?.name ?? null
14040
+ enabled: found?.source?.trailers !== false,
14041
+ workspace: found?.workspace ?? null,
14042
+ from: found?.source?.name ?? null
14041
14043
  };
14044
+ }, hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, ""), TIER_KEYS, TIER_HOSTS, workspaceTierModelIssues = (cwd, dir) => {
14045
+ let matched;
14046
+ try {
14047
+ matched = matchRuntimeWorkspace(cwd, dir);
14048
+ } catch {
14049
+ return [];
14050
+ }
14051
+ const issues = [];
14052
+ for (const entry of matched?.matches ?? []) {
14053
+ const models = entry.fanout?.tierModels;
14054
+ if (models === undefined)
14055
+ continue;
14056
+ const where = `workspace ${entry.name} fanout.tierModels`;
14057
+ if (!models || typeof models !== "object" || Array.isArray(models)) {
14058
+ issues.push(`${where} is not an object; ignored`);
14059
+ continue;
14060
+ }
14061
+ for (const [tier, value] of Object.entries(models)) {
14062
+ if (!TIER_KEYS.has(tier))
14063
+ issues.push(`${where}.${tier}: unknown tier; ignored`);
14064
+ else if (typeof value === "string") {
14065
+ if (!value.trim())
14066
+ issues.push(`${where}.${tier}: empty model name; ignored`);
14067
+ } else if (!value || typeof value !== "object" || Array.isArray(value))
14068
+ issues.push(`${where}.${tier}: not a model name or a per-host object; ignored`);
14069
+ else
14070
+ for (const [host, model] of Object.entries(value)) {
14071
+ if (!TIER_HOSTS.has(host))
14072
+ issues.push(`${where}.${tier}.${host}: unknown host; ignored`);
14073
+ else if (typeof model !== "string" || !model.trim())
14074
+ issues.push(`${where}.${tier}.${host}: not a model name; ignored`);
14075
+ }
14076
+ }
14077
+ }
14078
+ return issues;
14079
+ }, workspaceTierModel = (cwd, tier, host, dir) => {
14080
+ const pick = (entry) => {
14081
+ const fanout = entry.fanout;
14082
+ const models = fanout?.tierModels;
14083
+ if (!models || typeof models !== "object" || !Object.hasOwn(models, tier))
14084
+ return null;
14085
+ const value = models[tier];
14086
+ if (typeof value === "string")
14087
+ return value.trim() || null;
14088
+ if (!value || typeof value !== "object")
14089
+ return null;
14090
+ const byHost = value;
14091
+ const family = host ? hostFamily(host) : null;
14092
+ const named = (family && Object.hasOwn(byHost, family) ? byHost[family] : undefined) ?? (host && Object.hasOwn(byHost, host) ? byHost[host] : undefined) ?? (Object.hasOwn(byHost, "default") ? byHost.default : undefined);
14093
+ return typeof named === "string" && named.trim() ? named.trim() : null;
14094
+ };
14095
+ const found = inheritedSetting(cwd, dir, (entry) => pick(entry) !== null);
14096
+ const model = found?.source ? pick(found.source) : null;
14097
+ return model && found?.source ? { model, workspace: found.source.name } : null;
14042
14098
  }, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
14043
14099
  const matched = matchRuntimeWorkspace(cwd);
14044
14100
  if (!matched)
@@ -14190,6 +14246,8 @@ var init_workspaces = __esm(() => {
14190
14246
  defaultEndpoint: unknown().optional(),
14191
14247
  verification: _enum(["self", "independent"]).optional(),
14192
14248
  trailers: boolean2().optional(),
14249
+ liveSessionGuard: boolean2().optional(),
14250
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
14193
14251
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
14194
14252
  kind,
14195
14253
  union([boolean2(), literal("verified")]).optional()
@@ -14239,6 +14297,17 @@ var init_workspaces = __esm(() => {
14239
14297
  runtimeWorkspaceIndexSchema = object({
14240
14298
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
14241
14299
  }).passthrough();
14300
+ TIER_KEYS = new Set(["mundane", "standard", "hard"]);
14301
+ TIER_HOSTS = new Set([
14302
+ "claude_code",
14303
+ "opencode",
14304
+ "codex",
14305
+ "codex_cli",
14306
+ "codex_desktop",
14307
+ "cursor",
14308
+ "pi",
14309
+ "default"
14310
+ ]);
14242
14311
  });
14243
14312
 
14244
14313
  // packages/workit-core/src/core/release-tracks.ts
@@ -73303,9 +73372,15 @@ function show(argv, io) {
73303
73372
  return emit(io, fail("invalid_input", error instanceof Error ? error.message : String(error)));
73304
73373
  }
73305
73374
  const tracks = releaseTracksReport(io.cwd);
73306
- const shown = tracks.tracks.length || tracks.error ? { ...autonomy, releaseTracks: tracks } : autonomy;
73375
+ const tierModelIssues = workspaceTierModelIssues(io.cwd);
73376
+ const shown = {
73377
+ ...autonomy,
73378
+ ...tracks.tracks.length || tracks.error ? { releaseTracks: tracks } : {},
73379
+ ...tierModelIssues.length ? { tierModelIssues } : {}
73380
+ };
73307
73381
  return emit(io, ok(shown), (data) => [
73308
73382
  ...data.note ? [`note: ${data.note}`] : [],
73383
+ ...tierModelIssues.map((issue) => `note: ${issue}`),
73309
73384
  data.workspace ? `workspace ${data.workspace} (${data.source === "default" ? "D4 defaults" : `from ${data.source}`})` : "no workspace matches this checkout: D4 defaults apply",
73310
73385
  ...describe2(data.grants, data.configured, data, data.verification),
73311
73386
  ...describeReleaseTracks(tracks).map((line) => ` ${line}`),
@@ -73401,6 +73476,7 @@ usage: ${USAGE2}`)), run9 = (argv, io) => runGrant(argv, io);
73401
73476
  var init_grant = __esm(() => {
73402
73477
  init_autonomy();
73403
73478
  init_release_tracks();
73479
+ init_workspaces();
73404
73480
  init_output();
73405
73481
  init_prompt();
73406
73482
  AGENT_ENV = [
@@ -82374,6 +82450,7 @@ function renderBrief(cwd, plan, input) {
82374
82450
  what: String(row.what)
82375
82451
  }));
82376
82452
  const identity = identityOf(input.actor.host);
82453
+ const model = workspaceTierModel(cwd, slice.tier, input.actor.host);
82377
82454
  const minted = workerSession(sessionSafe(input.actor.session), slice.id, input.attempt);
82378
82455
  const session = identity === "declared" ? minted : null;
82379
82456
  const fanIn = plan.fanIn === "integration" ? [
@@ -82397,6 +82474,7 @@ function renderBrief(cwd, plan, input) {
82397
82474
  ...slice.acceptance.map((line) => ` - ${line}`),
82398
82475
  `VERIFY: ${slice.verify.join("; ")}`,
82399
82476
  `TIER: ${slice.tier}`,
82477
+ ...model ? [` model: ${model.model} (fanout.tierModels in workspace ${model.workspace})`] : [],
82400
82478
  `TIMEBOX: ${slice.timebox ?? DEFAULT_TIMEBOX}; past it without a new commit you will be replaced`,
82401
82479
  scratchLine,
82402
82480
  `FORBIDDEN: ${slice.forbidden.join("; ")}`,
@@ -82417,6 +82495,7 @@ function renderBrief(cwd, plan, input) {
82417
82495
  branch,
82418
82496
  base: slice.base,
82419
82497
  tier: slice.tier,
82498
+ model: model?.model ?? null,
82420
82499
  scratch,
82421
82500
  session,
82422
82501
  standing,
@@ -82459,6 +82538,7 @@ var DEFAULT_TIMEBOX = "30 minutes", REPORT, identityOf = (host) => {
82459
82538
  ];
82460
82539
  }, sessionSafe = (text) => text.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-"), SESSION_MAX = 128;
82461
82540
  var init_fanout_brief = __esm(() => {
82541
+ init_workspaces();
82462
82542
  init_ledger();
82463
82543
  init_fanout();
82464
82544
  init_fanout_worktree();
@@ -83076,6 +83156,15 @@ function fanoutStatus(cwd, plan, options) {
83076
83156
  if (row.waitsFor.length && !suggested.includes(id))
83077
83157
  row.reasons.push(`waits for ${row.waitsFor.join(", ")}`);
83078
83158
  }
83159
+ const host = options.host === undefined ? actorFromEnv(process.env).host : options.host;
83160
+ const spawnModels = {};
83161
+ for (const id of spawnable) {
83162
+ const tier = plan.slices.find((slice) => slice.id === id)?.tier;
83163
+ const model = tier ? workspaceTierModel(cwd, tier, host) : null;
83164
+ if (model)
83165
+ spawnModels[id] = model.model;
83166
+ }
83167
+ const spawnList = spawnable.map((id) => spawnModels[id] ? `${id} (model ${spawnModels[id]})` : id).join(", ");
83079
83168
  const stuck = order.filter((id) => out.get(id)?.stuck);
83080
83169
  const down = breaker.down();
83081
83170
  if (down)
@@ -83085,7 +83174,7 @@ function fanoutStatus(cwd, plan, options) {
83085
83174
  const row = out.get(id);
83086
83175
  return !row.landed && row.reasons.length === 1 && row.reasons[0] === "no PR";
83087
83176
  });
83088
- const next = stuck.length ? `replace ${stuck.join(", ")}: stop the worker, then workit fanout worktree release ${stuck[0]} (records git status first) and respawn it in MODE: resume` : suggested.length ? online ? `land in this order: ${suggested.join(", ")} (workit fanout check first; workit pr merge each)` : `land in this order: ${suggested.join(", ")} once each has a PR (offline: PRs unchecked; open a missing one with workit git push && workit pr create --fill, then workit fanout status online)` : noPr.length ? `open PRs for ${noPr.join(", ")}: workit git push && workit pr create --fill on each branch` : spawnable.length ? `spawn ${spawnable.join(", ")} (workit fanout worktree create <slice> on hosts without native worktrees)` : order.every((id) => out.get(id)?.landed) ? "every slice has landed" : "wait for workers, then workit fanout status again";
83177
+ const next = stuck.length ? `replace ${stuck.join(", ")}: stop the worker, then workit fanout worktree release ${stuck[0]} (records git status first) and respawn it in MODE: resume` : suggested.length ? online ? `land in this order: ${suggested.join(", ")} (workit fanout check first; workit pr merge each)` : `land in this order: ${suggested.join(", ")} once each has a PR (offline: PRs unchecked; open a missing one with workit git push && workit pr create --fill, then workit fanout status online)` : noPr.length ? `open PRs for ${noPr.join(", ")}: workit git push && workit pr create --fill on each branch` : spawnable.length ? `spawn ${spawnList} (workit fanout worktree create <slice> on hosts without native worktrees)` : order.every((id) => out.get(id)?.landed) ? "every slice has landed" : "wait for workers, then workit fanout status again";
83089
83178
  return {
83090
83179
  ok: true,
83091
83180
  data: {
@@ -83099,6 +83188,7 @@ function fanoutStatus(cwd, plan, options) {
83099
83188
  slices: order.map((id) => out.get(id)),
83100
83189
  landingOrder: suggested,
83101
83190
  spawnable,
83191
+ spawnModels,
83102
83192
  stuck,
83103
83193
  next,
83104
83194
  notes
@@ -83112,6 +83202,7 @@ var DEFAULT_STUCK_MS, isoOf = (seconds) => {
83112
83202
  var init_fanout_status = __esm(() => {
83113
83203
  init_duration();
83114
83204
  init_report();
83205
+ init_workspaces();
83115
83206
  init_ledger();
83116
83207
  init_fanout_check();
83117
83208
  init_fanout_landed();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "8.13.1",
3
+ "version": "8.14.0",
4
4
  "private": false,
5
5
  "description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
6
6
  "keywords": [
@@ -38,13 +38,13 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@babel/parser": "7.29.9",
41
- "@brainervirus/workit-cursor": "^8.13.1",
41
+ "@brainervirus/workit-cursor": "^8.14.0",
42
42
  "@inkjs/ui": "2.0.0",
43
43
  "ink": "7.1.1",
44
44
  "react": "19.2.8"
45
45
  },
46
46
  "devDependencies": {
47
- "@brainervirus/workit-core": "^8.13.1"
47
+ "@brainervirus/workit-core": "^8.14.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=24"