@synkro-sh/cli 1.6.50 → 1.6.51

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
 
@@ -7396,7 +7431,10 @@ __export(dockerInstall_exports, {
7396
7431
  dockerStop: () => dockerStop,
7397
7432
  dockerUpdate: () => dockerUpdate,
7398
7433
  imageTag: () => imageTag,
7434
+ normalizeProvider: () => normalizeProvider,
7435
+ poolLabel: () => poolLabel,
7399
7436
  readContainerConfig: () => readContainerConfig,
7437
+ resolveGraderPool: () => resolveGraderPool,
7400
7438
  resolveWorkerConfig: () => resolveWorkerConfig,
7401
7439
  splitWorkers: () => splitWorkers,
7402
7440
  waitForContainerReady: () => waitForContainerReady,
@@ -7423,6 +7461,39 @@ function normalizeProvider(p) {
7423
7461
  if (v === "cursor") return "cursor";
7424
7462
  return null;
7425
7463
  }
7464
+ function resolveGraderPool(parsed) {
7465
+ const warnings = [];
7466
+ const grader = parsed?.grader && typeof parsed.grader === "object" ? parsed.grader : {};
7467
+ const counts = {};
7468
+ const collect = (obj, where, flagUnknown) => {
7469
+ if (!obj || typeof obj !== "object") return;
7470
+ for (const [k, v] of Object.entries(obj)) {
7471
+ if (typeof v !== "number") continue;
7472
+ const np = normalizeProvider(k);
7473
+ if (np === "claude_code") counts.claude_code = Math.max(0, Math.floor(v));
7474
+ else if (np === "cursor") counts.cursor = Math.max(0, Math.floor(v));
7475
+ else if (flagUnknown) {
7476
+ warnings.push(`.synkro: ${where}.${k} is not a known provider (use claude-code or cursor) \u2014 ignored`);
7477
+ }
7478
+ }
7479
+ };
7480
+ collect(grader, "grader", true);
7481
+ collect(parsed?.workers, "workers", false);
7482
+ let pool = "auto";
7483
+ const rawPool = grader.pool;
7484
+ if (rawPool != null && rawPool !== "auto") {
7485
+ const np = normalizeProvider(String(rawPool));
7486
+ if (np) pool = np;
7487
+ else warnings.push(`.synkro: grader.pool="${rawPool}" is not recognized (use auto | claude-code | cursor) \u2014 falling back to auto`);
7488
+ }
7489
+ if (counts.claude_code != null || counts.cursor != null) {
7490
+ return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, pool, warnings };
7491
+ }
7492
+ return { pool, warnings };
7493
+ }
7494
+ function poolLabel(pool) {
7495
+ return pool === "claude_code" ? "claude-code" : pool;
7496
+ }
7426
7497
  function parseSynkroYaml(raw) {
7427
7498
  const result = {};
7428
7499
  const lines = raw.split("\n");
@@ -7469,18 +7540,15 @@ function parseSynkroYaml(raw) {
7469
7540
  function readSynkroFileConfig() {
7470
7541
  try {
7471
7542
  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" };
7543
+ if (!root) return { pool: "auto", warnings: [] };
7473
7544
  const fp = join6(root, ".synkro");
7474
- if (!existsSync8(fp)) return { pool: "auto" };
7545
+ if (!existsSync8(fp)) return { pool: "auto", warnings: [] };
7475
7546
  const raw = readFileSync6(fp, "utf-8");
7476
7547
  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 };
7548
+ return resolveGraderPool(parsed);
7481
7549
  } catch {
7482
7550
  }
7483
- return { pool: "auto" };
7551
+ return { pool: "auto", warnings: [] };
7484
7552
  }
7485
7553
  function resolveWorkerConfig(rest) {
7486
7554
  let workers = 8;
@@ -7516,6 +7584,7 @@ function resolveWorkerConfig(rest) {
7516
7584
  let provs = providers;
7517
7585
  if (provs.length === 0) {
7518
7586
  const sc = readSynkroFileConfig();
7587
+ for (const w of sc.warnings) console.warn(` \u26A0 ${w}`);
7519
7588
  if (sc.claudeWorkers != null || sc.cursorWorkers != null) {
7520
7589
  const cw = sc.claudeWorkers || 0;
7521
7590
  const curw = sc.cursorWorkers || 0;
@@ -7523,7 +7592,7 @@ function resolveWorkerConfig(rest) {
7523
7592
  }
7524
7593
  if (sc.pool === "cursor") {
7525
7594
  provs = ["cursor"];
7526
- } else if (sc.pool === "claude") {
7595
+ } else if (sc.pool === "claude_code") {
7527
7596
  provs = ["claude_code"];
7528
7597
  } else {
7529
7598
  provs = detectAgents().map((a) => a.kind);
@@ -8647,7 +8716,7 @@ function writeConfigEnv(opts) {
8647
8716
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
8648
8717
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
8649
8718
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
8650
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.50")}`
8719
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.51")}`
8651
8720
  ];
8652
8721
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
8653
8722
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -8842,8 +8911,9 @@ async function installCommand(opts = {}) {
8842
8911
  gradingMode = existingSynkro.grader.mode === "byok" ? "byok" : "local";
8843
8912
  storageMode = "local";
8844
8913
  console.log(`Using .synkro config:`);
8914
+ for (const w of existingSynkro.warnings) console.warn(` \u26A0 ${w}`);
8845
8915
  console.log(` harness: ${existingSynkro.harness.join(", ")}`);
8846
- console.log(` grading: ${gradingMode} pool: ${existingSynkro.grader.pool}`);
8916
+ console.log(` grading: ${gradingMode} pool: ${poolLabel(existingSynkro.grader.pool)}`);
8847
8917
  for (const a of agents) {
8848
8918
  console.log(` \u2713 ${a.name}${a.version ? ` (${a.version})` : ""}`);
8849
8919
  }
@@ -9092,6 +9162,7 @@ async function installCommand(opts = {}) {
9092
9162
  }
9093
9163
  console.log("Installing Synkro server container...");
9094
9164
  const sf = readFullSynkroFile();
9165
+ for (const w of sf?.warnings || []) console.warn(` \u26A0 ${w}`);
9095
9166
  let claudeWorkers;
9096
9167
  let cursorWorkers;
9097
9168
  if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {
@@ -9104,18 +9175,18 @@ async function installCommand(opts = {}) {
9104
9175
  console.log(` .synkro: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor`);
9105
9176
  } else {
9106
9177
  const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
9107
- const synkroFilePool = sf?.grader?.pool || readSynkroFilePool();
9178
+ const synkroFilePool = sf?.grader?.pool || "auto";
9108
9179
  let providers = [];
9109
9180
  if (synkroFilePool === "cursor") {
9110
9181
  providers = ["cursor"];
9111
- } else if (synkroFilePool === "claude") {
9182
+ } else if (synkroFilePool === "claude_code") {
9112
9183
  providers = ["claude_code"];
9113
9184
  } else {
9114
9185
  if (hasClaudeCode) providers.push("claude_code");
9115
9186
  if (hasCursor) providers.push("cursor");
9116
9187
  }
9117
9188
  ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));
9118
- if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${synkroFilePool}`);
9189
+ if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${poolLabel(synkroFilePool)}`);
9119
9190
  }
9120
9191
  console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);
9121
9192
  const connectedRepo = detectGitRepo2() || void 0;
@@ -9336,19 +9407,6 @@ function writeSynkroFileIfMissing(opts) {
9336
9407
  } catch {
9337
9408
  }
9338
9409
  }
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
9410
  function readFullSynkroFile() {
9353
9411
  try {
9354
9412
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
@@ -9358,16 +9416,18 @@ function readFullSynkroFile() {
9358
9416
  const parsed = parseSynkroFileRaw(readFileSync8(fp, "utf-8"));
9359
9417
  const valid = ["claude-code", "cursor"];
9360
9418
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
9419
+ const resolved = resolveGraderPool(parsed);
9361
9420
  return {
9362
9421
  harness: harness.length > 0 ? harness : ["claude-code", "cursor"],
9363
9422
  grader: {
9364
- pool: ["auto", "claude", "cursor"].includes(parsed.grader?.pool) ? parsed.grader.pool : "auto",
9423
+ pool: resolved.pool,
9365
9424
  mode: ["local", "byok"].includes(parsed.grader?.mode) ? parsed.grader.mode : "local"
9366
9425
  },
9367
9426
  workers: {
9368
- ...typeof parsed.workers?.claude === "number" ? { claude: parsed.workers.claude } : {},
9369
- ...typeof parsed.workers?.cursor === "number" ? { cursor: parsed.workers.cursor } : {}
9427
+ ...resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {},
9428
+ ...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}
9370
9429
  },
9430
+ warnings: resolved.warnings,
9371
9431
  ruleset: parsed.ruleset || "default",
9372
9432
  skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
9373
9433
  scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
@@ -9385,7 +9445,8 @@ function reconcileHarness() {
9385
9445
  }
9386
9446
  const wantCC = sf.harness.includes("claude-code");
9387
9447
  const wantCursor = sf.harness.includes("cursor");
9388
- console.log(`.synkro: harness=[${sf.harness.join(", ")}] pool=${sf.grader.pool} mode=${sf.grader.mode}`);
9448
+ for (const w of sf.warnings) console.warn(` \u26A0 ${w}`);
9449
+ console.log(`.synkro: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
9389
9450
  const scripts = writeHookScripts(resolvePersistedHookMode());
9390
9451
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
9391
9452
  const ccSettings = join8(homedir8(), ".claude", "settings.json");
@@ -9454,7 +9515,7 @@ function reconcileHarness() {
9454
9515
  const providers = [];
9455
9516
  if (sf.grader.pool === "cursor") {
9456
9517
  providers.push("cursor");
9457
- } else if (sf.grader.pool === "claude") {
9518
+ } else if (sf.grader.pool === "claude_code") {
9458
9519
  providers.push("claude_code");
9459
9520
  } else {
9460
9521
  if (wantCC) providers.push("claude_code");
@@ -11839,7 +11900,7 @@ var args = process.argv.slice(2);
11839
11900
  var cmd = args[0] || "";
11840
11901
  var subArgs = args.slice(1);
11841
11902
  function printVersion() {
11842
- console.log("1.6.50");
11903
+ console.log("1.6.51");
11843
11904
  }
11844
11905
  function printHelp2() {
11845
11906
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents