@bilalimamoglu/sift 0.4.0 → 0.4.2
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 +65 -212
- package/dist/cli.js +26 -16
- package/dist/index.d.ts +2 -1
- package/dist/index.js +28 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,17 +4,19 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/bilalimamoglu/sift/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Turn 13,000 lines of test output into 2 root causes.
|
|
8
8
|
|
|
9
|
-
Your
|
|
9
|
+
Your agent reads a diagnosis, not a log file.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="assets/readme/test-status-demo.gif" alt="sift turning a pytest failure wall into a short diagnosis" width="960" />
|
|
13
|
+
</p>
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
## Before / After
|
|
16
|
+
|
|
17
|
+
128 test failures. 13,000 lines of logs. The agent reads all of it.
|
|
18
|
+
|
|
19
|
+
With `sift`, it reads this instead:
|
|
18
20
|
|
|
19
21
|
```text
|
|
20
22
|
- Tests did not pass.
|
|
@@ -28,13 +30,18 @@ sift exec --preset test-status -- pytest -q
|
|
|
28
30
|
- Decision: stop and act.
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Same diagnosis. One run compressed from 198,000 tokens to 129.
|
|
32
34
|
|
|
33
|
-
##
|
|
35
|
+
## Not just tests
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
The same idea applies across noisy dev workflows:
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
- **Type errors** → grouped by error code, no model call
|
|
40
|
+
- **Lint output** → grouped by rule, no model call
|
|
41
|
+
- **Build failures** → first real error from webpack, esbuild/Vite, Cargo, Go, GCC/Clang
|
|
42
|
+
- **`npm audit`** → high/critical vulnerabilities only, no model call
|
|
43
|
+
- **`terraform plan`** → destructive risk detection, no model call
|
|
44
|
+
- **Diffs and logs** → compressed through a cheaper model before reaching your agent
|
|
38
45
|
|
|
39
46
|
## Install
|
|
40
47
|
|
|
@@ -44,255 +51,101 @@ npm install -g @bilalimamoglu/sift
|
|
|
44
51
|
|
|
45
52
|
Requires Node.js 20+.
|
|
46
53
|
|
|
47
|
-
## Try it
|
|
48
|
-
|
|
49
|
-
If you already have an API key, you can try `sift` without any setup wizard:
|
|
54
|
+
## Try it
|
|
50
55
|
|
|
51
56
|
```bash
|
|
52
|
-
export OPENAI_API_KEY=your_openai_api_key
|
|
53
57
|
sift exec --preset test-status -- pytest -q
|
|
58
|
+
sift exec --preset test-status -- npx vitest run
|
|
59
|
+
sift exec --preset test-status -- npx jest
|
|
54
60
|
```
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
sift exec "what changed?" -- git diff
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Set it up for daily use
|
|
63
|
-
|
|
64
|
-
Guided setup writes a machine-wide config, verifies the provider, and makes the CLI easier to use day to day:
|
|
62
|
+
Other workflows:
|
|
65
63
|
|
|
66
64
|
```bash
|
|
67
|
-
sift
|
|
68
|
-
sift
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Config lives at `~/.config/sift/config.yaml`. A repo-local `sift.config.yaml` can override it later.
|
|
72
|
-
|
|
73
|
-
If you want your coding agent to use `sift` automatically, install the managed instruction block too:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
sift agent install codex
|
|
77
|
-
sift agent install claude
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Then run noisy commands through `sift`:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
sift exec --preset test-status -- <test command>
|
|
84
|
-
sift exec "what changed?" -- git diff
|
|
65
|
+
sift exec --preset typecheck-summary -- npx tsc --noEmit
|
|
66
|
+
sift exec --preset lint-failures -- npx eslint src/
|
|
67
|
+
sift exec --preset build-failure -- npm run build
|
|
85
68
|
sift exec --preset audit-critical -- npm audit
|
|
86
69
|
sift exec --preset infra-risk -- terraform plan
|
|
70
|
+
sift exec "what changed?" -- git diff
|
|
87
71
|
```
|
|
88
72
|
|
|
89
|
-
Useful flags:
|
|
90
|
-
- `--dry-run` to preview the reduced input and prompt without calling a provider
|
|
91
|
-
- `--show-raw` to print captured raw output to `stderr`
|
|
92
|
-
- `--fail-on` to let reduced results fail CI for commands such as `npm audit` or `terraform plan`
|
|
93
|
-
|
|
94
|
-
If you prefer environment variables instead of setup:
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# OpenAI
|
|
98
|
-
export SIFT_PROVIDER=openai
|
|
99
|
-
export SIFT_BASE_URL=https://api.openai.com/v1
|
|
100
|
-
export SIFT_MODEL=gpt-5-nano
|
|
101
|
-
export OPENAI_API_KEY=your_openai_api_key
|
|
102
|
-
|
|
103
|
-
# OpenRouter
|
|
104
|
-
export SIFT_PROVIDER=openrouter
|
|
105
|
-
export OPENROUTER_API_KEY=your_openrouter_api_key
|
|
106
|
-
|
|
107
|
-
# Any OpenAI-compatible endpoint
|
|
108
|
-
export SIFT_PROVIDER=openai-compatible
|
|
109
|
-
export SIFT_BASE_URL=https://your-endpoint/v1
|
|
110
|
-
export SIFT_PROVIDER_API_KEY=your_api_key
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Why it helps
|
|
114
|
-
|
|
115
|
-
The core abstraction is a **bucket**: one distinct root cause, no matter how many tests it affects.
|
|
116
|
-
|
|
117
|
-
Instead of making an agent reason over 125 repeated tracebacks, `sift` compresses them into one actionable bucket with:
|
|
118
|
-
- a label
|
|
119
|
-
- an affected count
|
|
120
|
-
- an anchor
|
|
121
|
-
- a likely fix
|
|
122
|
-
- a decision signal
|
|
123
|
-
|
|
124
|
-
That changes the agent's job from "figure out what happened" to "act on the diagnosis."
|
|
125
|
-
|
|
126
73
|
## How it works
|
|
127
74
|
|
|
128
|
-
`sift`
|
|
129
|
-
|
|
130
|
-
1. Capture command output.
|
|
131
|
-
2. Sanitize sensitive-looking material.
|
|
132
|
-
3. Apply local heuristics for known failure shapes.
|
|
133
|
-
4. Escalate to a cheaper provider only if needed.
|
|
134
|
-
5. Return a short diagnosis to the main agent.
|
|
75
|
+
`sift` sits between a noisy command and a coding agent.
|
|
135
76
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
77
|
+
1. Capture output.
|
|
78
|
+
2. Run local heuristics for known failure shapes.
|
|
79
|
+
3. If heuristics are confident, return the diagnosis. No model call.
|
|
80
|
+
4. If not, call a cheaper model — not your agent's.
|
|
140
81
|
|
|
141
|
-
|
|
82
|
+
The agent gets the root cause, where it happens, and what to do next.
|
|
142
83
|
|
|
143
|
-
|
|
84
|
+
So your agent spends tokens fixing, not reading.
|
|
144
85
|
|
|
145
86
|
## Built-in presets
|
|
146
87
|
|
|
147
|
-
Every preset runs local heuristics first. When the heuristic
|
|
148
|
-
|
|
149
|
-
| Preset | Heuristic | What it does |
|
|
150
|
-
|--------|-----------|-------------|
|
|
151
|
-
| `test-status` | Deep | Bucket/anchor/decision system for pytest, vitest, jest. 30+ failure patterns, confidence-gated stop/zoom decisions. |
|
|
152
|
-
| `typecheck-summary` | Deterministic | Parses `tsc` output (standard and pretty formats), groups by error code, returns max 5 bullets. |
|
|
153
|
-
| `lint-failures` | Deterministic | Parses ESLint stylish output, groups by rule, distinguishes errors from warnings, detects fixable hints. |
|
|
154
|
-
| `audit-critical` | Deterministic | Extracts high/critical vulnerabilities from `npm audit` or similar. |
|
|
155
|
-
| `infra-risk` | Deterministic | Detects destructive signals in `terraform plan` output. Returns pass/fail verdict. |
|
|
156
|
-
| `build-failure` | Deterministic-first | Extracts the first concrete build error for recognized webpack, esbuild/Vite, Cargo, Go, GCC/Clang, and `tsc --build` output; falls back to the provider for unsupported formats. |
|
|
157
|
-
| `diff-summary` | Provider | Summarizes changes and risks in diff output. |
|
|
158
|
-
| `log-errors` | Provider | Extracts top error signals from log output. |
|
|
159
|
-
|
|
160
|
-
Presets marked **Deterministic** bypass the provider entirely for recognized output formats. Presets marked **Deterministic-first** try a local heuristic first and fall back to the provider only when the captured output is unsupported or ambiguous. Presets marked **Provider** always call the LLM but benefit from input sanitization and truncation.
|
|
88
|
+
Every preset runs local heuristics first. When the heuristic handles the output, the provider is never called.
|
|
161
89
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
```text
|
|
173
|
-
[sift: heuristic • LLM skipped • summary 47ms]
|
|
174
|
-
[sift: provider • LLM used • 380 tokens • summary 1.2s]
|
|
175
|
-
```
|
|
90
|
+
| Preset | What it does |
|
|
91
|
+
|--------|-------------|
|
|
92
|
+
| `test-status` | Groups pytest, vitest, jest failures into root-cause buckets with anchors and fix suggestions. 30+ failure patterns. |
|
|
93
|
+
| `typecheck-summary` | Parses `tsc` output, groups by error code, returns max 5 bullets. No model call. |
|
|
94
|
+
| `lint-failures` | Parses ESLint output, groups by rule, detects fixable hints. No model call. |
|
|
95
|
+
| `build-failure` | Extracts first concrete error from webpack, esbuild/Vite, Cargo, Go, GCC/Clang, `tsc --build`. Falls back to model for unsupported formats. |
|
|
96
|
+
| `audit-critical` | Extracts high/critical vulnerabilities from `npm audit`. No model call. |
|
|
97
|
+
| `infra-risk` | Detects destructive signals in `terraform plan`. No model call. |
|
|
98
|
+
| `diff-summary` | Summarizes changes and risks in diff output. |
|
|
99
|
+
| `log-errors` | Extracts top error signals from log output. |
|
|
176
100
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
sift exec --preset typecheck-summary --quiet -- npx tsc --noEmit
|
|
181
|
-
```
|
|
101
|
+
## Benchmark
|
|
182
102
|
|
|
183
|
-
|
|
103
|
+
End-to-end debug loop on a real 640-test Python backend (125 repeated setup errors, 3 contract failures, 510 passing tests):
|
|
184
104
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
-
|
|
105
|
+
| Metric | Without sift | With sift | Reduction |
|
|
106
|
+
|--------|-------------|-----------|-----------|
|
|
107
|
+
| Tokens | 52,944 | 20,049 | 62% fewer |
|
|
108
|
+
| Tool calls | 40.8 | 12 | 71% fewer |
|
|
109
|
+
| Wall-clock time | 244s | 85s | 65% faster |
|
|
110
|
+
| Commands | 15.5 | 6 | 61% fewer |
|
|
111
|
+
| Diagnosis | Same | Same | — |
|
|
190
112
|
|
|
191
|
-
|
|
192
|
-
- large `pytest`, `vitest`, or `jest` runs
|
|
193
|
-
- `tsc` type errors and `eslint` lint failures
|
|
194
|
-
- build failures from webpack, esbuild/Vite, Cargo, Go, GCC/Clang
|
|
195
|
-
- `npm audit` and `terraform plan`
|
|
196
|
-
- repeated CI blockers
|
|
197
|
-
- noisy diffs and log streams
|
|
113
|
+
Methodology and caveats: [BENCHMARK_NOTES.md](BENCHMARK_NOTES.md)
|
|
198
114
|
|
|
199
115
|
## Test debugging workflow
|
|
200
116
|
|
|
201
|
-
This is where `sift` is strongest today.
|
|
202
|
-
|
|
203
117
|
Think of it like this:
|
|
204
118
|
- `standard` = map
|
|
205
119
|
- `focused` = zoom
|
|
206
120
|
- raw traceback = last resort
|
|
207
121
|
|
|
208
|
-
Typical loop:
|
|
209
|
-
|
|
210
122
|
```bash
|
|
211
123
|
sift exec --preset test-status -- <test command>
|
|
212
124
|
sift rerun
|
|
213
125
|
sift rerun --remaining --detail focused
|
|
214
126
|
```
|
|
215
127
|
|
|
216
|
-
If `standard` already gives you the root cause, anchor, and fix
|
|
128
|
+
If `standard` already gives you the root cause, anchor, and fix — stop and act.
|
|
217
129
|
|
|
218
|
-
`sift rerun --remaining` narrows automatically for cached `pytest` runs.
|
|
130
|
+
`sift rerun --remaining` narrows automatically for cached `pytest` runs. For `vitest` and `jest`, it reruns the full command and keeps diagnosis focused on what still fails.
|
|
219
131
|
|
|
220
|
-
|
|
132
|
+
## Setup
|
|
221
133
|
|
|
222
|
-
|
|
134
|
+
Guided setup writes a config, verifies the provider, and makes daily use easier:
|
|
223
135
|
|
|
224
136
|
```bash
|
|
225
|
-
sift
|
|
226
|
-
sift
|
|
227
|
-
sift agent remove claude
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Where it helps less
|
|
231
|
-
|
|
232
|
-
`sift` adds less value when:
|
|
233
|
-
- the output is already short and obvious
|
|
234
|
-
- the command is interactive or TUI-based
|
|
235
|
-
- the exact raw log matters
|
|
236
|
-
- the output does not expose enough evidence for reliable grouping
|
|
237
|
-
|
|
238
|
-
When it cannot be confident, it tells you to zoom or read raw instead of pretending certainty.
|
|
239
|
-
|
|
240
|
-
## Benchmark
|
|
241
|
-
|
|
242
|
-
On a real 640-test Python backend (125 repeated setup errors, 3 contract failures, 510 passing tests):
|
|
243
|
-
|
|
244
|
-
| Metric | Raw agent | sift-first | Reduction |
|
|
245
|
-
|--------|-----------|------------|-----------|
|
|
246
|
-
| Tokens | 305K | 600 | 99.8% |
|
|
247
|
-
| Tool calls | 16 | 7 | 56% |
|
|
248
|
-
| Diagnosis | Same | Same | — |
|
|
249
|
-
|
|
250
|
-
The table above is the single-fixture reduction story: the largest real test log in the benchmark shrank from `198026` raw tokens to `129` `standard` tokens.
|
|
251
|
-
|
|
252
|
-
The end-to-end workflow benchmark is a different metric:
|
|
253
|
-
- `62%` fewer total debugging tokens
|
|
254
|
-
- `71%` fewer tool calls
|
|
255
|
-
- `65%` faster wall-clock time
|
|
256
|
-
|
|
257
|
-
Both matter. The table shows how aggressively `sift` can compress one large noisy run. The workflow numbers show how that compounds across a full debug loop.
|
|
258
|
-
|
|
259
|
-
Methodology and caveats live in [BENCHMARK_NOTES.md](BENCHMARK_NOTES.md).
|
|
260
|
-
|
|
261
|
-
## Configuration
|
|
262
|
-
|
|
263
|
-
Inspect and validate config with:
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
sift config show
|
|
267
|
-
sift config show --show-secrets
|
|
268
|
-
sift config validate
|
|
137
|
+
sift config setup
|
|
138
|
+
sift doctor
|
|
269
139
|
```
|
|
270
140
|
|
|
271
|
-
To
|
|
141
|
+
To wire `sift` into your coding agent automatically:
|
|
272
142
|
|
|
273
143
|
```bash
|
|
274
|
-
sift
|
|
275
|
-
sift
|
|
144
|
+
sift agent install claude
|
|
145
|
+
sift agent install codex
|
|
276
146
|
```
|
|
277
147
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
```yaml
|
|
281
|
-
provider:
|
|
282
|
-
provider: openai
|
|
283
|
-
model: gpt-5-nano
|
|
284
|
-
baseUrl: https://api.openai.com/v1
|
|
285
|
-
apiKey: YOUR_API_KEY
|
|
286
|
-
|
|
287
|
-
input:
|
|
288
|
-
stripAnsi: true
|
|
289
|
-
redact: false
|
|
290
|
-
maxCaptureChars: 400000
|
|
291
|
-
maxInputChars: 60000
|
|
292
|
-
|
|
293
|
-
runtime:
|
|
294
|
-
rawFallback: true
|
|
295
|
-
```
|
|
148
|
+
Config details: [docs/cli-reference.md](docs/cli-reference.md)
|
|
296
149
|
|
|
297
150
|
## Docs
|
|
298
151
|
|
package/dist/cli.js
CHANGED
|
@@ -7594,7 +7594,7 @@ function buildGenericRawSlice(args) {
|
|
|
7594
7594
|
|
|
7595
7595
|
// src/core/run.ts
|
|
7596
7596
|
var RETRY_DELAY_MS = 300;
|
|
7597
|
-
var
|
|
7597
|
+
var PENDING_NOTICE_DELAY_MS = 150;
|
|
7598
7598
|
function estimateTokenCount(text) {
|
|
7599
7599
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
7600
7600
|
}
|
|
@@ -7675,17 +7675,16 @@ function buildDryRunOutput(args) {
|
|
|
7675
7675
|
async function delay(ms) {
|
|
7676
7676
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
7677
7677
|
}
|
|
7678
|
-
function
|
|
7679
|
-
if (!
|
|
7678
|
+
function startPendingNotice(message, enabled) {
|
|
7679
|
+
if (!enabled) {
|
|
7680
7680
|
return () => {
|
|
7681
7681
|
};
|
|
7682
7682
|
}
|
|
7683
|
-
const message = "sift waiting for provider...";
|
|
7684
7683
|
let shown = false;
|
|
7685
7684
|
const timer = setTimeout(() => {
|
|
7686
7685
|
shown = true;
|
|
7687
7686
|
process.stderr.write(`${message}\r`);
|
|
7688
|
-
},
|
|
7687
|
+
}, PENDING_NOTICE_DELAY_MS);
|
|
7689
7688
|
return () => {
|
|
7690
7689
|
clearTimeout(timer);
|
|
7691
7690
|
if (!shown) {
|
|
@@ -7715,7 +7714,10 @@ async function generateWithRetry(args) {
|
|
|
7715
7714
|
responseMode: args.responseMode,
|
|
7716
7715
|
jsonResponseFormat: args.request.config.provider.jsonResponseFormat
|
|
7717
7716
|
});
|
|
7718
|
-
const stopPendingNotice =
|
|
7717
|
+
const stopPendingNotice = startPendingNotice(
|
|
7718
|
+
"sift waiting for provider...",
|
|
7719
|
+
Boolean(process.stderr.isTTY)
|
|
7720
|
+
);
|
|
7719
7721
|
try {
|
|
7720
7722
|
try {
|
|
7721
7723
|
return await generate();
|
|
@@ -9281,6 +9283,10 @@ async function runExec(request) {
|
|
|
9281
9283
|
cwd: commandCwd,
|
|
9282
9284
|
stdio: ["inherit", "pipe", "pipe"]
|
|
9283
9285
|
});
|
|
9286
|
+
const stopChildPendingNotice = startPendingNotice(
|
|
9287
|
+
"sift waiting for child command...",
|
|
9288
|
+
Boolean(process.stderr.isTTY) && !request.quiet
|
|
9289
|
+
);
|
|
9284
9290
|
const handleChunk = (chunk) => {
|
|
9285
9291
|
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
9286
9292
|
if (bypassed) {
|
|
@@ -9301,21 +9307,25 @@ async function runExec(request) {
|
|
|
9301
9307
|
};
|
|
9302
9308
|
child.stdout.on("data", handleChunk);
|
|
9303
9309
|
child.stderr.on("data", handleChunk);
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9310
|
+
try {
|
|
9311
|
+
await new Promise((resolve, reject) => {
|
|
9312
|
+
child.on("error", (error) => {
|
|
9313
|
+
reject(error);
|
|
9314
|
+
});
|
|
9315
|
+
child.on("close", (status, signal) => {
|
|
9316
|
+
childStatus = status;
|
|
9317
|
+
childSignal = signal;
|
|
9318
|
+
resolve();
|
|
9319
|
+
});
|
|
9312
9320
|
});
|
|
9313
|
-
}
|
|
9321
|
+
} catch (error) {
|
|
9314
9322
|
if (error instanceof Error) {
|
|
9315
9323
|
throw error;
|
|
9316
9324
|
}
|
|
9317
9325
|
throw new Error("Failed to start child process.");
|
|
9318
|
-
}
|
|
9326
|
+
} finally {
|
|
9327
|
+
stopChildPendingNotice();
|
|
9328
|
+
}
|
|
9319
9329
|
const exitCode = normalizeChildExitCode(childStatus, childSignal);
|
|
9320
9330
|
const capturedOutput = capture.render();
|
|
9321
9331
|
const autoWatchDetected = !request.watch && looksLikeWatchStream(capturedOutput);
|
package/dist/index.d.ts
CHANGED
|
@@ -162,6 +162,7 @@ interface RunResult {
|
|
|
162
162
|
stats: RunStats | null;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
declare function startPendingNotice(message: string, enabled: boolean): () => void;
|
|
165
166
|
declare function runSift(request: RunRequest): Promise<string>;
|
|
166
167
|
declare function runSiftWithStats(request: RunRequest): Promise<RunResult>;
|
|
167
168
|
|
|
@@ -178,4 +179,4 @@ interface ResolveOptions {
|
|
|
178
179
|
}
|
|
179
180
|
declare function resolveConfig(options?: ResolveOptions): SiftConfig;
|
|
180
181
|
|
|
181
|
-
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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -5774,7 +5774,7 @@ function buildGenericRawSlice(args) {
|
|
|
5774
5774
|
|
|
5775
5775
|
// src/core/run.ts
|
|
5776
5776
|
var RETRY_DELAY_MS = 300;
|
|
5777
|
-
var
|
|
5777
|
+
var PENDING_NOTICE_DELAY_MS = 150;
|
|
5778
5778
|
function estimateTokenCount(text) {
|
|
5779
5779
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
5780
5780
|
}
|
|
@@ -5855,17 +5855,16 @@ function buildDryRunOutput(args) {
|
|
|
5855
5855
|
async function delay(ms) {
|
|
5856
5856
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
5857
5857
|
}
|
|
5858
|
-
function
|
|
5859
|
-
if (!
|
|
5858
|
+
function startPendingNotice(message, enabled) {
|
|
5859
|
+
if (!enabled) {
|
|
5860
5860
|
return () => {
|
|
5861
5861
|
};
|
|
5862
5862
|
}
|
|
5863
|
-
const message = "sift waiting for provider...";
|
|
5864
5863
|
let shown = false;
|
|
5865
5864
|
const timer = setTimeout(() => {
|
|
5866
5865
|
shown = true;
|
|
5867
5866
|
process.stderr.write(`${message}\r`);
|
|
5868
|
-
},
|
|
5867
|
+
}, PENDING_NOTICE_DELAY_MS);
|
|
5869
5868
|
return () => {
|
|
5870
5869
|
clearTimeout(timer);
|
|
5871
5870
|
if (!shown) {
|
|
@@ -5895,7 +5894,10 @@ async function generateWithRetry(args) {
|
|
|
5895
5894
|
responseMode: args.responseMode,
|
|
5896
5895
|
jsonResponseFormat: args.request.config.provider.jsonResponseFormat
|
|
5897
5896
|
});
|
|
5898
|
-
const stopPendingNotice =
|
|
5897
|
+
const stopPendingNotice = startPendingNotice(
|
|
5898
|
+
"sift waiting for provider...",
|
|
5899
|
+
Boolean(process.stderr.isTTY)
|
|
5900
|
+
);
|
|
5899
5901
|
try {
|
|
5900
5902
|
try {
|
|
5901
5903
|
return await generate();
|
|
@@ -7293,6 +7295,10 @@ async function runExec(request) {
|
|
|
7293
7295
|
cwd: commandCwd,
|
|
7294
7296
|
stdio: ["inherit", "pipe", "pipe"]
|
|
7295
7297
|
});
|
|
7298
|
+
const stopChildPendingNotice = startPendingNotice(
|
|
7299
|
+
"sift waiting for child command...",
|
|
7300
|
+
Boolean(process.stderr.isTTY) && !request.quiet
|
|
7301
|
+
);
|
|
7296
7302
|
const handleChunk = (chunk) => {
|
|
7297
7303
|
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
7298
7304
|
if (bypassed) {
|
|
@@ -7313,21 +7319,25 @@ async function runExec(request) {
|
|
|
7313
7319
|
};
|
|
7314
7320
|
child.stdout.on("data", handleChunk);
|
|
7315
7321
|
child.stderr.on("data", handleChunk);
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7322
|
+
try {
|
|
7323
|
+
await new Promise((resolve, reject) => {
|
|
7324
|
+
child.on("error", (error) => {
|
|
7325
|
+
reject(error);
|
|
7326
|
+
});
|
|
7327
|
+
child.on("close", (status, signal) => {
|
|
7328
|
+
childStatus = status;
|
|
7329
|
+
childSignal = signal;
|
|
7330
|
+
resolve();
|
|
7331
|
+
});
|
|
7324
7332
|
});
|
|
7325
|
-
}
|
|
7333
|
+
} catch (error) {
|
|
7326
7334
|
if (error instanceof Error) {
|
|
7327
7335
|
throw error;
|
|
7328
7336
|
}
|
|
7329
7337
|
throw new Error("Failed to start child process.");
|
|
7330
|
-
}
|
|
7338
|
+
} finally {
|
|
7339
|
+
stopChildPendingNotice();
|
|
7340
|
+
}
|
|
7331
7341
|
const exitCode = normalizeChildExitCode(childStatus, childSignal);
|
|
7332
7342
|
const capturedOutput = capture.render();
|
|
7333
7343
|
const autoWatchDetected = !request.watch && looksLikeWatchStream(capturedOutput);
|
|
@@ -7853,5 +7863,6 @@ export {
|
|
|
7853
7863
|
resolveConfig,
|
|
7854
7864
|
runExec,
|
|
7855
7865
|
runSift,
|
|
7856
|
-
runSiftWithStats
|
|
7866
|
+
runSiftWithStats,
|
|
7867
|
+
startPendingNotice
|
|
7857
7868
|
};
|