sift Docs / sift 文档
English
sift is a cost-controlled open-source project auditor for dependency and repository intake. It is built around a tiered funnel: static AST dehydration first, deterministic local findings second, and large-model convergence only after the input has been reduced.
The primary product surface is a local CLI that can inspect a project path or safely fetch a GitHub repository before setup, install, build, or agent execution:
sift ./repo --agent-gate
sift ./repo --benchmark
sift github owner/repo --ref main --agent-gate
The deterministic agent gate emits a stable pre-run verdict:
VERDICT: ACCEPT | CAUTION | REJECT | INCOMPLETE
SAFE_TO_AGENT_RUN: yes | no
中文
sift 是一个面向依赖引入和仓库预审的可控成本开源项目审计器。它先做静态 AST 脱水,再生成确定性本地发现,最后只把压缩后的输入交给大模型收敛。
主要入口是本地 CLI:可以审本地项目,也可以在 setup、install、build 或 agent 执行之前安全获取 GitHub 仓库并做门禁判断。
sift ./repo --agent-gate
sift ./repo --benchmark
sift github owner/repo --ref main --agent-gate
确定性 agent gate 输出稳定预运行 verdict:
VERDICT: ACCEPT | CAUTION | REJECT | INCOMPLETE
SAFE_TO_AGENT_RUN: yes | no
sift
English | 中文
Cost-controlled open-source project auditor: tiered funnel + compute mismatch + ReACT scheduling. Before adopting a dependency, get a file/line-level risk ledger without force-feeding tens of thousands of lines into a frontier model.
- Grunt work (structure extraction / deterministic coarse filtering) -> tree-sitter + local rules
- Logic convergence -> frontier large model, orchestrated by a ReACT state machine over deterministic findings
- Single binary, zero-config; audits a whole project or a single module; sift must pass its internal release gates
See Roadmap for full design.
Usage
sift ./repo --scan-only # scan layer only (no key needed)
sift ./repo --agent-gate # deterministic pre-run gate (no key needed)
sift ./repo --agent-gate --format json
sift ./repo --benchmark # scan/model budget telemetry JSON (no key needed)
sift github owner/repo # safe GitHub intake, defaults to --agent-gate
sift github owner/repo --ref main --scan-only
sift eval-corpus # run the checked-in repo-intake precision corpus
sift ./repo --module src # audit a submodule
SIFT_API_KEY=<KEY> sift ./repo # full pipeline
sift ./repo --api-key-file ~/.sift/key
sift ./repo --report-language zh # request a Simplified Chinese Markdown report
sift ./repo --debug # print extra diagnostics to stderr
sift doctor # check config, key_env, and endpoint/key mismatches
--agent-gate is a local, deterministic repo-intake gate for agents and wrapper scripts. It writes only this stable contract to stdout:
VERDICT: ACCEPT | CAUTION | REJECT | INCOMPLETE
WHY:
- <top evidence>
BLOCKERS:
- <file:line evidence or coverage blocker>
SAFE_TO_AGENT_RUN: yes | no
The command exits 0 only when SAFE_TO_AGENT_RUN: yes; CAUTION, REJECT, and INCOMPLETE exit non-zero so callers can stop before setup, install, build, or run steps.
Use --format json with --agent-gate for automation. The JSON contract contains verdict, safe_to_agent_run, exit_reason, coverage, findings, blockers, artifact inventory, truncation details, and policy actions.
The deterministic supply-chain layer currently flags npm install lifecycle scripts, manifest/lockfile reproducibility gaps, git/path/http dependency sources, Rust build.rs command boundaries, shell/Dockerfile download-execute patterns, base64 decode-to-execute flows, GitHub Actions permission/trigger risk, secrets coupled to shell execution, unpinned GitHub Actions, Dockerfile root/remote repository patterns, and suspicious binary/archive artifacts.
sift github accepts owner/repo or https://github.com/owner/repo, fetches a temporary checkout with git, resolves the commit SHA, then runs the local scan/gate/benchmark pipeline against that checkout. It never runs repository code, package manager commands, build scripts, hooks, install commands, or submodules. The checkout is inspected for file/byte limits, .gitmodules, and Git LFS indicators before scanning. Temporary checkouts are removed by default; use --keep-checkout only when you need to inspect the fetched tree.
Project-local policy lives in sift-policy.toml. It supports max_candidate_files, [[allowlist]], [[denylist]], and [[severity_override]] entries keyed by path, rule, severity, and reason; applied policy decisions are shown in text and JSON gate output.
The current full-audit path does not call small-model Map by default. It converges from the deterministic ledger with the configured large model, while the small-model Map implementation remains an experimental diagnostic path.
Supported Languages
The scan layer currently dehydrates Rust, Python, Go, JavaScript, TypeScript/TSX, HTML, CSS, Zig, Bash-compatible shell files (.sh, .bash, .zsh), Dart, Kotlin, Java, C/C++, C#, PHP, Swift, Ruby, SQL, Dockerfile/Containerfile, YAML, HCL/Terraform, Vue, Svelte, package.json, common package manifests/lockfiles, Makefile, and Markdown install snippets.
Install
make ci
make install
More
sift Project Profile & Roadmap
English | 中文
North star + guardrails + phased build boundaries. Defines what it should become / what it must never do / what each phase ships / when internal gates apply. Name: sift (CLI is
sift). Language: Rust.
Overview
A cost-controlled open-source project auditor. Before adopting a library, get a file/line-level risk ledger without trial-running it or force-feeding tens of thousands of lines into a frontier model.
Core: tiered funnel + compute mismatch + ReACT scheduling. Grunt work (structure extraction, coarse filtering) currently goes to zero-cost static parsing and deterministic local rules; heavy logic convergence goes to a frontier model; a ReACT state machine orchestrates the Reduce pass over deterministic findings. Ships as a single binary, zero-config, auditing a whole project or a single module. sift itself must pass its internal release gates.
- Architecture
CLI key file / ENV / ~/.sift/config.toml ──(fallback resolve, exit if no key)
▼
Scan ignore::Walk → bounded channel (consume & drop) [P0 ✓]
▼
Tier-0 tree-sitter dehydrate (sig/import/calls) → JSON → drop AST [P1 ✓]
│ cross-boundary refs marked [EXTERNAL_BLACKBOX]
▼
Models multi-model registry · per-call hard timeout · breaker+backoff [P2 ✓]
▼
ReACT scheduler (tool protocol, deterministic findings, retry≤N) [P3 ✓]
│ └─ large model (Reduce convergence) ─────────┘
▼
Report stdout Markdown risk list (line/call-chain) [P4 started]
▼
Internal gate scored source checks + release evidence [P5/P6]
Project Profile (target state)
- Zero-friction cold start.
sift ./repo --scan-onlyjust runs; missing~/.sift/config.tomlis created with non-secret defaults; no interactive prompts; exits with an injection hint if the key is missing. - Cost-controlled & budgetable. The deterministic baseline is local; the large model only sees the dehydrated skeleton when full audit is requested.
- Model orchestration. A ReACT state machine chains deterministic findings and large-model convergence; skills are compile-time local functions.
- Multi-model + concurrency. Multiple endpoints are configurable; scan/model concurrency remains bounded and observable.
- Never grind blindly. Every external call has a hard timeout; repeated failures trip the breaker; on trip, back off / degrade or emit a partial report — never hang.
- Engineering-grade by default. A clean-looking but incomplete audit is a defect. Any skipped input, truncation, fallback, partial model result, or invalid config must be visible and testable.
- Stable machine contracts. Scan JSONL, final Markdown, diagnostics, and generated reports have separate channels. Downstream scripts must be able to consume stdout without guessing whether it contains mixed formats.
- Memory decoupled from scale. Stream and drop; resident memory stays low.
- Internally gated. The project must pass its own maintainer-only release gates; modular, TDD-guarded, clear boundaries.
- Priority on conflict: robust > usable report > cheap > fast > small.
Non-goals (hard rules)
- No vector DB / embeddings / RAG. For one-shot low-frequency audits, index upkeep costs more than prompt assembly; plain-text pipeline, read once and discard.
- No runtime plugins / dynamic skills. Skills = compile-time enum + match local fns; extend by editing and recompiling.
- No service / Web UI / multi-tenant. One-shot CLI only.
- No process panics. Dirty data dropped & logged; hallucinations/bad JSON tripped; Result/Option throughout, no unwrap/expect.
- No unbounded blocking. Any subprocess/network/model call must have a deadline.
- Module audit must not balloon to global. Cross-boundary refs marked and handed to the large model; no chasing.
- No trial-run instead of audit. Value is the pre-adoption verdict.
- No scaffold masquerading as product. Placeholders are allowed only inside explicitly unfinished phases; they must not produce reports that look production-complete.
- No silent fallback. Invalid config, truncated seed, skipped files, missing model roles, and degraded model paths must fail loudly or be shown in the report.
Code Map
Every
src/*.rscarries unit tests; new subsystem ⇒ tests built alongside (TDD). Module boundaries are responsibility boundaries.
src/main.rs entry wiring: parse→Config→schedule→report→exit code
src/config.rs fallback resolve, multi-model config [P0✓→P2]
src/scanner.rs Walk + bounded channel [P0✓]
src/extract.rs tree-sitter dehydrate → AstSummary [P1✓]
src/query.rs stateless evidence query (rescan + regex) [P1✓]
src/model.rs multi-model registry/client trait/timeout [P2✓]
src/react.rs ReACT state machine + skill enum/match [P3 ✓]
src/skills.rs local skill fns (coarse filter / reduce) [P3 ✓→P4]
src/report.rs Markdown risk-list renderer [P4]
src/audit.rs internal gate dimension scoring [P5]
Multi-model & concurrency (config schema)
concurrency = 8 # scan/model concurrency cap
[[model]]
role = "small" # reserved for experimental Map diagnostics
endpoint = "..."
key_env = "SIFT_SMALL_KEY"
timeout_ms = 8000
max_retries = 1
[[model]]
role = "large"
endpoint = "..."
key_env = "SIFT_API_KEY"
timeout_ms = 60000
max_retries = 1
Resolve order: CLI key file > ENV > toml > default; no large key ⇒ exit. The current full-audit path does not call small-role models by default; missing small models do not change the deterministic-ledger Reduce path.
The default user config path is ~/.sift/config.toml; it is created on first run from config.example.toml-equivalent defaults and must not contain raw secrets.
Timeout, breaker & recovery (never grind)
- Per-call deadline: time out and drop; no unbounded wait.
- Breaker counter: consecutive failures / bad JSON / unknown skill ≥ N ⇒ break, stop I/O.
- Backoff recovery: transient errors retry with exponential backoff to budget; non-transient degrade (small→AST, large→partial).
- Budget cap: global token/time ceiling; on hit, force-converge a
[TRUNCATED]report.
Engineering Contract
- A phase marked done must have behavior-level proof, not only type-level plumbing or happy-path unit tests.
- Full audit stdout is the final report stream.
--scan-onlyis the JSONL stream. Diagnostics stay off stdout. - Report coverage must disclose how much input was scanned, dehydrated, sent to models, skipped, or truncated.
- Config files are part of the trust boundary. Missing user config is auto-created from safe defaults; if a config file exists but is invalid, the process fails instead of reverting to defaults.
- Program source under
src/is English-only for runtime text, prompts, and comments; bilingual documentation stays in docs.
Phased Roadmap
Each phase: feature list / boundaries / internal gate. All-green gate ⇒ next phase; next steps set by gate evidence. For a point-in-time done/partial/pending snapshot of every item below against real evidence, see CHECKLIST.md.
P0 Scaffold — done ✓
Features: clap fallback resolve, bounded scanner, exit on missing key, minimal wiring. Bounds: no net/parse/tree. Gate: cargo build green, 0 unwrap, --scan-only scans, missing key exit1.
P1 Tier-0 AST dehydrate — done ✓
Features: tree-sitter Rust/Python/Go/JavaScript/TypeScript/HTML/CSS/Zig/Bash/Dart/Kotlin/Java/C/C++/C#/PHP/Swift/Ruby/SQL/Dockerfile/YAML/HCL/Vue/Svelte, extract sig/import/calls → flat AstSummary JSON; cross-boundary [EXTERNAL_BLACKBOX]; drop AST. Bounds: omit bodies/comments; tolerate malformed syntax without panicking and account for incomplete coverage in downstream reporting. Gate: 100MB repo memory stable & no crash; extract.rs tests cover typical+broken.
P2 Model layer (multi-model + breaker) — done ✓
Features: ModelClient trait, registry, role routing; per-call timeout, breaker, backoff. Bounds: no cache/persist; keys env/file only, never logged. Gate: timeout/bad-response simulated, breaker trips; no plaintext keys.
P3 ReACT scheduler — done ✓
Features: enum state machine, initial tool protocol prompt, large model emits <TOOL_CALL>, match-routes local skills via $SEED; retry≤N then partial. Bounds: compile-time skills, no dynamic load. Gate: bad JSON/unknown skill/N errors all trip; react.rs tested.
P4 Deterministic Reduce+report
Features: deterministic AST coarse ledger, Markdown renderer, real [[model]] TOML parsing, explicit input coverage, stable JSON agent-gate output, policy controls, artifact inventory, eval corpus, and clean stdout boundaries. Bounds: module mode slices root only; truncation and degraded model paths must be visible. Gate: hits seeded risks; module/project don’t bleed; full-audit stdout contains only the report; invalid config fails; fake-endpoint full audit smoke proves the user-facing path.
P5 Internal Quality Gate — done ✓
Features: audit.rs scores trimmed dimensions and writes maintainer-only reports to reports/ (gitignored). Gate: no FAIL/WARN for hard rules, including no broad dead-code allows, no Chinese source strings/comments, clean report stream boundary, and visible seed truncation.
P6 Release hardening
Features: ReleaseSafe single binary, Makefile install path, macOS Homebrew tap publishing, more grammars, stable JSON. Gate: single-file dist, internal gates pass, docs↔code consistent, brew install jamiesun/tap/sift backed by release checksums.
Definition of done
- Zero-config run;
~/.sift/config.tomlauto-created; missing key exits with hint; never hangs. - 100MB repo stable memory; no crash on dirty input.
- Report cites line numbers + cross-module deps + concurrency/resource risk.
- Report declares input coverage and truncation state; incomplete coverage never looks like a complete verdict.
- Every external call times out; failures trip to partial, never grind.
- One binary audits project and
--modulewithout bleed. - Internal release gates have no FAIL or hard-rule WARN.
Suggestions (not rules): rayon, exact timeout/size/latency numbers per benchmark. Hard rules: single binary, fallback resolve, bounded channel, hard-timeout breaker, no unwrap, TDD, bilingual docs (EN default, ZH twin), passing internal gates.
sift Acceptance Checklist
English | 中文
A point-in-time acceptance snapshot of every feature and gate promised in ROADMAP.md, scoped strictly to its phases (P0–P6) and its non-goals. Nothing outside that boundary is graded here — see AGENT.md for the hard rules this checklist assumes. Line numbers are as of the snapshot commit below and may drift; prefer the named function/test when they disagree.
Snapshot
| Commit | f9a374b — “fix agent gate issue regressions (#39)” (4 commits past tag v0.2.0), plus uncommitted follow-up fixes from this session (see Self-audit dogfood check) |
| Date assessed | 2026-07-01 |
cargo build | ✅ pass |
make ci (fmt-check + test + clippy -D warnings + internal-gate) | ✅ pass, exit 0 |
| Tests | ✅ 127 passed, 0 failed (119 unit tests in src/** + 8 black-box tests in tests/*.rs) |
Internal quality gate (reports/internal-gate.md) | ✅ 13/13 checks PASS, 0 WARN, 0 FAIL |
Legend
| Mark | Meaning |
|---|---|
| ✅ Done | Shipped with behavior-level evidence (a passing test and/or a real run performed for this snapshot) — not only type-level plumbing or a happy-path unit test. |
| 🟡 Partial | Shipped but capped in scope, intentionally inactive scaffolding, or missing one specific proof point noted in the row. |
| ⏳ Pending | No fixed target yet, awaiting a maintainer decision, or explicitly open-ended in ROADMAP.md. |
| ⬜ Not done | Promised but not implemented, or no automated evidence exists at all. |
Mapped to the three buckets this checklist is meant to answer: 完成 = ✅, 待定 = ⏳, 未完成 = 🟡 / ⬜.
Type column: F = Feature bullet, G = Gate/acceptance-criterion bullet, B = Boundary constraint, taken verbatim from each phase’s ROADMAP.md text.
P0 — Scaffold
ROADMAP status: done ✓
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | Fallback key resolution: CLI key file › ENV › project .env › ~/.sift/config.toml › default | ✅ Done | src/config.rs::Config::resolve; tests parses_project_env_file, explicit_api_key_file_must_be_readable_and_non_empty |
| 2 | F | Bounded-channel scanner (walk → bounded channel, consume & drop) | ✅ Done | src/scanner.rs (crossbeam_channel::bounded::<PathBuf>(1024)); test scan_skips_ignored_dirs_and_large_files |
| 3 | F | Minimal end-to-end wiring: parse → Config → schedule → report → exit code | ✅ Done | src/main.rs::main |
| 4 | G | cargo build green | ✅ Done | Verified this session (make ci exit 0) |
| 5 | G | Zero unwrap()/expect() in src/ | ✅ Done | reports/internal-gate.md: “No direct unwrap/expect in src” — PASS |
| 6 | G | --scan-only scans without any model key | ✅ Done | tests/benchmark_mode.rs::scan_only_stdout_remains_jsonl_not_benchmark_json |
| 7 | G | Missing large-model key exits before scheduling a full audit | 🟡 Partial | Code path exists (src/main.rs:83-86, config::missing_large_key_hint), unit-tested for message content only (missing_key_hint_uses_parseable_model_block); no black-box test spawns the real binary with no key on a non---scan-only/--agent-gate/--benchmark path to assert the process exit code |
Phase verdict: ✅ Done, with one test-coverage gap (#7).
P1 — Tier-0 AST dehydrate
ROADMAP status: done ✓
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | tree-sitter grammar coverage: Rust, Python, Go, JavaScript, TypeScript/TSX, HTML, CSS, Zig, Bash, Dart, Kotlin, Java, C, C++, C#, PHP, Swift, Ruby, SQL, Dockerfile, YAML, HCL, Vue, Svelte (23 grammars) | ✅ Done | src/extract.rs::Lang, Cargo.toml (23 tree-sitter-* deps); one test per language family (rust_extracts_sig_import_call, go_extracts_import_signature_and_call, typescript_and_tsx_extract_symbols, dart_kotlin_java_extract_symbols, c_cpp_csharp_extract_symbols, php_swift_ruby_extract_symbols, sql_docker_yaml_hcl_vue_svelte_extract_structure, …) |
| 2 | F | Structural extraction for package.json, other manifests/lockfiles, Makefile, Markdown install snippets | ✅ Done | dehydrate_package_json, dehydrate_manifest, dehydrate_makefile, dehydrate_markdown; tests package_json_extracts_lifecycle_scripts, makefile_extracts_targets_and_recipe_lines, markdown_extracts_dangerous_install_commands_only |
| 3 | F | Extract signatures/imports/calls into a flat AstSummary JSON record | ✅ Done | struct AstSummary, fn dehydrate |
| 4 | F | Cross-boundary references marked [EXTERNAL_BLACKBOX] | ✅ Done | fn is_external; test intra_crate_rust_imports_are_not_external confirms it does not over-flag crate::/super:: |
| 5 | B | Bodies/comments omitted; AST dropped immediately after dehydration (never retained) | ✅ Done | By construction: dehydrate() returns only the flat summary; no tree_sitter::Tree is stored anywhere in main.rs |
| 6 | B | Malformed syntax tolerated without panicking | ✅ Done | Test broken_input_no_panic |
| 7 | G | 100 MB repo: stable memory, no crash | ⬜ Not done | No committed large-repo/stress fixture or CI job of this scale exists. --benchmark can report resident memory, but only on Linux (resident_memory_metric in src/main.rs is #[cfg(target_os = "linux")]); on macOS it always reports "unavailable", and CI’s macos-latest job never exercises this metric |
| 8 | G | extract.rs tests cover typical + broken input | ✅ Done | 17 test functions in extract.rs::tests, including malformed-input and unknown-extension cases |
Phase verdict: 🟡 Mostly done. The only unverified gate is the 100 MB memory-stability claim, and macOS (a supported CI/release target) currently has no working resident-memory metric at all.
P2 — Model layer (multi-model + breaker)
ROADMAP status: done ✓
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | ModelClient + Transport trait abstraction | ✅ Done | src/model.rs::ModelClient, trait Transport |
| 2 | F | Registry with small/large role routing | ✅ Done | struct Registry { small, large }, enum Role |
| 3 | F | Per-call hard timeout | ✅ Done | UreqTransport wires .timeout(timeout); internal-gate PASS “Model transport has a hard timeout” |
| 4 | F | Breaker on consecutive failures | ✅ Done | struct Breaker; tests timeouts_trip_breaker, bad_status_not_retried_exhausts |
| 5 | F | Exponential backoff recovery | ✅ Done | fn backoff, used from ModelClient::complete |
| 6 | F | Keys never logged; redacted in Debug | ✅ Done | impl fmt::Debug for ModelSpec; test key_redacted_in_debug |
| 7 | F | Real [[model]] TOML config parsing (role/endpoint/model/key_env/timeout_ms/max_retries) | ✅ Done | FileModelConfig; tests parses_model_blocks, rejects_unknown_model_role, rejects_wrong_types_inside_model_blocks, parses_documented_model_config, local_model_can_omit_key_env |
| 8 | G | Timeout/bad-response simulated, breaker trips | ✅ Done | mod tests Fake transport in model.rs |
| 9 | G | No plaintext keys anywhere (docs, debug output) | ✅ Done | internal-gate PASS “Docs avoid direct API key command-line values”; test key_redacted_in_debug |
| 10 | B | No cache/persistence of model calls | ✅ Done | No cache crate or on-disk cache path in Cargo.toml / model.rs |
Phase verdict: ✅ Done.
P3 — ReACT scheduler
ROADMAP status: done ✓
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | Bounded state machine (max_steps/max_errors) | ✅ Done | src/react.rs::ReAct::run |
| 2 | F | Tool-call protocol prompt (<TOOL_CALL>/<FINAL>) | ✅ Done | fn initial_prompt; test initial_prompt_declares_tool_protocol |
| 3 | F | $SEED alias resolves to the full seed text for tool input | ✅ Done | fn resolve_tool_input; test seed_alias_feeds_tool_observation |
| 4 | F | Compile-time skill routing via enum + match (coarse_filter, converge) | ✅ Done | src/skills.rs::Skill |
| 5 | G | Unknown skill / bad JSON trips to Partial, never panics | ✅ Done | Tests unknown_skill_trips_to_partial, bad_json_trips_to_partial |
| 6 | G | Step cap returns Partial instead of looping forever | ✅ Done | Test step_cap_returns_partial_not_hang |
| 7 | F | Report-language-aware prompts (en/zh) | ✅ Done | Test initial_prompt_declares_report_language |
| 8 | F | Scope rubric injected so tests/fixtures are never reported as production risk | ✅ Done | Test prompts_carry_scope_rubric |
Phase verdict: ✅ Done.
P4 — Deterministic Reduce + report
ROADMAP status: not marked done; README self-reports “in progress.” This is the phase carrying the most feature growth, so it is split into three groups below.
P4a — Deterministic ledger & Markdown report
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | Deterministic AST coarse-filter rule engine | ✅ Done | src/report.rs::findings_from_seed, push_call_risk, push_supply_chain_risks, push_manifest_risks, push_container_global_risks |
| 2 | F | Severity + path-scope classification (Production/CI/Test/TestFixture/Docs, severity caps) | ✅ Done | PathScope::classify; tests path_scope_classifies_common_layouts, production_panic_edge_stays_high, panic_edge_in_tests_is_capped_to_low, fixture_supply_chain_is_capped_to_low |
| 3 | F | Markdown ledger renderer, bilingual headings | ✅ Done | render_markdown_with_language, render_table_with_language; test renders_localized_markdown |
| 4 | F | Explicit input-coverage reporting (candidate/dehydrated/seed bytes/cap/batches) | ✅ Done | struct InputCoverage, markdown_section, agent_gate_coverage |
| 5 | F | Per-record truncation visibility (reason, original vs. compacted bytes) | ✅ Done | struct TruncatedRecord, compact_seed_record_with_limits; test compact_seed_record_caps_oversized_files; internal-gate PASS “Model seed truncation is reported” |
| 6 | G | Hits seeded risks in known fixtures | ✅ Done | tests/repo_intake_fixtures.rs (10 malicious + 1 benign fixture, all pass) |
| 7 | G | Full-audit stdout contains only the final report | ✅ Done | internal-gate PASS “Full audit stdout is reserved for the final report”; test scan_only_stdout_remains_jsonl_not_benchmark_json |
| 8 | G | Invalid config fails loudly, never silently reverts to defaults | ✅ Done | Tests dirty_values_reject_config_not_silent_default, valid_toml_wrong_types_reject_config_not_silent_default, rejects_dirty_env_lines |
| 9 | G | --module audit is contained inside the project root, never bleeds to global | ✅ Done | Tests absolute_module_must_stay_inside_target, absolute_module_inside_target_is_allowed; internal-gate PASS “Module path is contained by project root” |
| 10 | G | Fake-endpoint full-audit smoke proves the user-facing path | 🟡 Partial | Manual evidence only: reports/full-audit-local-model-test.md was produced against a local OpenAI-compatible endpoint. Not wired as an automated/CI-reproducible test (needs a mock HTTP server or recorded fixture responses). That report also predates the current “small-model Map inactive by default” behavior, so it no longer reflects the default Reduce-only path |
P4b — Agent gate & policy
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | Stable text contract (VERDICT/WHY/BLOCKERS/SAFE_TO_AGENT_RUN) | ✅ Done | fn render_agent_gate; tests/repo_intake_fixtures.rs |
| 2 | F | Stable JSON contract (schema_version, verdict, safe_to_agent_run, exit_reason, why, blockers, coverage, findings, policy_actions) | ✅ Done | struct AgentGateJson; test agent_gate_json_exposes_stable_verdict_shape (black-box) |
| 3 | F | Exit code 0 iff SAFE_TO_AGENT_RUN: yes, non-zero for CAUTION/REJECT/INCOMPLETE | ✅ Done | tests/repo_intake_fixtures.rs (all 10 malicious fixtures assert non-zero exit) |
| 4 | F | Supply-chain rule set: npm lifecycle scripts, manifest/lockfile gaps, git/path/http dependency sources, build.rs command boundaries, shell/Dockerfile download-execute, base64 decode-execute, GitHub Actions permission/trigger risk, secrets-coupled shell, unpinned Actions, Docker root/remote-repo patterns, suspicious binary/archive artifacts | ✅ Done | 21 fixtures under tests/fixtures/repo-intake/, exercised by sift eval-corpus (eval_cases, 21 cases) and tests/repo_intake_fixtures.rs |
| 5 | F | Project-local sift-policy.toml (max_candidate_files, [[allowlist]], [[denylist]], [[severity_override]]) | ✅ Done | load_policy_config/parse_policy_config in config.rs; test parses_policy_schema_and_rejects_bad_severity; apply_policy/policy_match/policy_override_match in report.rs |
| 6 | F | Suspicious binary/archive artifact inventory | ✅ Done | inspect_suspicious_artifact, is_binary_or_archive_name; fixtures binary-artifact-exec, binary-extension, archive-payload |
| 7 | F | sift eval-corpus: ≥20-case precision table | ✅ Done | run_eval_corpus, 21 eval_cases; test eval_corpus_reports_twenty_or_more_cases |
| 8 | G | Recent regression fixes: Cargo.lock registry source no longer flagged as a git dependency; workflow-write-all no longer conflates single-scope contents:/actions:/packages: write with broad write-all; record_truncated > 0 no longer forces INCOMPLETE by itself; VCS metadata dirs (.git, .hg, .svn, .jj) excluded from scan | ✅ Done | Landed in current HEAD f9a374b, superseding the open items in reports/project-audit-2026-07-01.md (written against parent commit 88c5334). Evidence: tests ignores_cargo_lock_crates_io_registry_source, flags_broad_but_not_scoped_workflow_write_permissions; scanner.rs::VCS_METADATA_DIRS; report.rs::gate_incomplete_reasons no longer reads record_truncated |
| 9 | ⛑ | Dogfood finding, fixed this session: sift . --agent-gate on sift’s own repository returned CAUTION due to two real bugs, both now fixed — see Self-audit dogfood check | ✅ Done | (a) looks_like_eval_invocation added to report.rs/extract.rs, requiring a shell-substitution token after a standalone eval word so English prose like “eval corpus” no longer trips dynamic-shell-eval; tests flags_real_dynamic_shell_eval_invocation, ignores_eval_used_as_an_english_word, markdown_prose_mentioning_eval_corpus_is_not_a_command. (b) [[allowlist]] policy matching extended from RiskFindings to coverage.suspicious_artifacts via apply_policy_to_artifacts/policy_match_artifact, plus a new root sift-policy.toml allowlisting .githooks/pre-commit and the tests/fixtures/repo-intake/ synthetic artifacts; tests policy_allowlist_suppresses_matching_suspicious_artifact, policy_allowlisting_every_artifact_reaches_accept, policy_allowlist_matches_one_tag_within_a_combined_artifact_reason. Re-run after both fixes: 0 blockers, but verdict is still CAUTION — this is now understood to be correct, not a bug (see dogfood section) |
P4c — Operational modes
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | --benchmark local telemetry (no model calls; optional USD cost estimate) | ✅ Done | tests/benchmark_mode.rs (3/3 passing) |
| 2 | F | sift github owner/repo safe intake — never builds, installs, runs hooks, or touches submodules; inspects file/byte limits, .gitmodules, Git LFS before scanning | ✅ Done | run_github_intake, parse_github_repo, inspect_checkout_dir; tests github_repo_parser_accepts_owner_repo_and_https, checkout_inspection_reports_lfs_and_limits, github_intake_rejects_non_github_url_without_network (black-box). Both git fetch and the recursive local sift invocation run under run_command_with_timeout (120s / 600s hard deadlines with kill-on-timeout) |
| 3 | F | sift doctor — config/key/endpoint diagnostics | 🟡 Partial | Implemented (run_doctor, check_config_permissions, check_file_config, check_endpoint_key_pair, …) but has zero automated test coverage — no unit test in config.rs::tests exercises run_doctor/Doctor, and no integration test in tests/ spawns sift doctor. The internal gate’s “each file has #[cfg(test)]” check (BT) passes for config.rs only because other functions in the same file are tested — it cannot see this gap |
| 4 | F | --save/--save-to persisted reports (reports/sift-audit-result-YYYYMMDD-NNN.md) | ✅ Done | save_audit_result, next_audit_result_path, utc_yyyymmdd, civil_from_days in main.rs |
| 5 | F | --report-language {en,zh} bilingual Markdown reports | ✅ Done | ReportLanguage; test localized_headings_render_for_zh |
| 6 | F | --debug extra stderr diagnostics | ✅ Done | main.rs debug eprintln! blocks |
| 7 | B | Small-model Map (map_small_pool) is retained as inactive diagnostic scaffolding, not called by the default full-audit path | 🟡 Partial (by design) | Code + 4 tests exist in model.rs (small_pool_maps_successful_observations, etc.), but main.rs prints "small-model Map inactive: reduce converges from deterministic findings" and never calls it. This matches AGENT.md’s framing exactly — it is correctly labeled scaffolding, not a defect — but it is still an open roadmap decision: reintroduce behind a behavior-level gate, or retire it |
Phase verdict: 🟡 Mostly done — matches the project’s own “P4 in progress” self-report. The two genuinely open engineering items are #10 in P4a (no CI-automated full-audit smoke) and #3 in P4c (doctor untested); the small-model Map question (#7 in P4c) is an intentional open decision, not a bug.
P5 — Internal Quality Gate
ROADMAP status: heading now carries the ✓ (updated this session); the feature and its gate are fully built and green.
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | audit.rs self-audit module scoring dimensions CQ/SEC/RB/DF/BT/CC/UX | ✅ Done | src/audit.rs::run_checks (13 checks) |
| 2 | F | Writes a maintainer-only report to reports/internal-gate.md (gitignored) | ✅ Done | write_internal_gate; .gitignore contains /reports/ |
| 3 | F | Hidden from the public CLI (triggered by SIFT_INTERNAL_GATE=1, not a documented flag) | ✅ Done | internal_gate_target() in main.rs; test self_audit_flag_is_not_public_cli_argument confirms no --self-audit flag exists |
| 4 | F | Wired into make internal-gate / make ci | ✅ Done | Makefile; verified this session (make ci exit 0) |
| 5 | G | No FAIL/WARN for hard rules, including no broad dead_code allow, no raw CJK source literals, clean report-stream boundary, visible seed truncation | ✅ Done | This session’s fresh run: 13/13 PASS, 0 WARN, 0 FAIL (reports/internal-gate.md) |
| 6 | ⚑ | Test-coverage check (BT) is file-granularity only | 🟡 Known limitation | test_coverage_status only checks that a file contains #[cfg(test)] somewhere — it cannot detect that a specific function (e.g., run_doctor) is untested inside an otherwise-tested file. See P4c #3 |
Phase verdict: ✅ Done. ROADMAP.md/ROADMAP.zh.md P5 headings were updated to — done ✓ this session to match this evidence. Remaining suggestion: tighten the BT check toward function-level coverage.
P6 — Release hardening
ROADMAP status: no checkmark in the heading; substantial evidence exists.
| # | Type | Item | Status | Evidence |
|---|---|---|---|---|
| 1 | F | Size-tuned release profile (opt-level=z, lto, codegen-units=1, strip, panic=abort) | ✅ Done | Cargo.toml::[profile.release] |
| 2 | F | Makefile install/uninstall path (~/.local/bin default, PREFIX/BINDIR overrides) | ✅ Done | Makefile install/uninstall targets |
| 3 | F | Git hooks install/uninstall; pre-commit runs make local-ci | ✅ Done | Makefile githooks-install/githooks-uninstall; .githooks/pre-commit |
| 4 | F | CI: fmt/test/clippy/internal-gate on an ubuntu-latest + macos-latest matrix | ✅ Done | .github/workflows/ci.yml |
| 5 | F | Release workflow: SemVer tag guard, macOS amd64/arm64 build, tar.xz + sha256, environment-gated draft→published GitHub release | ✅ Done | .github/workflows/release.yml; tags v0.1.0, v0.2.0 exist |
| 6 | F | Homebrew tap auto-publish (jamiesun/homebrew-tap formula render + push) | ✅ Done | release.yml::homebrew job; depends on the HOMEBREW_TAP_TOKEN repo secret being configured, which is outside this repo’s own verifiable scope |
| 7 | F | More grammars | ⏳ Pending (open-ended) | 23 tree-sitter grammars + 4 structural extractors already shipped (see P1); ROADMAP intentionally leaves this unbounded, so it can never be marked fully “done” |
| 8 | F | Stable JSON output contracts (schema_version) across --benchmark, --agent-gate --format json, eval-corpus | ✅ Done | schema_version: 1 asserted in benchmark_mode_outputs_stable_json_without_model_keys, agent_gate_json_exposes_stable_verdict_shape |
| 9 | G | Single-file dist | ✅ Done | release.yml packages one sift binary (+ docs/README/config template) per tar.xz |
| 10 | G | Internal gates pass | ✅ Done | See P5 |
| 11 | G | Docs ↔ code consistent | 🟡 Partial (manual only) | No automated check diffs documentation (supported-language lists, CLI flags, version strings) against source of truth; verified by manual cross-reading this session, but nothing in make ci would catch future drift |
| 12 | G | brew install jamiesun/tap/sift backed by release checksums | ✅ Done (unverified externally) | sha256/formula-render logic present in release.yml; not independently re-checked against the live jamiesun/homebrew-tap repository in this session |
Phase verdict: 🟡 Mostly done. Two open threads: docs↔code consistency has no automated guard, and “more grammars” is an intentionally unbounded target rather than a gate to close.
Cross-cutting: Engineering Contract (ROADMAP.md)
| # | Rule | Status | Evidence |
|---|---|---|---|
| 1 | A phase marked done has behavior-level proof, not just type-level plumbing | ✅ Held for P0–P3; 🟡 two exceptions noted above (P0 #7, P4c #3) | |
| 2 | Full-audit stdout is the final report; --scan-only is JSONL; diagnostics stay off stdout | ✅ Done | See P4a #7 |
| 3 | Report discloses how much input was scanned/dehydrated/sent/skipped/truncated | ✅ Done | InputCoverage, AgentGateCoverage |
| 4 | Missing user config auto-created from safe defaults; an invalid config file fails instead of reverting to defaults | ✅ Done | See P4a #8 |
| 5 | src/ is English-only for runtime text, prompts, and comments | ✅ Done | internal-gate PASS “Program source avoids raw CJK literals” |
Cross-cutting: Definition of Done (ROADMAP.md)
| # | Criterion | Status |
|---|---|---|
| 1 | Zero-config run; ~/.sift/config.toml auto-created; missing key exits with a hint; never hangs | ✅ Done |
| 2 | 100 MB repo stable memory; no crash on dirty input | ⬜ Not done — see P1 #7 |
| 3 | Report cites line numbers + cross-module deps + concurrency/resource risk | ✅ Done |
| 4 | Report declares input coverage and truncation state; incomplete coverage never looks like a complete verdict | ✅ Done |
| 5 | Every external call times out; failures trip to partial, never grind | ✅ Done — model HTTP calls (model.rs) and GitHub-intake subprocesses (run_command_with_timeout, 120s/600s) both verified |
| 6 | One binary audits project and --module without bleed | ✅ Done |
| 7 | Internal release gates have no FAIL or hard-rule WARN | ✅ Done |
Non-goals guardrail
Confirms none of ROADMAP.md’s hard “must never do” rules have been crossed.
| # | Non-goal | Held? | Evidence |
|---|---|---|---|
| 1 | No vector DB / embeddings / RAG | ✅ Held | Cargo.toml dependency list has no vector-DB/embedding crate |
| 2 | No runtime plugins / dynamic skills | ✅ Held | skills.rs::Skill is a compile-time enum + match; no dynamic-loading dependency |
| 3 | No service / Web UI / multi-tenant | ✅ Held | No web-server crate in Cargo.toml; CLI-only via clap |
| 4 | No process panics | ✅ Held (heuristic, not formal) | internal-gate PASS on both explicit panic! and unwrap()/expect() literal-pattern checks. Note: panic = "abort" in the release profile changes unwind behavior if a panic ever happens — it is not itself a no-panic guarantee. The real guarantee is the source-text scan, which cannot catch e.g. indexing/overflow panics |
| 5 | No unbounded blocking | ✅ Held | Model calls: ureq timeout in model.rs. Subprocesses: run_command_with_timeout (git fetch 120s, recursive local sift invocation 600s, kill-on-timeout) |
| 6 | Module audit must not balloon to global | ✅ Held | See P4a #9 |
| 7 | No trial-run instead of audit | ✅ Held | sift github never builds/installs/runs hooks/submodules regardless of flags; --no-build/--no-install on GithubCli are explicit safety-intent markers, not toggles — the tool never builds or installs either way |
| 8 | No scaffold masquerading as product | ✅ Held | Small-model Map is explicitly labeled “inactive diagnostic scaffolding” in both code output and docs, not counted as shipped default behavior |
| 9 | No silent fallback | ✅ Held | See P4a #8; invalid config always fails loudly |
Self-audit dogfood check
AGENT.md states “sift itself must pass its internal release gates.” That claim covers two different gates, which this snapshot deliberately keeps separate:
- Internal quality gate (
SIFT_INTERNAL_GATE=1, i.e.make internal-gate) — sift’s own code-quality gate. Result: 13/13 PASS, 0 FAIL, 0 WARN. ✅ This is the gate ROADMAP.md and AGENT.md are talking about, and it is green. - Agent gate (
sift . --agent-gate) — the product feature meant to screen arbitrary third-party repositories before an agent runs setup/build/install. There is no roadmap requirement that sift accepts its own repository under this gate, but running it is a useful dogfood check.
First run (start of this session): two real bugs found
VERDICT: CAUTION
SAFE_TO_AGENT_RUN: no
coverage: candidate_files=69 dehydrated_files=62 unsupported_files=7
record_truncated=12 seed_bytes=148402
- Rule false positive:
docs/ROADMAP.zh.mdand other prose files were flaggeddynamic-shell-eval(scope=docs, MEDIUM) purely because the English phrase “eval corpus” (sift’s owneval-corpusfeature name) contains the substring"eval ", whichlooks_like_dynamic_shell_eval(src/report.rs) andlooks_like_shell_command(src/extract.rs) both matched unconditionally. Not a real shell-eval risk. - Unreviewed but legitimate artifacts:
.githooks/pre-commit(an extensionless, real, committed executable) and two committed test fixtures (archive-payload/assets/payload.tar.gz,binary-extension/bin/tool.dylib) tripped the suspicious-artifact rule. There was no project-localsift-policy.toml(onlysift-policy.example.toml), and even with one, policy[[allowlist]]matching only applied toRiskFindings, never tocoverage.suspicious_artifacts— so these blockers had no suppression path at all.
Fixes landed this session
- Added
looks_like_eval_invocation(word-boundary + shell-substitution-token check) in bothreport.rsandextract.rs, soevalonly flags a real invocation — the standalone word immediately followed by a command substitution, backticks, or a$variable— and never English/Chinese prose mentioning “eval corpus”/“retrieval”. Covered byflags_real_dynamic_shell_eval_invocation,ignores_eval_used_as_an_english_word,markdown_prose_mentioning_eval_corpus_is_not_a_command. - Extended the policy engine so
[[allowlist]]also suppressessuspicious_artifactsblockers, matchingruleagainst the artifact’sreasontag (apply_policy_to_artifacts,policy_match_artifactinreport.rs; handles comma-joined multi-reason artifacts too). Covered bypolicy_allowlist_suppresses_matching_suspicious_artifact,policy_allowlisting_every_artifact_reaches_accept,policy_allowlist_matches_one_tag_within_a_combined_artifact_reason. - Added a real root
sift-policy.toml(previously onlysift-policy.example.tomlexisted) allowlisting.githooks/pre-commitand thetests/fixtures/repo-intake/synthetic artifacts, each with a written reason.sift-policy.example.tomlwas extended with a documented example of the new artifact-allowlist form.
Second run (after fixes): blockers gone, verdict still (correctly) CAUTION
VERDICT: CAUTION
SAFE_TO_AGENT_RUN: no
coverage: candidate_files=72 dehydrated_files=64 unsupported_files=8
record_truncated=12 seed_bytes=148542
BLOCKERS: none
POLICY:
- suppressed artifact extensionless_or_binary_executable at .githooks/pre-commit by allowlist (...)
- suppressed artifact binary_or_archive_extension at tests/fixtures/repo-intake/archive-payload/assets/payload.tar.gz by allowlist (...)
- suppressed artifact binary_or_archive_extension at tests/fixtures/repo-intake/binary-extension/bin/tool.dylib by allowlist (...)
(Note for future editors of this very section: describing these two rules’ trigger shapes in a literal, directly reproducible way can make this file itself trip them. Keep any such illustrative examples suitably paraphrased.)
Both root causes are fixed and verified: the eval false positive is gone (the one remaining dynamic-shell-eval finding is a real shell-invocation fixture — bash with an inline -c command interpolating a secret — exactly as intended), and all three unreviewed-artifact blockers are now suppressed with a written, reviewed reason.
The verdict nonetheless stays CAUTION, and this is now understood to be correct — not a defect to chase. All 40 remaining findings are Severity::Low, none Medium/High, and every one traces to one of two intentional, by-design sources:
- The 21 synthetic attack-pattern fixtures under
tests/fixtures/repo-intake/(the same corpussift eval-corpusscores). They exist specifically to prove the supply-chain rule engine detectsnpm-lifecycle-script,download-execute,dependency-git-source,workflow-write-all, etc. If self-scanning made these disappear, the rules would be broken, not fixed. panic-edge(.expect()/.unwrap()) findings insidetests/*.rs. Hard Rule #1 forbidsunwrap()/expect()only insrc/; using them in tests is normal and correct, andPathScope::classifyalready caps these to Low — they still show up as findings (informational), they just cannot be silently hidden.
The agent gate’s verdict rule (render_agent_gate) only returns ACCEPT when findings is completely empty. Forcing that for sift’s own repository would require either deleting its own regression corpus or blanket-allowlisting every rule across tests/, both of which would remove the evidence this checklist’s P4a/P4b rows cite. The honest, durable dogfood claim is therefore: 0 High findings, 0 unexplained blockers, every Low finding accounted for — not a literal ACCEPT.
Consolidated open items
Everything not marked ✅ Done above, in one place. Two items from the previous snapshot were resolved this session and are omitted here (agent-gate self-CAUTION root causes fixed; ROADMAP P5 heading refreshed) — see Self-audit dogfood check for the former.
| Item | Phase | Status | Suggested next step |
|---|---|---|---|
| No black-box test asserts exit code 1 for a real full-audit run with no key | P0 | 🟡 Partial | Add an integration test under tests/ |
No 100 MB stress fixture; macOS resident-memory metric is always "unavailable" | P1 | ⬜ Not done | Add a large-corpus smoke test; extend resident_memory_metric to macOS (task_info/ps) |
| Fake-endpoint full-audit smoke is manual-only, not CI-automated, and predates the current small-model-Map-inactive default | P4a | 🟡 Partial | Add a mock-HTTP-server integration test exercising react::ReAct end to end |
The pre-existing policy-suppression logic (apply_policy/policy_match/policy_override_match for RiskFindings) has no direct unit test exercising suppression end-to-end — only TOML parsing is tested (parses_policy_schema_and_rejects_bad_severity). The new artifact-allowlist path added this session is tested; the original finding-allowlist path still is not | P4b | 🟡 Partial | Add apply_policy/denylist/severity-override unit tests in report.rs, mirroring the new policy_allowlist_* artifact tests |
sift doctor has zero automated test coverage | P4c | 🟡 Partial | Add unit tests for Doctor/run_doctor and/or a tests/doctor.rs black-box test |
| Small-model Map is inactive scaffolding; reintroduce-or-retire decision is still open | P4c | 🟡 Partial (by design) | Maintainer decision, then either wire behind a behavior-level gate or delete |
| “More grammars” has no fixed target | P6 | ⏳ Pending | Not a defect; track via issues per language request instead of this checklist |
| Docs ↔ code consistency has no automated guard | P6 | 🟡 Partial | Consider an audit.rs check that greps README.md’s supported-language list against extract.rs::Lang variants |
Refreshing this snapshot
cargo build
make ci # fmt-check + test + clippy -D warnings + internal-gate
cat reports/internal-gate.md # P5 gate detail (gitignored, local only)
cargo run --quiet -- . --agent-gate --format json # live self-scan (dogfood check above)
sift eval-corpus # repo-intake precision table
This file reflects one commit in time. Re-run the commands above and update the Snapshot table, the phase tables, and the Consolidated open items whenever a phase’s evidence changes — do not hand-edit a status mark without re-checking its evidence.
AGENT.md - sift Contributor Handbook
English | 中文
The implementation handbook for humans and agents working on sift. Source of truth for hard rules, layout, and habits. Profile/boundaries live in Roadmap.
What sift is
A cost-controlled, single-binary open-source auditor: tree-sitter dehydration -> deterministic coarse ledger -> large-model convergence (Reduce), orchestrated by a ReACT state machine. Audits a whole project or one module. Small-model Map code is retained as inactive diagnostic scaffolding until behavior-level gates reintroduce it. sift must pass its internal release gates.
Hard Rules
- No
unwrap()/expect()insrc/. Dirty data takes aResult/Optionbranch and is dropped+logged; the main process never panics. - Every external call has a hard timeout. Unbounded blocking is a bug. Repeated failure trips a breaker; on trip, back off, degrade, or emit partial output.
- Single binary, low deps. No vector DB, embeddings/RAG, DB, or cache.
- Compile-time skills only. Skills are an
enumplusmatchto local functions. - Streaming, memory decoupled from scale. Bounded channel, drop the AST after dehydrating.
- Fallback key resolution. CLI key file > ENV > project
.env>~/.sift/config.toml> default. - Secrets via env/file only. Never compiled in, committed, printed, or logged.
- Module audit must not balloon to global. Cross-boundary refs are marked
[EXTERNAL_BLACKBOX]; do not chase. - TDD. Each
src/*.rscarries unit tests; build tests alongside new subsystems. - Bilingual docs, English default. Every user-facing doc has a Chinese counterpart, and commands/rules must match across languages.
- No toy gates or fake capability claims. Scaffold code must be named as scaffold and isolated behind explicit modes.
- Stable output contracts.
--scan-onlywrites JSONL to stdout; full audit stdout is reserved for the final report. - No silent degradation. Truncation, skipped files, model fallback, partial reports, invalid config, and parse failures must be visible.
- Program source is English-only. Runtime strings, prompts, and comments in
src/are English.
Code Map
| Path | Role | Phase |
|---|---|---|
src/main.rs | wiring: parse -> Config -> schedule -> report -> exit | P0 |
src/config.rs | fallback resolve, multi-model config | P0 -> P2 |
src/scanner.rs | Walk + bounded channel | P0 |
src/extract.rs | tree-sitter dehydrate -> AstSummary | P1 |
src/model.rs | model registry/client/timeout/breaker | P2 |
src/react.rs | ReACT state machine + skill match | P3 |
src/skills.rs | local skill functions | P3 -> P4 |
src/report.rs | Markdown risk list | P4 |
src/audit.rs | internal gate scoring | P5 |
Workflow
cargo build
cargo test
cargo fmt && cargo clippy
make ci
rg 'unwrap\(|expect\(|panic!' src
rg '[\p{Han}]' src
- One concern per commit.
- Before adding a feature, check it does not cross a roadmap non-goal.
- A phase is not done until its internal gate and at least one behavior-level smoke are green.
- Reports go to
reports/, which is gitignored.
sift
English | 中文
可控成本的开源项目审计器:分级漏斗 + 算力错配 + ReACT 调度。引入开源库前,不必生吞数万行代码进前沿大模型,就能拿到定位到文件/行号的风险账本。
- 脏活(结构提取/确定性粗筛)→ tree-sitter + 本地规则
- 逻辑收敛 → 前沿大模型,ReACT 状态机基于确定性发现统一调度
- 单二进制、零配置、可审项目或模块;sift 自身必须通过内部发布门禁
详见 ROADMAP.zh.md。
用法
sift ./repo --scan-only # 仅扫描层
sift ./repo --agent-gate # 确定性预运行门禁,无需模型 Key
sift ./repo --agent-gate --format json
sift ./repo --benchmark # 扫描/模型预算 telemetry JSON,无需模型 Key
sift github owner/repo # 安全 GitHub intake,默认 --agent-gate
sift github owner/repo --ref main --scan-only
sift eval-corpus # 运行内置 repo-intake 精度样本集
sift query ./repo --calls 'exec|spawn' # 无状态证据检索 → file:line
sift query ./repo --imports reqwest --lang rust # 谁引入了 reqwest,仅看 rust 文件
sift query ./repo --any 'curl|wget' --format json
sift ./repo --module src # 审子模块
SIFT_API_KEY=<KEY> sift ./repo # 全链路
sift ./repo --api-key-file ~/.sift/key
sift ./repo --report-language zh # 输出中文 Markdown 报告
sift ./repo --save # 同时保存报告到 reports/sift-audit-result-YYYYMMDD-NNN.md
sift ./repo --save-to out/audits # 保存报告到指定目录(隐含 --save)
sift ./repo --debug # 向 stderr 打印更多诊断
sift doctor # 检查配置、key_env 与 endpoint/key 错配
--agent-gate 是给 agent 和包装脚本使用的本地确定性 repo-intake
门禁。它只向 stdout 写入以下稳定契约:
VERDICT: ACCEPT | CAUTION | REJECT | INCOMPLETE
WHY:
- <top evidence>
BLOCKERS:
- <file:line evidence or coverage blocker>
SAFE_TO_AGENT_RUN: yes | no
自动化集成可对 --agent-gate 使用 --format json。JSON 契约包含
verdict、safe_to_agent_run、exit_reason、coverage、findings、
blockers、artifact inventory、截断明细和 policy actions。
只有 SAFE_TO_AGENT_RUN: yes 时命令退出码为 0;CAUTION、REJECT
和 INCOMPLETE 都返回非零,方便调用方在 setup、install、build 或 run
之前停止。
sift query 是对 --scan-only 同一份脱水证据的无状态检索视图。每次
调用都重新执行本地扫描(秒级、无需 Key、无索引无缓存),并用平面
regex 旗标过滤证据:--calls、--imports、--signatures、
--external、--any,外加 --lang 与 --path 记录过滤。多个旗标在
文件级做 AND。文本输出是 grep 风格的 path:line: kind: text 证据;
--format json 输出单个文档,包含 schema_version、回显的 query、
coverage、匹配计数和 matches。输出证据由 --limit(默认 200)
封顶且截断可见。退出码遵循 grep 惯例:0 有命中,1 无命中,2
用法或配置错误。
确定性供应链规则目前会标记 npm 安装生命周期脚本、manifest/lockfile
可复现性缺口、git/path/http 依赖来源、Rust build.rs 命令边界、
shell/Dockerfile 下载后执行模式、base64 解码后执行流、GitHub Actions
权限/触发器风险、secrets 与 shell 执行耦合、未 pin 到 commit SHA 的
GitHub Actions、Dockerfile root/远程仓库模式,以及可疑二进制/归档 artifact。
sift github 接受 owner/repo 或 https://github.com/owner/repo,
用 git 获取临时 checkout,解析 commit SHA,然后对该 checkout 运行本地
scan/gate/benchmark 管线。它不会运行仓库代码、包管理器命令、build
script、hook、install 命令或 submodule。扫描前会检查 checkout 文件数/
字节上限、.gitmodules 和 Git LFS 指示。临时 checkout 默认清理;只有
需要人工查看取回的源码树时才使用 --keep-checkout。
项目本地 policy 使用 sift-policy.toml。它支持 max_candidate_files、
[[allowlist]]、[[denylist]] 和 [[severity_override]],可按 path、
rule、severity、reason 配置;命中的 policy 决策会出现在文本和 JSON
门禁输出中。
首次运行时,sift 会自动创建 ~/.sift/config.toml 默认配置文件。默认配置只包含非密钥项;模型密钥放在环境变量里,或通过 --api-key-file 传入。
完整审计的 stdout 只保留最终 Markdown 报告;进度、状态和 debug 诊断都走 stderr,长任务不会看起来像卡死,也不影响下游工具安全消费 stdout。
当前完整审计默认不会调用小模型 Map。它会把确定性账本交给配置的大模型收敛;小模型 Map 实现保留为实验性诊断路径。
--benchmark 是本地 telemetry 模式,用于 release note 和成本核算。
它不会调用模型;默认向 stdout 输出稳定 JSON,也可以用
--benchmark-output <path> 写入文件。报告包含候选/脱水/跳过计数、
扫描耗时、可用的 resident memory 指标、seed 字节数、计划 Reduce
批次、模型调用计数、近似 token 数,以及可选 USD 成本估算。价格必须
显式传入,不会自动猜测:
sift ./repo --benchmark \
--benchmark-input-1m-cost 0.25 \
--benchmark-output-1m-cost 1.00 \
--benchmark-estimated-output-tokens 2000
支持语言
扫描层目前支持 Rust、Python、Go、JavaScript、TypeScript/TSX、HTML、CSS、Zig、Bash 兼容 shell 文件(.sh、.bash、.zsh)、Dart、Kotlin、Java、C/C++、C#、PHP、Swift、Ruby、SQL、Dockerfile/Containerfile、YAML、HCL/Terraform、Vue、Svelte、package.json、常见 package manifest/lockfile、Makefile 和 Markdown 安装片段。
安装
源码构建:
make ci
make install
安装本地 git hooks:
make githooks-install
pre-commit hook 会在每次提交前运行 make local-ci。确需临时跳过时,可执行 SIFT_SKIP_LOCAL_CI=1 git commit ...。
测试样本
tests/fixtures/repo-intake/ 包含合成的恶意与良性仓库树,用于
确定性 --agent-gate 回归测试。sift eval-corpus 会基于这些 fixture
输出 release 级别的精度表。这些 fixture 命令只是惰性样例,绝不能当作安装脚本执行。
macOS release 通过已有 tap 安装:
brew install jamiesun/tap/sift
状态:P0 脚手架 + P1 AST 脱水 + P2 模型层 + P3 ReACT 调度器(工具协议、编译期技能、retry→半成品)已完成。P4 进行中:本地 AST 风险账本、Markdown 渲染、[[model]] 配置解析、稳定 JSON 门禁、policy、artifact inventory 与 eval corpus 已接线。内部发布门禁会为维护者在 reports/ 下写入本地报告。
sift 项目画像与开发路线图
English | 中文
北极星 + 护栏 + 分阶段施工边界。说清“做成什么样 / 绝不做什么 / 每阶段交付什么 / 内部门禁何时生效“。 项目名:sift(CLI 即
sift)。语言:Rust。
项目概述
一个可控成本的开源项目审计器。引入开源库前,不必直接试用、也不必让前沿大模型生吞数万行代码,就能拿到一份定位到文件/行号的风险账本,据此决定是否引入。
核心是 分级漏斗 + 算力错配 + ReACT 调度:脏活(结构提取、粗筛压缩)当前交给零成本静态解析和确定性本地规则;高强度逻辑收敛交给前沿大模型;一个 ReACT 状态机基于确定性发现调度 Reduce。整个工具编译为单一二进制,零配置即可审整个项目或单个模块。sift 自身必须通过内部发布门禁。
- 架构图
CLI key file / ENV / ~/.sift/config.toml ──(降级寻址, 缺 Key 即退)
▼
扫描层 ignore::Walk → 有界 channel(消费即丢) [P0 ✓]
▼
零阶 tree-sitter 脱水(签名/import/调用) → JSON → drop AST [P1 ✓]
│ 跨界引用打 [EXTERNAL_BLACKBOX]
▼
模型层 多模型注册表 · 每调用硬超时 · 熔断+退避恢复 [P2 ✓]
▼
ReACT 调度器(工具协议, 确定性发现, retry≤N) [P3 ✓]
│ └─ 大模型(Reduce 收敛) ─────┘
▼
报表层 stdout Markdown 风险清单(行号/调用链) [P4 已启动]
▼
内部门禁 源码评分检查 + 发布证据 [P5/P6]
项目画像(目标状态)
- 零摩擦冷启动。
sift ./repo --scan-only直接跑;缺~/.sift/config.toml时自动创建不含密钥的默认配置;不交互追问;缺 Key 立退给注入提示。 - 成本可控可预算。 确定性 baseline 本地完成;完整审计才把脱水骨架交给大模型。
- 模型调度。 ReACT 状态机把确定性发现与大模型收敛编排成一条链,技能是编译期写死的本地函数。
- 多模型 + 并发提速。 可配置多个模型端点;扫描/模型并发保持有界且可观测。
- 绝不无脑死磕。 每个外部调用有硬超时;连续失败触发熔断;熔断后退避恢复或降级,到顶则输出半成品而非挂死。
- 默认工程级。 一份看起来完整但实际不完整的审计报告就是缺陷。跳过输入、截断、回退、半成品模型结果、无效配置都必须可见且可测试。
- 稳定机器契约。 扫描 JSONL、最终 Markdown、诊断信息和生成报告各走清晰通道。下游脚本消费 stdout 时不应该猜里面是否混了多种格式。
- 内存与规模脱钩。 流式处理、处理完即丢,常驻内存压低位。
- 内部门禁约束。 项目必须通过维护者专用发布门禁;代码模块化、TDD 守护、边界清晰。
- 品质冲突优先级: 鲁棒不崩 > 报表可用 > 成本低 > 速度快 > 体积小。
非目标(铁律)
- 不做向量库 / Embedding / RAG。 单次低频审计,索引成本大于直接拼 prompt,纯文本管道阅后即焚。
- 不做运行时插件 / 动态技能注册。 技能 = 编译期 enum + match 本地函数;扩展靠改码重编译。
- 不做服务化 / Web UI / 多租户。 一次性 CLI,无常驻、无界面。
- 不允许 panic 主进程。 脏数据静默丢弃记日志;幻觉/坏 JSON 走熔断;全程 Result/Option,无 unwrap/expect。
- 不允许无超时阻塞。 任何子进程/网络/模型调用必须有 deadline,无界等待视为 bug。
- 模块审计不膨胀成全局。 跨界引用打断点交大模型脑补,不盲目追链。
- 不靠“直接试用“替代审计。 价值在引入前判断。
- 脚手架不得冒充产品能力。 占位实现只能存在于明确未完成的阶段内;不能产出看起来像生产完成的报告。
- 不允许静默回退。 无效配置、seed 截断、跳过文件、缺模型角色、模型路径降级,必须明确失败或写入报告。
模块化结构(Code Map)
每个
src/*.rs自带单测;新子系统建则单测同建(TDD)。模块边界即责任边界,禁跨层乱伸手。
src/main.rs 入口装配:解析→Config→调度→报表→退出码
src/config.rs 降级寻址、多模型配置加载 [P0✓→P2扩]
src/scanner.rs Walk + 有界 channel [P0✓]
src/extract.rs tree-sitter 脱水 → AstSummary [P1]
src/query.rs 无状态证据检索(重扫+regex过滤) [P1✓]
src/model.rs 多模型注册表/客户端 trait/超时熔断 [P2✓]
src/react.rs ReACT 状态机 + 技能 enum/match [P3 ✓]
src/skills.rs 本地技能函数(粗筛/reduce收敛) [P3 ✓→P4]
src/report.rs Markdown 风险清单渲染 [P4]
src/audit.rs 内部门禁维度评分(借鉴 scoot, 裁剪) [P5]
多模型与并发(config schema)
concurrency = 8 # 扫描/模型并发上限
[[model]] # 可多条;role 决定用途
role = "small" # 保留给实验性 Map 诊断
endpoint = "..."
key_env = "SIFT_SMALL_KEY"
timeout_ms = 8000
max_retries = 1
[[model]]
role = "large"
endpoint = "..."
key_env = "SIFT_API_KEY"
timeout_ms = 60000
max_retries = 1
寻址降级:CLI key file > ENV > toml > 默认;无 large key 即退。当前完整审计默认不调用 small role 模型;小模型缺失不会改变确定性账本 Reduce 路径。
默认用户配置路径为 ~/.sift/config.toml;首次运行从等价于 config.example.toml 的安全默认值创建,不能写入明文密钥。
超时熔断与恢复(绝不死磕)
- 每调用 deadline:超时即弃,不无界等待。
- 熔断计数器:单链连续失败/坏 JSON/未注册技能达阈值 → break,停 I/O。
- 退避恢复:瞬时错指数退避重试到预算;非瞬时错降级(小模型回退 AST、大模型回退半成品)。
- 预算上限:全局 token/时长封顶,触顶强制收敛输出
[TRUNCATED]报表。
工程契约
- 标记完成的阶段必须有行为级证据,不能只有类型接线或 happy-path 单测。
- 完整审计 stdout 是最终报告流;
--scan-only是 JSONL 流;诊断信息不得进入 stdout。 - 报告必须披露输入覆盖:扫描、脱水、送入模型、跳过、截断的规模。
- 配置文件属于信任边界。用户配置缺失时从安全默认值自动创建;配置文件存在但无效时,进程必须失败,不能退回默认值。
src/下程序源码的运行时文本、prompt 和注释只用英文;双语文档保留在 docs。
阶段路线图
每阶段含:功能清单 / 边界约束 / 内部门禁。门禁全绿才进下阶段,并据门禁证据定下一步。 下方每一项对照真实证据的完成/部分完成/待定快照,见 CHECKLIST.zh.md。
P0 脚手架 — 已完成 ✓
- 功能:clap 降级寻址、有界通道扫描、缺 Key 熔断退出、最小装配。
- 边界:不连网、不解析、不留内存树。
- 门禁:
cargo build绿 / 0 unwrap /--scan-only能扫 / 缺 Key exit1。
P1 零阶 AST 脱水 — 已完成 ✓
- 功能:tree-sitter 接 Rust/Python/Go/JavaScript/TypeScript/HTML/CSS/Zig/Bash/Dart/Kotlin/Java/C/C++/C#/PHP/Swift/Ruby/SQL/Dockerfile/YAML/HCL/Vue/Svelte,提签名/import/调用,输出扁平 AstSummary JSON;跨界打
[EXTERNAL_BLACKBOX];解析即 drop。 - 边界:丢注释与代码体;遇到残缺语法不 panic,并在下游报告披露覆盖不完整;不评价质量。
- 门禁:百兆库内存稳定低位、坏文件不崩;extract.rs 单测覆盖典型/残缺样本。
P2 模型层(多模型+超时熔断) — 已完成 ✓
- 功能:ModelClient trait、注册表、role 路由;每调用硬超时、熔断、退避恢复;可配多端点。
- 边界:不写缓存、不持久化;密钥仅 env/文件、不入日志。
- 门禁:超时/坏响应有测试模拟,熔断必触发不死磕;无明文密钥。粗筛/收敛接线留 P3。
P3 ReACT 调度器 — 已完成 ✓
- 功能:enum 状态机,初始工具协议提示,大模型出
<TOOL_CALL>,经$SEEDmatch 路由本地技能;retry≤N 熔断半成品。 - 边界:技能编译期写死;无动态加载。
- 门禁:注入坏 JSON/未注册技能/连错 N 次能熔断;react.rs 单测覆盖。
P4 确定性 Reduce+报表
- 功能:确定性 AST 粗筛账本、Markdown 渲染、真实
[[model]]TOML 解析、显式输入覆盖率、稳定 JSON 门禁、policy、artifact inventory、eval corpus 与干净 stdout 边界。 - 边界:模块审计只切根;跨界不追;截断和模型降级路径必须可见。
- 门禁:审已知样本命中预埋风险;模块/项目模式不串;完整审计 stdout 只含报告;无效配置失败;fake-endpoint full audit smoke 证明用户路径可用。
P5 内部质量门禁 — 已完成 ✓
- 功能:audit.rs 跑裁剪维度评分,并把维护者专用报告写入
reports/(gitignore)。 - 门禁:硬规则无 FAIL/WARN,包括无 broad dead-code allow、无中文源码字符串/注释、报告流边界干净、seed 截断可见。
P6 发布加固
- 功能:ReleaseSafe 单二进制、Makefile 安装路径、macOS Homebrew tap 发布、多语法扩展、JSON 输出稳定。
- 门禁:单文件分发、内部门禁通过、文档↔功能一致,
brew install jamiesun/tap/sift由 release checksum 支撑。
完成的样子
- 空配置可跑,自动创建
~/.sift/config.toml,缺 Key 即退给提示;不挂起。 - 百兆库内存稳定、扫坏不崩。
- 报表定位行号、含跨模块依赖与并发/资源风险,可直接拍板。
- 报表声明输入覆盖和截断状态;覆盖不完整时绝不能看起来像完整结论。
- 任一外部调用必超时;连错熔断出半成品而非死磕。
- 同一二进制审项目与
--module子目录不串。 - 内部发布门禁无 FAIL,硬规则无 WARN。
建议非铁律:rayon/具体超时阈值/体积耗时数字按基准定,别当验收红线锁死。已确立铁律:单二进制、降级寻址、有界通道、硬超时熔断、无 unwrap、TDD、内部门禁达标。
sift 验收清单
English | 中文
针对 ROADMAP.md 承诺的每一项功能与门禁的时间点验收快照,严格限定在其阶段边界(P0–P6)与非目标范围内;边界之外的内容本清单不予评分——铁律见 AGENT.md。 行号以下方快照提交为准,后续可能漂移;行号与函数/测试名冲突时,以函数/测试名为准。
快照信息
| 提交 | f9a374b —「fix agent gate issue regressions (#39)」(比标签 v0.2.0 多 4 个提交),另有本次会话的后续修复尚未提交(见自我审计 dogfood 检查) |
| 评估日期 | 2026-07-01 |
cargo build | ✅ 通过 |
make ci(fmt-check + test + clippy -D warnings + internal-gate) | ✅ 通过,退出码 0 |
| 测试 | ✅ 127 个通过,0 个失败(src/** 内 119 个单测 + tests/*.rs 内 8 个黑盒测试) |
内部质量门禁(reports/internal-gate.md) | ✅ 13/13 检查 PASS,0 WARN,0 FAIL |
图例
| 标记 | 含义 |
|---|---|
| ✅ 完成 | 已交付且有行为级证据支撑(本次评估中的一个通过测试和/或一次真实运行)——不只是类型层接线或 happy-path 单测。 |
| 🟡 部分完成 | 已交付但范围受限、按设计暂未激活,或缺少行内注明的某一项证据点。 |
| ⏳ 待定 | 尚无固定目标、等待维护者决策,或 ROADMAP.md 中本就明确留白/开放式。 |
| ⬜ 未完成 | 承诺过但未实现,或完全没有自动化证据。 |
对应本清单要回答的三分类:完成 = ✅,待定 = ⏳,未完成 = 🟡 / ⬜。
Type 列:F = 功能条目,G = 门禁/验收标准条目,B = 边界约束,均逐字取自各阶段 ROADMAP.md 原文。
P0 — 脚手架
ROADMAP 状态:已完成 ✓
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | 降级寻址:CLI key file › ENV › 项目 .env › ~/.sift/config.toml › 默认值 | ✅ 完成 | src/config.rs::Config::resolve;测试 parses_project_env_file、explicit_api_key_file_must_be_readable_and_non_empty |
| 2 | F | 有界通道扫描器(Walk → 有界 channel,消费即丢) | ✅ 完成 | src/scanner.rs(crossbeam_channel::bounded::<PathBuf>(1024));测试 scan_skips_ignored_dirs_and_large_files |
| 3 | F | 最小端到端装配:解析 → Config → 调度 → 报表 → 退出码 | ✅ 完成 | src/main.rs::main |
| 4 | G | cargo build 绿 | ✅ 完成 | 本次会话验证(make ci 退出码 0) |
| 5 | G | src/ 内 0 处 unwrap()/expect() | ✅ 完成 | reports/internal-gate.md:「No direct unwrap/expect in src」— PASS |
| 6 | G | --scan-only 无需任何模型 Key 即可扫描 | ✅ 完成 | tests/benchmark_mode.rs::scan_only_stdout_remains_jsonl_not_benchmark_json |
| 7 | G | 完整审计缺大模型 Key 时在调度前退出 | 🟡 部分完成 | 代码路径已存在(src/main.rs:83-86、config::missing_large_key_hint),但单测只覆盖提示文案内容(missing_key_hint_uses_parseable_model_block);没有黑盒测试实际拉起二进制、在非 --scan-only/--agent-gate/--benchmark 路径下缺 Key 时断言进程退出码 |
阶段结论:✅ 完成,仅 #7 存在测试覆盖缺口。
P1 — 零阶 AST 脱水
ROADMAP 状态:已完成 ✓
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | tree-sitter 语法覆盖:Rust、Python、Go、JavaScript、TypeScript/TSX、HTML、CSS、Zig、Bash、Dart、Kotlin、Java、C、C++、C#、PHP、Swift、Ruby、SQL、Dockerfile、YAML、HCL、Vue、Svelte(23 种语法) | ✅ 完成 | src/extract.rs::Lang、Cargo.toml(23 个 tree-sitter-* 依赖);每个语言族至少一个测试(rust_extracts_sig_import_call、go_extracts_import_signature_and_call、typescript_and_tsx_extract_symbols、dart_kotlin_java_extract_symbols、c_cpp_csharp_extract_symbols、php_swift_ruby_extract_symbols、sql_docker_yaml_hcl_vue_svelte_extract_structure 等) |
| 2 | F | package.json、其他 manifest/lockfile、Makefile、Markdown 安装片段的结构化提取 | ✅ 完成 | dehydrate_package_json、dehydrate_manifest、dehydrate_makefile、dehydrate_markdown;测试 package_json_extracts_lifecycle_scripts、makefile_extracts_targets_and_recipe_lines、markdown_extracts_dangerous_install_commands_only |
| 3 | F | 签名/import/调用提取为扁平 AstSummary JSON 记录 | ✅ 完成 | struct AstSummary、fn dehydrate |
| 4 | F | 跨界引用标记 [EXTERNAL_BLACKBOX] | ✅ 完成 | fn is_external;测试 intra_crate_rust_imports_are_not_external 确认不会对 crate::/super:: 误标 |
| 5 | B | 丢弃注释与函数体;脱水后立即 drop AST(从不保留) | ✅ 完成 | 由实现方式保证:dehydrate() 只返回扁平摘要;main.rs 中任何位置都未保存 tree_sitter::Tree |
| 6 | B | 残缺语法不 panic | ✅ 完成 | 测试 broken_input_no_panic |
| 7 | G | 百兆仓库:内存稳定、不崩溃 | ⬜ 未完成 | 没有已提交的大仓库/压力测试样本,也没有对应规模的 CI job。--benchmark 可以报告常驻内存,但 resident_memory_metric(src/main.rs)仅在 #[cfg(target_os = "linux")] 下实现;macOS 上永远返回 "unavailable",而 CI 的 macos-latest job 从未真正验证过这个指标 |
| 8 | G | extract.rs 测试覆盖典型输入与残缺输入 | ✅ 完成 | extract.rs::tests 内 17 个测试函数,含畸形输入与未知扩展名场景 |
阶段结论:🟡 基本完成。 唯一未验证的门禁是百兆内存稳定性声明;且 macOS(一个受支持的 CI/发布目标)目前完全没有可用的常驻内存指标。
P2 — 模型层(多模型 + 熔断)
ROADMAP 状态:已完成 ✓
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | ModelClient + Transport trait 抽象 | ✅ 完成 | src/model.rs::ModelClient、trait Transport |
| 2 | F | 带 small/large role 路由的 Registry | ✅ 完成 | struct Registry { small, large }、enum Role |
| 3 | F | 每调用硬超时 | ✅ 完成 | UreqTransport 接入 .timeout(timeout);internal-gate PASS「Model transport has a hard timeout」 |
| 4 | F | 连续失败触发熔断 | ✅ 完成 | struct Breaker;测试 timeouts_trip_breaker、bad_status_not_retried_exhausts |
| 5 | F | 指数退避恢复 | ✅ 完成 | fn backoff,在 ModelClient::complete 中调用 |
| 6 | F | 密钥不入日志,Debug 输出脱敏 | ✅ 完成 | impl fmt::Debug for ModelSpec;测试 key_redacted_in_debug |
| 7 | F | 真实 [[model]] TOML 配置解析(role/endpoint/model/key_env/timeout_ms/max_retries) | ✅ 完成 | FileModelConfig;测试 parses_model_blocks、rejects_unknown_model_role、rejects_wrong_types_inside_model_blocks、parses_documented_model_config、local_model_can_omit_key_env |
| 8 | G | 超时/坏响应有模拟测试,熔断确实触发 | ✅ 完成 | model.rs 中 mod tests 的 Fake transport |
| 9 | G | 任何位置都无明文密钥(文档、debug 输出) | ✅ 完成 | internal-gate PASS「Docs avoid direct API key command-line values」;测试 key_redacted_in_debug |
| 10 | B | 不缓存、不持久化模型调用 | ✅ 完成 | Cargo.toml/model.rs 中没有缓存 crate 或磁盘缓存路径 |
阶段结论:✅ 完成。
P3 — ReACT 调度器
ROADMAP 状态:已完成 ✓
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | 有界状态机(max_steps/max_errors) | ✅ 完成 | src/react.rs::ReAct::run |
| 2 | F | 工具调用协议提示(<TOOL_CALL>/<FINAL>) | ✅ 完成 | fn initial_prompt;测试 initial_prompt_declares_tool_protocol |
| 3 | F | $SEED 别名解析为完整 seed 文本作为工具输入 | ✅ 完成 | fn resolve_tool_input;测试 seed_alias_feeds_tool_observation |
| 4 | F | 编译期 enum + match 技能路由(coarse_filter、converge) | ✅ 完成 | src/skills.rs::Skill |
| 5 | G | 未知技能/坏 JSON 熔断为 Partial,绝不 panic | ✅ 完成 | 测试 unknown_skill_trips_to_partial、bad_json_trips_to_partial |
| 6 | G | 触达步数上限返回 Partial 而非无限循环 | ✅ 完成 | 测试 step_cap_returns_partial_not_hang |
| 7 | F | 提示词感知报告语言(en/zh) | ✅ 完成 | 测试 initial_prompt_declares_report_language |
| 8 | F | 注入 scope 规则,测试/样本永不被报成生产风险 | ✅ 完成 | 测试 prompts_carry_scope_rubric |
阶段结论:✅ 完成。
P4 — 确定性 Reduce + 报表
ROADMAP 状态:标题未标 ✓;README 自述「进行中」。这是功能增长最多的阶段,下面拆成三组呈现。
P4a — 确定性账本与 Markdown 报告
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | 确定性 AST 粗筛规则引擎 | ✅ 完成 | src/report.rs::findings_from_seed、push_call_risk、push_supply_chain_risks、push_manifest_risks、push_container_global_risks |
| 2 | F | 严重度 + 路径 scope 分级(Production/CI/Test/TestFixture/Docs,严重度封顶) | ✅ 完成 | PathScope::classify;测试 path_scope_classifies_common_layouts、production_panic_edge_stays_high、panic_edge_in_tests_is_capped_to_low、fixture_supply_chain_is_capped_to_low |
| 3 | F | Markdown 账本渲染器,双语标题 | ✅ 完成 | render_markdown_with_language、render_table_with_language;测试 renders_localized_markdown |
| 4 | F | 显式输入覆盖率报告(候选/脱水/seed 字节/上限/批次) | ✅ 完成 | struct InputCoverage、markdown_section、agent_gate_coverage |
| 5 | F | 单条记录截断可见性(原因、原始字节 vs 压缩后字节) | ✅ 完成 | struct TruncatedRecord、compact_seed_record_with_limits;测试 compact_seed_record_caps_oversized_files;internal-gate PASS「Model seed truncation is reported」 |
| 6 | G | 在已知样本上命中预埋风险 | ✅ 完成 | tests/repo_intake_fixtures.rs(10 个恶意样本 + 1 个良性样本全部通过) |
| 7 | G | 完整审计 stdout 只含最终报告 | ✅ 完成 | internal-gate PASS「Full audit stdout is reserved for the final report」;测试 scan_only_stdout_remains_jsonl_not_benchmark_json |
| 8 | G | 无效配置明确失败,绝不静默回退默认值 | ✅ 完成 | 测试 dirty_values_reject_config_not_silent_default、valid_toml_wrong_types_reject_config_not_silent_default、rejects_dirty_env_lines |
| 9 | G | --module 审计限定在项目根内,不串到全局 | ✅ 完成 | 测试 absolute_module_must_stay_inside_target、absolute_module_inside_target_is_allowed;internal-gate PASS「Module path is contained by project root」 |
| 10 | G | fake-endpoint 完整审计 smoke 证明用户路径可用 | 🟡 部分完成 | 仅有人工证据:reports/full-audit-local-model-test.md 是针对某个本地 OpenAI 兼容端点跑出来的。没有接成自动化/可在 CI 复现的测试(需要 mock HTTP server 或录制好的 fixture 响应)。且该报告早于当前「small-model Map 默认不激活」的行为,已经不能反映当前默认的纯 Reduce 路径 |
P4b — Agent gate 与 policy
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | 稳定文本契约(VERDICT/WHY/BLOCKERS/SAFE_TO_AGENT_RUN) | ✅ 完成 | fn render_agent_gate;tests/repo_intake_fixtures.rs |
| 2 | F | 稳定 JSON 契约(schema_version、verdict、safe_to_agent_run、exit_reason、why、blockers、coverage、findings、policy_actions) | ✅ 完成 | struct AgentGateJson;测试 agent_gate_json_exposes_stable_verdict_shape(黑盒) |
| 3 | F | 仅 SAFE_TO_AGENT_RUN: yes 时退出码为 0,CAUTION/REJECT/INCOMPLETE 均非零 | ✅ 完成 | tests/repo_intake_fixtures.rs(10 个恶意样本均断言非零退出码) |
| 4 | F | 供应链规则集:npm 生命周期脚本、manifest/lockfile 缺口、git/path/http 依赖来源、build.rs 命令边界、shell/Dockerfile 下载后执行、base64 解码后执行、GitHub Actions 权限/触发器风险、secrets 与 shell 耦合、未 pin 的 Actions、Docker root/远程仓库模式、可疑二进制/归档 artifact | ✅ 完成 | tests/fixtures/repo-intake/ 下 21 个样本,由 sift eval-corpus(eval_cases,21 例)与 tests/repo_intake_fixtures.rs 共同验证 |
| 5 | F | 项目本地 sift-policy.toml(max_candidate_files、[[allowlist]]、[[denylist]]、[[severity_override]]) | ✅ 完成 | config.rs 中 load_policy_config/parse_policy_config;测试 parses_policy_schema_and_rejects_bad_severity;report.rs 中 apply_policy/policy_match/policy_override_match |
| 6 | F | 可疑二进制/归档 artifact 清单 | ✅ 完成 | inspect_suspicious_artifact、is_binary_or_archive_name;样本 binary-artifact-exec、binary-extension、archive-payload |
| 7 | F | sift eval-corpus:≥20 例精度表 | ✅ 完成 | run_eval_corpus,21 个 eval_cases;测试 eval_corpus_reports_twenty_or_more_cases |
| 8 | G | 近期回归修复:Cargo.lock 的 registry 来源不再被误判成 git dependency;workflow-write-all 不再把单项 contents:/actions:/packages: write 和真正的 broad write-all 混为一谈;record_truncated > 0 本身不再直接判 INCOMPLETE;VCS 元数据目录(.git、.hg、.svn、.jj)默认从扫描中排除 | ✅ 完成 | 已落地在当前 HEAD f9a374b,覆盖了 reports/project-audit-2026-07-01.md(针对父提交 88c5334 写成)中列出的待办项。证据:测试 ignores_cargo_lock_crates_io_registry_source、flags_broad_but_not_scoped_workflow_write_permissions;scanner.rs::VCS_METADATA_DIRS;report.rs::gate_incomplete_reasons 已不再读取 record_truncated |
| 9 | ⛑ | 本会话已修复的 dogfood 发现: sift . --agent-gate 审计 sift 自身仓库时曾返回 CAUTION,根因是两个真实 bug,现均已修复——详见自我审计 dogfood 检查 | ✅ 完成 | (a) 在 report.rs/extract.rs 中新增 looks_like_eval_invocation,要求独立的 eval 单词后面跟一个 shell 替换词归才算命中,让 “eval corpus” 这类英文行文不再误触 dynamic-shell-eval;测试 flags_real_dynamic_shell_eval_invocation、ignores_eval_used_as_an_english_word、markdown_prose_mentioning_eval_corpus_is_not_a_command。(b) 把 [[allowlist]] policy 匹配从只适用于 RiskFinding 扩展到也适用于 coverage.suspicious_artifacts(新增 apply_policy_to_artifacts/policy_match_artifact),并新增了一个真正的根目录 sift-policy.toml,为 .githooks/pre-commit 和 tests/fixtures/repo-intake/ 下的合成 artifact 加白;测试 policy_allowlist_suppresses_matching_suspicious_artifact、policy_allowlisting_every_artifact_reaches_accept、policy_allowlist_matches_one_tag_within_a_combined_artifact_reason。两项修复后重跑:blocker 归零,但 verdict 仍为 CAUTION——现已确认这是预期中的正确结果,不是 bug(详见 dogfood 部分) |
P4c — 运行模式
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | --benchmark 本地 telemetry(不调用模型;可选 USD 成本估算) | ✅ 完成 | tests/benchmark_mode.rs(3/3 通过) |
| 2 | F | sift github owner/repo 安全 intake——绝不 build/install/跑 hook/碰 submodule;扫描前检查文件/字节上限、.gitmodules、Git LFS | ✅ 完成 | run_github_intake、parse_github_repo、inspect_checkout_dir;测试 github_repo_parser_accepts_owner_repo_and_https、checkout_inspection_reports_lfs_and_limits、github_intake_rejects_non_github_url_without_network(黑盒)。git fetch 与递归调用本地 sift 均跑在 run_command_with_timeout 之下(120s / 600s 硬 deadline,超时即 kill) |
| 3 | F | sift doctor——配置/密钥/端点诊断 | 🟡 部分完成 | 已实现(run_doctor、check_config_permissions、check_file_config、check_endpoint_key_pair 等),但自动化测试覆盖为零——config.rs::tests 里没有任何单测覆盖 run_doctor/Doctor,tests/ 下也没有黑盒测试拉起 sift doctor。内部门禁「每个文件有 #[cfg(test)]」的 BT 检查之所以对 config.rs 显示 PASS,只是因为同一文件里其他函数有测试——它看不见这个缺口 |
| 4 | F | --save/--save-to 持久化报告(reports/sift-audit-result-YYYYMMDD-NNN.md) | ✅ 完成 | main.rs 中 save_audit_result、next_audit_result_path、utc_yyyymmdd、civil_from_days |
| 5 | F | --report-language {en,zh} 双语 Markdown 报告 | ✅ 完成 | ReportLanguage;测试 localized_headings_render_for_zh |
| 6 | F | --debug 额外 stderr 诊断 | ✅ 完成 | main.rs 中的 debug eprintln! 代码块 |
| 7 | B | 小模型 Map(map_small_pool)保留为未激活的诊断脚手架,默认完整审计路径不调用 | 🟡 部分完成(按设计如此) | model.rs 中代码与 4 个测试均存在(small_pool_maps_successful_observations 等),但 main.rs 只打印 "small-model Map inactive: reduce converges from deterministic findings",从不调用它。这与 AGENT.md 的表述完全一致——它被正确标注成脚手架,不是缺陷——但仍是一个尚未决定的路线图问题:是在行为级门禁后重新接入,还是彻底下线 |
阶段结论:🟡 基本完成——与项目自述的「P4 进行中」一致。 真正悬而未决的工程问题是 P4a 的 #10(没有 CI 自动化的完整审计 smoke)和 P4c 的 #3(doctor 无测试);小模型 Map 的去留(P4c #7)是一个明确的待决策问题,不是 bug。
P5 — 内部质量门禁
ROADMAP 状态:标题已在本会话中补上 ✓,功能与门禁本身早已完整落地且全绿。
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | audit.rs 自审模块,覆盖 CQ/SEC/RB/DF/BT/CC/UX 维度评分 | ✅ 完成 | src/audit.rs::run_checks(13 项检查) |
| 2 | F | 把维护者专用报告写入 reports/internal-gate.md(已 gitignore) | ✅ 完成 | write_internal_gate;.gitignore 含 /reports/ |
| 3 | F | 对公开 CLI 隐藏(由 SIFT_INTERNAL_GATE=1 触发,不是文档化 flag) | ✅ 完成 | main.rs 中 internal_gate_target();测试 self_audit_flag_is_not_public_cli_argument 确认不存在 --self-audit flag |
| 4 | F | 接入 make internal-gate / make ci | ✅ 完成 | Makefile;本次会话验证(make ci 退出码 0) |
| 5 | G | 硬规则无 FAIL/WARN,包括无 broad dead_code allow、无原始中文源码字符串、报告流边界干净、seed 截断可见 | ✅ 完成 | 本次会话现跑结果:13/13 PASS,0 WARN,0 FAIL(reports/internal-gate.md) |
| 6 | ⚑ | 测试覆盖检查(BT)只到文件粒度 | 🟡 已知局限 | test_coverage_status 只检查文件里某处是否含 #[cfg(test)]——无法探测某个具体函数(例如 run_doctor)在一个整体有测试的文件里其实完全没被测。见 P4c #3 |
阶段结论:✅ 完成。 ROADMAP.md/ROADMAP.zh.md 的 P5 标题已在本会话中改为「— done ✓ / 已完成 ✓」以匹配现状,剩余建议是把 BT 检查收紧到函数级粒度。
P6 — 发布加固
ROADMAP 状态:标题未标 ✓,但已有相当充分的证据。
| # | Type | 条目 | 状态 | 证据 |
|---|---|---|---|---|
| 1 | F | 体积调优的 release profile(opt-level=z、lto、codegen-units=1、strip、panic=abort) | ✅ 完成 | Cargo.toml::[profile.release] |
| 2 | F | Makefile 安装/卸载路径(默认 ~/.local/bin,可用 PREFIX/BINDIR 覆盖) | ✅ 完成 | Makefile 的 install/uninstall target |
| 3 | F | Git hooks 安装/卸载;pre-commit 跑 make local-ci | ✅ 完成 | Makefile 的 githooks-install/githooks-uninstall;.githooks/pre-commit |
| 4 | F | CI:在 ubuntu-latest + macos-latest 矩阵上跑 fmt/test/clippy/internal-gate | ✅ 完成 | .github/workflows/ci.yml |
| 5 | F | Release workflow:SemVer 标签校验、macOS amd64/arm64 构建、tar.xz + sha256、environment 审批后 draft→published | ✅ 完成 | .github/workflows/release.yml;已有标签 v0.1.0、v0.2.0 |
| 6 | F | Homebrew tap 自动发布(渲染并推送 jamiesun/homebrew-tap formula) | ✅ 完成 | release.yml::homebrew job;依赖仓库 secret HOMEBREW_TAP_TOKEN 是否配置,这一点超出本仓库自身可验证的范围 |
| 7 | F | 更多语法 | ⏳ 待定(开放式) | 已交付 23 种 tree-sitter 语法 + 4 种结构化提取器(见 P1);ROADMAP 有意将其保持无上限,因此永远无法标记为「完全完成」 |
| 8 | F | --benchmark、--agent-gate --format json、eval-corpus 的稳定 JSON 输出契约(schema_version) | ✅ 完成 | benchmark_mode_outputs_stable_json_without_model_keys、agent_gate_json_exposes_stable_verdict_shape 均断言 schema_version: 1 |
| 9 | G | 单文件分发 | ✅ 完成 | release.yml 把单个 sift 二进制(+ docs/README/config 模板)打进一个 tar.xz |
| 10 | G | 内部门禁通过 | ✅ 完成 | 见 P5 |
| 11 | G | 文档 ↔ 功能一致 | 🟡 部分完成(仅人工) | 没有任何自动化检查会把文档(支持语言列表、CLI flag、版本号)与源码事实来源做 diff;本次会话通过人工交叉阅读验证,但**make ci 无法捕捉未来的漂移** |
| 12 | G | brew install jamiesun/tap/sift 由 release checksum 支撑 | ✅ 完成(未做外部复核) | release.yml 中已有 sha256/formula 渲染逻辑;本次会话未对真实的 jamiesun/homebrew-tap 仓库做独立复核 |
阶段结论:🟡 基本完成。 两条悬而未决的线:文档↔代码一致性没有自动化守卫;「更多语法」是有意保持无上限的目标,而不是一个可以关闭的门禁。
横切检查:工程契约(ROADMAP.md)
| # | 规则 | 状态 | 证据 |
|---|---|---|---|
| 1 | 标记完成的阶段有行为级证据,不只是类型层接线 | ✅ P0–P3 成立;🟡 上文标注了两处例外(P0 #7、P4c #3) | |
| 2 | 完整审计 stdout 是最终报告;--scan-only 是 JSONL;诊断信息不进 stdout | ✅ 完成 | 见 P4a #7 |
| 3 | 报告披露扫描/脱水/送入模型/跳过/截断的规模 | ✅ 完成 | InputCoverage、AgentGateCoverage |
| 4 | 用户配置缺失时从安全默认值自动创建;配置文件存在但无效时必须失败,不能回退默认值 | ✅ 完成 | 见 P4a #8 |
| 5 | src/ 内运行时文本、prompt、注释只用英文 | ✅ 完成 | internal-gate PASS「Program source avoids raw CJK literals」 |
横切检查:完成的样子(ROADMAP.md)
| # | 标准 | 状态 |
|---|---|---|
| 1 | 零配置可跑;自动创建 ~/.sift/config.toml;缺 Key 即退给提示;不挂起 | ✅ 完成 |
| 2 | 百兆仓库内存稳定;坏输入不崩溃 | ⬜ 未完成——见 P1 #7 |
| 3 | 报表定位行号 + 跨模块依赖 + 并发/资源风险 | ✅ 完成 |
| 4 | 报表声明输入覆盖率和截断状态;覆盖不完整时绝不能看起来像完整结论 | ✅ 完成 |
| 5 | 任一外部调用必超时;失败即熔断出半成品,绝不死磕 | ✅ 完成——模型 HTTP 调用(model.rs)与 GitHub intake 子进程(run_command_with_timeout,120s/600s)均已验证 |
| 6 | 同一二进制审项目与 --module 不串 | ✅ 完成 |
| 7 | 内部发布门禁无 FAIL,硬规则无 WARN | ✅ 完成 |
非目标护栏
确认 ROADMAP.md 里「绝不做」的铁律没有被越界。
| # | 非目标 | 是否守住 | 证据 |
|---|---|---|---|
| 1 | 不做向量库/embedding/RAG | ✅ 守住 | Cargo.toml 依赖列表中没有向量库/embedding crate |
| 2 | 不做运行时插件/动态技能注册 | ✅ 守住 | skills.rs::Skill 是编译期 enum + match;无动态加载类依赖 |
| 3 | 不做服务化/Web UI/多租户 | ✅ 守住 | Cargo.toml 中无 web-server crate;只通过 clap 提供 CLI |
| 4 | 不允许 panic 主进程 | ✅ 守住(启发式,非形式化证明) | internal-gate 对显式 panic! 和 unwrap()/expect() 字面模式检查均 PASS。注意:release profile 里的 panic = "abort" 只是改变了一旦真的 panic时的 unwind 行为,本身并不是「不会 panic」的保证;真正的保证来自源码文本扫描,它无法捕捉例如下标越界/溢出类 panic |
| 5 | 不允许无超时阻塞 | ✅ 守住 | 模型调用:model.rs 中的 ureq timeout;子进程:run_command_with_timeout(git fetch 120s,递归调用本地 sift 600s,超时即 kill) |
| 6 | 模块审计不能膨胀成全局 | ✅ 守住 | 见 P4a #9 |
| 7 | 不靠「直接试用」替代审计 | ✅ 守住 | sift github 无论 flag 如何都绝不 build/install/跑 hook/碰 submodule;GithubCli 上的 --no-build/--no-install 是明确的安全意图标记,不是开关——工具本来就两种情况下都不会 build 或 install |
| 8 | 脚手架不得冒充产品能力 | ✅ 守住 | 小模型 Map 在代码输出和文档中都被明确标成「未激活的诊断脚手架」,不计入已交付的默认行为 |
| 9 | 不允许静默回退 | ✅ 守住 | 见 P4a #8;无效配置总是明确失败 |
自我审计 dogfood 检查
AGENT.md 写道「sift 必须通过内部发布门禁」。这句话其实涉及两个不同的门禁,本清单刻意把它们分开:
- 内部质量门禁(
SIFT_INTERNAL_GATE=1,即make internal-gate)——sift 自身的代码质量门禁。结果:13/13 PASS,0 FAIL,0 WARN。 ✅ 这正是 ROADMAP.md 和 AGENT.md 所说的门禁,且是全绿的。 - Agent gate(
sift . --agent-gate)——用来在 agent 执行 setup/build/install 之前,筛查任意第三方仓库的产品功能。ROADMAP 并没有要求 sift 用这个门禁审自己的仓库必须得到 ACCEPT,但拿它做一次 dogfood 检查很有意义。
第一次跑分(本会话开始时):发现两个真实 bug
VERDICT: CAUTION
SAFE_TO_AGENT_RUN: no
coverage: candidate_files=69 dehydrated_files=62 unsupported_files=7
record_truncated=12 seed_bytes=148402
- 规则误报:
docs/ROADMAP.zh.md等行文文件被标成dynamic-shell-eval(scope=docs,MEDIUM),原因纯粹是英文短语 “eval corpus”(正是 sift 自己的eval-corpus功能名)包含子串"eval ",而looks_like_dynamic_shell_eval(src/report.rs)和looks_like_shell_command(src/extract.rs)对这个子串都是无条件匹配。这不是真正的 shell-eval 风险。 - 未加白但合法的 artifact:
.githooks/pre-commit(一个没有扩展名、真实存在且已提交的可执行文件)和两个已提交的测试样本(archive-payload/assets/payload.tar.gz、binary-extension/bin/tool.dylib)触发了可疑 artifact 规则。项目根目录没有真正的sift-policy.toml(只有sift-policy.example.toml),而即使有,policy 的[[allowlist]]匹配也只适用于RiskFinding,从未覆盖过coverage.suspicious_artifacts——所以这些 blocker 根本无法被压制。
本会话落地的修复
- 在
report.rs和extract.rs中都新增了looks_like_eval_invocation(单词边界 + shell 替换词 token 检查),让eval只在这个独立单词后面紧跟 command substitution、反引号或$变量时才命中,永远不会误读提到 “eval corpus”/“retrieval” 的英中文行文。测试:flags_real_dynamic_shell_eval_invocation、ignores_eval_used_as_an_english_word、markdown_prose_mentioning_eval_corpus_is_not_a_command。 - 把 policy 引擎扩展为
[[allowlist]]也能压制suspicious_artifactsblocker,用rule匹配 artifact 的reason标签(apply_policy_to_artifacts、policy_match_artifact,均在report.rs;也处理了逗号拼接的多 reason 情况)。测试:policy_allowlist_suppresses_matching_suspicious_artifact、policy_allowlisting_every_artifact_reaches_accept、policy_allowlist_matches_one_tag_within_a_combined_artifact_reason。 - 新增了一个真正的根目录
sift-policy.toml(之前只有sift-policy.example.toml),为.githooks/pre-commit和tests/fixtures/repo-intake/下的合成 artifact 加白,每条都写了理由。sift-policy.example.toml也同步补充了 artifact 加白写法的文档示例。
第二次跑分(修复后):blocker 消失,但 verdict 仍然(正确地)为 CAUTION
VERDICT: CAUTION
SAFE_TO_AGENT_RUN: no
coverage: candidate_files=72 dehydrated_files=64 unsupported_files=8
record_truncated=12 seed_bytes=148542
BLOCKERS: none
POLICY:
- suppressed artifact extensionless_or_binary_executable at .githooks/pre-commit by allowlist (...)
- suppressed artifact binary_or_archive_extension at tests/fixtures/repo-intake/archive-payload/assets/payload.tar.gz by allowlist (...)
- suppressed artifact binary_or_archive_extension at tests/fixtures/repo-intake/binary-extension/bin/tool.dylib by allowlist (...)
(给未来编辑本节的人一个提醒:如果把这两条规则的触发形状写得过于具体、可直接复现,就会让这个文件自己触发它们。请让任何这类示例都保持适度改写。)
两个根因都已修复并验证:eval 误报消失了(唯一剩下的一条 dynamic-shell-eval 是一个真实的 shell 调用样本——bash 内联 -c 命令并插值了一个 secret,正是预期中应该被标出的),且三个未加白 artifact blocker 现在都已被写有理由的白名单压制。
但 verdict 仍然是 CAUTION,现在已确认这是预期中的正确结果——不是需要追逐消除的缺陷。 剩余的 40 条发现全部是 Severity::Low,没有 Medium/High,且每一条都能追溯到下面两个有意设计的来源之一:
tests/fixtures/repo-intake/下 21 个合成攻击模式样本(与sift eval-corpus评分用的是同一份语料)。它们的存在就是为了证明供应链规则引擎能检测到npm-lifecycle-script、download-execute、dependency-git-source、workflow-write-all等。如果自扫让这些发现消失,那说明规则坏了,而不是修好了。tests/*.rs里的panic-edge(.expect()/.unwrap())发现。铁律 #1 只禁止在src/里用unwrap()/expect(),在测试里用它们完全正常且正确,PathScope::classify也已经把这些封顶到 Low——它们依旧会以发现形式出现(信息性的),只是不能被静静藏起来。
Agent gate 的 verdict 规则(render_agent_gate)只有在 findings 完全为空时才返回 ACCEPT。强行让 sift 自己的仓库做到这一点,只能靠删除自己的回归语料,或者对 tests/ 下所有规则一概加白,这两种做法都会抹掉本清单 P4a/P4b 行引用的证据。因此诚实、经得起推敲的 dogfood 结论应该是:0 条 High 发现、0 条无法解释的 blocker、每一条 Low 发现都有归属——而不是字面上的 ACCEPT。
汇总:待办事项
把上文所有非 ✅ 完成的条目汇总在一处。上一份快照中的两项已在本会话中解决,此处不再列入(agent gate 自审 CAUTION 的根因已修复;ROADMAP P5 标题已刷新)——前者详见自我审计 dogfood 检查。
| 事项 | 阶段 | 状态 | 建议下一步 |
|---|---|---|---|
| 没有黑盒测试断言「完整审计缺 Key 时退出码为 1」 | P0 | 🟡 部分完成 | 在 tests/ 下新增一个集成测试 |
没有百兆压力测试样本;macOS 上常驻内存指标永远是 "unavailable" | P1 | ⬜ 未完成 | 新增大仓库 smoke 测试;把 resident_memory_metric 扩展到 macOS(task_info/ps) |
| fake-endpoint 完整审计 smoke 仅为人工验证,未接入 CI,且早于当前小模型 Map 默认不激活的行为 | P4a | 🟡 部分完成 | 新增一个基于 mock HTTP server、端到端跑通 react::ReAct 的集成测试 |
原有的 policy 压制逻辑(针对 RiskFinding 的 apply_policy/policy_match/policy_override_match)没有直接的端到端单测验证压制本身——只测试了 TOML 解析(parses_policy_schema_and_rejects_bad_severity)。本会话新增的 artifact 加白路径有测试,但原有的 finding 加白路径仍然没有 | P4b | 🟡 部分完成 | 在 report.rs 中为 apply_policy/denylist/severity-override 新增单测,参照新增的 policy_allowlist_* artifact 测试写法 |
sift doctor 自动化测试覆盖为零 | P4c | 🟡 部分完成 | 为 Doctor/run_doctor 补单测,和/或新增 tests/doctor.rs 黑盒测试 |
| 小模型 Map 是未激活脚手架;重新接入还是下线仍未决定 | P4c | 🟡 部分完成(按设计如此) | 由维护者决策,之后要么接到行为级门禁之后,要么删除 |
| 「更多语法」没有固定目标 | P6 | ⏳ 待定 | 不算缺陷;按语言诉求逐条建 issue 跟踪,而不是靠本清单 |
| 文档 ↔ 代码一致性没有自动化守卫 | P6 | 🟡 部分完成 | 可以考虑在 audit.rs 里加一条检查,把 README.md 的支持语言列表和 extract.rs::Lang 的变体做交叉核对 |
如何刷新本快照
cargo build
make ci # fmt-check + test + clippy -D warnings + internal-gate
cat reports/internal-gate.md # P5 门禁细节(已 gitignore,仅本地)
cargo run --quiet -- . --agent-gate --format json # 现跑一次自我扫描(对应上文 dogfood 检查)
sift eval-corpus # repo-intake 精度表
本文件反映的是某一个提交时间点的状态。每当某个阶段的证据发生变化,请重新执行上面的命令, 并更新「快照信息」表、各阶段表格与「汇总:待办事项」——不要在没有重新核对证据的情况下手改状态标记。
AGENT.md — sift 贡献者手册
English | 中文
给参与 sift 的人与 agent 的实现手册:铁律、结构、习惯的事实来源。画像/边界见 ROADMAP.zh.md。
sift 是什么
可控成本的单二进制开源审计器:tree-sitter 脱水 → 确定性粗筛账本 → 大模型收敛(Reduce),由 ReACT 状态机调度。审项目或单模块。小模型 Map 代码暂作为未激活的诊断脚手架保留,直到有行为级门禁再重新接入。sift 必须通过内部发布门禁。
铁律
src/内禁unwrap()/expect()。 脏数据走 Result/Option 分支丢弃+记日志;主进程绝不 panic。- 每个外部调用(子进程/网络/模型)必有硬超时。 无界阻塞即 bug;连错触发熔断;熔断后退避/降级/出半成品,绝不死磕。
- 单二进制、低依赖。 无向量库、无 embedding/RAG、无数据库、无缓存;纯文本管道,阅后即焚。
- 技能仅编译期写死。 技能 = enum + match 本地函数;无动态加载、无运行时插件。
- 流式、内存与规模脱钩。 有界通道,脱水后即 drop AST,常驻内存压低位。
- 密钥降级寻址。 CLI key file > ENV > 项目
.env>~/.sift/config.toml> 默认;缺大模型 Key 立退给提示,绝不挂起或交互追问。缺用户配置时自动创建不含密钥的默认配置。 - 密钥仅 env/文件。 不编译进、不提交、不打印、不入日志。
- 模块审计不膨胀成全局。 跨界引用打
[EXTERNAL_BLACKBOX],不追链。 - TDD。 每个
src/*.rs自带单测;新子系统单测同建。 - 中英双语、默认英文。 每文档有 ZH 副本(
docs/*.zh.md);英文为准,跨语言范围/命令/规则须一致。 - 禁止玩具门禁或虚假能力声明。 脚手架代码必须明确标成 scaffold,并隔离在显式模式后面;只有行为级门禁证明后,才能算阶段完成。
- 输出契约稳定。
--scan-only可以向 stdout 写 JSONL;sift query向 stdout 写 grep 风格证据行或单个 JSON 文档;完整审计的 stdout 只留给最终报告。进度、诊断、模型遥测走 stderr 或 reports,不能混进报告流。 - 禁止静默降级。 截断、跳过文件、模型回退、半成品报告、无效配置和解析失败,必须体现在输出、退出码或内部门禁证据里。无效配置文件必须失败,不能悄悄回默认值。
- 程序源码只用英文。
src/内运行时字符串、prompt 和源码注释使用英文;双语用户文档保留在docs/*.zh.md。
任一铁律违反即内部门禁 FAIL。
模块地图
| 路径 | 责任 | 阶段 |
|---|---|---|
src/main.rs | 装配:解析→Config→调度→报表→退出码 | P0 ✓ |
src/config.rs | 降级寻址、多模型配置 | P0 ✓→P2 |
src/scanner.rs | Walk + 有界通道 | P0 ✓ |
src/extract.rs | tree-sitter 脱水 → AstSummary | P1 ✓ |
src/query.rs | 无状态证据检索(重扫 + regex 过滤) | P1 ✓ |
src/model.rs | 模型注册表/客户端/超时/熔断 | P2 ✓ |
src/react.rs | ReACT 状态机 + 技能 match | P3 ✓ |
src/skills.rs | 本地技能函数(map/reduce) | P3 ✓→P4 |
src/report.rs | Markdown 风险清单 | P4 |
src/audit.rs | 内部门禁评分 | P5 |
工作流
cargo build # 必须绿
cargo test # 必须过
cargo fmt && cargo clippy # 提交前清
make ci # 对齐本地发布门禁
rg 'unwrap\(|expect\(|panic!' src # 必须为 0
rg '[\p{Han}]' src # 必须为 0
- 一次提交一个关注点;带
Co-authored-by: Copilottrailer。 - 加功能前查是否越 ROADMAP 非目标;越界先改铁律。
- 阶段内部门禁和至少一个用户路径行为 smoke 不绿,不算完成。
- 如果阶段使用脚手架,文档和代码必须明确写出仍未完成的部分。
习惯
- 全程 Result/Option;每个等待都有界;临时数据尽早 drop。
- 模块各守责任,不跨层乱伸手。
- 优先生态 crate,但拒重依赖。
- 报表入
reports/(gitignore);审计不脏化跟踪文件。 - 宁可明确失败,也不要输出一份看起来完整但实际不完整的审计报告。