@brainervirus/workit-cursor 0.8.7 → 0.8.9

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.
@@ -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("\\", "/");
@@ -21864,18 +21883,18 @@ var named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_
21864
21883
  var init_registration = () => {};
21865
21884
 
21866
21885
  // packages/workit-core/src/core/skill-manifests.ts
21867
- import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
21868
- import path8 from "node:path";
21869
- 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) => {
21870
21889
  const actual = skillManifestNames(root);
21871
21890
  const missing = expected.filter((name) => !actual.includes(name));
21872
21891
  const extra = actual.filter((name) => !expected.includes(name));
21873
21892
  return missing.length === 0 && extra.length === 0 ? null : `${label} mismatch at ${root} (missing: ${missing.join(", ") || "none"}; extra: ${extra.join(", ") || "none"})`;
21874
21893
  }, validateCursorSkills = (pluginDir) => {
21875
- 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");
21876
21895
  if (workit)
21877
21896
  return workit;
21878
- const vendor = path8.join(pluginDir, "vendor/superpowers/skills");
21897
+ const vendor = path9.join(pluginDir, "vendor/superpowers/skills");
21879
21898
  const superpowers = validateSkillManifests(vendor, CANONICAL_SKILLS.superpowers, "Cursor Superpowers skills");
21880
21899
  if (superpowers)
21881
21900
  return superpowers;
@@ -21883,7 +21902,7 @@ var CANONICAL_SKILLS, skillManifestNames = (root) => existsSync4(root) ? readdir
21883
21902
  while (pending.length > 0) {
21884
21903
  const dir = pending.pop();
21885
21904
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
21886
- const file = path8.join(dir, entry.name);
21905
+ const file = path9.join(dir, entry.name);
21887
21906
  if (entry.isDirectory())
21888
21907
  pending.push(file);
21889
21908
  else if ((statSync2(file).mode & 73) !== 0 || readFileSync6(file).subarray(0, 2).toString("latin1") === "#!") {
@@ -21931,15 +21950,15 @@ var init_skill_manifests = __esm(() => {
21931
21950
 
21932
21951
  // packages/workit-core/src/core/doctor.ts
21933
21952
  import { spawnSync as spawnSync4 } from "node:child_process";
21934
- 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";
21935
21954
  import os3 from "node:os";
21936
- import path9 from "node:path";
21955
+ import path10 from "node:path";
21937
21956
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21938
- let dir = path9.resolve(cwd);
21957
+ let dir = path10.resolve(cwd);
21939
21958
  while (true) {
21940
- if (existsSync5(path9.join(dir, "packages", "workit-core", "package.json")))
21959
+ if (existsSync6(path10.join(dir, "packages", "workit-core", "package.json")))
21941
21960
  return dir;
21942
- const parent = path9.dirname(dir);
21961
+ const parent = path10.dirname(dir);
21943
21962
  if (parent === dir)
21944
21963
  return null;
21945
21964
  dir = parent;
@@ -21947,8 +21966,8 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21947
21966
  }, resolve = (options) => {
21948
21967
  const env = options.env ?? process.env;
21949
21968
  const home = options.home ?? env.HOME ?? os3.homedir();
21950
- const configDir2 = options.configDir ?? env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path9.join(home, ".config", "workit");
21951
- 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");
21952
21971
  const cwd = options.cwd ?? process.cwd();
21953
21972
  const dev = options.dev ?? env.WORKFLOW_TOOLKIT_DEV ?? findDevFromCwd(cwd);
21954
21973
  return {
@@ -21958,10 +21977,10 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21958
21977
  stateDir,
21959
21978
  cwd,
21960
21979
  dev,
21961
- opencodeConfig: options.opencodeConfig ?? path9.join(home, ".config", "opencode", "opencode.json"),
21962
- cursorSettings: options.cursorSettings ?? path9.join(home, ".cursor", "settings.json"),
21963
- cursorMcp: options.cursorMcp ?? path9.join(home, ".cursor", "mcp.json"),
21964
- 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"),
21965
21984
  env,
21966
21985
  installer: options.installer ?? false
21967
21986
  };
@@ -21983,13 +22002,13 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
21983
22002
  const list = Array.isArray(plugin) ? plugin : typeof plugin === "string" ? [plugin] : [];
21984
22003
  return list.map(String).filter(isWorkitPlugin);
21985
22004
  }, commandOnPath2 = (name, env) => {
21986
- const dirs = (env.PATH ?? process.env.PATH ?? "").split(path9.delimiter);
22005
+ const dirs = (env.PATH ?? process.env.PATH ?? "").split(path10.delimiter);
21987
22006
  const names = process.platform === "win32" ? [name, `${name}.exe`] : [name];
21988
22007
  for (const dir of dirs) {
21989
22008
  if (!dir)
21990
22009
  continue;
21991
22010
  for (const candidateName of names) {
21992
- const candidate = path9.join(dir, candidateName);
22011
+ const candidate = path10.join(dir, candidateName);
21993
22012
  try {
21994
22013
  const st = statSync3(candidate);
21995
22014
  if (process.platform !== "win32" && (st.mode & 73) === 0)
@@ -22015,14 +22034,14 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22015
22034
  }
22016
22035
  return true;
22017
22036
  }, resolveBun = (env) => {
22018
- if (env.BUN && existsSync5(env.BUN))
22037
+ if (env.BUN && existsSync6(env.BUN))
22019
22038
  return env.BUN;
22020
22039
  for (const candidate of [
22021
- path9.join(os3.homedir(), ".bun/bin/bun"),
22040
+ path10.join(os3.homedir(), ".bun/bin/bun"),
22022
22041
  "/usr/local/bin/bun",
22023
22042
  "/usr/bin/bun"
22024
22043
  ]) {
22025
- if (existsSync5(candidate))
22044
+ if (existsSync6(candidate))
22026
22045
  return candidate;
22027
22046
  }
22028
22047
  return commandOnPath2("bun", env) ? "bun" : null;
@@ -22070,7 +22089,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22070
22089
  detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping version parity"
22071
22090
  };
22072
22091
  }
22073
- 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"));
22074
22093
  if (!corePkg) {
22075
22094
  return {
22076
22095
  id: "versions",
@@ -22080,7 +22099,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22080
22099
  }
22081
22100
  const refs = new Set;
22082
22101
  for (const name of ["workit-opencode", "workit-cursor", "workit-cli"]) {
22083
- const pkg = readJson(path9.join(res.dev, "packages", name, "package.json"));
22102
+ const pkg = readJson(path10.join(res.dev, "packages", name, "package.json"));
22084
22103
  const dep = pkg?.dependencies?.["@brainervirus/workit-core"];
22085
22104
  if (typeof dep === "string")
22086
22105
  refs.add(dep);
@@ -22096,7 +22115,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22096
22115
  if (refs.size > 1) {
22097
22116
  problems.push(`adapters pin different core versions: ${[...refs].join(", ")}`);
22098
22117
  }
22099
- 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"));
22100
22119
  const sdk = opencodePkg?.dependencies?.["@opencode-ai/plugin"];
22101
22120
  const sdkVersion = typeof sdk === "string" ? (sdk.match(/^\d+(?:\.\d+){0,2}/) ?? [])[0] : null;
22102
22121
  if (sdkVersion && !semverAtLeast(sdkVersion, SUPPORT_MATRIX.opencode.minimum)) {
@@ -22116,27 +22135,27 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22116
22135
  fix: "Align every adapter to the same @brainervirus/workit-core version (rewrite-workspace-deps.ts) or reinstall"
22117
22136
  };
22118
22137
  }, assetPathsFor = (host, dev) => {
22119
- const pkg = path9.join(dev, "packages", `workit-${host}`);
22138
+ const pkg = path10.join(dev, "packages", `workit-${host}`);
22120
22139
  switch (host) {
22121
22140
  case "opencode":
22122
22141
  return [
22123
- path9.join(pkg, "assets", "commands", "wk-init.md"),
22124
- path9.join(pkg, "assets", "skills", "wk-init", "SKILL.md"),
22125
- path9.join(pkg, "assets", "templates", "spec-template.md"),
22126
- 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")
22127
22146
  ];
22128
22147
  case "cursor":
22129
22148
  return [
22130
- path9.join(pkg, "assets", "templates", "spec-template.md"),
22131
- path9.join(pkg, "mcp.json"),
22132
- 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")
22133
22152
  ];
22134
22153
  case "cli":
22135
- return [path9.join(pkg, "assets", "templates", "spec-template.md")];
22154
+ return [path10.join(pkg, "assets", "templates", "spec-template.md")];
22136
22155
  }
22137
22156
  }, hostsFor = (host) => host === "cli" ? ["opencode", "cursor", "cli"] : [host], checkAssets = (res) => {
22138
22157
  const dev = res.dev;
22139
- 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}`)) : [];
22140
22159
  if (res.host === "cursor" || res.host === "cli") {
22141
22160
  const cursorError = validateCursorSkills(res.cursorPluginDir);
22142
22161
  if (cursorError)
@@ -22163,17 +22182,17 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22163
22182
  fix: "Reinstall or rebuild the workit package (missing assets under packages/workit-<host>)"
22164
22183
  };
22165
22184
  }, launcherSlotsFor = (host, dev) => {
22166
- const pkg = path9.join(dev, "packages", `workit-${host}`);
22185
+ const pkg = path10.join(dev, "packages", `workit-${host}`);
22167
22186
  switch (host) {
22168
22187
  case "opencode":
22169
- 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")]];
22170
22189
  case "cursor":
22171
22190
  return [
22172
- [path9.join(pkg, "dist", "mcp-server.js")],
22173
- [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")]
22174
22193
  ];
22175
22194
  case "cli":
22176
- 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")]];
22177
22196
  }
22178
22197
  }, validNodeEntry = (entry, runtime, env) => {
22179
22198
  try {
@@ -22187,7 +22206,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22187
22206
  return false;
22188
22207
  }
22189
22208
  }, registeredCursorLauncher = (res) => {
22190
- if (!existsSync5(res.cursorMcp))
22209
+ if (!existsSync6(res.cursorMcp))
22191
22210
  return "invalid";
22192
22211
  const config2 = readJson(res.cursorMcp);
22193
22212
  if (!config2)
@@ -22200,7 +22219,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22200
22219
  if (typeof command !== "string" || !Array.isArray(args) || typeof args[0] !== "string") {
22201
22220
  return "invalid";
22202
22221
  }
22203
- const executable = path9.basename(command).toLowerCase();
22222
+ const executable = path10.basename(command).toLowerCase();
22204
22223
  const canonical = cursorMcpServerEntry("").args;
22205
22224
  if (executable === "npx" || executable === "npx.exe" || executable === "npx.cmd") {
22206
22225
  if (args.length !== canonical.length || args.some((a, i) => a !== canonical[i]))
@@ -22212,11 +22231,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22212
22231
  return {
22213
22232
  kind: "node",
22214
22233
  runtime: command,
22215
- 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])
22216
22235
  };
22217
22236
  }, canonicalCursorHook, registeredCursorHook = (res) => {
22218
- const hooksFile = path9.join(res.cursorPluginDir, "hooks", "hooks-cursor.json");
22219
- if (!existsSync5(hooksFile))
22237
+ const hooksFile = path10.join(res.cursorPluginDir, "hooks", "hooks-cursor.json");
22238
+ if (!existsSync6(hooksFile))
22220
22239
  return "invalid";
22221
22240
  const config2 = readJson(hooksFile);
22222
22241
  if (!config2)
@@ -22233,9 +22252,9 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22233
22252
  if (!m)
22234
22253
  return false;
22235
22254
  const entry = m[1].trim();
22236
- if (!path9.isAbsolute(entry))
22255
+ if (!path10.isAbsolute(entry))
22237
22256
  return false;
22238
- 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"))) {
22239
22258
  return false;
22240
22259
  }
22241
22260
  return validNodeEntry(entry, "node", res.env);
@@ -22244,7 +22263,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22244
22263
  const hosts = hostsFor(res.host);
22245
22264
  const registered = hosts.includes("cursor") ? registeredCursorLauncher(res) : null;
22246
22265
  const runtime = registered && registered !== "invalid" && registered.kind === "node" ? registered.runtime : "node";
22247
- 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}`) : [];
22248
22267
  if (hosts.includes("cursor")) {
22249
22268
  if (registered === "invalid") {
22250
22269
  missing.push(`cursor: canonical workit MCP launcher in ${res.cursorMcp}`);
@@ -22253,13 +22272,13 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22253
22272
  }
22254
22273
  const hook = registeredCursorHook(res);
22255
22274
  if (hook === "invalid") {
22256
- 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")}`);
22257
22276
  } else if (hook !== null && hook !== canonicalCursorHook && !validLocalDistHook(hook, res)) {
22258
- 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})`);
22259
22278
  }
22260
22279
  }
22261
22280
  if (dev) {
22262
- 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 ")}`)));
22263
22282
  }
22264
22283
  if (missing.length > 0) {
22265
22284
  return {
@@ -22276,7 +22295,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22276
22295
  detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping non-Cursor launcher checks"
22277
22296
  };
22278
22297
  }
22279
- 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
+ };
22280
22303
  }, checkUtility = (res) => {
22281
22304
  const git = commandOnPath2("git", res.env);
22282
22305
  if (!git) {
@@ -22295,7 +22318,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22295
22318
  fix: "Install util-linux (flock)"
22296
22319
  };
22297
22320
  }
22298
- 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
+ };
22299
22326
  }, staleEntry = (entry) => {
22300
22327
  if (!entry)
22301
22328
  return "stale";
@@ -22303,7 +22330,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22303
22330
  return "stale";
22304
22331
  if (entry.startsWith("file:")) {
22305
22332
  const target = entry.replace(/^file:\/\//, "").replace(/^file:/, "");
22306
- return existsSync5(target) ? "ok" : "missing-file";
22333
+ return existsSync6(target) ? "ok" : "missing-file";
22307
22334
  }
22308
22335
  return "ok";
22309
22336
  }, checkStalePin = (res) => {
@@ -22314,8 +22341,12 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22314
22341
  detail: "opencode pin not inspected on the cursor host"
22315
22342
  };
22316
22343
  }
22317
- if (!existsSync5(res.opencodeConfig)) {
22318
- 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
+ };
22319
22350
  }
22320
22351
  const cfg = readJson(res.opencodeConfig);
22321
22352
  const entries = pluginEntries(cfg);
@@ -22345,17 +22376,125 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22345
22376
  };
22346
22377
  }
22347
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
+ };
22348
22487
  }, checkDuplicateRegistration = (res) => {
22349
22488
  const problems = [];
22350
22489
  const opencodeHost = res.host !== "cursor";
22351
22490
  const cursorHost = res.host !== "opencode";
22352
- if (opencodeHost && existsSync5(res.opencodeConfig)) {
22491
+ if (opencodeHost && existsSync6(res.opencodeConfig)) {
22353
22492
  const cfg = readJson(res.opencodeConfig);
22354
22493
  const entries = pluginEntries(cfg);
22355
22494
  if (entries.length > 1)
22356
22495
  problems.push(`opencode registers ${entries.length} workit plugin entries`);
22357
22496
  }
22358
- if (cursorHost && existsSync5(res.cursorSettings)) {
22497
+ if (cursorHost && existsSync6(res.cursorSettings)) {
22359
22498
  const settings = readJson(res.cursorSettings);
22360
22499
  const enabled = settings?.enabled_plugins;
22361
22500
  if (enabled && typeof enabled === "object") {
@@ -22370,7 +22509,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22370
22509
  if (dirs.length > 1)
22371
22510
  problems.push(`cursor plugin_dirs has ${dirs.length} workit entries`);
22372
22511
  }
22373
- if (cursorHost && existsSync5(res.cursorMcp)) {
22512
+ if (cursorHost && existsSync6(res.cursorMcp)) {
22374
22513
  const mcp = readJson(res.cursorMcp);
22375
22514
  const servers = mcp?.mcpServers ?? {};
22376
22515
  if (servers && typeof servers === "object") {
@@ -22380,7 +22519,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22380
22519
  }
22381
22520
  }
22382
22521
  if (problems.length === 0) {
22383
- 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
+ };
22384
22527
  }
22385
22528
  return {
22386
22529
  id: "duplicate_registration",
@@ -22391,21 +22534,25 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22391
22534
  }, checkMalformedConfig = (res) => {
22392
22535
  const files = [];
22393
22536
  for (const name of ["config.json", "youtrack.json", "vcs.json", "workspaces.json"]) {
22394
- const p = path9.join(res.configDir, name);
22395
- if (existsSync5(p))
22537
+ const p = path10.join(res.configDir, name);
22538
+ if (existsSync6(p))
22396
22539
  files.push(p);
22397
22540
  }
22398
22541
  const opencodeHost = res.host !== "cursor";
22399
22542
  const cursorHost = res.host !== "opencode";
22400
- if (opencodeHost && existsSync5(res.opencodeConfig))
22543
+ if (opencodeHost && existsSync6(res.opencodeConfig))
22401
22544
  files.push(res.opencodeConfig);
22402
- if (cursorHost && existsSync5(res.cursorSettings))
22545
+ if (cursorHost && existsSync6(res.cursorSettings))
22403
22546
  files.push(res.cursorSettings);
22404
- if (cursorHost && existsSync5(res.cursorMcp))
22547
+ if (cursorHost && existsSync6(res.cursorMcp))
22405
22548
  files.push(res.cursorMcp);
22406
22549
  const bad = files.filter((p) => !parsesAsConfigObject(p));
22407
22550
  if (bad.length === 0)
22408
- 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
+ };
22409
22556
  return {
22410
22557
  id: "malformed_config",
22411
22558
  status: "fail",
@@ -22413,12 +22560,20 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22413
22560
  fix: `Repair the malformed config in ${bad[0]}`
22414
22561
  };
22415
22562
  }, checkWorkspaceMismatch = (res) => {
22416
- const file = path9.join(res.configDir, "workspaces.json");
22417
- if (!existsSync5(file))
22418
- 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
+ };
22419
22570
  const ws = readJson(file);
22420
22571
  if (!Array.isArray(ws?.workspaces)) {
22421
- 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
+ };
22422
22577
  }
22423
22578
  const match = resolveWorkspaceFrom(res.cwd, res.configDir);
22424
22579
  if (match)
@@ -22441,29 +22596,33 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22441
22596
  }
22442
22597
  }, checkCredentialMetadata = (res) => {
22443
22598
  const tokenPaths = [];
22444
- const youtrackJson = readJson(path9.join(res.configDir, "youtrack.json"));
22599
+ const youtrackJson = readJson(path10.join(res.configDir, "youtrack.json"));
22445
22600
  if (youtrackJson) {
22446
- tokenPaths.push(typeof youtrackJson.tokenFile === "string" ? youtrackJson.tokenFile : path9.join(res.configDir, "youtrack.token"));
22447
- } else if (existsSync5(path9.join(res.configDir, "youtrack.token"))) {
22448
- 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"));
22449
22604
  }
22450
- const vcsJson = readJson(path9.join(res.configDir, "vcs.json"));
22605
+ const vcsJson = readJson(path10.join(res.configDir, "vcs.json"));
22451
22606
  for (const key of ["gitlab", "github"]) {
22452
22607
  const provider = vcsJson?.[key];
22453
22608
  if (provider && typeof provider === "object") {
22454
22609
  const tf = provider.tokenFile;
22455
- tokenPaths.push(typeof tf === "string" ? tf : path9.join(res.configDir, `${key}.token`));
22456
- } else if (!vcsJson && existsSync5(path9.join(res.configDir, `${key}.token`))) {
22457
- 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`));
22458
22613
  }
22459
22614
  }
22460
22615
  if (tokenPaths.length === 0) {
22461
- 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
+ };
22462
22621
  }
22463
22622
  const problems = [];
22464
22623
  for (const raw of tokenPaths) {
22465
- const p = path9.isAbsolute(raw) ? raw : path9.resolve(res.configDir, raw);
22466
- if (!existsSync5(p)) {
22624
+ const p = path10.isAbsolute(raw) ? raw : path10.resolve(res.configDir, raw);
22625
+ if (!existsSync6(p)) {
22467
22626
  problems.push(`${p} is missing`);
22468
22627
  continue;
22469
22628
  }
@@ -22489,13 +22648,17 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22489
22648
  fix: "Create or fix the token file (mode 0600, real value) — see /wk-status for the exact path"
22490
22649
  };
22491
22650
  }, checkLogWritable = (res) => {
22492
- const logsDir = path9.join(res.stateDir, "logs");
22493
- 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");
22494
22653
  try {
22495
22654
  mkdirSync4(logsDir, { recursive: true, mode: 448 });
22496
22655
  writeFileSync3(probe, `{"probe":true}
22497
22656
  `, { mode: 384 });
22498
- 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
+ };
22499
22662
  } catch (err) {
22500
22663
  return {
22501
22664
  id: "log_writable",
@@ -22511,15 +22674,20 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22511
22674
  }, RUN_CHECKS, INSTALLER_REQUIRED, runDoctor = (options = {}) => {
22512
22675
  const res = resolve(options);
22513
22676
  const raw = RUN_CHECKS.map((fn) => fn(res));
22514
- 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;
22515
22682
  const failed = checks4.filter((c) => c.status === "fail").length;
22516
22683
  const warned = checks4.filter((c) => c.status === "warn").length;
22517
22684
  const passed = checks4.filter((c) => c.status === "pass").length;
22518
22685
  const exitCode = failed > 0 ? 1 : 0;
22686
+ const offline = !raw.some((c) => ("registryProbed" in c) && c.registryProbed);
22519
22687
  const report = {
22520
22688
  ok: failed === 0,
22521
22689
  exitCode,
22522
- offline: true,
22690
+ offline,
22523
22691
  host: res.host,
22524
22692
  checked_at: new Date().toISOString(),
22525
22693
  summary: { passed, warned, failed, total: checks4.length },
@@ -22529,7 +22697,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
22529
22697
  getDiagnosticLogger()?.info(EVENT.doctor, {
22530
22698
  host: res.host,
22531
22699
  exit_code: exitCode,
22532
- offline: true,
22700
+ offline,
22533
22701
  failed: checks4.filter((c) => c.status === "fail").map((c) => c.id),
22534
22702
  total: checks4.length
22535
22703
  });
@@ -22539,6 +22707,7 @@ var init_doctor = __esm(() => {
22539
22707
  init_support_matrix();
22540
22708
  init_boundary();
22541
22709
  init_config();
22710
+ init_package_root();
22542
22711
  init_registration();
22543
22712
  init_workspaces();
22544
22713
  init_skill_manifests();
@@ -22550,6 +22719,7 @@ var init_doctor = __esm(() => {
22550
22719
  checkLauncher,
22551
22720
  checkUtility,
22552
22721
  checkStalePin,
22722
+ checkStaleInstall,
22553
22723
  checkDuplicateRegistration,
22554
22724
  checkMalformedConfig,
22555
22725
  checkWorkspaceMismatch,
@@ -22562,6 +22732,7 @@ var init_doctor = __esm(() => {
22562
22732
  "launcher",
22563
22733
  "utility",
22564
22734
  "stale_pin",
22735
+ "stale_install",
22565
22736
  "duplicate_registration",
22566
22737
  "malformed_config"
22567
22738
  ]);
@@ -22569,7 +22740,7 @@ var init_doctor = __esm(() => {
22569
22740
 
22570
22741
  // packages/workit-core/src/core/pr-create.ts
22571
22742
  import fs2 from "node:fs";
22572
- import path10 from "node:path";
22743
+ import path11 from "node:path";
22573
22744
  import { spawnSync as spawnSync5 } from "node:child_process";
22574
22745
  function parseGhRepo(remote) {
22575
22746
  remote = (remote || "").trim().replace(/\/+$/, "");
@@ -22634,11 +22805,11 @@ ${line}` : line;
22634
22805
  }
22635
22806
  function whichOnPath(tool) {
22636
22807
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
22637
- for (const dir of (process.env.PATH ?? "").split(path10.delimiter)) {
22808
+ for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
22638
22809
  if (!dir)
22639
22810
  continue;
22640
22811
  for (const name of names) {
22641
- const candidate = path10.join(dir, name);
22812
+ const candidate = path11.join(dir, name);
22642
22813
  try {
22643
22814
  fs2.accessSync(candidate, fs2.constants.X_OK);
22644
22815
  return candidate;
@@ -22740,7 +22911,7 @@ function prCreate(env, cwd) {
22740
22911
  const draft = String(env.WF_PR_DRAFT ?? "false").toLowerCase() === "true";
22741
22912
  let baseUrl = cfg.youtrack_base_url;
22742
22913
  if (!baseUrl) {
22743
- 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");
22744
22915
  try {
22745
22916
  const yt = JSON.parse(fs2.readFileSync(ytCfg, "utf8"));
22746
22917
  if (yt && typeof yt === "object")
@@ -22849,25 +23020,6 @@ var init_pr_create = __esm(() => {
22849
23020
  init_branch();
22850
23021
  });
22851
23022
 
22852
- // packages/workit-core/src/core/package-root.ts
22853
- import { existsSync as existsSync6 } from "node:fs";
22854
- import path11 from "node:path";
22855
- import { fileURLToPath } from "node:url";
22856
- var packageRoot = () => {
22857
- let dir = path11.dirname(fileURLToPath(import.meta.url));
22858
- while (true) {
22859
- const parent = path11.dirname(dir);
22860
- if (existsSync6(path11.join(dir, "package.json")) || parent === dir)
22861
- return dir;
22862
- dir = parent;
22863
- }
22864
- }, assetRoot = () => {
22865
- const root = packageRoot();
22866
- const assets = path11.join(root, "assets");
22867
- return existsSync6(assets) ? assets : root;
22868
- };
22869
- var init_package_root = () => {};
22870
-
22871
23023
  // packages/workit-core/src/core/scripts.ts
22872
23024
  var PLUGIN_ROOT, ASSET_ROOT, resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
22873
23025
  var init_scripts = __esm(() => {
@@ -24114,6 +24266,58 @@ function sddAppendProgress({
24114
24266
  const rel = posix2(path19.relative(contained.base, path_));
24115
24267
  return { ok: true, line: trimmed, progress_path: rel };
24116
24268
  }
24269
+ function sddAppendAdvisory({
24270
+ advisories_path,
24271
+ task_id,
24272
+ text,
24273
+ workspace_root
24274
+ }) {
24275
+ if (typeof task_id !== "number" || !Number.isSafeInteger(task_id) || task_id <= 0) {
24276
+ return { error: "task_id must be a positive safe integer", code: "advisory_task_invalid" };
24277
+ }
24278
+ if (typeof text !== "string") {
24279
+ return {
24280
+ error: "advisory text must be a string of 1-1000 characters after normalization",
24281
+ code: "advisory_text_invalid"
24282
+ };
24283
+ }
24284
+ if (text.includes("\r") || text.includes(`
24285
+ `)) {
24286
+ return {
24287
+ error: "advisory text must be a single line (no CR/LF)",
24288
+ code: "advisory_text_invalid"
24289
+ };
24290
+ }
24291
+ const collapsed = text.trim().replace(/[ \t]+/g, " ");
24292
+ if (collapsed.length === 0 || collapsed.length > 1000) {
24293
+ return {
24294
+ error: "advisory text must be 1-1000 characters after trim and horizontal-space collapse",
24295
+ code: "advisory_text_invalid"
24296
+ };
24297
+ }
24298
+ if (!/^docs\/[^/]+\/sdd\/advisories\.md$/.test(advisories_path)) {
24299
+ return {
24300
+ error: `advisories_path must be docs/<slug>/sdd/advisories.md: ${advisories_path}`,
24301
+ code: "advisory_path_invalid"
24302
+ };
24303
+ }
24304
+ const contained = resolveDocsPath({ workspace_root, path: advisories_path });
24305
+ if (!contained.ok)
24306
+ return { error: contained.error, code: "advisory_path_invalid" };
24307
+ const abs = contained.path;
24308
+ if (existsSync11(abs) && statSync6(abs).isDirectory()) {
24309
+ return {
24310
+ error: `advisory target is a directory: ${advisories_path}`,
24311
+ code: "advisory_target_invalid"
24312
+ };
24313
+ }
24314
+ mkdirSync6(path19.dirname(abs), { recursive: true });
24315
+ const line = `- Task ${task_id}: ${collapsed}
24316
+ `;
24317
+ appendFileSync2(abs, line, "utf8");
24318
+ const rel = posix2(path19.relative(contained.base, abs));
24319
+ return { ok: true, advisory: collapsed, advisories_path: rel };
24320
+ }
24117
24321
  var posix2 = (p) => p.split(path19.sep).join("/"), PROGRESS_RE;
24118
24322
  var init_sdd = __esm(() => {
24119
24323
  init_docs_validate();
@@ -24134,6 +24338,7 @@ import {
24134
24338
  fsyncSync,
24135
24339
  mkdirSync as mkdirSync7,
24136
24340
  openSync,
24341
+ readdirSync as readdirSync6,
24137
24342
  readFileSync as readFileSync13,
24138
24343
  renameSync,
24139
24344
  rmSync,
@@ -24146,16 +24351,20 @@ import path20 from "node:path";
24146
24351
 
24147
24352
  class HostReceiptStore {
24148
24353
  #bySession = new Map;
24149
- record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question) {
24150
- const label = selectedLabel.trim();
24151
- if (!label)
24354
+ record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question = "", purpose) {
24355
+ const trimmed = selectedLabel.trim();
24356
+ if (!trimmed)
24152
24357
  return;
24153
24358
  if (recordedAt > Date.now() + MAX_CLOCK_SKEW_MS)
24154
24359
  return;
24360
+ const derived = purpose ?? receiptPurposeForLabel(selectedLabel);
24361
+ if (derived === undefined)
24362
+ return;
24363
+ const q = question ?? "";
24155
24364
  const queue = this.#bySession.get(sessionId) ?? [];
24156
24365
  if (queue.length >= MAX_RECEIPTS_PER_SESSION)
24157
24366
  queue.shift();
24158
- queue.push({ sessionId, callID, selectedLabel: label, recordedAt, question });
24367
+ queue.push({ sessionId, callID, selectedLabel, recordedAt, question: q, purpose: derived });
24159
24368
  this.#bySession.set(sessionId, queue);
24160
24369
  }
24161
24370
  count(sessionId) {
@@ -24172,10 +24381,40 @@ class HostReceiptStore {
24172
24381
  if (!queue || queue.length === 0) {
24173
24382
  return err2("receipt_missing", "no host-observed native-question receipt for this session — ask the native " + "`question` tool and have the user answer before calling this tool");
24174
24383
  }
24175
- const index = queue.length - 1;
24384
+ let index = -1;
24385
+ if (opts.purpose !== undefined) {
24386
+ const top = queue[queue.length - 1];
24387
+ if (top && top.purpose === opts.purpose && isNegativeLabel(top.selectedLabel)) {
24388
+ const filtered = queue.filter((r) => r.purpose !== opts.purpose);
24389
+ if (filtered.length === 0)
24390
+ this.#bySession.delete(sessionId);
24391
+ else
24392
+ this.#bySession.set(sessionId, filtered);
24393
+ return err2("receipt_rejected", `the user's most recent answer (${JSON.stringify(top.selectedLabel)}) is a ` + "negative answer — it cannot authorize an approval; ask the native question again");
24394
+ }
24395
+ for (let i = queue.length - 1;i >= 0; i--) {
24396
+ if (queue[i].purpose === opts.purpose) {
24397
+ index = i;
24398
+ break;
24399
+ }
24400
+ }
24401
+ if (index === -1) {
24402
+ return err2("receipt_missing", `no host-observed receipt for purpose ${JSON.stringify(opts.purpose)} — ask the native question for that purpose`);
24403
+ }
24404
+ } else {
24405
+ index = queue.length - 1;
24406
+ }
24176
24407
  const receipt = queue[index];
24177
24408
  if (isNegativeLabel(receipt.selectedLabel)) {
24178
- this.#bySession.delete(sessionId);
24409
+ if (opts.purpose !== undefined) {
24410
+ const filtered = queue.filter((r) => r.purpose !== opts.purpose);
24411
+ if (filtered.length === 0)
24412
+ this.#bySession.delete(sessionId);
24413
+ else
24414
+ this.#bySession.set(sessionId, filtered);
24415
+ } else {
24416
+ this.#bySession.delete(sessionId);
24417
+ }
24179
24418
  return err2("receipt_rejected", `the user's most recent answer (${JSON.stringify(receipt.selectedLabel)}) is a ` + "negative answer — it cannot authorize an approval; ask the native question again");
24180
24419
  }
24181
24420
  if (opts.label !== undefined && !sameChoiceLabel(receipt.selectedLabel, opts.label)) {
@@ -24244,7 +24483,8 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24244
24483
  execution: {
24245
24484
  status: execution.status ?? "pending",
24246
24485
  mode: execution.mode ?? null,
24247
- evidence: execution.evidence ?? null
24486
+ evidence: execution.evidence ?? null,
24487
+ coordinator_session_id: execution.coordinator_session_id ?? null
24248
24488
  },
24249
24489
  handoff_destination: p.handoff_destination ?? false,
24250
24490
  updated_at: p.updated_at ?? Date.now()
@@ -24255,7 +24495,7 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24255
24495
  spec: { path: "", status: "draft", evidence: null, approved_digest: null },
24256
24496
  plan: { path: "", status: "draft", evidence: null, approved_digest: null },
24257
24497
  menu: { presented: false, chosen: "", evidence: null },
24258
- execution: { status: "pending", mode: null, evidence: null },
24498
+ execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
24259
24499
  handoff_destination: false,
24260
24500
  updated_at: Date.now()
24261
24501
  }), readFlowState = (root, slug) => {
@@ -24364,6 +24604,12 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24364
24604
  if (execRaw?.evidence !== undefined && !validateEvidenceValue(execRaw.evidence, true)) {
24365
24605
  return { ok: false, error: "flow state execution.evidence has an unsupported shape" };
24366
24606
  }
24607
+ if (execRaw?.coordinator_session_id !== undefined && execRaw.coordinator_session_id !== null && typeof execRaw.coordinator_session_id !== "string") {
24608
+ return {
24609
+ ok: false,
24610
+ error: "flow state execution.coordinator_session_id must be a string or null"
24611
+ };
24612
+ }
24367
24613
  return {
24368
24614
  ok: true,
24369
24615
  state: {
@@ -24379,7 +24625,8 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24379
24625
  execution: {
24380
24626
  status: execRaw?.status ?? "pending",
24381
24627
  mode: execRaw?.mode ?? null,
24382
- evidence: execRaw?.evidence ?? null
24628
+ evidence: execRaw?.evidence ?? null,
24629
+ coordinator_session_id: execRaw?.coordinator_session_id ?? null
24383
24630
  },
24384
24631
  handoff_destination: parsed.handoff_destination ?? false,
24385
24632
  updated_at: parsed.updated_at ?? Date.now()
@@ -24506,7 +24753,7 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24506
24753
  spec: { ...state.spec, status: "draft", evidence: null, approved_digest: null },
24507
24754
  plan: { ...state.plan, status: "draft", evidence: null, approved_digest: null },
24508
24755
  menu: { presented: false, chosen: "", evidence: null },
24509
- execution: { status: "pending", mode: null, evidence: null },
24756
+ execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
24510
24757
  handoff_destination: false,
24511
24758
  updated_at: Date.now()
24512
24759
  }), resetForPlanDrift = (state) => ({
@@ -24545,9 +24792,14 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24545
24792
  }, deriveLegacyExecution = (root, slug, state) => {
24546
24793
  const ledger = ledgerCompletion(root, slug);
24547
24794
  if (state.plan.status === "approved" && state.menu.chosen === "subagent-driven" && ledger.started && !ledger.complete) {
24548
- return { status: "active", mode: "subagent-driven", evidence: null };
24795
+ return {
24796
+ status: "active",
24797
+ mode: "subagent-driven",
24798
+ evidence: null,
24799
+ coordinator_session_id: null
24800
+ };
24549
24801
  }
24550
- return { status: "pending", mode: null, evidence: null };
24802
+ return { status: "pending", mode: null, evidence: null, coordinator_session_id: null };
24551
24803
  }, normalizeCompatibility = (root, slug, parsed, state) => {
24552
24804
  if (!isRecord(parsed) || !("execution" in parsed)) {
24553
24805
  const derived = deriveLegacyExecution(root, slug, state);
@@ -24555,6 +24807,11 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24555
24807
  if (derived.status !== current.status || derived.mode !== current.mode) {
24556
24808
  return { state: { ...state, execution: derived, updated_at: Date.now() }, changed: true };
24557
24809
  }
24810
+ return { state, changed: false };
24811
+ }
24812
+ const execRaw = isRecord(parsed.execution) ? parsed.execution : undefined;
24813
+ if (execRaw && !("coordinator_session_id" in execRaw)) {
24814
+ return { state: { ...state, updated_at: Date.now() }, changed: true };
24558
24815
  }
24559
24816
  return { state, changed: false };
24560
24817
  }, withFlowLock = (file, fn) => {
@@ -24684,14 +24941,6 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24684
24941
  return err2("workspace_mismatch", `mutation context workspace ${JSON.stringify(ctx.hostWorkspace)} does not match flow workspace ${JSON.stringify(root)}`);
24685
24942
  }
24686
24943
  return { ok: true };
24687
- }, assertCoordinatorBoundary = (ctx, state) => {
24688
- if (ctx?.role === "coordinator" && state.execution.status === "active" && state.execution.mode === "subagent-driven") {
24689
- return err2("coordinator_blocked", COORDINATOR_RECOVERY_TEXT);
24690
- }
24691
- if (ctx?.role === "delegated" && !ctx.taskIdentity) {
24692
- return err2("delegated_unauthenticated", "delegated mutations require an authenticated task identity (taskIdentity) — re-run inside the delegated worker session");
24693
- }
24694
- return { ok: true };
24695
24944
  }, MAX_CLOCK_SKEW_MS = 60000, MAX_RECEIPTS_PER_SESSION = 10, RECEIPT_FRESHNESS_MS, EVIDENCE_WINDOW_MS, NEGATIVE_ANSWER_LABELS, isNegativeLabel = (label) => {
24696
24945
  const normalized = label.trim().toLowerCase();
24697
24946
  return NEGATIVE_ANSWER_LABELS.some((entry) => {
@@ -24702,6 +24951,31 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24702
24951
  }
24703
24952
  return firstWord.replace(/[^a-z]/g, "") === entry;
24704
24953
  });
24954
+ }, receiptPurposeForLabel = (label) => {
24955
+ const n = normalizeLabel(label);
24956
+ if (n === "approve spec" || n === "approve spec recommended")
24957
+ return "spec-approval";
24958
+ if (n === "approve plan" || n === "approve plan recommended")
24959
+ return "plan-approval";
24960
+ if (n === "approve")
24961
+ return;
24962
+ if (n === "pause plan")
24963
+ return "plan-pause";
24964
+ if (n === "resume plan")
24965
+ return "plan-resume";
24966
+ if (n === "complete plan")
24967
+ return "plan-complete";
24968
+ const exec = new Set([
24969
+ "subagent driven",
24970
+ "inline",
24971
+ "handoff",
24972
+ "review spec",
24973
+ "review plan",
24974
+ "change model"
24975
+ ]);
24976
+ if (exec.has(n))
24977
+ return "execution-menu";
24978
+ return;
24705
24979
  }, sameChoiceLabel = (a, b) => normalizeLabel(a) === normalizeLabel(b), normalizeLabel = (s) => s.replace(/\s*\([^)]*\)/g, " ").replace(/\s*\bfirst\b\s*$/i, " ").replace(/[^a-z0-9]+/gi, " ").trim().toLowerCase(), createCursorConfirmation = () => ({
24706
24980
  ok: true,
24707
24981
  evidence: { host: "cursor", attested: false, confirmation: "contract" }
@@ -24806,7 +25080,7 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24806
25080
  spec: { path: specPath, status: "draft", evidence: null, approved_digest: null },
24807
25081
  plan: { path: planPath, status: "draft", evidence: null, approved_digest: null },
24808
25082
  menu: { presented: false, chosen: "", evidence: null },
24809
- execution: { status: "pending", mode: null, evidence: null },
25083
+ execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
24810
25084
  handoff_destination: false,
24811
25085
  updated_at: Date.now()
24812
25086
  });
@@ -24949,13 +25223,24 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
24949
25223
  return err2("recursive_handoff", "this flow is already a handoff destination — a second handoff is rejected");
24950
25224
  }
24951
25225
  const executing = choice === "subagent-driven" || choice === "inline";
25226
+ const coordinatorSessionId = choice === "subagent-driven" && recorded.evidence.host === "opencode" ? ctx?.sessionId ?? null : null;
24952
25227
  return {
24953
25228
  ok: true,
24954
25229
  next: {
24955
25230
  ...state,
24956
25231
  plan: { ...state.plan, path: state.plan.path || `docs/${slug}/plan.md` },
24957
25232
  menu: { presented: true, chosen: choice, evidence: recorded.evidence },
24958
- execution: executing ? { status: "active", mode: choice, evidence: recorded.evidence } : { status: "pending", mode: null, evidence: recorded.evidence },
25233
+ execution: executing ? {
25234
+ status: "active",
25235
+ mode: choice,
25236
+ evidence: recorded.evidence,
25237
+ coordinator_session_id: coordinatorSessionId
25238
+ } : {
25239
+ status: "pending",
25240
+ mode: null,
25241
+ evidence: recorded.evidence,
25242
+ coordinator_session_id: null
25243
+ },
24959
25244
  updated_at: Date.now()
24960
25245
  }
24961
25246
  };
@@ -25043,7 +25328,7 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
25043
25328
  }
25044
25329
  const next = {
25045
25330
  ...reconciled.state,
25046
- execution: { ...exec, status: "completed" },
25331
+ execution: { ...exec, status: "completed", coordinator_session_id: null },
25047
25332
  handoff_destination: false,
25048
25333
  updated_at: Date.now()
25049
25334
  };
@@ -25103,7 +25388,7 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
25103
25388
  }, slugFromSddPath = (p) => {
25104
25389
  const match = p.split(path20.sep).join("/").match(/^docs\/([^/]+)\/sdd(\/|$|['"])/);
25105
25390
  return match?.[1] ?? "";
25106
- }, assertProductGates = (root, slug, opts = {}, ctx) => {
25391
+ }, assertSddControlGates = (root, slug, opts = {}, ctx) => {
25107
25392
  const bound = assertMutationWorkspace(root, ctx);
25108
25393
  if (!bound.ok)
25109
25394
  return bound;
@@ -25129,15 +25414,18 @@ var COORDINATOR_RECOVERY_TEXT, CURSOR_SUBAGENT_UNSUPPORTED_TEXT, MENU_CHOICES, e
25129
25414
  if (validated.ok === false)
25130
25415
  return err2("docs_invalid", validated.error);
25131
25416
  }
25132
- return assertCoordinatorBoundary(ctx, state);
25133
- }, BASH_READ_TOKENS, BASH_GIT_READ_SUBCOMMANDS, BASH_GIT_MUTABLE_SUBCOMMANDS, BASH_GIT_READ_FLAGS, BASH_FIND_DENIED_FLAGS, BASH_TEST_VERBS, BASH_DENIED_HEADS, BASH_OUTPUT_FLAG_VERBS, BASH_PAREN_EXEMPT_HEADS, BASH_GIT_VALUE_FLAGS, COORDINATOR_SHELL_DENIED_TEXT;
25417
+ if (state.execution.status === "active" && state.execution.mode === "subagent-driven" && roleFromParentage(ctx?.parentSessionId, state.execution.coordinator_session_id) === "delegated") {
25418
+ return err2("sdd_control_denied", "SDD control metadata is coordinator-owned while a subagent-driven plan is active — delegated workers cannot mutate task briefs, review packages, progress, or advisories");
25419
+ }
25420
+ return { ok: true };
25421
+ }, roleFromParentage = (parentID, coordinatorSessionId) => typeof parentID === "string" && parentID !== "" && parentID === coordinatorSessionId ? "delegated" : "coordinator", BASH_READ_TOKENS, BASH_GIT_READ_SUBCOMMANDS, BASH_GIT_MUTABLE_SUBCOMMANDS, BASH_GIT_READ_FLAGS, BASH_FIND_DENIED_FLAGS, BASH_TEST_VERBS, BASH_DENIED_HEADS, BASH_OUTPUT_FLAG_VERBS, BASH_PAREN_EXEMPT_HEADS, BASH_GIT_VALUE_FLAGS, COORDINATOR_SHELL_DENIED_TEXT;
25134
25422
  var init_flow_state = __esm(() => {
25135
25423
  init_docs_validate();
25136
25424
  init_docs_layout();
25137
25425
  init_sdd();
25138
25426
  init_verify_project();
25139
25427
  init_menu();
25140
- COORDINATOR_RECOVERY_TEXT = "A subagent-driven plan is active: coordinator product edits are blocked. " + "Delegate product mutations (task briefs, progress, review packages) to an " + "authenticated delegated worker via `task` / `wk-implement` instead of " + "editing in the coordinator session.";
25428
+ COORDINATOR_RECOVERY_TEXT = "A subagent-driven plan is active: coordinator product edits are blocked. " + "Delegate product mutations to an authenticated delegated worker via `task` / `wk-implement` instead of " + "editing in the coordinator session.";
25141
25429
  CURSOR_SUBAGENT_UNSUPPORTED_TEXT = "Cursor cannot execute subagent-driven plans: the MCP has no child-session " + "support. Choose Inline, Handoff, or a review option in this session, or " + "run the plan in OpenCode with `wk-implement`.";
25142
25430
  MENU_CHOICES = [
25143
25431
  "subagent-driven",
@@ -25308,7 +25596,7 @@ import {
25308
25596
  existsSync as existsSync13,
25309
25597
  lstatSync as lstatSync2,
25310
25598
  mkdirSync as mkdirSync8,
25311
- readdirSync as readdirSync6,
25599
+ readdirSync as readdirSync7,
25312
25600
  readFileSync as readFileSync14,
25313
25601
  realpathSync as realpathSync3,
25314
25602
  renameSync as renameSync2,
@@ -25353,7 +25641,7 @@ var LEGACY_DIR = "docs/superpowers", MIGRATION_CHOICES, SLUG_RE3, RESERVED_SLUG
25353
25641
  const root = legacyRoot(workspace_root);
25354
25642
  const raw = [];
25355
25643
  if (existsSync13(root)) {
25356
- for (const entry of readdirSync6(root, { withFileTypes: true })) {
25644
+ for (const entry of readdirSync7(root, { withFileTypes: true })) {
25357
25645
  const abs = path21.join(root, entry.name);
25358
25646
  const rel = posix3(path21.relative(workspace_root, abs));
25359
25647
  if (entry.isDirectory()) {
@@ -25487,7 +25775,7 @@ var LEGACY_DIR = "docs/superpowers", MIGRATION_CHOICES, SLUG_RE3, RESERVED_SLUG
25487
25775
  if (visits.has(real))
25488
25776
  return { ok: true };
25489
25777
  visits.add(real);
25490
- for (const entry of readdirSync6(real, { withFileTypes: true })) {
25778
+ for (const entry of readdirSync7(real, { withFileTypes: true })) {
25491
25779
  const fromAbs = path21.join(real, entry.name);
25492
25780
  const fromRel = posix3(path21.join(legacyRel, entry.name));
25493
25781
  const toRel = posix3(path21.join(destRelRoot, entry.name));
@@ -25676,7 +25964,7 @@ var init_docs_migration = __esm(() => {
25676
25964
  });
25677
25965
 
25678
25966
  // packages/workit-core/src/core/docs-repo.ts
25679
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8, readdirSync as readdirSync7 } from "node:fs";
25967
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8, readdirSync as readdirSync8 } from "node:fs";
25680
25968
  import { execFileSync as execFileSync6 } from "node:child_process";
25681
25969
  import path22 from "node:path";
25682
25970
  var configPath = () => process.env.WORKFLOW_DOCS_REPO_CONFIG ?? path22.join(configDir(), "docs-repo.json"), readDocsRepoConfig = () => {
@@ -25716,7 +26004,7 @@ var configPath = () => process.env.WORKFLOW_DOCS_REPO_CONFIG ?? path22.join(conf
25716
26004
  const specs = [];
25717
26005
  const docsDir = path22.join(workspaceRoot, "docs");
25718
26006
  if (existsSync14(docsDir)) {
25719
- for (const slug of readdirSync7(docsDir)) {
26007
+ for (const slug of readdirSync8(docsDir)) {
25720
26008
  if (slug.startsWith("."))
25721
26009
  continue;
25722
26010
  const spec = path22.posix.join("docs", slug, "spec.md");
@@ -25727,7 +26015,7 @@ var configPath = () => process.env.WORKFLOW_DOCS_REPO_CONFIG ?? path22.join(conf
25727
26015
  if (repoPath) {
25728
26016
  const featuresDir = path22.join(repoPath, "features");
25729
26017
  if (existsSync14(featuresDir)) {
25730
- const match = readdirSync7(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d));
26018
+ const match = readdirSync8(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d));
25731
26019
  if (match) {
25732
26020
  promoted = true;
25733
26021
  target = path22.join(repoPath, "features", match);
@@ -25814,7 +26102,7 @@ var configPath = () => process.env.WORKFLOW_DOCS_REPO_CONFIG ?? path22.join(conf
25814
26102
  }
25815
26103
  const prefix = monthPrefix();
25816
26104
  const featuresDir = path22.join(repoPath, "features");
25817
- const existing = existsSync14(featuresDir) ? readdirSync7(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d)) : undefined;
26105
+ const existing = existsSync14(featuresDir) ? readdirSync8(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d)) : undefined;
25818
26106
  const targetDir = path22.join(featuresDir, existing ?? `${prefix}-${slug}`);
25819
26107
  mkdirSync9(targetDir, { recursive: true });
25820
26108
  const files = ["spec.md"];
@@ -25959,7 +26247,7 @@ var init_templates = __esm(() => {
25959
26247
  });
25960
26248
 
25961
26249
  // packages/workit-core/src/core/rules.ts
25962
- import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync18, readdirSync as readdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
26250
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync18, readdirSync as readdirSync9, writeFileSync as writeFileSync11 } from "node:fs";
25963
26251
  import path25 from "node:path";
25964
26252
  var rulesDir = () => path25.join(configDir(), "rules"), parseRule = (markdown) => {
25965
26253
  const fm = markdown.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -25989,7 +26277,7 @@ var rulesDir = () => path25.join(configDir(), "rules"), parseRule = (markdown) =
25989
26277
  const result = [];
25990
26278
  const dir = rulesDir();
25991
26279
  if (existsSync17(dir)) {
25992
- for (const entry of readdirSync8(dir)) {
26280
+ for (const entry of readdirSync9(dir)) {
25993
26281
  const file = path25.join(dir, entry, "rule.md");
25994
26282
  if (!existsSync17(file))
25995
26283
  continue;
@@ -26112,7 +26400,7 @@ import {
26112
26400
  mkdirSync as mkdirSync12,
26113
26401
  mkdtempSync,
26114
26402
  readFileSync as readFileSync19,
26115
- readdirSync as readdirSync9,
26403
+ readdirSync as readdirSync10,
26116
26404
  renameSync as renameSync3,
26117
26405
  rmSync as rmSync3,
26118
26406
  statSync as statSync9,
@@ -27742,7 +28030,7 @@ var init_server3 = __esm(async () => {
27742
28030
  const slug = slugFromSddPath(sdd_dir);
27743
28031
  if (!slug)
27744
28032
  return jsonResult({ error: "could not derive slug — expected docs/<slug>/sdd/..." });
27745
- const gate = assertProductGates(workspace_root, slug, { requireMenu: true, requireDocs: true }, cursorMutationContext(workspace_root));
28033
+ const gate = assertSddControlGates(workspace_root, slug, { requireMenu: true, requireDocs: true }, cursorMutationContext(workspace_root));
27746
28034
  if (!gate.ok)
27747
28035
  return jsonResult({ error: gate.error, code: gate.code });
27748
28036
  const data = sddTaskBrief({
@@ -27765,7 +28053,7 @@ var init_server3 = __esm(async () => {
27765
28053
  const slug = slugFromSddPath(sdd_dir);
27766
28054
  if (!slug)
27767
28055
  return jsonResult({ error: "could not derive slug — expected docs/<slug>/sdd/..." });
27768
- const gate = assertProductGates(workspace_root, slug, { requireMenu: true, requireDocs: true }, cursorMutationContext(workspace_root));
28056
+ const gate = assertSddControlGates(workspace_root, slug, { requireMenu: true, requireDocs: true }, cursorMutationContext(workspace_root));
27769
28057
  if (!gate.ok)
27770
28058
  return jsonResult({ error: gate.error, code: gate.code });
27771
28059
  const data = sddReviewPackage({
@@ -27789,7 +28077,7 @@ var init_server3 = __esm(async () => {
27789
28077
  const slug = slugFromSddPath(progress_path);
27790
28078
  if (!slug)
27791
28079
  return jsonResult({ error: "could not derive slug — expected docs/<slug>/sdd/..." });
27792
- const gate = assertProductGates(workspace_root, slug, { requireMenu: true, requireDocs: true }, cursorMutationContext(workspace_root));
28080
+ const gate = assertSddControlGates(workspace_root, slug, { requireMenu: true, requireDocs: true }, cursorMutationContext(workspace_root));
27793
28081
  if (!gate.ok)
27794
28082
  return jsonResult({ error: gate.error, code: gate.code });
27795
28083
  const data = sddAppendProgress({ progress_path, line, workspace_root });
@@ -27797,6 +28085,26 @@ var init_server3 = __esm(async () => {
27797
28085
  return jsonResult({ error: data.error });
27798
28086
  return jsonResult(withWorkspace(workspace_root, data));
27799
28087
  });
28088
+ registerTool("workflow_sdd_append_advisory", {
28089
+ description: "Append a validated advisory line to docs/<slug>/sdd/advisories.md (coordinator-owned)",
28090
+ inputSchema: {
28091
+ advisories_path: exports_external.string(),
28092
+ task_id: exports_external.number(),
28093
+ text: exports_external.string(),
28094
+ workspace_root: workspaceRootSchema
28095
+ }
28096
+ }, async ({ advisories_path, task_id, text, workspace_root }) => {
28097
+ const slug = slugFromSddPath(advisories_path);
28098
+ if (!slug)
28099
+ return jsonResult({ error: "could not derive slug — expected docs/<slug>/sdd/..." });
28100
+ const gate = assertSddControlGates(workspace_root, slug, { requireMenu: true, requireDocs: true }, cursorMutationContext(workspace_root));
28101
+ if (!gate.ok)
28102
+ return jsonResult({ error: gate.error, code: gate.code });
28103
+ const data = sddAppendAdvisory({ advisories_path, task_id, text, workspace_root });
28104
+ if ("error" in data)
28105
+ return jsonResult({ error: data.error, code: data.code });
28106
+ return jsonResult(withWorkspace(workspace_root, data));
28107
+ });
27800
28108
  registerTool("workflow_docs_branch", {
27801
28109
  description: "Resolve branch for spec/plan authors: keep current feature|bugfix or create from the configured base.",
27802
28110
  inputSchema: {