@synkro-sh/cli 1.7.2 → 1.7.4
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 +80 -7
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1426,7 +1426,7 @@ export function effectiveGraderPool(synkroFile: SynkroFileConfig, hookAgentKind:
|
|
|
1426
1426
|
// Falls back to synkro.toml whenever the server pool is absent, so a missing
|
|
1427
1427
|
// value never breaks grading \u2014 it just reverts to today's behavior.
|
|
1428
1428
|
const managed = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud'
|
|
1429
|
-
|| (process.env.SYNKRO_GRADING_MODE || synkroFile.grader?.mode
|
|
1429
|
+
|| (process.env.SYNKRO_GRADING_MODE || config?.gradingMode || synkroFile.grader?.mode) === 'byok';
|
|
1430
1430
|
const pool = (managed && config?.graderPool)
|
|
1431
1431
|
? normalizePoolToken(config.graderPool)
|
|
1432
1432
|
: normalizePoolToken(synkroFile.grader.pool);
|
|
@@ -1632,7 +1632,7 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1632
1632
|
// \u2500\u2500\u2500 Routing \u2500\u2500\u2500
|
|
1633
1633
|
|
|
1634
1634
|
export async function route(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'cloud'> {
|
|
1635
|
-
const gradingMode =
|
|
1635
|
+
const gradingMode = process.env.SYNKRO_GRADING_MODE || config.gradingMode || synkroFile?.grader?.mode || 'local';
|
|
1636
1636
|
if (gradingMode === 'byok') return 'cloud';
|
|
1637
1637
|
// Cloud-container deploy ([grader] location = cloud): grade via localGrade(),
|
|
1638
1638
|
// which routes to the hosted container (containers.synkro.sh) and emits the
|
|
@@ -1653,7 +1653,7 @@ export async function route(config: HookConfig, synkroFile?: SynkroFileConfig):
|
|
|
1653
1653
|
}
|
|
1654
1654
|
|
|
1655
1655
|
export async function cweRoute(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'byok' | 'skip'> {
|
|
1656
|
-
const gradingMode =
|
|
1656
|
+
const gradingMode = process.env.SYNKRO_GRADING_MODE || config.gradingMode || synkroFile?.grader?.mode || 'local';
|
|
1657
1657
|
if (gradingMode === 'byok') return 'byok';
|
|
1658
1658
|
// Cloud-container: CWE scanning runs on the hosted container via localGradeCwe()
|
|
1659
1659
|
// (which honors SYNKRO_DEPLOY_LOCATION). Without this, cweChannelUp() is false in
|
|
@@ -1951,6 +1951,62 @@ export async function localGradeCwe(prompt: string, agentKind: AgentKind = 'clau
|
|
|
1951
1951
|
return channelGrade('grade-cwe', prompt, jwt, 18930, timeoutMs, agentKind);
|
|
1952
1952
|
}
|
|
1953
1953
|
|
|
1954
|
+
// \u2500\u2500\u2500 Fix-poll directive (ask-mode human-preference capture) \u2500\u2500\u2500
|
|
1955
|
+
// Turns an ask-mode flag into a labeled DPO pair: candidate A is the grader's
|
|
1956
|
+
// suggested fix; candidate B is a best-effort second grade nudged toward a
|
|
1957
|
+
// different technique. Registers a pending fix_labels item and returns an agent
|
|
1958
|
+
// directive to poll the user + call record_fix_choice. PURELY ADDITIVE \u2014 any
|
|
1959
|
+
// failure returns '' so enforcement behaves exactly as before.
|
|
1960
|
+
export async function buildFixPollDirective(o: { candA: string; code: string; ruleId: string; sessionId: string; filePath: string; model: string; graderPrompt: string; graderPool?: AgentKind }): Promise<string> {
|
|
1961
|
+
try {
|
|
1962
|
+
const candA = (o.candA || '').trim();
|
|
1963
|
+
if (!candA) return '';
|
|
1964
|
+
const cands: string[] = [candA];
|
|
1965
|
+
try {
|
|
1966
|
+
// Candidate B from the SAME grader that produced A (the edit/rule grader),
|
|
1967
|
+
// reusing its prompt + rules \u2014 nudged toward a different remediation. Using
|
|
1968
|
+
// grade-cwe here would mis-grade an org-rule violation ('no rules' / wrong
|
|
1969
|
+
// surface), so we re-grade the edit surface. Best-effort: 1 candidate is fine.
|
|
1970
|
+
const altPrompt = (o.graderPrompt || '') + '\\n\\n// SECURITY REVIEWER: also propose an ALTERNATIVE remediation that uses a DIFFERENT technique than the most obvious one.';
|
|
1971
|
+
const rawB = await localGrade('edit', altPrompt, 12000, o.graderPool);
|
|
1972
|
+
const candB = (parseVerdict(rawB).suggestedFix || '').trim();
|
|
1973
|
+
if (candB && candB !== candA) cands.push(candB);
|
|
1974
|
+
} catch (e) { /* best-effort: one candidate is fine */ }
|
|
1975
|
+
// Route by deploy location: cloud -> the gateway (Timescale, org-scoped, auth'd);
|
|
1976
|
+
// local -> the on-device server (PGLite). GATEWAY_URL is host-allowlisted; the
|
|
1977
|
+
// loopback port is range-validated (CWE-918) so neither is an arbitrary host.
|
|
1978
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
1979
|
+
let url: string;
|
|
1980
|
+
if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') {
|
|
1981
|
+
const jwt = loadJwt();
|
|
1982
|
+
if (!jwt) return '';
|
|
1983
|
+
headers['Authorization'] = 'Bearer ' + jwt;
|
|
1984
|
+
url = GATEWAY_URL + '/api/v1/cli/fix-poll';
|
|
1985
|
+
} else {
|
|
1986
|
+
const rawPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);
|
|
1987
|
+
const mcpPort = (rawPort > 0 && rawPort < 65536) ? rawPort : 18931;
|
|
1988
|
+
url = 'http://127.0.0.1:' + mcpPort + '/api/local/fix-poll';
|
|
1989
|
+
}
|
|
1990
|
+
const resp = await fetch(url, {
|
|
1991
|
+
method: 'POST',
|
|
1992
|
+
headers,
|
|
1993
|
+
body: JSON.stringify({
|
|
1994
|
+
session_id: o.sessionId, file_path: o.filePath, cwe: o.ruleId,
|
|
1995
|
+
vulnerable_code: (o.code || '').slice(0, 8000), candidates: cands, generator_model: o.model || '',
|
|
1996
|
+
}),
|
|
1997
|
+
signal: AbortSignal.timeout(5000),
|
|
1998
|
+
});
|
|
1999
|
+
if (!resp.ok) return '';
|
|
2000
|
+
const j = await resp.json() as { item_id?: string };
|
|
2001
|
+
if (!j || !j.item_id) return '';
|
|
2002
|
+
let optsStr = '';
|
|
2003
|
+
for (let i = 0; i < cands.length; i++) optsStr += '\\n [' + i + '] ' + cands[i];
|
|
2004
|
+
return '\\n\\nSYNKRO FIX POLL (item_id=' + j.item_id + '): Before retrying, present these candidate fixes to the user with the AskUserQuestion tool (one option per candidate, plus a "None of the above" option), then call the record_fix_choice tool with item_id="' + j.item_id + '" and chosen_idx set to the 0-based option the user picked (-1 for none of the above). Candidates:' + optsStr;
|
|
2005
|
+
} catch (e) {
|
|
2006
|
+
return '';
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
1954
2010
|
// \u2500\u2500\u2500 Rule Pre-Filter (embedding-based) \u2500\u2500\u2500
|
|
1955
2011
|
|
|
1956
2012
|
/** User message + action for embedding search \u2014 intent surfaces boundary/consent rules the command alone misses. */
|
|
@@ -4524,6 +4580,11 @@ async function main() {
|
|
|
4524
4580
|
|
|
4525
4581
|
if (rt === 'local') {
|
|
4526
4582
|
// \u2500\u2500\u2500 Local grading: org rules ONLY (channel 1, port 18929) \u2500\u2500\u2500
|
|
4583
|
+
// CAP COUPLING: 4000 chars keeps the grade payload small enough to finish
|
|
4584
|
+
// well inside the timing budget (hook abort 45s < channel 120s < dispatch
|
|
4585
|
+
// 125s). Raising this materially grows per-grade inference time and, under
|
|
4586
|
+
// concurrency, pushes the latency tail toward those ceilings \u2014 so if you
|
|
4587
|
+
// raise it, revisit WORKER_DISPATCH_TIMEOUT_MS / the channel timeout first.
|
|
4527
4588
|
const proposedShort = proposed.slice(0, 4000);
|
|
4528
4589
|
// The TASK grade sees the DIFF for large files, so a contradiction or drift DEEP
|
|
4529
4590
|
// in a big file is visible \u2014 the old "first 4KB of the whole file" window missed
|
|
@@ -4715,8 +4776,12 @@ async function main() {
|
|
|
4715
4776
|
const mode = normalizeMode(verdict.ruleMode || ruleMode(verdict.ruleId, config.rules));
|
|
4716
4777
|
const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');
|
|
4717
4778
|
|
|
4779
|
+
// fix mode: the grader still produces a fix, but the MODEL applies it (no
|
|
4780
|
+
// human poll) \u2014 surface our suggested fix so the agent picks it up rather
|
|
4781
|
+
// than re-deriving. ask mode: no fix in the deny text; the poll directive
|
|
4782
|
+
// (below) lets the HUMAN choose among candidates.
|
|
4718
4783
|
const denyReason = mode === 'fix'
|
|
4719
|
-
? 'Guard: ' + guardReason + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \u2014 resolve the violation in code yourself.'
|
|
4784
|
+
? 'Guard: ' + guardReason + (verdict.suggestedFix ? '\\nSuggested fix (apply this): ' + verdict.suggestedFix : '') + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \u2014 resolve the violation in code yourself.'
|
|
4720
4785
|
: 'Guard: ' + guardReason + '\\nAsk the user for explicit consent before retrying.';
|
|
4721
4786
|
dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',
|
|
4722
4787
|
toolName, gitRepo, sessionId, config.captureDepth, {
|
|
@@ -4724,9 +4789,17 @@ async function main() {
|
|
|
4724
4789
|
rulesChecked: relevantRules, violatedRules,
|
|
4725
4790
|
ccModel: captureModel, codeChange: editDiff, ...lineMetrics,
|
|
4726
4791
|
});
|
|
4792
|
+
// Ask-mode only: turn this flag into a human-preference label (additive; '' on any failure).
|
|
4793
|
+
let pollDirective = '';
|
|
4794
|
+
if (mode !== 'fix' && verdict.suggestedFix) {
|
|
4795
|
+
pollDirective = await buildFixPollDirective({
|
|
4796
|
+
candA: verdict.suggestedFix, code: proposed, ruleId: verdict.ruleId,
|
|
4797
|
+
sessionId, filePath, model: captureModel, graderPrompt, graderPool,
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4727
4800
|
outputJson({
|
|
4728
4801
|
systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 blocked: ' + guardReason,
|
|
4729
|
-
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason },
|
|
4802
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: denyReason, additionalContext: denyReason + pollDirective },
|
|
4730
4803
|
});
|
|
4731
4804
|
return;
|
|
4732
4805
|
}
|
|
@@ -11538,7 +11611,7 @@ function writeConfigEnv(opts) {
|
|
|
11538
11611
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11539
11612
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11540
11613
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11541
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.7.
|
|
11614
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.7.4")}`
|
|
11542
11615
|
];
|
|
11543
11616
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11544
11617
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -15601,7 +15674,7 @@ var args = process.argv.slice(2);
|
|
|
15601
15674
|
var cmd = args[0] || "";
|
|
15602
15675
|
var subArgs = args.slice(1);
|
|
15603
15676
|
function printVersion() {
|
|
15604
|
-
console.log("1.7.
|
|
15677
|
+
console.log("1.7.4");
|
|
15605
15678
|
}
|
|
15606
15679
|
function printHelp2() {
|
|
15607
15680
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|