@synkro-sh/cli 1.4.70 → 1.4.72

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
@@ -369,15 +369,15 @@ function installCursorHooks(hooksJsonPath, config) {
369
369
  });
370
370
  pushCcHook(h, "preToolUse", config.editPrecheckScriptPath, {
371
371
  timeout: 15,
372
- matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook"
372
+ matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch"
373
373
  });
374
374
  pushCcHook(h, "preToolUse", config.cwePrecheckScriptPath, {
375
- timeout: 15,
376
- matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook"
375
+ timeout: 60,
376
+ matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch"
377
377
  });
378
378
  pushCcHook(h, "preToolUse", config.cvePrecheckScriptPath, {
379
- timeout: 10,
380
- matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook"
379
+ timeout: 20,
380
+ matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch"
381
381
  });
382
382
  pushCcHook(h, "preToolUse", config.agentJudgeScriptPath, {
383
383
  timeout: 15,
@@ -1437,11 +1437,40 @@ export function ruleMode(ruleId: string, rules: Rule[]): 'blocking' | 'audit' {
1437
1437
 
1438
1438
  // \u2500\u2500\u2500 Content Reconstruction \u2500\u2500\u2500
1439
1439
 
1440
+ function patchTextFromToolInput(toolInput: any): string {
1441
+ return String(toolInput.patch ?? toolInput.content ?? toolInput.code_edit ?? toolInput.new_string ?? toolInput.input ?? toolInput.diff ?? '');
1442
+ }
1443
+
1444
+ function filePathFromPatch(patchText: string): string {
1445
+ const match = patchText.match(/^\\*\\*\\* (?:Add|Update|Delete) File:\\s*(.+)$/m);
1446
+ return match?.[1]?.trim() || '';
1447
+ }
1448
+
1449
+ function contentFromPatch(patchText: string): string {
1450
+ const addedOrContext = patchText
1451
+ .split('\\n')
1452
+ .filter(line => (line.startsWith('+') && !line.startsWith('+++')) || line.startsWith(' '))
1453
+ .map(line => line.slice(1))
1454
+ .join('\\n')
1455
+ .trim();
1456
+ return addedOrContext || patchText;
1457
+ }
1458
+
1459
+ export function filePathFromToolInput(toolInput: any): string {
1460
+ return toolInput.file_path || toolInput.notebook_path || toolInput.path || toolInput.target_file || filePathFromPatch(patchTextFromToolInput(toolInput));
1461
+ }
1462
+
1440
1463
  export function reconstructContent(toolName: string, toolInput: any, filePath: string, cwd?: string): string {
1441
1464
  const canRead = filePath && cwd && isPathUnder(filePath, cwd);
1442
1465
  switch (toolName) {
1466
+ case 'ApplyPatch':
1467
+ case 'apply_patch':
1468
+ return contentFromPatch(patchTextFromToolInput(toolInput));
1443
1469
  case 'Write':
1444
1470
  return toolInput.content || '';
1471
+ case 'edit_file':
1472
+ case 'reapply':
1473
+ return toolInput.content || toolInput.new_string || toolInput.code_edit || '';
1445
1474
  case 'Edit': {
1446
1475
  let content = '';
1447
1476
  try {
@@ -1475,6 +1504,7 @@ export function reconstructContent(toolName: string, toolInput: any, filePath: s
1475
1504
  return content;
1476
1505
  }
1477
1506
  case 'NotebookEdit':
1507
+ case 'edit_notebook':
1478
1508
  return toolInput.new_source || '';
1479
1509
  case 'StrReplace':
1480
1510
  return toolInput.new_string || toolInput.content || toolInput.code_edit || '';
@@ -1797,6 +1827,7 @@ export function dispatchFinding(
1797
1827
 
1798
1828
  export const EDIT_TOOL_NAMES = new Set([
1799
1829
  'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'StrReplace',
1830
+ 'edit_file', 'reapply', 'edit_notebook', 'ApplyPatch', 'apply_patch',
1800
1831
  ]);
1801
1832
  export const SHELL_TOOL_NAMES = new Set([
1802
1833
  'Bash', 'Shell', 'Read', 'Grep', 'Glob', 'terminal', 'run_terminal_cmd', 'execute_command',
@@ -1896,7 +1927,7 @@ export function outputEmpty(): void {
1896
1927
  import {
1897
1928
  loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
1898
1929
  parseVerdict, dispatchCapture, ruleMode, reconstructContent, isPathUnder, postWithRetry,
1899
- readStdin, extractTranscript, readLastPrompt, findNearestDeps, log,
1930
+ readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput, log,
1900
1931
  outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,
1901
1932
  type HookConfig, type Rule,
1902
1933
  } from './_synkro-common.ts';
@@ -1923,7 +1954,7 @@ async function main() {
1923
1954
  const permissionMode = payload.permission_mode || '';
1924
1955
  const transcriptPath = payload.transcript_path || '';
1925
1956
 
1926
- const filePath = toolInput.file_path || toolInput.notebook_path || toolInput.path || '';
1957
+ const filePath = filePathFromToolInput(toolInput);
1927
1958
  if (!filePath) { outputEmpty(); return; }
1928
1959
 
1929
1960
  if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }
@@ -2105,11 +2136,11 @@ async function main() {
2105
2136
 
2106
2137
  main();
2107
2138
  `;
2108
- CWE_PRECHECK_TS = `#!/usr/bin/env bun
2139
+ CWE_PRECHECK_TS = String.raw`#!/usr/bin/env bun
2109
2140
  import {
2110
2141
  loadJwt, ensureFreshJwt, detectRepo, loadConfig, cweRoute, tag,
2111
2142
  localGradeCwe, parseVerdict, reconstructContent, readStdin, log,
2112
- outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, dispatchFinding, GATEWAY_URL,
2143
+ outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, filePathFromToolInput, dispatchFinding, dispatchCapture, GATEWAY_URL,
2113
2144
  } from './_synkro-common.ts';
2114
2145
  import { basename, extname } from 'node:path';
2115
2146
 
@@ -2131,29 +2162,28 @@ async function main() {
2131
2162
  const cwd = payload.cwd || '';
2132
2163
  const gitRepo = detectRepo(cwd || '.');
2133
2164
 
2134
- const filePath = toolInput.file_path || toolInput.notebook_path || toolInput.path || '';
2165
+ const filePath = filePathFromToolInput(toolInput);
2135
2166
  if (!filePath) { outputEmpty(); return; }
2136
2167
 
2137
2168
  if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }
2138
2169
 
2139
2170
  const fileShort = basename(filePath);
2140
- const fileExt = extname(filePath); // e.g. ".ts"
2171
+ const fileExt = extname(filePath);
2141
2172
 
2142
2173
  let jwt = loadJwt();
2143
2174
  if (!jwt) { outputEmpty(); return; }
2144
2175
  jwt = await ensureFreshJwt(jwt);
2145
2176
 
2146
- // Reconstruct proposed content
2147
2177
  const proposed = reconstructContent(toolName, toolInput, filePath, cwd);
2148
2178
  if (!proposed) { outputEmpty(); return; }
2149
2179
 
2150
- // Change-anchored window: for Edit/MultiEdit send context around the diff,
2151
- // for Write send first 4000 chars (new files have patterns at the top).
2152
2180
  let cweContent: string;
2153
- if (toolName === 'Edit' || toolName === 'MultiEdit') {
2154
- const newStr = toolName === 'Edit'
2181
+ if (toolName === 'Edit' || toolName === 'MultiEdit' || toolName === 'edit_file' || toolName === 'reapply' || toolName === 'ApplyPatch' || toolName === 'apply_patch') {
2182
+ const newStr = toolName === 'Edit' || toolName === 'edit_file' || toolName === 'reapply'
2155
2183
  ? (toolInput.new_string || '')
2156
- : (Array.isArray(toolInput.edits) ? toolInput.edits.map((e: any) => e?.new_string || '').join('\\n') : '');
2184
+ : toolName === 'ApplyPatch' || toolName === 'apply_patch'
2185
+ ? (toolInput.patch || toolInput.content || toolInput.code_edit || '')
2186
+ : (Array.isArray(toolInput.edits) ? toolInput.edits.map((e: any) => e?.new_string || '').join('\n') : '');
2157
2187
  const changeIdx = proposed.indexOf(newStr);
2158
2188
  if (changeIdx >= 0 && proposed.length > 6000) {
2159
2189
  const start = Math.max(0, changeIdx - 2000);
@@ -2169,7 +2199,6 @@ async function main() {
2169
2199
  const config = await loadConfig(jwt);
2170
2200
  const rt = await cweRoute(config);
2171
2201
 
2172
- // Build set of exempted CWE IDs for this file path
2173
2202
  const exemptedCwes = new Set<string>();
2174
2203
  for (const ex of config.scanExemptions) {
2175
2204
  if (ex.cwe_id && filePath.includes(ex.path)) {
@@ -2177,14 +2206,13 @@ async function main() {
2177
2206
  }
2178
2207
  }
2179
2208
  if (config.silent) {
2180
- outputJson({ systemMessage: '[synkro:' + rt + ':cweScan] ' + fileShort + ' \\u2192 skipped (silent mode)' });
2209
+ outputJson({ systemMessage: '[synkro:' + rt + ':cweScan] ' + fileShort + ' \u2192 skipped (silent mode)' });
2181
2210
  return;
2182
2211
  }
2183
2212
 
2184
2213
  const cweTag = '[synkro:' + rt + ':cweScan]';
2185
2214
 
2186
2215
  if (rt === 'local') {
2187
- // \u2500\u2500\u2500 Local CWE grading on channel 2 (port 8930) \u2500\u2500\u2500
2188
2216
  let cweRules: any[] = [];
2189
2217
  try {
2190
2218
  const resp = await fetch(GATEWAY_URL + '/api/v1/cwe-rules?ext=' + encodeURIComponent(fileExt), {
@@ -2196,7 +2224,7 @@ async function main() {
2196
2224
  } catch {}
2197
2225
 
2198
2226
  if (cweRules.length === 0) {
2199
- outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (no CWE rules for ' + fileExt + ')' });
2227
+ outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 clean (no CWE rules for ' + fileExt + ')' });
2200
2228
  return;
2201
2229
  }
2202
2230
 
@@ -2207,38 +2235,37 @@ async function main() {
2207
2235
  '',
2208
2236
  'CWE rules to check against:',
2209
2237
  JSON.stringify(cweRules),
2210
- ].join('\\n');
2238
+ ].join('\n');
2211
2239
 
2212
2240
  let gradeResp: string;
2213
2241
  try {
2214
2242
  gradeResp = await localGradeCwe(graderPrompt);
2215
2243
  } catch (gradeErr: any) {
2216
2244
  const reason = gradeErr?.message || String(gradeErr);
2217
- outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 grader unavailable (' + reason + '), skipped' });
2245
+ outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
2218
2246
  return;
2219
2247
  }
2220
2248
 
2221
2249
  const verdict = parseVerdict(gradeResp);
2222
2250
 
2223
2251
  if (!verdict.ok) {
2224
- const ruleIdMatches = gradeResp.match(/<rule_id>([^<]+)<\\/rule_id>/g) || [];
2252
+ const ruleIdMatches = gradeResp.match(/<rule_id>([^<]+)<\/rule_id>/g) || [];
2225
2253
  const cweIds: string[] = [];
2226
2254
  for (const match of ruleIdMatches.slice(0, 5)) {
2227
- const id = match.replace(/<\\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-');
2255
+ const id = match.replace(/<\/?rule_id>/g, '').trim().replace(/^cwe-/, 'CWE-');
2228
2256
  if (id && !cweIds.includes(id)) cweIds.push(id);
2229
2257
  }
2230
2258
 
2231
- const fixMatches = gradeResp.match(/<suggested_fix>([^<]+)<\\/suggested_fix>/g) || [];
2259
+ const fixMatches = gradeResp.match(/<suggested_fix>([^<]+)<\/suggested_fix>/g) || [];
2232
2260
  const fixes: Record<string, string> = {};
2233
2261
  for (let i = 0; i < Math.min(cweIds.length, fixMatches.length); i++) {
2234
- fixes[cweIds[i]] = fixMatches[i].replace(/<\\/?suggested_fix>/g, '').trim();
2262
+ fixes[cweIds[i]] = fixMatches[i].replace(/<\/?suggested_fix>/g, '').trim();
2235
2263
  }
2236
2264
 
2237
- // Filter out exempted CWEs for this file
2238
2265
  const activeCweIds = cweIds.filter(id => !exemptedCwes.has(id.toUpperCase()));
2239
2266
 
2240
2267
  if (activeCweIds.length === 0) {
2241
- outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean (exempted: ' + cweIds.join(', ') + ')' });
2268
+ outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 clean (exempted: ' + cweIds.join(', ') + ')' });
2242
2269
  return;
2243
2270
  }
2244
2271
 
@@ -2250,13 +2277,13 @@ async function main() {
2250
2277
  const displayIds = activeCweIds.slice(0, 3).join(', ');
2251
2278
  const count = activeCweIds.length;
2252
2279
  const label = count === 1 ? 'match' : 'matches';
2253
- const cweMsg = cweTag + ' ' + fileShort + ' \\u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';
2280
+ const cweMsg = cweTag + ' ' + fileShort + ' \u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';
2254
2281
  const denyDetail = '[' + displayIds + '] ' + (verdict.reason || 'code weakness detected');
2255
2282
  const fixLines = activeCweIds
2256
2283
  .filter(id => fixes[id])
2257
2284
  .map(id => '[' + id + '] Fix: ' + fixes[id]);
2258
- const fixHint = fixLines.length > 0 ? '\\n' + fixLines.join('\\n') : '';
2259
- const ctx = 'CWE: ' + denyDetail + fixHint + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \u2014 resolve the weakness in code yourself.';
2285
+ const fixHint = fixLines.length > 0 ? '\n' + fixLines.join('\n') : '';
2286
+ const ctx = 'CWE: ' + denyDetail + fixHint + '\nFix all issues before retrying. Do NOT ask the user to make the edit manually resolve the weakness in code yourself.';
2260
2287
 
2261
2288
  for (const cweId of activeCweIds) {
2262
2289
  dispatchFinding(jwt, {
@@ -2299,16 +2326,14 @@ async function main() {
2299
2326
  reasoning: verdict.reason || 'no CWE weaknesses detected',
2300
2327
  });
2301
2328
 
2302
- const cleanMsg = cweTag + ' ' + fileShort + ' \\u2192 clean' + (verdict.reason ? ' (' + verdict.reason + ')' : '');
2329
+ const cleanMsg = cweTag + ' ' + fileShort + ' \u2192 clean' + (verdict.reason ? ' (' + verdict.reason + ')' : '');
2303
2330
  outputJson({ systemMessage: cleanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: cleanMsg } });
2304
2331
  return;
2305
2332
  }
2306
2333
 
2307
- // \u2500\u2500\u2500 Cloud CWE grading (handled by server) \u2500\u2500\u2500
2308
- // Cloud edit precheck already includes CWE \u2014 this hook is a no-op for cloud.
2309
2334
  outputEmpty();
2310
2335
  } catch (err) {
2311
- process.stderr.write('[synkro] cweGuard error: ' + String(err) + '\\n');
2336
+ process.stderr.write('[synkro] cweGuard error: ' + String(err) + '\n');
2312
2337
  outputEmpty();
2313
2338
  }
2314
2339
  }
@@ -2318,7 +2343,7 @@ main();
2318
2343
  CVE_PRECHECK_TS = `#!/usr/bin/env bun
2319
2344
  import {
2320
2345
  loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,
2321
- reconstructContent, readStdin, findNearestDeps, log,
2346
+ reconstructContent, readStdin, findNearestDeps, filePathFromToolInput, log,
2322
2347
  outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, dispatchFinding, dispatchCapture, GATEWAY_URL,
2323
2348
  } from './_synkro-common.ts';
2324
2349
  import { basename } from 'node:path';
@@ -2354,7 +2379,7 @@ async function main() {
2354
2379
  const cwd = payload.cwd || '';
2355
2380
  const gitRepo = detectRepo(cwd || '.');
2356
2381
 
2357
- const filePath = toolInput.file_path || toolInput.notebook_path || toolInput.path || '';
2382
+ const filePath = filePathFromToolInput(toolInput);
2358
2383
  if (!filePath) { outputEmpty(); return; }
2359
2384
 
2360
2385
  if (filePath.includes('/.synkro/hooks/')) { outputEmpty(); return; }
@@ -3924,7 +3949,7 @@ async function main() {
3924
3949
  if (!input.trim()) finish();
3925
3950
 
3926
3951
  const payload = JSON.parse(input);
3927
- const filePath = payload.file_path || '';
3952
+ const filePath = payload.file_path || payload.path || payload.target_file || '';
3928
3953
  if (!filePath) finish();
3929
3954
 
3930
3955
  const cwd = payload.cwd || payload.workspace_roots?.[0] || '';
@@ -7120,7 +7145,7 @@ function writeConfigEnv(opts) {
7120
7145
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
7121
7146
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
7122
7147
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
7123
- `SYNKRO_VERSION=${shellQuoteSingle("1.4.70")}`
7148
+ `SYNKRO_VERSION=${shellQuoteSingle("1.4.72")}`
7124
7149
  ];
7125
7150
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
7126
7151
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -7249,7 +7274,6 @@ function isAlreadyInstalled() {
7249
7274
  join11(HOOKS_DIR, "cc-bash-judge.ts"),
7250
7275
  join11(HOOKS_DIR, "cc-bash-followup.ts"),
7251
7276
  join11(HOOKS_DIR, "cc-edit-precheck.ts"),
7252
- join11(HOOKS_DIR, "cc-cwe-precheck.ts"),
7253
7277
  join11(HOOKS_DIR, "cc-cve-precheck.ts"),
7254
7278
  join11(HOOKS_DIR, "cc-plan-judge.ts"),
7255
7279
  join11(HOOKS_DIR, "cc-stop-summary.ts"),
@@ -7521,6 +7545,8 @@ async function installCommand(opts = {}) {
7521
7545
  console.log(` ${scripts.bashScript}`);
7522
7546
  console.log(` ${scripts.bashFollowupScript}`);
7523
7547
  console.log(` ${scripts.editPrecheckScript}`);
7548
+ console.log(` ${scripts.cwePrecheckScript}`);
7549
+ console.log(` ${scripts.cvePrecheckScript}`);
7524
7550
  console.log(` ${scripts.planJudgeScript}`);
7525
7551
  console.log(` ${scripts.agentJudgeScript}`);
7526
7552
  console.log(` ${scripts.stopSummaryScript}`);
@@ -8207,7 +8233,6 @@ async function statusCommand() {
8207
8233
  "cc-bash-judge.ts",
8208
8234
  "cc-bash-followup.ts",
8209
8235
  "cc-edit-precheck.ts",
8210
- "cc-cwe-precheck.ts",
8211
8236
  "cc-cve-precheck.ts",
8212
8237
  "cc-plan-judge.ts",
8213
8238
  "cc-agent-judge.ts",
@@ -8221,7 +8246,6 @@ async function statusCommand() {
8221
8246
  "cursor-bash-judge.ts",
8222
8247
  "cursor-edit-capture.ts",
8223
8248
  "cc-edit-precheck.ts",
8224
- "cc-cwe-precheck.ts",
8225
8249
  "cc-cve-precheck.ts",
8226
8250
  "cc-agent-judge.ts",
8227
8251
  "cc-plan-judge.ts",