@brainervirus/workit-cursor 0.8.6 → 0.8.8

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": "OpenCode-style verify, PR, changelog, commit, and session handoff for Cursor",
5
- "version": "0.8.6",
5
+ "version": "0.8.8",
6
6
  "author": {
7
7
  "name": "Cristhofer Pincetti"
8
8
  },
package/README.md CHANGED
@@ -23,7 +23,8 @@ npx @brainervirus/workit-cli init
23
23
  "command": "npx",
24
24
  "args": [
25
25
  "-y",
26
- "--package=@brainervirus/workit-cursor@0.8.5",
26
+ "--prefer-online",
27
+ "--package=@brainervirus/workit-cursor@latest",
27
28
  "workit-cursor-mcp",
28
29
  "${workspaceFolder}"
29
30
  ]
@@ -35,7 +36,7 @@ npx @brainervirus/workit-cli init
35
36
  ### Requirements
36
37
 
37
38
  - **Node.js ≥ 22** — the MCP server and session-start hook are self-contained Node bundles invoked through `npx`.
38
- - **Network** — `npx -y …@0.8.5` resolves and downloads the package on first run in each environment; a machine that cannot reach the npm registry cannot start the MCP server or hook (see [Runtime](#runtime)).
39
+ - **Network** — `npx -y --prefer-online …@latest` resolves and downloads the package on first run in each environment; a machine that cannot reach the npm registry cannot start the MCP server or hook (see [Runtime](#runtime)).
39
40
 
40
41
  ## What it provides
41
42
 
@@ -59,11 +60,11 @@ Cursor adapts workit through policy-only confirmation: approvals and lifecycle t
59
60
 
60
61
  Cursor launches the MCP server and session-start hook through `npx`, so the shipped manifests contain no repository-relative `dist` paths:
61
62
 
62
- - **MCP server** — `npx -y --package=@brainervirus/workit-cursor@0.8.5 workit-cursor-mcp ${workspaceFolder}`. It speaks the MCP stdio protocol; `stdout` is reserved for protocol messages and diagnostics go to `stderr`.
63
- - **Session-start hook** — `npx -y --package=@brainervirus/workit-cursor@0.8.5 workit-cursor-session-start`. It emits valid hook output and a diagnostic on runtime failure, and remains fail-open where Cursor's hook contract requires startup continuity.
63
+ - **MCP server** — `npx -y --prefer-online --package=@brainervirus/workit-cursor@latest workit-cursor-mcp ${workspaceFolder}`. It speaks the MCP stdio protocol; `stdout` is reserved for protocol messages and diagnostics go to `stderr`.
64
+ - **Session-start hook** — `npx -y --prefer-online --package=@brainervirus/workit-cursor@latest workit-cursor-session-start`. It emits valid hook output and a diagnostic on runtime failure, and remains fail-open where Cursor's hook contract requires startup continuity.
64
65
  - `npx` startup or network failure is surfaced by Cursor as an MCP/hook startup failure; Workit never silently substitutes stale local runtime code.
65
66
 
66
- The runtime runs from the exact reviewed pin `@0.8.5`: Cursor reviews plugin metadata from Git, while npm serves the pinned runtime. Bumping the pin is a deliberate reviewed update, made only after the target npm version is public — never a mutable `latest` dist-tag. See [Update review](#update-review).
67
+ The runtime runs from `@latest` with the mandatory `--prefer-online` flag: Cursor reviews plugin metadata from Git, while npm serves the runtime. `--prefer-online` forces npx to check the registry so a stale cached `latest` resolution is never reused. See [Update review](#update-review).
67
68
 
68
69
  ## Security and data handling
69
70
 
@@ -96,9 +97,17 @@ The repository root carries `.cursor-plugin/marketplace.json`, indexing `package
96
97
 
97
98
  - **Installing from Marketplace** — a Marketplace admin adds the repository URL through Cursor's authenticated publisher flow; end users then install the plugin from the Cursor Marketplace UI, which reads `.cursor-plugin/plugin.json` and the tracked components directly from Git.
98
99
  - **Submission** — Marketplace submission is a separate, later authenticated action at `https://cursor.com/marketplace/publish`. It is **not** performed here and no publication or acceptance is claimed; the repository is kept validated and submission-ready.
99
- - **Update review** — Git plugin metadata (manifest, rules, skills, assets) is reviewed by Cursor on Marketplace updates, while the npm runtime is pinned to the exact reviewed `@0.8.5`. Bumping that pin in `mcp.json` / `hooks-cursor.json` is a deliberate reviewed change: only after the target npm version is public, never a mutable `latest` dist-tag.
100
+ - **Update review** — Git plugin metadata (manifest, rules, skills, assets) is reviewed by Cursor on Marketplace updates, while the npm runtime runs from `@latest` with `--prefer-online`. The selector is shared across `mcp.json`, `hooks-cursor.json`, and `run-cursor-mcp.sh`, and a stale `latest` resolution is prevented by the mandatory `--prefer-online` flag.
100
101
  - **Troubleshooting** — `workit doctor` (or the `workflow_doctor` tool) reports installation health including runtime, token, VCS/YouTrack, and log-writability checks; it exits nonzero on failure. An MCP/hook startup failure with no network is an `npx`/registry reachability issue, not a Workit defect.
101
102
 
103
+ ## Auto-load repair
104
+
105
+ Workit self-heals stale Cursor plugin installs so the workflow features never silently drop out of auto-load (CA-01/CA-04):
106
+
107
+ - **Detection** — the doctor's `stale_install` finding reads the installed `~/.cursor/plugins/local/workit` directory and fails on a legacy `--package=` pin in the plugin's own `mcp.json`, a sessionStart hook running a legacy selector, or a local-dist install behind the current/published runtime; each finding carries the exact repair step.
108
+ - **Self-heal** — `install-cursor-plugin.sh` runs `doctor-check.ts cursor --stale` as a pre-check: exit 2 (stale) triggers a refresh of the plugin directory plus a rewrite of the workit MCP/hook entries to the canonical `@latest` + `--prefer-online` selector, preserving unrelated MCP servers; a healthy install is byte-untouched.
109
+ - **Fail-open** — the one network probe (the npm registry version comparison for local-dist installs) never blocks an install: an unreachable registry warns as `registry_unreachable`, never `stale_install` and never a failure. Canonical `@latest` installs skip the probe entirely — the selector resolves fresh at launch, so the installed `package.json` version is metadata, not a freshness signal.
110
+
102
111
  ## Docs
103
112
 
104
113
  Full usage: https://github.com/BrainerVirus/workit#readme
@@ -21835,6 +21835,25 @@ var init_support_matrix = __esm(() => {
21835
21835
  };
21836
21836
  });
21837
21837
 
21838
+ // packages/workit-core/src/core/package-root.ts
21839
+ import { existsSync as existsSync4 } from "node:fs";
21840
+ import path8 from "node:path";
21841
+ import { fileURLToPath } from "node:url";
21842
+ var packageRoot = () => {
21843
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
21844
+ while (true) {
21845
+ const parent = path8.dirname(dir);
21846
+ if (existsSync4(path8.join(dir, "package.json")) || parent === dir)
21847
+ return dir;
21848
+ dir = parent;
21849
+ }
21850
+ }, assetRoot = () => {
21851
+ const root = packageRoot();
21852
+ const assets = path8.join(root, "assets");
21853
+ return existsSync4(assets) ? assets : root;
21854
+ };
21855
+ var init_package_root = () => {};
21856
+
21838
21857
  // packages/workit-core/src/core/registration.ts
21839
21858
  function isWorkitPlugin(value) {
21840
21859
  const s = String(value).replaceAll("\\", "/");
@@ -21842,28 +21861,40 @@ function isWorkitPlugin(value) {
21842
21861
  const pkgPath = s.includes("/packages/workit-opencode/") || s.includes("/packages/workit-cursor/") || s.includes("/node_modules/@brainervirus/workit-opencode/") || s.includes("/node_modules/@brainervirus/workit-cursor/");
21843
21862
  return named(s, "workflow-toolkit") || named(s, "workflow-toolkit-opencode") || named(s, "local/workflow-toolkit") || named(s, "workit") || named(s, "local/workit") || named(s, "@brainervirus/workit-opencode") || named(s, "@brainervirus/workit-cursor") || url && pkgPath || s.startsWith("git+file://") && s.includes("workflow-toolkit");
21844
21863
  }
21864
+ function cursorMcpServerEntry(_packageDir) {
21865
+ return {
21866
+ command: "npx",
21867
+ args: [
21868
+ "-y",
21869
+ "--prefer-online",
21870
+ `--package=${CURSOR_RUNTIME_PACKAGE}`,
21871
+ "workit-cursor-mcp",
21872
+ "${workspaceFolder}"
21873
+ ]
21874
+ };
21875
+ }
21845
21876
  function cursorHooksEntry(_packageDir) {
21846
21877
  return {
21847
- command: `npx -y --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-session-start`,
21878
+ command: `npx -y --prefer-online --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-session-start`,
21848
21879
  args: []
21849
21880
  };
21850
21881
  }
21851
- var named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@0.8.5";
21882
+ var named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest";
21852
21883
  var init_registration = () => {};
21853
21884
 
21854
21885
  // packages/workit-core/src/core/skill-manifests.ts
21855
- import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
21856
- import path8 from "node:path";
21857
- var CANONICAL_SKILLS, skillManifestNames = (root) => existsSync4(root) ? readdirSync4(root).filter((name) => existsSync4(path8.join(root, name, "SKILL.md"))).sort() : [], validateSkillManifests = (root, expected, label) => {
21886
+ import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
21887
+ import path9 from "node:path";
21888
+ var CANONICAL_SKILLS, skillManifestNames = (root) => existsSync5(root) ? readdirSync4(root).filter((name) => existsSync5(path9.join(root, name, "SKILL.md"))).sort() : [], validateSkillManifests = (root, expected, label) => {
21858
21889
  const actual = skillManifestNames(root);
21859
21890
  const missing = expected.filter((name) => !actual.includes(name));
21860
21891
  const extra = actual.filter((name) => !expected.includes(name));
21861
21892
  return missing.length === 0 && extra.length === 0 ? null : `${label} mismatch at ${root} (missing: ${missing.join(", ") || "none"}; extra: ${extra.join(", ") || "none"})`;
21862
21893
  }, validateCursorSkills = (pluginDir) => {
21863
- const workit = validateSkillManifests(path8.join(pluginDir, "skills"), CANONICAL_SKILLS.workit, "Cursor Workit skills");
21894
+ const workit = validateSkillManifests(path9.join(pluginDir, "skills"), CANONICAL_SKILLS.workit, "Cursor Workit skills");
21864
21895
  if (workit)
21865
21896
  return workit;
21866
- const vendor = path8.join(pluginDir, "vendor/superpowers/skills");
21897
+ const vendor = path9.join(pluginDir, "vendor/superpowers/skills");
21867
21898
  const superpowers = validateSkillManifests(vendor, CANONICAL_SKILLS.superpowers, "Cursor Superpowers skills");
21868
21899
  if (superpowers)
21869
21900
  return superpowers;
@@ -21871,7 +21902,7 @@ var CANONICAL_SKILLS, skillManifestNames = (root) => existsSync4(root) ? readdir
21871
21902
  while (pending.length > 0) {
21872
21903
  const dir = pending.pop();
21873
21904
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
21874
- const file = path8.join(dir, entry.name);
21905
+ const file = path9.join(dir, entry.name);
21875
21906
  if (entry.isDirectory())
21876
21907
  pending.push(file);
21877
21908
  else if ((statSync2(file).mode & 73) !== 0 || readFileSync6(file).subarray(0, 2).toString("latin1") === "#!") {
@@ -21919,15 +21950,15 @@ var init_skill_manifests = __esm(() => {
21919
21950
 
21920
21951
  // packages/workit-core/src/core/doctor.ts
21921
21952
  import { spawnSync as spawnSync4 } from "node:child_process";
21922
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
21953
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
21923
21954
  import os3 from "node:os";
21924
- import path9 from "node:path";
21955
+ import path10 from "node:path";
21925
21956
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21926
- let dir = path9.resolve(cwd);
21957
+ let dir = path10.resolve(cwd);
21927
21958
  while (true) {
21928
- if (existsSync5(path9.join(dir, "packages", "workit-core", "package.json")))
21959
+ if (existsSync6(path10.join(dir, "packages", "workit-core", "package.json")))
21929
21960
  return dir;
21930
- const parent = path9.dirname(dir);
21961
+ const parent = path10.dirname(dir);
21931
21962
  if (parent === dir)
21932
21963
  return null;
21933
21964
  dir = parent;
@@ -21935,8 +21966,8 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21935
21966
  }, resolve = (options) => {
21936
21967
  const env = options.env ?? process.env;
21937
21968
  const home = options.home ?? env.HOME ?? os3.homedir();
21938
- const configDir2 = options.configDir ?? env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path9.join(home, ".config", "workit");
21939
- const stateDir = options.stateDir ?? env.WORKFLOW_TOOLKIT_STATE ?? path9.join(home, ".local", "state", "workit");
21969
+ const configDir2 = options.configDir ?? env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path10.join(home, ".config", "workit");
21970
+ const stateDir = options.stateDir ?? env.WORKFLOW_TOOLKIT_STATE ?? path10.join(home, ".local", "state", "workit");
21940
21971
  const cwd = options.cwd ?? process.cwd();
21941
21972
  const dev = options.dev ?? env.WORKFLOW_TOOLKIT_DEV ?? findDevFromCwd(cwd);
21942
21973
  return {
@@ -21946,10 +21977,10 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21946
21977
  stateDir,
21947
21978
  cwd,
21948
21979
  dev,
21949
- opencodeConfig: options.opencodeConfig ?? path9.join(home, ".config", "opencode", "opencode.json"),
21950
- cursorSettings: options.cursorSettings ?? path9.join(home, ".cursor", "settings.json"),
21951
- cursorMcp: options.cursorMcp ?? path9.join(home, ".cursor", "mcp.json"),
21952
- cursorPluginDir: options.cursorPluginDir ?? path9.join(home, ".cursor", "plugins", "local", "workit"),
21980
+ opencodeConfig: options.opencodeConfig ?? path10.join(home, ".config", "opencode", "opencode.json"),
21981
+ cursorSettings: options.cursorSettings ?? path10.join(home, ".cursor", "settings.json"),
21982
+ cursorMcp: options.cursorMcp ?? path10.join(home, ".cursor", "mcp.json"),
21983
+ cursorPluginDir: options.cursorPluginDir ?? path10.join(home, ".cursor", "plugins", "local", "workit"),
21953
21984
  env,
21954
21985
  installer: options.installer ?? false
21955
21986
  };
@@ -21971,13 +22002,13 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21971
22002
  const list = Array.isArray(plugin) ? plugin : typeof plugin === "string" ? [plugin] : [];
21972
22003
  return list.map(String).filter(isWorkitPlugin);
21973
22004
  }, commandOnPath2 = (name, env) => {
21974
- const dirs = (env.PATH ?? process.env.PATH ?? "").split(path9.delimiter);
22005
+ const dirs = (env.PATH ?? process.env.PATH ?? "").split(path10.delimiter);
21975
22006
  const names = process.platform === "win32" ? [name, `${name}.exe`] : [name];
21976
22007
  for (const dir of dirs) {
21977
22008
  if (!dir)
21978
22009
  continue;
21979
22010
  for (const candidateName of names) {
21980
- const candidate = path9.join(dir, candidateName);
22011
+ const candidate = path10.join(dir, candidateName);
21981
22012
  try {
21982
22013
  const st = statSync3(candidate);
21983
22014
  if (process.platform !== "win32" && (st.mode & 73) === 0)
@@ -22003,14 +22034,14 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22003
22034
  }
22004
22035
  return true;
22005
22036
  }, resolveBun = (env) => {
22006
- if (env.BUN && existsSync5(env.BUN))
22037
+ if (env.BUN && existsSync6(env.BUN))
22007
22038
  return env.BUN;
22008
22039
  for (const candidate of [
22009
- path9.join(os3.homedir(), ".bun/bin/bun"),
22040
+ path10.join(os3.homedir(), ".bun/bin/bun"),
22010
22041
  "/usr/local/bin/bun",
22011
22042
  "/usr/bin/bun"
22012
22043
  ]) {
22013
- if (existsSync5(candidate))
22044
+ if (existsSync6(candidate))
22014
22045
  return candidate;
22015
22046
  }
22016
22047
  return commandOnPath2("bun", env) ? "bun" : null;
@@ -22058,7 +22089,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22058
22089
  detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping version parity"
22059
22090
  };
22060
22091
  }
22061
- const corePkg = readJson(path9.join(res.dev, "packages/workit-core/package.json"));
22092
+ const corePkg = readJson(path10.join(res.dev, "packages/workit-core/package.json"));
22062
22093
  if (!corePkg) {
22063
22094
  return {
22064
22095
  id: "versions",
@@ -22068,7 +22099,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22068
22099
  }
22069
22100
  const refs = new Set;
22070
22101
  for (const name of ["workit-opencode", "workit-cursor", "workit-cli"]) {
22071
- const pkg = readJson(path9.join(res.dev, "packages", name, "package.json"));
22102
+ const pkg = readJson(path10.join(res.dev, "packages", name, "package.json"));
22072
22103
  const dep = pkg?.dependencies?.["@brainervirus/workit-core"];
22073
22104
  if (typeof dep === "string")
22074
22105
  refs.add(dep);
@@ -22084,7 +22115,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22084
22115
  if (refs.size > 1) {
22085
22116
  problems.push(`adapters pin different core versions: ${[...refs].join(", ")}`);
22086
22117
  }
22087
- const opencodePkg = readJson(path9.join(res.dev, "packages/workit-opencode/package.json"));
22118
+ const opencodePkg = readJson(path10.join(res.dev, "packages/workit-opencode/package.json"));
22088
22119
  const sdk = opencodePkg?.dependencies?.["@opencode-ai/plugin"];
22089
22120
  const sdkVersion = typeof sdk === "string" ? (sdk.match(/^\d+(?:\.\d+){0,2}/) ?? [])[0] : null;
22090
22121
  if (sdkVersion && !semverAtLeast(sdkVersion, SUPPORT_MATRIX.opencode.minimum)) {
@@ -22104,27 +22135,27 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22104
22135
  fix: "Align every adapter to the same @brainervirus/workit-core version (rewrite-workspace-deps.ts) or reinstall"
22105
22136
  };
22106
22137
  }, assetPathsFor = (host, dev) => {
22107
- const pkg = path9.join(dev, "packages", `workit-${host}`);
22138
+ const pkg = path10.join(dev, "packages", `workit-${host}`);
22108
22139
  switch (host) {
22109
22140
  case "opencode":
22110
22141
  return [
22111
- path9.join(pkg, "assets", "commands", "wk-init.md"),
22112
- path9.join(pkg, "assets", "skills", "wk-init", "SKILL.md"),
22113
- path9.join(pkg, "assets", "templates", "spec-template.md"),
22114
- path9.join(pkg, "assets", "vendor", "superpowers", "skills", "brainstorming", "SKILL.md")
22142
+ path10.join(pkg, "assets", "commands", "wk-init.md"),
22143
+ path10.join(pkg, "assets", "skills", "wk-init", "SKILL.md"),
22144
+ path10.join(pkg, "assets", "templates", "spec-template.md"),
22145
+ path10.join(pkg, "assets", "vendor", "superpowers", "skills", "brainstorming", "SKILL.md")
22115
22146
  ];
22116
22147
  case "cursor":
22117
22148
  return [
22118
- path9.join(pkg, "assets", "templates", "spec-template.md"),
22119
- path9.join(pkg, "mcp.json"),
22120
- path9.join(pkg, ".cursor-plugin")
22149
+ path10.join(pkg, "assets", "templates", "spec-template.md"),
22150
+ path10.join(pkg, "mcp.json"),
22151
+ path10.join(pkg, ".cursor-plugin")
22121
22152
  ];
22122
22153
  case "cli":
22123
- return [path9.join(pkg, "assets", "templates", "spec-template.md")];
22154
+ return [path10.join(pkg, "assets", "templates", "spec-template.md")];
22124
22155
  }
22125
22156
  }, hostsFor = (host) => host === "cli" ? ["opencode", "cursor", "cli"] : [host], checkAssets = (res) => {
22126
22157
  const dev = res.dev;
22127
- const missing = dev ? hostsFor(res.host).flatMap((h) => assetPathsFor(h, dev).filter((p) => !existsSync5(p)).map((p) => `${h}: ${p}`)) : [];
22158
+ const missing = dev ? hostsFor(res.host).flatMap((h) => assetPathsFor(h, dev).filter((p) => !existsSync6(p)).map((p) => `${h}: ${p}`)) : [];
22128
22159
  if (res.host === "cursor" || res.host === "cli") {
22129
22160
  const cursorError = validateCursorSkills(res.cursorPluginDir);
22130
22161
  if (cursorError)
@@ -22151,17 +22182,17 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22151
22182
  fix: "Reinstall or rebuild the workit package (missing assets under packages/workit-<host>)"
22152
22183
  };
22153
22184
  }, launcherSlotsFor = (host, dev) => {
22154
- const pkg = path9.join(dev, "packages", `workit-${host}`);
22185
+ const pkg = path10.join(dev, "packages", `workit-${host}`);
22155
22186
  switch (host) {
22156
22187
  case "opencode":
22157
- return [[path9.join(pkg, "src", "plugin.ts"), path9.join(pkg, "dist", "plugin.js")]];
22188
+ return [[path10.join(pkg, "src", "plugin.ts"), path10.join(pkg, "dist", "plugin.js")]];
22158
22189
  case "cursor":
22159
22190
  return [
22160
- [path9.join(pkg, "dist", "mcp-server.js")],
22161
- [path9.join(pkg, "dist", "cursor-session-start.js")]
22191
+ [path10.join(pkg, "dist", "mcp-server.js")],
22192
+ [path10.join(pkg, "dist", "cursor-session-start.js")]
22162
22193
  ];
22163
22194
  case "cli":
22164
- return [[path9.join(pkg, "src", "index.tsx"), path9.join(pkg, "dist", "index.js")]];
22195
+ return [[path10.join(pkg, "src", "index.tsx"), path10.join(pkg, "dist", "index.js")]];
22165
22196
  }
22166
22197
  }, validNodeEntry = (entry, runtime, env) => {
22167
22198
  try {
@@ -22175,7 +22206,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22175
22206
  return false;
22176
22207
  }
22177
22208
  }, registeredCursorLauncher = (res) => {
22178
- if (!existsSync5(res.cursorMcp))
22209
+ if (!existsSync6(res.cursorMcp))
22179
22210
  return "invalid";
22180
22211
  const config2 = readJson(res.cursorMcp);
22181
22212
  if (!config2)
@@ -22188,13 +22219,10 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22188
22219
  if (typeof command !== "string" || !Array.isArray(args) || typeof args[0] !== "string") {
22189
22220
  return "invalid";
22190
22221
  }
22191
- const executable = path9.basename(command).toLowerCase();
22222
+ const executable = path10.basename(command).toLowerCase();
22223
+ const canonical = cursorMcpServerEntry("").args;
22192
22224
  if (executable === "npx" || executable === "npx.exe" || executable === "npx.cmd") {
22193
- if (args[0] !== "-y")
22194
- return "invalid";
22195
- if (args[1] !== `--package=${CURSOR_RUNTIME_PACKAGE}`)
22196
- return "invalid";
22197
- if (args[2] !== "workit-cursor-mcp")
22225
+ if (args.length !== canonical.length || args.some((a, i) => a !== canonical[i]))
22198
22226
  return "invalid";
22199
22227
  return { kind: "npx" };
22200
22228
  }
@@ -22203,11 +22231,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22203
22231
  return {
22204
22232
  kind: "node",
22205
22233
  runtime: command,
22206
- entry: path9.isAbsolute(args[0]) ? args[0] : path9.resolve(path9.dirname(res.cursorMcp), args[0])
22234
+ entry: path10.isAbsolute(args[0]) ? args[0] : path10.resolve(path10.dirname(res.cursorMcp), args[0])
22207
22235
  };
22208
22236
  }, canonicalCursorHook, registeredCursorHook = (res) => {
22209
- const hooksFile = path9.join(res.cursorPluginDir, "hooks", "hooks-cursor.json");
22210
- if (!existsSync5(hooksFile))
22237
+ const hooksFile = path10.join(res.cursorPluginDir, "hooks", "hooks-cursor.json");
22238
+ if (!existsSync6(hooksFile))
22211
22239
  return "invalid";
22212
22240
  const config2 = readJson(hooksFile);
22213
22241
  if (!config2)
@@ -22224,9 +22252,9 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22224
22252
  if (!m)
22225
22253
  return false;
22226
22254
  const entry = m[1].trim();
22227
- if (!path9.isAbsolute(entry))
22255
+ if (!path10.isAbsolute(entry))
22228
22256
  return false;
22229
- if (path9.resolve(entry) !== path9.resolve(path9.join(res.cursorPluginDir, "dist", "cursor-session-start.js"))) {
22257
+ if (path10.resolve(entry) !== path10.resolve(path10.join(res.cursorPluginDir, "dist", "cursor-session-start.js"))) {
22230
22258
  return false;
22231
22259
  }
22232
22260
  return validNodeEntry(entry, "node", res.env);
@@ -22235,7 +22263,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22235
22263
  const hosts = hostsFor(res.host);
22236
22264
  const registered = hosts.includes("cursor") ? registeredCursorLauncher(res) : null;
22237
22265
  const runtime = registered && registered !== "invalid" && registered.kind === "node" ? registered.runtime : "node";
22238
- const missing = hosts.includes("cursor") ? ["dist/mcp-server.js", "dist/cursor-session-start.js"].map((rel) => path9.join(res.cursorPluginDir, rel)).filter((p) => !validNodeEntry(p, runtime, res.env)).map((p) => `cursor: ${p}`) : [];
22266
+ const missing = hosts.includes("cursor") ? ["dist/mcp-server.js", "dist/cursor-session-start.js"].map((rel) => path10.join(res.cursorPluginDir, rel)).filter((p) => !validNodeEntry(p, runtime, res.env)).map((p) => `cursor: ${p}`) : [];
22239
22267
  if (hosts.includes("cursor")) {
22240
22268
  if (registered === "invalid") {
22241
22269
  missing.push(`cursor: canonical workit MCP launcher in ${res.cursorMcp}`);
@@ -22244,13 +22272,13 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22244
22272
  }
22245
22273
  const hook = registeredCursorHook(res);
22246
22274
  if (hook === "invalid") {
22247
- missing.push(`cursor: canonical session-start hook in ${path9.join(res.cursorPluginDir, "hooks", "hooks-cursor.json")}`);
22275
+ missing.push(`cursor: canonical session-start hook in ${path10.join(res.cursorPluginDir, "hooks", "hooks-cursor.json")}`);
22248
22276
  } else if (hook !== null && hook !== canonicalCursorHook && !validLocalDistHook(hook, res)) {
22249
- missing.push(`cursor: canonical session-start hook in ${path9.join(res.cursorPluginDir, "hooks", "hooks-cursor.json")} (registered ${hook})`);
22277
+ missing.push(`cursor: canonical session-start hook in ${path10.join(res.cursorPluginDir, "hooks", "hooks-cursor.json")} (registered ${hook})`);
22250
22278
  }
22251
22279
  }
22252
22280
  if (dev) {
22253
- missing.push(...hosts.filter((h) => h !== "cursor").flatMap((h) => launcherSlotsFor(h, dev).filter((slot) => !slot.some((p) => existsSync5(p))).map((slot) => `${h}: ${slot.join(" or ")}`)));
22281
+ missing.push(...hosts.filter((h) => h !== "cursor").flatMap((h) => launcherSlotsFor(h, dev).filter((slot) => !slot.some((p) => existsSync6(p))).map((slot) => `${h}: ${slot.join(" or ")}`)));
22254
22282
  }
22255
22283
  if (missing.length > 0) {
22256
22284
  return {
@@ -22267,7 +22295,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22267
22295
  detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping non-Cursor launcher checks"
22268
22296
  };
22269
22297
  }
22270
- return { id: "launcher", status: "pass", detail: `${res.host} launcher/hook entries present` };
22298
+ return {
22299
+ id: "launcher",
22300
+ status: "pass",
22301
+ detail: `${res.host} launcher/hook entries present`
22302
+ };
22271
22303
  }, checkUtility = (res) => {
22272
22304
  const git = commandOnPath2("git", res.env);
22273
22305
  if (!git) {
@@ -22286,7 +22318,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22286
22318
  fix: "Install util-linux (flock)"
22287
22319
  };
22288
22320
  }
22289
- return { id: "utility", status: "pass", detail: "git (and flock where required) on PATH" };
22321
+ return {
22322
+ id: "utility",
22323
+ status: "pass",
22324
+ detail: "git (and flock where required) on PATH"
22325
+ };
22290
22326
  }, staleEntry = (entry) => {
22291
22327
  if (!entry)
22292
22328
  return "stale";
@@ -22294,7 +22330,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22294
22330
  return "stale";
22295
22331
  if (entry.startsWith("file:")) {
22296
22332
  const target = entry.replace(/^file:\/\//, "").replace(/^file:/, "");
22297
- return existsSync5(target) ? "ok" : "missing-file";
22333
+ return existsSync6(target) ? "ok" : "missing-file";
22298
22334
  }
22299
22335
  return "ok";
22300
22336
  }, checkStalePin = (res) => {
@@ -22305,8 +22341,12 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22305
22341
  detail: "opencode pin not inspected on the cursor host"
22306
22342
  };
22307
22343
  }
22308
- if (!existsSync5(res.opencodeConfig)) {
22309
- return { id: "stale_pin", status: "pass", detail: "no opencode config — not registered" };
22344
+ if (!existsSync6(res.opencodeConfig)) {
22345
+ return {
22346
+ id: "stale_pin",
22347
+ status: "pass",
22348
+ detail: "no opencode config — not registered"
22349
+ };
22310
22350
  }
22311
22351
  const cfg = readJson(res.opencodeConfig);
22312
22352
  const entries = pluginEntries(cfg);
@@ -22336,17 +22376,125 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22336
22376
  };
22337
22377
  }
22338
22378
  return { id: "stale_pin", status: "pass", detail: "opencode pin resolves" };
22379
+ }, pluginMcpSelectors = (res) => {
22380
+ const p = path10.join(res.cursorPluginDir, "mcp.json");
22381
+ if (!existsSync6(p))
22382
+ return null;
22383
+ const cfg = readJson(p);
22384
+ const server = cfg?.mcpServers?.workit;
22385
+ if (!server || typeof server !== "object" || Array.isArray(server))
22386
+ return null;
22387
+ const args = server.args;
22388
+ if (!Array.isArray(args))
22389
+ return null;
22390
+ return args.map(String).filter((a) => a.startsWith("--package="));
22391
+ }, registryLatestVersion = (res) => {
22392
+ const seam = res.env.WORKIT_DOCTOR_STALE_REGISTRY_VERSION;
22393
+ if (typeof seam === "string" && seam) {
22394
+ return /^\d+(?:\.\d+){1,2}$/.test(seam) ? seam : null;
22395
+ }
22396
+ const cmd = res.env.WORKIT_DOCTOR_STALE_REGISTRY_CMD ?? (commandOnPath2("npm", res.env) ? "npm" : null);
22397
+ if (!cmd)
22398
+ return null;
22399
+ try {
22400
+ const r = spawnSync4(cmd, ["view", CURSOR_RUNTIME_PACKAGE, "version"], {
22401
+ encoding: "utf8",
22402
+ timeout: 20000,
22403
+ env: res.env
22404
+ });
22405
+ const v = (r.stdout ?? "").trim().split(/\s+/)[0];
22406
+ return r.status === 0 && /^\d+(?:\.\d+){1,2}$/.test(v) ? v : null;
22407
+ } catch {
22408
+ return null;
22409
+ }
22410
+ }, installedPluginVersion = (res) => {
22411
+ const pkg = readJson(path10.join(res.cursorPluginDir, "package.json"));
22412
+ return typeof pkg?.version === "string" && pkg.version ? pkg.version : null;
22413
+ }, checkStaleInstall = (res) => {
22414
+ if (res.host !== "cursor" && res.host !== "cli") {
22415
+ return {
22416
+ id: "stale_install",
22417
+ status: "pass",
22418
+ detail: "cursor plugin not inspected on the opencode host"
22419
+ };
22420
+ }
22421
+ const canonicalMcp = cursorMcpServerEntry("").args.find((a) => a.startsWith("--package="));
22422
+ const mcpSelectors = pluginMcpSelectors(res);
22423
+ const legacyPin = (mcpSelectors ?? []).find((s) => s !== canonicalMcp && s.includes("@brainervirus/workit-cursor@"));
22424
+ if (legacyPin) {
22425
+ return {
22426
+ id: "stale_install",
22427
+ status: "fail",
22428
+ detail: `stale_install: plugin mcp.json pins a legacy selector ${legacyPin} (canonical: ${canonicalMcp})`,
22429
+ fix: "Re-run install-cursor-plugin.sh — it rewrites the workit MCP entry to the canonical @latest selector"
22430
+ };
22431
+ }
22432
+ const hooksFile = path10.join(res.cursorPluginDir, "hooks", "hooks-cursor.json");
22433
+ const hookCmd = readJson(hooksFile)?.hooks?.sessionStart?.[0]?.command ?? null;
22434
+ const canonicalHook = canonicalCursorHook;
22435
+ const staleHook = hookCmd !== null && hookCmd !== canonicalHook && !hookCmd.startsWith("node ") && hookCmd.includes("@brainervirus/workit-cursor@");
22436
+ if (staleHook) {
22437
+ return {
22438
+ id: "stale_install",
22439
+ status: "fail",
22440
+ detail: `stale_install: sessionStart hook runs a legacy selector (canonical: ${canonicalHook})`,
22441
+ fix: "Re-run install-cursor-plugin.sh — it rewrites the sessionStart hook to the canonical @latest selector"
22442
+ };
22443
+ }
22444
+ const installed = installedPluginVersion(res);
22445
+ const source = res.dev ? readJson(path10.join(res.dev, "packages/workit-cursor/package.json"))?.version ?? null : readJson(path10.join(packageRoot(), "package.json"))?.version ?? null;
22446
+ const localDist = mcpSelectors === null && hookCmd !== null && hookCmd.startsWith("node ");
22447
+ if (localDist && installed !== null && source !== null && !semverAtLeast(installed, source)) {
22448
+ return {
22449
+ id: "stale_install",
22450
+ status: "fail",
22451
+ detail: `stale_install: installed workit-cursor ${installed} is behind the current runtime ${source}`,
22452
+ fix: "Re-run install-cursor-plugin.sh — it refreshes the plugin directory and rewrites the workit MCP/hook entries"
22453
+ };
22454
+ }
22455
+ if (installed !== null && localDist) {
22456
+ const expected = registryLatestVersion(res);
22457
+ if (expected === null) {
22458
+ return {
22459
+ id: "registry_unreachable",
22460
+ status: "warn",
22461
+ registryProbed: true,
22462
+ detail: "registry_unreachable: cannot compare installed workit-cursor against the published runtime",
22463
+ fix: "Retry when the npm registry is reachable, or re-run install-cursor-plugin.sh to refresh the install"
22464
+ };
22465
+ }
22466
+ if (!semverAtLeast(installed, expected)) {
22467
+ return {
22468
+ id: "stale_install",
22469
+ status: "fail",
22470
+ registryProbed: true,
22471
+ detail: `stale_install: local-dist workit-cursor ${installed} is behind the published runtime ${expected}`,
22472
+ fix: "Re-run install-cursor-plugin.sh — it refreshes the plugin directory with the current build"
22473
+ };
22474
+ }
22475
+ return {
22476
+ id: "stale_install",
22477
+ status: "pass",
22478
+ registryProbed: true,
22479
+ detail: `installed local-dist workit-cursor ${installed} matches the published runtime ${expected}`
22480
+ };
22481
+ }
22482
+ return {
22483
+ id: "stale_install",
22484
+ status: "pass",
22485
+ detail: installed === null ? "installed workit-cursor selectors are canonical" : `installed workit-cursor ${installed} is metadata on the canonical @latest install (fresh at launch)`
22486
+ };
22339
22487
  }, checkDuplicateRegistration = (res) => {
22340
22488
  const problems = [];
22341
22489
  const opencodeHost = res.host !== "cursor";
22342
22490
  const cursorHost = res.host !== "opencode";
22343
- if (opencodeHost && existsSync5(res.opencodeConfig)) {
22491
+ if (opencodeHost && existsSync6(res.opencodeConfig)) {
22344
22492
  const cfg = readJson(res.opencodeConfig);
22345
22493
  const entries = pluginEntries(cfg);
22346
22494
  if (entries.length > 1)
22347
22495
  problems.push(`opencode registers ${entries.length} workit plugin entries`);
22348
22496
  }
22349
- if (cursorHost && existsSync5(res.cursorSettings)) {
22497
+ if (cursorHost && existsSync6(res.cursorSettings)) {
22350
22498
  const settings = readJson(res.cursorSettings);
22351
22499
  const enabled = settings?.enabled_plugins;
22352
22500
  if (enabled && typeof enabled === "object") {
@@ -22361,7 +22509,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22361
22509
  if (dirs.length > 1)
22362
22510
  problems.push(`cursor plugin_dirs has ${dirs.length} workit entries`);
22363
22511
  }
22364
- if (cursorHost && existsSync5(res.cursorMcp)) {
22512
+ if (cursorHost && existsSync6(res.cursorMcp)) {
22365
22513
  const mcp = readJson(res.cursorMcp);
22366
22514
  const servers = mcp?.mcpServers ?? {};
22367
22515
  if (servers && typeof servers === "object") {
@@ -22371,7 +22519,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22371
22519
  }
22372
22520
  }
22373
22521
  if (problems.length === 0) {
22374
- return { id: "duplicate_registration", status: "pass", detail: "no duplicate registrations" };
22522
+ return {
22523
+ id: "duplicate_registration",
22524
+ status: "pass",
22525
+ detail: "no duplicate registrations"
22526
+ };
22375
22527
  }
22376
22528
  return {
22377
22529
  id: "duplicate_registration",
@@ -22382,21 +22534,25 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22382
22534
  }, checkMalformedConfig = (res) => {
22383
22535
  const files = [];
22384
22536
  for (const name of ["config.json", "youtrack.json", "vcs.json", "workspaces.json"]) {
22385
- const p = path9.join(res.configDir, name);
22386
- if (existsSync5(p))
22537
+ const p = path10.join(res.configDir, name);
22538
+ if (existsSync6(p))
22387
22539
  files.push(p);
22388
22540
  }
22389
22541
  const opencodeHost = res.host !== "cursor";
22390
22542
  const cursorHost = res.host !== "opencode";
22391
- if (opencodeHost && existsSync5(res.opencodeConfig))
22543
+ if (opencodeHost && existsSync6(res.opencodeConfig))
22392
22544
  files.push(res.opencodeConfig);
22393
- if (cursorHost && existsSync5(res.cursorSettings))
22545
+ if (cursorHost && existsSync6(res.cursorSettings))
22394
22546
  files.push(res.cursorSettings);
22395
- if (cursorHost && existsSync5(res.cursorMcp))
22547
+ if (cursorHost && existsSync6(res.cursorMcp))
22396
22548
  files.push(res.cursorMcp);
22397
22549
  const bad = files.filter((p) => !parsesAsConfigObject(p));
22398
22550
  if (bad.length === 0)
22399
- return { id: "malformed_config", status: "pass", detail: "config files parse" };
22551
+ return {
22552
+ id: "malformed_config",
22553
+ status: "pass",
22554
+ detail: "config files parse"
22555
+ };
22400
22556
  return {
22401
22557
  id: "malformed_config",
22402
22558
  status: "fail",
@@ -22404,12 +22560,20 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22404
22560
  fix: `Repair the malformed config in ${bad[0]}`
22405
22561
  };
22406
22562
  }, checkWorkspaceMismatch = (res) => {
22407
- const file = path9.join(res.configDir, "workspaces.json");
22408
- if (!existsSync5(file))
22409
- return { id: "workspace_mismatch", status: "pass", detail: "no workspaces configured" };
22563
+ const file = path10.join(res.configDir, "workspaces.json");
22564
+ if (!existsSync6(file))
22565
+ return {
22566
+ id: "workspace_mismatch",
22567
+ status: "pass",
22568
+ detail: "no workspaces configured"
22569
+ };
22410
22570
  const ws = readJson(file);
22411
22571
  if (!Array.isArray(ws?.workspaces)) {
22412
- return { id: "workspace_mismatch", status: "pass", detail: "no workspaces configured" };
22572
+ return {
22573
+ id: "workspace_mismatch",
22574
+ status: "pass",
22575
+ detail: "no workspaces configured"
22576
+ };
22413
22577
  }
22414
22578
  const match = resolveWorkspaceFrom(res.cwd, res.configDir);
22415
22579
  if (match)
@@ -22432,29 +22596,33 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22432
22596
  }
22433
22597
  }, checkCredentialMetadata = (res) => {
22434
22598
  const tokenPaths = [];
22435
- const youtrackJson = readJson(path9.join(res.configDir, "youtrack.json"));
22599
+ const youtrackJson = readJson(path10.join(res.configDir, "youtrack.json"));
22436
22600
  if (youtrackJson) {
22437
- tokenPaths.push(typeof youtrackJson.tokenFile === "string" ? youtrackJson.tokenFile : path9.join(res.configDir, "youtrack.token"));
22438
- } else if (existsSync5(path9.join(res.configDir, "youtrack.token"))) {
22439
- tokenPaths.push(path9.join(res.configDir, "youtrack.token"));
22601
+ tokenPaths.push(typeof youtrackJson.tokenFile === "string" ? youtrackJson.tokenFile : path10.join(res.configDir, "youtrack.token"));
22602
+ } else if (existsSync6(path10.join(res.configDir, "youtrack.token"))) {
22603
+ tokenPaths.push(path10.join(res.configDir, "youtrack.token"));
22440
22604
  }
22441
- const vcsJson = readJson(path9.join(res.configDir, "vcs.json"));
22605
+ const vcsJson = readJson(path10.join(res.configDir, "vcs.json"));
22442
22606
  for (const key of ["gitlab", "github"]) {
22443
22607
  const provider = vcsJson?.[key];
22444
22608
  if (provider && typeof provider === "object") {
22445
22609
  const tf = provider.tokenFile;
22446
- tokenPaths.push(typeof tf === "string" ? tf : path9.join(res.configDir, `${key}.token`));
22447
- } else if (!vcsJson && existsSync5(path9.join(res.configDir, `${key}.token`))) {
22448
- tokenPaths.push(path9.join(res.configDir, `${key}.token`));
22610
+ tokenPaths.push(typeof tf === "string" ? tf : path10.join(res.configDir, `${key}.token`));
22611
+ } else if (!vcsJson && existsSync6(path10.join(res.configDir, `${key}.token`))) {
22612
+ tokenPaths.push(path10.join(res.configDir, `${key}.token`));
22449
22613
  }
22450
22614
  }
22451
22615
  if (tokenPaths.length === 0) {
22452
- return { id: "credential_metadata", status: "pass", detail: "no credentials configured" };
22616
+ return {
22617
+ id: "credential_metadata",
22618
+ status: "pass",
22619
+ detail: "no credentials configured"
22620
+ };
22453
22621
  }
22454
22622
  const problems = [];
22455
22623
  for (const raw of tokenPaths) {
22456
- const p = path9.isAbsolute(raw) ? raw : path9.resolve(res.configDir, raw);
22457
- if (!existsSync5(p)) {
22624
+ const p = path10.isAbsolute(raw) ? raw : path10.resolve(res.configDir, raw);
22625
+ if (!existsSync6(p)) {
22458
22626
  problems.push(`${p} is missing`);
22459
22627
  continue;
22460
22628
  }
@@ -22480,13 +22648,17 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22480
22648
  fix: "Create or fix the token file (mode 0600, real value) — see /wk-status for the exact path"
22481
22649
  };
22482
22650
  }, checkLogWritable = (res) => {
22483
- const logsDir = path9.join(res.stateDir, "logs");
22484
- const probe = path9.join(logsDir, "doctor-probe.tmp");
22651
+ const logsDir = path10.join(res.stateDir, "logs");
22652
+ const probe = path10.join(logsDir, "doctor-probe.tmp");
22485
22653
  try {
22486
22654
  mkdirSync4(logsDir, { recursive: true, mode: 448 });
22487
22655
  writeFileSync3(probe, `{"probe":true}
22488
22656
  `, { mode: 384 });
22489
- return { id: "log_writable", status: "pass", detail: "log directory writable" };
22657
+ return {
22658
+ id: "log_writable",
22659
+ status: "pass",
22660
+ detail: "log directory writable"
22661
+ };
22490
22662
  } catch (err) {
22491
22663
  return {
22492
22664
  id: "log_writable",
@@ -22502,15 +22674,20 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22502
22674
  }, RUN_CHECKS, INSTALLER_REQUIRED, runDoctor = (options = {}) => {
22503
22675
  const res = resolve(options);
22504
22676
  const raw = RUN_CHECKS.map((fn) => fn(res));
22505
- const checks4 = res.installer ? raw.map((c) => c.status === "fail" && !INSTALLER_REQUIRED.has(c.id) ? { ...c, status: "warn", detail: `${c.detail} (not enforced by installer)` } : c) : raw;
22677
+ const checks4 = res.installer ? raw.map((c) => c.status === "fail" && !INSTALLER_REQUIRED.has(c.id) ? {
22678
+ ...c,
22679
+ status: "warn",
22680
+ detail: `${c.detail} (not enforced by installer)`
22681
+ } : c) : raw;
22506
22682
  const failed = checks4.filter((c) => c.status === "fail").length;
22507
22683
  const warned = checks4.filter((c) => c.status === "warn").length;
22508
22684
  const passed = checks4.filter((c) => c.status === "pass").length;
22509
22685
  const exitCode = failed > 0 ? 1 : 0;
22686
+ const offline = !raw.some((c) => ("registryProbed" in c) && c.registryProbed);
22510
22687
  const report = {
22511
22688
  ok: failed === 0,
22512
22689
  exitCode,
22513
- offline: true,
22690
+ offline,
22514
22691
  host: res.host,
22515
22692
  checked_at: new Date().toISOString(),
22516
22693
  summary: { passed, warned, failed, total: checks4.length },
@@ -22520,7 +22697,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22520
22697
  getDiagnosticLogger()?.info(EVENT.doctor, {
22521
22698
  host: res.host,
22522
22699
  exit_code: exitCode,
22523
- offline: true,
22700
+ offline,
22524
22701
  failed: checks4.filter((c) => c.status === "fail").map((c) => c.id),
22525
22702
  total: checks4.length
22526
22703
  });
@@ -22530,6 +22707,7 @@ var init_doctor = __esm(() => {
22530
22707
  init_support_matrix();
22531
22708
  init_boundary();
22532
22709
  init_config();
22710
+ init_package_root();
22533
22711
  init_registration();
22534
22712
  init_workspaces();
22535
22713
  init_skill_manifests();
@@ -22541,6 +22719,7 @@ var init_doctor = __esm(() => {
22541
22719
  checkLauncher,
22542
22720
  checkUtility,
22543
22721
  checkStalePin,
22722
+ checkStaleInstall,
22544
22723
  checkDuplicateRegistration,
22545
22724
  checkMalformedConfig,
22546
22725
  checkWorkspaceMismatch,
@@ -22553,6 +22732,7 @@ var init_doctor = __esm(() => {
22553
22732
  "launcher",
22554
22733
  "utility",
22555
22734
  "stale_pin",
22735
+ "stale_install",
22556
22736
  "duplicate_registration",
22557
22737
  "malformed_config"
22558
22738
  ]);
@@ -22560,7 +22740,7 @@ var init_doctor = __esm(() => {
22560
22740
 
22561
22741
  // packages/workit-core/src/core/pr-create.ts
22562
22742
  import fs2 from "node:fs";
22563
- import path10 from "node:path";
22743
+ import path11 from "node:path";
22564
22744
  import { spawnSync as spawnSync5 } from "node:child_process";
22565
22745
  function parseGhRepo(remote) {
22566
22746
  remote = (remote || "").trim().replace(/\/+$/, "");
@@ -22625,11 +22805,11 @@ ${line}` : line;
22625
22805
  }
22626
22806
  function whichOnPath(tool) {
22627
22807
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
22628
- for (const dir of (process.env.PATH ?? "").split(path10.delimiter)) {
22808
+ for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
22629
22809
  if (!dir)
22630
22810
  continue;
22631
22811
  for (const name of names) {
22632
- const candidate = path10.join(dir, name);
22812
+ const candidate = path11.join(dir, name);
22633
22813
  try {
22634
22814
  fs2.accessSync(candidate, fs2.constants.X_OK);
22635
22815
  return candidate;
@@ -22731,7 +22911,7 @@ function prCreate(env, cwd) {
22731
22911
  const draft = String(env.WF_PR_DRAFT ?? "false").toLowerCase() === "true";
22732
22912
  let baseUrl = cfg.youtrack_base_url;
22733
22913
  if (!baseUrl) {
22734
- const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path10.join(path10.dirname(String(cfg.configPath)), "youtrack.json");
22914
+ const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path11.join(path11.dirname(String(cfg.configPath)), "youtrack.json");
22735
22915
  try {
22736
22916
  const yt = JSON.parse(fs2.readFileSync(ytCfg, "utf8"));
22737
22917
  if (yt && typeof yt === "object")
@@ -22840,25 +23020,6 @@ var init_pr_create = __esm(() => {
22840
23020
  init_branch();
22841
23021
  });
22842
23022
 
22843
- // packages/workit-core/src/core/package-root.ts
22844
- import { existsSync as existsSync6 } from "node:fs";
22845
- import path11 from "node:path";
22846
- import { fileURLToPath } from "node:url";
22847
- var packageRoot = () => {
22848
- let dir = path11.dirname(fileURLToPath(import.meta.url));
22849
- while (true) {
22850
- const parent = path11.dirname(dir);
22851
- if (existsSync6(path11.join(dir, "package.json")) || parent === dir)
22852
- return dir;
22853
- dir = parent;
22854
- }
22855
- }, assetRoot = () => {
22856
- const root = packageRoot();
22857
- const assets = path11.join(root, "assets");
22858
- return existsSync6(assets) ? assets : root;
22859
- };
22860
- var init_package_root = () => {};
22861
-
22862
23023
  // packages/workit-core/src/core/scripts.ts
22863
23024
  var PLUGIN_ROOT, ASSET_ROOT, resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
22864
23025
  var init_scripts = __esm(() => {
@@ -24503,9 +24664,6 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24503
24664
  }), resetForPlanDrift = (state) => ({
24504
24665
  ...state,
24505
24666
  plan: { ...state.plan, status: "draft", evidence: null, approved_digest: null },
24506
- menu: { presented: false, chosen: "", evidence: null },
24507
- execution: { status: "pending", mode: null, evidence: null },
24508
- handoff_destination: false,
24509
24667
  updated_at: Date.now()
24510
24668
  }), driftCodeFor = (root, relPath, storedDigest) => {
24511
24669
  if (storedDigest === null)
@@ -3,7 +3,7 @@
3
3
  "hooks": {
4
4
  "sessionStart": [
5
5
  {
6
- "command": "npx -y --package=@brainervirus/workit-cursor@0.8.5 workit-cursor-session-start"
6
+ "command": "npx -y --prefer-online --package=@brainervirus/workit-cursor@latest workit-cursor-session-start"
7
7
  }
8
8
  ]
9
9
  }
package/mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "workit": {
4
4
  "command": "npx",
5
- "args": ["-y", "--package=@brainervirus/workit-cursor@0.8.5", "workit-cursor-mcp", "${workspaceFolder}"]
5
+ "args": ["-y", "--prefer-online", "--package=@brainervirus/workit-cursor@latest", "workit-cursor-mcp", "${workspaceFolder}"]
6
6
  }
7
7
  }
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cursor",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "private": false,
5
5
  "description": "Workit Cursor plugin: MCP server, hooks, rules, marketplace manifest (thin over @brainervirus/workit-core)",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "build": "bun scripts/build.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@brainervirus/workit-core": "^0.8.6",
42
+ "@brainervirus/workit-core": "^0.8.8",
43
43
  "@modelcontextprotocol/sdk": "^1.12.0",
44
44
  "zod": "^3.24.0"
45
45
  },