@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 +218 -22
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
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
|
|
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
|
-
|
|
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.
|
|
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();
|