@synkro-sh/cli 1.4.92 → 1.4.94

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
@@ -1661,8 +1661,12 @@ export function extractTranscript(transcriptPath: string | undefined): Transcrip
1661
1661
 
1662
1662
  // \u2500\u2500\u2500 Last Prompt \u2500\u2500\u2500
1663
1663
 
1664
- export function readLastPrompt(): string {
1664
+ export function readLastPrompt(sessionId?: string): string {
1665
1665
  try {
1666
+ if (sessionId) {
1667
+ const perSession = join(SESSIONS_DIR, sessionId + '.last-prompt');
1668
+ if (existsSync(perSession)) return readFileSync(perSession, 'utf-8').trim();
1669
+ }
1666
1670
  if (!existsSync(LAST_PROMPT_FILE)) return '';
1667
1671
  return readFileSync(LAST_PROMPT_FILE, 'utf-8').trim();
1668
1672
  } catch {
@@ -1986,7 +1990,7 @@ async function main() {
1986
1990
 
1987
1991
  // Extract transcript context
1988
1992
  const transcript = extractTranscript(transcriptPath);
1989
- const lastPrompt = readLastPrompt();
1993
+ const lastPrompt = readLastPrompt(sessionId);
1990
1994
 
1991
1995
  // Load config and decide route
1992
1996
  const config = await loadConfig(jwt);
@@ -2297,12 +2301,14 @@ async function main() {
2297
2301
  if (!proposed) { outputEmpty(); return; }
2298
2302
 
2299
2303
  let cweContent: string;
2304
+ let cweDiffSection = '';
2300
2305
  if (toolName === 'Edit' || toolName === 'MultiEdit' || toolName === 'edit_file' || toolName === 'reapply' || toolName === 'ApplyPatch' || toolName === 'apply_patch') {
2301
2306
  const newStr = toolName === 'Edit' || toolName === 'edit_file' || toolName === 'reapply'
2302
2307
  ? (toolInput.new_string || '')
2303
2308
  : toolName === 'ApplyPatch' || toolName === 'apply_patch'
2304
2309
  ? (toolInput.patch || toolInput.content || toolInput.code_edit || '')
2305
2310
  : (Array.isArray(toolInput.edits) ? toolInput.edits.map((e: any) => e?.new_string || '').join('\n') : '');
2311
+ cweDiffSection = newStr.slice(0, 4000);
2306
2312
  const changeIdx = proposed.indexOf(newStr);
2307
2313
  if (changeIdx >= 0 && proposed.length > 6000) {
2308
2314
  const start = Math.max(0, changeIdx - 2000);
@@ -2333,14 +2339,28 @@ async function main() {
2333
2339
 
2334
2340
  if (rt === 'local') {
2335
2341
  let cweRules: any[] = [];
2342
+ let cweRuleFetchFailed = false;
2336
2343
  try {
2337
2344
  const resp = await fetch(GATEWAY_URL + '/api/v1/cwe-rules?ext=' + encodeURIComponent(fileExt), {
2338
2345
  headers: { Authorization: 'Bearer ' + jwt },
2339
2346
  signal: AbortSignal.timeout(4000),
2340
2347
  });
2341
- const data = await resp.json() as any;
2342
- cweRules = data.rules || [];
2343
- } catch {}
2348
+ if (!resp.ok) {
2349
+ log('CWE rules fetch failed: HTTP ' + resp.status);
2350
+ cweRuleFetchFailed = true;
2351
+ } else {
2352
+ const data = await resp.json() as any;
2353
+ cweRules = data.rules || [];
2354
+ }
2355
+ } catch (fetchErr: any) {
2356
+ log('CWE rules fetch error: ' + (fetchErr?.message || String(fetchErr)));
2357
+ cweRuleFetchFailed = true;
2358
+ }
2359
+
2360
+ if (cweRuleFetchFailed) {
2361
+ outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 CWE rules unavailable \u2014 scan skipped' });
2362
+ return;
2363
+ }
2344
2364
 
2345
2365
  if (cweRules.length === 0) {
2346
2366
  outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 clean (no CWE rules for ' + fileExt + ')' });
@@ -2359,40 +2379,86 @@ async function main() {
2359
2379
  }
2360
2380
  }
2361
2381
 
2362
- const graderPrompt = [
2363
- 'File: ' + filePath,
2364
- 'Content:',
2365
- cweContent,
2366
- '',
2367
- 'CWE rules to check against:',
2368
- JSON.stringify(cweRules),
2369
- ].join('\n') + localPkgContext;
2370
-
2371
- let gradeResp: string;
2372
- try {
2373
- gradeResp = await localGradeCwe(graderPrompt);
2374
- } catch (gradeErr: any) {
2375
- const reason = gradeErr?.message || String(gradeErr);
2376
- outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
2377
- return;
2382
+ const exemptionNote = exemptedCwes.size > 0
2383
+ ? '\n\nEXEMPTED CWEs (already known, DO NOT report these — focus on NEW weaknesses only): ' + [...exemptedCwes].join(', ')
2384
+ : '';
2385
+
2386
+ function buildCwePrompt(content: string): string {
2387
+ const diffBlock = cweDiffSection
2388
+ ? '\n\n=== NEW/CHANGED CODE (evaluate THIS for CWE weaknesses) ===\n' + cweDiffSection + '\n=== END NEW CODE ===\n\nThe surrounding content below is CONTEXT ONLY to help you understand imports, variables, and scope. Do NOT report issues found only in the context section.\n'
2389
+ : '';
2390
+ return [
2391
+ 'File: ' + filePath,
2392
+ diffBlock,
2393
+ 'Full content window:',
2394
+ content,
2395
+ '',
2396
+ 'CWE rules to check against:',
2397
+ JSON.stringify(cweRules),
2398
+ ].join('\n') + localPkgContext + exemptionNote;
2378
2399
  }
2379
2400
 
2380
- const verdict = parseVerdict(gradeResp);
2401
+ const SPLIT_THRESHOLD = 4000;
2402
+ const OVERLAP = 500;
2403
+ let gradeResponses: string[] = [];
2381
2404
 
2382
- if (!verdict.ok) {
2383
- const ruleIdMatches = gradeResp.match(/<rule_id>([^<]+)<\/rule_id>/g) || [];
2384
- const cweIds: string[] = [];
2385
- for (const match of ruleIdMatches.slice(0, 5)) {
2386
- const id = match.replace(/<\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-');
2387
- if (id && !cweIds.includes(id)) cweIds.push(id);
2405
+ if (cweContent.length > SPLIT_THRESHOLD) {
2406
+ const mid = Math.floor(cweContent.length / 2);
2407
+ const chunk1 = cweContent.slice(0, mid + OVERLAP);
2408
+ const chunk2 = cweContent.slice(mid - OVERLAP);
2409
+ try {
2410
+ const [resp1, resp2] = await Promise.all([
2411
+ localGradeCwe(buildCwePrompt(chunk1)),
2412
+ localGradeCwe(buildCwePrompt(chunk2)),
2413
+ ]);
2414
+ gradeResponses = [resp1, resp2];
2415
+ } catch (gradeErr: any) {
2416
+ const reason = gradeErr?.message || String(gradeErr);
2417
+ outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
2418
+ return;
2419
+ }
2420
+ } else {
2421
+ try {
2422
+ gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent))];
2423
+ } catch (gradeErr: any) {
2424
+ const reason = gradeErr?.message || String(gradeErr);
2425
+ outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
2426
+ return;
2388
2427
  }
2428
+ }
2389
2429
 
2390
- const fixMatches = gradeResp.match(/<suggested_fix>([^<]+)<\/suggested_fix>/g) || [];
2391
- const fixes: Record<string, string> = {};
2392
- for (let i = 0; i < Math.min(cweIds.length, fixMatches.length); i++) {
2393
- fixes[cweIds[i]] = fixMatches[i].replace(/<\/?suggested_fix>/g, '').trim();
2430
+ const cweIds: string[] = [];
2431
+ const fixes: Record<string, string> = {};
2432
+ let mergedReason = '';
2433
+ let mergedSeverity = '';
2434
+ let mergedCategory = '';
2435
+ let anyFailed = false;
2436
+
2437
+ for (const gradeResp of gradeResponses) {
2438
+ const v = parseVerdict(gradeResp);
2439
+ if (!v.ok) {
2440
+ anyFailed = true;
2441
+ if (v.reason) mergedReason = mergedReason || v.reason;
2442
+ if (v.severity) mergedSeverity = mergedSeverity || v.severity;
2443
+ if (v.category) mergedCategory = mergedCategory || v.category;
2444
+ const ruleIdMatches = gradeResp.match(/<rule_id>([^<]+)<\/rule_id>/g) || [];
2445
+ for (const m of ruleIdMatches.slice(0, 5)) {
2446
+ const id = m.replace(/<\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-');
2447
+ if (id && !cweIds.includes(id)) cweIds.push(id);
2448
+ }
2449
+ const fMatches = gradeResp.match(/<suggested_fix>([^<]+)<\/suggested_fix>/g) || [];
2450
+ const respIds = ruleIdMatches.map(rm => rm.replace(/<\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-'));
2451
+ for (let i = 0; i < Math.min(respIds.length, fMatches.length); i++) {
2452
+ if (!fixes[respIds[i]]) fixes[respIds[i]] = fMatches[i].replace(/<\/?suggested_fix>/g, '').trim();
2453
+ }
2394
2454
  }
2455
+ }
2395
2456
 
2457
+ const verdict = anyFailed
2458
+ ? { ok: false, reason: mergedReason, severity: mergedSeverity, category: mergedCategory }
2459
+ : { ok: true, reason: '', severity: '', category: '' };
2460
+
2461
+ if (!verdict.ok) {
2396
2462
  const activeCweIds = cweIds.filter(id => !exemptedCwes.has(id.toUpperCase()));
2397
2463
 
2398
2464
  if (activeCweIds.length === 0) {
@@ -2876,7 +2942,7 @@ async function main() {
2876
2942
  }
2877
2943
  }
2878
2944
 
2879
- const lastPrompt = readLastPrompt();
2945
+ const lastPrompt = readLastPrompt(sessionId);
2880
2946
 
2881
2947
  const config = await loadConfig(jwt);
2882
2948
  const rt = await route(config);
@@ -3057,7 +3123,7 @@ async function main() {
3057
3123
  jwt = await ensureFreshJwt(jwt);
3058
3124
 
3059
3125
  const transcript = extractTranscript(transcriptPath);
3060
- const lastPrompt = readLastPrompt();
3126
+ const lastPrompt = readLastPrompt(sessionId);
3061
3127
 
3062
3128
  const config = await loadConfig(jwt);
3063
3129
  const rt = await route(config);
@@ -3741,8 +3807,14 @@ async function main() {
3741
3807
  const msg = payload.message || payload.prompt || payload.content || '';
3742
3808
  if (msg) {
3743
3809
  const promptFile = join(homedir(), '.synkro', '.last-prompt');
3744
- mkdirSync(dirname(promptFile), { recursive: true });
3745
- writeFileSync(promptFile, msg, 'utf-8');
3810
+ mkdirSync(dirname(promptFile), { recursive: true, mode: 0o700 });
3811
+ writeFileSync(promptFile, msg, { encoding: 'utf-8', mode: 0o600 });
3812
+ const sid = hookSessionId(payload);
3813
+ if (sid) {
3814
+ const sessDir = join(homedir(), '.synkro', 'sessions');
3815
+ mkdirSync(sessDir, { recursive: true, mode: 0o700 });
3816
+ writeFileSync(join(sessDir, sid + '.last-prompt'), msg, { encoding: 'utf-8', mode: 0o600 });
3817
+ }
3746
3818
  }
3747
3819
 
3748
3820
  const sessionId = hookSessionId(payload);
@@ -3891,7 +3963,7 @@ async function main() {
3891
3963
  jwt = await ensureFreshJwt(jwt);
3892
3964
 
3893
3965
  const transcript = extractTranscript(transcriptPath);
3894
- const lastPrompt = readLastPrompt();
3966
+ const lastPrompt = readLastPrompt(sessionId);
3895
3967
 
3896
3968
  const config = await loadConfig(jwt);
3897
3969
  if (config.silent) finishAllow();
@@ -5660,7 +5732,7 @@ function writeConfigEnv(opts) {
5660
5732
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
5661
5733
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
5662
5734
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
5663
- `SYNKRO_VERSION=${shellQuoteSingle("1.4.92")}`
5735
+ `SYNKRO_VERSION=${shellQuoteSingle("1.4.94")}`
5664
5736
  ];
5665
5737
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
5666
5738
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -5677,12 +5749,13 @@ function writeConfigEnv(opts) {
5677
5749
  chmodSync2(CONFIG_PATH2, 384);
5678
5750
  }
5679
5751
  function resolveDeploymentMode() {
5680
- const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE;
5681
- if (envOverride) return envOverride.toLowerCase();
5752
+ const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
5753
+ if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
5682
5754
  try {
5683
5755
  if (existsSync9(CONFIG_PATH2)) {
5684
5756
  const m = readFileSync7(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
5685
- if (m && m[1]) return m[1].toLowerCase();
5757
+ const val = m?.[1]?.toLowerCase();
5758
+ if (val === "bare-host" || val === "docker") return val;
5686
5759
  }
5687
5760
  } catch {
5688
5761
  }
@@ -7095,7 +7168,7 @@ var args = process.argv.slice(2);
7095
7168
  var cmd = args[0] || "";
7096
7169
  var subArgs = args.slice(1);
7097
7170
  function printVersion() {
7098
- console.log("1.4.92");
7171
+ console.log("1.4.94");
7099
7172
  }
7100
7173
  function printHelp() {
7101
7174
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents