@synkro-sh/cli 1.6.77 → 1.6.79

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
@@ -2073,6 +2073,12 @@ interface SessionAction {
2073
2073
  outcome?: string;
2074
2074
  }
2075
2075
 
2076
+ // True when the grader runs in Synkro Cloud (CF Containers) vs the on-device
2077
+ // container. Defined here in the FULL common module too \u2014 it's referenced by
2078
+ // appendSessionAction/dispatch helpers below; previously it lived only in the
2079
+ // stub common, so the real editGuard hook hit a ReferenceError in cloud mode.
2080
+ function deployIsCloud(): boolean { return process.env.SYNKRO_DEPLOY_LOCATION === 'cloud'; }
2081
+
2076
2082
  function sessionLogPath(sessionId: string): string | null {
2077
2083
  if (!sessionId || !SAFE_SESSION_ID.test(sessionId)) return null;
2078
2084
  return join(SESSIONS_DIR, sessionId + '.jsonl');
@@ -3427,7 +3433,7 @@ export function emitUsageTick(params: {
3427
3433
  if (isCursorHookFormat() && model && !model.startsWith('cursor/') && model !== 'cursor') {
3428
3434
  model = 'cursor/' + model;
3429
3435
  }
3430
- appendLocalTelemetry({
3436
+ const body = {
3431
3437
  capture_type: 'usage_tick',
3432
3438
  event_id: mintEventId('usage'),
3433
3439
  hook_type: hookType,
@@ -3443,7 +3449,14 @@ export function emitUsageTick(params: {
3443
3449
  cache_read_input_tokens: usage.totals.cr,
3444
3450
  },
3445
3451
  ...(gitRepo ? { repo: gitRepo } : {}),
3446
- });
3452
+ };
3453
+ appendLocalTelemetry(body); // local spool \u2192 usage_ticks (no-op in cloud mode)
3454
+ // Cloud: ship the tick to /hook/capture \u2192 usage_ticks too. appendLocalTelemetry
3455
+ // is gated to local mode, so without this the cloud usage_ticks table stayed
3456
+ // EMPTY \u2014 no token/cost data for the Agents page. (/hook/capture is on the
3457
+ // long-lived MCP-token allowlist; loadJwt returns that token in cloud.)
3458
+ const jwt = loadJwt();
3459
+ if (jwt) shipCloud(jwt, '/api/v1/hook/capture', body);
3447
3460
  }
3448
3461
 
3449
3462
  export function cursorModelFromPayload(payload: Record<string, unknown>): string {
@@ -3924,13 +3937,13 @@ async function main() {
3924
3937
  ruleFilterText(graderContent, transcript.userIntent || lastPrompt),
3925
3938
  config.rules,
3926
3939
  );
3927
- const graderPrompt = [
3940
+ const buildGraderPrompt = (contentWindow: string) => [
3928
3941
  'Working directory: ' + (cwd || '.'),
3929
3942
  'Repo: ' + (gitRepo || 'unknown'),
3930
3943
  sessionLog,
3931
3944
  'File: ' + filePath,
3932
- 'Proposed content (first 4000 chars):',
3933
- proposedShort,
3945
+ 'Proposed content:',
3946
+ contentWindow,
3934
3947
  'User intent (last human message): ' + (transcript.userIntent || 'none stated'),
3935
3948
  'Last user prompt: ' + (lastPrompt || 'none'),
3936
3949
  'Org rules: ' + JSON.stringify(relevantRules),
@@ -3938,6 +3951,7 @@ async function main() {
3938
3951
  'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent \u2014 only a response to a shown block counts.',
3939
3952
  'The rules shown were pre-selected as the ones relevant to this edit \u2014 every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no hardcoded secrets in file. R005: in-repo path only." Cover every rule shown.',
3940
3953
  ].join('\\n');
3954
+ const graderPrompt = buildGraderPrompt(proposedShort);
3941
3955
 
3942
3956
  // \u2500\u2500\u2500 Combined org-rules + CWE in ONE inference (SYNKRO_COMBINED_EDIT_GRADE) \u2500\u2500\u2500
3943
3957
  // Self-contained early-return branch \u2014 the default two-grade path below is
@@ -3956,13 +3970,27 @@ async function main() {
3956
3970
  if (cr.ok) cweRules = ((await cr.json() as any) || {}).rules || [];
3957
3971
  } catch { /* CWE rules optional \u2014 rule grading still runs in the combined pass */ }
3958
3972
 
3959
- const combinedPrompt = graderPrompt
3960
- + '\\n\\nCWE rules to ALSO check the proposed content against (emit cwe-### violations with a <code_snippet>): '
3961
- + JSON.stringify(cweRules);
3962
-
3963
- let cResp: string;
3973
+ const cweSuffix = '\\n\\nCWE rules to ALSO check the proposed content against (emit cwe-### violations with a <code_snippet>): ' + JSON.stringify(cweRules);
3974
+ const buildCombined = (win: string) => buildGraderPrompt(win) + cweSuffix;
3975
+
3976
+ // Large-file strategy \u2014 mirrors local cweScanner.ts: rather than one giant
3977
+ // single inference (which blows the 45s cloud budget AND truncated to the
3978
+ // first 4000 chars, missing changes deeper in the file), split content over
3979
+ // the threshold into TWO overlapping halves and grade them IN PARALLEL.
3980
+ // Each half is ~half the size \u2192 faster \u2192 fits the timeout; the whole file
3981
+ // is covered; findings are merged.
3982
+ const SPLIT_THRESHOLD = 4000, OVERLAP = 500;
3983
+ let cResponses: string[];
3964
3984
  try {
3965
- cResp = await localGrade('edit-cwe', combinedPrompt, undefined, graderPool);
3985
+ if (proposed.length > SPLIT_THRESHOLD) {
3986
+ const mid = Math.floor(proposed.length / 2);
3987
+ cResponses = await Promise.all([
3988
+ localGrade('edit-cwe', buildCombined(proposed.slice(0, mid + OVERLAP)), undefined, graderPool),
3989
+ localGrade('edit-cwe', buildCombined(proposed.slice(mid - OVERLAP)), undefined, graderPool),
3990
+ ]);
3991
+ } else {
3992
+ cResponses = [await localGrade('edit-cwe', buildCombined(proposed), undefined, graderPool)];
3993
+ }
3966
3994
  } catch (err) {
3967
3995
  const errMsg = (err as Error).message || String(err);
3968
3996
  logGraderUnavailable('editGuard', fileShort, errMsg);
@@ -3970,7 +3998,18 @@ async function main() {
3970
3998
  return;
3971
3999
  }
3972
4000
 
3973
- const { ruleVerdict, cweFindings } = parseCombinedVerdict(cResp);
4001
+ // Merge across halves: a rule violation in EITHER half blocks (first one
4002
+ // wins, keeping its reason/severity); CWE findings are unioned by id.
4003
+ let ruleVerdict: any = null;
4004
+ const cweMap = new Map<string, any>();
4005
+ for (const r of cResponses) {
4006
+ const parsed = parseCombinedVerdict(r);
4007
+ if (!ruleVerdict) ruleVerdict = parsed.ruleVerdict;
4008
+ else if (!parsed.ruleVerdict.ok && ruleVerdict.ok) ruleVerdict = parsed.ruleVerdict;
4009
+ for (const f of parsed.cweFindings) if (f.id && !cweMap.has(f.id)) cweMap.set(f.id, f);
4010
+ }
4011
+ if (!ruleVerdict) ruleVerdict = { ok: true };
4012
+ const cweFindings = [...cweMap.values()];
3974
4013
  const editContent = 'file=' + filePath + ' content=' + proposed.slice(0, 2000);
3975
4014
  const violatedRules = ruleVerdict.ruleId ? [ruleVerdict.ruleId] : [];
3976
4015
  const cweBlock = cweFindings.slice(0, 5)
@@ -10670,7 +10709,7 @@ function writeConfigEnv(opts) {
10670
10709
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
10671
10710
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
10672
10711
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
10673
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.77")}`
10712
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.79")}`
10674
10713
  ];
10675
10714
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
10676
10715
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -14345,7 +14384,7 @@ var args = process.argv.slice(2);
14345
14384
  var cmd = args[0] || "";
14346
14385
  var subArgs = args.slice(1);
14347
14386
  function printVersion() {
14348
- console.log("1.6.77");
14387
+ console.log("1.6.79");
14349
14388
  }
14350
14389
  function printHelp2() {
14351
14390
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents