@synkro-sh/cli 1.7.1 → 1.7.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 +80 -7
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -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. */
|
|
@@ -1966,7 +2022,7 @@ export async function filterRules(commandText: string, allRules: Rule[]): Promis
|
|
|
1966
2022
|
if (allRules.length <= 3) return allRules;
|
|
1967
2023
|
|
|
1968
2024
|
// Cloud: the embedding index lives server-side (the container grade path can't
|
|
1969
|
-
// reach 127.0.0.1), so ask the gateway for the top-
|
|
2025
|
+
// reach 127.0.0.1), so ask the gateway for the top-5 relevant rules \u2014 same
|
|
1970
2026
|
// CF/BYOK bge-base vectors, same behavior as local. Without this, cloud grades
|
|
1971
2027
|
// dumped EVERY rule into the prompt (9KB+), the dominant cause of grade
|
|
1972
2028
|
// timeouts. Any failure falls back to all rules (status quo), never blocks.
|
|
@@ -1977,7 +2033,7 @@ export async function filterRules(commandText: string, allRules: Rule[]): Promis
|
|
|
1977
2033
|
const resp = await fetch(GATEWAY_URL + '/api/v1/hook/filter-rules', {
|
|
1978
2034
|
method: 'POST',
|
|
1979
2035
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1980
|
-
body: JSON.stringify({ text: commandText, top_k:
|
|
2036
|
+
body: JSON.stringify({ text: commandText, top_k: 5 }),
|
|
1981
2037
|
signal: AbortSignal.timeout(2000),
|
|
1982
2038
|
});
|
|
1983
2039
|
if (!resp.ok) return allRules;
|
|
@@ -1996,7 +2052,7 @@ export async function filterRules(commandText: string, allRules: Rule[]): Promis
|
|
|
1996
2052
|
const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/filter-rules', {
|
|
1997
2053
|
method: 'POST',
|
|
1998
2054
|
headers: { 'Content-Type': 'application/json' },
|
|
1999
|
-
body: JSON.stringify({ text: commandText, top_k:
|
|
2055
|
+
body: JSON.stringify({ text: commandText, top_k: 5 }),
|
|
2000
2056
|
signal: AbortSignal.timeout(500),
|
|
2001
2057
|
});
|
|
2002
2058
|
if (!resp.ok) return allRules;
|
|
@@ -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.3")}`
|
|
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.3");
|
|
15605
15678
|
}
|
|
15606
15679
|
function printHelp2() {
|
|
15607
15680
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|