@synkro-sh/cli 1.6.49 → 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.49")}`
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)}`);
@@ -8830,8 +8899,8 @@ async function installCommand(opts = {}) {
8830
8899
  let agents;
8831
8900
  let gradingMode;
8832
8901
  let storageMode;
8833
- let transcriptCC = true;
8834
- let transcriptCursor = true;
8902
+ let transcriptCC = false;
8903
+ let transcriptCursor = false;
8835
8904
  if (existingSynkro) {
8836
8905
  const wantCC = existingSynkro.harness.includes("claude-code");
8837
8906
  const wantCursor = existingSynkro.harness.includes("cursor");
@@ -8841,11 +8910,10 @@ async function installCommand(opts = {}) {
8841
8910
  if (agents.length === 0 && detected.length > 0) agents = detected;
8842
8911
  gradingMode = existingSynkro.grader.mode === "byok" ? "byok" : "local";
8843
8912
  storageMode = "local";
8844
- transcriptCC = agents.some((a) => a.kind === "claude_code");
8845
- transcriptCursor = agents.some((a) => a.kind === "cursor");
8846
8913
  console.log(`Using .synkro config:`);
8914
+ for (const w of existingSynkro.warnings) console.warn(` \u26A0 ${w}`);
8847
8915
  console.log(` harness: ${existingSynkro.harness.join(", ")}`);
8848
- console.log(` grading: ${gradingMode} pool: ${existingSynkro.grader.pool}`);
8916
+ console.log(` grading: ${gradingMode} pool: ${poolLabel(existingSynkro.grader.pool)}`);
8849
8917
  for (const a of agents) {
8850
8918
  console.log(` \u2713 ${a.name}${a.version ? ` (${a.version})` : ""}`);
8851
8919
  }
@@ -8873,18 +8941,23 @@ async function installCommand(opts = {}) {
8873
8941
  console.log(" BYOK grading uses your own provider key \u2014 register one in the");
8874
8942
  console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.\n");
8875
8943
  }
8944
+ }
8945
+ {
8876
8946
  const wantCC = agents.some((a) => a.kind === "claude_code");
8877
8947
  const wantCursor = agents.some((a) => a.kind === "cursor");
8878
- if (process.stdin.isTTY) {
8948
+ if ((wantCC || wantCursor) && process.stdin.isTTY) {
8879
8949
  const src = await promptTranscriptSources(wantCC, wantCursor);
8880
8950
  transcriptCC = src.cc;
8881
8951
  transcriptCursor = src.cursor;
8882
8952
  const chosen = [src.cc && "Claude Code", src.cursor && "Cursor"].filter(Boolean).join(" + ");
8883
8953
  console.log(chosen ? ` \u2713 Session import: ${chosen}
8884
8954
  ` : " \u2717 Session import skipped\n");
8885
- } else {
8955
+ } else if ((wantCC || wantCursor) && process.env.SYNKRO_IMPORT_TRANSCRIPTS === "yes") {
8886
8956
  transcriptCC = wantCC;
8887
8957
  transcriptCursor = wantCursor;
8958
+ console.log(" \u2713 Session import: SYNKRO_IMPORT_TRANSCRIPTS=yes\n");
8959
+ } else if (!process.stdin.isTTY) {
8960
+ console.log(" \u2717 Session import skipped (non-interactive; set SYNKRO_IMPORT_TRANSCRIPTS=yes to opt in)\n");
8888
8961
  }
8889
8962
  }
8890
8963
  const transcriptConsent = transcriptCC || transcriptCursor;
@@ -9089,6 +9162,7 @@ async function installCommand(opts = {}) {
9089
9162
  }
9090
9163
  console.log("Installing Synkro server container...");
9091
9164
  const sf = readFullSynkroFile();
9165
+ for (const w of sf?.warnings || []) console.warn(` \u26A0 ${w}`);
9092
9166
  let claudeWorkers;
9093
9167
  let cursorWorkers;
9094
9168
  if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {
@@ -9101,18 +9175,18 @@ async function installCommand(opts = {}) {
9101
9175
  console.log(` .synkro: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor`);
9102
9176
  } else {
9103
9177
  const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
9104
- const synkroFilePool = sf?.grader?.pool || readSynkroFilePool();
9178
+ const synkroFilePool = sf?.grader?.pool || "auto";
9105
9179
  let providers = [];
9106
9180
  if (synkroFilePool === "cursor") {
9107
9181
  providers = ["cursor"];
9108
- } else if (synkroFilePool === "claude") {
9182
+ } else if (synkroFilePool === "claude_code") {
9109
9183
  providers = ["claude_code"];
9110
9184
  } else {
9111
9185
  if (hasClaudeCode) providers.push("claude_code");
9112
9186
  if (hasCursor) providers.push("cursor");
9113
9187
  }
9114
9188
  ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));
9115
- if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${synkroFilePool}`);
9189
+ if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${poolLabel(synkroFilePool)}`);
9116
9190
  }
9117
9191
  console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);
9118
9192
  const connectedRepo = detectGitRepo2() || void 0;
@@ -9333,19 +9407,6 @@ function writeSynkroFileIfMissing(opts) {
9333
9407
  } catch {
9334
9408
  }
9335
9409
  }
9336
- function readSynkroFilePool() {
9337
- try {
9338
- const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
9339
- if (!root) return "auto";
9340
- const fp = join8(root, ".synkro");
9341
- if (!existsSync10(fp)) return "auto";
9342
- const parsed = parseSynkroFileRaw(readFileSync8(fp, "utf-8"));
9343
- const pool = parsed?.grader?.pool;
9344
- if (pool === "cursor" || pool === "claude") return pool;
9345
- } catch {
9346
- }
9347
- return "auto";
9348
- }
9349
9410
  function readFullSynkroFile() {
9350
9411
  try {
9351
9412
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
@@ -9355,16 +9416,18 @@ function readFullSynkroFile() {
9355
9416
  const parsed = parseSynkroFileRaw(readFileSync8(fp, "utf-8"));
9356
9417
  const valid = ["claude-code", "cursor"];
9357
9418
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
9419
+ const resolved = resolveGraderPool(parsed);
9358
9420
  return {
9359
9421
  harness: harness.length > 0 ? harness : ["claude-code", "cursor"],
9360
9422
  grader: {
9361
- pool: ["auto", "claude", "cursor"].includes(parsed.grader?.pool) ? parsed.grader.pool : "auto",
9423
+ pool: resolved.pool,
9362
9424
  mode: ["local", "byok"].includes(parsed.grader?.mode) ? parsed.grader.mode : "local"
9363
9425
  },
9364
9426
  workers: {
9365
- ...typeof parsed.workers?.claude === "number" ? { claude: parsed.workers.claude } : {},
9366
- ...typeof parsed.workers?.cursor === "number" ? { cursor: parsed.workers.cursor } : {}
9427
+ ...resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {},
9428
+ ...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}
9367
9429
  },
9430
+ warnings: resolved.warnings,
9368
9431
  ruleset: parsed.ruleset || "default",
9369
9432
  skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
9370
9433
  scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
@@ -9382,7 +9445,8 @@ function reconcileHarness() {
9382
9445
  }
9383
9446
  const wantCC = sf.harness.includes("claude-code");
9384
9447
  const wantCursor = sf.harness.includes("cursor");
9385
- 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}`);
9386
9450
  const scripts = writeHookScripts(resolvePersistedHookMode());
9387
9451
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
9388
9452
  const ccSettings = join8(homedir8(), ".claude", "settings.json");
@@ -9451,7 +9515,7 @@ function reconcileHarness() {
9451
9515
  const providers = [];
9452
9516
  if (sf.grader.pool === "cursor") {
9453
9517
  providers.push("cursor");
9454
- } else if (sf.grader.pool === "claude") {
9518
+ } else if (sf.grader.pool === "claude_code") {
9455
9519
  providers.push("claude_code");
9456
9520
  } else {
9457
9521
  if (wantCC) providers.push("claude_code");
@@ -11836,7 +11900,7 @@ var args = process.argv.slice(2);
11836
11900
  var cmd = args[0] || "";
11837
11901
  var subArgs = args.slice(1);
11838
11902
  function printVersion() {
11839
- console.log("1.6.49");
11903
+ console.log("1.6.51");
11840
11904
  }
11841
11905
  function printHelp2() {
11842
11906
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents