sift-cli 1.0.0__tar.gz
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.
- sift_cli-1.0.0/.gitattributes +21 -0
- sift_cli-1.0.0/.github/workflows/ci.yml +40 -0
- sift_cli-1.0.0/.github/workflows/release.yml +77 -0
- sift_cli-1.0.0/.gitignore +10 -0
- sift_cli-1.0.0/LICENSE +21 -0
- sift_cli-1.0.0/PKG-INFO +326 -0
- sift_cli-1.0.0/README.md +306 -0
- sift_cli-1.0.0/notlar/00-PLAN.md +155 -0
- sift_cli-1.0.0/notlar/01-YAKALAMA.md +97 -0
- sift_cli-1.0.0/notlar/02-MODEL-KOPRUSU.md +115 -0
- sift_cli-1.0.0/notlar/03-DAMITMA.md +116 -0
- sift_cli-1.0.0/notlar/04-GUVENLIK-AGI.md +135 -0
- sift_cli-1.0.0/notlar/05-DIL-BAGIMSIZLIGI.md +260 -0
- sift_cli-1.0.0/notlar/06-DOSYA-TASLAGI.md +348 -0
- sift_cli-1.0.0/notlar/07-MCP-SUNUCUSU.md +194 -0
- sift_cli-1.0.0/notlar/08-BUTCE-VE-OLCUM.md +323 -0
- sift_cli-1.0.0/notlar/09-ARKA-PLAN-KOMUTLARI.md +127 -0
- sift_cli-1.0.0/notlar/10-GIZLILIK.md +108 -0
- sift_cli-1.0.0/notlar/11-YAYIN.md +110 -0
- sift_cli-1.0.0/notlar/12-CAGIRANIN-SOZ-HAKKI.md +51 -0
- sift_cli-1.0.0/notlar/13-DOSYA-DAMITMA.md +39 -0
- sift_cli-1.0.0/notlar/14-ARAYAN-PEEK.md +38 -0
- sift_cli-1.0.0/notlar/15-COKLU-IS.md +58 -0
- sift_cli-1.0.0/notlar/16-HAFIZA.md +41 -0
- sift_cli-1.0.0/notlar/17-YOGUN-ARACLAR.md +39 -0
- sift_cli-1.0.0/notlar/18-KAPSAM.md +52 -0
- sift_cli-1.0.0/notlar/19-KALINTI-MUTASYON.md +188 -0
- sift_cli-1.0.0/notlar/20-KAYIT-BIRIMI.md +83 -0
- sift_cli-1.0.0/notlar/21-SAKLAMA.md +78 -0
- sift_cli-1.0.0/notlar/22-YANIT-ONBELLEGI.md +79 -0
- sift_cli-1.0.0/notlar/23-OLCUM-BIRIMI.md +67 -0
- sift_cli-1.0.0/notlar/24-ANAHTAR.md +69 -0
- sift_cli-1.0.0/notlar/25-TEKLIF.md +73 -0
- sift_cli-1.0.0/notlar/26-BEKLEMEK.md +147 -0
- sift_cli-1.0.0/notlar/27-WINDOWS.md +95 -0
- sift_cli-1.0.0/pyproject.toml +60 -0
- sift_cli-1.0.0/src/sift/__init__.py +18 -0
- sift_cli-1.0.0/src/sift/answers.py +175 -0
- sift_cli-1.0.0/src/sift/background.py +444 -0
- sift_cli-1.0.0/src/sift/capture.py +240 -0
- sift_cli-1.0.0/src/sift/cli.py +820 -0
- sift_cli-1.0.0/src/sift/digest.py +101 -0
- sift_cli-1.0.0/src/sift/distill.py +670 -0
- sift_cli-1.0.0/src/sift/fallback.py +98 -0
- sift_cli-1.0.0/src/sift/hook.py +275 -0
- sift_cli-1.0.0/src/sift/lines.py +37 -0
- sift_cli-1.0.0/src/sift/many.py +51 -0
- sift_cli-1.0.0/src/sift/memory.py +94 -0
- sift_cli-1.0.0/src/sift/model.py +433 -0
- sift_cli-1.0.0/src/sift/outline.py +117 -0
- sift_cli-1.0.0/src/sift/peek.py +161 -0
- sift_cli-1.0.0/src/sift/privacy.py +145 -0
- sift_cli-1.0.0/src/sift/records.py +95 -0
- sift_cli-1.0.0/src/sift/server.py +552 -0
- sift_cli-1.0.0/src/sift/store.py +499 -0
- sift_cli-1.0.0/src/sift/tools.py +76 -0
- sift_cli-1.0.0/src/sift/view.py +317 -0
- sift_cli-1.0.0/src/sift/watch.py +166 -0
- sift_cli-1.0.0/test/budget.py +307 -0
- sift_cli-1.0.0/test/conftest.py +151 -0
- sift_cli-1.0.0/test/korpus/README.md +73 -0
- sift_cli-1.0.0/test/korpus/ansi-progress.json +23 -0
- sift_cli-1.0.0/test/korpus/ansi-progress.txt +17 -0
- sift_cli-1.0.0/test/korpus/cargo-de.json +29 -0
- sift_cli-1.0.0/test/korpus/cargo-de.txt +43 -0
- sift_cli-1.0.0/test/korpus/cmake-vi.json +24 -0
- sift_cli-1.0.0/test/korpus/cmake-vi.txt +25 -0
- sift_cli-1.0.0/test/korpus/cobol-mainframe.json +36 -0
- sift_cli-1.0.0/test/korpus/cobol-mainframe.txt +32 -0
- sift_cli-1.0.0/test/korpus/docker-ar.json +33 -0
- sift_cli-1.0.0/test/korpus/docker-ar.txt +43 -0
- sift_cli-1.0.0/test/korpus/dotnet-fr.json +29 -0
- sift_cli-1.0.0/test/korpus/dotnet-fr.txt +15 -0
- sift_cli-1.0.0/test/korpus/ffmpeg-fa.json +21 -0
- sift_cli-1.0.0/test/korpus/ffmpeg-fa.txt +36 -0
- sift_cli-1.0.0/test/korpus/gcc-ru.json +27 -0
- sift_cli-1.0.0/test/korpus/gcc-ru.txt +21 -0
- sift_cli-1.0.0/test/korpus/git-quiet.json +13 -0
- sift_cli-1.0.0/test/korpus/git-quiet.txt +9 -0
- sift_cli-1.0.0/test/korpus/gotest-zh.json +33 -0
- sift_cli-1.0.0/test/korpus/gotest-zh.txt +23 -0
- sift_cli-1.0.0/test/korpus/gradle-ko.json +34 -0
- sift_cli-1.0.0/test/korpus/gradle-ko.txt +33 -0
- sift_cli-1.0.0/test/korpus/journalctl-it.json +30 -0
- sift_cli-1.0.0/test/korpus/journalctl-it.txt +23 -0
- sift_cli-1.0.0/test/korpus/kubectl-hi.json +43 -0
- sift_cli-1.0.0/test/korpus/kubectl-hi.txt +41 -0
- sift_cli-1.0.0/test/korpus/latex-pl.json +24 -0
- sift_cli-1.0.0/test/korpus/latex-pl.txt +38 -0
- sift_cli-1.0.0/test/korpus/maven-tr.json +40 -0
- sift_cli-1.0.0/test/korpus/maven-tr.txt +36 -0
- sift_cli-1.0.0/test/korpus/mix-he.json +31 -0
- sift_cli-1.0.0/test/korpus/mix-he.txt +19 -0
- sift_cli-1.0.0/test/korpus/psql-es.json +25 -0
- sift_cli-1.0.0/test/korpus/psql-es.txt +26 -0
- sift_cli-1.0.0/test/korpus/pytest-en.json +26 -0
- sift_cli-1.0.0/test/korpus/pytest-en.txt +30 -0
- sift_cli-1.0.0/test/korpus/rspec-id.json +31 -0
- sift_cli-1.0.0/test/korpus/rspec-id.txt +21 -0
- sift_cli-1.0.0/test/korpus/terraform-pt.json +20 -0
- sift_cli-1.0.0/test/korpus/terraform-pt.txt +40 -0
- sift_cli-1.0.0/test/korpus/vite-ja.json +39 -0
- sift_cli-1.0.0/test/korpus/vite-ja.txt +28 -0
- sift_cli-1.0.0/test/korpus/zig-sw.json +32 -0
- sift_cli-1.0.0/test/korpus/zig-sw.txt +22 -0
- sift_cli-1.0.0/test/korpus-kaynak/clojure-rapor.json +39 -0
- sift_cli-1.0.0/test/korpus-kaynak/clojure-rapor.txt +31 -0
- sift_cli-1.0.0/test/korpus-kaynak/cpp-baslik.json +25 -0
- sift_cli-1.0.0/test/korpus-kaynak/cpp-baslik.txt +35 -0
- sift_cli-1.0.0/test/korpus-kaynak/elixir-onbellek.json +46 -0
- sift_cli-1.0.0/test/korpus-kaynak/elixir-onbellek.txt +40 -0
- sift_cli-1.0.0/test/korpus-kaynak/go-kuyruk.json +38 -0
- sift_cli-1.0.0/test/korpus-kaynak/go-kuyruk.txt +50 -0
- sift_cli-1.0.0/test/korpus-kaynak/haskell-ayristirici.json +37 -0
- sift_cli-1.0.0/test/korpus-kaynak/haskell-ayristirici.txt +44 -0
- sift_cli-1.0.0/test/korpus-kaynak/hcl-altyapi.json +20 -0
- sift_cli-1.0.0/test/korpus-kaynak/hcl-altyapi.txt +44 -0
- sift_cli-1.0.0/test/korpus-kaynak/java-depo.json +46 -0
- sift_cli-1.0.0/test/korpus-kaynak/java-depo.txt +48 -0
- sift_cli-1.0.0/test/korpus-kaynak/makefile-yapi.json +45 -0
- sift_cli-1.0.0/test/korpus-kaynak/makefile-yapi.txt +27 -0
- sift_cli-1.0.0/test/korpus-kaynak/python-akis.json +40 -0
- sift_cli-1.0.0/test/korpus-kaynak/python-akis.txt +46 -0
- sift_cli-1.0.0/test/korpus-kaynak/qqzz-uydurma.json +40 -0
- sift_cli-1.0.0/test/korpus-kaynak/qqzz-uydurma.txt +35 -0
- sift_cli-1.0.0/test/korpus-kaynak/ruby-fatura.json +48 -0
- sift_cli-1.0.0/test/korpus-kaynak/ruby-fatura.txt +37 -0
- sift_cli-1.0.0/test/korpus-kaynak/rust-matris.json +42 -0
- sift_cli-1.0.0/test/korpus-kaynak/rust-matris.txt +42 -0
- sift_cli-1.0.0/test/korpus-kaynak/shell-dagitim.json +47 -0
- sift_cli-1.0.0/test/korpus-kaynak/shell-dagitim.txt +40 -0
- sift_cli-1.0.0/test/korpus-kaynak/sql-sema.json +33 -0
- sift_cli-1.0.0/test/korpus-kaynak/sql-sema.txt +37 -0
- sift_cli-1.0.0/test/korpus_reader.py +134 -0
- sift_cli-1.0.0/test/languages.py +129 -0
- sift_cli-1.0.0/test/mutations.py +1450 -0
- sift_cli-1.0.0/test/outlines.py +162 -0
- sift_cli-1.0.0/test/test_answers.py +206 -0
- sift_cli-1.0.0/test/test_background.py +660 -0
- sift_cli-1.0.0/test/test_capture.py +290 -0
- sift_cli-1.0.0/test/test_cli.py +742 -0
- sift_cli-1.0.0/test/test_digest.py +108 -0
- sift_cli-1.0.0/test/test_distill.py +614 -0
- sift_cli-1.0.0/test/test_fallback.py +90 -0
- sift_cli-1.0.0/test/test_gc.py +187 -0
- sift_cli-1.0.0/test/test_hook.py +75 -0
- sift_cli-1.0.0/test/test_hook_setup.py +221 -0
- sift_cli-1.0.0/test/test_kaynak.py +209 -0
- sift_cli-1.0.0/test/test_korpus.py +271 -0
- sift_cli-1.0.0/test/test_lines.py +118 -0
- sift_cli-1.0.0/test/test_many.py +166 -0
- sift_cli-1.0.0/test/test_model.py +436 -0
- sift_cli-1.0.0/test/test_outline.py +247 -0
- sift_cli-1.0.0/test/test_package.py +95 -0
- sift_cli-1.0.0/test/test_privacy.py +180 -0
- sift_cli-1.0.0/test/test_records.py +149 -0
- sift_cli-1.0.0/test/test_server.py +330 -0
- sift_cli-1.0.0/test/test_setup.py +159 -0
- sift_cli-1.0.0/test/test_tokens.py +184 -0
- sift_cli-1.0.0/test/wire.py +102 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# The corpus is data, not source, and its bytes are the measurement.
|
|
2
|
+
#
|
|
3
|
+
# Git's newline conversion is helpful for code and destructive here. Checked out
|
|
4
|
+
# on Windows with the default `core.autocrlf`, every `\n` in a sample becomes
|
|
5
|
+
# `\r\n` -- and these samples exist precisely to hold line endings nobody else
|
|
6
|
+
# counts the same way: a NEL from an EBCDIC conversion, a form feed, a progress
|
|
7
|
+
# bar that redraws with carriage returns. Convert them and the corpus stops
|
|
8
|
+
# being a record of what tools actually print and becomes a record of what git
|
|
9
|
+
# thought it should print.
|
|
10
|
+
#
|
|
11
|
+
# Measured: on windows-latest the `cargo-de` sample arrived with CRLF and the
|
|
12
|
+
# test that proves `str.splitlines()` would miscount it found nothing to prove.
|
|
13
|
+
test/korpus/** -text
|
|
14
|
+
test/korpus-kaynak/** -text
|
|
15
|
+
|
|
16
|
+
# Source keeps LF in the repository whatever the checkout does, so a diff never
|
|
17
|
+
# turns into every line at once.
|
|
18
|
+
*.py text eol=lf
|
|
19
|
+
*.md text eol=lf
|
|
20
|
+
*.toml text eol=lf
|
|
21
|
+
*.yml text eol=lf
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
check:
|
|
14
|
+
# Windows is here on purpose. `capture.py` takes a different path there --
|
|
15
|
+
# no process groups, no selector on a pipe -- and a path nobody runs is a
|
|
16
|
+
# path nobody knows about.
|
|
17
|
+
runs-on: ${{ matrix.os }}
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
22
|
+
python: ["3.12", "3.13"]
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- uses: astral-sh/setup-uv@v5
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Install
|
|
32
|
+
run: uv sync --all-extras --dev
|
|
33
|
+
|
|
34
|
+
- name: Lint
|
|
35
|
+
run: uv run ruff check src test
|
|
36
|
+
|
|
37
|
+
- name: Test
|
|
38
|
+
# SIFT_LIVE is deliberately unset: the suite must not depend on a key or
|
|
39
|
+
# a network. The one test that calls NVIDIA skips itself without it.
|
|
40
|
+
run: uv run pytest -q --no-header
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Publishing happens on a tag and nowhere else. A push to main must never be
|
|
4
|
+
# able to put something on PyPI: a version that exists cannot be taken back, and
|
|
5
|
+
# the difference between "merged" and "released" is the whole point of a tag.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
# The same checks CI runs, run again here against the exact commit the tag
|
|
16
|
+
# points at. CI having been green on that commit earlier is not the same
|
|
17
|
+
# claim: a tag can be moved, and a release is the one place to be sure.
|
|
18
|
+
check:
|
|
19
|
+
runs-on: ${{ matrix.os }}
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
24
|
+
python: ["3.12", "3.13"]
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v5
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
- name: Install
|
|
31
|
+
run: uv sync --all-extras --dev
|
|
32
|
+
- name: Lint
|
|
33
|
+
run: uv run ruff check src test
|
|
34
|
+
- name: Test
|
|
35
|
+
run: uv run pytest -q --no-header
|
|
36
|
+
|
|
37
|
+
build:
|
|
38
|
+
needs: check
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- uses: astral-sh/setup-uv@v5
|
|
43
|
+
|
|
44
|
+
# The tag and the version in the package have to be the same thing. They
|
|
45
|
+
# are written in two places by two people at two times, so they are
|
|
46
|
+
# compared here rather than trusted -- a wheel whose name disagrees with
|
|
47
|
+
# its tag is a release nobody can reason about afterwards.
|
|
48
|
+
- name: The tag and the version agree
|
|
49
|
+
run: |
|
|
50
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
51
|
+
version=$(uv run python -c "import tomllib,pathlib;print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
52
|
+
echo "tag=$tag version=$version"
|
|
53
|
+
test "$tag" = "$version"
|
|
54
|
+
|
|
55
|
+
- name: Build
|
|
56
|
+
run: uv build
|
|
57
|
+
|
|
58
|
+
- uses: actions/upload-artifact@v4
|
|
59
|
+
with:
|
|
60
|
+
name: dist
|
|
61
|
+
path: dist/
|
|
62
|
+
|
|
63
|
+
publish:
|
|
64
|
+
needs: build
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
environment: pypi
|
|
67
|
+
# Trusted publishing: PyPI is told which repository and which workflow may
|
|
68
|
+
# publish this project, and GitHub signs a short-lived token to prove it.
|
|
69
|
+
# No API token is stored anywhere, so there is none to leak or to rotate.
|
|
70
|
+
permissions:
|
|
71
|
+
id-token: write
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/download-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
name: dist
|
|
76
|
+
path: dist/
|
|
77
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
sift_cli-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 slmnys
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sift_cli-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sift-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Runs your command, then gives the model only the lines that matter.
|
|
5
|
+
Project-URL: Homepage, https://github.com/slymnysr/sift
|
|
6
|
+
Author: slmnys
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: cli,context,distillation,llm,mcp
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Provides-Extra: mcp
|
|
18
|
+
Requires-Dist: mcp>=2; extra == 'mcp'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# sift
|
|
22
|
+
|
|
23
|
+
Runs your command, then gives the model only the lines that matter.
|
|
24
|
+
|
|
25
|
+
A test suite prints 4,000 lines and eleven of them are the failure. A build
|
|
26
|
+
prints a progress bar that redraws 900 times. An install lists every package it
|
|
27
|
+
touched. All of it lands in the conversation, and — this is the part that costs
|
|
28
|
+
— it is re-sent in full on every turn that follows.
|
|
29
|
+
|
|
30
|
+
`sift` runs the command itself, keeps every byte on disk, and hands the model a
|
|
31
|
+
view: the failures, the summary, the lines a reader would actually stop on. The
|
|
32
|
+
rest is marked, not deleted.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$ sift run -- pytest
|
|
36
|
+
FAILED test/test_auth.py::test_expired_token - assert 401 == 200
|
|
37
|
+
...
|
|
38
|
+
─ 3,914 lines not shown · sift peek 9f2c41ab for any of them ─
|
|
39
|
+
=== 1 failed, 212 passed in 18.4s ===
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A file you did not produce — a log, a saved CI transcript, a crash dump — gets
|
|
43
|
+
the same treatment without being run:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
$ sift digest ci-run-8812.log
|
|
47
|
+
2026-09-02T04:11:07 Building 214 targets
|
|
48
|
+
─ 38,904 lines not shown · sift peek ci-run-8812.log for any of them ─
|
|
49
|
+
ERROR: //src/parser:parse_test failed in 4.1s
|
|
50
|
+
─ 1,022 lines not shown · sift peek ci-run-8812.log for any of them ─
|
|
51
|
+
FAILED: 1 of 214 targets
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A list is not read in lines. A JSON array written for a machine often has no
|
|
55
|
+
newlines at all, and there is nothing in a line of one worth choosing, so the
|
|
56
|
+
record becomes the unit and the same rules hold:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
$ sift digest export.json
|
|
60
|
+
{"id": 3, "status": "failed", "error": "connection refused"}
|
|
61
|
+
─ 412 records not shown · sift peek export.json for the text they came from ─
|
|
62
|
+
{"id": 416, "status": "ok"}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The same question, asked about source code instead, is a table of
|
|
66
|
+
contents:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
$ sift outline src/parser.rs
|
|
70
|
+
pub struct Parser {
|
|
71
|
+
pub fn parse(input: &str) -> Result<Ast, Error> {
|
|
72
|
+
─ 34 lines not shown · sift peek src/parser.rs for any of them ─
|
|
73
|
+
impl Iterator for Tokens {
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
And a command that does not end — a dev server, a log tail, a build you want to
|
|
77
|
+
keep working during — is started and then read a slice at a time:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
$ sift run --background -- cargo build --release
|
|
81
|
+
9f2c41ab
|
|
82
|
+
$ sift follow 9f2c41ab
|
|
83
|
+
warning: unused import: `std::fmt`
|
|
84
|
+
─ 212 lines not shown · sift peek 9f2c41ab for any of them ─
|
|
85
|
+
error[E0308]: mismatched types
|
|
86
|
+
sift 9f2c41ab · running · new lines 1-247 · 2 shown · nemotron
|
|
87
|
+
$ sift follow 9f2c41ab # only what has arrived since
|
|
88
|
+
$ sift follow --all --wait 30 # every run at once, holding for something new
|
|
89
|
+
$ sift stop 9f2c41ab # ends it, and everything it started
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Nothing is shown twice, and the numbers are the run's own: line 247 stays line
|
|
93
|
+
247 in `sift peek` for as long as the capture exists.
|
|
94
|
+
|
|
95
|
+
## What decides
|
|
96
|
+
|
|
97
|
+
A free model does. `sift` sends it the numbered lines and asks one question:
|
|
98
|
+
*which numbers matter?* It answers with numbers, and nothing else it says is
|
|
99
|
+
used — the text you read above is printed from the local capture, byte for byte.
|
|
100
|
+
|
|
101
|
+
That is the whole trick, and it is why this is not a summariser. A summariser
|
|
102
|
+
can be wrong about what a line said. `sift` cannot be: it never writes a line,
|
|
103
|
+
it only chooses one.
|
|
104
|
+
|
|
105
|
+
It also means language coverage is not a list. A model reads Turkish, Japanese,
|
|
106
|
+
Arabic and Hindi; it reads Rust, COBOL, Mojo and a language released last week.
|
|
107
|
+
Nothing here enumerates them, so nothing here can be missing one.
|
|
108
|
+
|
|
109
|
+
## Three rules
|
|
110
|
+
|
|
111
|
+
**Nothing shown is invented.** The judge returns line numbers. Text always comes
|
|
112
|
+
from the local file.
|
|
113
|
+
|
|
114
|
+
**Nothing is thrown away.** `sift peek <handle>` returns the raw capture,
|
|
115
|
+
unchanged. Every gap in a view says how many lines it covers.
|
|
116
|
+
|
|
117
|
+
**Nothing can break your command.** No API key, no network, an overloaded
|
|
118
|
+
endpoint, a nonsense reply, a bug in the distiller, or a decision that nothing
|
|
119
|
+
may leave this machine — every one of these falls back to rules that need none
|
|
120
|
+
of them. The command still runs; you still get its output; you get its exit
|
|
121
|
+
code.
|
|
122
|
+
|
|
123
|
+
## What leaves the machine
|
|
124
|
+
|
|
125
|
+
One thing: the text of a question. Before it is sent, anything credential-shaped
|
|
126
|
+
is replaced — tokens with a known prefix, JWTs, authorization headers, passwords
|
|
127
|
+
in connection strings, the body of a PEM block.
|
|
128
|
+
|
|
129
|
+
That costs you nothing to read. The model is only ever asked for line numbers,
|
|
130
|
+
and the lines are printed from your own file, so **a line masked on the way out
|
|
131
|
+
is still shown to you in full**.
|
|
132
|
+
|
|
133
|
+
Three switches:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
SIFT_NO_MODEL=1 # never send anything; use the deterministic view
|
|
137
|
+
SIFT_MASK=0 # send unmasked
|
|
138
|
+
SIFT_CACHE=0 # ask again, even about text already answered
|
|
139
|
+
SIFT_EFFORT=low # let the model think less, and lose some of what matters
|
|
140
|
+
SIFT_PATIENCE=0 # one quick pass only; do not wait out a busy hour
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Masking is not complete and does not claim to be: a bare secret shaped like
|
|
144
|
+
nothing in particular gets through. `SIFT_NO_MODEL` is the one that guarantees.
|
|
145
|
+
|
|
146
|
+
The fourth is a measured trade and is off by default. Asked which lines matter
|
|
147
|
+
in a 404-line build, the model writes about 900 tokens of reasoning to produce a
|
|
148
|
+
twelve-token answer, and you wait 15 seconds for it. At `SIFT_EFFORT=low` the
|
|
149
|
+
same question takes 2.4 seconds — and over the corpus it loses 7.8% of the lines
|
|
150
|
+
a reader could not do without, in exactly the places this tool exists for: a
|
|
151
|
+
mainframe job's return code, a crash loop's diagnosis. Speed is available; it is
|
|
152
|
+
not the default, and the price is written down.
|
|
153
|
+
|
|
154
|
+
The third is about not paying twice. An answer already given for exactly these
|
|
155
|
+
bytes and exactly this question is used again instead of bought again — and what
|
|
156
|
+
is kept is the numbers, never the view, so the text is still rendered from your
|
|
157
|
+
own file and the gap still names your own capture. A view that cost no request
|
|
158
|
+
says `(remembered)` where it would otherwise name the model.
|
|
159
|
+
|
|
160
|
+
Captured bytes never leave `$SIFT_HOME` (`~/.cache/sift` by default). Nothing is
|
|
161
|
+
uploaded, nothing is logged elsewhere, and removing a capture directory removes
|
|
162
|
+
everything that was ever kept about it.
|
|
163
|
+
|
|
164
|
+
They also never go away on their own. Nothing here sweeps, expires or tidies in
|
|
165
|
+
the background: `sift gc [DAYS]` is the only thing that deletes a capture, and
|
|
166
|
+
it deletes when you type it and not before. What it leaves is one line per
|
|
167
|
+
handle — when it went and how big it was, never the command — so that a gap
|
|
168
|
+
marker read a fortnight later gets *"removed on the 8th"* instead of the answer
|
|
169
|
+
it would give for a handle you made up.
|
|
170
|
+
|
|
171
|
+
## Commands
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
sift run [--timeout SECONDS] [--shell] [--background] [--cwd DIR]
|
|
175
|
+
[--budget LINES] [--keep PATTERN] [--] COMMAND...
|
|
176
|
+
sift follow [HANDLE] [--all] [--wait N]
|
|
177
|
+
what a background run has said since you last looked
|
|
178
|
+
sift stop [HANDLE] end it, and everything it started
|
|
179
|
+
sift outline PATH what a file declares, without its bodies
|
|
180
|
+
sift digest PATH... what is in files somebody else produced
|
|
181
|
+
sift peek HANDLE|PATH [FIRST] [LAST]
|
|
182
|
+
sift hook answer one shell-command event on stdin
|
|
183
|
+
sift tools which dense tools this machine has
|
|
184
|
+
sift tool NAME [ARGS...] run one of them, distilled
|
|
185
|
+
sift memory [TERM] what has been run here before, and how it went
|
|
186
|
+
sift list [COUNT] what is running, and what has been run
|
|
187
|
+
sift stats [COUNT] what the shortening cost, and what it saved
|
|
188
|
+
sift gc [DAYS] remove captures older than that, and say what went
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Several paths given to `digest` are asked about at the same time, and `--all`
|
|
192
|
+
follows every running command in one go. Both are the same idea: the waiting is
|
|
193
|
+
the cost, so do it once. `--wait N` holds until a run actually says something
|
|
194
|
+
instead of answering that nothing has happened yet.
|
|
195
|
+
|
|
196
|
+
`sift hook` is the way to catch the shell commands a client runs on its own,
|
|
197
|
+
without a proxy: point the client's pre-tool hook at it and every shell command
|
|
198
|
+
comes here first. **Everything is routed** — nothing here guesses which commands
|
|
199
|
+
are worth catching, because a list of those is a list of tools in disguise and
|
|
200
|
+
how much a command prints is not knowable before it runs. Routing everything
|
|
201
|
+
costs nothing: a view of twelve lines is twelve lines. It fails open, so a bug
|
|
202
|
+
in it leaves your shell exactly as it was, and `SIFT_HOOK=0` switches it off.
|
|
203
|
+
|
|
204
|
+
It is the part of this that pays most and the only part you have to turn on, so
|
|
205
|
+
it offers itself rather than waiting to be found:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
sift hook --install # says what it gives and what it costs, then asks
|
|
209
|
+
sift hook --uninstall # and takes it back out
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
It writes one line into your client's settings, keeps a copy of the file as it
|
|
213
|
+
was, and touches nothing else that is in there. `sift run` mentions it once,
|
|
214
|
+
ever, and then stops.
|
|
215
|
+
|
|
216
|
+
`sift tool` runs one of three programs that answer a question *without opening
|
|
217
|
+
the file* — `sg` (ast-grep) for structural search, `diff` (difftastic) for a diff
|
|
218
|
+
that can tell a reindent from a change, `loc` (scc) for the size of a tree. Their
|
|
219
|
+
output is large by nature, which is exactly why they belong here. **No binaries
|
|
220
|
+
ship with this package**: `sift tools` says which of them this machine has and
|
|
221
|
+
what each is called, and installing one stays your decision.
|
|
222
|
+
|
|
223
|
+
`sift memory` asks no model at all. The question is counting — how often, how it
|
|
224
|
+
went, which command has never once worked here — and a model asked to count is
|
|
225
|
+
slower, costs a request and is sometimes wrong. The model decides what cannot be
|
|
226
|
+
computed, and nothing else.
|
|
227
|
+
|
|
228
|
+
`sift stats` says what the shortening saved and what it cost, and keeps those
|
|
229
|
+
two apart: the share is this tool's own arithmetic over bytes it holds, so it is
|
|
230
|
+
exact, while the cost is the endpoint's count of its own tokens, so it is
|
|
231
|
+
measured. A run the endpoint did not count is left out and said so, rather than
|
|
232
|
+
filled in with bytes divided by four.
|
|
233
|
+
|
|
234
|
+
`--keep PATTERN` shows every line matching it whatever else was chosen and
|
|
235
|
+
whatever the budget says. It is your pattern, not one this tool guessed at —
|
|
236
|
+
the only place a pattern decides anything here, and it decides nothing until
|
|
237
|
+
you type it. `--budget LINES` is the ceiling for one view.
|
|
238
|
+
|
|
239
|
+
## In an agent
|
|
240
|
+
|
|
241
|
+
The same answers are available over MCP, and that is where they pay most: a tool
|
|
242
|
+
result is re-sent on every turn that follows it, so a build log kept out of a
|
|
243
|
+
transcript goes on staying out of it.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
pip install "sift-cli[mcp]"
|
|
247
|
+
export SIFT_API_KEY=nvapi-... # yours; see Installing
|
|
248
|
+
claude mcp add sift -- sift-mcp
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Without a key the server starts and every tool that would need a model declines
|
|
252
|
+
with an explanation, so the agent falls back to its own shell rather than being
|
|
253
|
+
handed a worse answer it cannot tell apart from a good one.
|
|
254
|
+
|
|
255
|
+
Any client that speaks stdio will do — the command is `sift-mcp`. It offers
|
|
256
|
+
`run`, `follow`, `outline`, `digest`, `digest_many`, `tool` and `peek`. `list`
|
|
257
|
+
and `stats` are deliberately not offered: they would hand a model every command lately run on this machine,
|
|
258
|
+
including the ones it never asked about, and the person at a terminal already
|
|
259
|
+
has that access while a model connecting over a socket does not.
|
|
260
|
+
|
|
261
|
+
Because a client never sees stderr, the last line of every result says what you
|
|
262
|
+
are looking at: which handle, how the command ended, and whether a model chose
|
|
263
|
+
the lines or none could be reached.
|
|
264
|
+
|
|
265
|
+
## Installing
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
pip install sift-cli # the command line, no dependencies at all
|
|
269
|
+
pip install "sift-cli[mcp]" # and the MCP server
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The package is `sift-cli` and the commands are `sift` and `sift-mcp`. The names
|
|
273
|
+
differ because `sift-mcp` on PyPI belongs to somebody else's project — an
|
|
274
|
+
unrelated MCP server about authorising agent actions. Nothing here is theirs and
|
|
275
|
+
nothing there is this.
|
|
276
|
+
|
|
277
|
+
Python 3.12 or newer, and no dependencies for the command line.
|
|
278
|
+
|
|
279
|
+
### You need your own key
|
|
280
|
+
|
|
281
|
+
**This is not optional and it is not shipped.** `sift` asks a free NVIDIA model
|
|
282
|
+
which lines matter, and that key has to be yours — one cannot be bundled and one
|
|
283
|
+
cannot be shared. It is free, and it takes a minute:
|
|
284
|
+
|
|
285
|
+
1. Get a key at **<https://build.nvidia.com>**
|
|
286
|
+
2. Put it anywhere `sift` looks:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
export SIFT_API_KEY=nvapi-... # or NVIDIA_API_KEY
|
|
290
|
+
# or, once and for good:
|
|
291
|
+
mkdir -p ~/.config/nvidia && echo 'nvapi-...' > ~/.config/nvidia/api_key
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Without it the two callers are answered differently, on purpose:
|
|
295
|
+
|
|
296
|
+
- **At a terminal** everything still runs — the command, the bytes, the exit
|
|
297
|
+
code, the third rule — and a loud banner says no model chose these lines and
|
|
298
|
+
that you are looking at the ends of the output.
|
|
299
|
+
- **Over MCP** the tools decline and say why, and tell the agent to use its own
|
|
300
|
+
shell instead. A person can see a degraded view and judge it; a model is handed
|
|
301
|
+
a short text with a footer it has no reason to distrust, and quietly worse is
|
|
302
|
+
the one thing this will not do to a reader who cannot check.
|
|
303
|
+
|
|
304
|
+
If you *want* to run without a model, say so with `SIFT_NO_MODEL=1`. That is a
|
|
305
|
+
decision rather than an oversight, everything works, and nothing lectures you.
|
|
306
|
+
|
|
307
|
+
## How it was built
|
|
308
|
+
|
|
309
|
+
Twenty-four phases, each one closed before the next began, each with a note in
|
|
310
|
+
`notlar/` saying what was decided and what it cost. `notlar/00-PLAN.md` is the
|
|
311
|
+
arc, including the things that were deliberately not built and why.
|
|
312
|
+
|
|
313
|
+
The tests are in `test/`. Beside them is `test/mutations.py`, which breaks each
|
|
314
|
+
rule the code follows — 162 of them, one at a time — and checks that the suite
|
|
315
|
+
notices. A green suite says the tests did not object to *this* version of the
|
|
316
|
+
code, not that they would object to a worse one.
|
|
317
|
+
|
|
318
|
+
It also says what happens when that battery is interrupted, because it was: a
|
|
319
|
+
break left on disk survived every ordinary test run and took the machine down
|
|
320
|
+
six times before anybody looked. `test/conftest.py` repairs one now, and the
|
|
321
|
+
rule it was breaking is kept twice over, so that no single edit anywhere can
|
|
322
|
+
turn `sift stop` into a signal to everything you own. That is `notlar/19`.
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
MIT.
|