chore(ci): pin all GitHub Actions by commit SHA#116
Merged
Conversation
Closes the 16 open `PinnedDependenciesID` Scorecard alerts. Each `uses:` reference now carries a 40-char commit SHA plus a `# ratchet:org/repo@<tag>` comment marking the original constraint so the version remains greppable. Generated with `ratchet pin .github/workflows/*.yml` (sethvargo/ratchet v0.11.4). Dependabot continues to bump SHAs based on the trailing tag comment; `ratchet upgrade` can re-sync any drift later. Workflows affected: ci, dependency-review, release, scorecard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jahala
added a commit
that referenced
this pull request
May 17, 2026
Patch release: search cap fix, NFS walker narrow, dep refreshes, community standards, SHA-pinned workflows. Includes (since v0.8.2): - fix(search): --full raises match cap from 10 to 100 (#65 / #111) - fix(walker): stop at mount boundaries, recognize composer.json (#110) - chore(deps): tree-sitter-scala 0.24 → 0.26 (#114) - chore(deps): tree-sitter-python 0.23.6 → 0.25 (#115) - chore(deps): actions/checkout v4 → v6 (#113) - chore(ci): pin all GitHub Actions by commit SHA (#116) - docs: community standards files + Scorecard badge (#109) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
paulnsorensen
referenced
this pull request
in paulnsorensen/tilth
May 18, 2026
* perf(diff): parallelise overlay construction across files Extracted from #61 part 4 (rayon parallelisation, diff overlay half). Independent of the timeout (#82) and batch-edit work — touches only src/diff/mod.rs. `compute_overlay` is pure per-`FileDiff` work: each file independently fetches old/new content, runs tree-sitter, and matches symbols. Switching the overlay build from `.iter()` to `.par_iter()` lets multi-file diffs fan out across cores — a meaningful win on large PRs where each file does non-trivial AST work. Thread safety: `compute_overlay` constructs its own `tree_sitter::Parser::new()` per call inside `lang::outline::get_outline_entries` — no shared mutable state crosses worker boundaries. `FileDiff` and `DiffSource` are Send+Sync via auto trait derivation. Scope deliberately limited to `diff::diff()` — left the per-commit overlay loop in `diff_log()` alone since each commit's overlay set is typically small. Also left the `tool_read` batch parallelisation from the original #61 out of this PR pending the deadline-preservation discussion (review item #5). Test plan - cargo build — clean - cargo test — 344 pass - cargo clippy -- -D warnings — clean - cargo fmt --check — clean * feat(mcp): batch tilth_edit — accept files: [{path, edits}] Extracted from #61 part 3 (batch tilth_edit). Independent of the timeout (#82) and diff-overlay (#83) extractions; touches src/edit.rs (the new apply_batch + FileEditTask), src/mcp.rs (new tool_edit + parsing helpers + schema), and AGENTS.md (user-facing docs). What changes for callers - tilth_edit now takes {"files": [{"path", "edits"}, ...]} instead of {"path", "edits"}. Each file is processed independently in parallel — a hash mismatch on one file no longer blocks its siblings. - Per-file results render as Markdown sections (## <path>) joined by --- separators. Returns isError only when every file failed. - Cap of 20 files per call, surfaced both in the JSON Schema (maxItems) and at runtime. Why the schema break is safe - MCP serves tools/list dynamically on every session; clients always get the current inputSchema. The LLM sees the new shape on next connect and adapts. There is no cached-schema problem to migrate around. Review fixes from #61 layered in here - #4 (duplicate paths → data corruption): detect_duplicate_paths now rejects the whole batch up front when two Ready tasks resolve to the same canonical path. Falls back to the literal path for files that don't exist yet, so the dedup key is still well-defined. - Medium (parse_file_edit short-circuit): parse_edit_entry returns the failing edit index in its error message, so the LLM can fix exactly the right entry instead of guessing which edit was malformed. - Medium (tests use tempfile::tempdir): new batch tests use tempdir rather than fixed-name paths in std::env::temp_dir. Implementation shape - src/edit.rs: pub apply_batch + pub FileEditTask::{Ready, ParseError}, with apply_one / render_applied keeping the parallel closure trivial. apply_edits and EditResult are now private — callers go through apply_batch. - src/mcp.rs: tool_edit shrinks from ~85 lines of inline JSON parsing to parse → cap-check → dedup-check → record_read → apply_batch. JSON parsing stays at the MCP wire boundary; parallel I/O and blast radius live next to apply_edits. Test plan - cargo build — clean - cargo test — 348 pass (4 new in edit::tests covering two-file success, partial failure, all-failed, and parse-error surfacing — all using tempfile::tempdir) - cargo clippy -- -D warnings — clean - cargo fmt --check — clean * refactor(error): adopt thiserror for Display/Error derives NIH audit finding — replace 25 lines of mechanical write! formatting in `src/error.rs` with `thiserror::Error` derives. Each variant gets an inline `#[error(...)]` attribute; the manual `impl Display` and empty `impl std::error::Error` blocks are removed. Net: +11 / -40 in src/error.rs; one new dependency (thiserror v2). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(diff): adopt strsim::sorensen_dice for fuzzy symbol matching NIH audit finding — replace hand-rolled `jaccard_similarity` (whitespace-set Jaccard) in `src/diff/matching.rs` Phase 3 fuzzy matcher with `strsim::sorensen_dice` (character-bigram). Same [0.0, 1.0] range, same 0.8 threshold, no rescaling needed. Net: +1 / -15 in src/diff/matching.rs; one new dependency (strsim). Existing tests `fuzzy_match` and `below_fuzzy_threshold` still pass at the unchanged 0.8 threshold. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(install): adopt home crate for cross-platform home dir lookup Replace the cfg-split `$HOME` / `$USERPROFILE` block in `home_dir()` with a one-liner around `home::home_dir()`. The `home` crate is maintained by the cargo team itself (rust-lang/cargo) and handles a corner case the hand-rolled version misses: an empty `$HOME` is treated as missing rather than silently producing an empty `PathBuf` that joins onto relative paths. Behavior preserved: - Same env-var precedence ($HOME on unix, $USERPROFILE on windows). - Wrapper kept (4 call sites) with an actionable error message that names both env vars. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(mcp): adopt percent-encoding crate for file:// URI decoding Replace the hand-rolled `percent_decode` + `hex_val` helpers (`src/mcp.rs`) with `percent_encoding::percent_decode_str`. Single call site in `extract_root_from_response`, so the helpers are inlined rather than wrapped. The fallback contract on invalid UTF-8 is preserved: rather than substituting U+FFFD replacement characters via `decode_utf8_lossy`, fall back to the raw undecoded input so the subsequent `is_dir()` check rejects mangled paths cleanly. The unit-test for the deleted helper (`percent_decode_basic`) is removed; end-to-end coverage of the same code path lives in `extract_root_percent_encoded_uri`, which exercises a real `file://` URI with `%20` resolving to a real directory. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(read): adopt strsim::levenshtein for filename / heading suggestions Replace the 16-LOC hand-rolled Wagner-Fischer implementation in `src/read/mod.rs` with a thin wrapper around `strsim::levenshtein`. Two call sites — `suggest_similar` (filename "did you mean?") and `suggest_headings` (markdown heading suggestion) — so the wrapper is kept rather than inlined. `strsim::levenshtein` wraps `generic_levenshtein` over `StringWrapper`, which iterates `.chars()`. Same Unicode-scalar semantics as the hand-rolled version: a single CJK or emoji glyph counts as one edit unit, not 3-4 bytes. The `edit_distance_is_unicode_aware` regression test (CJK + emoji + ASCII) is updated to lock in this contract — if `strsim` ever switches to byte-level distance, the test fails loudly. Note: PR #88 (`refactor-strsim` for diff fuzzy matching) also adds `strsim = "0.11"`. Whichever lands first, the second can drop the duplicate Cargo.toml line in a trivial rebase. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: char-boundary panic in is_minified_by_name with multi-byte UTF-8 stem.len() - 4 is a byte offset, not a char offset — it can land inside a CJK or emoji character, causing a panic. Use char_indices().nth_back(3) to find the byte offset of the 4th character from the end instead. * Update dependencies in Cargo.toml Fixed an error I did when manually resolving a conflict * chore: bump version to v0.8.1 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(bloom): adopt fastbloom for BloomFilter implementation NIH audit finding — drop ~105 LOC of hand-rolled bloom filter math in `src/index/bloom.rs` (`double_hash`, `combined_hash`, `hash_with_seed`, optimal-bit/hash sizing) in favor of `fastbloom`, the SIMD-optimized de facto Rust crate. The `extract_identifiers` byte state machine — the unique-to-tilth piece — stays. `BloomFilterCache` now wraps `fastbloom::BloomFilter` constructed via `with_false_pos(0.01).expected_items(n)`. Drop `test_bloom_filter_sizing` — it asserted private fields (`num_bits`, `num_hashes`) of the removed type. Equivalent guarantees are part of fastbloom's own test suite. Net: +5 / -133 in src/index/bloom.rs; one new dependency (fastbloom). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(mcp): extract per-request timeout into its own module Extracted from #61 part 2 (timeout module). Depends on the Services struct from #63 (already merged) for the per-instance ThreadTracker. Moves the inline mpsc::recv_timeout machinery in handle_tool_call into src/timeout.rs as spawn_with_timeout, backed by crossbeam-channel's select! { default(timeout) => ... } — the sync-world equivalent of Future.get(timeout, unit). Two correctness changes vs. the original #61 timeout draft, addressing the review feedback there: - ThreadTracker::is_at_cap uses Ordering::Acquire (was Relaxed). Pairs with the Release in record_timeout / record_finish_after_timeout, so a load that observes the new count also observes any state the incrementing thread published before it. - The deadline arm now CAS-claims the timeout *before* incrementing the tracker (was: increment, then CAS, then conditionally roll back). The worker, if it lost the CAS, spins on a new timeout_acked AtomicBool before decrementing — so the tracker can never go negative and a concurrent is_at_cap() can never observe an inflated count that gets rolled back. Closes the false-rejection window the reviewer flagged near the hard cap. Also wires an upfront tracker.is_at_cap() check in handle_tool_call so the server refuses new work cleanly under sustained pressure instead of piling on more abandoned threads. ThreadTracker is owned by Services rather than a static global, so unit tests instantiate their own and don't serialise on shared state. No behaviour change for the happy path: same 90s default, same TILTH_TIMEOUT env override, same client-visible "tool timed out" / "tool panicked" messages. Test plan - cargo build — clean - cargo test — 349 pass (5 new in timeout::tests covering the CAS roundtrip, fast path, panic surfacing, saturated tracker, and env parsing) - cargo clippy -- -D warnings — clean - cargo fmt --check — clean * fix(timeout): address PR 82 review feedback - Switch tracker RMWs to AcqRel — canonical pessimistic ordering for an atomic counter read from another thread. Release/Acquire was sufficient for the counter value alone, but AcqRel documents the cross-thread contract more explicitly so the next reader doesn't have to re-derive that the value composes with the rest of the spawn state machine. - Add std::thread::yield_now() after spin_loop() in wait_for_timeout_ack so a worker scheduled before the main thread on a single-CPU container surrenders the rest of its quantum instead of burning ~10ms before the ack becomes visible. - Doc-comment ABANDONED_THREAD_WARN as a deliberate warn-once policy so a future maintainer doesn't switch the deadline arm's `==` check back to `>=`. The hard cap at MAX_ABANDONED_THREADS bounds the silence past the threshold. - Mark SpawnFailure as #[non_exhaustive] so a future failure mode (e.g. OS-level thread spawn failure) can be added without churning every call site. - Relax abandoned_counter_roundtrips_through_cas deadline from 2s to 5s to reduce flake risk on contended CI runners. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(edit): address PR 84 review — path dedup, empty edits, encapsulation - Moves normalize_path_key and detect_duplicate_paths from mcp.rs to edit.rs - Adds macOS case-insensitive APFS dedup (ASCII-lowercasing on cfg target_os) - Adds runtime validation for empty edits array (was schema-only; now parse error) - Moves dedup gate into apply_batch to prevent wire-layer bypass - Simplifies apply_one/render_applied signatures (&Arc<T> → &T with deref-coercion) - Updates AGENTS.md documentation (isError semantics, parse-error behavior) - Adds 5 tests (case aliases, empty edits, integration gate) Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump version to v0.8.2 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: oss-hygiene — supply-chain workflows and harden permissions Add three supply-chain hygiene workflows and tighten GITHUB_TOKEN permissions on existing workflows. All free for public repos. New: - .github/dependabot.yml — weekly version updates for cargo, npm, github-actions - .github/workflows/dependency-review.yml — block PRs that introduce vulnerable or disallowed-license deps (free on public repos) - .github/workflows/scorecard.yml — OpenSSF Scorecard analysis and badge publication Hardened: - ci.yml — add top-level `permissions: contents: read` (least privilege) - release.yml — drop top-level `contents: write`; scope it to the `build` job that actually needs it for softprops/action-gh-release. Publish jobs use CARGO_REGISTRY_TOKEN / NPM_TOKEN secrets and don't need any GITHUB_TOKEN write scope. Generated via the /oss-hygiene skill from skillz-that-grillz. * chore(deps): bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump softprops/action-gh-release from 2 to 3 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](softprops/action-gh-release@v2...v3) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump actions/upload-artifact from 4 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump actions/dependency-review-action from 4 to 5 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4 to 5. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](actions/dependency-review-action@v4...v5) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump actions/setup-node from 4 to 6 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@v4...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * fix(scorecard): pin to v2.4.3 — @v2 ref doesn't exist Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(deps): bump the cargo-minor-and-patch group with 7 updates Bumps the cargo-minor-and-patch group with 7 updates: | Package | From | To | | --- | --- | --- | | [clap](https://github.com/clap-rs/clap) | `4.5.60` | `4.6.1` | | [clap_complete](https://github.com/clap-rs/clap) | `4.5.66` | `4.6.5` | | [tree-sitter-rust](https://github.com/tree-sitter/tree-sitter-rust) | `0.24.0` | `0.24.2` | | [tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) | `0.24.1` | `0.24.2` | | [tree-sitter-c-sharp](https://github.com/tree-sitter/tree-sitter-c-sharp) | `0.23.1` | `0.23.5` | | [tree-sitter-swift](https://github.com/alex-pinkus/tree-sitter-swift) | `0.7.1` | `0.7.2` | | [rayon](https://github.com/rayon-rs/rayon) | `1.11.0` | `1.12.0` | Updates `clap` from 4.5.60 to 4.6.1 - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](clap-rs/clap@clap_complete-v4.5.60...clap_complete-v4.6.1) Updates `clap_complete` from 4.5.66 to 4.6.5 - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](clap-rs/clap@clap_complete-v4.5.66...clap_complete-v4.6.5) Updates `tree-sitter-rust` from 0.24.0 to 0.24.2 - [Release notes](https://github.com/tree-sitter/tree-sitter-rust/releases) - [Commits](tree-sitter/tree-sitter-rust@v0.24.0...v0.24.2) Updates `tree-sitter-c` from 0.24.1 to 0.24.2 - [Release notes](https://github.com/tree-sitter/tree-sitter-c/releases) - [Commits](tree-sitter/tree-sitter-c@v0.24.1...v0.24.2) Updates `tree-sitter-c-sharp` from 0.23.1 to 0.23.5 - [Release notes](https://github.com/tree-sitter/tree-sitter-c-sharp/releases) - [Commits](tree-sitter/tree-sitter-c-sharp@v0.23.1...v0.23.5) Updates `tree-sitter-swift` from 0.7.1 to 0.7.2 - [Release notes](https://github.com/alex-pinkus/tree-sitter-swift/releases) - [Commits](alex-pinkus/tree-sitter-swift@0.7.1...0.7.2) Updates `rayon` from 1.11.0 to 1.12.0 - [Changelog](https://github.com/rayon-rs/rayon/blob/main/RELEASES.md) - [Commits](rayon-rs/rayon@rayon-core-v1.11.0...rayon-core-v1.12.0) --- updated-dependencies: - dependency-name: clap dependency-version: 4.6.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-minor-and-patch - dependency-name: clap_complete dependency-version: 4.6.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-rust dependency-version: 0.24.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-c dependency-version: 0.24.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-c-sharp dependency-version: 0.23.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-swift dependency-version: 0.7.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: rayon dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump toml from 0.8.23 to 1.1.2+spec-1.1.0 Bumps [toml](https://github.com/toml-rs/toml) from 0.8.23 to 1.1.2+spec-1.1.0. - [Commits](toml-rs/toml@toml-v0.8.23...toml-v1.1.2) --- updated-dependencies: - dependency-name: toml dependency-version: 1.1.2+spec-1.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-javascript from 0.23.1 to 0.25.0 Bumps [tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript) from 0.23.1 to 0.25.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-javascript/releases) - [Commits](tree-sitter/tree-sitter-javascript@v0.23.1...v0.25.0) --- updated-dependencies: - dependency-name: tree-sitter-javascript dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-go from 0.23.4 to 0.25.0 Bumps [tree-sitter-go](https://github.com/tree-sitter/tree-sitter-go) from 0.23.4 to 0.25.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-go/releases) - [Commits](tree-sitter/tree-sitter-go@v0.23.4...v0.25.0) --- updated-dependencies: - dependency-name: tree-sitter-go dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * fix(structured): use toml::from_str for document parsing; add regression tests The `content.parse::<toml::Value>()` path relied on toml 0.8's FromStr parsing a full document. In toml 1.1, FromStr instead parses a single value and rejects further content, so any TOML file rendered by toml_outline would show "[parse error: unexpected content]" instead of the section/scalar tree. Switching to `toml::from_str(content)` is forward-and-backward compatible (works on both 0.8 and 1.1) and matches the deserialize path already used in `overview.rs` for Cargo.toml/pyproject.toml. Pinned four regression tests on `toml_outline` covering the failure mode this fixed: - Top-level table renders as `[section]` headers (Value::Table arm) - Tables beyond max_depth collapse to `{N keys}` summary - Arrays render as `[N items]` (Value::Array arm) - Parse errors surface as `[parse error: ...]` markers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(deps): bump tree-sitter from 0.25.10 to 0.26.8 Bumps [tree-sitter](https://github.com/tree-sitter/tree-sitter) from 0.25.10 to 0.26.8. - [Release notes](https://github.com/tree-sitter/tree-sitter/releases) - [Commits](tree-sitter/tree-sitter@v0.25.10...v0.26.8) --- updated-dependencies: - dependency-name: tree-sitter dependency-version: 0.26.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * fix: stop walker at mount boundaries, recognize composer.json Two minimal hardening fixes for the issues @quanhavn surfaced in #62: 1. `same_file_system(true)` on the WalkBuilder in both `walker()` and `find_basename_fallback()`. Stops traversal at mount-point boundaries so NFS / OrbStack / external-volume directories accessible from a project root don't get walked into. Pairs with the existing `follow_links(true)` — symlinks are still followed, just not across device boundaries. 2. `composer.json` added to `MANIFESTS` in `package_root()`. PHP / Laravel projects use composer.json as their manifest; without this, scope resolution falls through to a parent and pulls in unrelated trees. Scope deliberately narrower than #62 — drops `follow_links(false)` (would break pnpm, npm/yarn workspaces, macOS `/tmp` symlinks) and the `.git`-as-manifest fallback (would expand scope to the entire git repo for any project lacking a manifest). Closes #62. Co-Authored-By: quanhavn <anhquanhh4@gmail.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(deps): bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-scala from 0.24.0 to 0.26.0 Bumps [tree-sitter-scala](https://github.com/tree-sitter/tree-sitter-scala) from 0.24.0 to 0.26.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-scala/releases) - [Commits](tree-sitter/tree-sitter-scala@v0.24.0...v0.26.0) --- updated-dependencies: - dependency-name: tree-sitter-scala dependency-version: 0.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-python from 0.23.6 to 0.25.0 Bumps [tree-sitter-python](https://github.com/tree-sitter/tree-sitter-python) from 0.23.6 to 0.25.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-python/releases) - [Commits](tree-sitter/tree-sitter-python@v0.23.6...v0.25.0) --- updated-dependencies: - dependency-name: tree-sitter-python dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * docs: add community standards files + Scorecard badge (#109) Closes #97 (scaffold CoC / CONTRIBUTING / SECURITY / issue + PR templates). Closes half of #98 (Scorecard badge; Best Practices badge pending registration). - CODE_OF_CONDUCT.md — references Contributor Covenant 2.1, brief - CONTRIBUTING.md — light tone, points at CLAUDE.md and the three local gates - SECURITY.md — private vuln reporting via GitHub advisory flow - .github/ISSUE_TEMPLATE/{bug_report,feature_request,config}.yml — minimal, blank issues disabled - .github/PULL_REQUEST_TEMPLATE.md — summary + 3-line test plan checklist - README.md — OpenSSF Scorecard badge near the title Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(search): --full raises match cap from 10 to 100 (#65) (#111) Without --full, search/callers cap at 10 matches and show "... and N more matches" — users asking for the full picture were getting a truncated view. With --full, the cap is raised to 100 and the walker's early-quit threshold is raised proportionally (300), so all real matches become visible up to a generous bound. The cap-bump is gated strictly on the parsed --full flag, never on the piped-derived `full = cli.full || !is_tty`. This preserves the existing contract (`piped_invocation_does_not_auto_expand` test): subprocess / pipeline callers must keep the concise outline by default. To wire this through cleanly, run_inner and run_expanded now take an explicit cli_full parameter alongside the existing full (which stays as the file-content semantic for FilePath queries). Symbol, content, callers, and regex search all honor --full. The new symbol::full_flag_raises_match_cap test pins the cap behavior on a synthetic 15-file repo. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(ci): pin all GitHub Actions by commit SHA (#116) Closes the 16 open `PinnedDependenciesID` Scorecard alerts. Each `uses:` reference now carries a 40-char commit SHA plus a `# ratchet:org/repo@<tag>` comment marking the original constraint so the version remains greppable. Generated with `ratchet pin .github/workflows/*.yml` (sethvargo/ratchet v0.11.4). Dependabot continues to bump SHAs based on the trailing tag comment; `ratchet upgrade` can re-sync any drift later. Workflows affected: ci, dependency-review, release, scorecard. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version to v0.8.3 (#117) Patch release: search cap fix, NFS walker narrow, dep refreshes, community standards, SHA-pinned workflows. Includes (since v0.8.2): - fix(search): --full raises match cap from 10 to 100 (#65 / #111) - fix(walker): stop at mount boundaries, recognize composer.json (#110) - chore(deps): tree-sitter-scala 0.24 → 0.26 (#114) - chore(deps): tree-sitter-python 0.23.6 → 0.25 (#115) - chore(deps): actions/checkout v4 → v6 (#113) - chore(ci): pin all GitHub Actions by commit SHA (#116) - docs: community standards files + Scorecard badge (#109) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(fuzz): cargo-fuzz harness + nightly CI + OSS-Fuzz prep (#118) * feat(fuzz): scaffold cargo-fuzz with three targets Addresses the OpenSSF Scorecard FuzzingID alert. Adds the fuzz/ workspace with three initial targets covering tilth's primary input surfaces: - outline — feeds bytes through `read::outline::code::outline` against all 18 Lang variants. Exercises tree-sitter parsing + traversal + signature extraction + formatter. ~1900 exec/sec; primary attack surface ("any file on disk in any language"). - strip — feeds bytes through `search::strip::strip_noise` across 6 representative paths. ~26k exec/sec. Past bugs have lived in multi- byte UTF-8 handling. - diff_parse — feeds bytes through `diff::parse::parse_unified_diff`. Our own parser (not a crate boundary). ~78k exec/sec. All three pass 20s smoke runs with no findings. Plan and design notes in docs/research/fuzzing-plan.md (gitignored). The `__fuzz` module re-exports the fuzz surface without widening pub(crate) visibility elsewhere. Marked `#[doc(hidden)]` and documented as unstable. Tasks tracked: FUZZ-A1, FUZZ-B1, FUZZ-B2, FUZZ-B3 (done). Next: corpus seeding, CI nightly workflow, OSS-Fuzz application. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(fuzz): nightly CI, SECURITY.md, OSS-Fuzz artifacts Builds on the scaffolding commit: - .github/workflows/fuzz.yml — nightly cron + workflow_dispatch, runs each target for 5 min (configurable), caches corpus across runs, uploads crash artifacts on failure. Does NOT block PR merges. - SECURITY.md — documents the security testing surface (unit tests, CodeQL, Scorecard, fuzz). Includes local repro recipe. - oss-fuzz/projects/tilth/ — ready-to-submit OSS-Fuzz artifacts (project.yaml, Dockerfile, build.sh). README explains the submission workflow. Also: fmt fix on lib.rs (single-line wrapper signature), and `#[must_use]` on outline / strip_noise wrappers (now reachable publicly via __fuzz, so clippy::must_use_candidate fires under the pedantic lint group). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fuzz: preserve OOM finding as tracked seed 5-min outline target run surfaced one OOM finding: a 9KB Rust-shaped input (real source from ripgrep's globset crate) causes tree-sitter to allocate >2GB inside ts_parser_parse. Likely one of the 14 grammars we iterate is choking on input that's the wrong language for it. Preserved as fuzz/seeds/outline/oom-rust-imports-9kb so future runs have it as a permanent seed. Diagnostics + fix tracked separately (task FUZZ-FIX-1). The two other targets (strip, diff_parse) completed 1.82M and 7.92M runs respectively with no findings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(edit): remove cwd race in normalize_path_key (root-cause flaky test) normalize_path_key used std::path::absolute() to resolve `./foo` vs `foo` aliases for nonexistent files. absolute() reads current_dir() — process- global mutable state. Another test (mcp::tests::scope_handoff_when_cwd_is_root) calls set_current_dir("/") and restores it; under parallel test execution, the two normalize_path_key calls inside detect_duplicate_paths could see different cwds, producing different keys, breaking the dedup invariant. Surfaced as a flaky CI failure on PR #118: thread 'edit::tests::dedup_catches_nonexistent_alias_spellings' panicked: alias spellings should collide Fix: replace std::path::absolute() with pure-lexical normalization (strip CurDir components, walk ParentDir against the in-memory stack). No filesystem touch, no current_dir() call → deterministic under parallel tests. Trade-off: `/abs/foo.rs` and `foo.rs` (cwd=/abs) no longer dedup when neither exists. Acceptable — agents typically send all-relative or all-absolute paths in one batch; canonicalize still handles the common case (when at least one of the paths exists, canonicalize succeeds for it). Three new lexical_normalize unit tests pin the CurDir-strip, ParentDir-pop, and absolute-preserve invariants. A new normalize_path_key_is_cwd_independent test pins the race-free property as a load-bearing guarantee. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(edit): preserve unresolved ParentDir in lexical_normalize v1 of lexical_normalize had a real bug surfaced during edge-case audit: `../foo.rs` and `foo.rs` would normalize to the same key, but they point to DIFFERENT files (one in parent dir, one in cwd). v1 popped `..` at the empty stack, silently collapsing the path. Fixed: track whether the path is absolute; on `ParentDir`, only pop if the tail is a real (Normal) segment, else preserve `..` (relative paths) or no-op (absolute paths at root, matching Linux's `/.. == /`). New test pins the regression: assert_ne!(normalize_path_key("../foo.rs"), normalize_path_key("foo.rs")) Plus tests for the multi-level case (foo/bar/../../../baz.rs → ../baz.rs), absolute /foo/../bar.rs → /bar.rs, and /.. → /. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(edit): track normal_count instead of peeking with next_back Replaces `out.components().next_back()` in lexical_normalize (which iterates the whole PathBuf to inspect the tail, O(N) per ParentDir hit) with a `normal_count: usize` running counter. The decision becomes O(1) per component, dropping the function from O(N·K) to O(N) overall. Behavior identical — all 23 edit tests still pass without modification. The smell flagged in the previous audit (iterator allocation just to peek at the tail) is gone; the intent (\"can I pop a real segment?\") is now expressed directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version to v0.8.4 (#119) Patch release. Adds cargo-fuzz infrastructure (3 fuzz targets, nightly CI, OSS-Fuzz artifacts) and root-causes the cwd-race in normalize_path_key that was producing flaky dedup test failures on Linux CI. Includes (since v0.8.3): - feat(fuzz): cargo-fuzz harness + nightly CI + OSS-Fuzz prep (#118) - fix(edit): normalize_path_key is now race-free + lexically correct for `..` (handled as part of #118 audit) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(bench): align rg_trait_implementors prompt with ground truth The task asserted `find_at` was in the agent's final answer, but the prompt never mentioned methods — only \"where each implementor is defined and what crate it lives in.\" Sonnet tended to mention method names anyway (more verbose answers); haiku tended to answer literally and omit them, producing a ~33% spurious failure rate. Fixed by updating the prompt to ask explicitly for the trait's required methods, with `find_at` as an example. Verified: 3/3 haiku reps pass after the change (vs 2/3 before). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(search): --full raises match cap from 10 to 100 Re-adds upstream PR #111's behavior on top of the current SymbolMode signature deferred during the sync (PR #34). The CLI's `--full` flag now raises search/callers cap from 10 to 100 with proportional walker early-quit thresholds (300 = 100 × 3). Gated strictly on the *parsed* cli.full — piped-derived `full = cli.full || !is_tty` does NOT bump the cap. MCP and library `_raw` callsites pass `false` to preserve token budgets. Adds `full_flag_raises_match_cap` test in symbol search plus content, regex, callers cap-raise tests and a source-level MCP `full=false` regression guard. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(search,cli,ci): apply age findings on the sync PR Curing /age findings on PR #34 (sync upstream jahala v0.8.4 + adapted PR 111 cap raise): Security (workflows) - fuzz.yml: move duration_seconds input to job env so it cannot inject shell at the run step; add a seed-copy step so the committed corpus seeds (e.g. fuzz/seeds/outline/oom-rust-imports-9kb) are actually fed to the fuzzer instead of sitting unreachable. - release.yml: explicit `permissions: {}` on publish-crate / publish-npm so they cannot inadvertently elevate to contents:write. Encapsulation + complexity (search slice) - Add search::callers::find_callers_batch_default that hardcodes the default BATCH_EARLY_QUIT threshold internally. Demote BATCH_EARLY_QUIT back to private const. External callers in diff/, search::blast, search::deps, and search::mod tests now use the wrapper instead of reaching into callers internals. - Hoist FULL_MAX_MATCHES and FULL_EARLY_QUIT_THRESHOLD to one location in search::mod (pub(crate)); drop the three duplicates from symbol/content/callers. The definitions-vs-usages thresholds are no longer a hardcoded 300 — they share the FULL_MAX_MATCHES * 3 form. Library API (lib.rs) - Collapse pub run / run_full / run_expanded into a single pub fn run(query, scope, cache, RunConfig). Introduce MatchCap enum (Default | Extended) to replace the cli_full: bool parameter so the public API no longer carries a CLI-flag-named bool. run_inner drops from 10 args to 4; the #[allow(too_many_arguments)] suppression is gone. ExpandedCtx.full_search renamed to cap: MatchCap. - main.rs updated to construct RunConfig and pass MatchCap. Gates: cargo build --release, cargo test (497 pass), cargo clippy --release -- -D warnings, cargo fmt --check all clean. CLI smoke test confirms `tilth fn` → 10 matches default, `tilth fn --full` → 100. Deferred (not silently dropped; rationale in cure report): - path-clean crate migration of lexical_normalize: skipped pending an intentional supply-chain trade-off decision. - fuzz/Cargo.lock dual maintenance: belongs in a dedicated supply-chain PR alongside the existing oss-hygiene work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(edit): preserve leading `..` on Windows drive-relative paths `lexical_normalize` flipped `is_absolute = true` on any `Component::Prefix(_)`, but on Windows a path like `C:foo` parses as `[Prefix("C:"), Normal("foo")]` with no `RootDir` — it is drive-relative (anchored to that drive's cwd), not absolute. With the old code, a leading `..` in such a path was silently dropped instead of being preserved as unresolved, which could produce incorrect dedup keys / collisions in `detect_duplicate_paths`. Split the match arm so only `RootDir` flips the flag. `Prefix(_)` is still pushed onto the output but does not change the absoluteness state. Caught by Copilot review on PR #34. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: jensenojs <jensenojs@qq.com> Co-authored-by: jahala <jahala@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: quanhavn <anhquanhh4@gmail.com>
paulnsorensen
referenced
this pull request
in paulnsorensen/tilth
May 22, 2026
* perf(diff): parallelise overlay construction across files Extracted from #61 part 4 (rayon parallelisation, diff overlay half). Independent of the timeout (#82) and batch-edit work — touches only src/diff/mod.rs. `compute_overlay` is pure per-`FileDiff` work: each file independently fetches old/new content, runs tree-sitter, and matches symbols. Switching the overlay build from `.iter()` to `.par_iter()` lets multi-file diffs fan out across cores — a meaningful win on large PRs where each file does non-trivial AST work. Thread safety: `compute_overlay` constructs its own `tree_sitter::Parser::new()` per call inside `lang::outline::get_outline_entries` — no shared mutable state crosses worker boundaries. `FileDiff` and `DiffSource` are Send+Sync via auto trait derivation. Scope deliberately limited to `diff::diff()` — left the per-commit overlay loop in `diff_log()` alone since each commit's overlay set is typically small. Also left the `tool_read` batch parallelisation from the original #61 out of this PR pending the deadline-preservation discussion (review item #5). Test plan - cargo build — clean - cargo test — 344 pass - cargo clippy -- -D warnings — clean - cargo fmt --check — clean * feat(mcp): batch tilth_edit — accept files: [{path, edits}] Extracted from #61 part 3 (batch tilth_edit). Independent of the timeout (#82) and diff-overlay (#83) extractions; touches src/edit.rs (the new apply_batch + FileEditTask), src/mcp.rs (new tool_edit + parsing helpers + schema), and AGENTS.md (user-facing docs). What changes for callers - tilth_edit now takes {"files": [{"path", "edits"}, ...]} instead of {"path", "edits"}. Each file is processed independently in parallel — a hash mismatch on one file no longer blocks its siblings. - Per-file results render as Markdown sections (## <path>) joined by --- separators. Returns isError only when every file failed. - Cap of 20 files per call, surfaced both in the JSON Schema (maxItems) and at runtime. Why the schema break is safe - MCP serves tools/list dynamically on every session; clients always get the current inputSchema. The LLM sees the new shape on next connect and adapts. There is no cached-schema problem to migrate around. Review fixes from #61 layered in here - #4 (duplicate paths → data corruption): detect_duplicate_paths now rejects the whole batch up front when two Ready tasks resolve to the same canonical path. Falls back to the literal path for files that don't exist yet, so the dedup key is still well-defined. - Medium (parse_file_edit short-circuit): parse_edit_entry returns the failing edit index in its error message, so the LLM can fix exactly the right entry instead of guessing which edit was malformed. - Medium (tests use tempfile::tempdir): new batch tests use tempdir rather than fixed-name paths in std::env::temp_dir. Implementation shape - src/edit.rs: pub apply_batch + pub FileEditTask::{Ready, ParseError}, with apply_one / render_applied keeping the parallel closure trivial. apply_edits and EditResult are now private — callers go through apply_batch. - src/mcp.rs: tool_edit shrinks from ~85 lines of inline JSON parsing to parse → cap-check → dedup-check → record_read → apply_batch. JSON parsing stays at the MCP wire boundary; parallel I/O and blast radius live next to apply_edits. Test plan - cargo build — clean - cargo test — 348 pass (4 new in edit::tests covering two-file success, partial failure, all-failed, and parse-error surfacing — all using tempfile::tempdir) - cargo clippy -- -D warnings — clean - cargo fmt --check — clean * refactor(error): adopt thiserror for Display/Error derives NIH audit finding — replace 25 lines of mechanical write! formatting in `src/error.rs` with `thiserror::Error` derives. Each variant gets an inline `#[error(...)]` attribute; the manual `impl Display` and empty `impl std::error::Error` blocks are removed. Net: +11 / -40 in src/error.rs; one new dependency (thiserror v2). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(diff): adopt strsim::sorensen_dice for fuzzy symbol matching NIH audit finding — replace hand-rolled `jaccard_similarity` (whitespace-set Jaccard) in `src/diff/matching.rs` Phase 3 fuzzy matcher with `strsim::sorensen_dice` (character-bigram). Same [0.0, 1.0] range, same 0.8 threshold, no rescaling needed. Net: +1 / -15 in src/diff/matching.rs; one new dependency (strsim). Existing tests `fuzzy_match` and `below_fuzzy_threshold` still pass at the unchanged 0.8 threshold. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(install): adopt home crate for cross-platform home dir lookup Replace the cfg-split `$HOME` / `$USERPROFILE` block in `home_dir()` with a one-liner around `home::home_dir()`. The `home` crate is maintained by the cargo team itself (rust-lang/cargo) and handles a corner case the hand-rolled version misses: an empty `$HOME` is treated as missing rather than silently producing an empty `PathBuf` that joins onto relative paths. Behavior preserved: - Same env-var precedence ($HOME on unix, $USERPROFILE on windows). - Wrapper kept (4 call sites) with an actionable error message that names both env vars. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(mcp): adopt percent-encoding crate for file:// URI decoding Replace the hand-rolled `percent_decode` + `hex_val` helpers (`src/mcp.rs`) with `percent_encoding::percent_decode_str`. Single call site in `extract_root_from_response`, so the helpers are inlined rather than wrapped. The fallback contract on invalid UTF-8 is preserved: rather than substituting U+FFFD replacement characters via `decode_utf8_lossy`, fall back to the raw undecoded input so the subsequent `is_dir()` check rejects mangled paths cleanly. The unit-test for the deleted helper (`percent_decode_basic`) is removed; end-to-end coverage of the same code path lives in `extract_root_percent_encoded_uri`, which exercises a real `file://` URI with `%20` resolving to a real directory. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(read): adopt strsim::levenshtein for filename / heading suggestions Replace the 16-LOC hand-rolled Wagner-Fischer implementation in `src/read/mod.rs` with a thin wrapper around `strsim::levenshtein`. Two call sites — `suggest_similar` (filename "did you mean?") and `suggest_headings` (markdown heading suggestion) — so the wrapper is kept rather than inlined. `strsim::levenshtein` wraps `generic_levenshtein` over `StringWrapper`, which iterates `.chars()`. Same Unicode-scalar semantics as the hand-rolled version: a single CJK or emoji glyph counts as one edit unit, not 3-4 bytes. The `edit_distance_is_unicode_aware` regression test (CJK + emoji + ASCII) is updated to lock in this contract — if `strsim` ever switches to byte-level distance, the test fails loudly. Note: PR #88 (`refactor-strsim` for diff fuzzy matching) also adds `strsim = "0.11"`. Whichever lands first, the second can drop the duplicate Cargo.toml line in a trivial rebase. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: char-boundary panic in is_minified_by_name with multi-byte UTF-8 stem.len() - 4 is a byte offset, not a char offset — it can land inside a CJK or emoji character, causing a panic. Use char_indices().nth_back(3) to find the byte offset of the 4th character from the end instead. * Update dependencies in Cargo.toml Fixed an error I did when manually resolving a conflict * chore: bump version to v0.8.1 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(bloom): adopt fastbloom for BloomFilter implementation NIH audit finding — drop ~105 LOC of hand-rolled bloom filter math in `src/index/bloom.rs` (`double_hash`, `combined_hash`, `hash_with_seed`, optimal-bit/hash sizing) in favor of `fastbloom`, the SIMD-optimized de facto Rust crate. The `extract_identifiers` byte state machine — the unique-to-tilth piece — stays. `BloomFilterCache` now wraps `fastbloom::BloomFilter` constructed via `with_false_pos(0.01).expected_items(n)`. Drop `test_bloom_filter_sizing` — it asserted private fields (`num_bits`, `num_hashes`) of the removed type. Equivalent guarantees are part of fastbloom's own test suite. Net: +5 / -133 in src/index/bloom.rs; one new dependency (fastbloom). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(mcp): extract per-request timeout into its own module Extracted from #61 part 2 (timeout module). Depends on the Services struct from #63 (already merged) for the per-instance ThreadTracker. Moves the inline mpsc::recv_timeout machinery in handle_tool_call into src/timeout.rs as spawn_with_timeout, backed by crossbeam-channel's select! { default(timeout) => ... } — the sync-world equivalent of Future.get(timeout, unit). Two correctness changes vs. the original #61 timeout draft, addressing the review feedback there: - ThreadTracker::is_at_cap uses Ordering::Acquire (was Relaxed). Pairs with the Release in record_timeout / record_finish_after_timeout, so a load that observes the new count also observes any state the incrementing thread published before it. - The deadline arm now CAS-claims the timeout *before* incrementing the tracker (was: increment, then CAS, then conditionally roll back). The worker, if it lost the CAS, spins on a new timeout_acked AtomicBool before decrementing — so the tracker can never go negative and a concurrent is_at_cap() can never observe an inflated count that gets rolled back. Closes the false-rejection window the reviewer flagged near the hard cap. Also wires an upfront tracker.is_at_cap() check in handle_tool_call so the server refuses new work cleanly under sustained pressure instead of piling on more abandoned threads. ThreadTracker is owned by Services rather than a static global, so unit tests instantiate their own and don't serialise on shared state. No behaviour change for the happy path: same 90s default, same TILTH_TIMEOUT env override, same client-visible "tool timed out" / "tool panicked" messages. Test plan - cargo build — clean - cargo test — 349 pass (5 new in timeout::tests covering the CAS roundtrip, fast path, panic surfacing, saturated tracker, and env parsing) - cargo clippy -- -D warnings — clean - cargo fmt --check — clean * fix(timeout): address PR 82 review feedback - Switch tracker RMWs to AcqRel — canonical pessimistic ordering for an atomic counter read from another thread. Release/Acquire was sufficient for the counter value alone, but AcqRel documents the cross-thread contract more explicitly so the next reader doesn't have to re-derive that the value composes with the rest of the spawn state machine. - Add std::thread::yield_now() after spin_loop() in wait_for_timeout_ack so a worker scheduled before the main thread on a single-CPU container surrenders the rest of its quantum instead of burning ~10ms before the ack becomes visible. - Doc-comment ABANDONED_THREAD_WARN as a deliberate warn-once policy so a future maintainer doesn't switch the deadline arm's `==` check back to `>=`. The hard cap at MAX_ABANDONED_THREADS bounds the silence past the threshold. - Mark SpawnFailure as #[non_exhaustive] so a future failure mode (e.g. OS-level thread spawn failure) can be added without churning every call site. - Relax abandoned_counter_roundtrips_through_cas deadline from 2s to 5s to reduce flake risk on contended CI runners. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(edit): address PR 84 review — path dedup, empty edits, encapsulation - Moves normalize_path_key and detect_duplicate_paths from mcp.rs to edit.rs - Adds macOS case-insensitive APFS dedup (ASCII-lowercasing on cfg target_os) - Adds runtime validation for empty edits array (was schema-only; now parse error) - Moves dedup gate into apply_batch to prevent wire-layer bypass - Simplifies apply_one/render_applied signatures (&Arc<T> → &T with deref-coercion) - Updates AGENTS.md documentation (isError semantics, parse-error behavior) - Adds 5 tests (case aliases, empty edits, integration gate) Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump version to v0.8.2 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: oss-hygiene — supply-chain workflows and harden permissions Add three supply-chain hygiene workflows and tighten GITHUB_TOKEN permissions on existing workflows. All free for public repos. New: - .github/dependabot.yml — weekly version updates for cargo, npm, github-actions - .github/workflows/dependency-review.yml — block PRs that introduce vulnerable or disallowed-license deps (free on public repos) - .github/workflows/scorecard.yml — OpenSSF Scorecard analysis and badge publication Hardened: - ci.yml — add top-level `permissions: contents: read` (least privilege) - release.yml — drop top-level `contents: write`; scope it to the `build` job that actually needs it for softprops/action-gh-release. Publish jobs use CARGO_REGISTRY_TOKEN / NPM_TOKEN secrets and don't need any GITHUB_TOKEN write scope. Generated via the /oss-hygiene skill from skillz-that-grillz. * chore(deps): bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump softprops/action-gh-release from 2 to 3 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](softprops/action-gh-release@v2...v3) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump actions/upload-artifact from 4 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump actions/dependency-review-action from 4 to 5 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4 to 5. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](actions/dependency-review-action@v4...v5) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump actions/setup-node from 4 to 6 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@v4...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * fix(scorecard): pin to v2.4.3 — @v2 ref doesn't exist Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(deps): bump the cargo-minor-and-patch group with 7 updates Bumps the cargo-minor-and-patch group with 7 updates: | Package | From | To | | --- | --- | --- | | [clap](https://github.com/clap-rs/clap) | `4.5.60` | `4.6.1` | | [clap_complete](https://github.com/clap-rs/clap) | `4.5.66` | `4.6.5` | | [tree-sitter-rust](https://github.com/tree-sitter/tree-sitter-rust) | `0.24.0` | `0.24.2` | | [tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) | `0.24.1` | `0.24.2` | | [tree-sitter-c-sharp](https://github.com/tree-sitter/tree-sitter-c-sharp) | `0.23.1` | `0.23.5` | | [tree-sitter-swift](https://github.com/alex-pinkus/tree-sitter-swift) | `0.7.1` | `0.7.2` | | [rayon](https://github.com/rayon-rs/rayon) | `1.11.0` | `1.12.0` | Updates `clap` from 4.5.60 to 4.6.1 - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](clap-rs/clap@clap_complete-v4.5.60...clap_complete-v4.6.1) Updates `clap_complete` from 4.5.66 to 4.6.5 - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](clap-rs/clap@clap_complete-v4.5.66...clap_complete-v4.6.5) Updates `tree-sitter-rust` from 0.24.0 to 0.24.2 - [Release notes](https://github.com/tree-sitter/tree-sitter-rust/releases) - [Commits](tree-sitter/tree-sitter-rust@v0.24.0...v0.24.2) Updates `tree-sitter-c` from 0.24.1 to 0.24.2 - [Release notes](https://github.com/tree-sitter/tree-sitter-c/releases) - [Commits](tree-sitter/tree-sitter-c@v0.24.1...v0.24.2) Updates `tree-sitter-c-sharp` from 0.23.1 to 0.23.5 - [Release notes](https://github.com/tree-sitter/tree-sitter-c-sharp/releases) - [Commits](tree-sitter/tree-sitter-c-sharp@v0.23.1...v0.23.5) Updates `tree-sitter-swift` from 0.7.1 to 0.7.2 - [Release notes](https://github.com/alex-pinkus/tree-sitter-swift/releases) - [Commits](alex-pinkus/tree-sitter-swift@0.7.1...0.7.2) Updates `rayon` from 1.11.0 to 1.12.0 - [Changelog](https://github.com/rayon-rs/rayon/blob/main/RELEASES.md) - [Commits](rayon-rs/rayon@rayon-core-v1.11.0...rayon-core-v1.12.0) --- updated-dependencies: - dependency-name: clap dependency-version: 4.6.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-minor-and-patch - dependency-name: clap_complete dependency-version: 4.6.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-rust dependency-version: 0.24.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-c dependency-version: 0.24.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-c-sharp dependency-version: 0.23.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: tree-sitter-swift dependency-version: 0.7.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: cargo-minor-and-patch - dependency-name: rayon dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo-minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump toml from 0.8.23 to 1.1.2+spec-1.1.0 Bumps [toml](https://github.com/toml-rs/toml) from 0.8.23 to 1.1.2+spec-1.1.0. - [Commits](toml-rs/toml@toml-v0.8.23...toml-v1.1.2) --- updated-dependencies: - dependency-name: toml dependency-version: 1.1.2+spec-1.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-javascript from 0.23.1 to 0.25.0 Bumps [tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript) from 0.23.1 to 0.25.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-javascript/releases) - [Commits](tree-sitter/tree-sitter-javascript@v0.23.1...v0.25.0) --- updated-dependencies: - dependency-name: tree-sitter-javascript dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-go from 0.23.4 to 0.25.0 Bumps [tree-sitter-go](https://github.com/tree-sitter/tree-sitter-go) from 0.23.4 to 0.25.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-go/releases) - [Commits](tree-sitter/tree-sitter-go@v0.23.4...v0.25.0) --- updated-dependencies: - dependency-name: tree-sitter-go dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * fix(structured): use toml::from_str for document parsing; add regression tests The `content.parse::<toml::Value>()` path relied on toml 0.8's FromStr parsing a full document. In toml 1.1, FromStr instead parses a single value and rejects further content, so any TOML file rendered by toml_outline would show "[parse error: unexpected content]" instead of the section/scalar tree. Switching to `toml::from_str(content)` is forward-and-backward compatible (works on both 0.8 and 1.1) and matches the deserialize path already used in `overview.rs` for Cargo.toml/pyproject.toml. Pinned four regression tests on `toml_outline` covering the failure mode this fixed: - Top-level table renders as `[section]` headers (Value::Table arm) - Tables beyond max_depth collapse to `{N keys}` summary - Arrays render as `[N items]` (Value::Array arm) - Parse errors surface as `[parse error: ...]` markers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(deps): bump tree-sitter from 0.25.10 to 0.26.8 Bumps [tree-sitter](https://github.com/tree-sitter/tree-sitter) from 0.25.10 to 0.26.8. - [Release notes](https://github.com/tree-sitter/tree-sitter/releases) - [Commits](tree-sitter/tree-sitter@v0.25.10...v0.26.8) --- updated-dependencies: - dependency-name: tree-sitter dependency-version: 0.26.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * fix: stop walker at mount boundaries, recognize composer.json Two minimal hardening fixes for the issues @quanhavn surfaced in #62: 1. `same_file_system(true)` on the WalkBuilder in both `walker()` and `find_basename_fallback()`. Stops traversal at mount-point boundaries so NFS / OrbStack / external-volume directories accessible from a project root don't get walked into. Pairs with the existing `follow_links(true)` — symlinks are still followed, just not across device boundaries. 2. `composer.json` added to `MANIFESTS` in `package_root()`. PHP / Laravel projects use composer.json as their manifest; without this, scope resolution falls through to a parent and pulls in unrelated trees. Scope deliberately narrower than #62 — drops `follow_links(false)` (would break pnpm, npm/yarn workspaces, macOS `/tmp` symlinks) and the `.git`-as-manifest fallback (would expand scope to the entire git repo for any project lacking a manifest). Closes #62. Co-Authored-By: quanhavn <anhquanhh4@gmail.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(deps): bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-scala from 0.24.0 to 0.26.0 Bumps [tree-sitter-scala](https://github.com/tree-sitter/tree-sitter-scala) from 0.24.0 to 0.26.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-scala/releases) - [Commits](tree-sitter/tree-sitter-scala@v0.24.0...v0.26.0) --- updated-dependencies: - dependency-name: tree-sitter-scala dependency-version: 0.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump tree-sitter-python from 0.23.6 to 0.25.0 Bumps [tree-sitter-python](https://github.com/tree-sitter/tree-sitter-python) from 0.23.6 to 0.25.0. - [Release notes](https://github.com/tree-sitter/tree-sitter-python/releases) - [Commits](tree-sitter/tree-sitter-python@v0.23.6...v0.25.0) --- updated-dependencies: - dependency-name: tree-sitter-python dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * docs: add community standards files + Scorecard badge (#109) Closes #97 (scaffold CoC / CONTRIBUTING / SECURITY / issue + PR templates). Closes half of #98 (Scorecard badge; Best Practices badge pending registration). - CODE_OF_CONDUCT.md — references Contributor Covenant 2.1, brief - CONTRIBUTING.md — light tone, points at CLAUDE.md and the three local gates - SECURITY.md — private vuln reporting via GitHub advisory flow - .github/ISSUE_TEMPLATE/{bug_report,feature_request,config}.yml — minimal, blank issues disabled - .github/PULL_REQUEST_TEMPLATE.md — summary + 3-line test plan checklist - README.md — OpenSSF Scorecard badge near the title Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(search): --full raises match cap from 10 to 100 (#65) (#111) Without --full, search/callers cap at 10 matches and show "... and N more matches" — users asking for the full picture were getting a truncated view. With --full, the cap is raised to 100 and the walker's early-quit threshold is raised proportionally (300), so all real matches become visible up to a generous bound. The cap-bump is gated strictly on the parsed --full flag, never on the piped-derived `full = cli.full || !is_tty`. This preserves the existing contract (`piped_invocation_does_not_auto_expand` test): subprocess / pipeline callers must keep the concise outline by default. To wire this through cleanly, run_inner and run_expanded now take an explicit cli_full parameter alongside the existing full (which stays as the file-content semantic for FilePath queries). Symbol, content, callers, and regex search all honor --full. The new symbol::full_flag_raises_match_cap test pins the cap behavior on a synthetic 15-file repo. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(ci): pin all GitHub Actions by commit SHA (#116) Closes the 16 open `PinnedDependenciesID` Scorecard alerts. Each `uses:` reference now carries a 40-char commit SHA plus a `# ratchet:org/repo@<tag>` comment marking the original constraint so the version remains greppable. Generated with `ratchet pin .github/workflows/*.yml` (sethvargo/ratchet v0.11.4). Dependabot continues to bump SHAs based on the trailing tag comment; `ratchet upgrade` can re-sync any drift later. Workflows affected: ci, dependency-review, release, scorecard. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version to v0.8.3 (#117) Patch release: search cap fix, NFS walker narrow, dep refreshes, community standards, SHA-pinned workflows. Includes (since v0.8.2): - fix(search): --full raises match cap from 10 to 100 (#65 / #111) - fix(walker): stop at mount boundaries, recognize composer.json (#110) - chore(deps): tree-sitter-scala 0.24 → 0.26 (#114) - chore(deps): tree-sitter-python 0.23.6 → 0.25 (#115) - chore(deps): actions/checkout v4 → v6 (#113) - chore(ci): pin all GitHub Actions by commit SHA (#116) - docs: community standards files + Scorecard badge (#109) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(fuzz): cargo-fuzz harness + nightly CI + OSS-Fuzz prep (#118) * feat(fuzz): scaffold cargo-fuzz with three targets Addresses the OpenSSF Scorecard FuzzingID alert. Adds the fuzz/ workspace with three initial targets covering tilth's primary input surfaces: - outline — feeds bytes through `read::outline::code::outline` against all 18 Lang variants. Exercises tree-sitter parsing + traversal + signature extraction + formatter. ~1900 exec/sec; primary attack surface ("any file on disk in any language"). - strip — feeds bytes through `search::strip::strip_noise` across 6 representative paths. ~26k exec/sec. Past bugs have lived in multi- byte UTF-8 handling. - diff_parse — feeds bytes through `diff::parse::parse_unified_diff`. Our own parser (not a crate boundary). ~78k exec/sec. All three pass 20s smoke runs with no findings. Plan and design notes in docs/research/fuzzing-plan.md (gitignored). The `__fuzz` module re-exports the fuzz surface without widening pub(crate) visibility elsewhere. Marked `#[doc(hidden)]` and documented as unstable. Tasks tracked: FUZZ-A1, FUZZ-B1, FUZZ-B2, FUZZ-B3 (done). Next: corpus seeding, CI nightly workflow, OSS-Fuzz application. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(fuzz): nightly CI, SECURITY.md, OSS-Fuzz artifacts Builds on the scaffolding commit: - .github/workflows/fuzz.yml — nightly cron + workflow_dispatch, runs each target for 5 min (configurable), caches corpus across runs, uploads crash artifacts on failure. Does NOT block PR merges. - SECURITY.md — documents the security testing surface (unit tests, CodeQL, Scorecard, fuzz). Includes local repro recipe. - oss-fuzz/projects/tilth/ — ready-to-submit OSS-Fuzz artifacts (project.yaml, Dockerfile, build.sh). README explains the submission workflow. Also: fmt fix on lib.rs (single-line wrapper signature), and `#[must_use]` on outline / strip_noise wrappers (now reachable publicly via __fuzz, so clippy::must_use_candidate fires under the pedantic lint group). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fuzz: preserve OOM finding as tracked seed 5-min outline target run surfaced one OOM finding: a 9KB Rust-shaped input (real source from ripgrep's globset crate) causes tree-sitter to allocate >2GB inside ts_parser_parse. Likely one of the 14 grammars we iterate is choking on input that's the wrong language for it. Preserved as fuzz/seeds/outline/oom-rust-imports-9kb so future runs have it as a permanent seed. Diagnostics + fix tracked separately (task FUZZ-FIX-1). The two other targets (strip, diff_parse) completed 1.82M and 7.92M runs respectively with no findings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(edit): remove cwd race in normalize_path_key (root-cause flaky test) normalize_path_key used std::path::absolute() to resolve `./foo` vs `foo` aliases for nonexistent files. absolute() reads current_dir() — process- global mutable state. Another test (mcp::tests::scope_handoff_when_cwd_is_root) calls set_current_dir("/") and restores it; under parallel test execution, the two normalize_path_key calls inside detect_duplicate_paths could see different cwds, producing different keys, breaking the dedup invariant. Surfaced as a flaky CI failure on PR #118: thread 'edit::tests::dedup_catches_nonexistent_alias_spellings' panicked: alias spellings should collide Fix: replace std::path::absolute() with pure-lexical normalization (strip CurDir components, walk ParentDir against the in-memory stack). No filesystem touch, no current_dir() call → deterministic under parallel tests. Trade-off: `/abs/foo.rs` and `foo.rs` (cwd=/abs) no longer dedup when neither exists. Acceptable — agents typically send all-relative or all-absolute paths in one batch; canonicalize still handles the common case (when at least one of the paths exists, canonicalize succeeds for it). Three new lexical_normalize unit tests pin the CurDir-strip, ParentDir-pop, and absolute-preserve invariants. A new normalize_path_key_is_cwd_independent test pins the race-free property as a load-bearing guarantee. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(edit): preserve unresolved ParentDir in lexical_normalize v1 of lexical_normalize had a real bug surfaced during edge-case audit: `../foo.rs` and `foo.rs` would normalize to the same key, but they point to DIFFERENT files (one in parent dir, one in cwd). v1 popped `..` at the empty stack, silently collapsing the path. Fixed: track whether the path is absolute; on `ParentDir`, only pop if the tail is a real (Normal) segment, else preserve `..` (relative paths) or no-op (absolute paths at root, matching Linux's `/.. == /`). New test pins the regression: assert_ne!(normalize_path_key("../foo.rs"), normalize_path_key("foo.rs")) Plus tests for the multi-level case (foo/bar/../../../baz.rs → ../baz.rs), absolute /foo/../bar.rs → /bar.rs, and /.. → /. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(edit): track normal_count instead of peeking with next_back Replaces `out.components().next_back()` in lexical_normalize (which iterates the whole PathBuf to inspect the tail, O(N) per ParentDir hit) with a `normal_count: usize` running counter. The decision becomes O(1) per component, dropping the function from O(N·K) to O(N) overall. Behavior identical — all 23 edit tests still pass without modification. The smell flagged in the previous audit (iterator allocation just to peek at the tail) is gone; the intent (\"can I pop a real segment?\") is now expressed directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version to v0.8.4 (#119) Patch release. Adds cargo-fuzz infrastructure (3 fuzz targets, nightly CI, OSS-Fuzz artifacts) and root-causes the cwd-race in normalize_path_key that was producing flaky dedup test failures on Linux CI. Includes (since v0.8.3): - feat(fuzz): cargo-fuzz harness + nightly CI + OSS-Fuzz prep (#118) - fix(edit): normalize_path_key is now race-free + lexically correct for `..` (handled as part of #118 audit) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(bench): align rg_trait_implementors prompt with ground truth The task asserted `find_at` was in the agent's final answer, but the prompt never mentioned methods — only \"where each implementor is defined and what crate it lives in.\" Sonnet tended to mention method names anyway (more verbose answers); haiku tended to answer literally and omit them, producing a ~33% spurious failure rate. Fixed by updating the prompt to ask explicitly for the trait's required methods, with `find_at` as an example. Verified: 3/3 haiku reps pass after the change (vs 2/3 before). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(mcp): split server into modules Extract the monolithic `src/mcp.rs` into `src/mcp/{mod,tools/*}.rs`: - `mcp/mod.rs` keeps the JSON-RPC scaffolding (request/response wire format, dispatch, `Services` shared deps). - `mcp/tools/mod.rs` re-exports each tool's entry point and hosts shared helpers (`resolve_scope`, `apply_budget`). - One file per MCP tool (`tools/{definitions,deps,diff,edit,files, read,search,session}.rs`). No behavior changes — inline prompts and tool surfaces are intact, so follow-up MCP prompt / v2-surface work can be reviewed separately. Forwarded from #33. Co-authored-by: paulnsorensen <429793+paulnsorensen@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * refactor(mcp): tighten module visibility and co-locate tests Three cleanup items from the /age review of the module split: 1. Tool entry points (tool_definitions, tool_deps, tool_diff, tool_edit, tool_files, tool_read, tool_search, tool_session) plus parse_file_edit narrowed from pub(crate) to pub(in crate::mcp). Their pub(super) use re-exports in src/mcp/tools/mod.rs likewise narrowed. The originals in the monolithic src/mcp.rs were truly private; pub(in crate::mcp) is the tightest visibility that keeps src/mcp/mod.rs's `use tools::tool_*` imports working. 2. Services struct, its impl methods, and dispatch_tool in src/mcp/mod.rs dropped pub(crate) — only callers live in mod.rs itself, so default (module-private) visibility is the right scope. 3. Tests co-located with the code they exercise: - resolve_scope_* and scope_flag_overrides_bad_cwd → src/mcp/tools/mod.rs - tool_files_* (+ scratch_project helper) → src/mcp/tools/files.rs - parse_file_edit_rejects_empty_edits_array → src/mcp/tools/edit.rs The extract_root_* tests and package_root_finds_project_from_subdirectory stay in src/mcp/mod.rs since they exercise mod.rs functions. The #[cfg(test)] use tools::{parse_file_edit, resolve_scope} import in mod.rs and the matching pub(super) use re-export in tools/mod.rs are removed since neither name is referenced from mod.rs anymore. No behavior changes; 366 tests pass. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * refactor(mcp): extract server instructions to prompts/*.md SERVER_INSTRUCTIONS and EDIT_MODE_EXTRA move out of inline string literals in src/mcp/mod.rs into versionable markdown sources at prompts/mcp-base.md and prompts/mcp-edit.md, wired back at compile time via include_str!. AGENTS.md becomes a generated artifact via scripts/regen-agents-md.sh and a CI step that fails if anyone edits the prompts without re-running it -- replacing the prior "both should stay in sync" manual discipline that had already drifted. Runtime MCP `instructions` is byte-identical to pre-refactor and locked by three new tests: - server_instructions_byte_lock (length + boundaries + interior markers + no triple newlines) - edit_mode_extra_byte_lock (same shape; preserves the leading "\n\n" that format!("{S}{E}") relies on) - instructions_compose_with_single_blank_line_between_sections (pins the exact concat boundary between the two sections) Reconciles the pre-existing SERVER_INSTRUCTIONS <-> AGENTS.md drift by canonicalising on SERVER_INSTRUCTIONS. Supersedes #85 (closed: wrong fork target) and re-shapes slice 1 of #112 against the current v1 tool surface so it stands alone on origin/main. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * feat(grok): tilth_grok — one-call structural understanding (#120) Collapses the search → expand → search-callers → read-context dance into one call. Returns everything structural about a symbol: definition, doc, body, signature, callees (internal + external), callers, siblings, and tests. - src/search/grok.rs — target resolution (symbol name or path:line), bundle assembly with seeded caps (5 callees / 5 callers / 8 siblings / 8 tests / 60 body lines by default; widen via full) - src/lib.rs run_grok + src/main.rs CLI subcommand - src/mcp/tools/grok.rs + src/mcp/tools/definitions.rs — MCP tool surface - prompts/mcp-base.md + AGENTS.md — instructions describing tilth_grok between tilth_deps and tilth_diff - benchmark/tasks/grok_tasks.py — 3 grok-shaped benchmark variants - byte-lock test updated 2952 → 3466 with a contains() assertion so accidental deletion of the grok description fails the test loudly Shares the underlying primitives (find_callers_batch, extract_callee_names, get_outline_entries) with tilth_deps but answers a symbol-level question where deps answers a file-level one — different granularity, different intent. See PR description for benchmark rationale. Reviewed by Opus 4.7. * fix: begin post-merge compatibility fixes Agent-Logs-Url: https://github.com/paulnsorensen/tilth/sessions/1eeb7e02-1a23-4cbd-914d-5c4b03e3c93d Co-authored-by: paulnsorensen <429793+paulnsorensen@users.noreply.github.com> * fix: align conflicted files with upstream main Agent-Logs-Url: https://github.com/paulnsorensen/tilth/sessions/1eeb7e02-1a23-4cbd-914d-5c4b03e3c93d Co-authored-by: paulnsorensen <429793+paulnsorensen@users.noreply.github.com> * fix(prompts): satisfy MD031/MD032 on stranded prompts/tools docs The new .markdownlint.json adopted from upstream uses numeric-rule keys and no longer disables MD031 (blanks-around-fences) or MD032 (blanks-around-lists), so the fork's pre-merge prompts/tools/*.md docs now fail the markdown CI gate. Add the missing blank lines around the fenced blocks and list start; AGENTS.md regen remains idempotent. These docs describe a tool surface (tilth_list, tilth_write-hash) that no longer exists after the upstream merge — flagged as a follow-up cleanup in .cheese/age/pr-35.md, out of scope for this sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Paul Sorensen <paulnsorensen@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: jensenojs <jensenojs@qq.com> Co-authored-by: jahala <jahala@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: quanhavn <anhquanhh4@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: paulnsorensen <429793+paulnsorensen@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Pins every
uses:reference across all four workflow files to a commit SHA, addressing the 16 openPinnedDependenciesIDScorecard alerts (security/code-scanning).# ratchet:owner/repo@v6) so the constraint stays greppable and Dependabot keeps workingsethvargo/ratchetv0.11.4 (single command:ratchet pin .github/workflows/*.yml)ratchet upgrade .github/workflows/*.ymlcan re-sync drift between SHA and tag commentWhy now
OpenSSF Scorecard flagged each unpinned action as
errorseverity in/security/code-scanning. The remediation guidance is to pin by SHA. After this merges, the next Scorecard run (weekly cron) closes the 16 alerts automatically.Test plan
PinnedDependenciesIDcount drops from 16 to 0🤖 Generated with Claude Code