@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 CHANGED
@@ -2173,8 +2173,68 @@ export function readSessionLog(sessionId: string): SessionAction[] {
2173
2173
  } catch { return []; }
2174
2174
  }
2175
2175
 
2176
- export function compressSessionLog(actions: SessionAction[]): string {
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.85")}`
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
- writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode, deployLocation });
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.85");
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