@brainervirus/workit-cli 8.6.1 → 8.8.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 +452 -31
  2. package/package.json +4 -4
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.6.1",
68
+ version: "8.8.0",
69
69
  private: false,
70
70
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
71
71
  keywords: [
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
103
103
  },
104
104
  dependencies: {
105
105
  "@babel/parser": "7.29.9",
106
- "@brainervirus/workit-core": "^8.6.1",
107
- "@brainervirus/workit-cursor": "^8.6.1",
108
- "@brainervirus/workit-opencode": "^8.6.1",
106
+ "@brainervirus/workit-core": "^8.8.0",
107
+ "@brainervirus/workit-cursor": "^8.8.0",
108
+ "@brainervirus/workit-opencode": "^8.8.0",
109
109
  "@inkjs/ui": "2.0.0",
110
110
  ink: "7.1.1",
111
111
  react: "19.2.8"
@@ -15136,6 +15136,9 @@ function summarizeRow(row) {
15136
15136
  case "pr.created":
15137
15137
  summary = `#${row.pr ?? "?"} -> ${text("base")} at ${(row.head ?? "?").slice(0, 12)}${row.created === false ? " (existing)" : ""}`;
15138
15138
  break;
15139
+ case "pr.status":
15140
+ summary = `#${row.pr ?? "?"} ${text("state") && text("state") !== "open" ? `${text("state")} ` : ""}checks ${text("checks") || "?"} at ${(row.head ?? "?").slice(0, 12)}`;
15141
+ break;
15139
15142
  case "pr.merged":
15140
15143
  summary = `#${row.pr ?? "?"} ${text("method")} at ${(row.head ?? "?").slice(0, 12)}${text("mergeSha") ? ` -> ${text("mergeSha").slice(0, 12)}` : ""}`;
15141
15144
  break;
@@ -63192,7 +63195,7 @@ function cursorHookDrift(installed, pluginDir) {
63192
63195
  if (pluginDir)
63193
63196
  commands.push(canonicalHookEntry(event, pluginDir).command);
63194
63197
  const entry = Array.isArray(list) ? list[0] : undefined;
63195
- if (!isRecord4(entry) || !commands.includes(entry.command) || entry.failClosed === true || event === "preToolUse" && entry.matcher !== canonical.matcher)
63198
+ if (!isRecord4(entry) || !commands.includes(entry.command) || entry.failClosed === true || event === "stop" && entry.loop_limit !== canonical.loop_limit || event === "preToolUse" && entry.matcher !== canonical.matcher)
63196
63199
  drift.push(event);
63197
63200
  }
63198
63201
  return drift;
@@ -63215,7 +63218,11 @@ function cursorHooksEntry(pluginDir) {
63215
63218
  }
63216
63219
  var OPENCODE_NPM_PIN = "@brainervirus/workit-opencode", isRecord4 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest", cursorHookLauncher = (pluginDir) => pluginDir ? `node "${shellQuotedPath(path26.join(pluginDir, "hooks", "launch.mjs"))}"` : 'node "${CURSOR_PLUGIN_ROOT}/hooks/launch.mjs"', shellQuotedPath = (file) => path26.sep === "\\" ? file : file.replace(/([$`"\\])/g, "\\$1"), CURSOR_HOOK_RUN_COMMAND, CURSOR_PRETOOLUSE_MATCHER = "Write|Edit|Delete|Shell|Remove|ApplyPatch|Apply_Patch|Patch|Rename|Mkdir|Mv|Cp|Touch", CURSOR_HOOK_EVENTS, canonicalHookEntry = (event, pluginDir) => {
63217
63220
  const command = `${cursorHookLauncher(pluginDir)} workit-cursor-hook`;
63218
- return event === "preToolUse" ? { command, matcher: CURSOR_PRETOOLUSE_MATCHER, failClosed: false } : { command, failClosed: false };
63221
+ if (event === "preToolUse")
63222
+ return { command, matcher: CURSOR_PRETOOLUSE_MATCHER, failClosed: false };
63223
+ if (event === "stop")
63224
+ return { command, loop_limit: 1, failClosed: false };
63225
+ return { command, failClosed: false };
63219
63226
  };
63220
63227
  var init_registration = __esm(() => {
63221
63228
  CURSOR_HOOK_RUN_COMMAND = `${cursorHookLauncher()} workit-cursor-hook`;
@@ -63224,6 +63231,7 @@ var init_registration = __esm(() => {
63224
63231
  "beforeShellExecution",
63225
63232
  "subagentStart",
63226
63233
  "subagentStop",
63234
+ "stop",
63227
63235
  "preCompact"
63228
63236
  ];
63229
63237
  });
@@ -63932,11 +63940,13 @@ var init_skill_manifests = __esm(() => {
63932
63940
 
63933
63941
  // packages/workit-cli/src/admin/doctor.ts
63934
63942
  import { spawnSync as spawnSync13 } from "node:child_process";
63943
+ import { createHash as createHash13 } from "node:crypto";
63935
63944
  import {
63936
63945
  existsSync as existsSync13,
63937
63946
  lstatSync as lstatSync4,
63938
63947
  mkdirSync as mkdirSync5,
63939
63948
  mkdtempSync,
63949
+ readdirSync as readdirSync7,
63940
63950
  readFileSync as readFileSync13,
63941
63951
  readlinkSync as readlinkSync2,
63942
63952
  rmSync as rmSync2,
@@ -63977,8 +63987,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63977
63987
  cursorMcp: options.cursorMcp ?? path30.join(home, ".cursor", "mcp.json"),
63978
63988
  cursorPluginDir: options.cursorPluginDir ?? path30.join(home, ".cursor", "plugins", "local", "workit"),
63979
63989
  cursorHeartbeatDir: options.stateDir ?? (env.WORKFLOW_TOOLKIT_STATE || resolveStateDir()),
63990
+ codexHome: options.codexHome,
63991
+ piAgentDir: options.piAgentDir,
63980
63992
  env,
63981
- installer: options.installer ?? false
63993
+ installer: options.installer ?? false,
63994
+ cliVersion: options.cliVersion ?? null
63982
63995
  };
63983
63996
  }, readJson = (p) => {
63984
63997
  try {
@@ -64199,6 +64212,368 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64199
64212
  detail: `codex CLI ${installed} differs from the qualified ${qualified}`,
64200
64213
  fix: `Reinstall the qualified Codex CLI ${qualified} or record fresh qualification evidence`
64201
64214
  };
64215
+ }, readTomlTables = (file) => {
64216
+ const tables = new Map;
64217
+ let text;
64218
+ try {
64219
+ text = readFileSync13(file, "utf8");
64220
+ } catch {
64221
+ return tables;
64222
+ }
64223
+ const unquote = (raw) => {
64224
+ const s = raw.trim();
64225
+ if (s.startsWith('"')) {
64226
+ try {
64227
+ return JSON.parse(s);
64228
+ } catch {
64229
+ return s.slice(1, -1);
64230
+ }
64231
+ }
64232
+ return s.startsWith("'") ? s.slice(1, -1) : s;
64233
+ };
64234
+ const SEGMENT = /\s*("(?:[^"\\]|\\.)*"|'[^']*'|[A-Za-z0-9_-]+)\s*(?:\.|$)/y;
64235
+ let current = {};
64236
+ tables.set("", current);
64237
+ for (const line of text.split(/\r?\n/)) {
64238
+ const header = /^\s*\[([^[\]]+)\]\s*(?:#.*)?$/.exec(line);
64239
+ if (header) {
64240
+ const parts = [];
64241
+ SEGMENT.lastIndex = 0;
64242
+ let match;
64243
+ while (SEGMENT.lastIndex < header[1].length && (match = SEGMENT.exec(header[1])))
64244
+ parts.push(unquote(match[1]));
64245
+ const key = parts.join("\x00");
64246
+ current = tables.get(key) ?? {};
64247
+ tables.set(key, current);
64248
+ continue;
64249
+ }
64250
+ const pair = /^\s*("(?:[^"\\]|\\.)*"|[A-Za-z0-9_-]+)\s*=\s*(.+?)\s*$/.exec(line);
64251
+ if (!pair)
64252
+ continue;
64253
+ const value = pair[2].replace(/\s+#[^"']*$/, "");
64254
+ current[unquote(pair[1])] = value === "true" ? true : value === "false" ? false : unquote(value);
64255
+ }
64256
+ return tables;
64257
+ }, codexHomeOf = (res) => res.codexHome ?? (res.env.CODEX_HOME || path30.join(res.home, ".codex")), codexWorkitInstalls = (res) => {
64258
+ const codexHome = codexHomeOf(res);
64259
+ const configFile = path30.join(codexHome, "config.toml");
64260
+ const installs = [];
64261
+ for (const [key, table] of readTomlTables(configFile)) {
64262
+ const [kind, id, ...rest] = key.split("\x00");
64263
+ if (kind !== "plugins" || rest.length > 0 || !id?.startsWith("workit@") || table.enabled === false)
64264
+ continue;
64265
+ const marketplace = id.slice("workit@".length);
64266
+ const dir = path30.join(codexHome, "plugins", "cache", marketplace, "workit");
64267
+ let versions = [];
64268
+ try {
64269
+ versions = readdirSync7(dir).filter((v) => existsSync13(path30.join(dir, v, ".codex-plugin")));
64270
+ } catch {
64271
+ versions = [];
64272
+ }
64273
+ const version = versions.reduce((best, v) => best && semverAtLeast(best, v) ? best : v, null);
64274
+ installs.push({
64275
+ id,
64276
+ marketplace,
64277
+ root: version ? path30.join(dir, version) : null,
64278
+ version,
64279
+ configFile
64280
+ });
64281
+ }
64282
+ return installs;
64283
+ }, snakeEvent = (event) => event.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase(), codexHookHash = (event, matcher, handler) => {
64284
+ if (handler.type !== "command" || typeof handler.command !== "string" || Object.keys(handler).some((k) => !["type", "command", "timeout", "async"].includes(k)))
64285
+ return null;
64286
+ const hook = {
64287
+ async: handler.async ?? false,
64288
+ command: handler.command,
64289
+ timeout: handler.timeout ?? 600,
64290
+ type: "command"
64291
+ };
64292
+ const body = {
64293
+ event_name: snakeEvent(event),
64294
+ hooks: [hook],
64295
+ ...typeof matcher === "string" ? { matcher } : {}
64296
+ };
64297
+ return `sha256:${createHash13("sha256").update(JSON.stringify(body)).digest("hex")}`;
64298
+ }, codexHookTrust = (install, tables) => {
64299
+ const hooks = install.root ? readJson(path30.join(install.root, "hooks", "hooks.json"))?.hooks : null;
64300
+ if (!hooks || typeof hooks !== "object")
64301
+ return null;
64302
+ const result = [];
64303
+ for (const [event, groups] of Object.entries(hooks)) {
64304
+ if (!Array.isArray(groups))
64305
+ continue;
64306
+ groups.forEach((group, g) => {
64307
+ const handlers = Array.isArray(group?.hooks) ? group.hooks : [];
64308
+ handlers.forEach((handler, h) => {
64309
+ const key = `${install.id}:hooks/hooks.json:${snakeEvent(event)}:${g}:${h}`;
64310
+ const hash = codexHookHash(event, group?.matcher, handler ?? {});
64311
+ const state = tables.get(["hooks", "state", key].join("\x00"));
64312
+ const trusted = typeof state?.trusted_hash === "string" ? state.trusted_hash : null;
64313
+ result.push({
64314
+ key,
64315
+ hash,
64316
+ state: state?.enabled === false ? "disabled" : trusted === null ? "untrusted" : hash !== null && trusted !== hash ? "modified" : "trusted"
64317
+ });
64318
+ });
64319
+ });
64320
+ }
64321
+ return result;
64322
+ }, checkCodexHooks = (res) => {
64323
+ const installs = codexWorkitInstalls(res);
64324
+ if (installs.length === 0)
64325
+ return {
64326
+ id: "codex_hooks",
64327
+ status: "pass",
64328
+ detail: "no Workit Codex plugin enabled — skipping"
64329
+ };
64330
+ const tables = readTomlTables(installs[0].configFile);
64331
+ const problems = [];
64332
+ const toTrust = [];
64333
+ let reinstall;
64334
+ let total = 0;
64335
+ for (const install of installs) {
64336
+ const trust = codexHookTrust(install, tables);
64337
+ if (!trust) {
64338
+ problems.push(`${install.id}: no installed hooks/hooks.json`);
64339
+ reinstall ??= `codex plugin remove ${install.id} && codex plugin add ${install.id}`;
64340
+ continue;
64341
+ }
64342
+ total += trust.length;
64343
+ for (const state of ["untrusted", "modified", "disabled"]) {
64344
+ const hit = trust.filter((t) => t.state === state);
64345
+ if (hit.length > 0)
64346
+ problems.push(`${install.id}: ${hit.length} of ${trust.length} hooks ${state === "modified" ? "changed since trusted" : state} (${hit.map((t) => t.key.split(":")[2]).join(", ")})`);
64347
+ }
64348
+ toTrust.push(...trust.filter((t) => t.state !== "trusted"));
64349
+ }
64350
+ if (problems.length === 0)
64351
+ return {
64352
+ id: "codex_hooks",
64353
+ status: "pass",
64354
+ detail: `${total} Workit Codex plugin hooks trusted (${installs.map((i) => `${i.id} ${i.version ?? "?"}`).join(", ")})`
64355
+ };
64356
+ const disabled = toTrust.some((t) => t.state === "disabled");
64357
+ const fix = reinstall ?? `Start \`codex\`, run \`/hooks\` and ${disabled ? "enable and " : ""}trust the workit hooks (or pick "Trust all and continue" when Codex asks you to review hooks at startup)`;
64358
+ return {
64359
+ id: "codex_hooks",
64360
+ status: "warn",
64361
+ detail: `Codex skips untrusted plugin hooks — ${problems.join("; ")}`,
64362
+ fix
64363
+ };
64364
+ }, CODEX_AGENT_MARKER = "# Generated by Workit", AGENT_MARKER_VERSION, agentMarkerVersion = (text) => AGENT_MARKER_VERSION.exec(text)?.[1] ?? null, stampCodexAgent = (content, version) => version && content.startsWith(CODEX_AGENT_MARKER) && !agentMarkerVersion(content) ? `${CODEX_AGENT_MARKER} ${version}${content.slice(CODEX_AGENT_MARKER.length)}` : content, checkCodexAgents = (res) => {
64365
+ const installs = codexWorkitInstalls(res);
64366
+ if (installs.length === 0)
64367
+ return {
64368
+ id: "codex_agents",
64369
+ status: "pass",
64370
+ detail: "no Workit Codex plugin enabled — skipping"
64371
+ };
64372
+ const agentsDir = path30.join(codexHomeOf(res), "agents");
64373
+ const problems = [];
64374
+ const fixes = [];
64375
+ const installed = [];
64376
+ for (const install of installs) {
64377
+ const source = install.root ? path30.join(install.root, "agents") : null;
64378
+ const files = source && existsSync13(source) ? readdirSync7(source).filter((f) => f.endsWith(".toml")).toSorted() : [];
64379
+ if (files.length === 0) {
64380
+ problems.push(`${install.id} ${install.version ?? "?"} bundles no agents`);
64381
+ fixes.push(`codex plugin remove ${install.id} && codex plugin add ${install.id}`);
64382
+ continue;
64383
+ }
64384
+ const install_fix = `node ${JSON.stringify(path30.join(install.root, "dist", "launch-mcp.js"))} --install-agents, then start a new Codex session`;
64385
+ const pluginVersion = readJson(path30.join(install.root, "package.json"))?.version;
64386
+ const version = typeof pluginVersion === "string" ? pluginVersion.match(SEMVER)?.[0] ?? null : null;
64387
+ let stale = false;
64388
+ for (const file of files) {
64389
+ const dest = path30.join(agentsDir, file);
64390
+ const want = stampCodexAgent(readFileSync13(path30.join(source, file), "utf8"), version);
64391
+ let link = false;
64392
+ try {
64393
+ link = lstatSync4(dest).isSymbolicLink();
64394
+ } catch {
64395
+ link = false;
64396
+ }
64397
+ if (link) {
64398
+ problems.push(`${dest} is a symlink (the plugin never writes through one)`);
64399
+ fixes.push(`replace the symlink ${dest} with a file or remove it, then run ${install_fix}`);
64400
+ continue;
64401
+ }
64402
+ let have = null;
64403
+ try {
64404
+ have = readFileSync13(dest, "utf8");
64405
+ } catch {
64406
+ have = null;
64407
+ }
64408
+ const written = have === null ? null : agentMarkerVersion(have);
64409
+ const newer = written !== null && version !== null && versionBehind(version, written);
64410
+ if (have === want || newer) {
64411
+ installed.push(file.replace(/\.toml$/, ""));
64412
+ continue;
64413
+ }
64414
+ if (have !== null && !have.startsWith(CODEX_AGENT_MARKER)) {
64415
+ problems.push(`${dest} is a user agent that shadows ${install.id}'s ${file}`);
64416
+ fixes.push(`rename or remove ${dest}, then run ${install_fix}`);
64417
+ continue;
64418
+ }
64419
+ problems.push(`${dest} ${have === null ? "missing" : "outdated"}`);
64420
+ stale = true;
64421
+ }
64422
+ if (stale)
64423
+ fixes.push(install_fix);
64424
+ }
64425
+ if (problems.length === 0)
64426
+ return {
64427
+ id: "codex_agents",
64428
+ status: "pass",
64429
+ detail: `Codex agents ${installed.join(", ")} installed in ${agentsDir}`
64430
+ };
64431
+ return {
64432
+ id: "codex_agents",
64433
+ status: "warn",
64434
+ detail: problems.join("; "),
64435
+ fix: [...new Set(fixes)].join("; ")
64436
+ };
64437
+ }, PI_PACKAGE = "@brainervirus/workit-pi", PI_EXTENSION = "dist/workit.js", piAgentDirOf = (res) => {
64438
+ if (res.piAgentDir)
64439
+ return res.piAgentDir;
64440
+ const env = res.env.PI_CODING_AGENT_DIR;
64441
+ if (env)
64442
+ return env === "~" ? res.home : env.replace(/^~(?=\/|\\)/, res.home);
64443
+ return path30.join(res.home, ".pi", "agent");
64444
+ }, npmSpecName = (spec) => {
64445
+ const at = spec.indexOf("@", spec.startsWith("@") ? 1 : 0);
64446
+ return at > 0 ? { name: spec.slice(0, at), pinned: true } : { name: spec, pinned: false };
64447
+ }, findPiEntry = (res, agentDir) => {
64448
+ const scopes = [
64449
+ {
64450
+ scope: "project",
64451
+ settingsFile: path30.join(res.cwd, ".pi", "settings.json"),
64452
+ base: path30.join(res.cwd, ".pi")
64453
+ },
64454
+ { scope: "user", settingsFile: path30.join(agentDir, "settings.json"), base: agentDir }
64455
+ ];
64456
+ for (const { scope, settingsFile, base } of scopes) {
64457
+ const packages = readJson(settingsFile)?.packages;
64458
+ if (!Array.isArray(packages))
64459
+ continue;
64460
+ for (const entry of packages) {
64461
+ const source = typeof entry === "string" ? entry : typeof entry?.source === "string" ? entry.source : null;
64462
+ if (!source)
64463
+ continue;
64464
+ const filter = typeof entry === "object" ? entry : null;
64465
+ if (source.startsWith("npm:")) {
64466
+ const spec = npmSpecName(source.slice(4).trim());
64467
+ if (spec.name !== PI_PACKAGE)
64468
+ continue;
64469
+ const root = path30.join(base, "npm", "node_modules", ...PI_PACKAGE.split("/"));
64470
+ return { scope, settingsFile, source, filter, root, npm: { pinned: spec.pinned } };
64471
+ }
64472
+ if (/^(git:|https?:|ssh:|git@)/.test(source))
64473
+ continue;
64474
+ const local = path30.resolve(base, source.replace(/^~(?=\/|\\)/, res.home));
64475
+ if (readJson(path30.join(local, "package.json"))?.name === PI_PACKAGE)
64476
+ return { scope, settingsFile, source, filter, root: local, npm: null };
64477
+ }
64478
+ }
64479
+ return null;
64480
+ }, globToRegExp3 = (pattern) => new RegExp(`^${pattern.replace(/^\.\//, "").replace(/[.+^${}()|[\]\\]/g, "\\$&").split(/\*\*\/?/).map((part) => part.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")).join(".*")}$`), piFilterKeeps = (patterns, file) => {
64481
+ if (!Array.isArray(patterns))
64482
+ return true;
64483
+ const list = patterns.filter((p) => typeof p === "string");
64484
+ if (list.length === 0)
64485
+ return false;
64486
+ const name = path30.posix.basename(file);
64487
+ const matches = (p) => {
64488
+ const re = globToRegExp3(p);
64489
+ return re.test(file) || re.test(name);
64490
+ };
64491
+ const exact = (p) => p.replace(/^\.\//, "") === file;
64492
+ const includes = list.filter((p) => !/^[!+-]/.test(p));
64493
+ let kept = includes.length === 0 || includes.some(matches);
64494
+ if (list.some((p) => p.startsWith("!") && matches(p.slice(1))))
64495
+ kept = false;
64496
+ if (list.some((p) => p.startsWith("+") && exact(p.slice(1))))
64497
+ kept = true;
64498
+ if (list.some((p) => p.startsWith("-") && exact(p.slice(1))))
64499
+ kept = false;
64500
+ return kept;
64501
+ }, checkPiExtension = (res) => {
64502
+ const agentDir = piAgentDirOf(res);
64503
+ if (!existsSync13(agentDir) && !commandOnPath("pi", res.env))
64504
+ return { id: "pi_extension", status: "pass", detail: "Pi not installed — skipping" };
64505
+ const installCmd = (scope, spec = `npm:${PI_PACKAGE}`) => `pi install ${spec}${scope === "project" ? " -l" : ""}`;
64506
+ const entry = findPiEntry(res, agentDir);
64507
+ if (!entry)
64508
+ return {
64509
+ id: "pi_extension",
64510
+ status: "warn",
64511
+ detail: `Workit Pi extension missing: no ${PI_PACKAGE} entry in ${path30.join(agentDir, "settings.json")} or ${path30.join(res.cwd, ".pi", "settings.json")}`,
64512
+ fix: installCmd("user")
64513
+ };
64514
+ const where = `${entry.source} (${entry.scope}, ${entry.settingsFile})`;
64515
+ const rebuild = `cd ${JSON.stringify(entry.root)} && bun run build`;
64516
+ const pkg = readJson(path30.join(entry.root, "package.json"));
64517
+ if (!pkg)
64518
+ return {
64519
+ id: "pi_extension",
64520
+ status: "warn",
64521
+ detail: `Workit Pi extension missing: ${where} is listed but not installed at ${entry.root}`,
64522
+ fix: entry.npm ? installCmd(entry.scope, entry.source) : `restore ${entry.root}, then ${rebuild}`
64523
+ };
64524
+ const declared = Array.isArray(pkg.pi?.extensions) ? pkg.pi.extensions.some((e) => typeof e === "string" && e.replace(/^\.\//, "") === PI_EXTENSION) : false;
64525
+ if (!declared || !existsSync13(path30.join(entry.root, PI_EXTENSION)))
64526
+ return {
64527
+ id: "pi_extension",
64528
+ status: "warn",
64529
+ detail: `Workit Pi extension not loading: ${entry.root} ${declared ? `has no ${PI_EXTENSION}` : `does not declare ${PI_EXTENSION} in its pi manifest`}`,
64530
+ fix: entry.npm ? `pi remove ${entry.source}${entry.scope === "project" ? " -l" : ""} && ${installCmd(entry.scope, entry.source)}` : rebuild
64531
+ };
64532
+ const filter = entry.filter?.extensions;
64533
+ if (!piFilterKeeps(filter, PI_EXTENSION))
64534
+ return {
64535
+ id: "pi_extension",
64536
+ status: "warn",
64537
+ detail: `Workit Pi extension not loading: the ${PI_PACKAGE} entry in ${entry.settingsFile} filters out ${PI_EXTENSION} (extensions: ${JSON.stringify(filter)})`,
64538
+ fix: `remove the "extensions" filter from the ${PI_PACKAGE} entry in ${entry.settingsFile}, or enable the extension with \`pi config${entry.scope === "project" ? " -l" : ""}\``
64539
+ };
64540
+ const version = typeof pkg.version === "string" ? pkg.version : null;
64541
+ const cli = res.cliVersion;
64542
+ if (version && cli && versionBehind(version, cli)) {
64543
+ if (!entry.npm)
64544
+ return {
64545
+ id: "pi_extension",
64546
+ status: "pass",
64547
+ detail: `Workit Pi extension ${version} from local checkout ${where} loads ${PI_EXTENSION} (older than workit ${cli}; a checkout is not compared with the registry)`
64548
+ };
64549
+ const latest = registryLatestVersion(res, PI_PACKAGE);
64550
+ const registryProbed = latest !== null && !res.env.WORKIT_DOCTOR_STALE_REGISTRY_VERSION;
64551
+ if (!latest)
64552
+ return {
64553
+ id: "pi_extension",
64554
+ status: "pass",
64555
+ detail: `Workit Pi extension ${version} from ${where} loads ${PI_EXTENSION}; registry unreachable, so whether a version newer than ${version} is published is unknown (workit ${cli})`
64556
+ };
64557
+ if (versionBehind(version, latest))
64558
+ return {
64559
+ id: "pi_extension",
64560
+ status: "warn",
64561
+ detail: `Workit Pi extension stale: ${PI_PACKAGE} ${version} from ${where} is older than workit ${cli} (published ${latest})`,
64562
+ fix: entry.npm.pinned ? installCmd(entry.scope, `npm:${PI_PACKAGE}@${latest}`) : `pi update npm:${PI_PACKAGE}`,
64563
+ registryProbed
64564
+ };
64565
+ return {
64566
+ id: "pi_extension",
64567
+ status: "pass",
64568
+ detail: `Workit Pi extension ${version} from ${where} is the newest published (workit ${cli})`,
64569
+ registryProbed
64570
+ };
64571
+ }
64572
+ return {
64573
+ id: "pi_extension",
64574
+ status: "pass",
64575
+ detail: `Workit Pi extension ${version ?? "?"} from ${where} loads ${PI_EXTENSION}`
64576
+ };
64202
64577
  }, assetPathsFor = (host, dev) => {
64203
64578
  const pkg = path30.join(dev, "packages", `workit-${host}`);
64204
64579
  switch (host) {
@@ -65306,6 +65681,7 @@ var init_doctor = __esm(() => {
65306
65681
  init_host_install();
65307
65682
  init_skill_manifests();
65308
65683
  OPENCODE_V1_FIX = `OpenCode < ${SUPPORT_MATRIX.opencode.minimum} cannot load Workit 3 (V2 plugin API only): upgrade OpenCode to ${SUPPORT_MATRIX.opencode.minimum}+, or stay on Workit 2.x by pinning "@brainervirus/workit-opencode@2" in the opencode.json "plugin" array`;
65684
+ AGENT_MARKER_VERSION = /^# Generated by Workit (\d+\.\d+\.\d+)\b/;
65309
65685
  canonicalCursorHook = cursorHooksEntry().command;
65310
65686
  SILENT_HOOK_GRACE_MS = 2 * 60000;
65311
65687
  SEMVER = /\d+\.\d+\.\d+/;
@@ -65314,6 +65690,9 @@ var init_doctor = __esm(() => {
65314
65690
  checkRuntime,
65315
65691
  checkVersions,
65316
65692
  checkCodexPin,
65693
+ checkCodexHooks,
65694
+ checkCodexAgents,
65695
+ checkPiExtension,
65317
65696
  checkOpencodeVersion,
65318
65697
  checkClaudePlugin,
65319
65698
  checkWorkitOnPath,
@@ -65353,7 +65732,7 @@ import {
65353
65732
  mkdirSync as mkdirSync6,
65354
65733
  mkdtempSync as mkdtempSync2,
65355
65734
  readFileSync as readFileSync14,
65356
- readdirSync as readdirSync7,
65735
+ readdirSync as readdirSync8,
65357
65736
  realpathSync as realpathSync7,
65358
65737
  renameSync as renameSync3,
65359
65738
  rmSync as rmSync3,
@@ -66239,7 +66618,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66239
66618
  }
66240
66619
  }, samePluginContent = (src, dest, pluginDir, relative = "") => {
66241
66620
  try {
66242
- for (const entry of readdirSync7(src, { withFileTypes: true })) {
66621
+ for (const entry of readdirSync8(src, { withFileTypes: true })) {
66243
66622
  if (entry.name === "node_modules")
66244
66623
  continue;
66245
66624
  const source = path31.join(src, entry.name);
@@ -66254,7 +66633,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66254
66633
  return false;
66255
66634
  }
66256
66635
  }
66257
- for (const entry of readdirSync7(dest, { withFileTypes: true })) {
66636
+ for (const entry of readdirSync8(dest, { withFileTypes: true })) {
66258
66637
  if (existsSync14(path31.join(src, entry.name)))
66259
66638
  continue;
66260
66639
  const rel = path31.join(relative, entry.name);
@@ -66273,7 +66652,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66273
66652
  const sourceRules = path31.join(src, "rules");
66274
66653
  const installedRules = path31.join(dest, "rules");
66275
66654
  try {
66276
- for (const entry of readdirSync7(installedRules, { withFileTypes: true })) {
66655
+ for (const entry of readdirSync8(installedRules, { withFileTypes: true })) {
66277
66656
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
66278
66657
  continue;
66279
66658
  if (existsSync14(path31.join(sourceRules, entry.name)))
@@ -66290,7 +66669,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66290
66669
  if (existsSync14(legacyRules)) {
66291
66670
  try {
66292
66671
  mkdirSync6(path31.join(res.cursorPluginDir, "rules"), { recursive: true });
66293
- for (const entry of readdirSync7(legacyRules, { withFileTypes: true })) {
66672
+ for (const entry of readdirSync8(legacyRules, { withFileTypes: true })) {
66294
66673
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
66295
66674
  continue;
66296
66675
  const target = path31.join(res.cursorPluginDir, "rules", entry.name);
@@ -70082,7 +70461,7 @@ import {
70082
70461
  } from "node:fs";
70083
70462
  import path35 from "node:path";
70084
70463
  import os12 from "node:os";
70085
- import { createHash as createHash13 } from "node:crypto";
70464
+ import { createHash as createHash14 } from "node:crypto";
70086
70465
  function previewUpgrade(hosts = HOSTS2, deps = {}) {
70087
70466
  const paths = upgradePaths(deps);
70088
70467
  const { home, configDir } = paths;
@@ -70210,7 +70589,7 @@ function applyUpgrade(plan, deps = {}) {
70210
70589
  mkdirSync8(backup, { recursive: true, mode: 448 });
70211
70590
  for (const file of Object.keys(plan.configDigests))
70212
70591
  if (existsSync17(file)) {
70213
- const target = path35.join(backup, createHash13("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
70592
+ const target = path35.join(backup, createHash14("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
70214
70593
  copyFileSync5(file, target);
70215
70594
  chmodSync(target, 384);
70216
70595
  }
@@ -70465,7 +70844,7 @@ var HOSTS2, upgradePaths = (deps) => {
70465
70844
  if (!data || typeof data !== "object" || Array.isArray(data))
70466
70845
  throw new Error(`${file} is not a configuration object`);
70467
70846
  return data;
70468
- }, digest2 = (file) => existsSync17(file) ? createHash13("sha256").update(readFileSync17(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
70847
+ }, digest2 = (file) => existsSync17(file) ? createHash14("sha256").update(readFileSync17(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
70469
70848
  const { home, env } = upgradePaths(deps);
70470
70849
  let step;
70471
70850
  try {
@@ -70951,7 +71330,12 @@ async function run5(argv, io) {
70951
71330
  }
70952
71331
  fixLock = clearStaleMetadataLock(root, { force });
70953
71332
  }
70954
- const report = runDoctor({ host: "cli", cwd: io.cwd, workspaceRoot: root });
71333
+ const report = runDoctor({
71334
+ host: "cli",
71335
+ cwd: io.cwd,
71336
+ workspaceRoot: root,
71337
+ cliVersion: package_default.version
71338
+ });
70955
71339
  let knowledge = null;
70956
71340
  try {
70957
71341
  knowledge = lintKnowledge(root);
@@ -70984,7 +71368,9 @@ async function run5(argv, io) {
70984
71368
  io.stdout(`${mark} ${check.id} — ${check.detail}
70985
71369
  `);
70986
71370
  if (check.fix)
70987
- io.stdout(` fix: ${check.fix}
71371
+ io.stdout(` fix: ${check.fix.replaceAll(`
71372
+ `, `
71373
+ `)}
70988
71374
  `);
70989
71375
  }
70990
71376
  io.stdout(`${knowledgeLine}
@@ -70994,6 +71380,7 @@ async function run5(argv, io) {
70994
71380
  return report.exitCode;
70995
71381
  }
70996
71382
  var init_doctor2 = __esm(() => {
71383
+ init_package();
70997
71384
  init_doctor();
70998
71385
  init_knowledge();
70999
71386
  init_store_lock();
@@ -71107,7 +71494,7 @@ var init_redact = __esm(() => {
71107
71494
 
71108
71495
  // packages/workit-core/src/checks.ts
71109
71496
  import { spawn, spawnSync as spawnSync16 } from "node:child_process";
71110
- import { createHash as createHash14 } from "node:crypto";
71497
+ import { createHash as createHash15 } from "node:crypto";
71111
71498
  import fs18 from "node:fs";
71112
71499
  import os13 from "node:os";
71113
71500
  import path37 from "node:path";
@@ -71441,7 +71828,7 @@ function pruneCheckLogs(storeRoot, options = {}) {
71441
71828
  }
71442
71829
  return report;
71443
71830
  }
71444
- var MAX_LOG_BYTES, TAIL_LINES = 80, MAX_LINE_CHARS = 400, redactLog = (text) => redactText(text), PEM_BEGIN, PEM_END, BATCH_BYTES, MAX_PENDING, PEM_BODY, MAX_KEY_LINES = 4096, MAX_KEY_BYTES, CMD_META, DRAIN_MS = 250, FORWARDED_SIGNALS, logDigest = (log) => `sha256:${createHash14("sha256").update(log).digest("hex")}`, LOG_RETENTION;
71831
+ var MAX_LOG_BYTES, TAIL_LINES = 80, MAX_LINE_CHARS = 400, redactLog = (text) => redactText(text), PEM_BEGIN, PEM_END, BATCH_BYTES, MAX_PENDING, PEM_BODY, MAX_KEY_LINES = 4096, MAX_KEY_BYTES, CMD_META, DRAIN_MS = 250, FORWARDED_SIGNALS, logDigest = (log) => `sha256:${createHash15("sha256").update(log).digest("hex")}`, LOG_RETENTION;
71445
71832
  var init_checks3 = __esm(() => {
71446
71833
  init_redact();
71447
71834
  MAX_LOG_BYTES = 2 * 1024 * 1024;
@@ -72916,6 +73303,33 @@ function buildStatusDoc(cwd, resolved, status, options) {
72916
73303
  babysit: babysitAction(blockers[0]?.next ?? "READY", blockers[0]?.reason ?? null, status.draft || blockers.some((blocker) => blocker.next === "MARK_READY"))
72917
73304
  });
72918
73305
  }
73306
+ function recordPrStatus(cwd, doc, actor) {
73307
+ const checks = doc.checks.state;
73308
+ const state = doc.state;
73309
+ if (state !== "open" && state !== "merged" && state !== "closed")
73310
+ return;
73311
+ if (state === "open" && checks !== "passing" && checks !== "failing" && checks !== "pending")
73312
+ return;
73313
+ try {
73314
+ const ledger = readLedger(cwd);
73315
+ if (!ledger.ok)
73316
+ return;
73317
+ const last = ledger.value.rows.findLast((row) => row.type === "pr.status" && row.observer === "workit_cli" && row.pr === doc.number && row.repo === doc.repo);
73318
+ if (last && last.head === doc.head.sha && last.checks === checks && (last.state ?? "open") === state)
73319
+ return;
73320
+ appendObserved(cwd, {
73321
+ type: "pr.status",
73322
+ actor,
73323
+ branch: doc.head.branch,
73324
+ head: doc.head.sha,
73325
+ pr: doc.number,
73326
+ repo: doc.repo,
73327
+ forge: doc.forge,
73328
+ state,
73329
+ checks
73330
+ });
73331
+ } catch {}
73332
+ }
72919
73333
  function prStatusReport(cwd, resolved, options = {}) {
72920
73334
  const number = selectPr(cwd, resolved, options);
72921
73335
  if (!number.ok)
@@ -72930,6 +73344,8 @@ function prStatusReport(cwd, resolved, options = {}) {
72930
73344
  });
72931
73345
  if (!doc.ok)
72932
73346
  return doc;
73347
+ if (options.actor)
73348
+ recordPrStatus(cwd, doc.data, options.actor);
72933
73349
  return success2({ doc: doc.data, raw: status.data });
72934
73350
  }
72935
73351
  function waitVerdict(doc, options) {
@@ -73055,6 +73471,7 @@ function recordAccepted(cwd, resolved, status, accepted, options) {
73055
73471
  var MAX_LOG_TAILS = 5, GITLAB_MERGEABLE, GITHUB_MERGEABLE, NO_CHECKS_GRACE_MS = 90000;
73056
73472
  var init_report = __esm(() => {
73057
73473
  init_rev();
73474
+ init_ledger();
73058
73475
  init_reruns();
73059
73476
  GITLAB_MERGEABLE = new Set(["mergeable"]);
73060
73477
  GITHUB_MERGEABLE = new Set(["clean", "unstable", "has_hooks"]);
@@ -73426,7 +73843,7 @@ var init_pr_ops = __esm(() => {
73426
73843
 
73427
73844
  // packages/workit-core/src/stack.ts
73428
73845
  import { spawnSync as spawnSync19 } from "node:child_process";
73429
- import { createHash as createHash15, randomBytes as randomBytes3 } from "node:crypto";
73846
+ import { createHash as createHash16, randomBytes as randomBytes3 } from "node:crypto";
73430
73847
  import fs20 from "node:fs";
73431
73848
  import path41 from "node:path";
73432
73849
  function worktreeOf(cwd, branch) {
@@ -74593,7 +75010,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
74593
75010
  return dir.ok ? path41.join(dir.data, "worktrees") : null;
74594
75011
  }, firstLine = (run) => (`${run.stderr}
74595
75012
  ${run.stdout}`.split(`
74596
- `).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash15("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
75013
+ `).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash16("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
74597
75014
  if (name.length === 0 || name.length > 200)
74598
75015
  return false;
74599
75016
  for (let index = 0;index < name.length; index += 1) {
@@ -76216,7 +76633,8 @@ async function status(argv, io) {
76216
76633
  pr,
76217
76634
  branch: flags.values.branch ?? null,
76218
76635
  logLines: logLines ?? 60,
76219
- identity: connected.data.identity
76636
+ identity: connected.data.identity,
76637
+ actor: actorFromEnv(io.env)
76220
76638
  });
76221
76639
  if (!report.ok)
76222
76640
  return forgeFail(io, report);
@@ -76587,6 +77005,7 @@ async function wait(argv, io) {
76587
77005
  const number = selectPr(io.cwd, resolved, { pr, branch: flags.values.branch ?? null });
76588
77006
  if (!number.ok)
76589
77007
  return forgeFail(io, number);
77008
+ const actor = actorFromEnv(io.env);
76590
77009
  let polls = 0;
76591
77010
  let errors = 0;
76592
77011
  let status = null;
@@ -76601,6 +77020,7 @@ async function wait(argv, io) {
76601
77020
  const doc = buildStatusDoc(io.cwd, resolved, status, { logLines: 0, behind: false });
76602
77021
  if (!doc.ok)
76603
77022
  return forgeFail(io, doc);
77023
+ recordPrStatus(io.cwd, doc.data, actor);
76604
77024
  verdict = waitVerdict(doc.data, { head, elapsedMs });
76605
77025
  if (verdict.state !== "waiting")
76606
77026
  break;
@@ -76727,6 +77147,7 @@ async function run11(argv, io) {
76727
77147
  var WAIT_USAGE = "workit ci wait [--pr <n> | --branch <b>] [--head <sha>] [--timeout 20m] [--interval 30s] [--json]", RERUN_USAGE = "workit ci rerun [--pr <n> | --branch <b>] [--check <name>… | --failed] --reason flake|infra [--force] [--json]", ONE_POLL_MS = 20000, MAX_POLL_ERRORS = 3, seconds = (ms) => `${Math.round(ms / 1000)}s`;
76728
77148
  var init_ci = __esm(() => {
76729
77149
  init_report();
77150
+ init_ledger();
76730
77151
  init_autonomy();
76731
77152
  init_output();
76732
77153
  init_forge_common();
@@ -77522,7 +77943,7 @@ var init_stack2 = __esm(() => {
77522
77943
 
77523
77944
  // packages/workit-core/src/fanout.ts
77524
77945
  import { spawnSync as spawnSync23 } from "node:child_process";
77525
- import { createHash as createHash16 } from "node:crypto";
77946
+ import { createHash as createHash17 } from "node:crypto";
77526
77947
  import fs23 from "node:fs";
77527
77948
  import path45 from "node:path";
77528
77949
  function expandBraces(pattern, limit = 64) {
@@ -78003,7 +78424,7 @@ function sliceHash(slice) {
78003
78424
  context: _context,
78004
78425
  ...definition
78005
78426
  } = slice;
78006
- return createHash16("sha256").update(JSON.stringify(definition)).digest("hex").slice(0, 16);
78427
+ return createHash17("sha256").update(JSON.stringify(definition)).digest("hex").slice(0, 16);
78007
78428
  }
78008
78429
  function fanoutsDir(cwd) {
78009
78430
  const root = storeRoot(cwd);
@@ -78708,13 +79129,13 @@ var init_fanout_worktree = __esm(() => {
78708
79129
  });
78709
79130
 
78710
79131
  // packages/workit-core/src/fanout-brief.ts
78711
- import { createHash as createHash17 } from "node:crypto";
79132
+ import { createHash as createHash18 } from "node:crypto";
78712
79133
  function workerSession(lead, slice, attempt) {
78713
79134
  const tail = attempt === null ? "" : `+try${attempt}`;
78714
79135
  const full = `${lead}-w-${slice}${tail}`;
78715
79136
  if (full.length <= SESSION_MAX)
78716
79137
  return full;
78717
- const digest = createHash17("sha256").update(slice).digest("hex").slice(0, 12);
79138
+ const digest = createHash18("sha256").update(slice).digest("hex").slice(0, 12);
78718
79139
  return `${lead.slice(0, SESSION_MAX - 3 - digest.length - tail.length)}-w-${digest}${tail}`;
78719
79140
  }
78720
79141
  function renderBrief(cwd, plan, input) {
@@ -95900,7 +96321,7 @@ var init_rules = __esm(() => {
95900
96321
 
95901
96322
  // packages/workit-cli/src/test-audit/audit.ts
95902
96323
  import { spawnSync as spawnSync25 } from "node:child_process";
95903
- import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync22, statSync as statSync7 } from "node:fs";
96324
+ import { existsSync as existsSync19, readdirSync as readdirSync9, readFileSync as readFileSync22, statSync as statSync7 } from "node:fs";
95904
96325
  import path49 from "node:path";
95905
96326
  function git7(cwd, args) {
95906
96327
  const run = spawnSync25("git", ["-c", "core.quotepath=off", ...args], {
@@ -95911,7 +96332,7 @@ function git7(cwd, args) {
95911
96332
  return { ok: run.status === 0, stdout: run.stdout ?? "" };
95912
96333
  }
95913
96334
  function walkTests(dir, out) {
95914
- for (const entry of readdirSync8(dir, { withFileTypes: true })) {
96335
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
95915
96336
  if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
95916
96337
  continue;
95917
96338
  const full = path49.join(dir, entry.name);
@@ -96047,7 +96468,7 @@ import {
96047
96468
  lstatSync as lstatSync7,
96048
96469
  mkdirSync as mkdirSync9,
96049
96470
  mkdtempSync as mkdtempSync4,
96050
- readdirSync as readdirSync9,
96471
+ readdirSync as readdirSync10,
96051
96472
  readFileSync as readFileSync23,
96052
96473
  readlinkSync as readlinkSync3,
96053
96474
  realpathSync as realpathSync9,
@@ -96144,7 +96565,7 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
96144
96565
  let removed = 0;
96145
96566
  let names = [];
96146
96567
  try {
96147
- names = readdirSync9(tmp);
96568
+ names = readdirSync10(tmp);
96148
96569
  } catch {
96149
96570
  return 0;
96150
96571
  }
@@ -96173,7 +96594,7 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
96173
96594
  }
96174
96595
  function linkNodeModules(origin, target, repo, copy) {
96175
96596
  mkdirSync9(target, { recursive: true });
96176
- for (const entry of readdirSync9(origin, { withFileTypes: true })) {
96597
+ for (const entry of readdirSync10(origin, { withFileTypes: true })) {
96177
96598
  const from = path50.join(origin, entry.name);
96178
96599
  const to = path50.join(target, entry.name);
96179
96600
  if (entry.name.startsWith("@") && entry.isDirectory()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "8.6.1",
3
+ "version": "8.8.0",
4
4
  "private": false,
5
5
  "description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
6
6
  "keywords": [
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@babel/parser": "7.29.9",
41
- "@brainervirus/workit-core": "^8.6.1",
42
- "@brainervirus/workit-cursor": "^8.6.1",
43
- "@brainervirus/workit-opencode": "^8.6.1",
41
+ "@brainervirus/workit-core": "^8.8.0",
42
+ "@brainervirus/workit-cursor": "^8.8.0",
43
+ "@brainervirus/workit-opencode": "^8.8.0",
44
44
  "@inkjs/ui": "2.0.0",
45
45
  "ink": "7.1.1",
46
46
  "react": "19.2.8"