@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.
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +15 -6
- package/dist/mcp-server.js +278 -120
- package/hooks/hooks-cursor.json +1 -1
- package/mcp.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,7 +23,8 @@ npx @brainervirus/workit-cli init
|
|
|
23
23
|
"command": "npx",
|
|
24
24
|
"args": [
|
|
25
25
|
"-y",
|
|
26
|
-
"--
|
|
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 …@
|
|
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@
|
|
63
|
-
- **Session-start hook** — `npx -y --package=@brainervirus/workit-cursor@
|
|
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
|
|
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
|
|
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
|
package/dist/mcp-server.js
CHANGED
|
@@ -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@
|
|
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
|
|
21856
|
-
import
|
|
21857
|
-
var CANONICAL_SKILLS, skillManifestNames = (root) =>
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
21955
|
+
import path10 from "node:path";
|
|
21925
21956
|
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
|
|
21926
|
-
let dir =
|
|
21957
|
+
let dir = path10.resolve(cwd);
|
|
21927
21958
|
while (true) {
|
|
21928
|
-
if (
|
|
21959
|
+
if (existsSync6(path10.join(dir, "packages", "workit-core", "package.json")))
|
|
21929
21960
|
return dir;
|
|
21930
|
-
const parent =
|
|
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 ??
|
|
21939
|
-
const stateDir = options.stateDir ?? env.WORKFLOW_TOOLKIT_STATE ??
|
|
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 ??
|
|
21950
|
-
cursorSettings: options.cursorSettings ??
|
|
21951
|
-
cursorMcp: options.cursorMcp ??
|
|
21952
|
-
cursorPluginDir: options.cursorPluginDir ??
|
|
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(
|
|
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 =
|
|
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 &&
|
|
22037
|
+
if (env.BUN && existsSync6(env.BUN))
|
|
22007
22038
|
return env.BUN;
|
|
22008
22039
|
for (const candidate of [
|
|
22009
|
-
|
|
22040
|
+
path10.join(os3.homedir(), ".bun/bin/bun"),
|
|
22010
22041
|
"/usr/local/bin/bun",
|
|
22011
22042
|
"/usr/bin/bun"
|
|
22012
22043
|
]) {
|
|
22013
|
-
if (
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
22138
|
+
const pkg = path10.join(dev, "packages", `workit-${host}`);
|
|
22108
22139
|
switch (host) {
|
|
22109
22140
|
case "opencode":
|
|
22110
22141
|
return [
|
|
22111
|
-
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
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
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
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 [
|
|
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) => !
|
|
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 =
|
|
22185
|
+
const pkg = path10.join(dev, "packages", `workit-${host}`);
|
|
22155
22186
|
switch (host) {
|
|
22156
22187
|
case "opencode":
|
|
22157
|
-
return [[
|
|
22188
|
+
return [[path10.join(pkg, "src", "plugin.ts"), path10.join(pkg, "dist", "plugin.js")]];
|
|
22158
22189
|
case "cursor":
|
|
22159
22190
|
return [
|
|
22160
|
-
[
|
|
22161
|
-
[
|
|
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 [[
|
|
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 (!
|
|
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 =
|
|
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
|
|
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:
|
|
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 =
|
|
22210
|
-
if (!
|
|
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 (!
|
|
22255
|
+
if (!path10.isAbsolute(entry))
|
|
22228
22256
|
return false;
|
|
22229
|
-
if (
|
|
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) =>
|
|
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 ${
|
|
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 ${
|
|
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) =>
|
|
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 {
|
|
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 {
|
|
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
|
|
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 (!
|
|
22309
|
-
return {
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 {
|
|
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 =
|
|
22386
|
-
if (
|
|
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 &&
|
|
22543
|
+
if (opencodeHost && existsSync6(res.opencodeConfig))
|
|
22392
22544
|
files.push(res.opencodeConfig);
|
|
22393
|
-
if (cursorHost &&
|
|
22545
|
+
if (cursorHost && existsSync6(res.cursorSettings))
|
|
22394
22546
|
files.push(res.cursorSettings);
|
|
22395
|
-
if (cursorHost &&
|
|
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 {
|
|
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 =
|
|
22408
|
-
if (!
|
|
22409
|
-
return {
|
|
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 {
|
|
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(
|
|
22599
|
+
const youtrackJson = readJson(path10.join(res.configDir, "youtrack.json"));
|
|
22436
22600
|
if (youtrackJson) {
|
|
22437
|
-
tokenPaths.push(typeof youtrackJson.tokenFile === "string" ? youtrackJson.tokenFile :
|
|
22438
|
-
} else if (
|
|
22439
|
-
tokenPaths.push(
|
|
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(
|
|
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 :
|
|
22447
|
-
} else if (!vcsJson &&
|
|
22448
|
-
tokenPaths.push(
|
|
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 {
|
|
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 =
|
|
22457
|
-
if (!
|
|
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 =
|
|
22484
|
-
const probe =
|
|
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 {
|
|
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) ? {
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 ??
|
|
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)
|
package/hooks/hooks-cursor.json
CHANGED
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@
|
|
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.
|
|
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.
|
|
42
|
+
"@brainervirus/workit-core": "^0.8.8",
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
44
44
|
"zod": "^3.24.0"
|
|
45
45
|
},
|