fold/AGENTS.md
trvon c9811b7d41
fix(scan): report unanalyzed translation units and exit 2
Problem: a TU the front end could not parse left no trace. ClangTool::run's
status was discarded, TUs with compile errors were analyzed on a partial
AST and counted as done, and the isolated-worker path dropped TUs that
never produced an AST. A scan of only broken files printed no findings and
exited 0, the same as a clean scan. Separately, the scan logger wrote to
stdout, so any warning corrupted a SARIF/JSON report written to stdout.

Fix: every TU now yields an outcome (clean, compile-errors,
analysis-failed); workers carry it on their file_finished record, and the
fan-out parent adds timeout/crash/worker-exit for the TU a worker died on.
tuCoverage() turns the requested file list plus outcomes into a
ScanCoverage (requested TUs with no outcome are frontend-failed). SARIF
gets a run `invocations` entry (executionSuccessful, one
toolExecutionNotification per gap); findings JSON gets an additive
`coverage` object. A scan with any gap, or with nothing analyzed, exits 2
after writing its report. CLI diagnostics now go to stderr, as
docs/internal/cli-philosophy.md principle 12 requires. USAGE.md and
AGENTS.md document the coverage fields and the exit code.

Test: tests/unit/test_scan_coverage.cpp (classification, path spelling,
complete/untracked controls, merge, both writers, and unchanged output
without coverage); test_cli_paths "[coverage]" covers a clean TU (exit 0,
complete) and a TU with a missing include (exit 2, gap in JSON, SARIF on
stdout parseable with executionSuccessful=false). Before: both probe files
exited 0 with no coverage information. Full suite: 139/139.
2026-09-24 20:37:51 -06:00

5.4 KiB

description argument-hint
Fold repo supplement — SAST boundaries, router contract, and validation rules [TASK=<description>] [MODE=engineering] [PHASE=<start|checkpoint|complete>]

AGENTS.md (Repo Supplement)

Operating model (retrieval contract, execution loop, metadata, handoff, ask-first list): ../yams/docs/prompts/PROMPT-eng-codex.md. C++ design/TDD/ assertion/profiling flow: ../yams/docs/prompts/PROMPT-yams-cpp-workflow.md. Use ../yams/AGENTS.md as the reference style for keeping this file lean: generic agent behavior belongs in the shared prompt files; this file holds only what is specific to Fold.

Project Contract

fold is a C++ SAST with two analysis paths:

  • Source path: Clang LibTooling plus bounded Z3-backed detectors.
  • Binary path: radare2-backed heuristic extraction for strings, symbols, calls, sections, xrefs, and simple pseudocode patterns.

Keep these paths separate unless a task explicitly asks for router work. Do not try to lift binary instructions into the source symbolic IR as a shortcut.

CLI And Router Work

Read docs/research/cli-router.md before changing scan-mode behavior.

The intended CLI contract is:

  • fold scan --mode source ... runs only the source backend.
  • fold scan --mode binary --binary <path> runs only the binary backend.
  • fold scan --mode hybrid --compile-db <db> --binary <path> runs both through the scan router.
  • fold scan --mode auto ... chooses hybrid when both source and binary inputs are present.

Compatibility requirements:

  • Keep existing --input-type source|binary behavior until explicitly removed.
  • Existing fold scan --input-type binary --binary <path> commands must keep working.
  • Exit codes stay stable: 0 no actionable findings, 1 actionable findings, 2 input/config/report failure, including a scan that left any requested translation unit unanalyzed (see coverage in docs/USAGE.md).
  • Reports must preserve per-finding backend provenance.

Router implementation rules:

  • Put orchestration in include/fold/cli/scan_router.hpp and src/cli/scan_router.cpp when implementation starts.
  • Prefer small router-specific structs over widening AnalysisBackend.
  • Hybrid mode should begin as merge-only; add hint exchange only after tests prove independent source and binary behavior is unchanged.
  • Binary hints may focus source work, but must not silently hide source coverage unless the user requested a capped hybrid candidate set.
  • Source hints may focus binary decompilation, but raw binary string/symbol scans should still run unless explicitly disabled.

Backend Boundaries

  • include/fold/core/analysis_backend.hpp is intentionally narrow.
  • ClangBackend owns source analysis and delegates to the mature detector pipeline.
  • R2Backend owns radare2 sessions and binary heuristics.
  • Keep binary findings heuristic unless there is source/Z3 corroboration.
  • Keep source solver metrics and binary heuristic metrics separate in profiles.

Validation

Run focused tests for the area you changed, then the full suite for CLI/router or backend changes:

ninja -C build/release
meson test -C build/release --print-errorlogs
git diff --check

Useful focused tests:

meson test -C build/release fold:scan_e2e --print-errorlogs
meson test -C build/release fold:binary_backend --print-errorlogs
meson test -C build/release fold:subprocess_fanout --print-errorlogs
meson test -C build/release fold:cross_tu_security --print-errorlogs

For binary or hybrid changes, run a curl source/binary smoke if the fixture paths are available in the workspace.

Worktree Safety

This repository often has unrelated local edits from benchmark and experiment runs. Do not revert or rewrite files you did not intentionally modify. Keep changes minimal and check git diff --check before handing off.

Cross-Language Wire Formats

When a production path spans Rust and C++ (fold-rust NDJSON → RustBackend parsing, fold-swift NDJSON → SwiftBackend parsing), JSON/NDJSON key names must be centralized in a single source of truth. Do not duplicate string literals across Rust and C++.

Canonical key homes:

  • IR expression trees: include/fold/ir/expr_json.hpp (namespace json_keys).
  • Frontend record envelopes (info/error/function/finding/summary/ symbolic-summary/interface/ir) and their data keys: include/fold/frontend/ndjson_keys.hpp (namespace fold::frontend::ndjson_keys).

Rules:

  • C++ serialization (toJson) and deserialization (fromJson, backend parsing) MUST use these constants, not raw string literals.
  • Rust/other-language producers MUST match the canonical names. Document the expected keys in a comment block at the emitter site (tools/fold-rust/src/main.rs near FindingData, tools/fold-swift/ fold_swift.cpp near emitFinding).
  • When a new expression shape (Call, Member, ArraySubscript, etc.) or NDJSON record shape is added, add its canonical keys to the owning header first, then implement on both sides.
  • New finding_kind values must also be added to FindingKind + findingKindName() (src/report/finding.cpp) and to the backend parse tables; unknown wire kinds map to FindingKind::UnknownForeign with a warning, never silently relabeled.
  • fromJson may accept legacy key aliases (read: j.value("canonical_key", j.value("legacy_key", default))) for backward compatibility, but MUST prefer the canonical form on write.