@bilalimamoglu/sift 0.4.4 → 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
@@ -5161,7 +5161,7 @@ function buildPrompt(args) {
5161
5161
  "If per-test or per-module mapping is unclear, fall back to the focused grouped-cause view instead of guessing."
5162
5162
  ] : [];
5163
5163
  const prompt = [
5164
- "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.",
5165
5165
  "Hard rules:",
5166
5166
  ...policy.sharedRules.map((rule) => `- ${rule}`),
5167
5167
  "",
@@ -7279,13 +7279,97 @@ function buildCommandPreview(request) {
7279
7279
  }
7280
7280
  return (request.command ?? []).join(" ");
7281
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
+ }
7282
7363
  function getExecSuccessShortcut(args) {
7283
7364
  if (args.exitCode !== 0) {
7284
7365
  return null;
7285
7366
  }
7286
- if (args.presetName === "typecheck-summary" && args.capturedOutput.trim() === "") {
7367
+ if (args.presetName === "typecheck-summary" && args.normalizedOutput.trim() === "") {
7287
7368
  return "No type errors.";
7288
7369
  }
7370
+ if (args.presetName === "lint-failures" && args.normalizedOutput.trim() === "") {
7371
+ return "No lint failures.";
7372
+ }
7289
7373
  return null;
7290
7374
  }
7291
7375
  async function runExec(request) {
@@ -7365,6 +7449,10 @@ async function runExec(request) {
7365
7449
  }
7366
7450
  const exitCode = normalizeChildExitCode(childStatus, childSignal);
7367
7451
  const capturedOutput = capture.render();
7452
+ const normalizedOutput = normalizeScriptWrapperOutput({
7453
+ commandPreview,
7454
+ capturedOutput
7455
+ });
7368
7456
  const autoWatchDetected = !request.watch && looksLikeWatchStream(capturedOutput);
7369
7457
  const useWatchFlow = Boolean(request.watch) || autoWatchDetected;
7370
7458
  const shouldBuildTestStatusState = isTestStatusPreset && !useWatchFlow;
@@ -7390,7 +7478,7 @@ async function runExec(request) {
7390
7478
  const execSuccessShortcut = useWatchFlow ? null : getExecSuccessShortcut({
7391
7479
  presetName: request.presetName,
7392
7480
  exitCode,
7393
- capturedOutput
7481
+ normalizedOutput
7394
7482
  });
7395
7483
  if (execSuccessShortcut && !request.dryRun) {
7396
7484
  if (request.config.runtime.verbose) {
@@ -7416,15 +7504,15 @@ async function runExec(request) {
7416
7504
  if (useWatchFlow) {
7417
7505
  let output2 = await runWatch({
7418
7506
  ...request,
7419
- stdin: capturedOutput
7507
+ stdin: normalizedOutput
7420
7508
  });
7421
7509
  if (isInsufficientSignalOutput(output2)) {
7422
7510
  output2 = buildInsufficientSignalOutput({
7423
7511
  presetName: request.presetName,
7424
- originalLength: capture.getTotalChars(),
7512
+ originalLength: normalizedOutput.length > 0 ? normalizedOutput.length : capture.getTotalChars(),
7425
7513
  truncatedApplied: capture.wasTruncated(),
7426
7514
  exitCode,
7427
- recognizedRunner: detectTestRunner(capturedOutput)
7515
+ recognizedRunner: detectTestRunner(normalizedOutput)
7428
7516
  });
7429
7517
  }
7430
7518
  process.stdout.write(`${output2}
@@ -7463,7 +7551,7 @@ async function runExec(request) {
7463
7551
  }) : null;
7464
7552
  const result = await runSiftWithStats({
7465
7553
  ...request,
7466
- stdin: capturedOutput,
7554
+ stdin: normalizedOutput,
7467
7555
  analysisContext: request.testStatusContext?.remainingMode && request.testStatusContext.remainingMode !== "none" && request.presetName === "test-status" ? [
7468
7556
  request.analysisContext,
7469
7557
  "Zoom context:",
@@ -7486,10 +7574,10 @@ async function runExec(request) {
7486
7574
  if (isInsufficientSignalOutput(output)) {
7487
7575
  output = buildInsufficientSignalOutput({
7488
7576
  presetName: request.presetName,
7489
- originalLength: capture.getTotalChars(),
7577
+ originalLength: normalizedOutput.length > 0 ? normalizedOutput.length : capture.getTotalChars(),
7490
7578
  truncatedApplied: capture.wasTruncated(),
7491
7579
  exitCode,
7492
- recognizedRunner: detectTestRunner(capturedOutput)
7580
+ recognizedRunner: detectTestRunner(normalizedOutput)
7493
7581
  });
7494
7582
  }
7495
7583
  if (request.diff && !request.dryRun && previousCachedRun && currentCachedRun) {
@@ -7516,10 +7604,10 @@ ${output}`;
7516
7604
  } else if (isInsufficientSignalOutput(output)) {
7517
7605
  output = buildInsufficientSignalOutput({
7518
7606
  presetName: request.presetName,
7519
- originalLength: capture.getTotalChars(),
7607
+ originalLength: normalizedOutput.length > 0 ? normalizedOutput.length : capture.getTotalChars(),
7520
7608
  truncatedApplied: capture.wasTruncated(),
7521
7609
  exitCode,
7522
- recognizedRunner: detectTestRunner(capturedOutput)
7610
+ recognizedRunner: detectTestRunner(normalizedOutput)
7523
7611
  });
7524
7612
  }
7525
7613
  process.stdout.write(`${output}
@@ -7560,6 +7648,7 @@ var defaultConfig = {
7560
7648
  tailChars: 2e4
7561
7649
  },
7562
7650
  runtime: {
7651
+ operationMode: "agent-escalation",
7563
7652
  rawFallback: true,
7564
7653
  verbose: false
7565
7654
  },
@@ -7637,6 +7726,50 @@ function loadRawConfig(explicitPath) {
7637
7726
  return YAML.parse(content) ?? {};
7638
7727
  }
7639
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
+
7640
7773
  // src/config/provider-api-key.ts
7641
7774
  var OPENAI_COMPATIBLE_BASE_URL_ENV = [
7642
7775
  { prefix: "https://api.openai.com/", envName: "OPENAI_API_KEY" },
@@ -7691,6 +7824,11 @@ function resolveProviderApiKey(provider, baseUrl, env) {
7691
7824
 
7692
7825
  // src/config/schema.ts
7693
7826
  import { z as z3 } from "zod";
7827
+ var operationModeSchema = z3.enum([
7828
+ "agent-escalation",
7829
+ "provider-assisted",
7830
+ "local-only"
7831
+ ]);
7694
7832
  var providerNameSchema = z3.enum([
7695
7833
  "openai",
7696
7834
  "openai-compatible",
@@ -7743,6 +7881,7 @@ var inputConfigSchema = z3.object({
7743
7881
  tailChars: z3.number().int().positive()
7744
7882
  });
7745
7883
  var runtimeConfigSchema = z3.object({
7884
+ operationMode: operationModeSchema,
7746
7885
  rawFallback: z3.boolean(),
7747
7886
  verbose: z3.boolean()
7748
7887
  });
@@ -7765,7 +7904,7 @@ var siftConfigSchema = z3.object({
7765
7904
  var PROVIDER_DEFAULT_OVERRIDES = {
7766
7905
  openrouter: {
7767
7906
  provider: {
7768
- model: "openrouter/free",
7907
+ model: getDefaultProviderModel("openrouter"),
7769
7908
  baseUrl: "https://openrouter.ai/api/v1"
7770
7909
  }
7771
7910
  }
@@ -7805,13 +7944,16 @@ function stripApiKey(overrides) {
7805
7944
  }
7806
7945
  function buildNonCredentialEnvOverrides(env) {
7807
7946
  const overrides = {};
7808
- 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) {
7809
7948
  overrides.provider = {
7810
7949
  provider: env.SIFT_PROVIDER,
7811
7950
  model: env.SIFT_MODEL,
7812
7951
  baseUrl: env.SIFT_BASE_URL,
7813
7952
  timeoutMs: env.SIFT_TIMEOUT_MS ? Number(env.SIFT_TIMEOUT_MS) : void 0
7814
7953
  };
7954
+ overrides.runtime = {
7955
+ operationMode: env.SIFT_OPERATION_MODE
7956
+ };
7815
7957
  }
7816
7958
  if (env.SIFT_MAX_INPUT_CHARS || env.SIFT_MAX_CAPTURE_CHARS) {
7817
7959
  overrides.input = {
@@ -7878,14 +8020,26 @@ function resolveConfig(options = {}) {
7878
8020
  );
7879
8021
  return siftConfigSchema.parse(merged);
7880
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
+ }
7881
8032
  export {
7882
8033
  BoundedCapture,
7883
8034
  buildCommandPreview,
8035
+ detectPackageManagerScriptKind,
7884
8036
  getExecSuccessShortcut,
7885
8037
  looksInteractivePrompt,
7886
8038
  mergeDefined,
7887
8039
  normalizeChildExitCode,
8040
+ normalizeScriptWrapperOutput,
7888
8041
  resolveConfig,
8042
+ resolveEffectiveOperationMode,
7889
8043
  runExec,
7890
8044
  runSift,
7891
8045
  runSiftWithStats,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bilalimamoglu/sift",
3
- "version": "0.4.4",
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",
@@ -30,7 +31,12 @@
30
31
  "test:e2e": "vitest run --config vitest.e2e.config.ts test/*.e2e.test.ts",
31
32
  "test:coverage": "vitest run --config vitest.config.ts --coverage --exclude=\"test/**/*.smoke.test.ts\" --exclude=\"test/**/*.e2e.test.ts\"",
32
33
  "test:watch": "vitest",
33
- "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"
34
40
  },
35
41
  "keywords": [
36
42
  "cli",
@@ -39,6 +45,10 @@
39
45
  "automation",
40
46
  "terminal",
41
47
  "logs",
48
+ "debugging",
49
+ "heuristics",
50
+ "root-cause",
51
+ "test-failures",
42
52
  "ci",
43
53
  "json"
44
54
  ],