@synkro-sh/cli 1.7.4 → 1.7.6

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
@@ -1492,6 +1492,23 @@ export async function cweChannelUp(): Promise<boolean> {
1492
1492
  return channelUp(18930);
1493
1493
  }
1494
1494
 
1495
+ // \u2500\u2500\u2500 Rule-mining status (user-facing) \u2500\u2500\u2500
1496
+ // Asks the local server for the current rule-mining status line. The server returns
1497
+ // a non-empty systemMessage only while a job is active or just completed (once per
1498
+ // session); empty when idle. Best-effort + tightly timed so it never delays a hook.
1499
+ export async function fetchMiningStatusMessage(sessionId: string): Promise<string> {
1500
+ try {
1501
+ const port = process.env.SYNKRO_MCP_PORT || '18931';
1502
+ const u = 'http://127.0.0.1:' + port + '/api/local/rule-mining/status?session_id=' + encodeURIComponent(sessionId || '');
1503
+ const r = await fetch(u, { signal: AbortSignal.timeout(1500) });
1504
+ if (!r.ok) return '';
1505
+ const j = await r.json() as { systemMessage?: unknown };
1506
+ return typeof j.systemMessage === 'string' ? j.systemMessage : '';
1507
+ } catch {
1508
+ return '';
1509
+ }
1510
+ }
1511
+
1495
1512
  // \u2500\u2500\u2500 Mode Normalization \u2500\u2500\u2500
1496
1513
 
1497
1514
  export function normalizeMode(m?: string): 'ask' | 'fix' {
@@ -5487,10 +5504,10 @@ async function main() {
5487
5504
  const id = m.replace(/<\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-');
5488
5505
  if (id && !cweIds.includes(id)) cweIds.push(id);
5489
5506
  }
5490
- const fMatches = gradeResp.match(/<suggested_fix>([^<]+)<\/suggested_fix>/g) || [];
5507
+ const fMatches = gradeResp.match(/<(?:suggested_fix|required_fix)>([\s\S]*?)<\/(?:suggested_fix|required_fix)>/g) || [];
5491
5508
  const respIds = ruleIdMatches.map(rm => rm.replace(/<\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-'));
5492
5509
  for (let i = 0; i < Math.min(respIds.length, fMatches.length); i++) {
5493
- if (!fixes[respIds[i]]) fixes[respIds[i]] = fMatches[i].replace(/<\/?suggested_fix>/g, '').trim();
5510
+ if (!fixes[respIds[i]]) fixes[respIds[i]] = fMatches[i].replace(/<\/?(?:suggested_fix|required_fix)>/g, '').trim();
5494
5511
  }
5495
5512
  // Per-<violation> pass for the verbatim snippet (code may contain '<'),
5496
5513
  // paired with its rule_id so each finding carries its own offending code.
@@ -5540,9 +5557,9 @@ async function main() {
5540
5557
  // Fix is dropped when it just restates the explanation so no line double-prints.
5541
5558
  const cweBlocks = activeCweIds.map(id => {
5542
5559
  const why = oneSentence(reasons[id] || verdict.reason || cweNameMap.get(id.toUpperCase()) || 'code weakness detected');
5543
- const fix = fixes[id] ? oneSentence(fixes[id]) : '';
5560
+ const fix = fixes[id] ? String(fixes[id]).replace(/[\x00-\x08\x0B-\x1F\x7F]/g, ' ').trim().slice(0, 1200) : '';
5544
5561
  const showFix = !!fix && !why.toLowerCase().includes(fix.slice(0, 40).toLowerCase());
5545
- return '[' + id + '] ' + why + (showFix ? ' Fix: ' + fix : '');
5562
+ return '[' + id + '] ' + why + (showFix ? ' REQUIRED FIX (apply exactly — do not substitute a different library/format/approach): ' + fix : '');
5546
5563
  });
5547
5564
  // Bound the total: list as many CWEs as fit, spill the rest to a "+N more" note.
5548
5565
  let denyDetail = cweBlocks.join('\n');
@@ -5553,7 +5570,7 @@ async function main() {
5553
5570
  const more = cweBlocks.length - kept.length;
5554
5571
  denyDetail = kept.join('\n') + (more > 0 ? '\n(+' + more + ' more CWE weakness' + (more === 1 ? '' : 'es') + ' — fix these first, then re-run to surface the rest)' : '');
5555
5572
  }
5556
- const ctx = 'CWE weaknesses to fix (' + count + '):\n' + denyDetail + '\nFix ALL of the above before retrying. Do NOT ask the user to make the edit manually — resolve every weakness in code yourself.';
5573
+ const ctx = 'CWE weaknesses to fix (' + count + '):\n' + denyDetail + '\nThese are REQUIRED FIXES apply each one exactly as given; do NOT substitute your own approach or a different library/format. Do NOT ask the user to make the edit manually — resolve every weakness in code yourself.';
5557
5574
 
5558
5575
  emitBlockScanFindings(
5559
5576
  jwt,
@@ -6844,7 +6861,7 @@ main();
6844
6861
  import {
6845
6862
  loadJwt, detectRepo, channelUp, tag, readStdin, writeCachedRepo,
6846
6863
  outputJson, outputEmpty, setupCursorHookSignals, hookSessionId, resolveTranscriptPath, GATEWAY_URL,
6847
- isLocalStorageMode, loadSynkroFile, log, synkroFilePresent, type HookConfig,
6864
+ isLocalStorageMode, loadSynkroFile, log, synkroFilePresent, fetchMiningStatusMessage, type HookConfig,
6848
6865
  } from './_synkro-common.ts';
6849
6866
 
6850
6867
  async function main() {
@@ -6921,13 +6938,13 @@ async function main() {
6921
6938
  signal: AbortSignal.timeout(3000),
6922
6939
  }).catch(() => {});
6923
6940
 
6924
- if (!openFindings) {
6925
- outputJson({ systemMessage: routeLine });
6926
- } else if (openFindings === 1) {
6927
- outputJson({ systemMessage: routeLine + '\\n' + tagStr + ' session start \u2192 1 open finding in this repo from a prior session.' });
6928
- } else {
6929
- outputJson({ systemMessage: routeLine + '\\n' + tagStr + ' session start \u2192 ' + openFindings + ' open findings in this repo from prior sessions.' });
6930
- }
6941
+ let sys = routeLine;
6942
+ if (openFindings === 1) sys += '\\n' + tagStr + ' session start \u2192 1 open finding in this repo from a prior session.';
6943
+ else if (openFindings) sys += '\\n' + tagStr + ' session start \u2192 ' + openFindings + ' open findings in this repo from prior sessions.';
6944
+ // Rule-mining status (initial sync). Empty unless a job is active / just finished.
6945
+ const miningMsg = await fetchMiningStatusMessage(sessionId);
6946
+ if (miningMsg) sys += '\\n' + miningMsg;
6947
+ outputJson({ systemMessage: sys });
6931
6948
  } catch (err) {
6932
6949
  log('sessionStart error: ' + String(err));
6933
6950
  outputEmpty();
@@ -7005,8 +7022,9 @@ main();
7005
7022
  TRANSCRIPT_SYNC_TS = `#!/usr/bin/env bun
7006
7023
  import {
7007
7024
  loadJwt, detectRepo, readStdin, aggregateUsage, appendLocalTelemetry,
7008
- outputEmpty, setupCursorHookSignals, hookSessionId, GATEWAY_URL, readSessionLog, shipCloud,
7025
+ outputEmpty, outputJson, setupCursorHookSignals, hookSessionId, GATEWAY_URL, readSessionLog, shipCloud,
7009
7026
  resolveTranscriptPath, syncConversationTranscript, emitUsageTick, cursorModelFromPayload, synkroFilePresent,
7027
+ fetchMiningStatusMessage,
7010
7028
  } from './_synkro-common.ts';
7011
7029
  import { readFileSync } from 'node:fs';
7012
7030
 
@@ -7057,6 +7075,11 @@ async function main() {
7057
7075
  }).catch(() => {});
7058
7076
  }
7059
7077
 
7078
+ // Rule-mining status \u2014 the ~50-msg sync cadence runs through this hook, so this is
7079
+ // where a "following run" surfaces. Empty unless a job is active / just finished,
7080
+ // so an idle pool prints nothing.
7081
+ const miningMsg = await fetchMiningStatusMessage(sessionId);
7082
+ if (miningMsg) { outputJson({ systemMessage: miningMsg }); return; }
7060
7083
  outputEmpty();
7061
7084
  } catch {
7062
7085
  outputEmpty();
@@ -11611,7 +11634,7 @@ function writeConfigEnv(opts) {
11611
11634
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
11612
11635
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
11613
11636
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
11614
- `SYNKRO_VERSION=${shellQuoteSingle("1.7.4")}`
11637
+ `SYNKRO_VERSION=${shellQuoteSingle("1.7.6")}`
11615
11638
  ];
11616
11639
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
11617
11640
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -15674,7 +15697,7 @@ var args = process.argv.slice(2);
15674
15697
  var cmd = args[0] || "";
15675
15698
  var subArgs = args.slice(1);
15676
15699
  function printVersion() {
15677
- console.log("1.7.4");
15700
+ console.log("1.7.6");
15678
15701
  }
15679
15702
  function printHelp2() {
15680
15703
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents