@synkro-sh/cli 1.3.29 → 1.3.31
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 +95 -11
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -602,9 +602,34 @@ fi
|
|
|
602
602
|
if [ "$USE_LOCAL" = "true" ]; then
|
|
603
603
|
# \u2500\u2500\u2500 FREE TIER: grade via the persistent claude daemon (mode=bash). \u2500\u2500\u2500
|
|
604
604
|
|
|
605
|
-
#
|
|
605
|
+
# Refresh primer if older than 24h (server-side IP, fetched on demand).
|
|
606
|
+
PRIMER_FILE="$HOME/.synkro/grader-primer-bash.txt"
|
|
607
|
+
if [ ! -f "$PRIMER_FILE" ] || ! find "$PRIMER_FILE" -mmin -1440 2>/dev/null | grep -q .; then
|
|
608
|
+
NEW_PRIMER=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/judge-prompts" \\
|
|
609
|
+
-H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null \\
|
|
610
|
+
| jq -r '.grader_primer_bash // empty' 2>/dev/null || echo "")
|
|
611
|
+
if [ -n "$NEW_PRIMER" ]; then
|
|
612
|
+
printf '%s' "$NEW_PRIMER" > "$PRIMER_FILE" 2>/dev/null || true
|
|
613
|
+
# Kill the daemon so it restarts with the fresh primer.
|
|
614
|
+
DAEMON_PID_FILE="$HOME/.synkro/daemon/bash/daemon.pid"
|
|
615
|
+
[ -f "$DAEMON_PID_FILE" ] && kill -TERM "$(cat "$DAEMON_PID_FILE" 2>/dev/null)" 2>/dev/null || true
|
|
616
|
+
fi
|
|
617
|
+
fi
|
|
618
|
+
|
|
619
|
+
# Fetch org guardrail rules. In local_only mode use GET (no command leaks);
|
|
620
|
+
# in other modes use POST with content for embedding-based top_k matching.
|
|
621
|
+
RULES_CACHE="$HOME/.synkro/.rules-cache-bash"
|
|
606
622
|
ORG_RULES="[]"
|
|
607
|
-
if [ "$SYNKRO_CAPTURE_DEPTH"
|
|
623
|
+
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
624
|
+
if find "$RULES_CACHE" -mmin -60 2>/dev/null | grep -q .; then
|
|
625
|
+
ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
|
|
626
|
+
else
|
|
627
|
+
ORG_RULES=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" \\
|
|
628
|
+
-H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null \\
|
|
629
|
+
| jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
|
|
630
|
+
[ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
|
|
631
|
+
fi
|
|
632
|
+
else
|
|
608
633
|
ORG_RULES=$(printf '%s' "$COMMAND" | head -c 4000 \\
|
|
609
634
|
| jq -Rs '{content: .}' \\
|
|
610
635
|
| curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=15" \\
|
|
@@ -612,8 +637,8 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
612
637
|
-H "Authorization: Bearer $JWT" \\
|
|
613
638
|
-d @- --max-time 2 2>/dev/null \\
|
|
614
639
|
| jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
|
|
615
|
-
if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
|
|
616
640
|
fi
|
|
641
|
+
if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
|
|
617
642
|
|
|
618
643
|
GRADER_PROMPT_FILE=$(mktemp -t synkro-bash-prompt.XXXXXX)
|
|
619
644
|
trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
|
|
@@ -670,6 +695,20 @@ VERDICT_KIND=$(echo "$VERDICT" | jq -r '.verdict // "warn"' 2>/dev/null)
|
|
|
670
695
|
REASONING=$(echo "$VERDICT" | jq -r '.reasoning // "matched dangerous-verb regex"' 2>/dev/null)
|
|
671
696
|
ALTERNATIVE=$(echo "$VERDICT" | jq -r '.alternative // ""' 2>/dev/null)
|
|
672
697
|
CATEGORY=$(echo "$VERDICT" | jq -r '.category // "destructive_command"' 2>/dev/null)
|
|
698
|
+
RISK_LEVEL=$(echo "$VERDICT" | jq -r '.risk_level // empty' 2>/dev/null)
|
|
699
|
+
|
|
700
|
+
# Backwards-compat: if severity isn't block/audit, derive it from verdict_kind
|
|
701
|
+
# and treat the original severity as the risk_level.
|
|
702
|
+
case "$SEVERITY" in
|
|
703
|
+
block|audit) ;;
|
|
704
|
+
low|medium|high|critical)
|
|
705
|
+
[ -z "$RISK_LEVEL" ] && RISK_LEVEL="$SEVERITY"
|
|
706
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
707
|
+
;;
|
|
708
|
+
*)
|
|
709
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
710
|
+
;;
|
|
711
|
+
esac
|
|
673
712
|
|
|
674
713
|
# Severity-driven surfacing:
|
|
675
714
|
# block \u2192 permissionDecision: "ask" (interactive) or "deny" (headless)
|
|
@@ -735,6 +774,7 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$VERDICT_KIND" ]; then
|
|
|
735
774
|
--arg hook_type "bash" \\
|
|
736
775
|
--arg verdict "$VERDICT_KIND" \\
|
|
737
776
|
--arg severity "$SEVERITY" \\
|
|
777
|
+
--arg risk_level "\${RISK_LEVEL:-low}" \\
|
|
738
778
|
--arg category "$CATEGORY" \\
|
|
739
779
|
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
740
780
|
--arg tool_name "$TOOL_NAME" \\
|
|
@@ -744,6 +784,7 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$VERDICT_KIND" ]; then
|
|
|
744
784
|
hook_type: $hook_type,
|
|
745
785
|
verdict: $verdict,
|
|
746
786
|
severity: $severity,
|
|
787
|
+
risk_level: $risk_level,
|
|
747
788
|
category: $category,
|
|
748
789
|
model: $model,
|
|
749
790
|
tool_name: $tool_name
|
|
@@ -1013,8 +1054,31 @@ fi
|
|
|
1013
1054
|
|
|
1014
1055
|
if [ "$USE_LOCAL" = "true" ]; then
|
|
1015
1056
|
# \u2500\u2500\u2500 LOCAL GRADING: grade via the persistent claude daemon (Python helper).
|
|
1057
|
+
|
|
1058
|
+
# Refresh primer if older than 24h (server-side IP, fetched on demand).
|
|
1059
|
+
PRIMER_FILE="$HOME/.synkro/grader-primer-edit.txt"
|
|
1060
|
+
if [ ! -f "$PRIMER_FILE" ] || ! find "$PRIMER_FILE" -mmin -1440 2>/dev/null | grep -q .; then
|
|
1061
|
+
NEW_PRIMER=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/judge-prompts" \\
|
|
1062
|
+
-H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null \\
|
|
1063
|
+
| jq -r '.grader_primer_edit // empty' 2>/dev/null || echo "")
|
|
1064
|
+
if [ -n "$NEW_PRIMER" ]; then
|
|
1065
|
+
printf '%s' "$NEW_PRIMER" > "$PRIMER_FILE" 2>/dev/null || true
|
|
1066
|
+
DAEMON_PID_FILE="$HOME/.synkro/daemon/edit/daemon.pid"
|
|
1067
|
+
[ -f "$DAEMON_PID_FILE" ] && kill -TERM "$(cat "$DAEMON_PID_FILE" 2>/dev/null)" 2>/dev/null || true
|
|
1068
|
+
fi
|
|
1069
|
+
fi
|
|
1070
|
+
RULES_CACHE="$HOME/.synkro/.rules-cache-edit"
|
|
1016
1071
|
ORG_RULES="[]"
|
|
1017
|
-
if [ "$SYNKRO_CAPTURE_DEPTH"
|
|
1072
|
+
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
1073
|
+
if find "$RULES_CACHE" -mmin -60 2>/dev/null | grep -q .; then
|
|
1074
|
+
ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
|
|
1075
|
+
else
|
|
1076
|
+
ORG_RULES=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" \\
|
|
1077
|
+
-H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null \\
|
|
1078
|
+
| jq -c '[.rules[]? | {rule_id, text, severity, category, mode}]' 2>/dev/null || echo "[]")
|
|
1079
|
+
[ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
|
|
1080
|
+
fi
|
|
1081
|
+
else
|
|
1018
1082
|
ORG_RULES=$(printf '%s' "$PROPOSED" | head -c 8000 \\
|
|
1019
1083
|
| jq -Rs '{content: .}' \\
|
|
1020
1084
|
| curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
|
|
@@ -1022,8 +1086,8 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1022
1086
|
-H "Authorization: Bearer $JWT" \\
|
|
1023
1087
|
-d @- --max-time 2 2>/dev/null \\
|
|
1024
1088
|
| jq -c '[.rules[]? | {rule_id, text, severity, category, mode}]' 2>/dev/null || echo "[]")
|
|
1025
|
-
if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
|
|
1026
1089
|
fi
|
|
1090
|
+
if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
|
|
1027
1091
|
|
|
1028
1092
|
GRADER_PROMPT_FILE=$(mktemp -t synkro-grade.XXXXXX)
|
|
1029
1093
|
trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
|
|
@@ -2199,7 +2263,12 @@ OUTPUT RULES \u2014 strictest possible, no exceptions:
|
|
|
2199
2263
|
|
|
2200
2264
|
1. NO reasoning. NO preamble. NO commentary.
|
|
2201
2265
|
2. Your reply is exactly one <synkro-verdict>JSON</synkro-verdict> block. Nothing else.
|
|
2202
|
-
3. JSON shape: {"verdict": "warn"|"allow", "severity": "low|medium|high|critical", "category": "snake_case", "reasoning": "<= 25 words, cites intent + match/mismatch", "alternative": "safer command or null"}
|
|
2266
|
+
3. JSON shape: {"verdict": "warn"|"allow", "severity": "block"|"audit", "risk_level": "low"|"medium"|"high"|"critical", "category": "snake_case", "reasoning": "<= 25 words, cites intent + match/mismatch", "alternative": "safer command or null"}
|
|
2267
|
+
|
|
2268
|
+
SEVERITY MAPPING (strict):
|
|
2269
|
+
- verdict="warn" \u2192 severity="block"
|
|
2270
|
+
- verdict="allow" \u2192 severity="audit"
|
|
2271
|
+
risk_level always reflects the underlying danger level (low/medium/high/critical), independent of the routing decision.
|
|
2203
2272
|
|
|
2204
2273
|
Rules:
|
|
2205
2274
|
- WARN if destructive/irreversible AND not aligned with user intent, OR has wildly disproportionate blast radius vs the request.
|
|
@@ -3407,12 +3476,27 @@ function ensureSynkroDir() {
|
|
|
3407
3476
|
mkdirSync5(BIN_DIR, { recursive: true });
|
|
3408
3477
|
mkdirSync5(OFFSETS_DIR, { recursive: true });
|
|
3409
3478
|
}
|
|
3410
|
-
function
|
|
3479
|
+
async function fetchGraderPrimers(gatewayUrl, token) {
|
|
3480
|
+
try {
|
|
3481
|
+
const resp = await fetch(`${gatewayUrl}/api/v1/cli/judge-prompts`, {
|
|
3482
|
+
headers: { "Authorization": `Bearer ${token}` }
|
|
3483
|
+
});
|
|
3484
|
+
if (!resp.ok) throw new Error(`status ${resp.status}`);
|
|
3485
|
+
const data = await resp.json();
|
|
3486
|
+
if (!data.grader_primer_bash || !data.grader_primer_edit) throw new Error("missing primer fields");
|
|
3487
|
+
return { bash: data.grader_primer_bash, edit: data.grader_primer_edit };
|
|
3488
|
+
} catch (err) {
|
|
3489
|
+
console.warn(`[synkro] primer fetch failed (${err.message}); using bundled fallback`);
|
|
3490
|
+
return { bash: GRADER_PRIMER_BASH, edit: GRADER_PRIMER_EDIT };
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
async function writeGraderDaemon(gatewayUrl, token) {
|
|
3411
3494
|
writeFileSync5(GRADER_DAEMON_PATH, GRADER_DAEMON_PY, "utf-8");
|
|
3412
3495
|
chmodSync(GRADER_DAEMON_PATH, 493);
|
|
3413
|
-
|
|
3496
|
+
const primers = await fetchGraderPrimers(gatewayUrl, token);
|
|
3497
|
+
writeFileSync5(GRADER_PRIMER_EDIT_PATH, primers.edit, "utf-8");
|
|
3414
3498
|
chmodSync(GRADER_PRIMER_EDIT_PATH, 420);
|
|
3415
|
-
writeFileSync5(GRADER_PRIMER_BASH_PATH,
|
|
3499
|
+
writeFileSync5(GRADER_PRIMER_BASH_PATH, primers.bash, "utf-8");
|
|
3416
3500
|
chmodSync(GRADER_PRIMER_BASH_PATH, 420);
|
|
3417
3501
|
}
|
|
3418
3502
|
function writeHookScripts() {
|
|
@@ -3470,7 +3554,7 @@ function writeConfigEnv(opts) {
|
|
|
3470
3554
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3471
3555
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3472
3556
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
3473
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
3557
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.31")}`
|
|
3474
3558
|
];
|
|
3475
3559
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3476
3560
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
@@ -3698,7 +3782,7 @@ async function installCommand(opts = {}) {
|
|
|
3698
3782
|
console.log(` ${scripts.sessionStartScript}`);
|
|
3699
3783
|
console.log(` ${scripts.transcriptSyncScript}
|
|
3700
3784
|
`);
|
|
3701
|
-
writeGraderDaemon();
|
|
3785
|
+
await writeGraderDaemon(gatewayUrl, token);
|
|
3702
3786
|
for (const mode of ["edit", "bash"]) {
|
|
3703
3787
|
const pidFile = join6(SYNKRO_DIR2, "daemon", mode, "daemon.pid");
|
|
3704
3788
|
try {
|