@synkro-sh/cli 1.4.31 → 1.4.33

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
@@ -148,6 +148,17 @@ function installCCHooks(settingsPath, config) {
148
148
  ],
149
149
  [SYNKRO_MARKER]: true
150
150
  });
151
+ settings.hooks.PreToolUse.push({
152
+ matcher: "ExitPlanMode",
153
+ hooks: [
154
+ {
155
+ type: "command",
156
+ command: config.planJudgeScriptPath,
157
+ timeout: 45
158
+ }
159
+ ],
160
+ [SYNKRO_MARKER]: true
161
+ });
151
162
  settings.hooks.PostToolUse.push({
152
163
  matcher: "Edit|Write|MultiEdit|NotebookEdit",
153
164
  hooks: [
@@ -432,7 +443,7 @@ var init_mcpConfig = __esm({
432
443
  });
433
444
 
434
445
  // cli/installer/hookScripts.ts
435
- var SYNKRO_COMMON_SCRIPT, CC_BASH_JUDGE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT, CC_BASH_FOLLOWUP_SCRIPT, CC_TRANSCRIPT_SYNC_SCRIPT, CURSOR_BASH_JUDGE_SCRIPT, CURSOR_EDIT_PRECHECK_SCRIPT, CURSOR_EDIT_CAPTURE_SCRIPT, CURSOR_BASH_FOLLOWUP_SCRIPT;
446
+ var SYNKRO_COMMON_SCRIPT, CC_BASH_JUDGE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_PLAN_JUDGE_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT, CC_BASH_FOLLOWUP_SCRIPT, CC_TRANSCRIPT_SYNC_SCRIPT, CURSOR_BASH_JUDGE_SCRIPT, CURSOR_EDIT_PRECHECK_SCRIPT, CURSOR_EDIT_CAPTURE_SCRIPT, CURSOR_BASH_FOLLOWUP_SCRIPT;
436
447
  var init_hookScripts = __esm({
437
448
  "cli/installer/hookScripts.ts"() {
438
449
  "use strict";
@@ -581,6 +592,49 @@ synkro_capture_local() {
581
592
  ) &
582
593
  }
583
594
 
595
+ # Fire full-content telemetry for local verdicts (used when capture_depth is full or evidence_on_violation).
596
+ synkro_capture_local_full() {
597
+ local hook_type="$1" verdict="$2" severity="$3" category="$4" tool_name="$5" repo="$6" session_id="$7"
598
+ local command="$8" reasoning="$9" rules_checked="\${10:-[]}" violated_rules="\${11:-[]}" recent_user_messages="\${12:-[]}"
599
+ (
600
+ BODY=$(jq -n \\
601
+ --arg eid "$(uuidgen 2>/dev/null || echo "evt_$(date +%s)_$$")" \\
602
+ --arg ht "$hook_type" --arg v "$verdict" --arg s "$severity" --arg c "$category" \\
603
+ --arg tn "$tool_name" --arg r "$repo" --arg sid "$session_id" \\
604
+ --arg cmd "$command" --arg rsn "$reasoning" --arg cd "$SYNKRO_CAPTURE_DEPTH" \\
605
+ --argjson rc "$rules_checked" --argjson vr "$violated_rules" --argjson rum "$recent_user_messages" \\
606
+ '{capture_type:"local_verdict",event_id:$eid,hook_type:$ht,verdict:$v,severity:$s,category:$c,
607
+ model:"claude-sonnet-4-6",tool_name:$tn,capture_depth:$cd,
608
+ command:(if ($cmd|length) > 0 then $cmd else null end),
609
+ reasoning:(if ($rsn|length) > 0 then $rsn else null end),
610
+ rules_checked:$rc, violated_rules:$vr, recent_user_messages:$rum}
611
+ + (if $r != "" then {repo:$r} else {} end)
612
+ + (if $sid != "" then {session_id:$sid} else {} end)')
613
+ curl -sS -X POST "\${GATEWAY_URL}/api/v1/hook/capture" \\
614
+ -H "Content-Type: application/json" -H "Authorization: Bearer $JWT" \\
615
+ -d "$BODY" --max-time 3 >/dev/null 2>&1
616
+ ) &
617
+ }
618
+
619
+ # Dispatch local verdict capture based on capture_depth privacy setting.
620
+ # For full: always send full content. For evidence_on_violation: full only on violations. For local_only: anonymized only.
621
+ synkro_dispatch_capture() {
622
+ local hook_type="$1" verdict="$2" severity="$3" category="$4" tool_name="$5" repo="$6" session_id="$7"
623
+ local command="$8" reasoning="$9" rules_checked="\${10:-[]}" violated_rules="\${11:-[]}" recent_user_messages="\${12:-[]}"
624
+ local send_full=false
625
+ case "\${SYNKRO_CAPTURE_DEPTH:-local_only}" in
626
+ full) send_full=true ;;
627
+ evidence_on_violation)
628
+ case "$verdict" in block|warning|deny) send_full=true ;; esac ;;
629
+ esac
630
+ if [ "$send_full" = "true" ]; then
631
+ synkro_capture_local_full "$hook_type" "$verdict" "$severity" "$category" "$tool_name" "$repo" "$session_id" \\
632
+ "$command" "$reasoning" "$rules_checked" "$violated_rules" "$recent_user_messages"
633
+ else
634
+ synkro_capture_local "$hook_type" "$verdict" "$severity" "$category" "$tool_name" "$repo" "$session_id"
635
+ fi
636
+ }
637
+
584
638
  # Look up a rule's mode from cached rules. Returns "blocking" or "audit".
585
639
  synkro_rule_mode() {
586
640
  local rid="$1"
@@ -700,18 +754,24 @@ if [ "$ROUTE" = "local" ]; then
700
754
  fi
701
755
  synkro_parse_local_verdict "$CC_RESP"
702
756
 
757
+ # Build violated rules JSON for full-content capture
758
+ VIOLATED_JSON="[]"
759
+ [ -n "$LOCAL_RULE_ID" ] && VIOLATED_JSON=$(jq -n --arg r "$LOCAL_RULE_ID" '[$r]')
760
+
703
761
  if [ "$LOCAL_OK" = "false" ]; then
704
762
  RULE_MODE="\${LOCAL_RULE_MODE:-$(synkro_rule_mode "\${LOCAL_RULE_ID}")}"
705
763
  if [ "$RULE_MODE" = "audit" ]; then
706
764
  REASON="[synkro:local] bashGuard \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
707
765
  jq -n --arg m "$REASON" '{systemMessage: $m}'
708
- synkro_capture_local "bash" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
766
+ synkro_dispatch_capture "bash" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
767
+ "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "\${RECENT_USER_MESSAGES:-[]}"
709
768
  else
710
769
  CMD_HASH=$(printf '%s' "$COMMAND" | shasum -a 256 | cut -c1-16)
711
770
  if [ -n "$SESSION_ID" ] && synkro_consent_has_active "$SESSION_ID" "$CMD_HASH"; then
712
771
  REASON="[synkro:local] bashGuard \u2192 pass (consented retry): \${LOCAL_REASON:-retrying previously consented command}"
713
772
  jq -n --arg m "$REASON" '{systemMessage: $m}'
714
- synkro_capture_local "bash" "pass" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
773
+ synkro_dispatch_capture "bash" "pass" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
774
+ "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "\${RECENT_USER_MESSAGES:-[]}"
715
775
  else
716
776
  if [ -n "$SESSION_ID" ] && synkro_consent_has_consumed "$SESSION_ID" "$CMD_HASH"; then
717
777
  synkro_consent_clear_consumed "$SESSION_ID" "$CMD_HASH"
@@ -720,12 +780,14 @@ if [ "$ROUTE" = "local" ]; then
720
780
  REASON="[synkro:local] bashGuard \u2192 block\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
721
781
  jq -n --arg dec "$DEC" --arg reason "$REASON" --arg ctx "$REASON" \\
722
782
  '{systemMessage:$reason,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:$dec,permissionDecisionReason:$reason,additionalContext:$ctx}}'
723
- synkro_capture_local "bash" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
783
+ synkro_dispatch_capture "bash" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
784
+ "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "\${RECENT_USER_MESSAGES:-[]}"
724
785
  fi
725
786
  fi
726
787
  else
727
788
  jq -n --arg m "[synkro:local] bashGuard \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
728
- synkro_capture_local "bash" "pass" "audit" "\${LOCAL_CAT:-trivial_utility}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
789
+ synkro_dispatch_capture "bash" "pass" "audit" "\${LOCAL_CAT:-trivial_utility}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
790
+ "$COMMAND" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "\${RECENT_USER_MESSAGES:-[]}"
729
791
  fi
730
792
  exit 0
731
793
  fi
@@ -893,22 +955,30 @@ if [ "$ROUTE" = "local" ]; then
893
955
  fi
894
956
  synkro_parse_local_verdict "$CC_RESP"
895
957
 
958
+ # Build edit content description and violated rules for full-content capture
959
+ EDIT_CONTENT="file=$FILE_PATH content=$(printf '%s' "$PROPOSED" | head -c 2000)"
960
+ VIOLATED_JSON="[]"
961
+ [ -n "$LOCAL_RULE_ID" ] && VIOLATED_JSON=$(jq -n --arg r "$LOCAL_RULE_ID" '[$r]')
962
+
896
963
  if [ "$LOCAL_OK" = "false" ]; then
897
964
  RULE_MODE="\${LOCAL_RULE_MODE:-$(synkro_rule_mode "\${LOCAL_RULE_ID}")}"
898
965
  if [ "$RULE_MODE" = "audit" ]; then
899
966
  REASON="[synkro:local] editGuard $FILE_SHORT \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
900
967
  jq -n --arg m "$REASON" '{systemMessage: $m}'
901
- synkro_capture_local "edit" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
968
+ synkro_dispatch_capture "edit" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
969
+ "$EDIT_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "[]"
902
970
  else
903
971
  if [ "$IS_HEADLESS" = "1" ]; then DEC="deny"; else DEC="ask"; fi
904
972
  REASON="[synkro:local] editGuard $FILE_SHORT \u2192 block\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
905
973
  jq -n --arg dec "$DEC" --arg reason "$REASON" --arg ctx "$REASON" \\
906
974
  '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:$dec,permissionDecisionReason:$reason,additionalContext:$ctx}}'
907
- synkro_capture_local "edit" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
975
+ synkro_dispatch_capture "edit" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
976
+ "$EDIT_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$VIOLATED_JSON" "[]"
908
977
  fi
909
978
  else
910
979
  jq -n --arg m "[synkro:local] editGuard $FILE_SHORT \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
911
- synkro_capture_local "edit" "pass" "audit" "\${LOCAL_CAT:-trivial_edit}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
980
+ synkro_dispatch_capture "edit" "pass" "audit" "\${LOCAL_CAT:-trivial_edit}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
981
+ "$EDIT_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "[]"
912
982
  fi
913
983
  exit 0
914
984
  fi
@@ -1045,20 +1115,27 @@ if [ "$ROUTE" = "local" ]; then
1045
1115
  fi
1046
1116
  synkro_parse_local_verdict "$CC_RESP"
1047
1117
 
1118
+ SCAN_CONTENT="file=$FILE_PATH"
1119
+ SCAN_VIOLATED="[]"
1120
+ [ -n "$LOCAL_RULE_ID" ] && SCAN_VIOLATED=$(jq -n --arg r "$LOCAL_RULE_ID" '[$r]')
1121
+
1048
1122
  if [ "$LOCAL_OK" = "false" ]; then
1049
1123
  RULE_MODE="\${LOCAL_RULE_MODE:-$(synkro_rule_mode "\${LOCAL_RULE_ID}")}"
1050
1124
  if [ "$RULE_MODE" = "audit" ]; then
1051
1125
  REASON="[synkro:local] editScan $BASENAME \u2192 warning\${LOCAL_RULE_ID:+ ($LOCAL_RULE_ID)}: \${LOCAL_REASON:-policy violation}"
1052
1126
  jq -n --arg m "$REASON" '{systemMessage: $m}'
1053
- synkro_capture_local "edit_scan" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
1127
+ synkro_dispatch_capture "edit_scan" "warning" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
1128
+ "$SCAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$SCAN_VIOLATED" "[]"
1054
1129
  else
1055
1130
  REASON="[synkro:local] editScan $BASENAME \u2192 block: \${LOCAL_REASON:-policy violation}"
1056
1131
  jq -n --arg m "$REASON" '{systemMessage: $m, additionalContext: $m}'
1057
- synkro_capture_local "edit_scan" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
1132
+ synkro_dispatch_capture "edit_scan" "block" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
1133
+ "$SCAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$SCAN_VIOLATED" "[]"
1058
1134
  fi
1059
1135
  else
1060
1136
  jq -n --arg m "[synkro:local] editScan $BASENAME \u2192 pass: \${LOCAL_REASON:-no policy violations detected}" '{systemMessage: $m}'
1061
- synkro_capture_local "edit_scan" "pass" "audit" "\${LOCAL_CAT:-trivial_edit}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID"
1137
+ synkro_dispatch_capture "edit_scan" "pass" "audit" "\${LOCAL_CAT:-trivial_edit}" "$TOOL_NAME" "$GIT_REPO" "$SESSION_ID" \\
1138
+ "$SCAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "[]"
1062
1139
  fi
1063
1140
  exit 0
1064
1141
  fi
@@ -1104,6 +1181,103 @@ else
1104
1181
  echo '{}'
1105
1182
  fi
1106
1183
  exit 0
1184
+ `;
1185
+ CC_PLAN_JUDGE_SCRIPT = `#!/bin/bash
1186
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
1187
+ . "$SCRIPT_DIR/_synkro-common.sh"
1188
+
1189
+ JWT=$(synkro_load_jwt)
1190
+ if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
1191
+ synkro_ensure_fresh_jwt
1192
+
1193
+ PAYLOAD=$(cat)
1194
+ if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
1195
+
1196
+ TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
1197
+ if [ "$TOOL_NAME" != "ExitPlanMode" ]; then echo '{}'; exit 0; fi
1198
+
1199
+ PLAN=$(echo "$PAYLOAD" | jq -r '.tool_input.plan // empty' 2>/dev/null)
1200
+ if [ -z "$PLAN" ] || [ \${#PLAN} -lt 20 ]; then echo '{}'; exit 0; fi
1201
+
1202
+ SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
1203
+ CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
1204
+ GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
1205
+
1206
+ PLAN_SHORT=$(printf '%s' "$PLAN" | head -c 80)
1207
+ synkro_log "planReview checking: $PLAN_SHORT..."
1208
+
1209
+ synkro_load_config
1210
+ ROUTE=$(synkro_route)
1211
+
1212
+ if [ "$ROUTE" = "local" ]; then
1213
+ GRADER_FILE=$(mktemp -t synkro-plan.XXXXXX)
1214
+ trap "rm -f \\"$GRADER_FILE\\"" EXIT
1215
+ printf 'Plan:\\n%s\\nOrg rules: %s\\n' "$(printf '%s' "$PLAN" | head -c 8000)" "\${SYNKRO_RULES:-[]}" > "$GRADER_FILE"
1216
+
1217
+ CC_RESP=$(synkro_local_grade plan < "$GRADER_FILE" 2>&1)
1218
+ if [ $? -ne 0 ]; then
1219
+ echo '{}'; exit 0
1220
+ fi
1221
+ synkro_parse_local_verdict "$CC_RESP"
1222
+
1223
+ PLAN_CONTENT=$(printf '%s' "$PLAN" | head -c 2000)
1224
+ PLAN_VIOLATED="[]"
1225
+ [ -n "$LOCAL_RULE_ID" ] && PLAN_VIOLATED=$(jq -n --arg r "$LOCAL_RULE_ID" '[$r]')
1226
+
1227
+ if [ "$LOCAL_OK" = "false" ]; then
1228
+ VCOUNT=$(printf '%s' "$CC_RESP" | grep -c '<violation>' 2>/dev/null || echo "0")
1229
+ [ "$VCOUNT" = "0" ] && VCOUNT="1"
1230
+ MSG="[synkro:local] planReview \u2192 \${VCOUNT} rule(s) relevant\${LOCAL_RULE_ID:+ (first: $LOCAL_RULE_ID)}: \${LOCAL_REASON:-check org rules during implementation}"
1231
+ jq -n --arg m "$MSG" '{systemMessage: $m}'
1232
+ synkro_dispatch_capture "plan_review" "advisory" "\${LOCAL_SEV}" "\${LOCAL_CAT}" "ExitPlanMode" "$GIT_REPO" "$SESSION_ID" \\
1233
+ "$PLAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "$PLAN_VIOLATED" "[]"
1234
+ else
1235
+ jq -n --arg m "[synkro:local] planReview \u2192 clean: \${LOCAL_REASON:-no relevant org rules for this plan}" '{systemMessage: $m}'
1236
+ synkro_dispatch_capture "plan_review" "clean" "audit" "\${LOCAL_CAT:-general}" "ExitPlanMode" "$GIT_REPO" "$SESSION_ID" \\
1237
+ "$PLAN_CONTENT" "$LOCAL_REASON" "\${SYNKRO_RULES:-[]}" "[]" "[]"
1238
+ fi
1239
+ exit 0
1240
+ fi
1241
+
1242
+ # \u2500\u2500\u2500 Cloud grading \u2500\u2500\u2500
1243
+ BODY=$(jq -n \\
1244
+ --arg hook_event "PreToolUse" \\
1245
+ --arg tool_name "ExitPlanMode" \\
1246
+ --arg plan "$(printf '%s' "$PLAN" | head -c 16000)" \\
1247
+ --arg session_id "$SESSION_ID" \\
1248
+ --arg cwd "$CWD" \\
1249
+ --arg repo "$GIT_REPO" \\
1250
+ '{
1251
+ hook_event: $hook_event,
1252
+ tool_name: $tool_name,
1253
+ tool_input: {plan: $plan},
1254
+ session_id: (if ($session_id | length) > 0 then $session_id else null end),
1255
+ cwd: (if ($cwd | length) > 0 then $cwd else null end),
1256
+ repo: (if ($repo | length) > 0 then $repo else null end)
1257
+ }')
1258
+
1259
+ RESP=$(synkro_post_with_retry "\${GATEWAY_URL}/api/v1/hook/judge" "$BODY" 12)
1260
+
1261
+ if [ -z "$RESP" ]; then
1262
+ synkro_log "planReview \u2192 error (timeout)"
1263
+ echo '{}'
1264
+ exit 0
1265
+ fi
1266
+
1267
+ if ! echo "$RESP" | jq -e '.hook_response' >/dev/null 2>&1; then
1268
+ echo '{}'
1269
+ exit 0
1270
+ fi
1271
+
1272
+ # Force advisory: convert any blocking decision to systemMessage
1273
+ HR=$(echo "$RESP" | jq -c '.hook_response')
1274
+ if echo "$HR" | jq -e '.hookSpecificOutput.permissionDecision' >/dev/null 2>&1; then
1275
+ REASON=$(echo "$HR" | jq -r '.hookSpecificOutput.permissionDecisionReason // "check org rules"' 2>/dev/null)
1276
+ jq -n --arg m "[synkro:cloud] planReview \u2192 advisory: $REASON" '{systemMessage: $m}'
1277
+ else
1278
+ echo "$HR"
1279
+ fi
1280
+ exit 0
1107
1281
  `;
1108
1282
  CC_STOP_SUMMARY_SCRIPT = `#!/bin/bash
1109
1283
  SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
@@ -1200,20 +1374,18 @@ RESP=$(curl -sS -G "\${GATEWAY_URL}/api/v1/hook/config" \\
1200
1374
  --data-urlencode "repo=\${GIT_REPO:-}" \\
1201
1375
  -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
1202
1376
 
1203
- PLAN_NUDGE="Before implementing any multi-step plan, call the synkro-guardrails analyze_plan tool with your implementation plan to check for relevant org coding rules."
1204
-
1205
1377
  OPEN=0
1206
1378
  if [ -n "$RESP" ]; then
1207
1379
  OPEN=$(echo "$RESP" | jq -r '.session_context.open_findings // 0' 2>/dev/null)
1208
1380
  fi
1209
1381
 
1210
1382
  if [ "$OPEN" = "0" ] || [ -z "$OPEN" ]; then
1211
- jq -n --arg m "$ROUTE_LINE"$'\\n'"[synkro] $PLAN_NUDGE" '{systemMessage: $m}'
1383
+ jq -n --arg m "$ROUTE_LINE" '{systemMessage: $m}'
1212
1384
  else
1213
1385
  if [ "$OPEN" = "1" ]; then
1214
- SYS_MSG="$ROUTE_LINE"$'\\n'"[synkro] session start \u2192 1 open finding in this repo from a prior session. $PLAN_NUDGE"
1386
+ SYS_MSG="$ROUTE_LINE"$'\\n'"[synkro] session start \u2192 1 open finding in this repo from a prior session."
1215
1387
  else
1216
- SYS_MSG="$ROUTE_LINE"$'\\n'"[synkro] session start \u2192 \${OPEN} open findings in this repo from prior sessions. $PLAN_NUDGE"
1388
+ SYS_MSG="$ROUTE_LINE"$'\\n'"[synkro] session start \u2192 \${OPEN} open findings in this repo from prior sessions."
1217
1389
  fi
1218
1390
  jq -n --arg m "$SYS_MSG" '{systemMessage: $m}'
1219
1391
  fi
@@ -3079,7 +3251,22 @@ tmux kill-session -t "$SESSION" 2>/dev/null || true
3079
3251
  tmux new-session -d -s "$SESSION" \\
3080
3252
  "claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
3081
3253
 
3082
- # Give claude a moment to start before checking
3254
+ # Claude's --dangerously-skip-permissions shows a one-time confirmation
3255
+ # prompt (accept option = '2'). Because --setting-sources skips user
3256
+ # settings, the acceptance is never persisted, so the prompt reappears
3257
+ # every launch. Auto-accept it by sending the right key sequence.
3258
+ sleep 3
3259
+ if tmux has-session -t "$SESSION" 2>/dev/null; then
3260
+ # Send '2' to accept bypass-permissions, then Enter for any follow-up prompts
3261
+ tmux send-keys -t "$SESSION" '2' 2>/dev/null || true
3262
+ sleep 1
3263
+ tmux send-keys -t "$SESSION" Enter 2>/dev/null || true
3264
+ sleep 1
3265
+ # Additional Enter for workspace trust / MCP consent prompts
3266
+ tmux send-keys -t "$SESSION" Enter 2>/dev/null || true
3267
+ log "Sent auto-accept keys to claude session."
3268
+ fi
3269
+
3083
3270
  sleep 2
3084
3271
  if ! tmux has-session -t "$SESSION" 2>/dev/null; then
3085
3272
  log "ERROR: tmux session died immediately. Check $LOG for details."
@@ -3233,14 +3420,15 @@ function probePort(host, port, timeoutMs = 500) {
3233
3420
  sock.setTimeout(timeoutMs, () => done(false));
3234
3421
  });
3235
3422
  }
3236
- function tmuxKickEnter() {
3423
+ function tmuxDismissPrompts() {
3424
+ spawnSync2("tmux", ["send-keys", "-t", TMUX_SESSION, "2"], { encoding: "utf-8" });
3237
3425
  spawnSync2("tmux", ["send-keys", "-t", TMUX_SESSION, "Enter"], { encoding: "utf-8" });
3238
3426
  }
3239
3427
  async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1") {
3240
3428
  const deadline = Date.now() + timeoutMs;
3241
3429
  while (Date.now() < deadline) {
3242
3430
  if (await probePort(host, port)) return true;
3243
- tmuxKickEnter();
3431
+ tmuxDismissPrompts();
3244
3432
  await new Promise((r) => setTimeout(r, 1e3));
3245
3433
  }
3246
3434
  return probePort(host, port);
@@ -3333,7 +3521,7 @@ async function fetchPrimers() {
3333
3521
  }
3334
3522
  async function getPrimer(role) {
3335
3523
  const prompts = await fetchPrimers();
3336
- const primer = role === "grade-edit" ? prompts.grader_primer_edit : prompts.grader_primer_bash;
3524
+ const primer = role === "grade-edit" ? prompts.grader_primer_edit : role === "grade-plan" ? prompts.grader_primer_plan : prompts.grader_primer_bash;
3337
3525
  if (!primer) {
3338
3526
  throw new Error(`No primer for role "${role}" returned from API.`);
3339
3527
  }
@@ -3642,6 +3830,7 @@ function writeHookScripts() {
3642
3830
  const bashFollowupScriptPath = join11(HOOKS_DIR, "cc-bash-followup.sh");
3643
3831
  const editCaptureScriptPath = join11(HOOKS_DIR, "cc-edit-capture.sh");
3644
3832
  const editPrecheckScriptPath = join11(HOOKS_DIR, "cc-edit-precheck.sh");
3833
+ const planJudgeScriptPath = join11(HOOKS_DIR, "cc-plan-judge.sh");
3645
3834
  const stopSummaryScriptPath = join11(HOOKS_DIR, "cc-stop-summary.sh");
3646
3835
  const sessionStartScriptPath = join11(HOOKS_DIR, "cc-session-start.sh");
3647
3836
  const transcriptSyncScriptPath = join11(HOOKS_DIR, "cc-transcript-sync.sh");
@@ -3654,6 +3843,7 @@ function writeHookScripts() {
3654
3843
  writeFileSync7(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
3655
3844
  writeFileSync7(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
3656
3845
  writeFileSync7(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, "utf-8");
3846
+ writeFileSync7(planJudgeScriptPath, CC_PLAN_JUDGE_SCRIPT, "utf-8");
3657
3847
  writeFileSync7(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, "utf-8");
3658
3848
  writeFileSync7(sessionStartScriptPath, CC_SESSION_START_SCRIPT, "utf-8");
3659
3849
  writeFileSync7(transcriptSyncScriptPath, CC_TRANSCRIPT_SYNC_SCRIPT, "utf-8");
@@ -3666,6 +3856,7 @@ function writeHookScripts() {
3666
3856
  chmodSync2(bashFollowupScriptPath, 493);
3667
3857
  chmodSync2(editCaptureScriptPath, 493);
3668
3858
  chmodSync2(editPrecheckScriptPath, 493);
3859
+ chmodSync2(planJudgeScriptPath, 493);
3669
3860
  chmodSync2(stopSummaryScriptPath, 493);
3670
3861
  chmodSync2(sessionStartScriptPath, 493);
3671
3862
  chmodSync2(transcriptSyncScriptPath, 493);
@@ -3679,6 +3870,7 @@ function writeHookScripts() {
3679
3870
  bashFollowupScript: bashFollowupScriptPath,
3680
3871
  editCaptureScript: editCaptureScriptPath,
3681
3872
  editPrecheckScript: editPrecheckScriptPath,
3873
+ planJudgeScript: planJudgeScriptPath,
3682
3874
  stopSummaryScript: stopSummaryScriptPath,
3683
3875
  sessionStartScript: sessionStartScriptPath,
3684
3876
  transcriptSyncScript: transcriptSyncScriptPath,
@@ -3717,7 +3909,7 @@ function writeConfigEnv(opts) {
3717
3909
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3718
3910
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3719
3911
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3720
- `SYNKRO_VERSION=${shellQuoteSingle("1.4.31")}`
3912
+ `SYNKRO_VERSION=${shellQuoteSingle("1.4.33")}`
3721
3913
  ];
3722
3914
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
3723
3915
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -3846,6 +4038,7 @@ function isAlreadyInstalled() {
3846
4038
  join11(HOOKS_DIR, "cc-bash-followup.sh"),
3847
4039
  join11(HOOKS_DIR, "cc-edit-precheck.sh"),
3848
4040
  join11(HOOKS_DIR, "cc-edit-capture.sh"),
4041
+ join11(HOOKS_DIR, "cc-plan-judge.sh"),
3849
4042
  join11(HOOKS_DIR, "cc-stop-summary.sh"),
3850
4043
  join11(HOOKS_DIR, "cc-session-start.sh")
3851
4044
  ];
@@ -3984,6 +4177,7 @@ async function installCommand(opts = {}) {
3984
4177
  console.log(` ${scripts.bashFollowupScript}`);
3985
4178
  console.log(` ${scripts.editCaptureScript}`);
3986
4179
  console.log(` ${scripts.editPrecheckScript}`);
4180
+ console.log(` ${scripts.planJudgeScript}`);
3987
4181
  console.log(` ${scripts.stopSummaryScript}`);
3988
4182
  console.log(` ${scripts.sessionStartScript}`);
3989
4183
  console.log(` ${scripts.transcriptSyncScript}
@@ -4018,6 +4212,7 @@ async function installCommand(opts = {}) {
4018
4212
  bashFollowupScriptPath: scripts.bashFollowupScript,
4019
4213
  editCaptureScriptPath: scripts.editCaptureScript,
4020
4214
  editPrecheckScriptPath: scripts.editPrecheckScript,
4215
+ planJudgeScriptPath: scripts.planJudgeScript,
4021
4216
  stopSummaryScriptPath: scripts.stopSummaryScript,
4022
4217
  sessionStartScriptPath: scripts.sessionStartScript,
4023
4218
  transcriptSyncScriptPath: scripts.transcriptSyncScript,
@@ -6063,8 +6258,9 @@ async function gradeCommand(args2) {
6063
6258
  let role;
6064
6259
  if (mode === "edit") role = "grade-edit";
6065
6260
  else if (mode === "bash") role = "grade-bash";
6261
+ else if (mode === "plan") role = "grade-plan";
6066
6262
  else {
6067
- console.error("Usage: synkro grade <edit|bash>");
6263
+ console.error("Usage: synkro grade <edit|bash|plan>");
6068
6264
  process.exit(2);
6069
6265
  }
6070
6266
  const payload = await readStdin();