@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/README.md +66 -21
- package/dist/cli.js +1328 -155
- package/dist/index.d.ts +11 -2
- package/dist/index.js +202 -23
- package/package.json +17 -5
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
6911
|
+
fs2.mkdirSync(path2.dirname(statePath), {
|
|
6888
6912
|
recursive: true
|
|
6889
6913
|
});
|
|
6890
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
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
|
|
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.
|
|
4
|
-
"description": "
|
|
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:
|
|
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
|
-
"
|
|
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
|
],
|