syntaur 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/README.md +64 -24
  3. package/dist/index.js +632 -254
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -8
  6. package/platforms/claude-code/.claude-plugin/plugin.json +15 -2
  7. package/{vendor/syntaur-skills → platforms/claude-code}/skills/complete-assignment/SKILL.md +0 -2
  8. package/{vendor/syntaur-skills → platforms/claude-code}/skills/grab-assignment/SKILL.md +2 -7
  9. package/{vendor/syntaur-skills → platforms/claude-code}/skills/plan-assignment/SKILL.md +1 -3
  10. package/{vendor/syntaur-skills → platforms/claude-code}/skills/syntaur-protocol/SKILL.md +4 -23
  11. package/{vendor/syntaur-skills → platforms/claude-code}/skills/syntaur-protocol/references/file-ownership.md +1 -2
  12. package/{vendor/syntaur-skills → platforms/claude-code}/skills/syntaur-protocol/references/protocol-summary.md +1 -6
  13. package/platforms/claude-code/skills/track-server/SKILL.md +49 -0
  14. package/platforms/codex/.codex-plugin/plugin.json +2 -2
  15. package/platforms/codex/skills/clear-assignment/SKILL.md +111 -0
  16. package/platforms/codex/skills/complete-assignment/SKILL.md +146 -0
  17. package/platforms/codex/skills/create-assignment/SKILL.md +73 -0
  18. package/platforms/codex/skills/create-project/SKILL.md +56 -0
  19. package/platforms/codex/skills/grab-assignment/SKILL.md +158 -0
  20. package/platforms/codex/skills/manage-statuses/SKILL.md +72 -0
  21. package/platforms/codex/skills/plan-assignment/SKILL.md +137 -0
  22. package/platforms/codex/skills/save-session-summary/SKILL.md +113 -0
  23. package/platforms/codex/skills/syntaur-protocol/SKILL.md +119 -0
  24. package/platforms/codex/skills/syntaur-protocol/references/file-ownership.md +67 -0
  25. package/platforms/codex/skills/syntaur-protocol/references/protocol-summary.md +82 -0
  26. package/platforms/codex/skills/track-server/SKILL.md +49 -0
  27. package/platforms/codex/skills/track-session/SKILL.md +63 -26
  28. package/skills/clear-assignment/SKILL.md +111 -0
  29. package/skills/complete-assignment/SKILL.md +146 -0
  30. package/skills/create-assignment/SKILL.md +73 -0
  31. package/skills/create-project/SKILL.md +56 -0
  32. package/skills/grab-assignment/SKILL.md +158 -0
  33. package/skills/manage-statuses/SKILL.md +72 -0
  34. package/skills/plan-assignment/SKILL.md +137 -0
  35. package/skills/save-session-summary/SKILL.md +113 -0
  36. package/skills/syntaur-protocol/SKILL.md +119 -0
  37. package/skills/syntaur-protocol/references/file-ownership.md +67 -0
  38. package/skills/syntaur-protocol/references/protocol-summary.md +82 -0
  39. package/skills/track-server/SKILL.md +49 -0
  40. package/skills/track-session/SKILL.md +86 -0
  41. package/scripts/postinstall-submodules.mjs +0 -40
  42. package/vendor/syntaur-skills/LICENSE +0 -21
  43. package/vendor/syntaur-skills/README.md +0 -57
  44. /package/{vendor/syntaur-skills → platforms/claude-code}/skills/clear-assignment/SKILL.md +0 -0
  45. /package/{vendor/syntaur-skills → platforms/claude-code}/skills/create-assignment/SKILL.md +0 -0
  46. /package/{vendor/syntaur-skills → platforms/claude-code}/skills/create-project/SKILL.md +0 -0
  47. /package/{vendor/syntaur-skills → platforms/claude-code}/skills/manage-statuses/SKILL.md +0 -0
  48. /package/{vendor/syntaur-skills → platforms/claude-code}/skills/save-session-summary/SKILL.md +0 -0
package/dist/index.js CHANGED
@@ -6706,7 +6706,7 @@ __export(launch_exports, {
6706
6706
  });
6707
6707
  import { spawn as spawn2 } from "child_process";
6708
6708
  import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
6709
- import { isAbsolute as isAbsolute3, resolve as resolve32 } from "path";
6709
+ import { isAbsolute as isAbsolute3, resolve as resolve33 } from "path";
6710
6710
  function formatFallbackCwdWarning(opts) {
6711
6711
  const missing = [];
6712
6712
  if (!opts.worktreePath) missing.push("worktreePath");
@@ -6750,8 +6750,8 @@ async function launchAgent(options) {
6750
6750
  console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
6751
6751
  process.exit(1);
6752
6752
  }
6753
- const projectDir = resolve32(projectsDir2, projectSlug);
6754
- const assignmentDir = resolve32(projectDir, "assignments", assignmentSlug);
6753
+ const projectDir = resolve33(projectsDir2, projectSlug);
6754
+ const assignmentDir = resolve33(projectDir, "assignments", assignmentSlug);
6755
6755
  const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
6756
6756
  const workspaceDir = resolvedFromWorkspace ?? process.cwd();
6757
6757
  if (!cwdOverride) {
@@ -6763,7 +6763,7 @@ async function launchAgent(options) {
6763
6763
  });
6764
6764
  if (warning) console.warn(warning);
6765
6765
  }
6766
- const contextDir = resolve32(workspaceDir, ".syntaur");
6766
+ const contextDir = resolve33(workspaceDir, ".syntaur");
6767
6767
  await mkdir6(contextDir, { recursive: true });
6768
6768
  const context = {
6769
6769
  projectSlug,
@@ -6776,7 +6776,7 @@ async function launchAgent(options) {
6776
6776
  grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
6777
6777
  };
6778
6778
  await writeFile9(
6779
- resolve32(contextDir, "context.json"),
6779
+ resolve33(contextDir, "context.json"),
6780
6780
  JSON.stringify(context, null, 2) + "\n"
6781
6781
  );
6782
6782
  const { argv, shellFallbackWarning } = buildAgentArgv(
@@ -6835,7 +6835,7 @@ __export(git_worktree_exports, {
6835
6835
  removeWorktree: () => removeWorktree
6836
6836
  });
6837
6837
  import { spawn as spawn3 } from "child_process";
6838
- import { readFile as readFile20 } from "fs/promises";
6838
+ import { readFile as readFile21 } from "fs/promises";
6839
6839
  function run(command, args, cwd) {
6840
6840
  return new Promise((resolvePromise) => {
6841
6841
  const child = spawn3(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -6879,7 +6879,7 @@ async function createWorktreeAndRecord(opts) {
6879
6879
  const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
6880
6880
  await createWorktree({ repository, branch, worktreePath, parentBranch });
6881
6881
  try {
6882
- const content = await readFile20(assignmentPath, "utf-8");
6882
+ const content = await readFile21(assignmentPath, "utf-8");
6883
6883
  const updated = updateAssignmentWorkspace(content, {
6884
6884
  repository,
6885
6885
  worktreePath,
@@ -7263,8 +7263,8 @@ async function migrateFromMarkdown(projectsDir2) {
7263
7263
  return allSessions.length;
7264
7264
  }
7265
7265
  async function parseMarkdownSessionsIndex(filePath, projectSlug) {
7266
- const { readFile: readFile32 } = await import("fs/promises");
7267
- const raw = await readFile32(filePath, "utf-8");
7266
+ const { readFile: readFile35 } = await import("fs/promises");
7267
+ const raw = await readFile35(filePath, "utf-8");
7268
7268
  const sessions = [];
7269
7269
  const lines = raw.split("\n");
7270
7270
  let inTable = false;
@@ -9736,8 +9736,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
9736
9736
  router.post("/:workspace/archive", async (req, res) => {
9737
9737
  try {
9738
9738
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
9739
- const { resolve: resolve47 } = await import("path");
9740
- const { readFile: readFile32 } = await import("fs/promises");
9739
+ const { resolve: resolve50 } = await import("path");
9740
+ const { readFile: readFile35 } = await import("fs/promises");
9741
9741
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
9742
9742
  const workspace = getWorkspaceParam(req.params.workspace);
9743
9743
  const checklist = await readChecklist(todosDir2, workspace);
@@ -9753,10 +9753,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
9753
9753
  (e) => e.itemIds.every((id) => completedIds.has(id))
9754
9754
  );
9755
9755
  const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
9756
- await ensureDir(resolve47(todosDir2, "archive"));
9756
+ await ensureDir(resolve50(todosDir2, "archive"));
9757
9757
  let archContent = "";
9758
9758
  if (await fileExists(archFile)) {
9759
- archContent = await readFile32(archFile, "utf-8");
9759
+ archContent = await readFile35(archFile, "utf-8");
9760
9760
  archContent = archContent.trimEnd() + "\n\n";
9761
9761
  } else {
9762
9762
  archContent = `---
@@ -10015,7 +10015,7 @@ workspace: ${workspace}
10015
10015
  const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
10016
10016
  const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
10017
10017
  const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
10018
- const { readFile: readFile32 } = await import("fs/promises");
10018
+ const { readFile: readFile35 } = await import("fs/promises");
10019
10019
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
10020
10020
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
10021
10021
  let assignmentRef;
@@ -10053,7 +10053,7 @@ workspace: ${workspace}
10053
10053
  if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
10054
10054
  }
10055
10055
  const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
10056
- let content = await readFile32(assignmentMdPath, "utf-8");
10056
+ let content = await readFile35(assignmentMdPath, "utf-8");
10057
10057
  content = appendTodosToAssignmentBody2(
10058
10058
  content,
10059
10059
  items.map((it) => ({
@@ -12447,6 +12447,7 @@ import {
12447
12447
  lstat,
12448
12448
  readFile as readFile16,
12449
12449
  readlink,
12450
+ rename as rename6,
12450
12451
  rm as rm3,
12451
12452
  unlink as unlink5,
12452
12453
  writeFile as writeFile6
@@ -12653,8 +12654,29 @@ async function readClaudeMarketplaceFile(manifestPath) {
12653
12654
  }
12654
12655
  async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
12655
12656
  await ensureDir(dirname8(manifestPath));
12656
- await writeFile6(manifestPath, `${JSON.stringify(marketplace, null, 2)}
12657
+ if (Array.isArray(marketplace.plugins)) {
12658
+ marketplace.plugins = [...marketplace.plugins].sort((a, b) => {
12659
+ const an = a?.name ?? "";
12660
+ const bn = b?.name ?? "";
12661
+ return an.localeCompare(bn);
12662
+ });
12663
+ }
12664
+ if (await fileExists(manifestPath)) {
12665
+ try {
12666
+ const prev = await readFile16(manifestPath, "utf-8");
12667
+ JSON.parse(prev);
12668
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
12669
+ await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
12670
+ } catch {
12671
+ throw new Error(
12672
+ `Refusing to overwrite ${manifestPath}: existing file is not valid JSON. Inspect and remove or repair it manually before re-running.`
12673
+ );
12674
+ }
12675
+ }
12676
+ const tmpPath = `${manifestPath}.tmp`;
12677
+ await writeFile6(tmpPath, `${JSON.stringify(marketplace, null, 2)}
12657
12678
  `, "utf-8");
12679
+ await rename6(tmpPath, manifestPath);
12658
12680
  }
12659
12681
  function buildClaudeMarketplaceSourcePath(pluginTargetDir, marketplaceRootDir) {
12660
12682
  const relPath = relative2(marketplaceRootDir, pluginTargetDir).replaceAll("\\", "/");
@@ -12787,11 +12809,25 @@ async function getPreferredClaudeMarketplace() {
12787
12809
  }
12788
12810
  async function registerKnownClaudeMarketplace(name, rootDir) {
12789
12811
  const manifestPath = getClaudeKnownMarketplacesPath();
12790
- const existing = await readJsonFileIfExists(
12791
- manifestPath
12792
- ) ?? {};
12793
- if (existing[name]?.installLocation === rootDir) {
12794
- return;
12812
+ let existing = {};
12813
+ if (await fileExists(manifestPath)) {
12814
+ const raw = await readFile16(manifestPath, "utf-8");
12815
+ try {
12816
+ const parsed = JSON.parse(raw);
12817
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
12818
+ existing = parsed;
12819
+ } else {
12820
+ throw new Error("not a JSON object");
12821
+ }
12822
+ } catch (err2) {
12823
+ throw new Error(
12824
+ `Refusing to update ${manifestPath}: existing file is not a valid JSON object (${err2 instanceof Error ? err2.message : String(err2)}). Inspect and repair (or delete) it before re-running install-plugin.`
12825
+ );
12826
+ }
12827
+ }
12828
+ const had = Object.prototype.hasOwnProperty.call(existing, name);
12829
+ if (had && existing[name]?.installLocation === rootDir) {
12830
+ return { added: false, updated: false };
12795
12831
  }
12796
12832
  existing[name] = {
12797
12833
  ...existing[name] ?? {},
@@ -12801,8 +12837,52 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
12801
12837
  existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
12802
12838
  existing[name].autoUpdate = true;
12803
12839
  await ensureDir(dirname8(manifestPath));
12804
- await writeFile6(manifestPath, `${JSON.stringify(existing, null, 2)}
12840
+ if (await fileExists(manifestPath)) {
12841
+ const prev = await readFile16(manifestPath, "utf-8");
12842
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
12843
+ await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
12844
+ }
12845
+ const tmpPath = `${manifestPath}.tmp`;
12846
+ await writeFile6(tmpPath, `${JSON.stringify(existing, null, 2)}
12847
+ `, "utf-8");
12848
+ await rename6(tmpPath, manifestPath);
12849
+ return { added: !had, updated: had };
12850
+ }
12851
+ async function ensureKnownClaudeMarketplaceForRoot(options) {
12852
+ return registerKnownClaudeMarketplace(options.name, options.rootDir);
12853
+ }
12854
+ async function setSyntaurPluginEnabled(options) {
12855
+ const settingsPath = resolve25(homedir2(), ".claude", "settings.json");
12856
+ const key = `syntaur@${options.marketplaceName}`;
12857
+ let parsed = {};
12858
+ if (await fileExists(settingsPath)) {
12859
+ const raw = await readFile16(settingsPath, "utf-8");
12860
+ try {
12861
+ parsed = JSON.parse(raw);
12862
+ } catch {
12863
+ throw new Error(
12864
+ `Cannot toggle plugin: ${settingsPath} is not valid JSON. Inspect and repair it before retrying.`
12865
+ );
12866
+ }
12867
+ }
12868
+ const enabledPlugins = parsed.enabledPlugins ?? {};
12869
+ const previous = typeof enabledPlugins[key] === "boolean" ? enabledPlugins[key] : void 0;
12870
+ if (previous === options.enabled) {
12871
+ return { key, previous, current: options.enabled, changed: false };
12872
+ }
12873
+ enabledPlugins[key] = options.enabled;
12874
+ parsed.enabledPlugins = enabledPlugins;
12875
+ await ensureDir(dirname8(settingsPath));
12876
+ if (await fileExists(settingsPath)) {
12877
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
12878
+ const prev = await readFile16(settingsPath, "utf-8");
12879
+ await writeFile6(`${settingsPath}.bak-${stamp}`, prev, "utf-8");
12880
+ }
12881
+ const tmpPath = `${settingsPath}.tmp`;
12882
+ await writeFile6(tmpPath, `${JSON.stringify(parsed, null, 2)}
12805
12883
  `, "utf-8");
12884
+ await rename6(tmpPath, settingsPath);
12885
+ return { key, previous, current: options.enabled, changed: true };
12806
12886
  }
12807
12887
  async function ensureClaudeUserMarketplace() {
12808
12888
  const existing = await getPreferredClaudeMarketplace();
@@ -13269,31 +13349,63 @@ async function textPrompt(question, defaultValue) {
13269
13349
 
13270
13350
  // src/utils/install-skills.ts
13271
13351
  init_fs();
13272
- import { readFile as readFile17, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4 } from "fs/promises";
13273
- import { dirname as dirname9, resolve as resolve26, relative as relative3, join as join3 } from "path";
13274
- import { fileURLToPath as fileURLToPath4 } from "url";
13352
+ import { readFile as readFile18, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
13353
+ import { resolve as resolve27, relative as relative3, join as join3 } from "path";
13354
+ import { homedir as homedir4 } from "os";
13355
+
13356
+ // src/utils/plugin-state.ts
13357
+ init_fs();
13358
+ import { readFile as readFile17 } from "fs/promises";
13359
+ import { resolve as resolve26 } from "path";
13275
13360
  import { homedir as homedir3 } from "os";
13276
- var REQUIRED_SKILLS = [
13361
+ function settingsPathFor(agent) {
13362
+ if (agent === "claude") return resolve26(homedir3(), ".claude", "settings.json");
13363
+ return null;
13364
+ }
13365
+ async function readJsonOrNull(path) {
13366
+ if (!path) return null;
13367
+ if (!await fileExists(path)) return null;
13368
+ try {
13369
+ const raw = await readFile17(path, "utf-8");
13370
+ return JSON.parse(raw);
13371
+ } catch {
13372
+ return null;
13373
+ }
13374
+ }
13375
+ async function isSyntaurPluginEnabledFor(agent) {
13376
+ const settings = await readJsonOrNull(settingsPathFor(agent));
13377
+ const enabled = settings?.enabledPlugins ?? {};
13378
+ for (const [key, value] of Object.entries(enabled)) {
13379
+ if (value !== true) continue;
13380
+ const atIndex = key.lastIndexOf("@");
13381
+ const pluginName = atIndex > 0 ? key.slice(0, atIndex) : key;
13382
+ if (pluginName === "syntaur") return true;
13383
+ }
13384
+ return false;
13385
+ }
13386
+
13387
+ // src/utils/install-skills.ts
13388
+ var KNOWN_SKILL_NAMES = [
13277
13389
  "syntaur-protocol",
13278
13390
  "grab-assignment",
13279
13391
  "plan-assignment",
13280
13392
  "complete-assignment",
13281
13393
  "create-assignment",
13282
13394
  "create-project",
13283
- "save-session-summary"
13395
+ "manage-statuses",
13396
+ "clear-assignment",
13397
+ "save-session-summary",
13398
+ "track-session",
13399
+ "track-server"
13284
13400
  ];
13285
- function getVendoredSkillsDir() {
13286
- const here = dirname9(fileURLToPath4(import.meta.url));
13287
- return resolve26(here, "..", "vendor", "syntaur-skills", "skills");
13288
- }
13289
- function getPlatformSkillsDir(target) {
13290
- const here = dirname9(fileURLToPath4(import.meta.url));
13291
- const kind = target === "claude" ? "claude-code" : "codex";
13292
- return resolve26(here, "..", "platforms", kind, "skills");
13401
+ var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
13402
+ async function getSkillsDir() {
13403
+ const packageRoot = await findPackageRoot("skills");
13404
+ return resolve27(packageRoot, "skills");
13293
13405
  }
13294
13406
  function defaultSkillTargetDir(target) {
13295
- if (target === "claude") return resolve26(homedir3(), ".claude", "skills");
13296
- return resolve26(homedir3(), ".codex", "skills");
13407
+ if (target === "claude") return resolve27(homedir4(), ".claude", "skills");
13408
+ return resolve27(homedir4(), ".codex", "skills");
13297
13409
  }
13298
13410
  async function walkFiles(root) {
13299
13411
  const out = [];
@@ -13313,7 +13425,7 @@ async function walkFiles(root) {
13313
13425
  }
13314
13426
  async function filesEqual(a, b) {
13315
13427
  try {
13316
- const [ba, bb] = await Promise.all([readFile17(a), readFile17(b)]);
13428
+ const [ba, bb] = await Promise.all([readFile18(a), readFile18(b)]);
13317
13429
  if (ba.length !== bb.length) return false;
13318
13430
  return ba.equals(bb);
13319
13431
  } catch {
@@ -13345,70 +13457,97 @@ async function skillMatches(srcDir, destDir) {
13345
13457
  if (destFiles.length !== srcFiles.length) return false;
13346
13458
  return true;
13347
13459
  }
13348
- async function installSkillDir(srcDir, destDir, skillName, source, force) {
13460
+ async function isSymlink(path) {
13461
+ try {
13462
+ const stat6 = await lstat2(path);
13463
+ return stat6.isSymbolicLink();
13464
+ } catch {
13465
+ return false;
13466
+ }
13467
+ }
13468
+ async function installSkillDir(srcDir, destDir, skillName, force) {
13469
+ if (await isSymlink(destDir)) {
13470
+ return {
13471
+ skill: skillName,
13472
+ status: "skipped-symlink",
13473
+ targetPath: destDir
13474
+ };
13475
+ }
13349
13476
  if (!await fileExists(destDir)) {
13350
13477
  await copyDir(srcDir, destDir);
13351
- return { skill: skillName, status: "installed", targetPath: destDir, source };
13478
+ return { skill: skillName, status: "installed", targetPath: destDir };
13352
13479
  }
13353
13480
  if (await skillMatches(srcDir, destDir)) {
13354
- return { skill: skillName, status: "already-current", targetPath: destDir, source };
13481
+ return { skill: skillName, status: "already-current", targetPath: destDir };
13355
13482
  }
13356
13483
  if (force) {
13357
13484
  await rm4(destDir, { recursive: true, force: true });
13358
13485
  await copyDir(srcDir, destDir);
13359
- return { skill: skillName, status: "overwritten", targetPath: destDir, source };
13486
+ return { skill: skillName, status: "overwritten", targetPath: destDir };
13360
13487
  }
13361
- return { skill: skillName, status: "differs-preserved", targetPath: destDir, source };
13488
+ return { skill: skillName, status: "differs-preserved", targetPath: destDir };
13362
13489
  }
13363
- async function installSkills(options) {
13364
- const source = options.sourceDir ?? getVendoredSkillsDir();
13365
- const platformSource = options.platformSkillsDir ?? getPlatformSkillsDir(options.target);
13490
+ async function discoverSkillNames(sourceDir) {
13491
+ const entries = await readdir11(sourceDir, { withFileTypes: true });
13492
+ const names = [];
13493
+ for (const entry of entries) {
13494
+ if (!entry.isDirectory()) continue;
13495
+ if (entry.name.startsWith(".")) continue;
13496
+ if (await fileExists(join3(sourceDir, entry.name, "SKILL.md"))) {
13497
+ names.push(entry.name);
13498
+ }
13499
+ }
13500
+ const pinnedSet = new Set(KNOWN_SKILL_NAMES);
13501
+ const pinned = KNOWN_SKILL_NAMES.filter((name) => names.includes(name));
13502
+ const extras = names.filter((name) => !pinnedSet.has(name)).sort();
13503
+ return [...pinned, ...extras];
13504
+ }
13505
+ async function installSkillsWithReport(options) {
13506
+ if (!options.ignorePluginActive && options.targetDir === void 0) {
13507
+ if (await isSyntaurPluginEnabledFor(options.target)) {
13508
+ return { results: [], skippedReason: "plugin-active" };
13509
+ }
13510
+ }
13511
+ const source = options.sourceDir ?? await getSkillsDir();
13366
13512
  const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
13367
13513
  const force = options.force ?? false;
13368
13514
  if (!await fileExists(source)) {
13369
13515
  throw new Error(
13370
- `Vendored skills not found at ${source}. Reinstall syntaur: npm install -g syntaur@latest`
13516
+ `Syntaur skills not found at ${source}. Reinstall syntaur: npm install -g syntaur@latest`
13371
13517
  );
13372
13518
  }
13519
+ const skillNames = await discoverSkillNames(source);
13373
13520
  const results = [];
13374
13521
  await mkdir4(targetRoot, { recursive: true });
13375
- for (const skill of REQUIRED_SKILLS) {
13522
+ for (const skill of skillNames) {
13376
13523
  const srcDir = join3(source, skill);
13377
- if (!await fileExists(srcDir)) continue;
13378
13524
  const destDir = join3(targetRoot, skill);
13379
- results.push(await installSkillDir(srcDir, destDir, skill, "shared", force));
13525
+ results.push(await installSkillDir(srcDir, destDir, skill, force));
13380
13526
  }
13381
- if (options.target === "claude" && await fileExists(platformSource)) {
13382
- const entries = await readdir11(platformSource, { withFileTypes: true });
13383
- for (const entry of entries) {
13384
- if (!entry.isDirectory()) continue;
13385
- const skill = entry.name;
13386
- if (REQUIRED_SKILLS.includes(skill)) continue;
13387
- const srcDir = join3(platformSource, skill);
13388
- const destDir = join3(targetRoot, skill);
13389
- results.push(await installSkillDir(srcDir, destDir, skill, "platform", force));
13390
- }
13391
- }
13392
- return results;
13527
+ return { results };
13393
13528
  }
13394
13529
  async function uninstallSkills(options) {
13395
13530
  const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
13396
13531
  if (!await fileExists(targetRoot)) return [];
13397
- const known = new Set(REQUIRED_SKILLS);
13398
- const platformSource = options.platformSkillsDir ?? getPlatformSkillsDir(options.target);
13399
- if (options.target === "claude" && await fileExists(platformSource)) {
13400
- const entries = await readdir11(platformSource, { withFileTypes: true });
13401
- for (const entry of entries) {
13402
- if (entry.isDirectory()) known.add(entry.name);
13532
+ const sourceDir = options.sourceDir ?? await getSkillsDir();
13533
+ const known = /* @__PURE__ */ new Set();
13534
+ if (await fileExists(sourceDir)) {
13535
+ for (const name of await discoverSkillNames(sourceDir)) {
13536
+ known.add(name);
13537
+ }
13538
+ } else {
13539
+ for (const name of KNOWN_SKILL_NAMES) {
13540
+ known.add(name);
13403
13541
  }
13404
13542
  }
13405
13543
  const removed = [];
13406
13544
  for (const skill of known) {
13407
13545
  const destDir = join3(targetRoot, skill);
13408
13546
  if (!await fileExists(destDir)) continue;
13547
+ if (await isSymlink(destDir)) continue;
13409
13548
  const skillMd = join3(destDir, "SKILL.md");
13410
13549
  if (!await fileExists(skillMd)) continue;
13411
- const content = await readFile17(skillMd, "utf-8").catch(() => "");
13550
+ const content = await readFile18(skillMd, "utf-8").catch(() => "");
13412
13551
  const match = content.match(/^name:\s*(\S+)\s*$/m);
13413
13552
  if (!match || match[1] !== skill) continue;
13414
13553
  await rm4(destDir, { recursive: true, force: true });
@@ -13416,11 +13555,22 @@ async function uninstallSkills(options) {
13416
13555
  }
13417
13556
  return removed;
13418
13557
  }
13419
- function formatInstallReport(results, target) {
13558
+ function formatInstallReport(resultsOrReport, target) {
13559
+ const report = Array.isArray(resultsOrReport) ? { results: resultsOrReport } : resultsOrReport;
13560
+ const { results, skippedReason } = report;
13420
13561
  const lines = [];
13421
13562
  lines.push(`Skill install (${target}):`);
13563
+ if (skippedReason === "plugin-active") {
13564
+ lines.push(
13565
+ " Skipped \u2014 syntaur plugin is enabled for this agent and already loads skills from its manifest."
13566
+ );
13567
+ lines.push(
13568
+ " Run with --force-skills to install globally anyway (creates duplicates with the plugin)."
13569
+ );
13570
+ return lines.join("\n");
13571
+ }
13422
13572
  for (const r of results) {
13423
- const marker = r.status === "installed" ? "+" : r.status === "overwritten" ? "!" : r.status === "differs-preserved" ? "?" : "=";
13573
+ const marker = r.status === "installed" ? "+" : r.status === "overwritten" ? "!" : r.status === "differs-preserved" ? "?" : r.status === "skipped-symlink" ? "~" : "=";
13424
13574
  lines.push(` ${marker} ${r.skill} (${r.status})`);
13425
13575
  }
13426
13576
  const diffs = results.filter((r) => r.status === "differs-preserved");
@@ -13430,7 +13580,14 @@ function formatInstallReport(results, target) {
13430
13580
  ` Note: ${diffs.length} skill(s) already exist with different content and were preserved.`
13431
13581
  );
13432
13582
  lines.push(
13433
- " Run with --force-skills to overwrite with the vendored version."
13583
+ " Run with --force-skills to overwrite with the syntaur version."
13584
+ );
13585
+ }
13586
+ const symlinked = results.filter((r) => r.status === "skipped-symlink");
13587
+ if (symlinked.length > 0) {
13588
+ lines.push("");
13589
+ lines.push(
13590
+ ` Note: ${symlinked.length} skill(s) were skipped because the target is a symlink (likely managed by skills.sh).`
13434
13591
  );
13435
13592
  }
13436
13593
  return lines.join("\n");
@@ -13448,11 +13605,12 @@ async function promptForInstallPath(question, recommendedPath) {
13448
13605
  }
13449
13606
  }
13450
13607
  async function installPluginCommand(options) {
13608
+ const envOverride = process.env.SYNTAUR_PLUGIN_TARGET?.trim();
13451
13609
  const shouldPromptForTarget = Boolean(
13452
- options.promptForTarget !== false && isInteractiveTerminal() && !options.targetDir
13610
+ options.promptForTarget !== false && isInteractiveTerminal() && !options.targetDir && !envOverride
13453
13611
  );
13454
13612
  const recommendedTargetDir = await recommendPluginTargetDir("claude");
13455
- const targetDir = options.targetDir ? normalizeAbsoluteInstallPath(options.targetDir, "Claude plugin target") : shouldPromptForTarget ? await promptForInstallPath("Claude plugin directory", recommendedTargetDir) : recommendedTargetDir;
13613
+ const targetDir = options.targetDir ? normalizeAbsoluteInstallPath(options.targetDir, "Claude plugin target") : envOverride ? normalizeAbsoluteInstallPath(envOverride, "SYNTAUR_PLUGIN_TARGET") : shouldPromptForTarget ? await promptForInstallPath("Claude plugin directory", recommendedTargetDir) : recommendedTargetDir;
13456
13614
  const previousTargetDir = await getConfiguredOrLegacyManagedPluginDir("claude");
13457
13615
  const migrating = Boolean(previousTargetDir && previousTargetDir !== targetDir);
13458
13616
  let previousInstall = previousTargetDir ? await inspectInstallPath("claude", previousTargetDir) : null;
@@ -13485,6 +13643,7 @@ async function installPluginCommand(options) {
13485
13643
  targetDir
13486
13644
  });
13487
13645
  const currentMarketplace = await detectClaudeMarketplaceForTarget(result.targetDir);
13646
+ let knownMarketplaceState = null;
13488
13647
  if (currentMarketplace) {
13489
13648
  await ensureClaudeMarketplaceEntry({
13490
13649
  marketplaceRootDir: currentMarketplace.rootDir,
@@ -13492,6 +13651,10 @@ async function installPluginCommand(options) {
13492
13651
  pluginTargetDir: result.targetDir,
13493
13652
  expectedExistingPluginTargetDir: previousMarketplace && previousMarketplace.manifestPath === currentMarketplace.manifestPath ? previousTargetDir : null
13494
13653
  });
13654
+ knownMarketplaceState = await ensureKnownClaudeMarketplaceForRoot({
13655
+ name: currentMarketplace.name,
13656
+ rootDir: currentMarketplace.rootDir
13657
+ });
13495
13658
  } else {
13496
13659
  console.warn(
13497
13660
  `Warning: ${result.targetDir} is not inside a Claude Code marketplace (expected parent path of the form ~/.claude/plugins/marketplaces/<name>/plugins/syntaur). The plugin files were copied, but Claude Code will not discover them until you place them inside a marketplace.`
@@ -13515,21 +13678,49 @@ async function installPluginCommand(options) {
13515
13678
  }
13516
13679
  previousInstall = null;
13517
13680
  }
13681
+ let enableResult = null;
13682
+ if (options.enable && currentMarketplace) {
13683
+ try {
13684
+ enableResult = await setSyntaurPluginEnabled({
13685
+ marketplaceName: currentMarketplace.name,
13686
+ enabled: true
13687
+ });
13688
+ } catch (error) {
13689
+ console.warn(
13690
+ `Warning: could not enable plugin \u2014 ${error instanceof Error ? error.message : String(error)}`
13691
+ );
13692
+ }
13693
+ }
13518
13694
  console.log("Installed Syntaur plugin:");
13519
13695
  console.log(` target: ${result.targetDir}`);
13520
13696
  console.log(` source: ${result.sourceDir}`);
13521
13697
  console.log(` mode: ${result.mode}`);
13522
13698
  if (currentMarketplace) {
13523
13699
  console.log(` marketplace: ${currentMarketplace.manifestPath}`);
13700
+ if (knownMarketplaceState) {
13701
+ const tag = knownMarketplaceState.added ? "registered (added)" : knownMarketplaceState.updated ? "registered (updated)" : "already registered";
13702
+ console.log(` known_marketplaces.json: ${tag}`);
13703
+ }
13704
+ const enabledKey = `syntaur@${currentMarketplace.name}`;
13705
+ if (enableResult) {
13706
+ console.log(
13707
+ ` enabledPlugins: ${enabledKey} = ${enableResult.current}` + (enableResult.changed ? "" : " (already)")
13708
+ );
13709
+ } else {
13710
+ console.log(
13711
+ ` enabledPlugins: ${enabledKey} not modified \u2014 run /plugin to enable, or pass --enable next time`
13712
+ );
13713
+ }
13524
13714
  }
13525
13715
  if (!options.skipSkills) {
13526
13716
  try {
13527
- const skillResults = await installSkills({
13717
+ const skillReport = await installSkillsWithReport({
13528
13718
  target: "claude",
13529
- force: options.forceSkills
13719
+ force: options.forceSkills,
13720
+ ignorePluginActive: options.forceSkills
13530
13721
  });
13531
13722
  console.log("");
13532
- console.log(formatInstallReport(skillResults, "claude"));
13723
+ console.log(formatInstallReport(skillReport, "claude"));
13533
13724
  } catch (error) {
13534
13725
  console.warn(
13535
13726
  `Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
@@ -13537,25 +13728,24 @@ async function installPluginCommand(options) {
13537
13728
  }
13538
13729
  }
13539
13730
  console.log("\nThe plugin is now available in Claude Code.");
13540
- console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project, /track-session, /save-session-summary");
13731
+ console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project, /track-session, /clear-assignment, /manage-statuses");
13541
13732
  console.log(" Background: syntaur-protocol skill (auto-invoked)");
13542
- console.log(" Claude-specific skill: track-session (agent session registration)");
13543
- console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End + PreCompact (prompts /save-session-summary)");
13733
+ console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End");
13544
13734
  }
13545
13735
 
13546
13736
  // src/commands/install-statusline.ts
13547
13737
  init_paths();
13548
13738
  init_fs();
13549
- import { readFile as readFile19, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
13550
- import { resolve as resolve28, dirname as dirname11 } from "path";
13551
- import { homedir as homedir4 } from "os";
13552
- import { fileURLToPath as fileURLToPath5 } from "url";
13739
+ import { readFile as readFile20, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink6, lstat as lstat3 } from "fs/promises";
13740
+ import { resolve as resolve29, dirname as dirname10 } from "path";
13741
+ import { homedir as homedir5 } from "os";
13742
+ import { fileURLToPath as fileURLToPath4 } from "url";
13553
13743
 
13554
13744
  // src/commands/configure-statusline.ts
13555
13745
  init_paths();
13556
13746
  init_fs();
13557
- import { readFile as readFile18, writeFile as writeFile7 } from "fs/promises";
13558
- import { resolve as resolve27, dirname as dirname10 } from "path";
13747
+ import { readFile as readFile19, writeFile as writeFile7 } from "fs/promises";
13748
+ import { resolve as resolve28, dirname as dirname9 } from "path";
13559
13749
  import { spawnSync } from "child_process";
13560
13750
  import { checkbox, input as input2, confirm } from "@inquirer/prompts";
13561
13751
  var AVAILABLE_SEGMENTS = [
@@ -13576,12 +13766,12 @@ var PRESETS = {
13576
13766
  tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
13577
13767
  };
13578
13768
  function getConfigPath(installRoot) {
13579
- return resolve27(installRoot, "statusline.config.json");
13769
+ return resolve28(installRoot, "statusline.config.json");
13580
13770
  }
13581
13771
  async function readConfig2(path) {
13582
13772
  if (!await fileExists(path)) return null;
13583
13773
  try {
13584
- const raw = await readFile18(path, "utf-8");
13774
+ const raw = await readFile19(path, "utf-8");
13585
13775
  const parsed = JSON.parse(raw);
13586
13776
  if (!parsed || typeof parsed !== "object") return null;
13587
13777
  const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
@@ -13676,7 +13866,7 @@ function renderPreview(config, statuslineScript, cwd) {
13676
13866
  env: {
13677
13867
  ...process.env,
13678
13868
  // Force the child to pick up the freshly-written config from install root.
13679
- HOME: dirname10(dirname10(statuslineScript))
13869
+ HOME: dirname9(dirname9(statuslineScript))
13680
13870
  }
13681
13871
  });
13682
13872
  if (res.status !== 0) return null;
@@ -13727,14 +13917,14 @@ async function configureStatuslineCommand(options = {}) {
13727
13917
  console.log("(preview mode \u2014 config NOT written)");
13728
13918
  return;
13729
13919
  }
13730
- await ensureDir(dirname10(configPath));
13920
+ await ensureDir(dirname9(configPath));
13731
13921
  await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
13732
13922
  console.log("Wrote statusline config:");
13733
13923
  console.log(` path: ${configPath}`);
13734
13924
  console.log(` segments: ${config.segments.join(", ")}`);
13735
13925
  console.log(` separator: ${JSON.stringify(config.separator)}`);
13736
13926
  if (config.wrap) console.log(` wrap: ${config.wrap}`);
13737
- const script = options.statuslineScript ?? resolve27(installRoot, "statusline.sh");
13927
+ const script = options.statuslineScript ?? resolve28(installRoot, "statusline.sh");
13738
13928
  if (await fileExists(script)) {
13739
13929
  console.log("");
13740
13930
  console.log("Live preview:");
@@ -13754,7 +13944,7 @@ async function configureStatuslineCommand(options = {}) {
13754
13944
  async function writeDefaultConfigIfMissing(installRoot) {
13755
13945
  const path = getConfigPath(installRoot);
13756
13946
  if (await fileExists(path)) return;
13757
- await ensureDir(dirname10(path));
13947
+ await ensureDir(dirname9(path));
13758
13948
  const defaultConfig = {
13759
13949
  segments: ["git", "assignment", "session"],
13760
13950
  separator: " \xB7 "
@@ -13764,12 +13954,12 @@ async function writeDefaultConfigIfMissing(installRoot) {
13764
13954
 
13765
13955
  // src/commands/install-statusline.ts
13766
13956
  function getPackageStatuslineSource() {
13767
- const here = dirname11(fileURLToPath5(import.meta.url));
13768
- return resolve28(here, "..", "statusline", "statusline.sh");
13957
+ const here = dirname10(fileURLToPath4(import.meta.url));
13958
+ return resolve29(here, "..", "statusline", "statusline.sh");
13769
13959
  }
13770
13960
  async function readSettingsJson(settingsPath) {
13771
13961
  if (!await fileExists(settingsPath)) return {};
13772
- const raw = await readFile19(settingsPath, "utf-8");
13962
+ const raw = await readFile20(settingsPath, "utf-8");
13773
13963
  if (raw.trim() === "") return {};
13774
13964
  try {
13775
13965
  const parsed = JSON.parse(raw);
@@ -13781,7 +13971,7 @@ async function readSettingsJson(settingsPath) {
13781
13971
  }
13782
13972
  }
13783
13973
  async function writeSettingsJson(settingsPath, data) {
13784
- await ensureDir(dirname11(settingsPath));
13974
+ await ensureDir(dirname10(settingsPath));
13785
13975
  await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
13786
13976
  }
13787
13977
  async function resolveMode(mode, existingCommand, ourCommand) {
@@ -13816,7 +14006,7 @@ function extractExistingCommand(settings) {
13816
14006
  };
13817
14007
  }
13818
14008
  async function backupSettings(settingsSnapshot, backupPath) {
13819
- await ensureDir(dirname11(backupPath));
14009
+ await ensureDir(dirname10(backupPath));
13820
14010
  await writeFile8(
13821
14011
  backupPath,
13822
14012
  JSON.stringify(
@@ -13833,9 +14023,9 @@ async function backupSettings(settingsSnapshot, backupPath) {
13833
14023
  );
13834
14024
  }
13835
14025
  async function installScript(sourceScript, destScript, link) {
13836
- await ensureDir(dirname11(destScript));
14026
+ await ensureDir(dirname10(destScript));
13837
14027
  try {
13838
- const s = await lstat2(destScript);
14028
+ const s = await lstat3(destScript);
13839
14029
  if (s.isSymbolicLink() || s.isFile()) {
13840
14030
  await unlink6(destScript);
13841
14031
  }
@@ -13849,12 +14039,12 @@ async function installScript(sourceScript, destScript, link) {
13849
14039
  }
13850
14040
  async function installStatuslineCommand(options = {}) {
13851
14041
  const mode = options.mode ?? "ask";
13852
- const settingsPath = options.settingsPath ?? resolve28(homedir4(), ".claude", "settings.json");
14042
+ const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
13853
14043
  const installRoot = options.installRoot ?? syntaurRoot();
13854
14044
  const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
13855
- const destScript = resolve28(installRoot, "statusline.sh");
13856
- const confPath = resolve28(installRoot, "statusline.conf");
13857
- const backupPath = resolve28(installRoot, "statusline.backup.json");
14045
+ const destScript = resolve29(installRoot, "statusline.sh");
14046
+ const confPath = resolve29(installRoot, "statusline.conf");
14047
+ const backupPath = resolve29(installRoot, "statusline.backup.json");
13858
14048
  if (!await fileExists(sourceScript)) {
13859
14049
  throw new Error(
13860
14050
  `Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
@@ -13890,7 +14080,7 @@ async function installStatuslineCommand(options = {}) {
13890
14080
  if (parsed) {
13891
14081
  wrapTarget = parsed;
13892
14082
  } else {
13893
- const wrapperPath = resolve28(installRoot, "statusline-wrapped.sh");
14083
+ const wrapperPath = resolve29(installRoot, "statusline-wrapped.sh");
13894
14084
  const wrapperBody = `#!/usr/bin/env bash
13895
14085
  # Auto-generated by syntaur install-statusline.
13896
14086
  # Executes the previously configured statusLine command.
@@ -13901,7 +14091,7 @@ exec ${existingCommand}
13901
14091
  wrapTarget = wrapperPath;
13902
14092
  }
13903
14093
  }
13904
- await ensureDir(dirname11(confPath));
14094
+ await ensureDir(dirname10(confPath));
13905
14095
  await writeFile8(
13906
14096
  confPath,
13907
14097
  wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
@@ -13939,25 +14129,25 @@ function parseWrapCommand(command) {
13939
14129
  async function chmodExec(path) {
13940
14130
  const fs = await import("fs/promises");
13941
14131
  try {
13942
- const s = await stat3(path);
14132
+ const s = await stat2(path);
13943
14133
  await fs.chmod(path, s.mode | 73);
13944
14134
  } catch {
13945
14135
  }
13946
14136
  }
13947
14137
  async function uninstallStatuslineCommand(options = {}) {
13948
- const settingsPath = options.settingsPath ?? resolve28(homedir4(), ".claude", "settings.json");
14138
+ const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
13949
14139
  const installRoot = options.installRoot ?? syntaurRoot();
13950
- const destScript = resolve28(installRoot, "statusline.sh");
13951
- const confPath = resolve28(installRoot, "statusline.conf");
13952
- const backupPath = resolve28(installRoot, "statusline.backup.json");
13953
- const wrapperPath = resolve28(installRoot, "statusline-wrapped.sh");
14140
+ const destScript = resolve29(installRoot, "statusline.sh");
14141
+ const confPath = resolve29(installRoot, "statusline.conf");
14142
+ const backupPath = resolve29(installRoot, "statusline.backup.json");
14143
+ const wrapperPath = resolve29(installRoot, "statusline-wrapped.sh");
13954
14144
  const settings = await readSettingsJson(settingsPath);
13955
14145
  const existing = extractExistingCommand(settings);
13956
14146
  const ourCommand = `bash ${destScript}`;
13957
14147
  let restored = null;
13958
14148
  if (await fileExists(backupPath)) {
13959
14149
  try {
13960
- const raw = await readFile19(backupPath, "utf-8");
14150
+ const raw = await readFile20(backupPath, "utf-8");
13961
14151
  const parsed = JSON.parse(raw);
13962
14152
  const prev = parsed?.previousStatusLine;
13963
14153
  if (prev && typeof prev === "object" && typeof prev.command === "string") {
@@ -13978,7 +14168,7 @@ async function uninstallStatuslineCommand(options = {}) {
13978
14168
  await writeSettingsJson(settingsPath, settings);
13979
14169
  }
13980
14170
  if (!options.keepScript) {
13981
- const configPath = resolve28(installRoot, "statusline.config.json");
14171
+ const configPath = resolve29(installRoot, "statusline.config.json");
13982
14172
  for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
13983
14173
  try {
13984
14174
  await rm5(path, { force: true });
@@ -14076,12 +14266,13 @@ async function installCodexPluginCommand(options) {
14076
14266
  console.log(` marketplace: ${marketplace.marketplacePath}`);
14077
14267
  if (!options.skipSkills) {
14078
14268
  try {
14079
- const skillResults = await installSkills({
14269
+ const skillReport = await installSkillsWithReport({
14080
14270
  target: "codex",
14081
- force: options.forceSkills
14271
+ force: options.forceSkills,
14272
+ ignorePluginActive: options.forceSkills
14082
14273
  });
14083
14274
  console.log("");
14084
- console.log(formatInstallReport(skillResults, "codex"));
14275
+ console.log(formatInstallReport(skillReport, "codex"));
14085
14276
  } catch (error) {
14086
14277
  console.warn(
14087
14278
  `Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
@@ -14234,7 +14425,7 @@ async function setupCommand(options) {
14234
14425
  }
14235
14426
 
14236
14427
  // src/commands/uninstall.ts
14237
- import { resolve as resolve29 } from "path";
14428
+ import { resolve as resolve30 } from "path";
14238
14429
  init_paths();
14239
14430
  function expandTargets(options) {
14240
14431
  if (options.all) {
@@ -14314,7 +14505,7 @@ async function uninstallCommand(options) {
14314
14505
  const configuredProjectDir = await getConfiguredProjectDir();
14315
14506
  await removeSyntaurData();
14316
14507
  console.log(`Removed ${syntaurRoot()}`);
14317
- if (configuredProjectDir && resolve29(configuredProjectDir) !== resolve29(syntaurRoot(), "projects")) {
14508
+ if (configuredProjectDir && resolve30(configuredProjectDir) !== resolve30(syntaurRoot(), "projects")) {
14318
14509
  console.warn(
14319
14510
  `Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
14320
14511
  );
@@ -14333,7 +14524,7 @@ init_slug();
14333
14524
  init_cursor_rules();
14334
14525
  init_codex_agents();
14335
14526
  init_opencode_config();
14336
- import { resolve as resolve30 } from "path";
14527
+ import { resolve as resolve31 } from "path";
14337
14528
  var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
14338
14529
  async function setupAdapterCommand(framework, options) {
14339
14530
  if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
@@ -14359,19 +14550,19 @@ async function setupAdapterCommand(framework, options) {
14359
14550
  }
14360
14551
  const config = await readConfig();
14361
14552
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
14362
- const projectDir = resolve30(baseDir, options.project);
14363
- const assignmentDir = resolve30(
14553
+ const projectDir = resolve31(baseDir, options.project);
14554
+ const assignmentDir = resolve31(
14364
14555
  projectDir,
14365
14556
  "assignments",
14366
14557
  options.assignment
14367
14558
  );
14368
- const projectMdPath = resolve30(projectDir, "project.md");
14559
+ const projectMdPath = resolve31(projectDir, "project.md");
14369
14560
  if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
14370
14561
  throw new Error(
14371
14562
  `Project "${options.project}" not found at ${projectDir}.`
14372
14563
  );
14373
14564
  }
14374
- const assignmentMdPath = resolve30(assignmentDir, "assignment.md");
14565
+ const assignmentMdPath = resolve31(assignmentDir, "assignment.md");
14375
14566
  if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
14376
14567
  throw new Error(
14377
14568
  `Assignment "${options.assignment}" not found at ${assignmentDir}.`
@@ -14399,15 +14590,15 @@ async function setupAdapterCommand(framework, options) {
14399
14590
  }
14400
14591
  }
14401
14592
  if (framework === "cursor") {
14402
- const protocolPath = resolve30(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
14403
- const assignmentPath = resolve30(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
14593
+ const protocolPath = resolve31(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
14594
+ const assignmentPath = resolve31(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
14404
14595
  await writeAdapterFile(protocolPath, renderCursorProtocol());
14405
14596
  await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
14406
14597
  } else if (framework === "codex" || framework === "opencode") {
14407
- const agentsPath = resolve30(cwd, "AGENTS.md");
14598
+ const agentsPath = resolve31(cwd, "AGENTS.md");
14408
14599
  await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
14409
14600
  if (framework === "opencode") {
14410
- const configPath = resolve30(cwd, "opencode.json");
14601
+ const configPath = resolve31(cwd, "opencode.json");
14411
14602
  await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
14412
14603
  }
14413
14604
  }
@@ -14432,7 +14623,7 @@ async function setupAdapterCommand(framework, options) {
14432
14623
  init_paths();
14433
14624
  init_fs();
14434
14625
  init_config2();
14435
- import { resolve as resolve31 } from "path";
14626
+ import { resolve as resolve32 } from "path";
14436
14627
  async function trackSessionCommand(options) {
14437
14628
  if (!options.agent) {
14438
14629
  throw new Error("--agent <name> is required.");
@@ -14445,7 +14636,7 @@ async function trackSessionCommand(options) {
14445
14636
  if (options.project) {
14446
14637
  const config = await readConfig();
14447
14638
  const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
14448
- const projectDir = resolve31(baseDir, options.project);
14639
+ const projectDir = resolve32(baseDir, options.project);
14449
14640
  if (!await fileExists(projectDir)) {
14450
14641
  throw new Error(
14451
14642
  `Project "${options.project}" not found at ${projectDir}.`
@@ -14479,8 +14670,8 @@ init_config2();
14479
14670
  init_paths();
14480
14671
  init_fs();
14481
14672
  import { spawnSync as spawnSync2 } from "child_process";
14482
- import { resolve as resolve33, isAbsolute as isAbsolute4 } from "path";
14483
- import { readFile as readFile21 } from "fs/promises";
14673
+ import { resolve as resolve34, isAbsolute as isAbsolute4 } from "path";
14674
+ import { readFile as readFile22 } from "fs/promises";
14484
14675
  import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
14485
14676
  async function browseCommand(options) {
14486
14677
  const config = await readConfig();
@@ -14549,7 +14740,7 @@ async function pickAgent(agents) {
14549
14740
  return picked;
14550
14741
  }
14551
14742
  async function ensureWorktree(opts) {
14552
- const assignmentPath = resolve33(
14743
+ const assignmentPath = resolve34(
14553
14744
  opts.projectsDir,
14554
14745
  opts.projectSlug,
14555
14746
  "assignments",
@@ -14559,7 +14750,7 @@ async function ensureWorktree(opts) {
14559
14750
  if (!await fileExists(assignmentPath)) {
14560
14751
  return void 0;
14561
14752
  }
14562
- const content = await readFile21(assignmentPath, "utf-8");
14753
+ const content = await readFile22(assignmentPath, "utf-8");
14563
14754
  const { parseAssignmentFrontmatter: parseAssignmentFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
14564
14755
  const fm = parseAssignmentFrontmatter2(content);
14565
14756
  const { workspace } = fm;
@@ -14630,7 +14821,7 @@ function computeWorktreeDefaults(opts) {
14630
14821
  const repository = opts.existing.repository ?? detectCurrentGitRoot();
14631
14822
  const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
14632
14823
  const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
14633
- const worktreeBase = resolve33(
14824
+ const worktreeBase = resolve34(
14634
14825
  syntaurRoot(),
14635
14826
  "worktrees",
14636
14827
  opts.projectSlug || "standalone",
@@ -14663,7 +14854,7 @@ function detectCurrentBranch() {
14663
14854
  async function runCreate(opts) {
14664
14855
  const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
14665
14856
  const expandedWorktree = expandHome(opts.worktreePath);
14666
- const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve33(expandedWorktree);
14857
+ const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve34(expandedWorktree);
14667
14858
  try {
14668
14859
  await createWorktreeAndRecord2({
14669
14860
  assignmentPath: opts.assignmentPath,
@@ -14692,7 +14883,7 @@ init_paths();
14692
14883
  init_fs();
14693
14884
  init_playbook();
14694
14885
  init_playbooks();
14695
- import { resolve as resolve34 } from "path";
14886
+ import { resolve as resolve35 } from "path";
14696
14887
  async function createPlaybookCommand(name, options) {
14697
14888
  if (!name.trim()) {
14698
14889
  throw new Error("Playbook name cannot be empty.");
@@ -14705,7 +14896,7 @@ async function createPlaybookCommand(name, options) {
14705
14896
  }
14706
14897
  const dir = playbooksDir();
14707
14898
  await ensureDir(dir);
14708
- const filePath = resolve34(dir, `${slug}.md`);
14899
+ const filePath = resolve35(dir, `${slug}.md`);
14709
14900
  if (await fileExists(filePath)) {
14710
14901
  throw new Error(
14711
14902
  `Playbook "${slug}" already exists at ${filePath}
@@ -14727,8 +14918,8 @@ init_paths();
14727
14918
  init_fs();
14728
14919
  init_parser();
14729
14920
  init_config2();
14730
- import { readdir as readdir12, readFile as readFile22 } from "fs/promises";
14731
- import { resolve as resolve35 } from "path";
14921
+ import { readdir as readdir12, readFile as readFile23 } from "fs/promises";
14922
+ import { resolve as resolve36 } from "path";
14732
14923
  async function listPlaybooksCommand(options = {}) {
14733
14924
  const dir = playbooksDir();
14734
14925
  if (!await fileExists(dir)) {
@@ -14743,8 +14934,8 @@ async function listPlaybooksCommand(options = {}) {
14743
14934
  );
14744
14935
  const rows = [];
14745
14936
  for (const entry of mdFiles) {
14746
- const filePath = resolve35(dir, entry.name);
14747
- const raw = await readFile22(filePath, "utf-8");
14937
+ const filePath = resolve36(dir, entry.name);
14938
+ const raw = await readFile23(filePath, "utf-8");
14748
14939
  const parsed = parsePlaybook(raw);
14749
14940
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
14750
14941
  const disabled = disabledSet.has(slug);
@@ -14842,8 +15033,8 @@ init_fs();
14842
15033
  init_config2();
14843
15034
  init_slug();
14844
15035
  import { Command } from "commander";
14845
- import { readFile as readFile23 } from "fs/promises";
14846
- import { resolve as resolve36 } from "path";
15036
+ import { readFile as readFile24 } from "fs/promises";
15037
+ import { resolve as resolve37 } from "path";
14847
15038
  var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
14848
15039
  async function resolveScope(options) {
14849
15040
  const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
@@ -14855,7 +15046,7 @@ async function resolveScope(options) {
14855
15046
  throw new Error(`Invalid project slug: "${options.project}".`);
14856
15047
  }
14857
15048
  const config = await readConfig();
14858
- const projectMd = resolve36(config.defaultProjectDir, options.project, "project.md");
15049
+ const projectMd = resolve37(config.defaultProjectDir, options.project, "project.md");
14859
15050
  if (!await fileExists(projectMd)) {
14860
15051
  throw new Error(`Project "${options.project}" not found.`);
14861
15052
  }
@@ -15174,10 +15365,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
15174
15365
  (e) => e.itemIds.every((id) => completedIds.has(id))
15175
15366
  );
15176
15367
  const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
15177
- await ensureDir(resolve36(todosPath, "archive"));
15368
+ await ensureDir(resolve37(todosPath, "archive"));
15178
15369
  let archContent = "";
15179
15370
  if (await fileExists(archFile)) {
15180
- archContent = await readFile23(archFile, "utf-8");
15371
+ archContent = await readFile24(archFile, "utf-8");
15181
15372
  archContent = archContent.trimEnd() + "\n\n";
15182
15373
  } else {
15183
15374
  archContent = `---
@@ -15350,12 +15541,12 @@ function describeScope(scope) {
15350
15541
  }
15351
15542
  async function injectPromotedTodos(assignmentDir, todos, scopeLabel) {
15352
15543
  const { resolve: resolvePath2 } = await import("path");
15353
- const { readFile: readFile32 } = await import("fs/promises");
15544
+ const { readFile: readFile35 } = await import("fs/promises");
15354
15545
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
15355
15546
  const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
15356
15547
  const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
15357
15548
  const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
15358
- let content = await readFile32(assignmentMdPath, "utf-8");
15549
+ let content = await readFile35(assignmentMdPath, "utf-8");
15359
15550
  content = appendTodosToAssignmentBody2(
15360
15551
  content,
15361
15552
  todos.map((t) => ({
@@ -15400,13 +15591,13 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
15400
15591
  }
15401
15592
  const planDir = todoPlanDir(todosPath, workspace, id);
15402
15593
  await ensureDir(planDir);
15403
- const { readdir: readdir18 } = await import("fs/promises");
15404
- const existingFiles = (await readdir18(planDir).catch(() => [])).filter(
15594
+ const { readdir: readdir19 } = await import("fs/promises");
15595
+ const existingFiles = (await readdir19(planDir).catch(() => [])).filter(
15405
15596
  (f) => /^plan(?:-v\d+)?\.md$/.test(f)
15406
15597
  );
15407
15598
  let target;
15408
15599
  if (existingFiles.length === 0) {
15409
- target = resolve36(planDir, "plan.md");
15600
+ target = resolve37(planDir, "plan.md");
15410
15601
  } else {
15411
15602
  const versions = /* @__PURE__ */ new Set();
15412
15603
  for (const f of existingFiles) {
@@ -15416,7 +15607,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
15416
15607
  }
15417
15608
  let n = 2;
15418
15609
  while (versions.has(n)) n++;
15419
- target = resolve36(planDir, `plan-v${n}.md`);
15610
+ target = resolve37(planDir, `plan-v${n}.md`);
15420
15611
  }
15421
15612
  if (!await fileExists(target)) {
15422
15613
  const stub = `---
@@ -15486,10 +15677,10 @@ async function moveTodo(id, options) {
15486
15677
  if (await fileExists(newPlanDir)) {
15487
15678
  throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
15488
15679
  }
15489
- const { rename: rename6, mkdir: mkdir7 } = await import("fs/promises");
15680
+ const { rename: rename7, mkdir: mkdir7 } = await import("fs/promises");
15490
15681
  const { dirname: dirname16 } = await import("path");
15491
15682
  await mkdir7(dirname16(newPlanDir), { recursive: true });
15492
- await rename6(item.planDir, newPlanDir);
15683
+ await rename7(item.planDir, newPlanDir);
15493
15684
  item.planDir = newPlanDir;
15494
15685
  }
15495
15686
  sourceChecklist.items.splice(idx, 1);
@@ -15607,20 +15798,20 @@ backupCommand.command("config").description("Show or update backup configuration
15607
15798
  import { Command as Command3 } from "commander";
15608
15799
 
15609
15800
  // src/utils/doctor/index.ts
15610
- import { fileURLToPath as fileURLToPath7 } from "url";
15611
- import { readFile as readFile27 } from "fs/promises";
15612
- import { dirname as dirname13, join as join5 } from "path";
15801
+ import { fileURLToPath as fileURLToPath6 } from "url";
15802
+ import { readFile as readFile30 } from "fs/promises";
15803
+ import { dirname as dirname13, join as join6 } from "path";
15613
15804
 
15614
15805
  // src/utils/doctor/context.ts
15615
15806
  init_config2();
15616
15807
  init_paths();
15617
15808
  init_fs();
15618
15809
  import Database2 from "better-sqlite3";
15619
- import { resolve as resolve37 } from "path";
15810
+ import { resolve as resolve38 } from "path";
15620
15811
  async function buildCheckContext(cwd = process.cwd()) {
15621
15812
  const config = await readConfig();
15622
15813
  const root = syntaurRoot();
15623
- const dbPath = resolve37(root, "syntaur.db");
15814
+ const dbPath = resolve38(root, "syntaur.db");
15624
15815
  let db2 = null;
15625
15816
  let dbError = null;
15626
15817
  if (await fileExists(dbPath)) {
@@ -15654,10 +15845,10 @@ function closeCheckContext(ctx) {
15654
15845
  // src/utils/doctor/checks/env.ts
15655
15846
  init_fs();
15656
15847
  init_paths();
15657
- import { resolve as resolve38, isAbsolute as isAbsolute5 } from "path";
15658
- import { readFile as readFile24, stat as stat4 } from "fs/promises";
15659
- import { fileURLToPath as fileURLToPath6 } from "url";
15660
- import { dirname as dirname12, join as join4 } from "path";
15848
+ import { resolve as resolve39, isAbsolute as isAbsolute5 } from "path";
15849
+ import { readFile as readFile25, stat as stat3 } from "fs/promises";
15850
+ import { fileURLToPath as fileURLToPath5 } from "url";
15851
+ import { dirname as dirname11, join as join4 } from "path";
15661
15852
  var CATEGORY = "env";
15662
15853
  var syntaurRootExists = {
15663
15854
  id: "env.syntaur-root-exists",
@@ -15665,7 +15856,7 @@ var syntaurRootExists = {
15665
15856
  title: "~/.syntaur/ directory exists",
15666
15857
  async run(ctx) {
15667
15858
  try {
15668
- const s = await stat4(ctx.syntaurRoot);
15859
+ const s = await stat3(ctx.syntaurRoot);
15669
15860
  if (!s.isDirectory()) {
15670
15861
  return err(this, `${ctx.syntaurRoot} exists but is not a directory`, [
15671
15862
  ctx.syntaurRoot
@@ -15695,7 +15886,7 @@ var configValid = {
15695
15886
  category: CATEGORY,
15696
15887
  title: "~/.syntaur/config.md is valid",
15697
15888
  async run(ctx) {
15698
- const configPath = resolve38(ctx.syntaurRoot, "config.md");
15889
+ const configPath = resolve39(ctx.syntaurRoot, "config.md");
15699
15890
  if (!await fileExists(configPath)) {
15700
15891
  return {
15701
15892
  id: this.id,
@@ -15712,7 +15903,7 @@ var configValid = {
15712
15903
  autoFixable: false
15713
15904
  };
15714
15905
  }
15715
- const content = await readFile24(configPath, "utf-8");
15906
+ const content = await readFile25(configPath, "utf-8");
15716
15907
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
15717
15908
  if (!fmMatch || fmMatch[1].trim() === "") {
15718
15909
  return {
@@ -15987,15 +16178,15 @@ async function readLocalVersion() {
15987
16178
  }
15988
16179
  async function readLocalPkg() {
15989
16180
  try {
15990
- const here = fileURLToPath6(import.meta.url);
15991
- let dir = dirname12(here);
16181
+ const here = fileURLToPath5(import.meta.url);
16182
+ let dir = dirname11(here);
15992
16183
  for (let i = 0; i < 6; i++) {
15993
16184
  const candidate = join4(dir, "package.json");
15994
16185
  try {
15995
- const text = await readFile24(candidate, "utf-8");
16186
+ const text = await readFile25(candidate, "utf-8");
15996
16187
  return JSON.parse(text);
15997
16188
  } catch {
15998
- dir = dirname12(dir);
16189
+ dir = dirname11(dir);
15999
16190
  }
16000
16191
  }
16001
16192
  return null;
@@ -16044,8 +16235,8 @@ function versionGte(a, b) {
16044
16235
 
16045
16236
  // src/utils/doctor/checks/structure.ts
16046
16237
  init_fs();
16047
- import { resolve as resolve39 } from "path";
16048
- import { readdir as readdir13, stat as stat5 } from "fs/promises";
16238
+ import { resolve as resolve40 } from "path";
16239
+ import { readdir as readdir13, stat as stat4 } from "fs/promises";
16049
16240
  var CATEGORY2 = "structure";
16050
16241
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
16051
16242
  "projects",
@@ -16064,7 +16255,7 @@ var projectsDir = {
16064
16255
  category: CATEGORY2,
16065
16256
  title: "projects/ directory exists",
16066
16257
  async run(ctx) {
16067
- const p = resolve39(ctx.syntaurRoot, "projects");
16258
+ const p = resolve40(ctx.syntaurRoot, "projects");
16068
16259
  if (!await fileExists(p)) {
16069
16260
  return {
16070
16261
  id: this.id,
@@ -16089,7 +16280,7 @@ var playbooksDir2 = {
16089
16280
  category: CATEGORY2,
16090
16281
  title: "playbooks/ directory exists",
16091
16282
  async run(ctx) {
16092
- const p = resolve39(ctx.syntaurRoot, "playbooks");
16283
+ const p = resolve40(ctx.syntaurRoot, "playbooks");
16093
16284
  if (!await fileExists(p)) {
16094
16285
  return {
16095
16286
  id: this.id,
@@ -16114,7 +16305,7 @@ var todosDirValid = {
16114
16305
  category: CATEGORY2,
16115
16306
  title: "todos/ directory is readable (if present)",
16116
16307
  async run(ctx) {
16117
- const p = resolve39(ctx.syntaurRoot, "todos");
16308
+ const p = resolve40(ctx.syntaurRoot, "todos");
16118
16309
  if (!await fileExists(p)) {
16119
16310
  return {
16120
16311
  id: this.id,
@@ -16125,7 +16316,7 @@ var todosDirValid = {
16125
16316
  autoFixable: false
16126
16317
  };
16127
16318
  }
16128
- const s = await stat5(p);
16319
+ const s = await stat4(p);
16129
16320
  if (!s.isDirectory()) {
16130
16321
  return {
16131
16322
  id: this.id,
@@ -16145,7 +16336,7 @@ var serversDirValid = {
16145
16336
  category: CATEGORY2,
16146
16337
  title: "servers/ directory is readable (if present)",
16147
16338
  async run(ctx) {
16148
- const p = resolve39(ctx.syntaurRoot, "servers");
16339
+ const p = resolve40(ctx.syntaurRoot, "servers");
16149
16340
  if (!await fileExists(p)) {
16150
16341
  return {
16151
16342
  id: this.id,
@@ -16156,7 +16347,7 @@ var serversDirValid = {
16156
16347
  autoFixable: false
16157
16348
  };
16158
16349
  }
16159
- const s = await stat5(p);
16350
+ const s = await stat4(p);
16160
16351
  if (!s.isDirectory()) {
16161
16352
  return {
16162
16353
  id: this.id,
@@ -16190,7 +16381,7 @@ var knownFilesRecognized = {
16190
16381
  title: this.title,
16191
16382
  status: "warn",
16192
16383
  detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
16193
- affected: unexpected.map((n) => resolve39(ctx.syntaurRoot, n)),
16384
+ affected: unexpected.map((n) => resolve40(ctx.syntaurRoot, n)),
16194
16385
  remediation: {
16195
16386
  kind: "manual",
16196
16387
  suggestion: "Review these entries \u2014 they may be leftover state from older versions",
@@ -16219,8 +16410,8 @@ function pass2(check) {
16219
16410
 
16220
16411
  // src/utils/doctor/checks/project.ts
16221
16412
  init_fs();
16222
- import { resolve as resolve40 } from "path";
16223
- import { readdir as readdir14, stat as stat6 } from "fs/promises";
16413
+ import { resolve as resolve41 } from "path";
16414
+ import { readdir as readdir14, stat as stat5 } from "fs/promises";
16224
16415
  var CATEGORY3 = "project";
16225
16416
  var REQUIRED_PROJECT_FILES = [
16226
16417
  "project.md",
@@ -16249,10 +16440,10 @@ async function listProjects2(ctx) {
16249
16440
  for (const e of entries) {
16250
16441
  if (!e.isDirectory()) continue;
16251
16442
  if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
16252
- const projectDir = resolve40(dir, e.name);
16443
+ const projectDir = resolve41(dir, e.name);
16253
16444
  let looksLikeProject = false;
16254
16445
  for (const marker of PROJECT_MARKERS) {
16255
- if (await fileExists(resolve40(projectDir, marker))) {
16446
+ if (await fileExists(resolve41(projectDir, marker))) {
16256
16447
  looksLikeProject = true;
16257
16448
  break;
16258
16449
  }
@@ -16271,7 +16462,7 @@ var requiredFiles = {
16271
16462
  for (const projectDir of projects) {
16272
16463
  const missing = [];
16273
16464
  for (const rel of REQUIRED_PROJECT_FILES) {
16274
- const p = resolve40(projectDir, rel);
16465
+ const p = resolve41(projectDir, rel);
16275
16466
  if (!await fileExists(p)) missing.push(rel);
16276
16467
  }
16277
16468
  if (missing.length === 0) continue;
@@ -16281,7 +16472,7 @@ var requiredFiles = {
16281
16472
  title: this.title,
16282
16473
  status: "error",
16283
16474
  detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
16284
- affected: missing.map((m) => resolve40(projectDir, m)),
16475
+ affected: missing.map((m) => resolve41(projectDir, m)),
16285
16476
  remediation: {
16286
16477
  kind: "manual",
16287
16478
  suggestion: "Recreate the missing scaffold files from templates",
@@ -16304,9 +16495,9 @@ var manifestStale = {
16304
16495
  const projects = await listProjects2(ctx);
16305
16496
  const results = [];
16306
16497
  for (const projectDir of projects) {
16307
- const manifestPath = resolve40(projectDir, "manifest.md");
16498
+ const manifestPath = resolve41(projectDir, "manifest.md");
16308
16499
  if (!await fileExists(manifestPath)) continue;
16309
- const manifestMtime = (await stat6(manifestPath)).mtimeMs;
16500
+ const manifestMtime = (await stat5(manifestPath)).mtimeMs;
16310
16501
  const newestAssignment = await newestAssignmentMtime(projectDir);
16311
16502
  if (newestAssignment === 0) continue;
16312
16503
  if (newestAssignment > manifestMtime) {
@@ -16353,7 +16544,7 @@ var orphanFiles = {
16353
16544
  title: this.title,
16354
16545
  status: "warn",
16355
16546
  detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
16356
- affected: orphans.map((o) => resolve40(projectDir, o)),
16547
+ affected: orphans.map((o) => resolve41(projectDir, o)),
16357
16548
  autoFixable: false
16358
16549
  });
16359
16550
  }
@@ -16363,7 +16554,7 @@ var orphanFiles = {
16363
16554
  };
16364
16555
  var projectChecks = [requiredFiles, manifestStale, orphanFiles];
16365
16556
  async function newestAssignmentMtime(projectDir) {
16366
- const assignmentsRoot = resolve40(projectDir, "assignments");
16557
+ const assignmentsRoot = resolve41(projectDir, "assignments");
16367
16558
  if (!await fileExists(assignmentsRoot)) return 0;
16368
16559
  let newest = 0;
16369
16560
  let entries;
@@ -16374,9 +16565,9 @@ async function newestAssignmentMtime(projectDir) {
16374
16565
  }
16375
16566
  for (const e of entries) {
16376
16567
  if (!e.isDirectory()) continue;
16377
- const assignmentMd = resolve40(assignmentsRoot, e.name, "assignment.md");
16568
+ const assignmentMd = resolve41(assignmentsRoot, e.name, "assignment.md");
16378
16569
  try {
16379
- const s = await stat6(assignmentMd);
16570
+ const s = await stat5(assignmentMd);
16380
16571
  if (s.mtimeMs > newest) newest = s.mtimeMs;
16381
16572
  } catch {
16382
16573
  }
@@ -16398,8 +16589,8 @@ init_fs();
16398
16589
  init_parser();
16399
16590
  init_types();
16400
16591
  init_paths();
16401
- import { resolve as resolve41 } from "path";
16402
- import { readdir as readdir15, readFile as readFile25 } from "fs/promises";
16592
+ import { resolve as resolve42 } from "path";
16593
+ import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
16403
16594
  var CATEGORY4 = "assignment";
16404
16595
  var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
16405
16596
  async function listAssignments(ctx) {
@@ -16410,16 +16601,16 @@ async function listAssignments(ctx) {
16410
16601
  for (const m of projects) {
16411
16602
  if (!m.isDirectory()) continue;
16412
16603
  if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
16413
- const assignmentsDir2 = resolve41(projectsDir2, m.name, "assignments");
16604
+ const assignmentsDir2 = resolve42(projectsDir2, m.name, "assignments");
16414
16605
  if (!await fileExists(assignmentsDir2)) continue;
16415
16606
  const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
16416
16607
  for (const a of entries) {
16417
16608
  if (!a.isDirectory()) continue;
16418
16609
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
16419
- const assignmentDir = resolve41(assignmentsDir2, a.name);
16420
- const assignmentMd = resolve41(assignmentDir, "assignment.md");
16610
+ const assignmentDir = resolve42(assignmentsDir2, a.name);
16611
+ const assignmentMd = resolve42(assignmentDir, "assignment.md");
16421
16612
  const entry = {
16422
- projectDir: resolve41(projectsDir2, m.name),
16613
+ projectDir: resolve42(projectsDir2, m.name),
16423
16614
  projectSlug: m.name,
16424
16615
  assignmentDir,
16425
16616
  assignmentSlug: a.name,
@@ -16439,8 +16630,8 @@ async function listAssignments(ctx) {
16439
16630
  for (const a of entries) {
16440
16631
  if (!a.isDirectory()) continue;
16441
16632
  if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
16442
- const assignmentDir = resolve41(standaloneRoot, a.name);
16443
- const assignmentMd = resolve41(assignmentDir, "assignment.md");
16633
+ const assignmentDir = resolve42(standaloneRoot, a.name);
16634
+ const assignmentMd = resolve42(assignmentDir, "assignment.md");
16444
16635
  const entry = {
16445
16636
  projectDir: standaloneRoot,
16446
16637
  projectSlug: null,
@@ -16518,7 +16709,7 @@ var invalidStatus = {
16518
16709
  const allowed = configuredStatuses(ctx);
16519
16710
  const results = [];
16520
16711
  for (const a of withAssignmentMd) {
16521
- const path = resolve41(a.assignmentDir, "assignment.md");
16712
+ const path = resolve42(a.assignmentDir, "assignment.md");
16522
16713
  const parsed = await parseSafe(path);
16523
16714
  if (!parsed) continue;
16524
16715
  if (!allowed.has(parsed.status)) {
@@ -16551,7 +16742,7 @@ var workspaceMissing = {
16551
16742
  const terminal = terminalStatuses(ctx);
16552
16743
  const results = [];
16553
16744
  for (const a of withAssignmentMd) {
16554
- const path = resolve41(a.assignmentDir, "assignment.md");
16745
+ const path = resolve42(a.assignmentDir, "assignment.md");
16555
16746
  const parsed = await parseSafe(path);
16556
16747
  if (!parsed) continue;
16557
16748
  if (terminal.has(parsed.status)) continue;
@@ -16598,12 +16789,12 @@ var requiredFilesByStatus = {
16598
16789
  const { withAssignmentMd } = await listAssignments(ctx);
16599
16790
  const results = [];
16600
16791
  for (const a of withAssignmentMd) {
16601
- const assignmentPath = resolve41(a.assignmentDir, "assignment.md");
16792
+ const assignmentPath = resolve42(a.assignmentDir, "assignment.md");
16602
16793
  const parsed = await parseSafe(assignmentPath);
16603
16794
  if (!parsed) continue;
16604
16795
  const missing = [];
16605
16796
  if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
16606
- const handoffPath = resolve41(a.assignmentDir, "handoff.md");
16797
+ const handoffPath = resolve42(a.assignmentDir, "handoff.md");
16607
16798
  if (!await fileExists(handoffPath)) missing.push("handoff.md");
16608
16799
  }
16609
16800
  if (missing.length === 0) continue;
@@ -16613,7 +16804,7 @@ var requiredFilesByStatus = {
16613
16804
  title: this.title,
16614
16805
  status: "warn",
16615
16806
  detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
16616
- affected: missing.map((m) => resolve41(a.assignmentDir, m)),
16807
+ affected: missing.map((m) => resolve42(a.assignmentDir, m)),
16617
16808
  remediation: {
16618
16809
  kind: "manual",
16619
16810
  suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
@@ -16636,7 +16827,7 @@ var companionFilesScaffolded = {
16636
16827
  for (const a of withAssignmentMd) {
16637
16828
  const missing = [];
16638
16829
  for (const filename of ["progress.md", "comments.md"]) {
16639
- if (!await fileExists(resolve41(a.assignmentDir, filename))) {
16830
+ if (!await fileExists(resolve42(a.assignmentDir, filename))) {
16640
16831
  missing.push(filename);
16641
16832
  }
16642
16833
  }
@@ -16648,7 +16839,7 @@ var companionFilesScaffolded = {
16648
16839
  title: this.title,
16649
16840
  status: "warn",
16650
16841
  detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
16651
- affected: missing.map((m) => resolve41(a.assignmentDir, m)),
16842
+ affected: missing.map((m) => resolve42(a.assignmentDir, m)),
16652
16843
  remediation: {
16653
16844
  kind: "manual",
16654
16845
  suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
@@ -16681,7 +16872,7 @@ var typeDefinition = {
16681
16872
  const { withAssignmentMd } = await listAssignments(ctx);
16682
16873
  const results = [];
16683
16874
  for (const a of withAssignmentMd) {
16684
- const path = resolve41(a.assignmentDir, "assignment.md");
16875
+ const path = resolve42(a.assignmentDir, "assignment.md");
16685
16876
  const parsed = await parseSafe(path);
16686
16877
  if (!parsed) continue;
16687
16878
  if (!parsed.type) continue;
@@ -16715,7 +16906,7 @@ var projectFrontmatterMatchesContainer = {
16715
16906
  const { withAssignmentMd } = await listAssignments(ctx);
16716
16907
  const results = [];
16717
16908
  for (const a of withAssignmentMd) {
16718
- const path = resolve41(a.assignmentDir, "assignment.md");
16909
+ const path = resolve42(a.assignmentDir, "assignment.md");
16719
16910
  const parsed = await parseSafe(path);
16720
16911
  if (!parsed) continue;
16721
16912
  if (a.standalone) {
@@ -16770,7 +16961,7 @@ var assignmentChecks = [
16770
16961
  ];
16771
16962
  async function parseSafe(path) {
16772
16963
  try {
16773
- const content = await readFile25(path, "utf-8");
16964
+ const content = await readFile26(path, "utf-8");
16774
16965
  return parseAssignmentFull(content);
16775
16966
  } catch {
16776
16967
  return null;
@@ -16789,7 +16980,7 @@ function pass4(check, detail) {
16789
16980
 
16790
16981
  // src/utils/doctor/checks/dashboard.ts
16791
16982
  init_fs();
16792
- import { resolve as resolve42 } from "path";
16983
+ import { resolve as resolve43 } from "path";
16793
16984
  var CATEGORY5 = "dashboard";
16794
16985
  var dbReachable = {
16795
16986
  id: "dashboard.db-reachable",
@@ -16803,7 +16994,7 @@ var dbReachable = {
16803
16994
  title: this.title,
16804
16995
  status: "error",
16805
16996
  detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
16806
- affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
16997
+ affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
16807
16998
  remediation: {
16808
16999
  kind: "manual",
16809
17000
  suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
@@ -16821,7 +17012,7 @@ var dbReachable = {
16821
17012
  title: this.title,
16822
17013
  status: "error",
16823
17014
  detail: 'syntaur.db is missing the expected "sessions" table',
16824
- affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
17015
+ affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
16825
17016
  autoFixable: false
16826
17017
  };
16827
17018
  }
@@ -16833,7 +17024,7 @@ var dbReachable = {
16833
17024
  title: this.title,
16834
17025
  status: "error",
16835
17026
  detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
16836
- affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
17027
+ affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
16837
17028
  autoFixable: false
16838
17029
  };
16839
17030
  }
@@ -16859,7 +17050,7 @@ var ghostSessions = {
16859
17050
  const results = [];
16860
17051
  for (const row of rows) {
16861
17052
  if (!row.project_slug) continue;
16862
- const projectPath = resolve42(projectsDir2, row.project_slug, "project.md");
17053
+ const projectPath = resolve43(projectsDir2, row.project_slug, "project.md");
16863
17054
  if (!await fileExists(projectPath)) {
16864
17055
  results.push({
16865
17056
  id: this.id,
@@ -16878,7 +17069,7 @@ var ghostSessions = {
16878
17069
  continue;
16879
17070
  }
16880
17071
  if (row.assignment_slug) {
16881
- const assignmentPath = resolve42(
17072
+ const assignmentPath = resolve43(
16882
17073
  projectsDir2,
16883
17074
  row.project_slug,
16884
17075
  "assignments",
@@ -16930,7 +17121,9 @@ function skipped(check, reason) {
16930
17121
 
16931
17122
  // src/utils/doctor/checks/integrations.ts
16932
17123
  init_fs();
16933
- import { readdir as readdir16 } from "fs/promises";
17124
+ import { resolve as resolve44, dirname as dirname12, basename as basename2 } from "path";
17125
+ import { readdir as readdir16, readFile as readFile27 } from "fs/promises";
17126
+ import { homedir as homedir6 } from "os";
16934
17127
  var CATEGORY6 = "integrations";
16935
17128
  var claudePluginLinked = {
16936
17129
  id: "integrations.claude-plugin-linked",
@@ -17010,7 +17203,111 @@ var backupConfigured = {
17010
17203
  };
17011
17204
  }
17012
17205
  };
17013
- var integrationChecks = [claudePluginLinked, codexPluginLinked, backupConfigured];
17206
+ async function readKnownMarketplaces() {
17207
+ const path = resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json");
17208
+ if (!await fileExists(path)) return {};
17209
+ try {
17210
+ const raw = await readFile27(path, "utf-8");
17211
+ return JSON.parse(raw);
17212
+ } catch {
17213
+ return {};
17214
+ }
17215
+ }
17216
+ var claudeMarketplaceRegistered = {
17217
+ id: "integrations.claude-marketplace-registered",
17218
+ category: CATEGORY6,
17219
+ title: "Claude marketplace containing syntaur is registered in known_marketplaces.json",
17220
+ async run(ctx) {
17221
+ const dir = ctx.config.integrations.claudePluginDir;
17222
+ if (!dir) return skipped2(this, "claudePluginDir not configured");
17223
+ if (!await fileExists(dir)) {
17224
+ return skipped2(this, "claudePluginDir does not exist (run install-plugin)");
17225
+ }
17226
+ const pluginsParent = dirname12(dir);
17227
+ if (basename2(pluginsParent) !== "plugins") {
17228
+ return {
17229
+ id: this.id,
17230
+ category: this.category,
17231
+ title: this.title,
17232
+ status: "error",
17233
+ detail: `claudePluginDir is ${dir}, which is not under a Claude marketplace (expected path of the form <marketplace-root>/plugins/syntaur). Claude Code will not show this plugin until it lives inside a marketplace.`,
17234
+ affected: [dir],
17235
+ remediation: {
17236
+ kind: "manual",
17237
+ suggestion: "Re-run install-plugin to relocate into a marketplace, or pass --target-dir <marketplace>/plugins/syntaur.",
17238
+ command: "syntaur install-plugin"
17239
+ },
17240
+ autoFixable: false
17241
+ };
17242
+ }
17243
+ const marketplaceRoot = dirname12(pluginsParent);
17244
+ const marketplaceManifest = resolve44(marketplaceRoot, ".claude-plugin", "marketplace.json");
17245
+ if (!await fileExists(marketplaceManifest)) {
17246
+ return {
17247
+ id: this.id,
17248
+ category: this.category,
17249
+ title: this.title,
17250
+ status: "error",
17251
+ detail: `${marketplaceManifest} does not exist \u2014 Claude won't see this plugin.`,
17252
+ affected: [marketplaceManifest],
17253
+ remediation: {
17254
+ kind: "manual",
17255
+ suggestion: "Re-run install-plugin to repair the marketplace files.",
17256
+ command: "syntaur install-plugin"
17257
+ },
17258
+ autoFixable: false
17259
+ };
17260
+ }
17261
+ let parsed = {};
17262
+ try {
17263
+ parsed = JSON.parse(await readFile27(marketplaceManifest, "utf-8"));
17264
+ } catch {
17265
+ return {
17266
+ id: this.id,
17267
+ category: this.category,
17268
+ title: this.title,
17269
+ status: "error",
17270
+ detail: `${marketplaceManifest} is not valid JSON.`,
17271
+ affected: [marketplaceManifest],
17272
+ autoFixable: false
17273
+ };
17274
+ }
17275
+ const marketplaceName = parsed.name ?? basename2(marketplaceRoot);
17276
+ const hasSyntaurEntry = (parsed.plugins ?? []).some((p) => p?.name === "syntaur");
17277
+ const known = await readKnownMarketplaces();
17278
+ const registered = known[marketplaceName]?.installLocation === marketplaceRoot || Object.values(known).some((v) => v.installLocation === marketplaceRoot);
17279
+ const issues = [];
17280
+ if (!hasSyntaurEntry) {
17281
+ issues.push(`marketplace.json at ${marketplaceManifest} does not list a "syntaur" plugin`);
17282
+ }
17283
+ if (!registered) {
17284
+ issues.push(
17285
+ `known_marketplaces.json does not register ${marketplaceName} \u2192 ${marketplaceRoot} (Claude will not show this plugin)`
17286
+ );
17287
+ }
17288
+ if (issues.length === 0) return pass6(this);
17289
+ return {
17290
+ id: this.id,
17291
+ category: this.category,
17292
+ title: this.title,
17293
+ status: "error",
17294
+ detail: issues.join("; "),
17295
+ affected: [marketplaceManifest, resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
17296
+ remediation: {
17297
+ kind: "manual",
17298
+ suggestion: "Re-run install-plugin to ensure both files are in sync.",
17299
+ command: "syntaur install-plugin"
17300
+ },
17301
+ autoFixable: false
17302
+ };
17303
+ }
17304
+ };
17305
+ var integrationChecks = [
17306
+ claudePluginLinked,
17307
+ claudeMarketplaceRegistered,
17308
+ codexPluginLinked,
17309
+ backupConfigured
17310
+ ];
17014
17311
  function pass6(check) {
17015
17312
  return {
17016
17313
  id: check.id,
@@ -17035,8 +17332,8 @@ function skipped2(check, reason) {
17035
17332
  init_fs();
17036
17333
  init_parser();
17037
17334
  init_types();
17038
- import { resolve as resolve43 } from "path";
17039
- import { readFile as readFile26 } from "fs/promises";
17335
+ import { resolve as resolve45 } from "path";
17336
+ import { readFile as readFile28 } from "fs/promises";
17040
17337
  var CATEGORY7 = "workspace";
17041
17338
  var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
17042
17339
  function hasAnyAssignmentField(ctx) {
@@ -17048,12 +17345,12 @@ function isStandaloneSession(ctx) {
17048
17345
  return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
17049
17346
  }
17050
17347
  async function loadContext(ctx) {
17051
- const path = resolve43(ctx.cwd, ".syntaur", "context.json");
17348
+ const path = resolve45(ctx.cwd, ".syntaur", "context.json");
17052
17349
  if (!await fileExists(path)) {
17053
17350
  return { data: null, path, exists: false, parseError: null };
17054
17351
  }
17055
17352
  try {
17056
- const raw = await readFile26(path, "utf-8");
17353
+ const raw = await readFile28(path, "utf-8");
17057
17354
  return { data: JSON.parse(raw), path, exists: true, parseError: null };
17058
17355
  } catch (err2) {
17059
17356
  return {
@@ -17128,7 +17425,7 @@ var contextAssignmentResolves = {
17128
17425
  if (!exists) return skipped3(this, "no context to resolve");
17129
17426
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
17130
17427
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
17131
- const assignmentMd = resolve43(data.assignmentDir, "assignment.md");
17428
+ const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
17132
17429
  if (!await fileExists(assignmentMd)) {
17133
17430
  return {
17134
17431
  id: this.id,
@@ -17157,10 +17454,10 @@ var contextTerminal = {
17157
17454
  if (!exists) return skipped3(this, "no context to check");
17158
17455
  if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
17159
17456
  if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
17160
- const assignmentMd = resolve43(data.assignmentDir, "assignment.md");
17457
+ const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
17161
17458
  if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
17162
17459
  try {
17163
- const content = await readFile26(assignmentMd, "utf-8");
17460
+ const content = await readFile28(assignmentMd, "utf-8");
17164
17461
  const parsed = parseAssignmentFull(content);
17165
17462
  const terminal = terminalStatuses2(ctx);
17166
17463
  if (terminal.has(parsed.status)) {
@@ -17303,6 +17600,86 @@ async function checkAgent(agent) {
17303
17600
  }
17304
17601
  var agentChecks = [agentsResolvable];
17305
17602
 
17603
+ // src/utils/doctor/checks/skills.ts
17604
+ init_fs();
17605
+ import { resolve as resolve46, join as join5 } from "path";
17606
+ import { readdir as readdir17, readFile as readFile29, lstat as lstat4 } from "fs/promises";
17607
+ import { homedir as homedir7 } from "os";
17608
+ var CATEGORY9 = "skills";
17609
+ var skillTargets = [
17610
+ { agent: "claude", dir: resolve46(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
17611
+ { agent: "codex", dir: resolve46(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
17612
+ ];
17613
+ var skillsDedupCheck = {
17614
+ id: "skills.dedup",
17615
+ category: CATEGORY9,
17616
+ title: "Syntaur skills are not duplicated across install paths",
17617
+ async run() {
17618
+ const findings = [];
17619
+ const affected = [];
17620
+ for (const { agent, dir, label } of skillTargets) {
17621
+ if (!await fileExists(dir)) continue;
17622
+ const pluginEnabled = await isSyntaurPluginEnabledFor(agent);
17623
+ const present = [];
17624
+ let entries;
17625
+ try {
17626
+ entries = await readdir17(dir, { withFileTypes: true });
17627
+ } catch {
17628
+ continue;
17629
+ }
17630
+ for (const entry of entries) {
17631
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
17632
+ if (!KNOWN_SKILLS.includes(entry.name)) continue;
17633
+ const skillMd = join5(dir, entry.name, "SKILL.md");
17634
+ if (!await fileExists(skillMd)) continue;
17635
+ const content = await readFile29(skillMd, "utf-8").catch(() => "");
17636
+ const match = content.match(/^name:\s*(\S+)\s*$/m);
17637
+ if (!match || match[1] !== entry.name) continue;
17638
+ let isSymlink2 = false;
17639
+ try {
17640
+ isSymlink2 = (await lstat4(join5(dir, entry.name))).isSymbolicLink();
17641
+ } catch {
17642
+ }
17643
+ present.push({ name: entry.name, isSymlink: isSymlink2 });
17644
+ }
17645
+ if (present.length === 0) continue;
17646
+ if (pluginEnabled) {
17647
+ const nonSymlink = present.filter((p) => !p.isSymlink);
17648
+ if (nonSymlink.length > 0) {
17649
+ findings.push(
17650
+ `${label}: ${nonSymlink.length} syntaur skill(s) installed globally while the syntaur plugin is enabled (${agent}) \u2014 duplicate registrations`
17651
+ );
17652
+ for (const p of nonSymlink) affected.push(join5(dir, p.name));
17653
+ }
17654
+ }
17655
+ }
17656
+ if (findings.length === 0) {
17657
+ return {
17658
+ id: this.id,
17659
+ category: this.category,
17660
+ title: this.title,
17661
+ status: "pass",
17662
+ autoFixable: false
17663
+ };
17664
+ }
17665
+ return {
17666
+ id: this.id,
17667
+ category: this.category,
17668
+ title: this.title,
17669
+ status: "warn",
17670
+ detail: findings.join("; "),
17671
+ affected,
17672
+ remediation: {
17673
+ kind: "manual",
17674
+ suggestion: "Either disable the plugin or remove the global skill copies. Recommended: keep the plugin path and remove the global copies via `syntaur uninstall --skills` (preserves user-authored skills with non-syntaur frontmatter).",
17675
+ command: null
17676
+ },
17677
+ autoFixable: false
17678
+ };
17679
+ }
17680
+ };
17681
+ var skillsChecks = [skillsDedupCheck];
17682
+
17306
17683
  // src/utils/doctor/registry.ts
17307
17684
  function allChecks() {
17308
17685
  return [
@@ -17313,7 +17690,8 @@ function allChecks() {
17313
17690
  ...dashboardChecks,
17314
17691
  ...integrationChecks,
17315
17692
  ...workspaceChecks,
17316
- ...agentChecks
17693
+ ...agentChecks,
17694
+ ...skillsChecks
17317
17695
  ];
17318
17696
  }
17319
17697
 
@@ -17395,11 +17773,11 @@ async function finalize(checks) {
17395
17773
  }
17396
17774
  async function readVersion() {
17397
17775
  try {
17398
- const here = fileURLToPath7(import.meta.url);
17776
+ const here = fileURLToPath6(import.meta.url);
17399
17777
  let dir = dirname13(here);
17400
17778
  for (let i = 0; i < 6; i++) {
17401
17779
  try {
17402
- const raw = await readFile27(join5(dir, "package.json"), "utf-8");
17780
+ const raw = await readFile30(join6(dir, "package.json"), "utf-8");
17403
17781
  const parsed = JSON.parse(raw);
17404
17782
  return typeof parsed.version === "string" ? parsed.version : null;
17405
17783
  } catch {
@@ -17747,8 +18125,8 @@ init_uuid();
17747
18125
  init_timestamp();
17748
18126
  init_assignment_resolver();
17749
18127
  init_templates();
17750
- import { resolve as resolve44 } from "path";
17751
- import { readFile as readFile28 } from "fs/promises";
18128
+ import { resolve as resolve47 } from "path";
18129
+ import { readFile as readFile31 } from "fs/promises";
17752
18130
  function shortId() {
17753
18131
  return generateId().split("-")[0];
17754
18132
  }
@@ -17778,7 +18156,7 @@ async function commentCommand(target, text, options = {}) {
17778
18156
  if (!isValidSlug(target)) {
17779
18157
  throw new Error(`Invalid assignment slug "${target}".`);
17780
18158
  }
17781
- assignmentDir = resolve44(baseDir, options.project, "assignments", target);
18159
+ assignmentDir = resolve47(baseDir, options.project, "assignments", target);
17782
18160
  assignmentRef = target;
17783
18161
  } else {
17784
18162
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -17788,13 +18166,13 @@ async function commentCommand(target, text, options = {}) {
17788
18166
  assignmentDir = resolved.assignmentDir;
17789
18167
  assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
17790
18168
  }
17791
- const commentsPath = resolve44(assignmentDir, "comments.md");
18169
+ const commentsPath = resolve47(assignmentDir, "comments.md");
17792
18170
  const timestamp = nowTimestamp();
17793
18171
  const author = options.author ?? process.env.USER ?? "unknown";
17794
18172
  let currentContent;
17795
18173
  let currentCount = 0;
17796
18174
  if (await fileExists(commentsPath)) {
17797
- currentContent = await readFile28(commentsPath, "utf-8");
18175
+ currentContent = await readFile31(commentsPath, "utf-8");
17798
18176
  const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
17799
18177
  if (countMatch) currentCount = parseInt(countMatch[1], 10);
17800
18178
  } else {
@@ -17835,8 +18213,8 @@ init_slug();
17835
18213
  init_timestamp();
17836
18214
  init_assignment_resolver();
17837
18215
  init_assignment_todos();
17838
- import { resolve as resolve45 } from "path";
17839
- import { readFile as readFile29 } from "fs/promises";
18216
+ import { resolve as resolve48 } from "path";
18217
+ import { readFile as readFile32 } from "fs/promises";
17840
18218
  async function requestCommand(target, text, options = {}) {
17841
18219
  if (!text || !text.trim()) {
17842
18220
  throw new Error("Request text cannot be empty.");
@@ -17852,7 +18230,7 @@ async function requestCommand(target, text, options = {}) {
17852
18230
  if (!isValidSlug(target)) {
17853
18231
  throw new Error(`Invalid assignment slug "${target}".`);
17854
18232
  }
17855
- assignmentDir = resolve45(baseDir, options.project, "assignments", target);
18233
+ assignmentDir = resolve48(baseDir, options.project, "assignments", target);
17856
18234
  targetRef = target;
17857
18235
  } else {
17858
18236
  const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
@@ -17862,12 +18240,12 @@ async function requestCommand(target, text, options = {}) {
17862
18240
  assignmentDir = resolved.assignmentDir;
17863
18241
  targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
17864
18242
  }
17865
- const assignmentMdPath = resolve45(assignmentDir, "assignment.md");
18243
+ const assignmentMdPath = resolve48(assignmentDir, "assignment.md");
17866
18244
  if (!await fileExists(assignmentMdPath)) {
17867
18245
  throw new Error(`assignment.md not found at ${assignmentMdPath}`);
17868
18246
  }
17869
18247
  const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
17870
- let content = await readFile29(assignmentMdPath, "utf-8");
18248
+ let content = await readFile32(assignmentMdPath, "utf-8");
17871
18249
  content = appendTodosToAssignmentBody(content, [
17872
18250
  { description: `${text.trim()} (from: ${source})` }
17873
18251
  ]);
@@ -17878,10 +18256,10 @@ async function requestCommand(target, text, options = {}) {
17878
18256
 
17879
18257
  // src/cli-default-command.ts
17880
18258
  init_config2();
17881
- import { readdir as readdir17 } from "fs/promises";
18259
+ import { readdir as readdir18 } from "fs/promises";
17882
18260
  async function hasAnyProjectContent(projectsDir2) {
17883
18261
  try {
17884
- const entries = await readdir17(projectsDir2, { withFileTypes: true });
18262
+ const entries = await readdir18(projectsDir2, { withFileTypes: true });
17885
18263
  return entries.some((entry) => entry.isDirectory());
17886
18264
  } catch {
17887
18265
  return false;
@@ -17917,21 +18295,21 @@ async function getDefaultCommandName() {
17917
18295
  // src/utils/npx-prompt.ts
17918
18296
  init_paths();
17919
18297
  init_fs();
17920
- import { fileURLToPath as fileURLToPath9 } from "url";
17921
- import { readFile as readFile31 } from "fs/promises";
17922
- import { dirname as dirname15, join as join7, resolve as resolve46 } from "path";
18298
+ import { fileURLToPath as fileURLToPath8 } from "url";
18299
+ import { readFile as readFile34 } from "fs/promises";
18300
+ import { dirname as dirname15, join as join8, resolve as resolve49 } from "path";
17923
18301
  import { spawn as spawn4 } from "child_process";
17924
18302
  import { createInterface as createInterface2 } from "readline/promises";
17925
18303
 
17926
18304
  // src/utils/version.ts
17927
- import { fileURLToPath as fileURLToPath8 } from "url";
17928
- import { readFile as readFile30 } from "fs/promises";
17929
- import { dirname as dirname14, join as join6 } from "path";
18305
+ import { fileURLToPath as fileURLToPath7 } from "url";
18306
+ import { readFile as readFile33 } from "fs/promises";
18307
+ import { dirname as dirname14, join as join7 } from "path";
17930
18308
  async function readPackageVersion(scriptUrl) {
17931
18309
  try {
17932
- const scriptPath = fileURLToPath8(scriptUrl);
18310
+ const scriptPath = fileURLToPath7(scriptUrl);
17933
18311
  const pkgRoot = dirname14(dirname14(scriptPath));
17934
- const raw = await readFile30(join6(pkgRoot, "package.json"), "utf-8");
18312
+ const raw = await readFile33(join7(pkgRoot, "package.json"), "utf-8");
17935
18313
  const parsed = JSON.parse(raw);
17936
18314
  return typeof parsed.version === "string" ? parsed.version : null;
17937
18315
  } catch {
@@ -17940,13 +18318,13 @@ async function readPackageVersion(scriptUrl) {
17940
18318
  }
17941
18319
 
17942
18320
  // src/utils/npx-prompt.ts
17943
- var STATE_FILE = resolve46(syntaurRoot(), "npx-install.json");
18321
+ var STATE_FILE = resolve49(syntaurRoot(), "npx-install.json");
17944
18322
  var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
17945
18323
  var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
17946
18324
  function isRunningViaNpx(scriptUrl) {
17947
18325
  let scriptPath;
17948
18326
  try {
17949
- scriptPath = fileURLToPath9(scriptUrl);
18327
+ scriptPath = fileURLToPath8(scriptUrl);
17950
18328
  } catch {
17951
18329
  return false;
17952
18330
  }
@@ -17961,7 +18339,7 @@ function isRunningViaNpx(scriptUrl) {
17961
18339
  async function readState() {
17962
18340
  if (!await fileExists(STATE_FILE)) return null;
17963
18341
  try {
17964
- const raw = await readFile31(STATE_FILE, "utf-8");
18342
+ const raw = await readFile34(STATE_FILE, "utf-8");
17965
18343
  return JSON.parse(raw);
17966
18344
  } catch {
17967
18345
  return null;
@@ -17975,7 +18353,7 @@ async function resolveNpmBin() {
17975
18353
  const nodeDir = dirname15(process.execPath);
17976
18354
  const isWin = process.platform === "win32";
17977
18355
  const npmName = isWin ? "npm.cmd" : "npm";
17978
- const nearNode = join7(nodeDir, npmName);
18356
+ const nearNode = join8(nodeDir, npmName);
17979
18357
  if (await fileExists(nearNode)) {
17980
18358
  return { cmd: nearNode, shell: false };
17981
18359
  }
@@ -18018,9 +18396,9 @@ async function readGlobalVersion() {
18018
18396
  });
18019
18397
  if (!rootPath) return null;
18020
18398
  try {
18021
- const manifestPath = join7(rootPath, "syntaur", "package.json");
18399
+ const manifestPath = join8(rootPath, "syntaur", "package.json");
18022
18400
  if (!await fileExists(manifestPath)) return null;
18023
- const raw = await readFile31(manifestPath, "utf-8");
18401
+ const raw = await readFile34(manifestPath, "utf-8");
18024
18402
  const parsed = JSON.parse(raw);
18025
18403
  return typeof parsed.version === "string" ? parsed.version : null;
18026
18404
  } catch {
@@ -18315,7 +18693,7 @@ program.command("setup").description("Initialize Syntaur and optionally install
18315
18693
  process.exit(1);
18316
18694
  }
18317
18695
  });
18318
- program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.claude/skills").option("--skip-skills", "Do not install protocol skills into ~/.claude/skills").action(async (options) => {
18696
+ program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.claude/skills").option("--skip-skills", "Do not install protocol skills into ~/.claude/skills").option("--enable", "Enable the plugin in ~/.claude/settings.json after install").action(async (options) => {
18319
18697
  try {
18320
18698
  await installPluginCommand({ ...options, promptForTarget: true });
18321
18699
  } catch (error) {