@synkro-sh/cli 1.6.35 → 1.6.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bootstrap.js CHANGED
@@ -602,54 +602,14 @@ var init_mcpConfig = __esm({
602
602
  });
603
603
 
604
604
  // cli/installer/skillParser.ts
605
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
606
- import { resolve as resolve2, basename } from "path";
607
- function parseSection(heading, body) {
608
- const lines = body.split("\n");
609
- const meta = {};
610
- const textLines = [];
611
- for (const line of lines) {
612
- const m = line.match(/^(mode|severity|category)\s*:\s*(.+)/i);
613
- if (m && META_KEYS.has(m[1].toLowerCase())) {
614
- meta[m[1].toLowerCase()] = m[2].trim();
615
- } else if (line.trim()) {
616
- textLines.push(line.trim());
617
- }
618
- }
619
- const description = textLines.join(" ").trim();
620
- const text = description ? `${heading}: ${description}` : heading;
621
- return {
622
- text,
623
- mode: meta.mode || "ask",
624
- severity: meta.severity || "medium",
625
- category: meta.category || "custom"
626
- };
627
- }
628
- function parseSkillFile(filePath) {
629
- if (!existsSync4(filePath)) return null;
630
- const content = readFileSync4(filePath, "utf-8");
631
- const source = `skill:${basename(filePath)}`;
632
- const sections = content.split(/^## /m).slice(1);
633
- if (sections.length === 0) return null;
634
- const rules = [];
635
- for (const section of sections) {
636
- const newlineIdx = section.indexOf("\n");
637
- if (newlineIdx === -1) continue;
638
- const heading = section.slice(0, newlineIdx).trim();
639
- const body = section.slice(newlineIdx + 1);
640
- if (!heading) continue;
641
- rules.push(parseSection(heading, body));
642
- }
643
- return rules.length > 0 ? { source, rules } : null;
644
- }
605
+ import { existsSync as existsSync4 } from "fs";
606
+ import { resolve as resolve2 } from "path";
645
607
  function resolveSkillPaths(skills, repoRoot) {
646
608
  return skills.filter((s) => s.endsWith(".md")).map((s) => resolve2(repoRoot, s)).filter((p) => existsSync4(p));
647
609
  }
648
- var META_KEYS;
649
610
  var init_skillParser = __esm({
650
611
  "cli/installer/skillParser.ts"() {
651
612
  "use strict";
652
- META_KEYS = /* @__PURE__ */ new Set(["mode", "severity", "category"]);
653
613
  }
654
614
  });
655
615
 
@@ -1198,6 +1158,7 @@ export interface SynkroFileConfig {
1198
1158
  version: number;
1199
1159
  harness: ('claude-code' | 'cursor')[];
1200
1160
  grader: { pool: 'auto' | 'claude' | 'cursor'; mode?: string };
1161
+ workers: { claude?: number; cursor?: number };
1201
1162
  ruleset: string;
1202
1163
  skills: string[];
1203
1164
  scanning: { cwe: boolean; cve: boolean };
@@ -1207,6 +1168,7 @@ const SYNKRO_FILE_DEFAULTS: SynkroFileConfig = {
1207
1168
  version: 1,
1208
1169
  harness: ['claude-code', 'cursor'],
1209
1170
  grader: { pool: 'auto' },
1171
+ workers: {},
1210
1172
  ruleset: 'default',
1211
1173
  skills: [],
1212
1174
  scanning: { cwe: true, cve: true },
@@ -1282,6 +1244,10 @@ export function loadSynkroFile(cwd?: string): SynkroFileConfig {
1282
1244
  pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',
1283
1245
  mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : undefined,
1284
1246
  },
1247
+ workers: {
1248
+ ...(typeof parsed.workers?.claude === 'number' ? { claude: parsed.workers.claude } : {}),
1249
+ ...(typeof parsed.workers?.cursor === 'number' ? { cursor: parsed.workers.cursor } : {}),
1250
+ },
1285
1251
  ruleset: typeof parsed.ruleset === 'string' ? parsed.ruleset : 'default',
1286
1252
  skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string') : [],
1287
1253
  scanning: {
@@ -6050,7 +6016,7 @@ __export(stub_exports, {
6050
6016
  saveCredentials: () => saveCredentials
6051
6017
  });
6052
6018
  import { createServer } from "http";
6053
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
6019
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
6054
6020
  import { homedir as homedir4, platform } from "os";
6055
6021
  import { join as join3, dirname as dirname4 } from "path";
6056
6022
  import { execFile } from "child_process";
@@ -6091,7 +6057,7 @@ function loadCredentials() {
6091
6057
  return null;
6092
6058
  }
6093
6059
  try {
6094
- const content = readFileSync5(AUTH_FILE, "utf8");
6060
+ const content = readFileSync4(AUTH_FILE, "utf8");
6095
6061
  return JSON.parse(content);
6096
6062
  } catch (error) {
6097
6063
  return null;
@@ -6949,7 +6915,7 @@ __export(macKeychain_exports, {
6949
6915
  writeCursorApiKey: () => writeCursorApiKey,
6950
6916
  writeRefreshAgent: () => writeRefreshAgent
6951
6917
  });
6952
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, chmodSync, readFileSync as readFileSync6, statSync } from "fs";
6918
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, chmodSync, readFileSync as readFileSync5, statSync } from "fs";
6953
6919
  import { homedir as homedir5, platform as platform2 } from "os";
6954
6920
  import { join as join5 } from "path";
6955
6921
  import { spawnSync } from "child_process";
@@ -6977,7 +6943,7 @@ function exportKeychainCreds() {
6977
6943
  }
6978
6944
  function cursorApiKeyConfigured() {
6979
6945
  try {
6980
- return existsSync7(CURSOR_API_KEY_FILE) && readFileSync6(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
6946
+ return existsSync7(CURSOR_API_KEY_FILE) && readFileSync5(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
6981
6947
  } catch {
6982
6948
  return false;
6983
6949
  }
@@ -6993,7 +6959,7 @@ function writeCursorApiKey(key) {
6993
6959
  async function validateCursorApiKey() {
6994
6960
  let key;
6995
6961
  try {
6996
- key = readFileSync6(CURSOR_API_KEY_FILE, "utf-8").trim();
6962
+ key = readFileSync5(CURSOR_API_KEY_FILE, "utf-8").trim();
6997
6963
  } catch {
6998
6964
  return null;
6999
6965
  }
@@ -7087,7 +7053,7 @@ function refreshCreds() {
7087
7053
  }
7088
7054
  function readExportedCreds() {
7089
7055
  try {
7090
- return readFileSync6(CLAUDE_CREDS_FILE, "utf-8");
7056
+ return readFileSync5(CLAUDE_CREDS_FILE, "utf-8");
7091
7057
  } catch {
7092
7058
  return null;
7093
7059
  }
@@ -7136,7 +7102,7 @@ __export(dockerInstall_exports, {
7136
7102
  splitWorkers: () => splitWorkers,
7137
7103
  waitForContainerReady: () => waitForContainerReady
7138
7104
  });
7139
- import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
7105
+ import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
7140
7106
  import { homedir as homedir6 } from "os";
7141
7107
  import { join as join6 } from "path";
7142
7108
  import { execSync as execSync4, spawnSync as spawnSync2 } from "child_process";
@@ -7200,19 +7166,21 @@ function parseSynkroYaml(raw) {
7200
7166
  if (currentArr && currentKey) result[currentKey] = currentArr;
7201
7167
  return result;
7202
7168
  }
7203
- function readSynkroFilePool() {
7169
+ function readSynkroFileConfig() {
7204
7170
  try {
7205
7171
  const root = execSync4("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
7206
- if (!root) return "auto";
7172
+ if (!root) return { pool: "auto" };
7207
7173
  const fp = join6(root, ".synkro");
7208
- if (!existsSync8(fp)) return "auto";
7209
- const raw = readFileSync7(fp, "utf-8");
7174
+ if (!existsSync8(fp)) return { pool: "auto" };
7175
+ const raw = readFileSync6(fp, "utf-8");
7210
7176
  const parsed = raw.trimStart().startsWith("{") ? JSON.parse(raw) : parseSynkroYaml(raw);
7211
- const pool = parsed?.grader?.pool;
7212
- if (pool === "cursor" || pool === "claude") return pool;
7177
+ const pool = ["auto", "claude", "cursor"].includes(parsed?.grader?.pool) ? parsed.grader.pool : "auto";
7178
+ const cw = typeof parsed?.workers?.claude === "number" ? Math.max(0, Math.floor(parsed.workers.claude)) : void 0;
7179
+ const curw = typeof parsed?.workers?.cursor === "number" ? Math.max(0, Math.floor(parsed.workers.cursor)) : void 0;
7180
+ return { pool, claudeWorkers: cw, cursorWorkers: curw };
7213
7181
  } catch {
7214
7182
  }
7215
- return "auto";
7183
+ return { pool: "auto" };
7216
7184
  }
7217
7185
  function resolveWorkerConfig(rest) {
7218
7186
  let workers = 8;
@@ -7247,10 +7215,15 @@ function resolveWorkerConfig(rest) {
7247
7215
  workers = Math.min(workers, 64);
7248
7216
  let provs = providers;
7249
7217
  if (provs.length === 0) {
7250
- const synkroPool = readSynkroFilePool();
7251
- if (synkroPool === "cursor") {
7218
+ const sc = readSynkroFileConfig();
7219
+ if (sc.claudeWorkers != null || sc.cursorWorkers != null) {
7220
+ const cw = sc.claudeWorkers || 0;
7221
+ const curw = sc.cursorWorkers || 0;
7222
+ if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw, explicit };
7223
+ }
7224
+ if (sc.pool === "cursor") {
7252
7225
  provs = ["cursor"];
7253
- } else if (synkroPool === "claude") {
7226
+ } else if (sc.pool === "claude") {
7254
7227
  provs = ["claude_code"];
7255
7228
  } else {
7256
7229
  provs = detectAgents().map((a) => a.kind);
@@ -7661,14 +7634,14 @@ __export(setupGithub_exports, {
7661
7634
  import { createInterface as createInterface2 } from "readline/promises";
7662
7635
  import { stdin as input, stdout as output } from "process";
7663
7636
  import { execSync as execSync5, spawn as nodeSpawn } from "child_process";
7664
- import { existsSync as existsSync9, readFileSync as readFileSync8, unlinkSync as unlinkSync3 } from "fs";
7637
+ import { existsSync as existsSync9, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
7665
7638
  import { homedir as homedir7, platform as platform3 } from "os";
7666
7639
  import { join as join7 } from "path";
7667
7640
  import { execFile as execFile2 } from "child_process";
7668
7641
  function readConfig() {
7669
7642
  if (!existsSync9(CONFIG_PATH)) return {};
7670
7643
  const out = {};
7671
- for (const line of readFileSync8(CONFIG_PATH, "utf-8").split("\n")) {
7644
+ for (const line of readFileSync7(CONFIG_PATH, "utf-8").split("\n")) {
7672
7645
  const t = line.trim();
7673
7646
  if (!t || t.startsWith("#")) continue;
7674
7647
  const eq = t.indexOf("=");
@@ -7738,7 +7711,7 @@ function captureClaudeSetupToken() {
7738
7711
  proc.on("close", (code) => {
7739
7712
  let raw = "";
7740
7713
  try {
7741
- raw = readFileSync8(tmpFile, "utf-8");
7714
+ raw = readFileSync7(tmpFile, "utf-8");
7742
7715
  } catch (e) {
7743
7716
  reject(new Error(`Could not read script output file: ${e.message}`));
7744
7717
  return;
@@ -8025,7 +7998,7 @@ __export(install_exports, {
8025
7998
  syncSkillFiles: () => syncSkillFiles,
8026
7999
  writeHookScripts: () => writeHookScripts
8027
8000
  });
8028
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync9, readdirSync as readdirSync3 } from "fs";
8001
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync8, readdirSync as readdirSync3 } from "fs";
8029
8002
  import { homedir as homedir8 } from "os";
8030
8003
  import { join as join8 } from "path";
8031
8004
  import { execSync as execSync6, spawnSync as spawnSync3 } from "child_process";
@@ -8271,7 +8244,7 @@ function writeConfigEnv(opts) {
8271
8244
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
8272
8245
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
8273
8246
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
8274
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.35")}`
8247
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.37")}`
8275
8248
  ];
8276
8249
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
8277
8250
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -8294,7 +8267,7 @@ function resolveDeploymentMode() {
8294
8267
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
8295
8268
  try {
8296
8269
  if (existsSync10(CONFIG_PATH2)) {
8297
- const m = readFileSync9(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8270
+ const m = readFileSync8(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8298
8271
  const val = m?.[1]?.toLowerCase();
8299
8272
  if (val === "bare-host" || val === "docker") return val;
8300
8273
  }
@@ -8323,14 +8296,14 @@ function collectLocalMetadata(includeClaudeCode = true) {
8323
8296
  }
8324
8297
  const claudeDir = join8(homedir8(), ".claude");
8325
8298
  try {
8326
- const settings = JSON.parse(readFileSync9(join8(claudeDir, "settings.json"), "utf-8"));
8299
+ const settings = JSON.parse(readFileSync8(join8(claudeDir, "settings.json"), "utf-8"));
8327
8300
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
8328
8301
  if (plugins.length) meta.enabled_plugins = plugins;
8329
8302
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
8330
8303
  } catch {
8331
8304
  }
8332
8305
  try {
8333
- const mcpCache = JSON.parse(readFileSync9(join8(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8306
+ const mcpCache = JSON.parse(readFileSync8(join8(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8334
8307
  const mcpNames = Object.keys(mcpCache);
8335
8308
  if (mcpNames.length) meta.mcp_servers = mcpNames;
8336
8309
  } catch {
@@ -8345,7 +8318,7 @@ function collectLocalMetadata(includeClaudeCode = true) {
8345
8318
  const sessionsDir = join8(claudeDir, "sessions");
8346
8319
  const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
8347
8320
  for (const f of files) {
8348
- const s = JSON.parse(readFileSync9(join8(sessionsDir, f), "utf-8"));
8321
+ const s = JSON.parse(readFileSync8(join8(sessionsDir, f), "utf-8"));
8349
8322
  if (s.version) {
8350
8323
  meta.cc_version = meta.cc_version || s.version;
8351
8324
  break;
@@ -8477,7 +8450,7 @@ async function installCommand(opts = {}) {
8477
8450
  for (const mode of ["edit", "bash"]) {
8478
8451
  const pidFile = join8(SYNKRO_DIR4, "daemon", mode, "daemon.pid");
8479
8452
  try {
8480
- const pid = parseInt(readFileSync9(pidFile, "utf-8").trim(), 10);
8453
+ const pid = parseInt(readFileSync8(pidFile, "utf-8").trim(), 10);
8481
8454
  if (pid > 0) {
8482
8455
  process.kill(pid, "SIGTERM");
8483
8456
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -8679,19 +8652,32 @@ async function installCommand(opts = {}) {
8679
8652
  await promptCursorApiKey(opts);
8680
8653
  }
8681
8654
  console.log("Installing Synkro server container...");
8682
- const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
8683
- const synkroFilePool = readSynkroFilePool2();
8684
- let providers = [];
8685
- if (synkroFilePool === "cursor") {
8686
- providers = ["cursor"];
8687
- } else if (synkroFilePool === "claude") {
8688
- providers = ["claude_code"];
8655
+ const sf = readFullSynkroFile();
8656
+ let claudeWorkers;
8657
+ let cursorWorkers;
8658
+ if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {
8659
+ claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));
8660
+ cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));
8661
+ if (claudeWorkers + cursorWorkers === 0) {
8662
+ claudeWorkers = 0;
8663
+ cursorWorkers = 8;
8664
+ }
8665
+ console.log(` .synkro: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor`);
8689
8666
  } else {
8690
- if (hasClaudeCode) providers.push("claude_code");
8691
- if (hasCursor) providers.push("cursor");
8667
+ const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
8668
+ const synkroFilePool = sf?.grader?.pool || readSynkroFilePool();
8669
+ let providers = [];
8670
+ if (synkroFilePool === "cursor") {
8671
+ providers = ["cursor"];
8672
+ } else if (synkroFilePool === "claude") {
8673
+ providers = ["claude_code"];
8674
+ } else {
8675
+ if (hasClaudeCode) providers.push("claude_code");
8676
+ if (hasCursor) providers.push("cursor");
8677
+ }
8678
+ ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));
8679
+ if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${synkroFilePool}`);
8692
8680
  }
8693
- const { claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers);
8694
- if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${synkroFilePool}`);
8695
8681
  console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);
8696
8682
  const connectedRepo = detectGitRepo2() || void 0;
8697
8683
  const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });
@@ -8701,7 +8687,7 @@ async function installCommand(opts = {}) {
8701
8687
  const ready = await waitForContainerReady(6e4);
8702
8688
  if (ready) {
8703
8689
  console.log(" \u2713 container ready");
8704
- const mcpJwt = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8690
+ const mcpJwt = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8705
8691
  try {
8706
8692
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
8707
8693
  method: "POST",
@@ -8733,7 +8719,7 @@ async function installCommand(opts = {}) {
8733
8719
  try {
8734
8720
  let mcpToken = "";
8735
8721
  try {
8736
- mcpToken = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8722
+ mcpToken = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8737
8723
  } catch {
8738
8724
  }
8739
8725
  if (mcpToken) {
@@ -8887,13 +8873,13 @@ function writeSynkroFileIfMissing(opts) {
8887
8873
  } catch {
8888
8874
  }
8889
8875
  }
8890
- function readSynkroFilePool2() {
8876
+ function readSynkroFilePool() {
8891
8877
  try {
8892
8878
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8893
8879
  if (!root) return "auto";
8894
8880
  const fp = join8(root, ".synkro");
8895
8881
  if (!existsSync10(fp)) return "auto";
8896
- const parsed = parseSynkroFileRaw(readFileSync9(fp, "utf-8"));
8882
+ const parsed = parseSynkroFileRaw(readFileSync8(fp, "utf-8"));
8897
8883
  const pool = parsed?.grader?.pool;
8898
8884
  if (pool === "cursor" || pool === "claude") return pool;
8899
8885
  } catch {
@@ -8906,7 +8892,7 @@ function readFullSynkroFile() {
8906
8892
  if (!root) return null;
8907
8893
  const fp = join8(root, ".synkro");
8908
8894
  if (!existsSync10(fp)) return null;
8909
- const parsed = parseSynkroFileRaw(readFileSync9(fp, "utf-8"));
8895
+ const parsed = parseSynkroFileRaw(readFileSync8(fp, "utf-8"));
8910
8896
  const valid = ["claude-code", "cursor"];
8911
8897
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
8912
8898
  return {
@@ -8915,6 +8901,10 @@ function readFullSynkroFile() {
8915
8901
  pool: ["auto", "claude", "cursor"].includes(parsed.grader?.pool) ? parsed.grader.pool : "auto",
8916
8902
  mode: ["local", "byok"].includes(parsed.grader?.mode) ? parsed.grader.mode : "local"
8917
8903
  },
8904
+ workers: {
8905
+ ...typeof parsed.workers?.claude === "number" ? { claude: parsed.workers.claude } : {},
8906
+ ...typeof parsed.workers?.cursor === "number" ? { cursor: parsed.workers.cursor } : {}
8907
+ },
8918
8908
  ruleset: parsed.ruleset || "default",
8919
8909
  skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
8920
8910
  scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
@@ -8953,7 +8943,7 @@ function reconcileHarness() {
8953
8943
  });
8954
8944
  console.log(" \u2713 Claude Code hooks registered");
8955
8945
  try {
8956
- const mcpJwt = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8946
+ const mcpJwt = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8957
8947
  if (mcpJwt) {
8958
8948
  installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt, local: true });
8959
8949
  console.log(" \u2713 Claude Code MCP registered");
@@ -8992,6 +8982,11 @@ function reconcileHarness() {
8992
8982
  if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
8993
8983
  if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
8994
8984
  }
8985
+ if (sf.workers.claude != null || sf.workers.cursor != null) {
8986
+ const cw = Math.max(0, Math.floor(sf.workers.claude || 0));
8987
+ const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));
8988
+ if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw };
8989
+ }
8995
8990
  const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
8996
8991
  const providers = [];
8997
8992
  if (sf.grader.pool === "cursor") {
@@ -9012,23 +9007,28 @@ async function syncSkillFiles() {
9012
9007
  if (resolved.length === 0) return;
9013
9008
  const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
9014
9009
  for (const fp of resolved) {
9015
- const skill = parseSkillFile(fp);
9016
- if (!skill || skill.rules.length === 0) continue;
9010
+ const content = readFileSync8(fp, "utf-8");
9011
+ if (!content.trim()) continue;
9012
+ const source = `skill:${fp.split("/").pop()}`;
9017
9013
  try {
9018
9014
  const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {
9019
9015
  method: "POST",
9020
9016
  headers: { "Content-Type": "application/json" },
9021
- body: JSON.stringify(skill),
9022
- signal: AbortSignal.timeout(1e4)
9017
+ body: JSON.stringify({ source, content }),
9018
+ signal: AbortSignal.timeout(65e3)
9023
9019
  });
9024
9020
  if (resp.ok) {
9025
9021
  const result = await resp.json();
9026
- console.log(` \u2713 skill ${skill.source}: ${result.created} rules synced (${result.removed} removed)`);
9022
+ if (result.created > 0) {
9023
+ console.log(` \u2713 skill ${source}: ${result.created} new rules added`);
9024
+ } else {
9025
+ console.log(` \u2713 skill ${source}: ${result.message || "up to date"}`);
9026
+ }
9027
9027
  } else {
9028
- console.warn(` \u26A0 skill ${skill.source}: sync failed (${resp.status})`);
9028
+ console.warn(` \u26A0 skill ${source}: sync failed (${resp.status})`);
9029
9029
  }
9030
9030
  } catch (e) {
9031
- console.warn(` \u26A0 skill ${skill.source}: ${e.message}`);
9031
+ console.warn(` \u26A0 skill ${source}: ${e.message}`);
9032
9032
  }
9033
9033
  }
9034
9034
  }
@@ -9060,7 +9060,7 @@ function extractSessionInsights(projectsDir) {
9060
9060
  const sessionId = file.replace(".jsonl", "");
9061
9061
  const filePath = join8(projectsDir, file);
9062
9062
  try {
9063
- const content = readFileSync9(filePath, "utf-8");
9063
+ const content = readFileSync8(filePath, "utf-8");
9064
9064
  const lines = content.split("\n").filter(Boolean);
9065
9065
  for (let i = 0; i < lines.length; i++) {
9066
9066
  try {
@@ -9136,7 +9136,7 @@ function extractTextContent(content) {
9136
9136
  return "";
9137
9137
  }
9138
9138
  function parseTranscriptFile(filePath) {
9139
- const content = readFileSync9(filePath, "utf-8");
9139
+ const content = readFileSync8(filePath, "utf-8");
9140
9140
  const lines = content.split("\n").filter(Boolean);
9141
9141
  const messages = [];
9142
9142
  for (let i = 0; i < lines.length; i++) {
@@ -9206,7 +9206,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
9206
9206
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
9207
9207
  }
9208
9208
  try {
9209
- const content = readFileSync9(join8(projectsDir, file), "utf-8");
9209
+ const content = readFileSync8(join8(projectsDir, file), "utf-8");
9210
9210
  const lineCount = content.split("\n").filter(Boolean).length;
9211
9211
  writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9212
9212
  } catch {
@@ -9260,7 +9260,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
9260
9260
  const sessionId = file.replace(".jsonl", "");
9261
9261
  const filePath = join8(projectsDir, file);
9262
9262
  try {
9263
- const content = readFileSync9(filePath, "utf-8");
9263
+ const content = readFileSync8(filePath, "utf-8");
9264
9264
  const lineCount = content.split("\n").filter(Boolean).length;
9265
9265
  writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9266
9266
  } catch {
@@ -9340,7 +9340,7 @@ rl.on('line', async (line) => {
9340
9340
  });
9341
9341
 
9342
9342
  // cli/local-cc/install.ts
9343
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync10, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
9343
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync9, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
9344
9344
  import { join as join9 } from "path";
9345
9345
  import { homedir as homedir9 } from "os";
9346
9346
  import { spawnSync as spawnSync4 } from "child_process";
@@ -9379,7 +9379,7 @@ function safelyMutateClaudeJson(mutator) {
9379
9379
  if (!existsSync11(CLAUDE_JSON_PATH)) {
9380
9380
  return;
9381
9381
  }
9382
- const originalText = readFileSync10(CLAUDE_JSON_PATH, "utf-8");
9382
+ const originalText = readFileSync9(CLAUDE_JSON_PATH, "utf-8");
9383
9383
  let parsed;
9384
9384
  try {
9385
9385
  parsed = JSON.parse(originalText);
@@ -9949,7 +9949,7 @@ var init_disconnect = __esm({
9949
9949
  });
9950
9950
 
9951
9951
  // cli/local-cc/turnLog.ts
9952
- import { appendFileSync, existsSync as existsSync13, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync11, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
9952
+ import { appendFileSync, existsSync as existsSync13, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync10, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
9953
9953
  import { dirname as dirname6, join as join11 } from "path";
9954
9954
  import { homedir as homedir11 } from "os";
9955
9955
  function truncate(s, max = PREVIEW_MAX) {
@@ -9991,7 +9991,7 @@ function readRecentTurns(n = 20) {
9991
9991
  try {
9992
9992
  const size = statSync2(TURN_LOG_PATH).size;
9993
9993
  if (size === 0) return [];
9994
- const text = readFileSync11(TURN_LOG_PATH, "utf-8");
9994
+ const text = readFileSync10(TURN_LOG_PATH, "utf-8");
9995
9995
  const lines = text.split("\n").filter(Boolean);
9996
9996
  const lastN = lines.slice(-n).reverse();
9997
9997
  return lastN.map((line) => {
@@ -10437,13 +10437,13 @@ var init_pueue = __esm({
10437
10437
  });
10438
10438
 
10439
10439
  // cli/local-cc/settings.ts
10440
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
10440
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
10441
10441
  import { homedir as homedir13 } from "os";
10442
10442
  import { join as join13 } from "path";
10443
10443
  function isLocalCCEnabled() {
10444
10444
  if (!existsSync14(CONFIG_PATH3)) return false;
10445
10445
  try {
10446
- const content = readFileSync12(CONFIG_PATH3, "utf-8");
10446
+ const content = readFileSync11(CONFIG_PATH3, "utf-8");
10447
10447
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
10448
10448
  return match?.[1] === "yes";
10449
10449
  } catch {
@@ -10467,7 +10467,7 @@ import { spawnSync as spawnSync7 } from "child_process";
10467
10467
  import { homedir as homedir14 } from "os";
10468
10468
  import { join as join14 } from "path";
10469
10469
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
10470
- import { existsSync as existsSync15, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
10470
+ import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
10471
10471
  function deploymentMode() {
10472
10472
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
10473
10473
  if (env === "docker") return "docker";
@@ -10574,14 +10574,14 @@ TROUBLESHOOTING
10574
10574
  }
10575
10575
  function readGatewayUrl() {
10576
10576
  if (existsSync15(CONFIG_PATH4)) {
10577
- const m = readFileSync13(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
10577
+ const m = readFileSync12(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
10578
10578
  if (m) return m[1];
10579
10579
  }
10580
10580
  return "https://api.synkro.sh";
10581
10581
  }
10582
10582
  function updateLocalInferenceFlag(enabled) {
10583
10583
  if (!existsSync15(CONFIG_PATH4)) return;
10584
- let content = readFileSync13(CONFIG_PATH4, "utf-8");
10584
+ let content = readFileSync12(CONFIG_PATH4, "utf-8");
10585
10585
  const flag = enabled ? "yes" : "no";
10586
10586
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
10587
10587
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -11124,13 +11124,13 @@ var config_exports = {};
11124
11124
  __export(config_exports, {
11125
11125
  configCommand: () => configCommand
11126
11126
  });
11127
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync16 } from "fs";
11127
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16 } from "fs";
11128
11128
  import { join as join15 } from "path";
11129
11129
  import { homedir as homedir15 } from "os";
11130
11130
  function readConfigEnv() {
11131
11131
  if (!existsSync16(CONFIG_PATH5)) return {};
11132
11132
  const out = {};
11133
- for (const line of readFileSync14(CONFIG_PATH5, "utf-8").split("\n")) {
11133
+ for (const line of readFileSync13(CONFIG_PATH5, "utf-8").split("\n")) {
11134
11134
  const t = line.trim();
11135
11135
  if (!t || t.startsWith("#")) continue;
11136
11136
  const eq = t.indexOf("=");
@@ -11143,7 +11143,7 @@ function updateConfigValue(key, value) {
11143
11143
  console.error("No config found. Run `synkro install` first.");
11144
11144
  process.exit(1);
11145
11145
  }
11146
- const lines = readFileSync14(CONFIG_PATH5, "utf-8").split("\n");
11146
+ const lines = readFileSync13(CONFIG_PATH5, "utf-8").split("\n");
11147
11147
  const pattern = new RegExp(`^${key}=`);
11148
11148
  let found = false;
11149
11149
  const updated = lines.map((line) => {
@@ -11270,14 +11270,14 @@ var init_config = __esm({
11270
11270
  });
11271
11271
 
11272
11272
  // cli/bootstrap.js
11273
- import { readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
11273
+ import { readFileSync as readFileSync14, existsSync as existsSync17 } from "fs";
11274
11274
  import { resolve as resolve3 } from "path";
11275
11275
  var envCandidates = [
11276
11276
  resolve3(process.env.HOME ?? "", ".synkro", "config.env")
11277
11277
  ];
11278
11278
  for (const envPath of envCandidates) {
11279
11279
  if (!existsSync17(envPath)) continue;
11280
- const envContent = readFileSync15(envPath, "utf-8");
11280
+ const envContent = readFileSync14(envPath, "utf-8");
11281
11281
  for (const line of envContent.split("\n")) {
11282
11282
  const trimmed = line.trim();
11283
11283
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -11292,7 +11292,7 @@ var args = process.argv.slice(2);
11292
11292
  var cmd = args[0] || "";
11293
11293
  var subArgs = args.slice(1);
11294
11294
  function printVersion() {
11295
- console.log("1.6.35");
11295
+ console.log("1.6.37");
11296
11296
  }
11297
11297
  function printHelp2() {
11298
11298
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents