@brainervirus/workit-cli 8.6.1 → 8.7.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 +407 -28
  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.7.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.7.0",
107
+ "@brainervirus/workit-cursor": "^8.7.0",
108
+ "@brainervirus/workit-opencode": "^8.7.0",
109
109
  "@inkjs/ui": "2.0.0",
110
110
  ink: "7.1.1",
111
111
  react: "19.2.8"
@@ -63932,11 +63932,13 @@ var init_skill_manifests = __esm(() => {
63932
63932
 
63933
63933
  // packages/workit-cli/src/admin/doctor.ts
63934
63934
  import { spawnSync as spawnSync13 } from "node:child_process";
63935
+ import { createHash as createHash13 } from "node:crypto";
63935
63936
  import {
63936
63937
  existsSync as existsSync13,
63937
63938
  lstatSync as lstatSync4,
63938
63939
  mkdirSync as mkdirSync5,
63939
63940
  mkdtempSync,
63941
+ readdirSync as readdirSync7,
63940
63942
  readFileSync as readFileSync13,
63941
63943
  readlinkSync as readlinkSync2,
63942
63944
  rmSync as rmSync2,
@@ -63977,8 +63979,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63977
63979
  cursorMcp: options.cursorMcp ?? path30.join(home, ".cursor", "mcp.json"),
63978
63980
  cursorPluginDir: options.cursorPluginDir ?? path30.join(home, ".cursor", "plugins", "local", "workit"),
63979
63981
  cursorHeartbeatDir: options.stateDir ?? (env.WORKFLOW_TOOLKIT_STATE || resolveStateDir()),
63982
+ codexHome: options.codexHome,
63983
+ piAgentDir: options.piAgentDir,
63980
63984
  env,
63981
- installer: options.installer ?? false
63985
+ installer: options.installer ?? false,
63986
+ cliVersion: options.cliVersion ?? null
63982
63987
  };
63983
63988
  }, readJson = (p) => {
63984
63989
  try {
@@ -64199,6 +64204,368 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64199
64204
  detail: `codex CLI ${installed} differs from the qualified ${qualified}`,
64200
64205
  fix: `Reinstall the qualified Codex CLI ${qualified} or record fresh qualification evidence`
64201
64206
  };
64207
+ }, readTomlTables = (file) => {
64208
+ const tables = new Map;
64209
+ let text;
64210
+ try {
64211
+ text = readFileSync13(file, "utf8");
64212
+ } catch {
64213
+ return tables;
64214
+ }
64215
+ const unquote = (raw) => {
64216
+ const s = raw.trim();
64217
+ if (s.startsWith('"')) {
64218
+ try {
64219
+ return JSON.parse(s);
64220
+ } catch {
64221
+ return s.slice(1, -1);
64222
+ }
64223
+ }
64224
+ return s.startsWith("'") ? s.slice(1, -1) : s;
64225
+ };
64226
+ const SEGMENT = /\s*("(?:[^"\\]|\\.)*"|'[^']*'|[A-Za-z0-9_-]+)\s*(?:\.|$)/y;
64227
+ let current = {};
64228
+ tables.set("", current);
64229
+ for (const line of text.split(/\r?\n/)) {
64230
+ const header = /^\s*\[([^[\]]+)\]\s*(?:#.*)?$/.exec(line);
64231
+ if (header) {
64232
+ const parts = [];
64233
+ SEGMENT.lastIndex = 0;
64234
+ let match;
64235
+ while (SEGMENT.lastIndex < header[1].length && (match = SEGMENT.exec(header[1])))
64236
+ parts.push(unquote(match[1]));
64237
+ const key = parts.join("\x00");
64238
+ current = tables.get(key) ?? {};
64239
+ tables.set(key, current);
64240
+ continue;
64241
+ }
64242
+ const pair = /^\s*("(?:[^"\\]|\\.)*"|[A-Za-z0-9_-]+)\s*=\s*(.+?)\s*$/.exec(line);
64243
+ if (!pair)
64244
+ continue;
64245
+ const value = pair[2].replace(/\s+#[^"']*$/, "");
64246
+ current[unquote(pair[1])] = value === "true" ? true : value === "false" ? false : unquote(value);
64247
+ }
64248
+ return tables;
64249
+ }, codexHomeOf = (res) => res.codexHome ?? (res.env.CODEX_HOME || path30.join(res.home, ".codex")), codexWorkitInstalls = (res) => {
64250
+ const codexHome = codexHomeOf(res);
64251
+ const configFile = path30.join(codexHome, "config.toml");
64252
+ const installs = [];
64253
+ for (const [key, table] of readTomlTables(configFile)) {
64254
+ const [kind, id, ...rest] = key.split("\x00");
64255
+ if (kind !== "plugins" || rest.length > 0 || !id?.startsWith("workit@") || table.enabled === false)
64256
+ continue;
64257
+ const marketplace = id.slice("workit@".length);
64258
+ const dir = path30.join(codexHome, "plugins", "cache", marketplace, "workit");
64259
+ let versions = [];
64260
+ try {
64261
+ versions = readdirSync7(dir).filter((v) => existsSync13(path30.join(dir, v, ".codex-plugin")));
64262
+ } catch {
64263
+ versions = [];
64264
+ }
64265
+ const version = versions.reduce((best, v) => best && semverAtLeast(best, v) ? best : v, null);
64266
+ installs.push({
64267
+ id,
64268
+ marketplace,
64269
+ root: version ? path30.join(dir, version) : null,
64270
+ version,
64271
+ configFile
64272
+ });
64273
+ }
64274
+ return installs;
64275
+ }, snakeEvent = (event) => event.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase(), codexHookHash = (event, matcher, handler) => {
64276
+ if (handler.type !== "command" || typeof handler.command !== "string" || Object.keys(handler).some((k) => !["type", "command", "timeout", "async"].includes(k)))
64277
+ return null;
64278
+ const hook = {
64279
+ async: handler.async ?? false,
64280
+ command: handler.command,
64281
+ timeout: handler.timeout ?? 600,
64282
+ type: "command"
64283
+ };
64284
+ const body = {
64285
+ event_name: snakeEvent(event),
64286
+ hooks: [hook],
64287
+ ...typeof matcher === "string" ? { matcher } : {}
64288
+ };
64289
+ return `sha256:${createHash13("sha256").update(JSON.stringify(body)).digest("hex")}`;
64290
+ }, codexHookTrust = (install, tables) => {
64291
+ const hooks = install.root ? readJson(path30.join(install.root, "hooks", "hooks.json"))?.hooks : null;
64292
+ if (!hooks || typeof hooks !== "object")
64293
+ return null;
64294
+ const result = [];
64295
+ for (const [event, groups] of Object.entries(hooks)) {
64296
+ if (!Array.isArray(groups))
64297
+ continue;
64298
+ groups.forEach((group, g) => {
64299
+ const handlers = Array.isArray(group?.hooks) ? group.hooks : [];
64300
+ handlers.forEach((handler, h) => {
64301
+ const key = `${install.id}:hooks/hooks.json:${snakeEvent(event)}:${g}:${h}`;
64302
+ const hash = codexHookHash(event, group?.matcher, handler ?? {});
64303
+ const state = tables.get(["hooks", "state", key].join("\x00"));
64304
+ const trusted = typeof state?.trusted_hash === "string" ? state.trusted_hash : null;
64305
+ result.push({
64306
+ key,
64307
+ hash,
64308
+ state: state?.enabled === false ? "disabled" : trusted === null ? "untrusted" : hash !== null && trusted !== hash ? "modified" : "trusted"
64309
+ });
64310
+ });
64311
+ });
64312
+ }
64313
+ return result;
64314
+ }, checkCodexHooks = (res) => {
64315
+ const installs = codexWorkitInstalls(res);
64316
+ if (installs.length === 0)
64317
+ return {
64318
+ id: "codex_hooks",
64319
+ status: "pass",
64320
+ detail: "no Workit Codex plugin enabled — skipping"
64321
+ };
64322
+ const tables = readTomlTables(installs[0].configFile);
64323
+ const problems = [];
64324
+ const toTrust = [];
64325
+ let reinstall;
64326
+ let total = 0;
64327
+ for (const install of installs) {
64328
+ const trust = codexHookTrust(install, tables);
64329
+ if (!trust) {
64330
+ problems.push(`${install.id}: no installed hooks/hooks.json`);
64331
+ reinstall ??= `codex plugin remove ${install.id} && codex plugin add ${install.id}`;
64332
+ continue;
64333
+ }
64334
+ total += trust.length;
64335
+ for (const state of ["untrusted", "modified", "disabled"]) {
64336
+ const hit = trust.filter((t) => t.state === state);
64337
+ if (hit.length > 0)
64338
+ problems.push(`${install.id}: ${hit.length} of ${trust.length} hooks ${state === "modified" ? "changed since trusted" : state} (${hit.map((t) => t.key.split(":")[2]).join(", ")})`);
64339
+ }
64340
+ toTrust.push(...trust.filter((t) => t.state !== "trusted"));
64341
+ }
64342
+ if (problems.length === 0)
64343
+ return {
64344
+ id: "codex_hooks",
64345
+ status: "pass",
64346
+ detail: `${total} Workit Codex plugin hooks trusted (${installs.map((i) => `${i.id} ${i.version ?? "?"}`).join(", ")})`
64347
+ };
64348
+ const disabled = toTrust.some((t) => t.state === "disabled");
64349
+ 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)`;
64350
+ return {
64351
+ id: "codex_hooks",
64352
+ status: "warn",
64353
+ detail: `Codex skips untrusted plugin hooks — ${problems.join("; ")}`,
64354
+ fix
64355
+ };
64356
+ }, 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) => {
64357
+ const installs = codexWorkitInstalls(res);
64358
+ if (installs.length === 0)
64359
+ return {
64360
+ id: "codex_agents",
64361
+ status: "pass",
64362
+ detail: "no Workit Codex plugin enabled — skipping"
64363
+ };
64364
+ const agentsDir = path30.join(codexHomeOf(res), "agents");
64365
+ const problems = [];
64366
+ const fixes = [];
64367
+ const installed = [];
64368
+ for (const install of installs) {
64369
+ const source = install.root ? path30.join(install.root, "agents") : null;
64370
+ const files = source && existsSync13(source) ? readdirSync7(source).filter((f) => f.endsWith(".toml")).toSorted() : [];
64371
+ if (files.length === 0) {
64372
+ problems.push(`${install.id} ${install.version ?? "?"} bundles no agents`);
64373
+ fixes.push(`codex plugin remove ${install.id} && codex plugin add ${install.id}`);
64374
+ continue;
64375
+ }
64376
+ const install_fix = `node ${JSON.stringify(path30.join(install.root, "dist", "launch-mcp.js"))} --install-agents, then start a new Codex session`;
64377
+ const pluginVersion = readJson(path30.join(install.root, "package.json"))?.version;
64378
+ const version = typeof pluginVersion === "string" ? pluginVersion.match(SEMVER)?.[0] ?? null : null;
64379
+ let stale = false;
64380
+ for (const file of files) {
64381
+ const dest = path30.join(agentsDir, file);
64382
+ const want = stampCodexAgent(readFileSync13(path30.join(source, file), "utf8"), version);
64383
+ let link = false;
64384
+ try {
64385
+ link = lstatSync4(dest).isSymbolicLink();
64386
+ } catch {
64387
+ link = false;
64388
+ }
64389
+ if (link) {
64390
+ problems.push(`${dest} is a symlink (the plugin never writes through one)`);
64391
+ fixes.push(`replace the symlink ${dest} with a file or remove it, then run ${install_fix}`);
64392
+ continue;
64393
+ }
64394
+ let have = null;
64395
+ try {
64396
+ have = readFileSync13(dest, "utf8");
64397
+ } catch {
64398
+ have = null;
64399
+ }
64400
+ const written = have === null ? null : agentMarkerVersion(have);
64401
+ const newer = written !== null && version !== null && versionBehind(version, written);
64402
+ if (have === want || newer) {
64403
+ installed.push(file.replace(/\.toml$/, ""));
64404
+ continue;
64405
+ }
64406
+ if (have !== null && !have.startsWith(CODEX_AGENT_MARKER)) {
64407
+ problems.push(`${dest} is a user agent that shadows ${install.id}'s ${file}`);
64408
+ fixes.push(`rename or remove ${dest}, then run ${install_fix}`);
64409
+ continue;
64410
+ }
64411
+ problems.push(`${dest} ${have === null ? "missing" : "outdated"}`);
64412
+ stale = true;
64413
+ }
64414
+ if (stale)
64415
+ fixes.push(install_fix);
64416
+ }
64417
+ if (problems.length === 0)
64418
+ return {
64419
+ id: "codex_agents",
64420
+ status: "pass",
64421
+ detail: `Codex agents ${installed.join(", ")} installed in ${agentsDir}`
64422
+ };
64423
+ return {
64424
+ id: "codex_agents",
64425
+ status: "warn",
64426
+ detail: problems.join("; "),
64427
+ fix: [...new Set(fixes)].join("; ")
64428
+ };
64429
+ }, PI_PACKAGE = "@brainervirus/workit-pi", PI_EXTENSION = "dist/workit.js", piAgentDirOf = (res) => {
64430
+ if (res.piAgentDir)
64431
+ return res.piAgentDir;
64432
+ const env = res.env.PI_CODING_AGENT_DIR;
64433
+ if (env)
64434
+ return env === "~" ? res.home : env.replace(/^~(?=\/|\\)/, res.home);
64435
+ return path30.join(res.home, ".pi", "agent");
64436
+ }, npmSpecName = (spec) => {
64437
+ const at = spec.indexOf("@", spec.startsWith("@") ? 1 : 0);
64438
+ return at > 0 ? { name: spec.slice(0, at), pinned: true } : { name: spec, pinned: false };
64439
+ }, findPiEntry = (res, agentDir) => {
64440
+ const scopes = [
64441
+ {
64442
+ scope: "project",
64443
+ settingsFile: path30.join(res.cwd, ".pi", "settings.json"),
64444
+ base: path30.join(res.cwd, ".pi")
64445
+ },
64446
+ { scope: "user", settingsFile: path30.join(agentDir, "settings.json"), base: agentDir }
64447
+ ];
64448
+ for (const { scope, settingsFile, base } of scopes) {
64449
+ const packages = readJson(settingsFile)?.packages;
64450
+ if (!Array.isArray(packages))
64451
+ continue;
64452
+ for (const entry of packages) {
64453
+ const source = typeof entry === "string" ? entry : typeof entry?.source === "string" ? entry.source : null;
64454
+ if (!source)
64455
+ continue;
64456
+ const filter = typeof entry === "object" ? entry : null;
64457
+ if (source.startsWith("npm:")) {
64458
+ const spec = npmSpecName(source.slice(4).trim());
64459
+ if (spec.name !== PI_PACKAGE)
64460
+ continue;
64461
+ const root = path30.join(base, "npm", "node_modules", ...PI_PACKAGE.split("/"));
64462
+ return { scope, settingsFile, source, filter, root, npm: { pinned: spec.pinned } };
64463
+ }
64464
+ if (/^(git:|https?:|ssh:|git@)/.test(source))
64465
+ continue;
64466
+ const local = path30.resolve(base, source.replace(/^~(?=\/|\\)/, res.home));
64467
+ if (readJson(path30.join(local, "package.json"))?.name === PI_PACKAGE)
64468
+ return { scope, settingsFile, source, filter, root: local, npm: null };
64469
+ }
64470
+ }
64471
+ return null;
64472
+ }, globToRegExp3 = (pattern) => new RegExp(`^${pattern.replace(/^\.\//, "").replace(/[.+^${}()|[\]\\]/g, "\\$&").split(/\*\*\/?/).map((part) => part.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")).join(".*")}$`), piFilterKeeps = (patterns, file) => {
64473
+ if (!Array.isArray(patterns))
64474
+ return true;
64475
+ const list = patterns.filter((p) => typeof p === "string");
64476
+ if (list.length === 0)
64477
+ return false;
64478
+ const name = path30.posix.basename(file);
64479
+ const matches = (p) => {
64480
+ const re = globToRegExp3(p);
64481
+ return re.test(file) || re.test(name);
64482
+ };
64483
+ const exact = (p) => p.replace(/^\.\//, "") === file;
64484
+ const includes = list.filter((p) => !/^[!+-]/.test(p));
64485
+ let kept = includes.length === 0 || includes.some(matches);
64486
+ if (list.some((p) => p.startsWith("!") && matches(p.slice(1))))
64487
+ kept = false;
64488
+ if (list.some((p) => p.startsWith("+") && exact(p.slice(1))))
64489
+ kept = true;
64490
+ if (list.some((p) => p.startsWith("-") && exact(p.slice(1))))
64491
+ kept = false;
64492
+ return kept;
64493
+ }, checkPiExtension = (res) => {
64494
+ const agentDir = piAgentDirOf(res);
64495
+ if (!existsSync13(agentDir) && !commandOnPath("pi", res.env))
64496
+ return { id: "pi_extension", status: "pass", detail: "Pi not installed — skipping" };
64497
+ const installCmd = (scope, spec = `npm:${PI_PACKAGE}`) => `pi install ${spec}${scope === "project" ? " -l" : ""}`;
64498
+ const entry = findPiEntry(res, agentDir);
64499
+ if (!entry)
64500
+ return {
64501
+ id: "pi_extension",
64502
+ status: "warn",
64503
+ detail: `Workit Pi extension missing: no ${PI_PACKAGE} entry in ${path30.join(agentDir, "settings.json")} or ${path30.join(res.cwd, ".pi", "settings.json")}`,
64504
+ fix: installCmd("user")
64505
+ };
64506
+ const where = `${entry.source} (${entry.scope}, ${entry.settingsFile})`;
64507
+ const rebuild = `cd ${JSON.stringify(entry.root)} && bun run build`;
64508
+ const pkg = readJson(path30.join(entry.root, "package.json"));
64509
+ if (!pkg)
64510
+ return {
64511
+ id: "pi_extension",
64512
+ status: "warn",
64513
+ detail: `Workit Pi extension missing: ${where} is listed but not installed at ${entry.root}`,
64514
+ fix: entry.npm ? installCmd(entry.scope, entry.source) : `restore ${entry.root}, then ${rebuild}`
64515
+ };
64516
+ const declared = Array.isArray(pkg.pi?.extensions) ? pkg.pi.extensions.some((e) => typeof e === "string" && e.replace(/^\.\//, "") === PI_EXTENSION) : false;
64517
+ if (!declared || !existsSync13(path30.join(entry.root, PI_EXTENSION)))
64518
+ return {
64519
+ id: "pi_extension",
64520
+ status: "warn",
64521
+ detail: `Workit Pi extension not loading: ${entry.root} ${declared ? `has no ${PI_EXTENSION}` : `does not declare ${PI_EXTENSION} in its pi manifest`}`,
64522
+ fix: entry.npm ? `pi remove ${entry.source}${entry.scope === "project" ? " -l" : ""} && ${installCmd(entry.scope, entry.source)}` : rebuild
64523
+ };
64524
+ const filter = entry.filter?.extensions;
64525
+ if (!piFilterKeeps(filter, PI_EXTENSION))
64526
+ return {
64527
+ id: "pi_extension",
64528
+ status: "warn",
64529
+ detail: `Workit Pi extension not loading: the ${PI_PACKAGE} entry in ${entry.settingsFile} filters out ${PI_EXTENSION} (extensions: ${JSON.stringify(filter)})`,
64530
+ fix: `remove the "extensions" filter from the ${PI_PACKAGE} entry in ${entry.settingsFile}, or enable the extension with \`pi config${entry.scope === "project" ? " -l" : ""}\``
64531
+ };
64532
+ const version = typeof pkg.version === "string" ? pkg.version : null;
64533
+ const cli = res.cliVersion;
64534
+ if (version && cli && versionBehind(version, cli)) {
64535
+ if (!entry.npm)
64536
+ return {
64537
+ id: "pi_extension",
64538
+ status: "pass",
64539
+ detail: `Workit Pi extension ${version} from local checkout ${where} loads ${PI_EXTENSION} (older than workit ${cli}; a checkout is not compared with the registry)`
64540
+ };
64541
+ const latest = registryLatestVersion(res, PI_PACKAGE);
64542
+ const registryProbed = latest !== null && !res.env.WORKIT_DOCTOR_STALE_REGISTRY_VERSION;
64543
+ if (!latest)
64544
+ return {
64545
+ id: "pi_extension",
64546
+ status: "pass",
64547
+ 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})`
64548
+ };
64549
+ if (versionBehind(version, latest))
64550
+ return {
64551
+ id: "pi_extension",
64552
+ status: "warn",
64553
+ detail: `Workit Pi extension stale: ${PI_PACKAGE} ${version} from ${where} is older than workit ${cli} (published ${latest})`,
64554
+ fix: entry.npm.pinned ? installCmd(entry.scope, `npm:${PI_PACKAGE}@${latest}`) : `pi update npm:${PI_PACKAGE}`,
64555
+ registryProbed
64556
+ };
64557
+ return {
64558
+ id: "pi_extension",
64559
+ status: "pass",
64560
+ detail: `Workit Pi extension ${version} from ${where} is the newest published (workit ${cli})`,
64561
+ registryProbed
64562
+ };
64563
+ }
64564
+ return {
64565
+ id: "pi_extension",
64566
+ status: "pass",
64567
+ detail: `Workit Pi extension ${version ?? "?"} from ${where} loads ${PI_EXTENSION}`
64568
+ };
64202
64569
  }, assetPathsFor = (host, dev) => {
64203
64570
  const pkg = path30.join(dev, "packages", `workit-${host}`);
64204
64571
  switch (host) {
@@ -65306,6 +65673,7 @@ var init_doctor = __esm(() => {
65306
65673
  init_host_install();
65307
65674
  init_skill_manifests();
65308
65675
  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`;
65676
+ AGENT_MARKER_VERSION = /^# Generated by Workit (\d+\.\d+\.\d+)\b/;
65309
65677
  canonicalCursorHook = cursorHooksEntry().command;
65310
65678
  SILENT_HOOK_GRACE_MS = 2 * 60000;
65311
65679
  SEMVER = /\d+\.\d+\.\d+/;
@@ -65314,6 +65682,9 @@ var init_doctor = __esm(() => {
65314
65682
  checkRuntime,
65315
65683
  checkVersions,
65316
65684
  checkCodexPin,
65685
+ checkCodexHooks,
65686
+ checkCodexAgents,
65687
+ checkPiExtension,
65317
65688
  checkOpencodeVersion,
65318
65689
  checkClaudePlugin,
65319
65690
  checkWorkitOnPath,
@@ -65353,7 +65724,7 @@ import {
65353
65724
  mkdirSync as mkdirSync6,
65354
65725
  mkdtempSync as mkdtempSync2,
65355
65726
  readFileSync as readFileSync14,
65356
- readdirSync as readdirSync7,
65727
+ readdirSync as readdirSync8,
65357
65728
  realpathSync as realpathSync7,
65358
65729
  renameSync as renameSync3,
65359
65730
  rmSync as rmSync3,
@@ -66239,7 +66610,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66239
66610
  }
66240
66611
  }, samePluginContent = (src, dest, pluginDir, relative = "") => {
66241
66612
  try {
66242
- for (const entry of readdirSync7(src, { withFileTypes: true })) {
66613
+ for (const entry of readdirSync8(src, { withFileTypes: true })) {
66243
66614
  if (entry.name === "node_modules")
66244
66615
  continue;
66245
66616
  const source = path31.join(src, entry.name);
@@ -66254,7 +66625,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66254
66625
  return false;
66255
66626
  }
66256
66627
  }
66257
- for (const entry of readdirSync7(dest, { withFileTypes: true })) {
66628
+ for (const entry of readdirSync8(dest, { withFileTypes: true })) {
66258
66629
  if (existsSync14(path31.join(src, entry.name)))
66259
66630
  continue;
66260
66631
  const rel = path31.join(relative, entry.name);
@@ -66273,7 +66644,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66273
66644
  const sourceRules = path31.join(src, "rules");
66274
66645
  const installedRules = path31.join(dest, "rules");
66275
66646
  try {
66276
- for (const entry of readdirSync7(installedRules, { withFileTypes: true })) {
66647
+ for (const entry of readdirSync8(installedRules, { withFileTypes: true })) {
66277
66648
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
66278
66649
  continue;
66279
66650
  if (existsSync14(path31.join(sourceRules, entry.name)))
@@ -66290,7 +66661,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
66290
66661
  if (existsSync14(legacyRules)) {
66291
66662
  try {
66292
66663
  mkdirSync6(path31.join(res.cursorPluginDir, "rules"), { recursive: true });
66293
- for (const entry of readdirSync7(legacyRules, { withFileTypes: true })) {
66664
+ for (const entry of readdirSync8(legacyRules, { withFileTypes: true })) {
66294
66665
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
66295
66666
  continue;
66296
66667
  const target = path31.join(res.cursorPluginDir, "rules", entry.name);
@@ -70082,7 +70453,7 @@ import {
70082
70453
  } from "node:fs";
70083
70454
  import path35 from "node:path";
70084
70455
  import os12 from "node:os";
70085
- import { createHash as createHash13 } from "node:crypto";
70456
+ import { createHash as createHash14 } from "node:crypto";
70086
70457
  function previewUpgrade(hosts = HOSTS2, deps = {}) {
70087
70458
  const paths = upgradePaths(deps);
70088
70459
  const { home, configDir } = paths;
@@ -70210,7 +70581,7 @@ function applyUpgrade(plan, deps = {}) {
70210
70581
  mkdirSync8(backup, { recursive: true, mode: 448 });
70211
70582
  for (const file of Object.keys(plan.configDigests))
70212
70583
  if (existsSync17(file)) {
70213
- const target = path35.join(backup, createHash13("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
70584
+ const target = path35.join(backup, createHash14("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
70214
70585
  copyFileSync5(file, target);
70215
70586
  chmodSync(target, 384);
70216
70587
  }
@@ -70465,7 +70836,7 @@ var HOSTS2, upgradePaths = (deps) => {
70465
70836
  if (!data || typeof data !== "object" || Array.isArray(data))
70466
70837
  throw new Error(`${file} is not a configuration object`);
70467
70838
  return data;
70468
- }, digest2 = (file) => existsSync17(file) ? createHash13("sha256").update(readFileSync17(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
70839
+ }, digest2 = (file) => existsSync17(file) ? createHash14("sha256").update(readFileSync17(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
70469
70840
  const { home, env } = upgradePaths(deps);
70470
70841
  let step;
70471
70842
  try {
@@ -70951,7 +71322,12 @@ async function run5(argv, io) {
70951
71322
  }
70952
71323
  fixLock = clearStaleMetadataLock(root, { force });
70953
71324
  }
70954
- const report = runDoctor({ host: "cli", cwd: io.cwd, workspaceRoot: root });
71325
+ const report = runDoctor({
71326
+ host: "cli",
71327
+ cwd: io.cwd,
71328
+ workspaceRoot: root,
71329
+ cliVersion: package_default.version
71330
+ });
70955
71331
  let knowledge = null;
70956
71332
  try {
70957
71333
  knowledge = lintKnowledge(root);
@@ -70984,7 +71360,9 @@ async function run5(argv, io) {
70984
71360
  io.stdout(`${mark} ${check.id} — ${check.detail}
70985
71361
  `);
70986
71362
  if (check.fix)
70987
- io.stdout(` fix: ${check.fix}
71363
+ io.stdout(` fix: ${check.fix.replaceAll(`
71364
+ `, `
71365
+ `)}
70988
71366
  `);
70989
71367
  }
70990
71368
  io.stdout(`${knowledgeLine}
@@ -70994,6 +71372,7 @@ async function run5(argv, io) {
70994
71372
  return report.exitCode;
70995
71373
  }
70996
71374
  var init_doctor2 = __esm(() => {
71375
+ init_package();
70997
71376
  init_doctor();
70998
71377
  init_knowledge();
70999
71378
  init_store_lock();
@@ -71107,7 +71486,7 @@ var init_redact = __esm(() => {
71107
71486
 
71108
71487
  // packages/workit-core/src/checks.ts
71109
71488
  import { spawn, spawnSync as spawnSync16 } from "node:child_process";
71110
- import { createHash as createHash14 } from "node:crypto";
71489
+ import { createHash as createHash15 } from "node:crypto";
71111
71490
  import fs18 from "node:fs";
71112
71491
  import os13 from "node:os";
71113
71492
  import path37 from "node:path";
@@ -71441,7 +71820,7 @@ function pruneCheckLogs(storeRoot, options = {}) {
71441
71820
  }
71442
71821
  return report;
71443
71822
  }
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;
71823
+ 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
71824
  var init_checks3 = __esm(() => {
71446
71825
  init_redact();
71447
71826
  MAX_LOG_BYTES = 2 * 1024 * 1024;
@@ -73426,7 +73805,7 @@ var init_pr_ops = __esm(() => {
73426
73805
 
73427
73806
  // packages/workit-core/src/stack.ts
73428
73807
  import { spawnSync as spawnSync19 } from "node:child_process";
73429
- import { createHash as createHash15, randomBytes as randomBytes3 } from "node:crypto";
73808
+ import { createHash as createHash16, randomBytes as randomBytes3 } from "node:crypto";
73430
73809
  import fs20 from "node:fs";
73431
73810
  import path41 from "node:path";
73432
73811
  function worktreeOf(cwd, branch) {
@@ -74593,7 +74972,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
74593
74972
  return dir.ok ? path41.join(dir.data, "worktrees") : null;
74594
74973
  }, firstLine = (run) => (`${run.stderr}
74595
74974
  ${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) => {
74975
+ `).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
74976
  if (name.length === 0 || name.length > 200)
74598
74977
  return false;
74599
74978
  for (let index = 0;index < name.length; index += 1) {
@@ -77522,7 +77901,7 @@ var init_stack2 = __esm(() => {
77522
77901
 
77523
77902
  // packages/workit-core/src/fanout.ts
77524
77903
  import { spawnSync as spawnSync23 } from "node:child_process";
77525
- import { createHash as createHash16 } from "node:crypto";
77904
+ import { createHash as createHash17 } from "node:crypto";
77526
77905
  import fs23 from "node:fs";
77527
77906
  import path45 from "node:path";
77528
77907
  function expandBraces(pattern, limit = 64) {
@@ -78003,7 +78382,7 @@ function sliceHash(slice) {
78003
78382
  context: _context,
78004
78383
  ...definition
78005
78384
  } = slice;
78006
- return createHash16("sha256").update(JSON.stringify(definition)).digest("hex").slice(0, 16);
78385
+ return createHash17("sha256").update(JSON.stringify(definition)).digest("hex").slice(0, 16);
78007
78386
  }
78008
78387
  function fanoutsDir(cwd) {
78009
78388
  const root = storeRoot(cwd);
@@ -78708,13 +79087,13 @@ var init_fanout_worktree = __esm(() => {
78708
79087
  });
78709
79088
 
78710
79089
  // packages/workit-core/src/fanout-brief.ts
78711
- import { createHash as createHash17 } from "node:crypto";
79090
+ import { createHash as createHash18 } from "node:crypto";
78712
79091
  function workerSession(lead, slice, attempt) {
78713
79092
  const tail = attempt === null ? "" : `+try${attempt}`;
78714
79093
  const full = `${lead}-w-${slice}${tail}`;
78715
79094
  if (full.length <= SESSION_MAX)
78716
79095
  return full;
78717
- const digest = createHash17("sha256").update(slice).digest("hex").slice(0, 12);
79096
+ const digest = createHash18("sha256").update(slice).digest("hex").slice(0, 12);
78718
79097
  return `${lead.slice(0, SESSION_MAX - 3 - digest.length - tail.length)}-w-${digest}${tail}`;
78719
79098
  }
78720
79099
  function renderBrief(cwd, plan, input) {
@@ -95900,7 +96279,7 @@ var init_rules = __esm(() => {
95900
96279
 
95901
96280
  // packages/workit-cli/src/test-audit/audit.ts
95902
96281
  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";
96282
+ import { existsSync as existsSync19, readdirSync as readdirSync9, readFileSync as readFileSync22, statSync as statSync7 } from "node:fs";
95904
96283
  import path49 from "node:path";
95905
96284
  function git7(cwd, args) {
95906
96285
  const run = spawnSync25("git", ["-c", "core.quotepath=off", ...args], {
@@ -95911,7 +96290,7 @@ function git7(cwd, args) {
95911
96290
  return { ok: run.status === 0, stdout: run.stdout ?? "" };
95912
96291
  }
95913
96292
  function walkTests(dir, out) {
95914
- for (const entry of readdirSync8(dir, { withFileTypes: true })) {
96293
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
95915
96294
  if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
95916
96295
  continue;
95917
96296
  const full = path49.join(dir, entry.name);
@@ -96047,7 +96426,7 @@ import {
96047
96426
  lstatSync as lstatSync7,
96048
96427
  mkdirSync as mkdirSync9,
96049
96428
  mkdtempSync as mkdtempSync4,
96050
- readdirSync as readdirSync9,
96429
+ readdirSync as readdirSync10,
96051
96430
  readFileSync as readFileSync23,
96052
96431
  readlinkSync as readlinkSync3,
96053
96432
  realpathSync as realpathSync9,
@@ -96144,7 +96523,7 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
96144
96523
  let removed = 0;
96145
96524
  let names = [];
96146
96525
  try {
96147
- names = readdirSync9(tmp);
96526
+ names = readdirSync10(tmp);
96148
96527
  } catch {
96149
96528
  return 0;
96150
96529
  }
@@ -96173,7 +96552,7 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
96173
96552
  }
96174
96553
  function linkNodeModules(origin, target, repo, copy) {
96175
96554
  mkdirSync9(target, { recursive: true });
96176
- for (const entry of readdirSync9(origin, { withFileTypes: true })) {
96555
+ for (const entry of readdirSync10(origin, { withFileTypes: true })) {
96177
96556
  const from = path50.join(origin, entry.name);
96178
96557
  const to = path50.join(target, entry.name);
96179
96558
  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.7.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.7.0",
42
+ "@brainervirus/workit-cursor": "^8.7.0",
43
+ "@brainervirus/workit-opencode": "^8.7.0",
44
44
  "@inkjs/ui": "2.0.0",
45
45
  "ink": "7.1.1",
46
46
  "react": "19.2.8"