@synkro-sh/cli 1.7.0 → 1.7.2
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 +108 -20
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1966,7 +1966,7 @@ export async function filterRules(commandText: string, allRules: Rule[]): Promis
|
|
|
1966
1966
|
if (allRules.length <= 3) return allRules;
|
|
1967
1967
|
|
|
1968
1968
|
// 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-
|
|
1969
|
+
// reach 127.0.0.1), so ask the gateway for the top-5 relevant rules \u2014 same
|
|
1970
1970
|
// CF/BYOK bge-base vectors, same behavior as local. Without this, cloud grades
|
|
1971
1971
|
// dumped EVERY rule into the prompt (9KB+), the dominant cause of grade
|
|
1972
1972
|
// timeouts. Any failure falls back to all rules (status quo), never blocks.
|
|
@@ -1977,7 +1977,7 @@ export async function filterRules(commandText: string, allRules: Rule[]): Promis
|
|
|
1977
1977
|
const resp = await fetch(GATEWAY_URL + '/api/v1/hook/filter-rules', {
|
|
1978
1978
|
method: 'POST',
|
|
1979
1979
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1980
|
-
body: JSON.stringify({ text: commandText, top_k:
|
|
1980
|
+
body: JSON.stringify({ text: commandText, top_k: 5 }),
|
|
1981
1981
|
signal: AbortSignal.timeout(2000),
|
|
1982
1982
|
});
|
|
1983
1983
|
if (!resp.ok) return allRules;
|
|
@@ -1996,7 +1996,7 @@ export async function filterRules(commandText: string, allRules: Rule[]): Promis
|
|
|
1996
1996
|
const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/filter-rules', {
|
|
1997
1997
|
method: 'POST',
|
|
1998
1998
|
headers: { 'Content-Type': 'application/json' },
|
|
1999
|
-
body: JSON.stringify({ text: commandText, top_k:
|
|
1999
|
+
body: JSON.stringify({ text: commandText, top_k: 5 }),
|
|
2000
2000
|
signal: AbortSignal.timeout(500),
|
|
2001
2001
|
});
|
|
2002
2002
|
if (!resp.ok) return allRules;
|
|
@@ -4619,8 +4619,16 @@ async function main() {
|
|
|
4619
4619
|
const cweFindings = [...cweMap.values()];
|
|
4620
4620
|
const editContent = 'file=' + filePath + ' content=' + proposed.slice(0, 2000);
|
|
4621
4621
|
const violatedRules = ruleVerdict.ruleId ? [ruleVerdict.ruleId] : [];
|
|
4622
|
-
|
|
4623
|
-
|
|
4622
|
+
// Include EVERY flagged CWE's description (not a 5-cap) so the agent can fix
|
|
4623
|
+
// all of them \u2014 even 30 in one edit. Per-finding fallback: reason -> CWE name.
|
|
4624
|
+
const cweClean = (s) => String(s == null ? '' : s).split('').map(c => { const k = c.charCodeAt(0); return (k < 32 || k === 127) ? ' ' : c; }).join('').replace(/ +/g, ' ').trim().slice(0, 400);
|
|
4625
|
+
const cweId = (s) => String(s == null ? 'CWE-UNKNOWN' : s).replace(/[^A-Za-z0-9_-]/g, '').slice(0, 20) || 'CWE-UNKNOWN';
|
|
4626
|
+
// One sentence each (trim at first . ! or ?) so several CWEs in one edit stay tight.
|
|
4627
|
+
const cweOne = (s) => { const t = cweClean(s); const i = t.search(/[.!?]( [A-Z]|$)/); return (i >= 0 ? t.slice(0, i + 1) : t).slice(0, 220); };
|
|
4628
|
+
// One line per CWE: explanation + Fix. Drop the Fix when it just restates the
|
|
4629
|
+
// explanation so a single CWE never double-prints the same prose.
|
|
4630
|
+
const cweBlock = cweFindings
|
|
4631
|
+
.map(f => { const why = cweOne(f.reason || f.name || 'code weakness'); const fix = f.suggestedFix ? cweOne(f.suggestedFix) : ''; const showFix = fix && why.toLowerCase().indexOf(fix.slice(0, 40).toLowerCase()) === -1; return '[' + cweId(f.id) + '] ' + why + (showFix ? ' Fix: ' + fix : ''); })
|
|
4624
4632
|
.join('\\n');
|
|
4625
4633
|
for (const f of cweFindings) {
|
|
4626
4634
|
dispatchFinding(jwt, { session_id: sessionId, file_path: filePath, finding_type: 'cwe', finding_id: f.id, status: 'open' }, config.captureDepth);
|
|
@@ -5143,12 +5151,41 @@ async function main() {
|
|
|
5143
5151
|
const label = count === 1 ? 'match' : 'matches';
|
|
5144
5152
|
const cweMsg = cweTag + ' ' + fileShort + ' \u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';
|
|
5145
5153
|
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
const
|
|
5150
|
-
|
|
5151
|
-
|
|
5154
|
+
// Sanitize any scan-derived text before embedding it in the agent message
|
|
5155
|
+
// (CWE-20): bound length + strip control chars; constrain the CWE id shape.
|
|
5156
|
+
const sanitizeDesc = (s: any) => String(s == null ? '' : s).replace(/[\x00-\x1F\x7F]/g, ' ').replace(/\s{2,}/g, ' ').trim().slice(0, 400);
|
|
5157
|
+
const sanitizeId = (s: any) => String(s == null ? '' : s).replace(/[^A-Za-z0-9_-]/g, '').slice(0, 20).toUpperCase() || 'CWE-UNKNOWN';
|
|
5158
|
+
// ONE sentence, hard-capped \u2014 keeps each line tight so 4+ CWEs in a single edit
|
|
5159
|
+
// never blow the message-size limit. Trims at the first sentence terminator.
|
|
5160
|
+
const oneSentence = (s: any) => { const t = sanitizeDesc(s); const m = t.match(/^[\s\S]*?[.!?](\s+(?=[A-Z])|$)/); return (m ? m[0] : t).trim().slice(0, 220); };
|
|
5161
|
+
// ONE line per flagged CWE: a one-sentence explanation + a one-sentence Fix.
|
|
5162
|
+
// The Fix is dropped when it merely restates the explanation (the grader
|
|
5163
|
+
// sometimes echoes it) so the line never double-prints the same prose.
|
|
5164
|
+
const cweSeen = new Set<string>();
|
|
5165
|
+
const cweBlocks = activeCweIds
|
|
5166
|
+
.map((cweId: string) => {
|
|
5167
|
+
const sid = sanitizeId(cweId);
|
|
5168
|
+
if (cweSeen.has(sid)) return '';
|
|
5169
|
+
cweSeen.add(sid);
|
|
5170
|
+
const f = findings.find((x: any) => x.cwe === cweId);
|
|
5171
|
+
const why = oneSentence(f && (f.reason || f.name) ? (f.reason || f.name) : 'code weakness detected');
|
|
5172
|
+
const fix = f && f.suggested_fix ? oneSentence(f.suggested_fix) : '';
|
|
5173
|
+
const showFix = !!fix && !why.toLowerCase().includes(fix.slice(0, 40).toLowerCase());
|
|
5174
|
+
return '[' + sid + '] ' + why + (showFix ? ' Fix: ' + fix : '');
|
|
5175
|
+
})
|
|
5176
|
+
.filter((line: string) => line.length > 0);
|
|
5177
|
+
// Bound the total: list as many CWEs as fit, spill the rest to a "+N more" note
|
|
5178
|
+
// (agent fixes the listed ones, then re-runs to surface the remainder).
|
|
5179
|
+
const MAX_CWE_CHARS = 4000;
|
|
5180
|
+
let denyDetail = cweBlocks.join('\n');
|
|
5181
|
+
if (denyDetail.length > MAX_CWE_CHARS) {
|
|
5182
|
+
const kept: string[] = [];
|
|
5183
|
+
let used = 0;
|
|
5184
|
+
for (const b of cweBlocks) { if (used + b.length + 1 > MAX_CWE_CHARS) break; kept.push(b); used += b.length + 1; }
|
|
5185
|
+
const more = cweBlocks.length - kept.length;
|
|
5186
|
+
denyDetail = kept.join('\n') + (more > 0 ? '\n(+' + more + ' more CWE weakness' + (more === 1 ? '' : 'es') + ' \u2014 fix these first, then re-run to surface the rest)' : '');
|
|
5187
|
+
}
|
|
5188
|
+
const ctx = 'CWE weaknesses to fix (' + count + '):\n' + denyDetail + '\nFix ALL of the above before retrying. Do NOT ask the user to make the edit manually \u2014 resolve every weakness in code yourself.';
|
|
5152
5189
|
|
|
5153
5190
|
emitBlockScanFindings(
|
|
5154
5191
|
jwt,
|
|
@@ -5294,6 +5331,7 @@ async function main() {
|
|
|
5294
5331
|
|
|
5295
5332
|
const cweIds: string[] = [];
|
|
5296
5333
|
const fixes: Record<string, string> = {};
|
|
5334
|
+
const reasons: Record<string, string> = {};
|
|
5297
5335
|
const snippets: Record<string, string> = {};
|
|
5298
5336
|
let mergedReason = '';
|
|
5299
5337
|
let mergedSeverity = '';
|
|
@@ -5301,6 +5339,35 @@ async function main() {
|
|
|
5301
5339
|
let anyFailed = false;
|
|
5302
5340
|
|
|
5303
5341
|
for (const gradeResp of gradeResponses) {
|
|
5342
|
+
// PRIMARY shape — the live CWE grader returns ONE JSON object:
|
|
5343
|
+
// {"cweIds":[..],"reasons":{id:..},"fixes":{id:..},"snippets":{id:..},"mergedReason":".."}
|
|
5344
|
+
// Parse it directly so each CWE keeps its OWN contextual reason + specific
|
|
5345
|
+
// fix. Critically this also bounds detection to cweIds — NOT a regex scrape of
|
|
5346
|
+
// the prose (which used to grab CWE-### tokens out of mergedReason, e.g. a
|
|
5347
|
+
// "no CWE-502 found" sentence, and over-report findings).
|
|
5348
|
+
try {
|
|
5349
|
+
const objM = gradeResp.match(/\{[\s\S]*"cweIds"[\s\S]*\}/);
|
|
5350
|
+
if (objM) {
|
|
5351
|
+
const p = JSON.parse(objM[0]);
|
|
5352
|
+
if (Array.isArray(p.cweIds)) {
|
|
5353
|
+
const ids = p.cweIds.map((x: any) => String(x).trim().replace(/^cwe-/i, 'CWE-')).filter((id: string) => /^CWE-\d+$/.test(id));
|
|
5354
|
+
if (ids.length > 0) {
|
|
5355
|
+
anyFailed = true;
|
|
5356
|
+
if (!mergedReason && typeof p.mergedReason === 'string') mergedReason = p.mergedReason;
|
|
5357
|
+
for (const id of ids) {
|
|
5358
|
+
if (!cweIds.includes(id)) cweIds.push(id);
|
|
5359
|
+
const rsn = p.reasons && typeof p.reasons[id] === 'string' ? p.reasons[id] : '';
|
|
5360
|
+
const fx = p.fixes && typeof p.fixes[id] === 'string' ? p.fixes[id] : '';
|
|
5361
|
+
const sn = p.snippets && typeof p.snippets[id] === 'string' ? p.snippets[id] : '';
|
|
5362
|
+
if (rsn && !reasons[id]) reasons[id] = rsn;
|
|
5363
|
+
if (fx && !fixes[id]) fixes[id] = fx;
|
|
5364
|
+
if (sn && !snippets[id]) snippets[id] = sn;
|
|
5365
|
+
}
|
|
5366
|
+
continue;
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
} catch { /* not the JSON-object shape — fall through to legacy parsing */ }
|
|
5304
5371
|
// The CWE grader emits findings, but its wrapper VARIES per call:
|
|
5305
5372
|
// {"findings":[...]} | <findings>[...]</findings> | a bare [...] array.
|
|
5306
5373
|
// Extract findings from any of them. Without this, parseVerdict sees no
|
|
@@ -5326,7 +5393,8 @@ async function main() {
|
|
|
5326
5393
|
// real CWE id (also blocks __proto__/constructor key injection).
|
|
5327
5394
|
if (!/^CWE-\d+$/.test(id)) continue;
|
|
5328
5395
|
if (!cweIds.includes(id)) cweIds.push(id);
|
|
5329
|
-
if (f.
|
|
5396
|
+
if (f.suggested_fix && !fixes[id]) fixes[id] = String(f.suggested_fix);
|
|
5397
|
+
if ((f.reason || f.summary || f.detail) && !reasons[id]) reasons[id] = String(f.reason || f.summary || f.detail);
|
|
5330
5398
|
if (f.code_snippet && !snippets[id]) snippets[id] = String(f.code_snippet);
|
|
5331
5399
|
if (!mergedReason && (f.summary || f.detail)) mergedReason = String(f.summary || f.detail);
|
|
5332
5400
|
if (!mergedSeverity && f.severity) mergedSeverity = String(f.severity);
|
|
@@ -5363,6 +5431,10 @@ async function main() {
|
|
|
5363
5431
|
const snip = snM[1].replace(/^\n+|\n+$/g, '');
|
|
5364
5432
|
if (snip.trim()) snippets[id] = snip;
|
|
5365
5433
|
}
|
|
5434
|
+
// Capture THIS violation's own reason so the deny can show a per-CWE
|
|
5435
|
+
// explanation, not just one merged reason for the whole batch.
|
|
5436
|
+
const rM = block.match(/<reason>([\s\S]*?)<\/reason>/);
|
|
5437
|
+
if (rM && !reasons[id]) { const rs = rM[1].trim(); if (rs) reasons[id] = rs; }
|
|
5366
5438
|
}
|
|
5367
5439
|
}
|
|
5368
5440
|
}
|
|
@@ -5387,12 +5459,28 @@ async function main() {
|
|
|
5387
5459
|
const count = activeCweIds.length;
|
|
5388
5460
|
const label = count === 1 ? 'match' : 'matches';
|
|
5389
5461
|
const cweMsg = cweTag + ' ' + fileShort + ' \u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5462
|
+
// ONE sentence, hard-capped (strip control chars, trim at first terminator) so
|
|
5463
|
+
// the deny stays tight even with several CWEs in one edit.
|
|
5464
|
+
const oneSentence = (s: any) => { const t = String(s == null ? '' : s).replace(/[\x00-\x1F\x7F]/g, ' ').replace(/\s{2,}/g, ' ').trim().slice(0, 400); const m = t.match(/^[\s\S]*?[.!?](\s+(?=[A-Z])|$)/); return (m ? m[0] : t).trim().slice(0, 220); };
|
|
5465
|
+
// ONE line per flagged CWE: its own one-sentence explanation + a one-sentence Fix.
|
|
5466
|
+
// Per-CWE reason (reasons[id]) → merged reason → CWE name → generic fallback. The
|
|
5467
|
+
// Fix is dropped when it just restates the explanation so no line double-prints.
|
|
5468
|
+
const cweBlocks = activeCweIds.map(id => {
|
|
5469
|
+
const why = oneSentence(reasons[id] || verdict.reason || cweNameMap.get(id.toUpperCase()) || 'code weakness detected');
|
|
5470
|
+
const fix = fixes[id] ? oneSentence(fixes[id]) : '';
|
|
5471
|
+
const showFix = !!fix && !why.toLowerCase().includes(fix.slice(0, 40).toLowerCase());
|
|
5472
|
+
return '[' + id + '] ' + why + (showFix ? ' Fix: ' + fix : '');
|
|
5473
|
+
});
|
|
5474
|
+
// Bound the total: list as many CWEs as fit, spill the rest to a "+N more" note.
|
|
5475
|
+
let denyDetail = cweBlocks.join('\n');
|
|
5476
|
+
if (denyDetail.length > 4000) {
|
|
5477
|
+
const kept: string[] = [];
|
|
5478
|
+
let used = 0;
|
|
5479
|
+
for (const b of cweBlocks) { if (used + b.length + 1 > 4000) break; kept.push(b); used += b.length + 1; }
|
|
5480
|
+
const more = cweBlocks.length - kept.length;
|
|
5481
|
+
denyDetail = kept.join('\n') + (more > 0 ? '\n(+' + more + ' more CWE weakness' + (more === 1 ? '' : 'es') + ' — fix these first, then re-run to surface the rest)' : '');
|
|
5482
|
+
}
|
|
5483
|
+
const ctx = 'CWE weaknesses to fix (' + count + '):\n' + denyDetail + '\nFix ALL of the above before retrying. Do NOT ask the user to make the edit manually — resolve every weakness in code yourself.';
|
|
5396
5484
|
|
|
5397
5485
|
emitBlockScanFindings(
|
|
5398
5486
|
jwt,
|
|
@@ -11450,7 +11538,7 @@ function writeConfigEnv(opts) {
|
|
|
11450
11538
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11451
11539
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11452
11540
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11453
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.7.
|
|
11541
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.7.2")}`
|
|
11454
11542
|
];
|
|
11455
11543
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11456
11544
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -15513,7 +15601,7 @@ var args = process.argv.slice(2);
|
|
|
15513
15601
|
var cmd = args[0] || "";
|
|
15514
15602
|
var subArgs = args.slice(1);
|
|
15515
15603
|
function printVersion() {
|
|
15516
|
-
console.log("1.7.
|
|
15604
|
+
console.log("1.7.2");
|
|
15517
15605
|
}
|
|
15518
15606
|
function printHelp2() {
|
|
15519
15607
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|