@synkro-sh/cli 1.6.50 → 1.6.52

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
@@ -1239,6 +1239,29 @@ function parseSynkroYaml(raw: string): Record<string, any> {
1239
1239
 
1240
1240
  let _synkroFileCache: SynkroFileConfig | undefined;
1241
1241
 
1242
+ /** Map any provider alias to the internal pool token. */
1243
+ function normalizePoolToken(p: unknown): 'auto' | 'claude' | 'cursor' {
1244
+ if (p === 'cursor') return 'cursor';
1245
+ if (p === 'claude' || p === 'claude-code' || p === 'cc' || p === 'claude_code') return 'claude';
1246
+ return 'auto';
1247
+ }
1248
+
1249
+ /**
1250
+ * Pull an explicit worker count for a provider, accepting both the unified
1251
+ * grader-block form (grader: claude-code: N) and the legacy workers block
1252
+ * (workers: claude: N). Provider aliases (claude / claude-code / cc) all match.
1253
+ */
1254
+ function pickWorkerCount(parsed: any, kind: 'claude_code' | 'cursor'): number | undefined {
1255
+ const keys = kind === 'cursor' ? ['cursor'] : ['claude-code', 'claude', 'cc', 'claude_code'];
1256
+ for (const src of [parsed?.grader, parsed?.workers]) {
1257
+ if (!src || typeof src !== 'object') continue;
1258
+ for (const k of keys) {
1259
+ if (typeof src[k] === 'number') return Math.max(0, Math.floor(src[k]));
1260
+ }
1261
+ }
1262
+ return undefined;
1263
+ }
1264
+
1242
1265
  export function loadSynkroFile(cwd?: string): SynkroFileConfig {
1243
1266
  if (_synkroFileCache) return _synkroFileCache;
1244
1267
  const root = cwd ? findGitRoot(cwd) : '';
@@ -1256,12 +1279,14 @@ export function loadSynkroFile(cwd?: string): SynkroFileConfig {
1256
1279
  version: parsed.version || 1,
1257
1280
  harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],
1258
1281
  grader: {
1259
- pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',
1282
+ pool: normalizePoolToken(parsed.grader?.pool),
1260
1283
  mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : undefined,
1261
1284
  },
1262
1285
  workers: {
1263
- ...(typeof parsed.workers?.claude === 'number' ? { claude: parsed.workers.claude } : {}),
1264
- ...(typeof parsed.workers?.cursor === 'number' ? { cursor: parsed.workers.cursor } : {}),
1286
+ // Counts may live under the grader block (unified form) or the legacy
1287
+ // workers block, keyed by claude / claude-code / cc / cursor.
1288
+ ...(pickWorkerCount(parsed, 'claude_code') != null ? { claude: pickWorkerCount(parsed, 'claude_code') } : {}),
1289
+ ...(pickWorkerCount(parsed, 'cursor') != null ? { cursor: pickWorkerCount(parsed, 'cursor') } : {}),
1265
1290
  },
1266
1291
  ruleset: typeof parsed.ruleset === 'string' ? parsed.ruleset : 'default',
1267
1292
  skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string') : [],
@@ -1278,8 +1303,9 @@ export function loadSynkroFile(cwd?: string): SynkroFileConfig {
1278
1303
  }
1279
1304
 
1280
1305
  export function effectiveGraderPool(synkroFile: SynkroFileConfig, hookAgentKind: AgentKind): AgentKind {
1281
- if (synkroFile.grader.pool === 'auto') return hookAgentKind;
1282
- if (synkroFile.grader.pool === 'claude') return 'claude_code';
1306
+ const pool = normalizePoolToken(synkroFile.grader.pool);
1307
+ if (pool === 'auto') return hookAgentKind;
1308
+ if (pool === 'claude') return 'claude_code';
1283
1309
  return 'cursor';
1284
1310
  }
1285
1311
 
@@ -1294,7 +1320,16 @@ export function synkroFilePresent(cwd?: string): boolean {
1294
1320
  try {
1295
1321
  const root = cwd ? findGitRoot(cwd) : '';
1296
1322
  if (!root) return false;
1297
- return existsSync(root + '/.synkro');
1323
+ // ~/.synkro is Synkro's CONFIG DIRECTORY, not a repo marker. If the git root
1324
+ // resolves to HOME (dotfiles tracked in git, or working in a non-git folder
1325
+ // under ~), never treat it as onboarded \u2014 otherwise grading fires in every
1326
+ // directory under home.
1327
+ if (root === HOME) return false;
1328
+ // A repo is onboarded ONLY via a regular .synkro FILE at its root. statSync +
1329
+ // isFile is required because existsSync also matches a DIRECTORY named
1330
+ // .synkro (exactly what ~/.synkro is) \u2014 the root cause of grading running in
1331
+ // repos that have no .synkro file.
1332
+ return statSync(root + '/.synkro').isFile();
1298
1333
  } catch { return false; }
1299
1334
  }
1300
1335
 
@@ -6228,9 +6263,12 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
6228
6263
  const sessionId = String(payload.session_id || payload.conversation_id || '');
6229
6264
  const root = gitRoot(cwd);
6230
6265
 
6231
- // Dormancy: a repo is onboarded only if it has a .synkro at its git root.
6266
+ // Dormancy: a repo is onboarded only if it has a .synkro FILE at its git
6267
+ // root. Guard root !== HOME — ~/.synkro is the config DIRECTORY, so without
6268
+ // this a home-rooted cwd (dotfiles in git, non-git dir under home) would
6269
+ // "find" the config dir and look onboarded.
6232
6270
  let synkroFileText = '';
6233
- if (root && existsSync(join(root, '.synkro'))) {
6271
+ if (root && root !== HOME && existsSync(join(root, '.synkro'))) {
6234
6272
  try { synkroFileText = readFileSync(join(root, '.synkro'), 'utf-8'); } catch {}
6235
6273
  }
6236
6274
  if (!synkroFileText) {
@@ -7396,7 +7434,10 @@ __export(dockerInstall_exports, {
7396
7434
  dockerStop: () => dockerStop,
7397
7435
  dockerUpdate: () => dockerUpdate,
7398
7436
  imageTag: () => imageTag,
7437
+ normalizeProvider: () => normalizeProvider,
7438
+ poolLabel: () => poolLabel,
7399
7439
  readContainerConfig: () => readContainerConfig,
7440
+ resolveGraderPool: () => resolveGraderPool,
7400
7441
  resolveWorkerConfig: () => resolveWorkerConfig,
7401
7442
  splitWorkers: () => splitWorkers,
7402
7443
  waitForContainerReady: () => waitForContainerReady,
@@ -7423,6 +7464,39 @@ function normalizeProvider(p) {
7423
7464
  if (v === "cursor") return "cursor";
7424
7465
  return null;
7425
7466
  }
7467
+ function resolveGraderPool(parsed) {
7468
+ const warnings = [];
7469
+ const grader = parsed?.grader && typeof parsed.grader === "object" ? parsed.grader : {};
7470
+ const counts = {};
7471
+ const collect = (obj, where, flagUnknown) => {
7472
+ if (!obj || typeof obj !== "object") return;
7473
+ for (const [k, v] of Object.entries(obj)) {
7474
+ if (typeof v !== "number") continue;
7475
+ const np = normalizeProvider(k);
7476
+ if (np === "claude_code") counts.claude_code = Math.max(0, Math.floor(v));
7477
+ else if (np === "cursor") counts.cursor = Math.max(0, Math.floor(v));
7478
+ else if (flagUnknown) {
7479
+ warnings.push(`.synkro: ${where}.${k} is not a known provider (use claude-code or cursor) \u2014 ignored`);
7480
+ }
7481
+ }
7482
+ };
7483
+ collect(grader, "grader", true);
7484
+ collect(parsed?.workers, "workers", false);
7485
+ let pool = "auto";
7486
+ const rawPool = grader.pool;
7487
+ if (rawPool != null && rawPool !== "auto") {
7488
+ const np = normalizeProvider(String(rawPool));
7489
+ if (np) pool = np;
7490
+ else warnings.push(`.synkro: grader.pool="${rawPool}" is not recognized (use auto | claude-code | cursor) \u2014 falling back to auto`);
7491
+ }
7492
+ if (counts.claude_code != null || counts.cursor != null) {
7493
+ return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, pool, warnings };
7494
+ }
7495
+ return { pool, warnings };
7496
+ }
7497
+ function poolLabel(pool) {
7498
+ return pool === "claude_code" ? "claude-code" : pool;
7499
+ }
7426
7500
  function parseSynkroYaml(raw) {
7427
7501
  const result = {};
7428
7502
  const lines = raw.split("\n");
@@ -7469,18 +7543,15 @@ function parseSynkroYaml(raw) {
7469
7543
  function readSynkroFileConfig() {
7470
7544
  try {
7471
7545
  const root = execSync4("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
7472
- if (!root) return { pool: "auto" };
7546
+ if (!root) return { pool: "auto", warnings: [] };
7473
7547
  const fp = join6(root, ".synkro");
7474
- if (!existsSync8(fp)) return { pool: "auto" };
7548
+ if (!existsSync8(fp)) return { pool: "auto", warnings: [] };
7475
7549
  const raw = readFileSync6(fp, "utf-8");
7476
7550
  const parsed = raw.trimStart().startsWith("{") ? JSON.parse(raw) : parseSynkroYaml(raw);
7477
- const pool = ["auto", "claude", "cursor"].includes(parsed?.grader?.pool) ? parsed.grader.pool : "auto";
7478
- const cw = typeof parsed?.workers?.claude === "number" ? Math.max(0, Math.floor(parsed.workers.claude)) : void 0;
7479
- const curw = typeof parsed?.workers?.cursor === "number" ? Math.max(0, Math.floor(parsed.workers.cursor)) : void 0;
7480
- return { pool, claudeWorkers: cw, cursorWorkers: curw };
7551
+ return resolveGraderPool(parsed);
7481
7552
  } catch {
7482
7553
  }
7483
- return { pool: "auto" };
7554
+ return { pool: "auto", warnings: [] };
7484
7555
  }
7485
7556
  function resolveWorkerConfig(rest) {
7486
7557
  let workers = 8;
@@ -7516,6 +7587,7 @@ function resolveWorkerConfig(rest) {
7516
7587
  let provs = providers;
7517
7588
  if (provs.length === 0) {
7518
7589
  const sc = readSynkroFileConfig();
7590
+ for (const w of sc.warnings) console.warn(` \u26A0 ${w}`);
7519
7591
  if (sc.claudeWorkers != null || sc.cursorWorkers != null) {
7520
7592
  const cw = sc.claudeWorkers || 0;
7521
7593
  const curw = sc.cursorWorkers || 0;
@@ -7523,7 +7595,7 @@ function resolveWorkerConfig(rest) {
7523
7595
  }
7524
7596
  if (sc.pool === "cursor") {
7525
7597
  provs = ["cursor"];
7526
- } else if (sc.pool === "claude") {
7598
+ } else if (sc.pool === "claude_code") {
7527
7599
  provs = ["claude_code"];
7528
7600
  } else {
7529
7601
  provs = detectAgents().map((a) => a.kind);
@@ -8324,7 +8396,7 @@ __export(install_exports, {
8324
8396
  syncSkillFiles: () => syncSkillFiles,
8325
8397
  writeHookScripts: () => writeHookScripts
8326
8398
  });
8327
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync8, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
8399
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync8, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
8328
8400
  import { homedir as homedir8 } from "os";
8329
8401
  import { join as join8 } from "path";
8330
8402
  import { execSync as execSync6 } from "child_process";
@@ -8647,7 +8719,7 @@ function writeConfigEnv(opts) {
8647
8719
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
8648
8720
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
8649
8721
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
8650
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.50")}`
8722
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.52")}`
8651
8723
  ];
8652
8724
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
8653
8725
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -8842,8 +8914,9 @@ async function installCommand(opts = {}) {
8842
8914
  gradingMode = existingSynkro.grader.mode === "byok" ? "byok" : "local";
8843
8915
  storageMode = "local";
8844
8916
  console.log(`Using .synkro config:`);
8917
+ for (const w of existingSynkro.warnings) console.warn(` \u26A0 ${w}`);
8845
8918
  console.log(` harness: ${existingSynkro.harness.join(", ")}`);
8846
- console.log(` grading: ${gradingMode} pool: ${existingSynkro.grader.pool}`);
8919
+ console.log(` grading: ${gradingMode} pool: ${poolLabel(existingSynkro.grader.pool)}`);
8847
8920
  for (const a of agents) {
8848
8921
  console.log(` \u2713 ${a.name}${a.version ? ` (${a.version})` : ""}`);
8849
8922
  }
@@ -9092,6 +9165,7 @@ async function installCommand(opts = {}) {
9092
9165
  }
9093
9166
  console.log("Installing Synkro server container...");
9094
9167
  const sf = readFullSynkroFile();
9168
+ for (const w of sf?.warnings || []) console.warn(` \u26A0 ${w}`);
9095
9169
  let claudeWorkers;
9096
9170
  let cursorWorkers;
9097
9171
  if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {
@@ -9104,18 +9178,18 @@ async function installCommand(opts = {}) {
9104
9178
  console.log(` .synkro: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor`);
9105
9179
  } else {
9106
9180
  const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
9107
- const synkroFilePool = sf?.grader?.pool || readSynkroFilePool();
9181
+ const synkroFilePool = sf?.grader?.pool || "auto";
9108
9182
  let providers = [];
9109
9183
  if (synkroFilePool === "cursor") {
9110
9184
  providers = ["cursor"];
9111
- } else if (synkroFilePool === "claude") {
9185
+ } else if (synkroFilePool === "claude_code") {
9112
9186
  providers = ["claude_code"];
9113
9187
  } else {
9114
9188
  if (hasClaudeCode) providers.push("claude_code");
9115
9189
  if (hasCursor) providers.push("cursor");
9116
9190
  }
9117
9191
  ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));
9118
- if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${synkroFilePool}`);
9192
+ if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${poolLabel(synkroFilePool)}`);
9119
9193
  }
9120
9194
  console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);
9121
9195
  const connectedRepo = detectGitRepo2() || void 0;
@@ -9296,8 +9370,14 @@ function writeSynkroFileIfMissing(opts) {
9296
9370
  try {
9297
9371
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
9298
9372
  if (!root) return;
9373
+ if (root === homedir8()) return;
9299
9374
  const fp = join8(root, ".synkro");
9300
- if (existsSync10(fp)) {
9375
+ let hasFile = false;
9376
+ try {
9377
+ hasFile = statSync2(fp).isFile();
9378
+ } catch {
9379
+ }
9380
+ if (hasFile) {
9301
9381
  console.log(` .synkro: ${fp} (existing, respected)`);
9302
9382
  return;
9303
9383
  }
@@ -9336,19 +9416,6 @@ function writeSynkroFileIfMissing(opts) {
9336
9416
  } catch {
9337
9417
  }
9338
9418
  }
9339
- function readSynkroFilePool() {
9340
- try {
9341
- const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
9342
- if (!root) return "auto";
9343
- const fp = join8(root, ".synkro");
9344
- if (!existsSync10(fp)) return "auto";
9345
- const parsed = parseSynkroFileRaw(readFileSync8(fp, "utf-8"));
9346
- const pool = parsed?.grader?.pool;
9347
- if (pool === "cursor" || pool === "claude") return pool;
9348
- } catch {
9349
- }
9350
- return "auto";
9351
- }
9352
9419
  function readFullSynkroFile() {
9353
9420
  try {
9354
9421
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
@@ -9358,16 +9425,18 @@ function readFullSynkroFile() {
9358
9425
  const parsed = parseSynkroFileRaw(readFileSync8(fp, "utf-8"));
9359
9426
  const valid = ["claude-code", "cursor"];
9360
9427
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
9428
+ const resolved = resolveGraderPool(parsed);
9361
9429
  return {
9362
9430
  harness: harness.length > 0 ? harness : ["claude-code", "cursor"],
9363
9431
  grader: {
9364
- pool: ["auto", "claude", "cursor"].includes(parsed.grader?.pool) ? parsed.grader.pool : "auto",
9432
+ pool: resolved.pool,
9365
9433
  mode: ["local", "byok"].includes(parsed.grader?.mode) ? parsed.grader.mode : "local"
9366
9434
  },
9367
9435
  workers: {
9368
- ...typeof parsed.workers?.claude === "number" ? { claude: parsed.workers.claude } : {},
9369
- ...typeof parsed.workers?.cursor === "number" ? { cursor: parsed.workers.cursor } : {}
9436
+ ...resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {},
9437
+ ...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}
9370
9438
  },
9439
+ warnings: resolved.warnings,
9371
9440
  ruleset: parsed.ruleset || "default",
9372
9441
  skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
9373
9442
  scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
@@ -9385,7 +9454,8 @@ function reconcileHarness() {
9385
9454
  }
9386
9455
  const wantCC = sf.harness.includes("claude-code");
9387
9456
  const wantCursor = sf.harness.includes("cursor");
9388
- console.log(`.synkro: harness=[${sf.harness.join(", ")}] pool=${sf.grader.pool} mode=${sf.grader.mode}`);
9457
+ for (const w of sf.warnings) console.warn(` \u26A0 ${w}`);
9458
+ console.log(`.synkro: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
9389
9459
  const scripts = writeHookScripts(resolvePersistedHookMode());
9390
9460
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
9391
9461
  const ccSettings = join8(homedir8(), ".claude", "settings.json");
@@ -9454,7 +9524,7 @@ function reconcileHarness() {
9454
9524
  const providers = [];
9455
9525
  if (sf.grader.pool === "cursor") {
9456
9526
  providers.push("cursor");
9457
- } else if (sf.grader.pool === "claude") {
9527
+ } else if (sf.grader.pool === "claude_code") {
9458
9528
  providers.push("claude_code");
9459
9529
  } else {
9460
9530
  if (wantCC) providers.push("claude_code");
@@ -10488,7 +10558,7 @@ var init_disconnect = __esm({
10488
10558
  });
10489
10559
 
10490
10560
  // cli/local-cc/turnLog.ts
10491
- 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";
10561
+ import { appendFileSync, existsSync as existsSync13, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync10, readSync, closeSync as closeSync2, statSync as statSync3, watchFile, unwatchFile } from "fs";
10492
10562
  import { dirname as dirname6, join as join11 } from "path";
10493
10563
  import { homedir as homedir11 } from "os";
10494
10564
  function truncate(s, max = PREVIEW_MAX) {
@@ -10528,7 +10598,7 @@ function appendTurn(args2) {
10528
10598
  function readRecentTurns(n = 20) {
10529
10599
  if (!existsSync13(TURN_LOG_PATH)) return [];
10530
10600
  try {
10531
- const size = statSync2(TURN_LOG_PATH).size;
10601
+ const size = statSync3(TURN_LOG_PATH).size;
10532
10602
  if (size === 0) return [];
10533
10603
  const text = readFileSync10(TURN_LOG_PATH, "utf-8");
10534
10604
  const lines = text.split("\n").filter(Boolean);
@@ -10554,7 +10624,7 @@ function followTurns(onEntry) {
10554
10624
  }
10555
10625
  let lastSize = (() => {
10556
10626
  try {
10557
- return statSync2(TURN_LOG_PATH).size;
10627
+ return statSync3(TURN_LOG_PATH).size;
10558
10628
  } catch {
10559
10629
  return 0;
10560
10630
  }
@@ -11839,7 +11909,7 @@ var args = process.argv.slice(2);
11839
11909
  var cmd = args[0] || "";
11840
11910
  var subArgs = args.slice(1);
11841
11911
  function printVersion() {
11842
- console.log("1.6.50");
11912
+ console.log("1.6.52");
11843
11913
  }
11844
11914
  function printHelp2() {
11845
11915
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents