@bilalimamoglu/sift 0.4.3 → 0.4.5

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/index.d.ts CHANGED
@@ -13,6 +13,7 @@ type RawSliceStrategy = "none" | "bucket_evidence" | "traceback_window" | "head_
13
13
  type TestStatusRemainingMode = "none" | "subset_rerun" | "full_rerun_diff";
14
14
  type ResponseMode = "text" | "json";
15
15
  type JsonResponseFormatMode = "auto" | "on" | "off";
16
+ type OperationMode = "agent-escalation" | "provider-assisted" | "local-only";
16
17
  type PromptPolicyName = "test-status" | "audit-critical" | "diff-summary" | "build-failure" | "log-errors" | "infra-risk" | "typecheck-summary" | "lint-failures";
17
18
  interface ProviderConfig {
18
19
  provider: ProviderName;
@@ -34,6 +35,7 @@ interface InputConfig {
34
35
  tailChars: number;
35
36
  }
36
37
  interface RuntimeConfig {
38
+ operationMode: OperationMode;
37
39
  rawFallback: boolean;
38
40
  verbose: boolean;
39
41
  }
@@ -143,10 +145,16 @@ interface ExecRequest extends Omit<RunRequest, "stdin"> {
143
145
  watch?: boolean;
144
146
  }
145
147
  declare function buildCommandPreview(request: ExecRequest): string;
148
+ type PackageManagerScriptKind = "npm" | "pnpm" | "yarn" | "bun";
149
+ declare function detectPackageManagerScriptKind(commandPreview: string): PackageManagerScriptKind | null;
150
+ declare function normalizeScriptWrapperOutput(args: {
151
+ commandPreview: string;
152
+ capturedOutput: string;
153
+ }): string;
146
154
  declare function getExecSuccessShortcut(args: {
147
155
  presetName?: string;
148
156
  exitCode: number;
149
- capturedOutput: string;
157
+ normalizedOutput: string;
150
158
  }): string | null;
151
159
  declare function runExec(request: ExecRequest): Promise<number>;
152
160
 
@@ -178,5 +186,6 @@ interface ResolveOptions {
178
186
  cliOverrides?: PartialSiftConfig;
179
187
  }
180
188
  declare function resolveConfig(options?: ResolveOptions): SiftConfig;
189
+ declare function resolveEffectiveOperationMode(config: SiftConfig): OperationMode;
181
190
 
182
- export { BoundedCapture, type DetailLevel, type ExecRequest, type GenerateInput, type GenerateResult, type Goal, type InputConfig, type JsonResponseFormatMode, type LLMProvider, type NativeProviderName, type OutputFormat, type PartialSiftConfig, type PreparedInput, type PresetDefinition, type PromptPolicyName, type ProviderConfig, type ProviderName, type ProviderProfile, type ProviderProfiles, type RawSliceStrategy, type ResolveOptions, type ResponseMode, type RunRequest, type RuntimeConfig, type SiftConfig, type TestStatusRemainingMode, type UsageInfo, buildCommandPreview, getExecSuccessShortcut, looksInteractivePrompt, mergeDefined, normalizeChildExitCode, resolveConfig, runExec, runSift, runSiftWithStats, startPendingNotice };
191
+ export { BoundedCapture, type DetailLevel, type ExecRequest, type GenerateInput, type GenerateResult, type Goal, type InputConfig, type JsonResponseFormatMode, type LLMProvider, type NativeProviderName, type OperationMode, type OutputFormat, type PackageManagerScriptKind, type PartialSiftConfig, type PreparedInput, type PresetDefinition, type PromptPolicyName, type ProviderConfig, type ProviderName, type ProviderProfile, type ProviderProfiles, type RawSliceStrategy, type ResolveOptions, type ResponseMode, type RunRequest, type RuntimeConfig, type SiftConfig, type TestStatusRemainingMode, type UsageInfo, buildCommandPreview, detectPackageManagerScriptKind, getExecSuccessShortcut, looksInteractivePrompt, mergeDefined, normalizeChildExitCode, normalizeScriptWrapperOutput, resolveConfig, resolveEffectiveOperationMode, runExec, runSift, runSiftWithStats, startPendingNotice };
package/dist/index.js CHANGED
@@ -4,8 +4,10 @@ import { constants as osConstants } from "os";
4
4
  import pc3 from "picocolors";
5
5
 
6
6
  // src/constants.ts
7
+ import fs from "fs";
7
8
  import os from "os";
8
9
  import path from "path";
10
+ import crypto from "crypto";
9
11
  function getDefaultGlobalConfigPath(homeDir = os.homedir()) {
10
12
  return path.join(homeDir, ".config", "sift", "config.yaml");
11
13
  }
@@ -15,6 +17,26 @@ function getDefaultGlobalStateDir(homeDir = os.homedir()) {
15
17
  function getDefaultTestStatusStatePath(homeDir = os.homedir()) {
16
18
  return path.join(getDefaultGlobalStateDir(homeDir), "last-test-status.json");
17
19
  }
20
+ function getDefaultScopedTestStatusStateDir(homeDir = os.homedir()) {
21
+ return path.join(getDefaultGlobalStateDir(homeDir), "test-status", "by-cwd");
22
+ }
23
+ function getScopedTestStatusStatePath(cwd, homeDir = os.homedir()) {
24
+ const normalizedCwd = normalizeScopedCacheCwd(cwd);
25
+ const baseName = slugCachePathSegment(path.basename(normalizedCwd)) || "root";
26
+ const shortHash = crypto.createHash("sha256").update(normalizedCwd).digest("hex").slice(0, 10);
27
+ return path.join(getDefaultScopedTestStatusStateDir(homeDir), `${baseName}-${shortHash}.json`);
28
+ }
29
+ function slugCachePathSegment(value) {
30
+ return value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
31
+ }
32
+ function normalizeScopedCacheCwd(cwd) {
33
+ const absoluteCwd = path.resolve(cwd);
34
+ try {
35
+ return fs.realpathSync.native(absoluteCwd);
36
+ } catch {
37
+ return absoluteCwd;
38
+ }
39
+ }
18
40
  function getDefaultConfigSearchPaths() {
19
41
  return [
20
42
  path.resolve(process.cwd(), "sift.config.yaml"),
@@ -4741,6 +4763,7 @@ var OpenAIProvider = class {
4741
4763
  signal: controller.signal,
4742
4764
  headers: {
4743
4765
  "content-type": "application/json",
4766
+ connection: "close",
4744
4767
  ...this.apiKey ? { authorization: `Bearer ${this.apiKey}` } : {}
4745
4768
  },
4746
4769
  body: JSON.stringify({
@@ -4841,6 +4864,7 @@ var OpenAICompatibleProvider = class {
4841
4864
  signal: controller.signal,
4842
4865
  headers: {
4843
4866
  "content-type": "application/json",
4867
+ connection: "close",
4844
4868
  ...this.apiKey ? { authorization: `Bearer ${this.apiKey}` } : {}
4845
4869
  },
4846
4870
  body: JSON.stringify({
@@ -5137,7 +5161,7 @@ function buildPrompt(args) {
5137
5161
  "If per-test or per-module mapping is unclear, fall back to the focused grouped-cause view instead of guessing."
5138
5162
  ] : [];
5139
5163
  const prompt = [
5140
- "You are Sift, a CLI output reduction assistant for downstream agents and automation.",
5164
+ "You are Sift, a CLI output-guidance and reduction assistant for downstream agents and automation.",
5141
5165
  "Hard rules:",
5142
5166
  ...policy.sharedRules.map((rule) => `- ${rule}`),
5143
5167
  "",
@@ -6413,7 +6437,7 @@ function emitStatsFooter(args) {
6413
6437
  }
6414
6438
 
6415
6439
  // src/core/testStatusState.ts
6416
- import fs from "fs";
6440
+ import fs2 from "fs";
6417
6441
  import path2 from "path";
6418
6442
  import { z as z2 } from "zod";
6419
6443
  var detailSchema = z2.enum(["standard", "focused", "verbose"]);
@@ -6863,7 +6887,7 @@ function migrateCachedTestStatusRun(state) {
6863
6887
  function readCachedTestStatusRun(statePath = getDefaultTestStatusStatePath()) {
6864
6888
  let raw = "";
6865
6889
  try {
6866
- raw = fs.readFileSync(statePath, "utf8");
6890
+ raw = fs2.readFileSync(statePath, "utf8");
6867
6891
  } catch (error) {
6868
6892
  if (error.code === "ENOENT") {
6869
6893
  throw new MissingCachedTestStatusRunError();
@@ -6884,10 +6908,10 @@ function tryReadCachedTestStatusRun(statePath = getDefaultTestStatusStatePath())
6884
6908
  }
6885
6909
  }
6886
6910
  function writeCachedTestStatusRun(state, statePath = getDefaultTestStatusStatePath()) {
6887
- fs.mkdirSync(path2.dirname(statePath), {
6911
+ fs2.mkdirSync(path2.dirname(statePath), {
6888
6912
  recursive: true
6889
6913
  });
6890
- fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}
6914
+ fs2.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}
6891
6915
  `, "utf8");
6892
6916
  }
6893
6917
  function buildTargetDelta(args) {
@@ -7255,13 +7279,97 @@ function buildCommandPreview(request) {
7255
7279
  }
7256
7280
  return (request.command ?? []).join(" ");
7257
7281
  }
7282
+ function detectPackageManagerScriptKind(commandPreview) {
7283
+ const trimmed = commandPreview.trim();
7284
+ if (/^npm(?:\s+--?[^\s]+(?:[=\s][^\s]+)?)?\s+run\s+\S+/i.test(trimmed)) {
7285
+ return "npm";
7286
+ }
7287
+ if (/^pnpm(?:\s+--?[^\s]+(?:[=\s][^\s]+)?)?\s+run\s+\S+/i.test(trimmed)) {
7288
+ return "pnpm";
7289
+ }
7290
+ if (/^yarn(?:\s+--?[^\s]+(?:[=\s][^\s]+)?)?\s+run\s+\S+/i.test(trimmed)) {
7291
+ return "yarn";
7292
+ }
7293
+ if (/^bun(?:\s+--?[^\s]+(?:[=\s][^\s]+)?)?\s+run\s+\S+/i.test(trimmed)) {
7294
+ return "bun";
7295
+ }
7296
+ return null;
7297
+ }
7298
+ function normalizeScriptWrapperOutput(args) {
7299
+ const kind = detectPackageManagerScriptKind(args.commandPreview);
7300
+ if (!kind) {
7301
+ return args.capturedOutput;
7302
+ }
7303
+ const lines = args.capturedOutput.split(/\r?\n/);
7304
+ const trimBlankEdges = () => {
7305
+ while (lines.length > 0 && lines[0].trim() === "") {
7306
+ lines.shift();
7307
+ }
7308
+ while (lines.length > 0 && lines.at(-1).trim() === "") {
7309
+ lines.pop();
7310
+ }
7311
+ };
7312
+ const stripLeadingWrapperNoise = () => {
7313
+ let removed = false;
7314
+ while (lines.length > 0) {
7315
+ const line = lines[0];
7316
+ const trimmed = line.trim();
7317
+ if (trimmed === "") {
7318
+ lines.shift();
7319
+ removed = true;
7320
+ continue;
7321
+ }
7322
+ if (/^(?:npm|pnpm)\s+warn\s+unknown user config\b/i.test(trimmed) || /^(?:npm|pnpm)\s+warn\s+unknown env config\b/i.test(trimmed) || /^npm\s+warn\s+config\b/i.test(trimmed) || /^yarn\s+warning\b/i.test(trimmed) || /^bun\s+warn\b/i.test(trimmed)) {
7323
+ lines.shift();
7324
+ removed = true;
7325
+ continue;
7326
+ }
7327
+ break;
7328
+ }
7329
+ if (removed) {
7330
+ trimBlankEdges();
7331
+ }
7332
+ };
7333
+ trimBlankEdges();
7334
+ stripLeadingWrapperNoise();
7335
+ if (kind === "npm" || kind === "pnpm") {
7336
+ let removed = 0;
7337
+ while (lines.length > 0 && removed < 2 && /^\s*>\s+/.test(lines[0])) {
7338
+ lines.shift();
7339
+ removed += 1;
7340
+ }
7341
+ trimBlankEdges();
7342
+ }
7343
+ if (kind === "yarn") {
7344
+ if (lines[0] && /^\s*yarn run v/i.test(lines[0])) {
7345
+ lines.shift();
7346
+ }
7347
+ if (lines[0] && /^\s*\$\s+/.test(lines[0])) {
7348
+ lines.shift();
7349
+ }
7350
+ trimBlankEdges();
7351
+ if (lines.at(-1) && /^\s*Done in\b/i.test(lines.at(-1))) {
7352
+ lines.pop();
7353
+ }
7354
+ }
7355
+ if (kind === "bun") {
7356
+ if (lines[0] && /^\s*\$\s+/.test(lines[0])) {
7357
+ lines.shift();
7358
+ }
7359
+ }
7360
+ trimBlankEdges();
7361
+ return lines.join("\n");
7362
+ }
7258
7363
  function getExecSuccessShortcut(args) {
7259
7364
  if (args.exitCode !== 0) {
7260
7365
  return null;
7261
7366
  }
7262
- if (args.presetName === "typecheck-summary" && args.capturedOutput.trim() === "") {
7367
+ if (args.presetName === "typecheck-summary" && args.normalizedOutput.trim() === "") {
7263
7368
  return "No type errors.";
7264
7369
  }
7370
+ if (args.presetName === "lint-failures" && args.normalizedOutput.trim() === "") {
7371
+ return "No lint failures.";
7372
+ }
7265
7373
  return null;
7266
7374
  }
7267
7375
  async function runExec(request) {
@@ -7273,10 +7381,11 @@ async function runExec(request) {
7273
7381
  const shellPath = process.env.SHELL || "/bin/bash";
7274
7382
  const commandPreview = buildCommandPreview(request);
7275
7383
  const commandCwd = request.cwd ?? process.cwd();
7384
+ const scopedStatePath = getScopedTestStatusStatePath(commandCwd);
7276
7385
  const isTestStatusPreset = request.presetName === "test-status";
7277
7386
  const readCachedBaseline = isTestStatusPreset && (request.readCachedBaseline ?? true);
7278
7387
  const writeCachedBaselineRequested = isTestStatusPreset && (request.writeCachedBaseline ?? (request.skipCacheWrite ? false : true));
7279
- const previousCachedRun = readCachedBaseline ? tryReadCachedTestStatusRun() : null;
7388
+ const previousCachedRun = readCachedBaseline ? tryReadCachedTestStatusRun(scopedStatePath) : null;
7280
7389
  if (request.config.runtime.verbose) {
7281
7390
  process.stderr.write(
7282
7391
  `${pc3.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
@@ -7340,6 +7449,10 @@ async function runExec(request) {
7340
7449
  }
7341
7450
  const exitCode = normalizeChildExitCode(childStatus, childSignal);
7342
7451
  const capturedOutput = capture.render();
7452
+ const normalizedOutput = normalizeScriptWrapperOutput({
7453
+ commandPreview,
7454
+ capturedOutput
7455
+ });
7343
7456
  const autoWatchDetected = !request.watch && looksLikeWatchStream(capturedOutput);
7344
7457
  const useWatchFlow = Boolean(request.watch) || autoWatchDetected;
7345
7458
  const shouldBuildTestStatusState = isTestStatusPreset && !useWatchFlow;
@@ -7365,7 +7478,7 @@ async function runExec(request) {
7365
7478
  const execSuccessShortcut = useWatchFlow ? null : getExecSuccessShortcut({
7366
7479
  presetName: request.presetName,
7367
7480
  exitCode,
7368
- capturedOutput
7481
+ normalizedOutput
7369
7482
  });
7370
7483
  if (execSuccessShortcut && !request.dryRun) {
7371
7484
  if (request.config.runtime.verbose) {
@@ -7391,15 +7504,15 @@ async function runExec(request) {
7391
7504
  if (useWatchFlow) {
7392
7505
  let output2 = await runWatch({
7393
7506
  ...request,
7394
- stdin: capturedOutput
7507
+ stdin: normalizedOutput
7395
7508
  });
7396
7509
  if (isInsufficientSignalOutput(output2)) {
7397
7510
  output2 = buildInsufficientSignalOutput({
7398
7511
  presetName: request.presetName,
7399
- originalLength: capture.getTotalChars(),
7512
+ originalLength: normalizedOutput.length > 0 ? normalizedOutput.length : capture.getTotalChars(),
7400
7513
  truncatedApplied: capture.wasTruncated(),
7401
7514
  exitCode,
7402
- recognizedRunner: detectTestRunner(capturedOutput)
7515
+ recognizedRunner: detectTestRunner(normalizedOutput)
7403
7516
  });
7404
7517
  }
7405
7518
  process.stdout.write(`${output2}
@@ -7438,7 +7551,7 @@ async function runExec(request) {
7438
7551
  }) : null;
7439
7552
  const result = await runSiftWithStats({
7440
7553
  ...request,
7441
- stdin: capturedOutput,
7554
+ stdin: normalizedOutput,
7442
7555
  analysisContext: request.testStatusContext?.remainingMode && request.testStatusContext.remainingMode !== "none" && request.presetName === "test-status" ? [
7443
7556
  request.analysisContext,
7444
7557
  "Zoom context:",
@@ -7461,10 +7574,10 @@ async function runExec(request) {
7461
7574
  if (isInsufficientSignalOutput(output)) {
7462
7575
  output = buildInsufficientSignalOutput({
7463
7576
  presetName: request.presetName,
7464
- originalLength: capture.getTotalChars(),
7577
+ originalLength: normalizedOutput.length > 0 ? normalizedOutput.length : capture.getTotalChars(),
7465
7578
  truncatedApplied: capture.wasTruncated(),
7466
7579
  exitCode,
7467
- recognizedRunner: detectTestRunner(capturedOutput)
7580
+ recognizedRunner: detectTestRunner(normalizedOutput)
7468
7581
  });
7469
7582
  }
7470
7583
  if (request.diff && !request.dryRun && previousCachedRun && currentCachedRun) {
@@ -7479,7 +7592,7 @@ ${output}`;
7479
7592
  }
7480
7593
  if (currentCachedRun && shouldWriteCachedBaseline) {
7481
7594
  try {
7482
- writeCachedTestStatusRun(currentCachedRun);
7595
+ writeCachedTestStatusRun(currentCachedRun, scopedStatePath);
7483
7596
  } catch (error) {
7484
7597
  if (request.config.runtime.verbose) {
7485
7598
  const reason = error instanceof Error ? error.message : "unknown_error";
@@ -7491,10 +7604,10 @@ ${output}`;
7491
7604
  } else if (isInsufficientSignalOutput(output)) {
7492
7605
  output = buildInsufficientSignalOutput({
7493
7606
  presetName: request.presetName,
7494
- originalLength: capture.getTotalChars(),
7607
+ originalLength: normalizedOutput.length > 0 ? normalizedOutput.length : capture.getTotalChars(),
7495
7608
  truncatedApplied: capture.wasTruncated(),
7496
7609
  exitCode,
7497
- recognizedRunner: detectTestRunner(capturedOutput)
7610
+ recognizedRunner: detectTestRunner(normalizedOutput)
7498
7611
  });
7499
7612
  }
7500
7613
  process.stdout.write(`${output}
@@ -7535,6 +7648,7 @@ var defaultConfig = {
7535
7648
  tailChars: 2e4
7536
7649
  },
7537
7650
  runtime: {
7651
+ operationMode: "agent-escalation",
7538
7652
  rawFallback: true,
7539
7653
  verbose: false
7540
7654
  },
@@ -7585,19 +7699,19 @@ var defaultConfig = {
7585
7699
  };
7586
7700
 
7587
7701
  // src/config/load.ts
7588
- import fs2 from "fs";
7702
+ import fs3 from "fs";
7589
7703
  import path3 from "path";
7590
7704
  import YAML from "yaml";
7591
7705
  function findConfigPath(explicitPath) {
7592
7706
  if (explicitPath) {
7593
7707
  const resolved = path3.resolve(explicitPath);
7594
- if (!fs2.existsSync(resolved)) {
7708
+ if (!fs3.existsSync(resolved)) {
7595
7709
  throw new Error(`Config file not found: ${resolved}`);
7596
7710
  }
7597
7711
  return resolved;
7598
7712
  }
7599
7713
  for (const candidate of getDefaultConfigSearchPaths()) {
7600
- if (fs2.existsSync(candidate)) {
7714
+ if (fs3.existsSync(candidate)) {
7601
7715
  return candidate;
7602
7716
  }
7603
7717
  }
@@ -7608,10 +7722,54 @@ function loadRawConfig(explicitPath) {
7608
7722
  if (!configPath) {
7609
7723
  return {};
7610
7724
  }
7611
- const content = fs2.readFileSync(configPath, "utf8");
7725
+ const content = fs3.readFileSync(configPath, "utf8");
7612
7726
  return YAML.parse(content) ?? {};
7613
7727
  }
7614
7728
 
7729
+ // src/config/provider-models.ts
7730
+ var OPENAI_MODELS = [
7731
+ {
7732
+ model: "gpt-5-nano",
7733
+ label: "gpt-5-nano",
7734
+ note: "default, cheapest, fast enough for most fallback passes",
7735
+ isDefault: true
7736
+ },
7737
+ {
7738
+ model: "gpt-5.4-nano",
7739
+ label: "gpt-5.4-nano",
7740
+ note: "newer nano backup, a touch smarter, a touch pricier"
7741
+ },
7742
+ {
7743
+ model: "gpt-5-mini",
7744
+ label: "gpt-5-mini",
7745
+ note: "smarter fallback, still saner than the expensive stuff"
7746
+ }
7747
+ ];
7748
+ var OPENROUTER_MODELS = [
7749
+ {
7750
+ model: "openrouter/free",
7751
+ label: "openrouter/free",
7752
+ note: "default, free, a little slower sometimes, still hard to argue with free",
7753
+ isDefault: true
7754
+ },
7755
+ {
7756
+ model: "qwen/qwen3-coder:free",
7757
+ label: "qwen/qwen3-coder:free",
7758
+ note: "free, code-focused, good when you want a named coding fallback"
7759
+ },
7760
+ {
7761
+ model: "deepseek/deepseek-r1:free",
7762
+ label: "deepseek/deepseek-r1:free",
7763
+ note: "free, stronger reasoning, usually slower"
7764
+ }
7765
+ ];
7766
+ function getProviderModelOptions(provider) {
7767
+ return provider === "openrouter" ? OPENROUTER_MODELS : OPENAI_MODELS;
7768
+ }
7769
+ function getDefaultProviderModel(provider) {
7770
+ return getProviderModelOptions(provider).find((option) => option.isDefault)?.model ?? getProviderModelOptions(provider)[0]?.model ?? "";
7771
+ }
7772
+
7615
7773
  // src/config/provider-api-key.ts
7616
7774
  var OPENAI_COMPATIBLE_BASE_URL_ENV = [
7617
7775
  { prefix: "https://api.openai.com/", envName: "OPENAI_API_KEY" },
@@ -7666,6 +7824,11 @@ function resolveProviderApiKey(provider, baseUrl, env) {
7666
7824
 
7667
7825
  // src/config/schema.ts
7668
7826
  import { z as z3 } from "zod";
7827
+ var operationModeSchema = z3.enum([
7828
+ "agent-escalation",
7829
+ "provider-assisted",
7830
+ "local-only"
7831
+ ]);
7669
7832
  var providerNameSchema = z3.enum([
7670
7833
  "openai",
7671
7834
  "openai-compatible",
@@ -7718,6 +7881,7 @@ var inputConfigSchema = z3.object({
7718
7881
  tailChars: z3.number().int().positive()
7719
7882
  });
7720
7883
  var runtimeConfigSchema = z3.object({
7884
+ operationMode: operationModeSchema,
7721
7885
  rawFallback: z3.boolean(),
7722
7886
  verbose: z3.boolean()
7723
7887
  });
@@ -7740,7 +7904,7 @@ var siftConfigSchema = z3.object({
7740
7904
  var PROVIDER_DEFAULT_OVERRIDES = {
7741
7905
  openrouter: {
7742
7906
  provider: {
7743
- model: "openrouter/free",
7907
+ model: getDefaultProviderModel("openrouter"),
7744
7908
  baseUrl: "https://openrouter.ai/api/v1"
7745
7909
  }
7746
7910
  }
@@ -7780,13 +7944,16 @@ function stripApiKey(overrides) {
7780
7944
  }
7781
7945
  function buildNonCredentialEnvOverrides(env) {
7782
7946
  const overrides = {};
7783
- if (env.SIFT_PROVIDER || env.SIFT_MODEL || env.SIFT_BASE_URL || env.SIFT_TIMEOUT_MS) {
7947
+ if (env.SIFT_PROVIDER || env.SIFT_MODEL || env.SIFT_BASE_URL || env.SIFT_TIMEOUT_MS || env.SIFT_OPERATION_MODE) {
7784
7948
  overrides.provider = {
7785
7949
  provider: env.SIFT_PROVIDER,
7786
7950
  model: env.SIFT_MODEL,
7787
7951
  baseUrl: env.SIFT_BASE_URL,
7788
7952
  timeoutMs: env.SIFT_TIMEOUT_MS ? Number(env.SIFT_TIMEOUT_MS) : void 0
7789
7953
  };
7954
+ overrides.runtime = {
7955
+ operationMode: env.SIFT_OPERATION_MODE
7956
+ };
7790
7957
  }
7791
7958
  if (env.SIFT_MAX_INPUT_CHARS || env.SIFT_MAX_CAPTURE_CHARS) {
7792
7959
  overrides.input = {
@@ -7853,14 +8020,26 @@ function resolveConfig(options = {}) {
7853
8020
  );
7854
8021
  return siftConfigSchema.parse(merged);
7855
8022
  }
8023
+ function hasUsableProvider(config) {
8024
+ return config.provider.apiKey !== void 0 && config.provider.apiKey.trim().length > 0;
8025
+ }
8026
+ function resolveEffectiveOperationMode(config) {
8027
+ if (config.runtime.operationMode === "provider-assisted") {
8028
+ return hasUsableProvider(config) ? "provider-assisted" : "agent-escalation";
8029
+ }
8030
+ return config.runtime.operationMode;
8031
+ }
7856
8032
  export {
7857
8033
  BoundedCapture,
7858
8034
  buildCommandPreview,
8035
+ detectPackageManagerScriptKind,
7859
8036
  getExecSuccessShortcut,
7860
8037
  looksInteractivePrompt,
7861
8038
  mergeDefined,
7862
8039
  normalizeChildExitCode,
8040
+ normalizeScriptWrapperOutput,
7863
8041
  resolveConfig,
8042
+ resolveEffectiveOperationMode,
7864
8043
  runExec,
7865
8044
  runSift,
7866
8045
  runSiftWithStats,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bilalimamoglu/sift",
3
- "version": "0.4.3",
4
- "description": "Agent-first command-output reduction layer for agents, CI, and automation.",
3
+ "version": "0.4.5",
4
+ "description": "Local-first output guidance for coding agents working through noisy command output.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "sift": "dist/cli.js"
@@ -17,6 +17,7 @@
17
17
  "engines": {
18
18
  "node": ">=20"
19
19
  },
20
+ "packageManager": "npm@11.8.0",
20
21
  "scripts": {
21
22
  "build": "tsup",
22
23
  "bench:history": "node --import tsx scripts/bench/generate-version-history.ts --since v0.3.0",
@@ -25,10 +26,17 @@
25
26
  "bench:report": "node --import tsx scripts/bench/generate-progress-report.ts",
26
27
  "dev": "tsx src/cli.ts",
27
28
  "typecheck": "tsc --noEmit",
28
- "test": "vitest run",
29
- "test:coverage": "vitest run --coverage",
29
+ "test": "vitest run --config vitest.config.ts",
30
+ "test:smoke": "vitest run --config vitest.config.ts test/*.smoke.test.ts",
31
+ "test:e2e": "vitest run --config vitest.e2e.config.ts test/*.e2e.test.ts",
32
+ "test:coverage": "vitest run --config vitest.config.ts --coverage --exclude=\"test/**/*.smoke.test.ts\" --exclude=\"test/**/*.e2e.test.ts\"",
30
33
  "test:watch": "vitest",
31
- "prepublishOnly": "npm run typecheck && npm run test:coverage && npm run build"
34
+ "setup:hooks": "git config core.hooksPath .githooks",
35
+ "verify:release": "node scripts/release-gate.mjs --tier full",
36
+ "verify:release:clean": "node scripts/release-gate.mjs --tier clean",
37
+ "verify:release:core": "node scripts/release-gate.mjs --tier core",
38
+ "verify:release:e2e": "node scripts/release-gate.mjs --tier e2e",
39
+ "prepublishOnly": "node scripts/release-gate.mjs --tier full"
32
40
  },
33
41
  "keywords": [
34
42
  "cli",
@@ -37,6 +45,10 @@
37
45
  "automation",
38
46
  "terminal",
39
47
  "logs",
48
+ "debugging",
49
+ "heuristics",
50
+ "root-cause",
51
+ "test-failures",
40
52
  "ci",
41
53
  "json"
42
54
  ],