@synkro-sh/cli 1.5.2 → 1.5.3

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
@@ -1854,6 +1854,28 @@ function cursorHookExit(): never {
1854
1854
  process.exit(0);
1855
1855
  }
1856
1856
 
1857
+ // \u2500\u2500\u2500 Grader-unavailable diagnostic log \u2500\u2500\u2500
1858
+ // Records every time a hook tried to call the local grader and fell open
1859
+ // because the call failed. JSONL at ~/.synkro/grader-unavailable.log so the
1860
+ // user can pinpoint cause (timeout vs ECONNREFUSED vs HTTP 5xx vs sick pool)
1861
+ // instead of guessing from a one-shot system message in the CC UI.
1862
+
1863
+ const UNAVAIL_LOG = join(HOME, '.synkro', 'grader-unavailable.log');
1864
+
1865
+ export function logGraderUnavailable(hook: string, target: string, errorMessage: string): void {
1866
+ try {
1867
+ const entry = {
1868
+ ts: new Date().toISOString(),
1869
+ hook,
1870
+ target,
1871
+ error: errorMessage.slice(0, 500),
1872
+ };
1873
+ appendFileSync(UNAVAIL_LOG, JSON.stringify(entry) + '\\n', 'utf-8');
1874
+ } catch {
1875
+ // best-effort \u2014 never let logging failure cascade into a hook failure
1876
+ }
1877
+ }
1878
+
1857
1879
  // \u2500\u2500\u2500 Output Helpers \u2500\u2500\u2500
1858
1880
 
1859
1881
  export function outputJson(obj: any): void {
@@ -1916,6 +1938,7 @@ import {
1916
1938
  readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput,
1917
1939
  appendSessionAction, readSessionLog, compressSessionLog, log,
1918
1940
  outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,
1941
+ logGraderUnavailable,
1919
1942
  type HookConfig, type Rule,
1920
1943
  } from './_synkro-common.ts';
1921
1944
  import { existsSync, readFileSync } from 'node:fs';
@@ -2010,7 +2033,8 @@ async function main() {
2010
2033
  let gradeResp: string;
2011
2034
  try {
2012
2035
  gradeResp = await localGrade('edit', graderPrompt);
2013
- } catch {
2036
+ } catch (err) {
2037
+ logGraderUnavailable('editGuard', fileShort, (err as Error).message || String(err));
2014
2038
  outputJson({ systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 local grader unavailable, skipped' });
2015
2039
  return;
2016
2040
  }
@@ -2133,6 +2157,7 @@ import {
2133
2157
  loadJwt, ensureFreshJwt, detectRepo, loadConfig, cweRoute, tag,
2134
2158
  localGradeCwe, parseVerdict, reconstructContent, readStdin, log,
2135
2159
  outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, filePathFromToolInput, dispatchFinding, dispatchCapture, GATEWAY_URL,
2160
+ logGraderUnavailable,
2136
2161
  } from './_synkro-common.ts';
2137
2162
  import { basename, extname, resolve, join, dirname } from 'node:path';
2138
2163
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
@@ -2403,6 +2428,7 @@ async function main() {
2403
2428
  gradeResponses = [resp1, resp2];
2404
2429
  } catch (gradeErr: any) {
2405
2430
  const reason = gradeErr?.message || String(gradeErr);
2431
+ logGraderUnavailable('cweGuard', fileShort, reason);
2406
2432
  outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
2407
2433
  return;
2408
2434
  }
@@ -2411,6 +2437,7 @@ async function main() {
2411
2437
  gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent))];
2412
2438
  } catch (gradeErr: any) {
2413
2439
  const reason = gradeErr?.message || String(gradeErr);
2440
+ logGraderUnavailable('cweGuard', fileShort, reason);
2414
2441
  outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
2415
2442
  return;
2416
2443
  }
@@ -2777,6 +2804,7 @@ import {
2777
2804
  parseVerdict, dispatchCapture, dispatchFinding, ruleMode, postWithRetry, readStdin,
2778
2805
  extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
2779
2806
  outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,
2807
+ logGraderUnavailable,
2780
2808
  type HookConfig, type Rule,
2781
2809
  } from './_synkro-common.ts';
2782
2810
 
@@ -2961,7 +2989,8 @@ async function main() {
2961
2989
  let gradeResp: string;
2962
2990
  try {
2963
2991
  gradeResp = await localGrade('bash', graderPrompt);
2964
- } catch {
2992
+ } catch (err) {
2993
+ logGraderUnavailable('bashGuard', toolInput.command?.slice(0, 200) || '', (err as Error).message || String(err));
2965
2994
  outputJson({ systemMessage: tagStr + ' bashGuard → local grader unavailable, skipped' });
2966
2995
  return;
2967
2996
  }
@@ -2973,8 +3002,12 @@ async function main() {
2973
3002
  const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);
2974
3003
  const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');
2975
3004
 
3005
+ const auditRulesInReason = (config.rules || [])
3006
+ .filter((r: any) => r.mode === 'audit' && r.rule_id && verdict.reason && verdict.reason.includes(r.rule_id))
3007
+ .map((r: any) => r.rule_id);
3008
+
2976
3009
  if (mode === 'audit') {
2977
- const reason = tagStr + ' bashGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation');
3010
+ const reason = tagStr + ' bashGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Fix this before proceeding — do not ask the user.';
2978
3011
  const combined = (installScanMsg ? installScanMsg + '\\n' : '') + reason;
2979
3012
  outputJson({ systemMessage: combined, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: combined } });
2980
3013
  dispatchCapture(jwt, 'bash', 'warning', verdict.severity || 'medium', verdict.category || 'security',
@@ -2983,11 +3016,14 @@ async function main() {
2983
3016
  recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,
2984
3017
  });
2985
3018
  } else {
2986
- const reason = tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';
2987
- const combined = (installScanMsg ? installScanMsg + '\\n' : '') + reason;
3019
+ let blockMsg = tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';
3020
+ if (auditRulesInReason.length > 0) {
3021
+ blockMsg += '\\nAudit violations (' + auditRulesInReason.join(', ') + '): fix these automatically before retrying — do not ask the user, just resolve them (e.g. run the required precondition).';
3022
+ }
3023
+ const combined = (installScanMsg ? installScanMsg + '\\n' : '') + blockMsg;
2988
3024
  outputJson({
2989
3025
  systemMessage: combined,
2990
- hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason, additionalContext: combined },
3026
+ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: blockMsg, additionalContext: combined },
2991
3027
  });
2992
3028
  dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',
2993
3029
  toolName, gitRepo, sessionId, config.captureDepth, {
@@ -3078,6 +3114,7 @@ import {
3078
3114
  parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
3079
3115
  extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
3080
3116
  outputJson, outputEmpty, setupCursorHookSignals, isAgentTool, hookSessionId, GATEWAY_URL,
3117
+ logGraderUnavailable,
3081
3118
  type HookConfig, type Rule,
3082
3119
  } from './_synkro-common.ts';
3083
3120
 
@@ -3149,7 +3186,8 @@ async function main() {
3149
3186
  let gradeResp: string;
3150
3187
  try {
3151
3188
  gradeResp = await localGrade('bash', graderPrompt);
3152
- } catch {
3189
+ } catch (err) {
3190
+ logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), (err as Error).message || String(err));
3153
3191
  outputJson({ systemMessage: tagStr + ' agentGuard \u2192 local grader unavailable, skipped' });
3154
3192
  return;
3155
3193
  }
@@ -3599,7 +3637,7 @@ async function main() {
3599
3637
  let isError = payload.tool_result?.is_error === true;
3600
3638
  try {
3601
3639
  const out = JSON.parse(payload.tool_output || '{}');
3602
- if (out.exitCode !== 0 || out.is_error === true) isError = true;
3640
+ if ((typeof out.exitCode === 'number' && out.exitCode !== 0) || out.is_error === true) isError = true;
3603
3641
  } catch {}
3604
3642
  const cmd = shellCmd;
3605
3643
  const cmdHash = cmd ? hashCommand(cmd) : '';
@@ -5758,7 +5796,7 @@ function writeConfigEnv(opts) {
5758
5796
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
5759
5797
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
5760
5798
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
5761
- `SYNKRO_VERSION=${shellQuoteSingle("1.5.2")}`
5799
+ `SYNKRO_VERSION=${shellQuoteSingle("1.5.3")}`
5762
5800
  ];
5763
5801
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
5764
5802
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -7148,7 +7186,7 @@ var args = process.argv.slice(2);
7148
7186
  var cmd = args[0] || "";
7149
7187
  var subArgs = args.slice(1);
7150
7188
  function printVersion() {
7151
- console.log("1.5.2");
7189
+ console.log("1.5.3");
7152
7190
  }
7153
7191
  function printHelp() {
7154
7192
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents