@synkro-sh/cli 1.6.85 → 1.6.87
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 +84 -27
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -2173,8 +2173,68 @@ export function readSessionLog(sessionId: string): SessionAction[] {
|
|
|
2173
2173
|
} catch { return []; }
|
|
2174
2174
|
}
|
|
2175
2175
|
|
|
2176
|
-
|
|
2176
|
+
const RETRIEVAL_RECENT_N = 40;
|
|
2177
|
+
const PROCESS_KW = ['test', 'typecheck', 'tsc', 'lint', 'build', 'migrate', 'migration', 'deploy', 'publish', 'push', 'seed'];
|
|
2178
|
+
|
|
2179
|
+
// Is this session action security- or rule-relevant (i.e. can it participate in a
|
|
2180
|
+
// violation)? Conservative net \u2014 uses substring checks (no regex, to stay safe
|
|
2181
|
+
// inside the materialized-hook template). Plus rule-awareness: an action that
|
|
2182
|
+
// performs a process keyword an active rule actually mentions (e.g. a "pnpm test"
|
|
2183
|
+
// when a rule is about tests) is kept so precondition rules stay resolvable.
|
|
2184
|
+
function actionSalience(a: SessionAction, ruleText: string): string | null {
|
|
2185
|
+
const x = ((a.tool || '') + ' ' + (a.file || '') + ' ' + (a.summary || '')).toLowerCase();
|
|
2186
|
+
const has = (arr: string[]): boolean => { for (const k of arr) if (x.indexOf(k) >= 0) return true; return false; };
|
|
2187
|
+
if (has(['.env', 'credential', 'secret', 'token', '.pem', '.key', 'password', 'api key', 'api_key', 'api-key', 'authorization', 'bearer', 'oauth', '.aws'])) return 'secret';
|
|
2188
|
+
if (has(['curl ', 'wget ', 'ssh ', 'scp ', 'netcat', 'http://', 'https://', 'fetch('])) return 'network';
|
|
2189
|
+
if (has(['rm -rf', 'rm -r ', 'drop table', 'drop database', 'truncate', 'chmod', 'chown', 'sudo', 'kill ', 'wrangler', '--force', 'deploy', 'publish', 'git push'])) return 'destructive';
|
|
2190
|
+
for (const kw of PROCESS_KW) if (ruleText.indexOf(kw) >= 0 && x.indexOf(kw) >= 0) return 'rule';
|
|
2191
|
+
return null;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
// Cloud session log \u2014 retrieval style (validated 4/4 vs the last-200 dump in the
|
|
2195
|
+
// shadow corpus). Keeps EVERY security-salient + rule-relevant action in order
|
|
2196
|
+
// regardless of age (so cross-turn violations the dump's 200-window drops are
|
|
2197
|
+
// still seen), a recent verbatim window, and a COMPLETENESS-asserting summary of
|
|
2198
|
+
// the routine remainder (the assertion is what lets the grader trust the collapsed
|
|
2199
|
+
// part is benign \u2014 safe only because the salience net above is conservative).
|
|
2200
|
+
function compressSessionLogRetrieval(actions: SessionAction[], rules: Rule[]): string {
|
|
2201
|
+
if (actions.length === 0) return '';
|
|
2202
|
+
const ruleText = (rules || []).map(r => String((r as any).text || '').toLowerCase()).join(' ');
|
|
2203
|
+
const total = actions.length;
|
|
2204
|
+
const recentStart = Math.max(0, total - RETRIEVAL_RECENT_N);
|
|
2205
|
+
const salient: Array<{ a: SessionAction; s: string; i: number }> = [];
|
|
2206
|
+
const noise: Record<string, number> = {};
|
|
2207
|
+
for (let i = 0; i < recentStart; i++) {
|
|
2208
|
+
const a = actions[i];
|
|
2209
|
+
const s = actionSalience(a, ruleText);
|
|
2210
|
+
if (s) salient.push({ a, s, i });
|
|
2211
|
+
else noise[a.tool] = (noise[a.tool] || 0) + 1;
|
|
2212
|
+
}
|
|
2213
|
+
const lines: string[] = ['SESSION HISTORY (' + total + ' actions; security-salient + recent, in order):'];
|
|
2214
|
+
if (salient.length) {
|
|
2215
|
+
lines.push(' --- relevant (any point this session) ---');
|
|
2216
|
+
for (const r of salient) {
|
|
2217
|
+
const target = r.a.file || (r.a.summary || '').slice(0, 80);
|
|
2218
|
+
lines.push(' ' + (r.i + 1) + '. [' + r.s + '] ' + r.a.tool + ' ' + target + (r.a.outcome ? ' -> ' + r.a.outcome : ''));
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
const noiseN = Object.values(noise).reduce((n, c) => n + c, 0);
|
|
2222
|
+
if (noiseN) {
|
|
2223
|
+
lines.push(' [' + noiseN + ' earlier routine actions (' + Object.entries(noise).map(([t, c]) => c + ' ' + t).join(', ') + '). The relevant list above is COMPLETE: every credential/secret read, outbound network call, and destructive or rule-gated action this session is listed there; these ' + noiseN + ' are non-salient in-repo reads/edits/builds only.]');
|
|
2224
|
+
}
|
|
2225
|
+
lines.push(' --- recent ---');
|
|
2226
|
+
for (let i = recentStart; i < total; i++) {
|
|
2227
|
+
const a = actions[i];
|
|
2228
|
+
lines.push(' ' + (i + 1) + '. ' + (a.summary || a.tool) + (a.outcome ? ' -> ' + a.outcome : ''));
|
|
2229
|
+
}
|
|
2230
|
+
return lines.join('\\n');
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
export function compressSessionLog(actions: SessionAction[], rules?: Rule[]): string {
|
|
2177
2234
|
if (actions.length === 0) return '';
|
|
2235
|
+
// Cloud grade path passes the active rules \u2192 retrieval-style context. Local
|
|
2236
|
+
// (no rules arg, or local storage mode) keeps the dump below, UNTOUCHED.
|
|
2237
|
+
if (rules !== undefined && !isLocalStorageMode()) return compressSessionLogRetrieval(actions, rules);
|
|
2178
2238
|
const total = actions.length;
|
|
2179
2239
|
const lines: string[] = [];
|
|
2180
2240
|
|
|
@@ -4088,7 +4148,7 @@ async function main() {
|
|
|
4088
4148
|
if (rt === 'local') {
|
|
4089
4149
|
// \u2500\u2500\u2500 Local grading: org rules ONLY (channel 1, port 18929) \u2500\u2500\u2500
|
|
4090
4150
|
const proposedShort = proposed.slice(0, 4000);
|
|
4091
|
-
const sessionLog = compressSessionLog(readSessionLog(sessionId));
|
|
4151
|
+
const sessionLog = compressSessionLog(readSessionLog(sessionId), config.rules);
|
|
4092
4152
|
const graderContent = 'file=' + filePath + ' content=' + proposedShort;
|
|
4093
4153
|
const relevantRules = await filterRules(
|
|
4094
4154
|
ruleFilterText(graderContent, transcript.userIntent || lastPrompt),
|
|
@@ -4104,9 +4164,6 @@ async function main() {
|
|
|
4104
4164
|
'User intent (last human message): ' + (transcript.userIntent || 'none stated'),
|
|
4105
4165
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
4106
4166
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
4107
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
4108
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
4109
|
-
'The rules shown were pre-selected as the ones relevant to this edit \u2014 every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no hardcoded secrets in file. R005: in-repo path only." Cover every rule shown.',
|
|
4110
4167
|
].join('\\n');
|
|
4111
4168
|
const graderPrompt = buildGraderPrompt(proposedShort);
|
|
4112
4169
|
|
|
@@ -4284,7 +4341,7 @@ async function main() {
|
|
|
4284
4341
|
recent_user_messages: transcript.recentUserMessages,
|
|
4285
4342
|
recent_messages: transcript.recentMessages,
|
|
4286
4343
|
recent_actions: transcript.recentActions,
|
|
4287
|
-
session_history: compressSessionLog(readSessionLog(sessionId)),
|
|
4344
|
+
session_history: compressSessionLog(readSessionLog(sessionId), config.rules),
|
|
4288
4345
|
session_id: sessionId || null,
|
|
4289
4346
|
tool_use_id: toolUseId || null,
|
|
4290
4347
|
cwd: cwd || null,
|
|
@@ -5523,7 +5580,7 @@ async function main() {
|
|
|
5523
5580
|
// mode was also checked up there.
|
|
5524
5581
|
|
|
5525
5582
|
if (rt === 'local') {
|
|
5526
|
-
const sessionLog = compressSessionLog(readSessionLog(sessionId));
|
|
5583
|
+
const sessionLog = compressSessionLog(readSessionLog(sessionId), config.rules);
|
|
5527
5584
|
const relevantRules = await filterRules(
|
|
5528
5585
|
ruleFilterText(command, transcript.userIntent || lastPrompt),
|
|
5529
5586
|
config.rules,
|
|
@@ -5537,10 +5594,6 @@ async function main() {
|
|
|
5537
5594
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
5538
5595
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
5539
5596
|
scanConcern,
|
|
5540
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix — your job is only to detect violations.',
|
|
5541
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent — only a response to a shown block counts.',
|
|
5542
|
-
'The rules shown were pre-selected as the ones relevant to this command — every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no secrets in grep args. R005: in-repo path only." Cover every rule shown.',
|
|
5543
|
-
'Rules with preconditions (e.g. "run X before Y") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',
|
|
5544
5597
|
].filter(Boolean).join('\\n');
|
|
5545
5598
|
|
|
5546
5599
|
let gradeResp: string;
|
|
@@ -5609,7 +5662,7 @@ async function main() {
|
|
|
5609
5662
|
recent_user_messages: transcript.recentUserMessages,
|
|
5610
5663
|
recent_messages: transcript.recentMessages,
|
|
5611
5664
|
recent_actions: transcript.recentActions,
|
|
5612
|
-
session_history: compressSessionLog(readSessionLog(sessionId)),
|
|
5665
|
+
session_history: compressSessionLog(readSessionLog(sessionId), config.rules),
|
|
5613
5666
|
session_id: sessionId || null,
|
|
5614
5667
|
tool_use_id: toolUseId || null,
|
|
5615
5668
|
cwd: cwd || null,
|
|
@@ -5742,7 +5795,7 @@ async function main() {
|
|
|
5742
5795
|
}
|
|
5743
5796
|
|
|
5744
5797
|
if (rt === 'local') {
|
|
5745
|
-
const sessionLog = compressSessionLog(readSessionLog(sessionId));
|
|
5798
|
+
const sessionLog = compressSessionLog(readSessionLog(sessionId), config.rules);
|
|
5746
5799
|
const agentText = 'agent=' + subagentType + ' description=' + description + ' prompt=' + prompt.slice(0, 2000);
|
|
5747
5800
|
const relevantRules = await filterRules(agentText, config.rules);
|
|
5748
5801
|
const graderPrompt = [
|
|
@@ -5757,8 +5810,6 @@ async function main() {
|
|
|
5757
5810
|
'User intent (last human message): ' + (transcript.userIntent || 'none stated'),
|
|
5758
5811
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
5759
5812
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
5760
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
5761
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
5762
5813
|
].filter(Boolean).join('\\n');
|
|
5763
5814
|
|
|
5764
5815
|
let gradeResp: string;
|
|
@@ -5817,7 +5868,7 @@ async function main() {
|
|
|
5817
5868
|
recent_user_messages: transcript.recentUserMessages,
|
|
5818
5869
|
recent_messages: transcript.recentMessages,
|
|
5819
5870
|
recent_actions: transcript.recentActions,
|
|
5820
|
-
session_history: compressSessionLog(readSessionLog(sessionId)),
|
|
5871
|
+
session_history: compressSessionLog(readSessionLog(sessionId), config.rules),
|
|
5821
5872
|
session_id: sessionId || null,
|
|
5822
5873
|
tool_use_id: toolUseId || null,
|
|
5823
5874
|
cwd: cwd || null,
|
|
@@ -5962,7 +6013,7 @@ async function main() {
|
|
|
5962
6013
|
}
|
|
5963
6014
|
|
|
5964
6015
|
if (rt === 'local') {
|
|
5965
|
-
const sessionLog = compressSessionLog(readSessionLog(sessionId));
|
|
6016
|
+
const sessionLog = compressSessionLog(readSessionLog(sessionId), config.rules);
|
|
5966
6017
|
const relevantRules = await filterRules(plan.slice(0, 2000), config.rules);
|
|
5967
6018
|
const graderPrompt = [
|
|
5968
6019
|
'Working directory: ' + (cwd || '.'),
|
|
@@ -6568,7 +6619,7 @@ async function main() {
|
|
|
6568
6619
|
}
|
|
6569
6620
|
|
|
6570
6621
|
if (rt === 'local') {
|
|
6571
|
-
const sessionLog = compressSessionLog(readSessionLog(sessionId));
|
|
6622
|
+
const sessionLog = compressSessionLog(readSessionLog(sessionId), config.rules);
|
|
6572
6623
|
const relevantRules = await filterRules(
|
|
6573
6624
|
ruleFilterText(command, transcript.userIntent || lastPrompt),
|
|
6574
6625
|
config.rules,
|
|
@@ -6583,10 +6634,6 @@ async function main() {
|
|
|
6583
6634
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
6584
6635
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
6585
6636
|
scanConcern,
|
|
6586
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
6587
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
6588
|
-
'The rules shown were pre-selected as the ones relevant to this command \u2014 every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no secrets in grep args. R005: in-repo path only." Cover every rule shown.',
|
|
6589
|
-
'Rules with preconditions (e.g. "run X before Y") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',
|
|
6590
6637
|
].filter(Boolean).join('\\n');
|
|
6591
6638
|
|
|
6592
6639
|
let gradeResp: string;
|
|
@@ -10869,7 +10916,7 @@ function writeConfigEnv(opts) {
|
|
|
10869
10916
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
10870
10917
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
10871
10918
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
10872
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
10919
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.87")}`
|
|
10873
10920
|
];
|
|
10874
10921
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
10875
10922
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -11685,7 +11732,12 @@ async function installCommand(opts = {}) {
|
|
|
11685
11732
|
} catch (err) {
|
|
11686
11733
|
console.warn(` \u26A0 Could not cache judge prompts: ${err.message}`);
|
|
11687
11734
|
}
|
|
11688
|
-
|
|
11735
|
+
if (deployLocation !== "cloud") {
|
|
11736
|
+
writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode, deployLocation });
|
|
11737
|
+
} else {
|
|
11738
|
+
console.log(" Cloud mode: standards + grader pool are configured in the dashboard");
|
|
11739
|
+
console.log(" (Settings \u2192 Standards / Pool) \u2014 no synkro.toml needed.");
|
|
11740
|
+
}
|
|
11689
11741
|
console.log();
|
|
11690
11742
|
let cloudGradeOk = null;
|
|
11691
11743
|
if (useLocalMcp) {
|
|
@@ -14463,8 +14515,7 @@ function stableStringify(value) {
|
|
|
14463
14515
|
function canonicalize(pack) {
|
|
14464
14516
|
return stableStringify({
|
|
14465
14517
|
rules: pack.rules ?? [],
|
|
14466
|
-
docs: pack.docs ?? []
|
|
14467
|
-
manifest: pack.manifest ?? {}
|
|
14518
|
+
docs: pack.docs ?? []
|
|
14468
14519
|
});
|
|
14469
14520
|
}
|
|
14470
14521
|
function computeDigest(canonical) {
|
|
@@ -14533,6 +14584,12 @@ function cacheKey(ref, version) {
|
|
|
14533
14584
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
14534
14585
|
}
|
|
14535
14586
|
async function syncCommand(_args = []) {
|
|
14587
|
+
if (process.env.SYNKRO_DEPLOY_LOCATION === "cloud") {
|
|
14588
|
+
console.log("Cloud mode: standards are applied org-wide from the dashboard");
|
|
14589
|
+
console.log("(Settings \u2192 Standards). No synkro.toml or `synkro sync` needed \u2014");
|
|
14590
|
+
console.log("packs enforce automatically wherever Synkro is installed.");
|
|
14591
|
+
return;
|
|
14592
|
+
}
|
|
14536
14593
|
const sf = readFullSynkroFile();
|
|
14537
14594
|
if (!sf) {
|
|
14538
14595
|
console.error("No synkro.toml found in the repo root. Run `synkro install` first.");
|
|
@@ -14902,7 +14959,7 @@ var args = process.argv.slice(2);
|
|
|
14902
14959
|
var cmd = args[0] || "";
|
|
14903
14960
|
var subArgs = args.slice(1);
|
|
14904
14961
|
function printVersion() {
|
|
14905
|
-
console.log("1.6.
|
|
14962
|
+
console.log("1.6.87");
|
|
14906
14963
|
}
|
|
14907
14964
|
function printHelp2() {
|
|
14908
14965
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|