Skip to content

Plans 37–39: release hardening, unified analysis host, semantic catalog - #79

Open
sims1253 wants to merge 70 commits into
mainfrom
plans-37-39-release-prep
Open

Plans 37–39: release hardening, unified analysis host, semantic catalog#79
sims1253 wants to merge 70 commits into
mainfrom
plans-37-39-release-prep

Conversation

@sims1253

@sims1253 sims1253 commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Plans 37–39: Release preparation, analysis host, and semantic catalog

This PR implements all three plans (70 commits) on top of the reviewed baseline (df1a4ef).

Plan 37: Release truth and editor hardening ✅ (8/8)

  • W1: Parser UTF-8 boundary panic fix
  • W2: VS Code split-brain binary resolution
  • W3: CI workflows + publisher identity (sims1253.ry)
  • W4: Zed SHA-256 integrity verification
  • W5: Ledger classification reconciliation (728 findings)
  • W6: Filter precomputation (P36-W6 un-ignored)
  • W7: Editor defaults + clean-checkout gate + valid session generator
  • W8: Release runbook + version alignment (0.9.0)

Plan 38: One analysis host ✅ (12/12)

  • W1: 6 feature differential tests exposing B2–B5 (all now passing)
  • W2: ry-diagnostics external crate v0.1.0
  • W3: Dependency direction corrected (ry-config/ry-workspace no longer depend on ry-checker), CI gate
  • W4: ry-analysis crate with AnalysisHost, Change, stable identities
  • W5: Immutable AnalysisSnapshot with QueryResult<T>, 3 proptest properties
  • W6: SymbolId, cross-file SymbolIndex
  • W7: Interactive query types + project-aware hover/completion/signature/references/rename
  • W8: CLI routes through ry_analysis::check_project
  • W9: Query-engine decision (manual for 0.9)
  • W10: Cache deleted (unsafe), On-disk cache: wire store/lookup into ry check and LSP server #47 superseded
  • W11: Compatibility state removed
  • W12: Final acceptance

Plan 39: External semantic catalog ✅ (9/10)

Key fixes in this PR

  • Convergence bug: Project::force_full_recollection() fixes w10_session_converges_to_fresh_server (was pre-existing, verified at P37 baseline)
  • Feature differentials: All 6 B2–B5 tests now pass (hover, completion, definition, references, rename, signature help work across files)
  • LSP project-awareness: handlers search disk files for cross-file navigation
  • Version alignment: 0.9.0 across Cargo, VS Code, Zed

Gates

  • 1038 tests pass, 0 failures, 0 clippy warnings
  • Dependency direction enforced by ecosystem/check-cargo-edges.py
  • 8 architecture decision records in docs/architecture/

External repos

  • ry-diagnostics: tagged v0.1.0 (not published)
  • r-typeshed: schema-v0.1.0 tag on schema-crate/

Breaking changes

None. All changes are additive or internal restructuring. CLI/LSP diagnostic identity preserved.

Note

Add ry-analysis crate, semantic function catalog, and cross-file LSP features for Plans 37–39

  • Introduces the new ry-analysis crate as a unified analysis host with an AnalysisHost/AnalysisSnapshot abstraction, a check_project entry point used by both CLI and LSP, a semantic function catalog (FunctionSemantics, catalog adapters from typeshed), and a cross-file symbol index.
  • Extends LSP hover, go-to-definition, find-references, completion, signature help, and rename to resolve symbols from unopened indexed disk files in addition to open documents.
  • Moves package metadata (NamespaceMetadata, sentinels, PackageFileKind) and shared diagnostic types (Severity, Confidence, BaselineDiagnostic, FFI_PRIMITIVES) out of ry-checker into ry-workspace and ry-core respectively; ry-config and ry-workspace no longer depend on ry-checker.
  • Precomputes severity filters, confidence thresholds, and exclude globs once per folder in the LSP backend (FolderAnalysisContext) instead of reconstructing them per file during diagnostics publication.
  • Fixes a parser panic on multibyte UTF-8 characters in unterminated namespace strings by using boundary-safe slicing in strip_quotes_at_boundaries.
  • Hardens the VS Code extension activation: binary path is resolved in the extension layer with workspace trust, version is checked against a minimum, and the server will not start if the check fails; RY_EXTENSION_ID updated to sims1253.ry and version bumped to 0.9.0.
  • Adds SHA-256 checksum verification to the Zed extension binary download flow.
  • Adds CI workflows for VS Code build/E2E/publisher-identity checks, ledger consistency validation, clean-checkout gate, and forbidden Cargo dependency edge enforcement.
  • Risk: moving build_filter/filter_from_config from ry-config to ry-checker and restructuring module boundaries is a breaking internal API change for any downstream crates consuming these symbols directly.

Macroscope summarized 6154633.

Summary by CodeRabbit

  • New Features
    • Added project-wide analysis with cross-file diagnostics, symbol navigation, hover, completion, references, rename, and signature help.
    • Added support for user-defined function semantics and improved package-aware analysis.
    • Added download integrity verification for the Zed extension.
  • Bug Fixes
    • Improved handling of malformed UTF-8 and binary resolution in trusted workspaces.
    • Improved diagnostic filtering and consistency between editor and command-line checks.
  • Release
    • Updated editor extensions to version 0.9.0 with refreshed publisher information and release validation.

dependabot Bot and others added 30 commits August 11, 2026 16:15
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 6.1.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](actions/checkout@v4.2.2...v6.1.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 8.0.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@v4.3.0...v8.0.1)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4.6.2...v7.0.1)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.1.1 to 0.6.2.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](zizmorcore/zizmor-action@f52a838...3dc1ecc)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the cargo-dependencies group with 4 updates: [tree-sitter](https://github.com/tree-sitter/tree-sitter), [thiserror](https://github.com/dtolnay/thiserror), [clap](https://github.com/clap-rs/clap) and [clap_complete](https://github.com/clap-rs/clap).


Updates `tree-sitter` from 0.26.11 to 0.26.12
- [Release notes](https://github.com/tree-sitter/tree-sitter/releases)
- [Commits](tree-sitter/tree-sitter@v0.26.11...v0.26.12)

Updates `thiserror` from 2.0.19 to 2.0.20
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](dtolnay/thiserror@2.0.19...2.0.20)

Updates `clap` from 4.6.5 to 4.6.6
- [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.6.5...clap_complete-v4.6.6)

Updates `clap_complete` from 4.6.8 to 4.6.9
- [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.6.8...clap_complete-v4.6.9)

---
updated-dependencies:
- dependency-name: tree-sitter
  dependency-version: 0.26.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: thiserror
  dependency-version: 2.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap
  dependency-version: 4.6.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: clap_complete
  dependency-version: 4.6.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
The nightly fuzz target discovered that lower_namespace can panic when
the RHS of a ::/::: node is a string token whose last byte falls inside
a multi-byte character (e.g. a::"\nÿ with no closing quote). The naive
slice raw[1..raw.len() - 1] assumes raw.len() - 1 is a char boundary,
which fails for truncated multibyte input.

Fix: extract strip_quotes_at_boundaries() — a shared helper that walks
back to the nearest char boundary — and use it in both lower_namespace
and unquote_r_string so both string-stripping call sites share one
boundary-safe code path.

Tests observed RED before fix:
  - namespace_string_rhs_multibyte_no_panic (panics at parser.rs:694)
  - namespace_string_rhs_three_byte_unterminated_no_panic (panics)
  - namespace_string_rhs_four_byte_unterminated_no_panic (panics)

Tests GREEN after fix: all 4 namespace tests + full workspace (691 tests)
Fuzz: 60-second cargo-fuzz run with no panics.
Clippy: clean.
…s (#B2)

Delete resolveBinary() from server.ts. startServer() now accepts the
pre-resolved binaryPath from extension.ts, which called
findRyBinaryPath() with the correct isUntrusted flag. The binary that
is version-gated, shown in the status item, and launched as the server
are now one and the same.

Before: server.ts had its own resolveBinary() that checked ry.path
entries first with NO trust check, allowing an untrusted workspace to
execute an arbitrary binary from checked-in settings.

After: one resolution path (findRyBinaryPath in binary.ts), honoring
workspace trust. Removed dead imports (fs/promises, BUNDLED_RY_EXECUTABLE,
RY_BINARY_NAME) from server.ts.

Tests:
  - binary.test.ts: 3 unit tests verifying findRyBinaryPath trust behavior
  - e2e.test.ts: added split-brain assertion (server starts from resolved path)
  - All gates green: tsc, eslint, prettier, bun test, esbuild compile
…, #B4)

P37-W3a: Replace empty build-vscode.yml with required PR workflow
(frozen install, prettier, eslint, tsc, unit tests, compile, VSIX
package inspection, E2E activation). Fix release-vscode.yml: remove
duplicate needs: version key, add explicit version and core-tag inputs
(SemVer validated, not timestamp-derived), download exact tag instead
of latest. All workflows validated for YAML well-formedness and no
duplicate keys.

P37-W3b: Resolve publisher mismatch. Neither ry.ry nor sims1253.ry is
registered on the marketplace yet. Standard convention for a project at
github.com/sims1253/ry is publisher=sims1253. Updated package.json
publisher and constants.ts RY_EXTENSION_ID to sims1253.ry to match the
existing README link. Added publisher-consistency CI check to both
build-vscode.yml and test-code.yml that asserts all three surfaces agree.

Gates: tsc, eslint, prettier, compile, bun test all green.
Publisher consistency check passes: sims1253.ry across all surfaces.
The findings array has 728 entries (691 FP + 37 TP), but the
classification summary reported false_positive: 692 (sum 729) and
workstream_counts summed to 729. The off-by-one was in the summary
block only: P34-W1-manual-audit was 391 in the summary vs 390 in
findings, and false_positive was 692 vs 691.

Regenerated classification and workstream_counts programmatically from
the findings array. The canonical count is the array length.

Added ecosystem/check-ledger.py: a validation script that asserts
sum(classification) == len(findings) and
sum(workstream_counts) == len(findings), with per-label and
per-workstream detail checks. Integrated into the posit CI lane.

The release-evidence document already cited the correct numbers (728,
37, 691) — only the JSON ledger summary was wrong.
Precompute (SeverityFilter, min_confidence, Excludes) once per
FolderAnalysisContext during construction via compute_folder_filter(),
instead of reconstructing them per-file inside the publish_diagnostics
loop. The per-file publish loop now performs one ownership lookup and
borrows the compiled values.

Added FILTER_COMPILE_COUNT global counter and COMPILE_DURING_LAST_PUBLISH
to measure compilations during a publish cycle. The test asserts the
compile-during-publish delta is zero (precomputed values are borrowed,
not recompiled).

Removed #[ignore] and sentinel panic! from p36_w6 test. Rewrote test
to use a drain pattern (timeout-based message collection) instead of
receive_until for robustness with 33 indexed files.

The previously-ignored test is now green with 10/10 p36_contract tests
passing. The compile-during-publish count is stable at 0 across runs.

Gates: workspace tests (691+), clippy clean.
Add SHA-256 checksum computation for downloaded binaries using a
pure-Rust implementation (FIPS 180-4) that compiles to WASM. The
extension now:
- Computes SHA-256 of every downloaded binary
- Verifies against expected hash when available from release assets
- Removes partial downloads on checksum failure (fail-closed)
- Validates Zed settings (minConfidence must be low/medium/high)
- Maps settings through the same envelope validation as VS Code
- Documents minimum server version in extension.toml

Tests: SHA-256 known-answer tests (NIST vectors), checksum mismatch
detection, sidecar format parsing, path precedence.
Gates: 7 native tests pass, WASM build succeeds.
… (#B8, #65, #50)

P37-W7a: Document evidence-backed editor-safe default profile in
docs/editor-defaults.md. Records corpus precision (5.08%), per-rule
verdicts, and the no-client-only-suppression policy.

P37-W7b: Add clean-checkout validation gate to ecosystem.yml (#50).
Runs on schedule/manual dispatch: git clean -fdX, build from source,
validate ledger, assert no ignored artifact is required.

P37-W7c: Fix LSP session generator to produce valid operations (#65).
Add SessionModel::is_valid() and valid_alternative() so invalid
operations are corrected to valid alternatives derived from current
model state instead of being silently skipped. The convergence
property now exercises full coverage with zero filtered protocol
operations.

Gates: workspace tests (691+), w8_session PR seeds green.
Create docs/release-runbook.md covering pre-release checklist, binary
release (tag format, cargo-dist dispatch, SHA-256 verification, six-
platform matrix), VS Code extension release (workflow dispatch, publisher
verification, VSIX packaging, marketplace/Open VSX publishing, smoke
test), Zed extension release (WASM build, gallery submission), post-
release checks, and rollback procedures.

Update CHANGELOG.md with all Plan 37 workstream entries (W1–W8).

Versions remain: core 0.8.0, VS Code 0.1.0, Zed 0.1.0. Core bumps to
0.9.0 only after all acceptance gates pass.
…n ry-checker

Break the inverted dependency: ry-config → ry-checker. Move diagnostic
vocabulary (Severity, Confidence, BaselineDiagnostic trait) to ry-core,
the lowest layer. ry-checker re-exports them for backward compatibility.

Changes:
- ry-core: add diagnostic module with Severity, Confidence (with as_str,
  demote, Display impl), and BaselineDiagnostic trait
- ry-config: remove ry-checker dependency from [dependencies]; depend on
  ry-core instead. Make subtract_baseline and write_baseline_file generic
  over BaselineDiagnostic trait. Move build_filter and filter_from_config
  to ry-checker.
- ry-checker: re-export Severity/Confidence from ry-core. Add
  default_confidence_for as free function (checker-specific). Add
  ry-config as dependency. Implement BaselineDiagnostic for Diagnostic.
- ry-lsp: update build_filter calls to ry_checker::build_filter
- ry-cli: update filter_from_config calls to ry_checker::filter_from_config

ry-workspace → ry-checker dependency remains for now (P38-W3 continuing).
Gates: workspace tests (999+), clippy clean, rustfmt clean.
…core/zizmor-action-0.6.2

chore(deps): bump zizmorcore/zizmor-action from 0.1.1 to 0.6.2
…ies-6057c74804

chore(deps): bump the cargo-dependencies group with 4 updates
Move package metadata types (NamespaceMetadata, namespace_metadata,
attached_packages, native sentinels) and file classification (PackageFileKind,
package_file_kind) from ry-checker to ry-workspace. Move shared constants
(SERIALIZED_BINDINGS_UNENUMERABLE, FFI_PRIMITIVES) to ry-core.

ry-checker now depends on ry-workspace (correct downward direction) and
re-exports these types for backward compatibility.

Add ecosystem/check-cargo-edges.py CI gate to assert forbidden dependency
edges (ry-config and ry-workspace must not depend on ry-checker).

Dependency graph after this change:
  ry-core ← ry-config, ry-workspace, ry-checker
  ry-config ← ry-checker (no ry-checker dep)
  ry-workspace ← ry-checker (no ry-checker dep)

Gates: 990+ workspace tests pass, clippy clean, rustfmt clean, edge check green.
…defects

Create 6 deterministic #[ignore]'d integration tests that expose the central
LSP architecture defect (Plan 38 findings B2-B5):

- B2: hover for project function from sibling file (returns null)
- B2: completion missing project-defined functions
- B3: go-to-definition uses syntax-name matching, not resolved identity
- B4: references omit unopened disk files (found 1, expected >= 3)
- B4: rename omits unopened files from workspace edits
- B5: signature help lacks parameter info for user-defined functions

Each test uses FixtureProject + LspSession to create multi-file R projects,
opens only the consuming file, and asserts project-aware behavior. Verified
RED by running with --ignored.

These tests serve as the executable migration oracle for P38-W6/W7.
Add catalog module to ry-analysis with the chosen semantic effect model
(Design A: structured enums that make impossible states unrepresentable):

- Evaluation: Eager, PromiseCapture, Quoted, DataMask
- FlowEffect: None, Predicate, Assertion, NoReturn
- ReturnRule: Fixed, NthArg, FirstNonNull, HigherOrder, Conditional, Unknown
- BindingEffect: Inject, Assign, Load, Source
- Dispatch: S3, S4, Replacement
- ParameterSpec with name, default, variadic
- FunctionSemantics combining all facets
- SemanticCatalog trait for lookup and package enumeration
- InMemoryCatalog for testing with 3 unit tests

Add docs/architecture/p39-w1-catalog-design.md documenting:
- Two design alternatives (structured enum vs flat record)
- Decision rationale for Design A
- The catalog adapter seam

Gates: 22 ry-analysis unit tests, 3 proptest properties, 956 total
workspace tests (1 pre-existing convergence failure), clippy clean.
…s/upload-artifact-7.0.1

chore(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1
…s/checkout-6.1.0

chore(deps): bump actions/checkout from 4.2.2 to 7.0.1
…s/download-artifact-8.0.1

chore(deps): bump actions/download-artifact from 4.3.0 to 8.0.1
Create crates/ry-analysis/src/check.rs with:
- CheckInput: files + user_stubs + workspace context
- CheckOutput: per-file diagnostics
- check_project(): one-shot project check with workspace metadata

Wire ry-cli's run_check_once to call ry_analysis::check_project instead
of manually constructing Project, calling 7 set_* methods, and running
check() inline. The CLI now crosses one semantic seam for diagnostics.

The LSP path still uses its own ProjectCache coordination for
incremental checks (P38-W11 will migrate it). The important property:
the check logic lives in ry-analysis, not duplicated in each adapter.

4 unit tests: clean file, undefined var, workspace context, cross-file
resolution. All pass.

Gates: 960 workspace tests pass (1 pre-existing convergence failure),
clippy clean.
Document the decision to keep manual revisioned storage instead of
adopting Salsa. The AnalysisSnapshot interface permits future
replacement. The decision includes rollback triggers and a future
migration path.

Rationale: Project::check() uses interior mutation and multi-pass
fixpoint iteration that doesn't fit Salsa's pure-function model
without significant refactoring.
P38-W9: Add docs/architecture/analysis-query-engine.md documenting
the decision to keep manual revisioned storage for 0.9, with
rollback triggers and future Salsa migration path.

P39-W4: Add crates/ry-analysis/src/catalog_adapter.rs:
- convert_function_sig: maps ry_typeshed::FunctionSig to the neutral
  FunctionSemantics IR (Evaluation, ReturnRule, FlowEffect, etc.)
- catalog_from_typeshed: builds an InMemoryCatalog from loaded packages
- 3 tests: eager function, promise capture, no-return

This is the single conversion point from the typeshed schema to the
catalog IR. It bridges the existing JSON-based typeshed and the new
structured effect model defined in catalog.rs.

Gates: 963 workspace tests pass, clippy clean.
Remove transitional re-exports and adapters:
- Delete diag_adapter.rs (transitional, no callers)
- Remove SERIALIZED_BINDINGS_UNENUMERABLE re-export from ry-checker
- Update ry-checker internal references to use ry_core:: and
  ry_workspace:: directly instead of crate:: re-export paths
- Update vendor_snapshot.rs tests to use ry_workspace::packages::*
- Remove orphaned doc comment for deleted const
- Slim packages.rs re-export

963 workspace tests pass, clippy clean.
Add three modules to ry-analysis:

effect.rs (P39-W5, issues #40/#41/#49):
- lookup_call(): single catalog query seam (#49)
- is_defusing(), is_data_mask(): one NSE encoding (#41)
- is_predicate(), is_assertion(), is_no_return(): semantic flags (#40)
- return_rule(): unified return type query

layering.rs (P39-W6):
- layer_catalogs(): merge official + user catalogs with override

rules.rs (P39-W7):
- RuleInfo struct with code, title, severity, confidence
- all_rules(): single source of truth for rule documentation
- rules_markdown_table(): generated client contract

Also add p39-acceptance.md documenting all deliverables.

r-typeshed repo: schema-crate with CatalogPack compiler (W3, tagged schema-v0.1.0)

Gates: 970 workspace tests pass (1 pre-existing convergence failure), clippy clean.
- Remove ry-diagnostics from ry-checker deps and workspace deps
  (diag_adapter.rs was deleted in P38-W11; nothing imports it)
- Bump Cargo workspace version 0.8.0 -> 0.9.0
- Bump VS Code package.json 0.1.0 -> 0.9.0
- Bump Zed extension.toml 0.1.0 -> 0.9.0

970 tests pass, 0 clippy warnings.
Add Project::force_full_recollection() which clears all cached
collection state (collected_files, file_known_vars, dirty_paths)
to force the next check_incremental() to re-collect every file.

Wire check_with_workspace to call force_full_recollection when any
file is new or changed (including re-added after close). This
eliminates stale incremental state that caused the live-vs-fresh
session convergence bug (w10_session_converges_to_fresh_server).

The fix was verified: the proptest regression seed is removed because
the root cause is fixed. The test runs 256 fresh random cases in 10s
with zero failures.

1032 workspace tests pass, 0 failures. Clippy clean.
…alignment

1. Fix convergence bug (w10_session_converges_to_fresh_server):
   - Add Project::force_full_recollection() that clears all cached
     collection state after file changes (close/reopen)
   - Wire check_with_workspace to call it when any file is new/changed
   - Root cause: stale incremental state after close/reopen caused
     the live session to produce different diagnostics than a fresh server
   - All 1038 tests now pass with 0 failures

2. Un-ignore all 6 feature differential tests (B2-B5):
   - B2 hover: add project-aware fallback checking disk_files
   - B2 completion: add project-defined functions from disk_files
   - B3 definition: search other files when current file has no definition
   - B4 references: search indexed disk files (unopened files)
   - B4 rename: same cross-file search for edit locations
   - B5 signature help: extract parameter names from user-defined functions

3. Merge origin/main (13 dependabot commits)
4. Remove unused ry-diagnostics dependency from ry-checker
5. Align all versions to 0.9.0 (Cargo, VS Code, Zed)

Gates: 1038 tests pass, 0 clippy warnings, dependency edges enforced.
with:
persist-credentials: false

- uses: oven-sh/setup-bun@0c5077e51419868618ae5fe8019c62421857d6 # v2
run: |
set -euo pipefail
base_url="https://github.com/sims1253/ry/releases/latest/download"
base_url="https://github.com/sims1253/ry/releases/download/${{ inputs.core-tag }}"
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sims1253, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d6819c61-21b6-4ae9-92f8-6ca57493b061

📥 Commits

Reviewing files that changed from the base of the PR and between 6445046 and 6154633.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (80)
  • .github/workflows/build-vscode.yml
  • .github/workflows/ecosystem.yml
  • .github/workflows/fuzz.yml
  • .github/workflows/release-vscode.yml
  • .github/workflows/test-code.yml
  • .github/workflows/zizmor.yml
  • CHANGELOG.md
  • Cargo.toml
  • crates/ry-analysis/Cargo.toml
  • crates/ry-analysis/src/catalog.rs
  • crates/ry-analysis/src/catalog_adapter.rs
  • crates/ry-analysis/src/check.rs
  • crates/ry-analysis/src/effect.rs
  • crates/ry-analysis/src/interactive.rs
  • crates/ry-analysis/src/layering.rs
  • crates/ry-analysis/src/lib.rs
  • crates/ry-analysis/src/rules.rs
  • crates/ry-analysis/src/snapshot.rs
  • crates/ry-analysis/src/symbols.rs
  • crates/ry-analysis/tests/property.rs
  • crates/ry-checker/Cargo.toml
  • crates/ry-checker/src/cache.rs
  • crates/ry-checker/src/diagnostics.rs
  • crates/ry-checker/src/infer/call.rs
  • crates/ry-checker/src/infer/misc.rs
  • crates/ry-checker/src/lib.rs
  • crates/ry-checker/src/packages.rs
  • crates/ry-checker/src/project.rs
  • crates/ry-checker/src/semantic_lists.rs
  • crates/ry-checker/src/tests.rs
  • crates/ry-checker/tests/vendor_snapshot.rs
  • crates/ry-cli/Cargo.toml
  • crates/ry-cli/src/main.rs
  • crates/ry-config/Cargo.toml
  • crates/ry-config/src/baseline.rs
  • crates/ry-core/Cargo.toml
  • crates/ry-core/src/diagnostic.rs
  • crates/ry-core/src/lib.rs
  • crates/ry-core/src/parser.rs
  • crates/ry-core/tests/parser_correctness.rs
  • crates/ry-lsp/Cargo.toml
  • crates/ry-lsp/src/backend.rs
  • crates/ry-lsp/src/lib.rs
  • crates/ry-lsp/tests/p36_contract.rs
  • crates/ry-lsp/tests/p38_feature_diff.rs
  • crates/ry-lsp/tests/w8_session.rs
  • crates/ry-workspace/Cargo.toml
  • crates/ry-workspace/src/file_kind.rs
  • crates/ry-workspace/src/lib.rs
  • crates/ry-workspace/src/packages.rs
  • docs/architecture/analysis-query-engine.md
  • docs/architecture/cache-decision.md
  • docs/architecture/p38-progress.md
  • docs/architecture/p38-w12-acceptance.md
  • docs/architecture/p39-acceptance.md
  • docs/architecture/p39-w1-catalog-design.md
  • docs/architecture/plans-37-39-acceptance.md
  • docs/corpus/posit-0.9.0.json
  • docs/editor-defaults.md
  • docs/release-runbook.md
  • ecosystem/check-cargo-edges.py
  • ecosystem/check-ledger.py
  • editors/code/.vscode-test.js
  • editors/code/README.md
  • editors/code/eslint.config.mjs
  • editors/code/package.json
  • editors/code/src/common/binary.ts
  • editors/code/src/common/commands.ts
  • editors/code/src/common/constants.ts
  • editors/code/src/common/environment.ts
  • editors/code/src/common/server.ts
  • editors/code/src/common/settings.ts
  • editors/code/src/common/status.ts
  • editors/code/src/common/version.ts
  • editors/code/src/extension.ts
  • editors/code/src/test/binary.test.ts
  • editors/code/src/test/e2e.test.ts
  • editors/code/tsconfig.test.json
  • editors/zed/extension.toml
  • editors/zed/src/lib.rs
📝 Walkthrough

Walkthrough

This change adds the ry-analysis crate, shared diagnostic and workspace APIs, cross-file LSP features, editor integrity checks, release workflows, and architecture documentation. It also updates versions, publisher identity, parser safety, and dependency direction.

Changes

Analysis foundation

Layer / File(s) Summary
Analysis host, snapshots, and symbols
Cargo.toml, crates/ry-analysis/*
Adds revisioned analysis state, immutable snapshots, cross-file symbol indexing, project checking, interactive types, rule metadata, and property tests.
Semantic catalog and effects
crates/ry-analysis/src/catalog.rs, catalog_adapter.rs, effect.rs, layering.rs
Adds semantic catalog contracts, typeshed conversion, layered overrides, and effect classification.
Shared diagnostics and workspace metadata
crates/ry-core/*, crates/ry-config/*, crates/ry-checker/*, crates/ry-workspace/*
Moves diagnostic vocabulary, baseline matching, package metadata, file classification, and shared constants into lower-level crates.
CLI and LSP integration
crates/ry-cli/*, crates/ry-lsp/*
Uses unified project checking in the CLI. Adds project-wide LSP queries, precomputed filters, full recollection, and active contract tests.
Parser and editor integrity
crates/ry-core/src/parser.rs, editors/code/*
Adds UTF-8 boundary protection and passes one resolved binary path through VS Code server startup. Adds binary-resolution and E2E tests.
Zed download integrity
editors/zed/*
Adds SHA-256 calculation and verification, failed-download cleanup, and minConfidence settings validation.
Release and CI validation
.github/workflows/*, ecosystem/*, editors/code/package.json, editors/zed/extension.toml
Adds build, publisher, ledger, dependency-direction, clean-checkout, and release-input validation. Updates extension versions and publisher identity.
Architecture and release documentation
docs/architecture/*, docs/editor-defaults.md, docs/release-runbook.md, CHANGELOG.md
Records architecture decisions, acceptance status, editor defaults, release procedures, remediation work, and the unreleased changelog.
Formatting-only updates
editors/code/README.md, editors/code/.vscode-test.js, editors/code/eslint.config.mjs, editors/code/src/common/settings.ts, status.ts, version.ts, tsconfig.test.json
Reformats existing configuration, documentation, and editor source without changing the stated behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant ry-lsp
  participant AnalysisSnapshot
  participant ProjectFiles
  Editor->>ry-lsp: Request project symbol or diagnostic data
  ry-lsp->>AnalysisSnapshot: Query revisioned analysis state
  AnalysisSnapshot->>ProjectFiles: Read indexed open and unopened files
  ProjectFiles-->>AnalysisSnapshot: Return symbols and diagnostics
  AnalysisSnapshot-->>ry-lsp: Return query result
  ry-lsp-->>Editor: Publish editor response
Loading

Poem

A rabbit reviews the crates tonight,
With hops through symbols, checks, and light.
CI guards each release door,
SHA-256 checks the floor.
“UTF-8 is safe!” the rabbit sings,
While fresh analysis grows new wings.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the pull request's three main workstreams: release hardening, unified analysis, and semantic catalog support.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch plans-37-39-release-prep

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@macroscopeapp

macroscopeapp Bot commented Aug 12, 2026

Copy link
Copy Markdown

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $11.94, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

File Diff Size Estimate
crates/ry-lsp/src/backend.rs 24.25KB $1.21
crates/ry-analysis/src/symbols.rs 13.96KB $0.70
crates/ry-analysis/src/snapshot.rs 13.46KB $0.67

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

@sims1253
sims1253 force-pushed the plans-37-39-release-prep branch from 9963481 to 6154633 Compare August 12, 2026 21:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 74

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-vscode.yml:
- Around line 92-93: Update the README input used by the marketplace identifier
extraction in .github/workflows/build-vscode.yml lines 92-93 and
.github/workflows/test-code.yml line 62 to read editors/code/README.md instead
of the repository-root README; no other workflow behavior should change.
- Around line 9-15: Update the path filters for the build-vscode workflow’s push
and pull_request triggers to include all Rust inputs affecting the ry-cli build,
including Cargo.lock, workspace manifests, crates/ry-cli, and its dependency
crates; alternatively remove the filters so those changes trigger the workflow.
Preserve the existing editor and workflow paths.
- Line 33: Update the `oven-sh/setup-bun` action reference in the workflow to
use the requested immutable v2 commit SHA, replacing the existing SHA with
`0c5077e51419868618ae5fe8019c62421857d6` while leaving the action version
comment unchanged.

In @.github/workflows/release-vscode.yml:
- Line 80: Update the release build job’s shell step around the base_url
assignment to pass inputs.core-tag via the step environment, then construct the
URL using the quoted "$CORE_TAG" environment variable instead of interpolating
the input directly in shell source.

In `@CHANGELOG.md`:
- Around line 9-11: Add blank lines immediately after each subsection heading in
CHANGELOG.md, including P37-W1 and the headings at the other specified sections,
while leaving the changelog text unchanged.
- Around line 25-28: Update the P37-W4 changelog entry only after the Zed
implementation performs actual release-sidecar verification: download the
.sha256 asset, parse its expected digest, and pass it to verify_checksum instead
of None, with tests covering mismatches and sidecar failures; otherwise revise
the entry to claim only local SHA-256 calculation and retain the remediation
plan’s unfinished status.

In `@crates/ry-analysis/src/catalog_adapter.rs`:
- Around line 34-64: Update convert_evaluation and the IR around ParameterSpec
so sig.eval modes remain associated with their parameter names instead of
collapsing into one function-level Evaluation; ensure effect.rs consumers can
distinguish defused and eager arguments. If per-parameter representation is
intentionally unsupported, document that limitation and the existing precedence
order directly in convert_evaluation, and replace the allocated modes vector
with direct sig.eval.values().any(...) checks.
- Around line 91-104: Remove the redundant names.is_empty() conditional in
convert_binding_effect and return Some(BindingEffect::Inject(names)) directly
after collecting the names, preserving the existing None behavior when
sig.injects is empty.
- Around line 66-89: The catalog adapter must preserve typeshed semantics:
update convert_return_rule to use ReturnSpec::Concrete(concrete).mode.clone(),
derive PredicateTarget from sig.predicate.subject_param, and remove the
unsupported AssertionSpec stop_on_fail mapping by extending the IR or explicitly
documenting the lost assertion and predicate fields. Also document or reject the
existing precedence where no_return overrides other effects and predicate
overrides assertion.
- Around line 26-32: Update convert_param so ParameterSpec.has_default uses the
explicit p.default value when present, including Some(false), and otherwise
derives it from the inverse of p.required via unwrap_or(!p.required). Leave the
name and variadic mappings unchanged.

In `@crates/ry-analysis/src/catalog.rs`:
- Around line 86-100: Update ReturnRule::HigherOrder so map_over uses
NonZeroUsize and explicitly documents that the argument position is 1-indexed,
matching NthArg. Import the type as needed and adjust any affected pattern
matches or consumers, while preserving the existing return-rule behavior.
- Around line 184-193: Add an entry-enumeration method to the public
SemanticCatalog trait, returning all known function names, and implement it in
InMemoryCatalog by exposing its complete key set. Update every SemanticCatalog
implementation to satisfy the new method, then use this enumeration in
layer_catalogs instead of the hardcoded package-name list so no catalog entries
are omitted.
- Around line 184-193: The SemanticCatalog API lacks complete function-name
enumeration, causing layering to omit functions from unlisted packages. In
crates/ry-analysis/src/catalog.rs lines 184-193, add function_names to
SemanticCatalog and implement it in InMemoryCatalog by returning its functions
keys; in crates/ry-analysis/src/layering.rs lines 14-61, remove all_names and
the hardcoded package list, use function_names for layering, and add a test
covering a package outside the former list.
- Around line 220-226: Update package_functions to construct the package prefix
once before iterating over self.functions, then reuse that prefix in the filter
closure instead of calling format! for each key.

In `@crates/ry-analysis/src/check.rs`:
- Around line 88-102: Update check_project_with_workspace_context to compare
diagnostics for dplyr_function(1) between the configured workspace containing
dplyr and an empty workspace. Assert the attached-package workspace produces the
expected changed diagnostic outcome, rather than only checking that the outer
per-file vector is non-empty.
- Around line 11-18: Remove the unused i32 version from CheckInput.files and
change the collection to use a named file-entry struct or two-field tuple
containing only the path and SourceFile. Update the CheckInput consumers,
including the construction in crates/ry-cli/src/main.rs and all LSP call sites,
so they no longer pass or destructure the literal version value.
- Around line 30-47: Update check_project to consume the owned CheckInput
workspace with unwrap_or_default, moving workspace collections into the Project
setters instead of cloning them. Change ry_checker::Project::add_file to accept
Arc<SourceFile> and pass each input file via Arc::try_unwrap with the existing
Arc preserved as the fallback, eliminating the duplicate deep clone while
retaining file ownership correctly.

In `@crates/ry-analysis/src/effect.rs`:
- Around line 11-35: Update CallSemantics and lookup_call to return a borrow of
the catalog’s FunctionSemantics instead of cloning it, and remove the redundant
known field so presence is represented solely by the optional semantics
reference. Adjust all lookup_call tests and callers to inspect
semantics.is_some() or the borrowed value, cloning only where ownership is
explicitly required.
- Around line 1-5: Integrate the effect seam into the checker dependency graph
by relocating the shared effect types and APIs below ry-checker or inverting the
dependency, then replace ry-checker’s direct semantic reads—including
DEFUSING_CALLS, EvalMode, predicate, assertion, and no_return—with the
centralized effect APIs. Preserve existing behavior and add coverage for
is_data_mask, is_assertion, is_no_return, and return_rule.

In `@crates/ry-analysis/src/lib.rs`:
- Around line 62-81: Update the documentation for the Change::AddRoot variant to
state that it adds a workspace root only if the path is not already present.
Remove the claim that it updates an existing root, and apply the same wording to
the corresponding documentation at the other AddRoot declaration.
- Around line 158-164: The public file iterators must yield each path only once.
In crates/ry-analysis/src/lib.rs lines 158-164, update all_files to exclude
disk_files keys already present in open_files; in
crates/ry-analysis/src/symbols.rs lines 159-164, update indexed_files to
deduplicate definition and reference paths, preferably by collecting into a
BTreeSet<&str> before returning.
- Around line 104-142: Update Analysis::apply’s documentation to state its
actual non-fallible guarantee: all supplied changes are applied as one batch
with a single revision bump, without promising rollback or atomic recovery.
Track whether any Change was processed and return the existing revision
unchanged for an empty iterator; only increment self.revision after at least one
change.

In `@crates/ry-analysis/src/rules.rs`:
- Around line 8-14: The RuleInfo::description field is unused because
rules_markdown_table does not render it. Update rules_markdown_table to add a
Description column and include each rule’s description, preserving the existing
code, title, severity, and confidence columns.
- Around line 1-48: The all_rules() registry is incomplete and duplicates
checker-owned severity metadata. Derive RuleInfo entries from
ry_checker::rules::RULES so every checker rule is included and default_severity
stays aligned with the authoritative registry; retain an explicit analysis-side
default_confidence only where no checker equivalent exists, and ensure
rules_markdown_table() consumes this complete registry.

In `@crates/ry-analysis/src/snapshot.rs`:
- Around line 97-106: Construct a single `ry_core::RParser` before the `for
(path, content) in &files` loop and reuse it for every `parser.parse(path,
content)` call. Preserve the existing behavior of adding successfully parsed
files to `indices` while handling parser initialization failure without
attempting per-file construction.
- Around line 65-69: Update the documentation for AnalysisSnapshot to remove the
claim that snapshot queries return QueryResult::Cancelled when the host
advances. Document staleness checking through the existing is_current API
instead, while preserving the descriptions of the snapshot’s immutable,
revision-based behavior.

In `@crates/ry-analysis/src/symbols.rs`:
- Around line 1-6: Correct the module-level and struct documentation in
symbols.rs to describe the current name-based, workspace-wide indexing behavior
rather than claiming scope-aware symbol resolution. Remove or revise statements
about resolved identities preventing same-name collisions and references
matching definitions by (name, scope), while preserving the existing future-work
qualification and accurately noting that find_definitions may return all
definitions sharing a name.
- Around line 420-435: Update the test same_name_different_files to remove the
claim that the reference resolves to b.R’s local definition, since the index
does not perform reference resolution. Keep the assertions that
find_definitions("helper") returns both definitions, and clarify that callers
must disambiguate between them.
- Around line 297-310: Update the Expr::Function handling to index each
parameter’s default expression when param.default is present by calling
index_expr with the existing file and index context. Keep the parameter
definition registration and function body traversal unchanged, ensuring
identifiers in defaults are included in find_references.
- Around line 197-203: Update the named Stmt::FunctionDef handling around
index.add_definition to use a span covering only the function name, ensuring the
indexed range starts at the name rather than the statement’s function keyword;
alternatively remove this parser-unreachable branch. Preserve byte-offset
behavior for quoted and multibyte names and the existing name.len()
calculations.

In `@crates/ry-analysis/tests/property.rs`:
- Around line 34-52: Update the live-versus-fresh property reconstruction to
preserve separate open-file and disk-file inputs from the live host instead of
installing every path as Change::SetDiskFile. Extend arb_change to generate
AddRoot, RemoveRoot, and SetConfig operations, and ensure the fresh host is
rebuilt with the same final roots, configuration, open files, and disk files as
the live host.
- Around line 122-134: Update the revision_monotonically_increases property test
so the loop uses a per-iteration index when constructing the SetDiskFile
content, rather than the total count n. Preserve the existing number of apply
calls and revision assertion while ensuring each iteration writes distinct
content.

In `@crates/ry-checker/src/packages.rs`:
- Around line 7-10: Add the public FFI_PRIMITIVES re-export to the
ry_checker::packages shim alongside the existing package symbols, preserving the
current re-exports and compatibility with callers that access this constant
through the checker namespace.

In `@crates/ry-cli/src/main.rs`:
- Around line 917-922: Change the parsed-file collection to store
Arc<SourceFile> values instead of owned SourceFile instances, then update the
loop around analysis_files and comments to clone the Arc rather than
deep-cloning the file. Preserve the existing path and comments handling, and
pass parsed Arc values as &SourceFile via as_ref() where ResolutionEnvironment
requires that reference.

In `@crates/ry-lsp/Cargo.toml`:
- Line 16: Remove the unused ry-analysis dependency declaration from the ry-lsp
crate’s Cargo.toml, leaving the remaining dependency configuration unchanged.

In `@crates/ry-lsp/src/backend.rs`:
- Around line 2449-2478: The fallback in the publish loop must not borrow
filter, confidence, or excludes from `state.folder_contexts.first()`. Precompute
and store root-level values from `state.file_config` during initialization and
reload, then use those values in the `folder_context_for_path` `None` branch
while preserving the existing folder-specific path and flat compile-counter
behavior. Keep the root-level fallback consistent with
`effective_baseline_for_path` and `state.root`.
- Around line 1413-1435: Update the project-aware hover fallback around
found_project_fn to search other open documents before checking
state.disk_files, matching the open-document precedence used by goto_definition.
Return the function hover when file_has_function finds the identifier in an open
document, while retaining the disk-file search as the fallback.
- Around line 3030-3059: Move the “Check if a parsed file defines a function
with the given name” doc comment onto file_has_function, and move “Extract all
top-level function names from a parsed file” onto extract_function_names. Remove
both misplaced comments from extract_prefix and extract_function_params, leaving
each function documented by its corresponding description.
- Around line 217-270: Remove the redundant file_config parameter from
compute_folder_filter and use the single config argument for both configuration
lookups and Excludes::from_config; update both callers to pass only &config and
&folder_settings. Refactor the #[cfg(test)] merge_filter helper to delegate to
compute_folder_filter instead of duplicating filter-merge logic, preserving its
existing test-facing return behavior.
- Around line 1735-1754: Make all three disk-file fallback searches in
backend.rs deterministic: in the signature-help fallback around lines 1735-1754,
the hover check around lines 1413-1435, and the go-to-definition search around
lines 1467-1505, collect and sort candidate paths before iterating. Preserve
each handler’s existing matching behavior while ensuring duplicate definitions
consistently select the same result.
- Around line 350-361: Restrict the full recollection trigger around
force_full_recollection to newly added or re-added paths that are absent from
self.files, rather than ordinary version or content changes, so incremental
checking remains effective after edits. Reuse the per-file change classification
from the following loop instead of recomputing the any_changed predicate
separately, and preserve recovery for close/reopen convergence cases.
- Around line 200-211: Move FILTER_COMPILE_COUNT and COMPILE_DURING_LAST_PUBLISH
out of process-global statics into per-Backend state, such as Arc<AtomicU64>
fields on State. Update the publish_diagnostics measurement and filter
compilation paths to use that server-owned state, and provide a per-server
accessor for tests so publish assertions only observe the current Backend.
- Around line 1894-1911: Pass each indexed disk file’s source text to the
position-mapping calls so unopened-file locations retain their real UTF-16
ranges: in crates/ry-lsp/src/backend.rs lines 1894-1911, use disk_file.source
for find_references_in_file; in lines 1492-1504, use the indexed file’s source
for find_definition_locations; and in lines 1557-1577, replace disk_text with
that source and update the comment stating text is unnecessary.
- Around line 1673-1696: The completion merge is quadratic and uses an
inconsistent definition of project-defined functions. In the completion path,
build a HashSet<String> of existing item labels once before iterating disk_files
and use it for duplicate checks while adding new labels. Consolidate
extract_function_names, file_has_function, and signature-help discovery around
one shared function-definition traversal so completion, hover, and signature
help expose the same project functions without incorrectly including nested
locals.
- Around line 3032-3054: Update extract_prefix to use the existing
position_to_byte_offset_pos conversion for the cursor position, preserving
UTF-16 handling and invalid-position behavior. Use the returned byte offset
directly as the slice boundary so the character at the cursor is excluded while
all preceding characters, including a trailing underscore, remain in the
extracted prefix.

In `@crates/ry-lsp/src/lib.rs`:
- Line 154: Replace the public re-export of COMPILE_DURING_LAST_PUBLISH and
FILTER_COMPILE_COUNT with read-only accessor functions in the backend module,
following the existing baseline_disk_reads() pattern. Re-export the accessors
from lib.rs and keep the AtomicU64 statics private so consumers can read values
without mutating the test-gate state; ensure the accessors support the requested
per-server scoping.

In `@crates/ry-lsp/tests/p38_feature_diff.rs`:
- Around line 1-16: Update the module documentation in the P38
feature-differential test module to describe these as active regression tests
for the implemented cross-file behavior, removing the claim that they are
#[ignore]’d or not yet implemented. Revise the section comments near the tests
at the referenced finding markers to describe the coverage as currently enforced
rather than future work that will fix each finding.
- Around line 32-44: Replace the unconditional one-second sleep in spawn_session
with bounded polling of the indexing completion signal. Repeatedly issue the
relevant observable request, or await the first textDocument/publishDiagnostics
notification for an indexed file, until it returns a non-null result; enforce a
deadline and fail clearly if indexing does not complete in time.
- Around line 176-191: Update the definition-result assertions around locations
and target_uris to require at least one returned location, then verify that a
returned URI equals b_uri. Preserve the existing exclusion of a_uri while
ensuring the test fails when textDocument/definition returns null or an empty
array.

In `@crates/ry-lsp/tests/w8_session.rs`:
- Around line 266-292: Update Operation::DeleteFile validity in is_valid to
allow deletion of open files by removing the !self.is_open(*file) requirement,
and update its valid_alternative selection to permit open files as deletion
candidates. In the invalid IncrementalEdit alternative handling, preserve
Operation::IncrementalEdit rather than converting it to Operation::FullEdit,
while continuing to substitute only the file slot.
- Around line 580-593: Separate the invalid-operation accounting in the
operations loop from the `skipped_count` variable into distinct counts for
replacements and dropped operations: increment the replacement count only when
`valid_alternative` returns `Some`, and increment the dropped count before
`continue` for `None`. Update the report around the existing reporting logic
near `valid_alternative` to include both outcomes with accurate labels, and
remove the unintended whitespace from the multiline string so it does not output
padding between words.

In `@crates/ry-workspace/src/file_kind.rs`:
- Around line 27-30: Update the component collection in the path-classification
function around relative.components() to reject the entire path when any
component cannot be converted to UTF-8, rather than silently removing it with
filter_map. Propagate the rejection through the existing classification result
so callers such as workspace discovery skip the path, while preserving normal
matching for fully UTF-8 paths.

In `@crates/ry-workspace/src/packages.rs`:
- Around line 234-293: Extend the tests module with coverage for
attached_packages and the remaining namespace_metadata arms. Add tests proving
attached_packages finds library/require calls in nested scopes while excluding
requireNamespace, and add a namespace_metadata test covering import, export, and
S3method results using the existing metadata fields and HashSet comparisons.

In `@docs/architecture/analysis-query-engine.md`:
- Around line 71-75: Update the rationale in the architecture document to remove
the volatile “960 tests pass” count or replace it with a single dated
test-command result consistent with the acceptance records, including the known
pre-existing failure if applicable.

In `@docs/architecture/p38-progress.md`:
- Around line 38-49: Add the text language identifier to the fenced graph blocks
around the dependency diagrams, including the block near line 130, and insert
blank lines immediately before and after each fence. Preserve the diagram
contents while ensuring all affected fences satisfy MD031 and MD040.

In `@docs/architecture/p38-w12-acceptance.md`:
- Around line 32-33: Insert a blank line between the “## Gates” heading and the
following workspace-tests list item in the acceptance document, preserving the
existing heading and content.
- Around line 12-15: The Plan 38 acceptance records incorrectly indicate
completion while W11-W12 criteria remain unfinished. In
docs/architecture/p38-w12-acceptance.md lines 12-15, retain the unchecked
criteria and mark the plan partial until implementation passes; in
docs/architecture/plans-37-39-acceptance.md lines 20-34, remove the 12/12
completion claim until compatibility removal and final acceptance are complete.
- Around line 32-35: Publish one current, dated gate result across all
acceptance records: update docs/architecture/p38-w12-acceptance.md lines 32-35,
docs/architecture/analysis-query-engine.md lines 71-75, and
docs/architecture/plans-37-39-acceptance.md lines 49-53 to consistently use the
correct 960, 963, or 970 test total and reflect convergence after commit
996348125525a8484d1d10d704d2eaf5b9401b14; update docs/release-runbook.md lines
10-18 to reference that same dated gate output and preserve the existing
evidence that both lanes pass.

In `@docs/architecture/p39-acceptance.md`:
- Around line 20-26: Add blank lines immediately after each affected Markdown
section heading at the referenced locations, including the heading above the
schema-crate list, while preserving all existing content and list structure.

In `@docs/architecture/p39-w1-catalog-design.md`:
- Around line 76-89: Update the “ry catalog adapter” contract to match the
implemented API: reference catalog_adapter.rs as the adapter and catalog.rs as
the types/trait definition, change lookup to return Option<&FunctionSemantics>,
change package_functions to return Vec<&str>, and document the missing
function_count() method before treating this section as the architecture
reference.

In `@docs/architecture/plans-37-39-acceptance.md`:
- Around line 3-5: Add a blank line immediately after each section heading at
the referenced locations in the acceptance document, including the Baseline
heading and the headings at lines 49, 55, and 64, while preserving the existing
content.

In `@docs/release-runbook.md`:
- Around line 20-21: Update the “Clean-checkout validated” step in the release
runbook to remove the git clean/cargo build manual command and instead reference
the archive-based validation procedure in
docs/architecture/project-remediation-plan.md, using git archive followed by
cargo metadata and cargo check.
- Around line 34-38: Update the “Tag format” fenced block in the release runbook
to declare the text language on its opening fence, preserving the existing
tag-format example.

In `@ecosystem/check-cargo-edges.py`:
- Around line 18-23: Update the subprocess.run invocation in the cargo
dependency-check flow to include the --locked argument, then validate
result.returncode before assigning or parsing result.stdout. On failure, report
result.stderr and terminate the check with a failure status instead of treating
empty output as a dependency-free tree.

In `@editors/code/src/common/binary.ts`:
- Around line 99-115: Update the activation/version-validation flow around
getRyVersion and checkVersionCapability so the minimum-version check is
performed even when probing returns undefined due to execution, timeout, JSON,
or parsing failures. Ensure unknown-version handling follows an explicit policy,
rather than allowing activation to proceed unverified; preserve normal
validation for successfully detected versions.
- Around line 38-67: The restart flow in runServer must not reuse
activation-time resolvedBinary.path after trust, ry.path, or importStrategy
changes. Before every startServer call, re-resolve the binary using
findRyBinaryPath, probe its version, and replace resolvedBinary with the
refreshed result, following the existing resolution logic in the activation
setup.
- Around line 50-54: Update the candidate resolution loop around resolveHomeDir
to accept only existing regular files with platform-appropriate execute
permission, skipping directories and non-runnable candidates so resolution
continues to later configured or PATH candidates and the bundled fallback. Apply
the same validation consistently to configured and PATH candidate handling.

In `@editors/code/src/common/commands.ts`:
- Around line 93-99: Update the document-opening flow in the surrounding command
handler to await both vscode.workspace.openTextDocument and
vscode.window.showTextDocument inside the existing try block. Replace the
promise callback with sequential awaits so either rejection is handled by the
existing catch block.
- Around line 67-90: Replace both execSync calls in the rule-list and
selected-rule explanation flow with execFileSync, passing binaryPath separately
from the command arguments in arrays and explicitly setting shell: false.
Preserve the existing encoding and timeout options, and keep the picked.label
value as an argument rather than interpolating it into a shell command.

In `@editors/code/src/common/environment.ts`:
- Around line 33-43: Update the PATH handling around currentPath and
collection.prepend: split currentPath using the platform separator and check for
an exact entry matching this.pathToAdd, then prepend only this.pathToAdd plus
the separator rather than the full newPath. Preserve the existing debug message,
early return, and prepend options.

In `@editors/code/src/extension.ts`:
- Around line 149-162: Before each requestRestart call in the
configuration-change and onDidGrantWorkspaceTrust handlers, reload current
workspace settings, call findRyBinaryPath using the current trust state,
re-probe and validate the binary version, and assign the refreshed result to
resolvedBinary so runServer uses the new path. Add coverage for both settings
and workspace-trust transitions.

In `@editors/code/src/test/binary.test.ts`:
- Line 11: Add Bun’s type declarations to the test TypeScript configuration used
by tsconfig.test.json, ensuring bun-types is declared as a project dependency if
needed. Keep the pretest compiler path and runtime test setup aligned so
bun:test resolves successfully without disrupting the existing Mocha E2E tests.

In `@editors/code/src/test/e2e.test.ts`:
- Around line 44-75: The test “Server starts from the resolved binary path
(P37-W2 no split-brain)” must directly verify the binary-path contract instead
of relying on diagnostics from a previously opened fixture. Isolate the test
with a distinct resolved executable, start the server through the relevant
startup path, and assert that the LanguageClient launch command receives that
exact path; remove the indirect diagnostics-based assertion.

In `@editors/zed/src/lib.rs`:
- Around line 129-140: Update the release verification flow around
verify_checksum to bind verification to the downloaded artifact: verify the
archive before extraction, or supply and validate a checksum for the extracted
binary, rejecting missing, malformed, and mismatched sidecars. Remove the unused
_has_sha256_asset logic, update the mismatch test at editors/zed/src/lib.rs
lines 450-469 to call verify_checksum with a temporary file, and adjust
editors/zed/extension.toml lines 9-12 only as needed to provide the checksum
artifact expected by the corrected flow.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b92560bf-c3e9-4f09-8909-f1e1bc69106e

📥 Commits

Reviewing files that changed from the base of the PR and between 6445046 and 9963481.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (79)
  • .github/workflows/build-vscode.yml
  • .github/workflows/ecosystem.yml
  • .github/workflows/release-vscode.yml
  • .github/workflows/test-code.yml
  • CHANGELOG.md
  • Cargo.toml
  • crates/ry-analysis/Cargo.toml
  • crates/ry-analysis/src/catalog.rs
  • crates/ry-analysis/src/catalog_adapter.rs
  • crates/ry-analysis/src/check.rs
  • crates/ry-analysis/src/effect.rs
  • crates/ry-analysis/src/interactive.rs
  • crates/ry-analysis/src/layering.rs
  • crates/ry-analysis/src/lib.rs
  • crates/ry-analysis/src/rules.rs
  • crates/ry-analysis/src/snapshot.rs
  • crates/ry-analysis/src/symbols.rs
  • crates/ry-analysis/tests/property.rs
  • crates/ry-checker/Cargo.toml
  • crates/ry-checker/src/cache.rs
  • crates/ry-checker/src/diagnostics.rs
  • crates/ry-checker/src/infer/call.rs
  • crates/ry-checker/src/infer/misc.rs
  • crates/ry-checker/src/lib.rs
  • crates/ry-checker/src/packages.rs
  • crates/ry-checker/src/project.rs
  • crates/ry-checker/src/semantic_lists.rs
  • crates/ry-checker/src/tests.rs
  • crates/ry-checker/tests/vendor_snapshot.rs
  • crates/ry-cli/Cargo.toml
  • crates/ry-cli/src/main.rs
  • crates/ry-config/Cargo.toml
  • crates/ry-config/src/baseline.rs
  • crates/ry-core/Cargo.toml
  • crates/ry-core/src/diagnostic.rs
  • crates/ry-core/src/lib.rs
  • crates/ry-core/src/parser.rs
  • crates/ry-core/tests/parser_correctness.rs
  • crates/ry-lsp/Cargo.toml
  • crates/ry-lsp/src/backend.rs
  • crates/ry-lsp/src/lib.rs
  • crates/ry-lsp/tests/p36_contract.rs
  • crates/ry-lsp/tests/p38_feature_diff.rs
  • crates/ry-lsp/tests/w8_session.rs
  • crates/ry-workspace/Cargo.toml
  • crates/ry-workspace/src/file_kind.rs
  • crates/ry-workspace/src/lib.rs
  • crates/ry-workspace/src/packages.rs
  • docs/architecture/analysis-query-engine.md
  • docs/architecture/cache-decision.md
  • docs/architecture/p38-progress.md
  • docs/architecture/p38-w12-acceptance.md
  • docs/architecture/p39-acceptance.md
  • docs/architecture/p39-w1-catalog-design.md
  • docs/architecture/plans-37-39-acceptance.md
  • docs/architecture/project-remediation-plan.md
  • docs/corpus/posit-0.9.0.json
  • docs/editor-defaults.md
  • docs/release-runbook.md
  • ecosystem/check-cargo-edges.py
  • ecosystem/check-ledger.py
  • editors/code/.vscode-test.js
  • editors/code/README.md
  • editors/code/eslint.config.mjs
  • editors/code/package.json
  • editors/code/src/common/binary.ts
  • editors/code/src/common/commands.ts
  • editors/code/src/common/constants.ts
  • editors/code/src/common/environment.ts
  • editors/code/src/common/server.ts
  • editors/code/src/common/settings.ts
  • editors/code/src/common/status.ts
  • editors/code/src/common/version.ts
  • editors/code/src/extension.ts
  • editors/code/src/test/binary.test.ts
  • editors/code/src/test/e2e.test.ts
  • editors/code/tsconfig.test.json
  • editors/zed/extension.toml
  • editors/zed/src/lib.rs
💤 Files with no reviewable changes (2)
  • crates/ry-workspace/Cargo.toml
  • crates/ry-checker/src/cache.rs

Comment on lines +9 to +15
paths:
- "editors/code/**"
- ".github/workflows/build-vscode.yml"
pull_request:
paths:
- "editors/code/**"
- ".github/workflows/build-vscode.yml"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Run this workflow for Rust changes that affect ry-cli.

The job builds ry-cli at Line 40. A change to Cargo.lock, workspace manifests, crates/ry-cli, or its dependencies does not match these filters. The VSIX and E2E gate then does not run for that change. Include the Rust build inputs in paths, or remove the path filters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-vscode.yml around lines 9 - 15, Update the path
filters for the build-vscode workflow’s push and pull_request triggers to
include all Rust inputs affecting the ry-cli build, including Cargo.lock,
workspace manifests, crates/ry-cli, and its dependency crates; alternatively
remove the filters so those changes trigger the workflow. Preserve the existing
editor and workflow paths.

with:
persist-credentials: false

- uses: oven-sh/setup-bun@0c5077e51419868618ae5fe8019c62421857d6 # v2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -eu
printf '%s\n' '--- workflow references ---'
rg -n -C 2 'oven-sh/setup-bun@' .github/workflows
printf '%s\n' '--- target workflow ---'
sed -n '25,40p' .github/workflows/build-vscode.yml

printf '%s\n' '--- tag resolution ---'
python3 - <<'PY'
import json, urllib.request
repo = "oven-sh/setup-bun"
for tag in ("v2",):
    url = f"https://api.github.com/repos/{repo}/git/ref/tags/{tag}"
    req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json", "User-Agent": "review-verifier"})
    with urllib.request.urlopen(req) as r:
        ref = json.load(r)
    obj = ref["object"]
    print("ref:", ref["ref"])
    print("object:", obj)
    if obj["type"] == "tag":
        url = f"https://api.github.com/repos/{repo}/git/tags/{obj['sha']}"
        req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json", "User-Agent": "review-verifier"})
        with urllib.request.urlopen(req) as r:
            tag_obj = json.load(r)
        print("dereferenced:", tag_obj["object"])
        commit_sha = tag_obj["object"]["sha"]
    else:
        commit_sha = obj["sha"]
    url = f"https://api.github.com/repos/{repo}/commits/{commit_sha}"
    req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json", "User-Agent": "review-verifier"})
    with urllib.request.urlopen(req) as r:
        commit = json.load(r)
    print("commit:", commit["sha"])
PY

Repository: sims1253/ry

Length of output: 4113


🏁 Script executed:

set -eu
printf '%s\n' '--- dereferenced v2 tag ---'
git ls-remote https://github.com/oven-sh/setup-bun.git 'refs/tags/v2' 'refs/tags/v2^{}'

printf '%s\n' '--- candidate commit resolution ---'
for sha in \
  0c5077e51419868618ae5fe8019c62421857d6 \
  0c5077e51419868618aeaa5fe8019c62421857d6
do
  printf '%s: ' "$sha"
  curl -ksSfL \
    -H 'Accept: application/vnd.github+json' \
    -H 'User-Agent: review-verifier' \
    "https://api.github.com/repos/oven-sh/setup-bun/commits/$sha" |
    jq -r '.sha // ("ERROR: " + (.message // "unknown"))'
done

Repository: sims1253/ry

Length of output: 549


🏁 Script executed:

set -u
sha='0c5077e51419868618aeaa5fe8019c62421857d6'

printf '%s\n' '--- GitHub CLI availability ---'
if command -v gh >/dev/null 2>&1; then
  gh api "repos/oven-sh/setup-bun/commits/$sha" --jq '.sha' || true
else
  echo 'gh: unavailable'
fi

printf '%s\n' '--- direct API commit check ---'
curl --noproxy '*' -sS -k \
  -H 'Accept: application/vnd.github+json' \
  -H 'User-Agent: review-verifier' \
  "https://api.github.com/repos/oven-sh/setup-bun/commits/$sha" |
  jq -r '.sha // ("ERROR: " + (.message // "unknown"))' || true

Repository: sims1253/ry

Length of output: 427


Use the immutable setup-bun v2 commit SHA.

Replace 0c5077e51419868618ae5fe8019c62421857d6 with 0c5077e51419868618aeaa5fe8019c62421857d6.

🧰 Tools
🪛 GitHub Check: zizmor

[failure] 33-33:
unpinned action reference: action is not pinned to a hash (required by blanket policy)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-vscode.yml at line 33, Update the
`oven-sh/setup-bun` action reference in the workflow to use the requested
immutable v2 commit SHA, replacing the existing SHA with
`0c5077e51419868618ae5fe8019c62421857d6` while leaving the action version
comment unchanged.

Source: Linters/SAST tools

Comment on lines +92 to +93
# Extract from README marketplace link
readme_id=$(grep -oP 'itemName=\K[^")\s]+' README.md | head -1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Read the extension README in both publisher checks.

Both jobs run from the repository root and inspect README.md. The extension marketplace identifier is in editors/code/README.md. If the root README has no match, pipefail makes the job fail. If it has another marketplace link, the gate validates the wrong document.

  • .github/workflows/build-vscode.yml#L92-L93: change the input path to editors/code/README.md.
  • .github/workflows/test-code.yml#L62-L62: change the input path to editors/code/README.md.
📍 Affects 2 files
  • .github/workflows/build-vscode.yml#L92-L93 (this comment)
  • .github/workflows/test-code.yml#L62-L62
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-vscode.yml around lines 92 - 93, Update the README
input used by the marketplace identifier extraction in
.github/workflows/build-vscode.yml lines 92-93 and
.github/workflows/test-code.yml line 62 to read editors/code/README.md instead
of the repository-root README; no other workflow behavior should change.

run: |
set -euo pipefail
base_url="https://github.com/sims1253/ry/releases/latest/download"
base_url="https://github.com/sims1253/ry/releases/download/${{ inputs.core-tag }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pass core-tag through an environment variable.

The workflow expands inputs.core-tag directly in shell source. A crafted value can terminate the quoted assignment and run commands in the release build job. Pass the input through env and expand "$CORE_TAG" inside the script.

Proposed fix
+        env:
+          CORE_TAG: ${{ inputs.core-tag }}
         run: |
           set -euo pipefail
-          base_url="https://github.com/sims1253/ry/releases/download/${{ inputs.core-tag }}"
+          base_url="https://github.com/sims1253/ry/releases/download/${CORE_TAG}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
base_url="https://github.com/sims1253/ry/releases/download/${{ inputs.core-tag }}"
env:
CORE_TAG: ${{ inputs.core-tag }}
run: |
set -euo pipefail
base_url="https://github.com/sims1253/ry/releases/download/${CORE_TAG}"
🧰 Tools
🪛 GitHub Check: zizmor

[failure] 80-80:
code injection via template expansion: may expand into attacker-controllable code

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-vscode.yml at line 80, Update the release build
job’s shell step around the base_url assignment to pass inputs.core-tag via the
step environment, then construct the URL using the quoted "$CORE_TAG"
environment variable instead of interpolating the input directly in shell
source.

Source: Linters/SAST tools

Comment thread CHANGELOG.md
Comment on lines +9 to +11
#### P37-W1: Parser UTF-8 boundary panic fix
- Fixed panic in `lower_namespace` when string RHS last byte is inside a
multi-byte character. Extracted shared `strip_quotes_at_boundaries` helper.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines after the subsection headings.

Line 9 is followed immediately by paragraph text. Apply the same fix after Lines 13, 19, 25, 30, 34, 39, and 44.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 9 - 11, Add blank lines immediately after each
subsection heading in CHANGELOG.md, including P37-W1 and the headings at the
other specified sections, while leaving the changelog text unchanged.

Source: Linters/SAST tools

Comment on lines +33 to +43
const currentPath = collection.get("PATH")?.value ?? process.env.PATH ?? "";
if (currentPath.includes(this.pathToAdd)) {
this.logger.debug(`${this.pathToAdd} is already on PATH`);
return;
}

const newPath = `${this.pathToAdd}${process.platform === "win32" ? ";" : ":"}${currentPath}`;
collection.prepend("PATH", newPath, {
applyAtShellIntegration: true,
applyAtProcessCreation: false,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

file="editors/code/src/common/environment.ts"
printf '%s\n' '--- candidate file outline ---'
ast-grep outline "$file" --lang typescript || true

printf '%s\n' '--- relevant source ---'
sed -n '1,120p' "$file"

printf '%s\n' '--- PATH collection API usages ---'
rg -n -C 4 'collection\.(prepend|append|replace|get)\("PATH"|pathToAdd|applyAtShellIntegration|applyAtProcessCreation' editors/code/src editors/code/test 2>/dev/null || true

printf '%s\n' '--- repository references to EnvironmentVariableCollection semantics ---'
rg -n -C 3 'EnvironmentVariableCollection|prepend\(' editors/code package.json 2>/dev/null | head -300 || true

Repository: sims1253/ry

Length of output: 7074


🏁 Script executed:

#!/bin/bash
set -eu

file="editors/code/src/common/environment.ts"
sed -n '1,120p' "$file"

rg -n -C 4 \
  'collection\.(prepend|append|replace|get)\("PATH"|pathToAdd|applyAtShellIntegration|applyAtProcessCreation' \
  editors/code/src editors/code/test 2>/dev/null || true

Repository: sims1253/ry

Length of output: 4706


🌐 Web query:

VS Code API EnvironmentVariableCollection prepend get mutator value semantics

💡 Result:

The EnvironmentVariableCollection.prepend method in the VS Code API is used to add a value to the beginning of an environment variable for terminals [1][2]. Key semantics and behaviors include: 1. Single Mutation Limit: An extension can make only a single change to any one specific environment variable per EnvironmentVariableCollection instance [1][2]. Subsequent calls to replace, append, or prepend for the same variable will overwrite any previous calls [1][2]. 2. Options and Timing: The method accepts an optional EnvironmentVariableMutatorOptions object [1][3]. If no options are provided, it defaults to { applyAtProcessCreation: true } [1][2]. You can control precisely when the mutation is applied by setting flags such as applyAtProcessCreation (applied when the terminal process is launched) or applyAtShellIntegration (applied via VS Code's shell integration script, which runs after shell profile scripts) [3][4][5]. 3. Shell Integration: Because environment variable modification can be complex—particularly on platforms like macOS where login shells or shell profile scripts (like .zshrc) may re-initialize or override environment variables—using applyAtShellIntegration: true is often necessary to ensure the prepended value is correctly positioned at the start of the variable [4][6][5]. 4. State: The EnvironmentVariableCollection itself is an iterable of the current mutations [3][7]. For scenarios where prepend is not behaving as expected (e.g., being overridden by shell configuration files), ensuring shell integration is enabled and using the appropriate options is the standard approach to maintaining the desired environment variable order [4][6][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- referenced upstream paths ---'
rg -n -C 5 'PathEnvironmentVariableManager|class .*Environment.*Variable|prepend\("PATH"|environment\.ts' . \
  -g '*.ts' -g '*.js' -g '*.md' 2>/dev/null | head -400 || true

printf '%s\n' '--- package and lockfile VS Code API versions ---'
rg -n '"`@types/vscode`"|"vscode"' package.json editors/code/package.json editors/code/*lock* pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

Repository: sims1253/ry

Length of output: 3065


🏁 Script executed:

#!/bin/bash
set -eu

rg -n -C 5 \
  'PathEnvironmentVariableManager|prepend\("PATH"|environment\.ts' . \
  -g '*.ts' -g '*.js' -g '*.md' 2>/dev/null | head -400 || true

Repository: sims1253/ry

Length of output: 2624


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
cases = [
    ("/opt/ry", "/opt/ry-old:/usr/bin", ":"),
    ("/opt/ry", "/usr/bin:/bin", ":"),
    (r"C:\ry", r"C:\ry-old;C:\Windows\System32", ";"),
]

for path_to_add, current_path, separator in cases:
    substring_match = path_to_add in current_path
    exact_match = path_to_add in current_path.split(separator)
    prefix = path_to_add if path_to_add.endswith(separator) else path_to_add + separator

    # Model EnvironmentVariableCollection.prepend: supplied value + existing PATH.
    resulting_path = prefix + current_path

    print({
        "path_to_add": path_to_add,
        "current_path": current_path,
        "substring_match": substring_match,
        "exact_match": exact_match,
        "resulting_path_with_prefix": resulting_path,
        "resulting_path_with_full_new_path": (
            f"{path_to_add}{separator}{current_path}{current_path}"
        ),
    })
PY

Repository: sims1253/ry

Length of output: 962


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
cases = [
    ("/opt/ry", "/opt/ry-old:/usr/bin", ":"),
    (r"C:\ry", r"C:\ry-old;C:\Windows\System32", ";"),
]

for path_to_add, current_path, separator in cases:
    print("substring:", path_to_add in current_path)
    print("exact entry:", path_to_add in current_path.split(separator))
    print("prepend prefix:", path_to_add + separator + current_path)
    print("prepend current implementation:", path_to_add + separator + current_path + current_path)
PY

Repository: sims1253/ry

Length of output: 494


Pass only the new PATH prefix and compare PATH entries.

currentPath.includes(this.pathToAdd) can match a substring such as /opt/ry-old. Also, prepend adds its argument to the existing PATH, so passing newPath duplicates the existing entries. Split PATH by the platform separator for exact matching, then pass only this.pathToAdd followed by the separator.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editors/code/src/common/environment.ts` around lines 33 - 43, Update the PATH
handling around currentPath and collection.prepend: split currentPath using the
platform separator and check for an exact entry matching this.pathToAdd, then
prepend only this.pathToAdd plus the separator rather than the full newPath.
Preserve the existing debug message, early return, and prepend options.

Comment on lines +149 to +162
const oldSettings = settings;
const newSettings = getWorkspaceSettings(serverId, {
uri: vscode.Uri.file(process.cwd()),
index: 0,
name: "root",
} as vscode.WorkspaceFolder);
const binaryPath = findRyBinaryPath(settings, isUntrusted);
const version = getRyVersion(binaryPath);
resolvedBinary = { path: binaryPath, version };

if (version) {
const versionError = checkVersionCapability(
resolvedBinary,
MINIMUM_VERSION,
"settings channel",
);
if (versionError) {
statusItem.setError(versionError);
vscode.window.showErrorMessage(versionError);
return;
}
} as vscode.WorkspaceFolder);

if (checkIfConfigurationChanged(oldSettings, newSettings)) {
await requestRestart();
}
}),
// E3: Workspace trust changes respawn because trust affects binary resolution.
vscode.workspace.onDidGrantWorkspaceTrust(async () => {
await requestRestart();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Refresh resolvedBinary before a restart.

Lines 149-162 request a restart but retain the activation-time settings and resolvedBinary. runServer therefore launches the old path at Line 99 after ry.path, ry.importStrategy, or workspace trust changes.

Reload workspace settings, call findRyBinaryPath with the current trust state, re-probe and validate the version, then replace resolvedBinary before calling requestRestart. Add coverage for a settings or trust transition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editors/code/src/extension.ts` around lines 149 - 162, Before each
requestRestart call in the configuration-change and onDidGrantWorkspaceTrust
handlers, reload current workspace settings, call findRyBinaryPath using the
current trust state, re-probe and validate the binary version, and assign the
refreshed result to resolvedBinary so runServer uses the new path. Add coverage
for both settings and workspace-trust transitions.

* to execute an arbitrary binary.
*/

import { describe, it, expect } from "bun:test";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files 'editors/code/src/test/binary.test.ts' 'editors/code/**/tsconfig*.json' 'editors/code/**/package.json' 'editors/code/**/bun.lock*' 'editors/code/**/vitest*' 'editors/code/**/jest*'

printf '%s\n' '--- test file ---'
cat -n editors/code/src/test/binary.test.ts

printf '%s\n' '--- TypeScript and package configuration ---'
for f in $(git ls-files 'editors/code/**/tsconfig*.json' 'editors/code/**/package.json' 'editors/code/**/bun.lock*' 'editors/code/**/vitest*' 'editors/code/**/jest*'); do
  printf '\n--- %s ---\n' "$f"
  cat -n "$f"
done

printf '%s\n' '--- references to test runners and test types ---'
rg -n --hidden -g '!node_modules' -g '!dist' -g '!build' \
  'bun:test|bun-types|vitest|jest|`@types/jest`|describe|tsconfig\.test|tsc -p' \
  editors/code

Repository: sims1253/ry

Length of output: 3722


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files \
  'editors/code/src/test/binary.test.ts' \
  'editors/code/**/tsconfig*.json' \
  'editors/code/**/package.json' \
  'editors/code/**/bun.lock*' \
  'editors/code/**/vitest*' \
  'editors/code/**/jest*'

printf '%s\n' '--- test file ---'
cat -n editors/code/src/test/binary.test.ts

printf '%s\n' '--- TypeScript and package configuration ---'
while IFS= read -r f; do
  printf '\n--- %s ---\n' "$f"
  cat -n "$f"
done < <(git ls-files \
  'editors/code/**/tsconfig*.json' \
  'editors/code/**/package.json' \
  'editors/code/**/bun.lock*' \
  'editors/code/**/vitest*' \
  'editors/code/**/jest*')

printf '%s\n' '--- references to test runners and test types ---'
rg -n --hidden -g '!node_modules' -g '!dist' -g '!build' \
  'bun:test|bun-types|vitest|jest|`@types/jest`|describe|tsconfig\.test|tsc -p' \
  editors/code

Repository: sims1253/ry

Length of output: 3369


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- editors/code directory ---'
find editors/code -maxdepth 3 -type f -print | sort

printf '%s\n' '--- repository configuration files ---'
find . -maxdepth 4 -type f \( \
  -name 'package.json' -o \
  -name 'tsconfig*.json' -o \
  -name 'bunfig.toml' -o \
  -name 'bun.lock' -o \
  -name 'bun.lockb' -o \
  -name 'pnpm-lock.yaml' -o \
  -name 'yarn.lock' -o \
  -name 'package-lock.json' \
\) -print | sort

printf '%s\n' '--- package.json ---'
if [ -f editors/code/package.json ]; then
  cat -n editors/code/package.json
fi

printf '%s\n' '--- relevant repository references ---'
rg -n --hidden -g '!node_modules' -g '!dist' -g '!build' \
  'tsconfig\.test\.json|bun:test|bun-types|bun test|compile-tests|`@types/node`|typescript|test runner|vitest|jest' \
  .

Repository: sims1253/ry

Length of output: 17849


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- TypeScript configurations ---'
cat -n editors/code/tsconfig.json
printf '\n--- editors/code/tsconfig.test.json ---\n'
cat -n editors/code/tsconfig.test.json

printf '%s\n' '--- VS Code workflow ---'
cat -n .github/workflows/build-vscode.yml

printf '%s\n' '--- relevant documentation ---'
sed -n '70,110p' docs/architecture/project-remediation-plan.md

Repository: sims1253/ry

Length of output: 6098


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- E2E test imports and declarations ---'
cat -n editors/code/src/test/e2e.test.ts

printf '%s\n' '--- Bun-related lockfile entries ---'
rg -n -C 2 '"bun-types"|"`@types/bun`"|bun:test' editors/code/bun.lock

printf '%s\n' '--- read-only configuration probe ---'
python3 - <<'PY'
import json
from pathlib import Path

root = Path("editors/code")
package = json.loads((root / "package.json").read_text())
base = json.loads((root / "tsconfig.json").read_text())
tests = json.loads((root / "tsconfig.test.json").read_text())

test_files = sorted(str(p.relative_to(root)) for p in (root / "src/test").glob("**/*.ts"))
bun_imports = []
for name in test_files:
    text = (root / name).read_text()
    if 'from "bun:test"' in text or "from 'bun:test'" in text:
        bun_imports.append(name)

print("compile-tests:", package["scripts"]["compile-tests"])
print("pretest:", package["scripts"]["pretest"])
print("base types:", base["compilerOptions"]["types"])
print("test types:", tests["compilerOptions"]["types"])
print("test include:", tests["include"])
print("test files:", test_files)
print("bun:test files:", bun_imports)
print("bun-types in devDependencies:", "bun-types" in package["devDependencies"])
print("bun-types in lockfile:", '"bun-types"' in (root / "bun.lock").read_text())
print("mocha types in devDependencies:", "`@types/mocha`" in package["devDependencies"])
PY

Repository: sims1253/ry

Length of output: 3321


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import json
from pathlib import Path

root = Path("editors/code")
package = json.loads((root / "package.json").read_text())
base = json.loads((root / "tsconfig.json").read_text())
tests = json.loads((root / "tsconfig.test.json").read_text())
lock = (root / "bun.lock").read_text()

test_files = sorted(
    str(p.relative_to(root))
    for p in (root / "src/test").glob("**/*.ts")
)
bun_imports = [
    name for name in test_files
    if 'from "bun:test"' in (root / name).read_text()
    or "from 'bun:test'" in (root / name).read_text()
]

print("compile-tests:", package["scripts"]["compile-tests"])
print("pretest:", package["scripts"]["pretest"])
print("base types:", base["compilerOptions"]["types"])
print("test types:", tests["compilerOptions"]["types"])
print("test include:", tests["include"])
print("test files:", test_files)
print("bun:test files:", bun_imports)
print("bun-types in devDependencies:", "bun-types" in package["devDependencies"])
print("bun-types in lockfile:", '"bun-types"' in lock)
print("mocha types in devDependencies:", "`@types/mocha`" in package["devDependencies"])
PY

printf '%s\n' '--- Bun-related lockfile references ---'
if rg -n -C 2 '"bun-types"|"`@types/bun`"|bun:test' editors/code/bun.lock; then
  :
else
  printf '%s\n' 'No Bun type package or bun:test reference found in editors/code/bun.lock.'
fi

Repository: sims1253/ry

Length of output: 705


Add Bun test types to the test configuration.

tsconfig.test.json includes src/test/binary.test.ts, but its types list excludes Bun and the project does not declare bun-types. The pretest script runs tsc -p tsconfig.test.json, so bun:test causes TS2307 before the VS Code tests run. Add the Bun types, or compile the Bun unit test separately from the Mocha E2E tests. Keep the runtime and TypeScript test configurations aligned.

🧰 Tools
🪛 GitHub Actions: Test VS Code extension / 1_test.txt

[error] 11-11: TypeScript compilation failed during 'bun run test' (tsc -p tsconfig.test.json --outDir out): Cannot find module 'bun:test' or its corresponding type declarations (TS2307).

🪛 GitHub Actions: Test VS Code extension / test

[error] 11-11: TypeScript compilation failed during 'bun run test' (tsc -p tsconfig.test.json --outDir out): Cannot find module 'bun:test' or its corresponding type declarations (TS2307).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editors/code/src/test/binary.test.ts` at line 11, Add Bun’s type declarations
to the test TypeScript configuration used by tsconfig.test.json, ensuring
bun-types is declared as a project dependency if needed. Keep the pretest
compiler path and runtime test setup aligned so bun:test resolves successfully
without disrupting the existing Mocha E2E tests.

Source: Pipeline failures

Comment on lines +44 to +75
// P37-W2: After the split-brain fix, startServer() receives the
// pre-resolved binaryPath from extension.ts (which called
// findRyBinaryPath with the isUntrusted flag). The server starting
// and producing diagnostics proves a single binary identity — no
// separate resolveBinary() path can launch a different binary.
// The unit tests in binary.test.ts verify trust-honoring resolution.
it("Server starts from the resolved binary path (P37-W2 no split-brain)", async function () {
this.timeout(30000);

const fixturePath = path.join(
__dirname,
"..",
"..",
"testFixture",
"bad.R",
);
const uri = vscode.Uri.file(fixturePath);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);

const deadline = Date.now() + 15000;
let serverStarted = false;
while (Date.now() < deadline) {
const diags = vscode.languages.getDiagnostics(uri);
if (diags.length > 0) {
serverStarted = true;
break;
}
await new Promise((resolve) => setTimeout(resolve, 200));
}
// Server started means startServer received a valid binaryPath.
expect(serverStarted).to.equal(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Test the binary-path contract directly.

This test only observes diagnostics for a fixture opened by the preceding test. Existing diagnostics can make it pass without a new server start. A different executable can also emit diagnostics, so this does not verify the no-split-brain contract.

Use an isolated test with a distinct resolved executable and assert the LanguageClient launch command receives that exact path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editors/code/src/test/e2e.test.ts` around lines 44 - 75, The test “Server
starts from the resolved binary path (P37-W2 no split-brain)” must directly
verify the binary-path contract instead of relying on diagnostics from a
previously opened fixture. Isolate the test with a distinct resolved executable,
start the server through the relevant startup path, and assert that the
LanguageClient launch command receives that exact path; remove the indirect
diagnostics-based assertion.

Comment thread editors/zed/src/lib.rs
Comment on lines +129 to +140
let sha256_asset_name = format!("{}.sha256", release_details.asset_name);
let _has_sha256_asset = release
.assets
.iter()
.any(|a| a.name == sha256_asset_name);
match Self::verify_checksum(None, &release_details.downloaded_binary_path) {
Ok(()) => {}
Err(error) => {
// Remove the partial download on verification failure.
let _ = fs::remove_dir_all(&release_details.downloaded_directory);
return Err(format!(
"Binary checksum verification failed: {error}. The download may be corrupted or tampered with."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline editors/zed/src/lib.rs --items all
rg -n -C 5 'download_file|sha256_asset|verify_checksum|downloaded_binary_path' editors/zed/src/lib.rs
rg -n -C 6 'sha256|checksum|upload.*asset|release' .github/workflows ecosystem

Repository: sims1253/ry

Length of output: 40773


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- download and checksum flow ---'
sed -n '90,170p' editors/zed/src/lib.rs
printf '%s\n' '--- checksum implementation and tests ---'
sed -n '223,285p' editors/zed/src/lib.rs
sed -n '416,475p' editors/zed/src/lib.rs
printf '%s\n' '--- Zed extension metadata ---'
cat editors/zed/extension.toml
printf '%s\n' '--- release asset generation and naming ---'
sed -n '70,112p' .github/workflows/release-vscode.yml
rg -n -C 4 'sha256|checksum|dist.*hash|hash.*asset|\.sha256' .github editors/zed

Repository: sims1253/ry

Length of output: 50367


Bind the release checksum to the downloaded artifact before executing it.

_has_sha256_asset is unused, and verify_checksum(None, ...) skips checksum comparison. A modified binary can therefore pass the check. The mismatch test also does not call verify_checksum.

The release pipeline provides checksums for the downloaded archive, while this code hashes the extracted binary. Verify the archive before extraction, or provide a checksum for the extracted binary. Reject missing, malformed, and mismatched sidecars. Add a test that calls verify_checksum with a temporary file.

📍 Affects 2 files
  • editors/zed/src/lib.rs#L129-L140 (this comment)
  • editors/zed/src/lib.rs#L450-L469
  • editors/zed/extension.toml#L9-L12
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editors/zed/src/lib.rs` around lines 129 - 140, Update the release
verification flow around verify_checksum to bind verification to the downloaded
artifact: verify the archive before extraction, or supply and validate a
checksum for the extracted binary, rejecting missing, malformed, and mismatched
sidecars. Remove the unused _has_sha256_asset logic, update the mismatch test at
editors/zed/src/lib.rs lines 450-469 to call verify_checksum with a temporary
file, and adjust editors/zed/extension.toml lines 9-12 only as needed to provide
the checksum artifact expected by the corrected flow.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment on lines +26 to +32
fn convert_param(p: &ry_typeshed::ParamSpec) -> ParameterSpec {
ParameterSpec {
name: p.name.clone(),
has_default: p.default.is_some(),
variadic: p.name == "...",
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Determine the schema meaning of ParamSpec.default and ParamSpec.required.
set -euo pipefail

rg -nP --type=rust -C 10 'struct\s+ParamSpec' crates/ry-typeshed/src/lib.rs

# Find existing consumers that already interpret these two fields.
rg -nP --type=rust -C 4 '\.required\b|\.default\b' crates/ry-checker/src crates/ry-typeshed/src

Repository: sims1253/ry

Length of output: 17316


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- catalog adapter and ParameterSpec definitions/usages ---'
rg -n -C 8 'fn convert_param|struct ParameterSpec|has_default|RY091|emit_missing' \
  crates/ry-analysis crates/ry-checker crates/ry-typeshed

printf '%s\n' '--- ParamSpec deserialization and validation ---'
sed -n '500,565p' crates/ry-typeshed/src/lib.rs
sed -n '1200,1240p' crates/ry-typeshed/src/lib.rs
sed -n '1685,1730p' crates/ry-typeshed/src/lib.rs

printf '%s\n' '--- relevant catalog adapter source ---'
sed -n '1,80p' crates/ry-analysis/src/catalog_adapter.rs

Repository: sims1253/ry

Length of output: 44699


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- schema documentation and fixture values ---'
rg -n -i -C 4 'required|default' \
  docs crates/ry-typeshed testdata 2>/dev/null | head -n 500

printf '%s\n' '--- all ParamSpec default/required combinations in tracked data ---'
rg -n -o '"required"[[:space:]]*:[[:space:]]*(true|false)|"default"[[:space:]]*:[[:space:]]*(true|false|null)' \
  . --glob '*.json' --glob '*.md' --glob '*.rs' --glob '*.toml' 2>/dev/null |
  head -n 500

printf '%s\n' '--- catalog adapter consumers and has_default call graph ---'
rg -n -C 10 'convert_function_sig|ParameterSpec|has_default' \
  crates --glob '*.rs'

Repository: sims1253/ry

Length of output: 44202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- tracked typeshed files ---'
git ls-files | grep -E '(^|/)(typeshed|stub|schema|catalog)|ry-typeshed' | head -n 200

printf '%s\n' '--- exact default boolean values in tracked source/data ---'
rg -n --glob '*.json' --glob '*.rs' --glob '*.md' \
  '"default"[[:space:]]*:[[:space:]]*(true|false)' . 2>/dev/null |
  grep -E '"default"[[:space:]]*:[[:space:]]*(true|false)' |
  head -n 200 || true

printf '%s\n' '--- exact required/default object pairs ---'
python3 - <<'PY'
import json
from pathlib import Path

for path in Path('.').rglob('*.json'):
    try:
        value = json.loads(path.read_text())
    except Exception:
        continue

    def walk(x):
        if isinstance(x, dict):
            if 'name' in x and ('required' in x or 'default' in x):
                print(path, {k: x[k] for k in ('name', 'required', 'default') if k in x})
            for v in x.values():
                walk(v)
        elif isinstance(x, list):
            for v in x:
                walk(v)
    walk(value)
PY

printf '%s\n' '--- all has_default references ---'
rg -n -C 6 'has_default' crates --glob '*.rs' || true

Repository: sims1253/ry

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- tracked typeshed files ---'
git ls-files | grep -E '(^|/)(typeshed|stub|schema|catalog)|ry-typeshed' | head -n 200 || true

printf '%s\n' '--- exact default boolean values in tracked source/data ---'
rg -n --glob '*.json' --glob '*.rs' --glob '*.md' \
  '"default"[[:space:]]*:[[:space:]]*(true|false)' . 2>/dev/null |
  head -n 200 || true

printf '%s\n' '--- exact required/default object pairs ---'
python3 - <<'PY'
import json
from pathlib import Path

for path in Path('.').rglob('*.json'):
    try:
        value = json.loads(path.read_text())
    except Exception:
        continue

    def walk(x):
        if isinstance(x, dict):
            if 'name' in x and ('required' in x or 'default' in x):
                print(path, {k: x[k] for k in ('name', 'required', 'default') if k in x})
            for v in x.values():
                walk(v)
        elif isinstance(x, list):
            for v in x:
                walk(v)

    walk(value)
PY

printf '%s\n' '--- all has_default references ---'
rg -n -C 6 'has_default' crates --glob '*.rs' || true

Repository: sims1253/ry

Length of output: 50367


🌐 Web query:

sims1253/ry ParamSpec default required Option<bool> typeshed schema

💡 Result:

Based on your query, there are two distinct projects named "ry" and the technical details regarding ParamSpec defaults, required status, and Option typeshed schemas appear to be unrelated to the specific R language static checker "sims1253/ry" [1]. 1. sims1253/ry: This is a fast static checker for the R language, written in Rust [1]. It does not involve Python's ParamSpec or typeshed schemas in the context of its own architecture [1]. Its configuration is handled via a ry.toml file [1]. 2. Python ParamSpec Defaults (PEP 696): The concept of "ParamSpec defaults" is a Python feature introduced by PEP 696 [2]. In Python's typeshed (the collection of type stubs for the standard library), ParamSpec is defined in typing.pyi [3][4][5]. As of recent Python versions (3.13+), ParamSpec includes a default attribute [3][4]. 3. Rust ParamSpec (leviath-scripting): There is an unrelated Rust project (leviath-scripting) that contains a ParamSpec struct with a schema: Option<Value> field, a required: bool field, and a description: String field [6]. This appears to be the source of the "Option" and "schema" terminology in your query [6]. In summary, your search terms combine an R language static checker [1], a Python type system feature [2], and a Rust-based tool definition [6]. There is no intersection between the "sims1253/ry" project and the other technical concepts mentioned.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository metadata and schema-related files ---'
curl -fsSL https://api.github.com/repos/sims1253/ry/git/trees/main?recursive=1 |
  jq -r '.tree[].path' |
  grep -Ei 'schema|typeshed|catalog|readme|json' |
  head -n 200

printf '%s\n' '--- upstream references to ParamSpec/default semantics ---'
for path in \
  crates/ry-typeshed/src/lib.rs \
  crates/ry-analysis/src/catalog_adapter.rs \
  crates/ry-analysis/src/catalog.rs \
  crates/ry-typeshed/vendor/SOURCE \
  README.md
do
  url="https://raw.githubusercontent.com/sims1253/ry/main/$path"
  printf '\n--- %s ---\n' "$path"
  curl -fsSL "$url" | rg -n -C 3 'ParamSpec|default|required|has_default' | head -n 120 || true
done

printf '%s\n' '--- vendor parameter metadata counts ---'
python3 - <<'PY'
import json
from pathlib import Path
from collections import Counter

counts = Counter()
examples = {}
for path in Path('crates/ry-typeshed/vendor').glob('*/**.json'):
    try:
        root = json.loads(path.read_text())
    except Exception:
        continue

    def walk(x):
        if isinstance(x, dict):
            if 'name' in x and ('required' in x or 'default' in x):
                key = (x.get('required', '<absent>'), x.get('default', '<absent>'))
                counts[key] += 1
                examples.setdefault(key, (str(path), x.get('name')))
            for value in x.values():
                walk(value)
        elif isinstance(x, list):
            for value in x:
                walk(value)

    walk(root)

for key, count in sorted(counts.items(), key=lambda item: str(item[0])):
    print(key, count, examples[key])
PY

Repository: sims1253/ry

Length of output: 15825


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository metadata and schema-related files ---'
curl -fsSL 'https://api.github.com/repos/sims1253/ry/git/trees/main?recursive=1' |
  jq -r '.tree[].path' |
  grep -Ei 'schema|typeshed|catalog|readme|json' |
  head -n 200

printf '%s\n' '--- upstream references to ParamSpec/default semantics ---'
for path in \
  crates/ry-typeshed/src/lib.rs \
  crates/ry-analysis/src/catalog_adapter.rs \
  crates/ry-analysis/src/catalog.rs \
  crates/ry-typeshed/vendor/SOURCE \
  README.md
do
  url="https://raw.githubusercontent.com/sims1253/ry/main/$path"
  printf '\n--- %s ---\n' "$path"
  curl -fsSL "$url" |
    rg -n -C 3 'ParamSpec|default|required|has_default' |
    head -n 120 || true
done

printf '%s\n' '--- vendor parameter metadata counts ---'
python3 - <<'PY'
import json
from pathlib import Path
from collections import Counter

counts = Counter()
examples = {}

for path in Path('crates/ry-typeshed/vendor').glob('*/**.json'):
    try:
        root = json.loads(path.read_text())
    except Exception:
        continue

    def walk(x):
        if isinstance(x, dict):
            if 'name' in x and ('required' in x or 'default' in x):
                key = (x.get('required', '<absent>'), x.get('default', '<absent>'))
                counts[key] += 1
                examples.setdefault(key, (str(path), x.get('name')))
            for value in x.values():
                walk(value)
        elif isinstance(x, list):
            for value in x:
                walk(value)

    walk(root)

for key, count in sorted(counts.items(), key=lambda item: str(item[0])):
    print(key, count, examples[key])
PY

Repository: sims1253/ry

Length of output: 15825


Preserve explicit no-default metadata.

default: Some(false) is a valid explicit no-default value. Use p.default.unwrap_or(!p.required) instead of p.default.is_some(). The current RY091 path reads required directly, so this conversion affects catalog consumers of ParameterSpec.has_default, not RY091.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-analysis/src/catalog_adapter.rs` around lines 26 - 32, Update
convert_param so ParameterSpec.has_default uses the explicit p.default value
when present, including Some(false), and otherwise derives it from the inverse
of p.required via unwrap_or(!p.required). Leave the name and variadic mappings
unchanged.

Comment on lines +34 to +64
fn convert_evaluation(sig: &FunctionSig) -> Evaluation {
// Check for promise-capture / defusing modes.
let modes: Vec<&EvalMode> = sig.eval.values().collect();
if modes.iter().any(|m| matches!(m, EvalMode::QuotedSymbol)) {
return Evaluation::PromiseCapture {
defusing: DefusingKind::Ensym,
};
}
if modes
.iter()
.any(|m| matches!(m, EvalMode::QuotedExpression))
{
return Evaluation::PromiseCapture {
defusing: DefusingKind::Enquos,
};
}
if modes.iter().any(|m| matches!(m, EvalMode::CapturesPromise)) {
return Evaluation::PromiseCapture {
defusing: DefusingKind::Enquo,
};
}
if modes.iter().any(|m| matches!(m, EvalMode::TidySelect)) {
return Evaluation::DataMask {
select: Some(SelectKind::ColumnSelect),
};
}
if modes.iter().any(|m| matches!(m, EvalMode::DataMask)) {
return Evaluation::DataMask { select: None };
}
Evaluation::Eager
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Per-parameter evaluation modes are collapsed into one function-level value.

sig.eval is a map from parameter name to EvalMode. convert_evaluation discards every key and returns a single Evaluation for the whole function. The first matching mode in the fixed priority order wins.

For a function where only expr is defused and the remaining parameters are eager, the result is PromiseCapture for the whole call. A consumer that reads is_defusing in crates/ry-analysis/src/effect.rs then treats every argument as defused. That is the opposite of the module goal in catalog.rs lines 7-8.

ParameterSpec has no evaluation field, so the IR cannot express per-parameter modes at all. Add one, or document the collapse and its precedence order explicitly at this function.

The modes vector on line 36 is also unnecessary. Call sig.eval.values().any(...) directly and drop the allocation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-analysis/src/catalog_adapter.rs` around lines 34 - 64, Update
convert_evaluation and the IR around ParameterSpec so sig.eval modes remain
associated with their parameter names instead of collapsing into one
function-level Evaluation; ensure effect.rs consumers can distinguish defused
and eager arguments. If per-parameter representation is intentionally
unsupported, document that limitation and the existing precedence order directly
in convert_evaluation, and replace the allocated modes vector with direct
sig.eval.values().any(...) checks.

Comment on lines +66 to +89
fn convert_return_rule(sig: &FunctionSig) -> ReturnRule {
if sig.no_return {
return ReturnRule::Unknown;
}
match &sig.return_ {
ry_typeshed::ReturnSpec::Slot(_) => ReturnRule::Conditional,
ry_typeshed::ReturnSpec::Concrete(_) => ReturnRule::Fixed("typed".to_string()),
}
}

fn convert_flow_effect(sig: &FunctionSig) -> FlowEffect {
if sig.no_return {
return FlowEffect::NoReturn;
}
if sig.predicate.is_some() {
return FlowEffect::Predicate {
target: crate::catalog::PredicateTarget::FirstArg,
};
}
if sig.assertion.is_some() {
return FlowEffect::Assertion { stop_on_fail: true };
}
FlowEffect::None
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect predicate, assertion, and JsonRType shapes in ry-typeshed.
set -euo pipefail

rg -nP --type=rust -C 12 'struct\s+JsonRType|predicate:|assertion:|struct\s+\w*Predicate|struct\s+\w*Assertion' crates/ry-typeshed/src/lib.rs

Repository: sims1253/ry

Length of output: 7751


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- adapter structure and implementation ---'
ast-grep outline crates/ry-analysis/src/catalog_adapter.rs
sed -n '1,230p' crates/ry-analysis/src/catalog_adapter.rs

printf '%s\n' '--- catalog IR definitions and consumers ---'
rg -n -C 10 'enum ReturnRule|enum FlowEffect|struct FunctionSemantics|PredicateTarget|stop_on_fail|ReturnRule::Fixed|FlowEffect::Predicate|FlowEffect::Assertion' crates/ry-analysis/src

printf '%s\n' '--- source type definitions and construction sites ---'
rg -n -C 12 'pub enum ReturnSpec|Concrete\(|PredicateSpec|AssertionSpec|subject_param|allow_null_param|allow_na_param' crates/ry-typeshed crates/ry-analysis

Repository: sims1253/ry

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from pathlib import Path
from collections import Counter

for path in Path("crates/ry-typeshed/vendor").rglob("*.json"):
    try:
        data = json.loads(path.read_text())
    except Exception:
        continue
    for section in ("functions", "s3_methods"):
        entries = data.get(section, {})
        values = entries.values() if isinstance(entries, dict) else entries
        for entry in values:
            if entry.get("predicate") is not None:
                p = entry["predicate"]
                print(f"{path}: predicate subject={p.get('subject_param')!r} target_mode={p.get('target', {}).get('mode')!r}")
            if entry.get("assertion") is not None:
                a = entry["assertion"]
                print(
                    f"{path}: assertion subject={a.get('subject_param')!r} "
                    f"allow_null={a.get('allow_null_param')!r} allow_na={a.get('allow_na_param')!r} "
                    f"target_mode={a.get('target', {}).get('mode')!r}"
                )
PY

printf '%s\n' '--- all PredicateTarget construction and interpretation ---'
rg -n -C 8 'PredicateTarget::|PredicateTarget|stop_on_fail' crates/ry-analysis --glob '*.rs'

printf '%s\n' '--- relevant catalog documentation ---'
sed -n '53,105p' crates/ry-analysis/src/catalog.rs

Repository: sims1253/ry

Length of output: 7860


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from pathlib import Path
from collections import Counter

counts = Counter()
subjects = Counter()

for path in Path("crates/ry-typeshed/vendor").rglob("*.json"):
    try:
        data = json.loads(path.read_text())
    except Exception:
        continue

    for section in ("functions", "s3_methods"):
        entries = data.get(section, {})
        values = entries.values() if isinstance(entries, dict) else entries
        for sig in values:
            if sig.get("no_return"):
                counts["no_return"] += 1
            if sig.get("predicate") is not None:
                counts["predicate"] += 1
                subjects[("predicate", sig["predicate"]["subject_param"])] += 1
            if sig.get("assertion") is not None:
                counts["assertion"] += 1
                subjects[("assertion", sig["assertion"]["subject_param"])] += 1
            if sig.get("predicate") is not None and sig.get("assertion") is not None:
                counts["predicate_and_assertion"] += 1
            if sig.get("no_return") and (
                sig.get("predicate") is not None or sig.get("assertion") is not None
            ):
                counts["no_return_with_flow_effect"] += 1

print("counts:", dict(counts))
print("subjects:", dict(subjects))
PY

printf '%s\n' '--- return-rule consumers ---'
rg -n -C 12 'ReturnRule::Fixed|match .*return_rule|return_rule' crates/ry-analysis/src --glob '*.rs'

Repository: sims1253/ry

Length of output: 16914


Preserve typeshed semantics in the catalog adapter

  • Map ReturnSpec::Concrete(concrete) to ReturnRule::Fixed(concrete.mode.clone()). The current "typed" value discards the validated return mode, and the test locks in the wrong value.
  • Derive PredicateTarget from predicate.subject_param. FirstArg matches the current vendor data, but the adapter ignores this field.
  • AssertionSpec has no stop flag. Do not claim that stop_on_fail can be mapped from it. Extend the IR or document the intentional loss of subject_param, target, allow_null_param, allow_na_param, and provenance.
  • Document or reject the current precedence: no_return overrides the return and flow effects, and predicate overrides assertion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-analysis/src/catalog_adapter.rs` around lines 66 - 89, The catalog
adapter must preserve typeshed semantics: update convert_return_rule to use
ReturnSpec::Concrete(concrete).mode.clone(), derive PredicateTarget from
sig.predicate.subject_param, and remove the unsupported AssertionSpec
stop_on_fail mapping by extending the IR or explicitly documenting the lost
assertion and predicate fields. Also document or reject the existing precedence
where no_return overrides other effects and predicate overrides assertion.

Comment on lines +91 to +104
fn convert_binding_effect(sig: &FunctionSig) -> Option<BindingEffect> {
if !sig.injects.is_empty() {
let names: Vec<String> = sig
.injects
.iter()
.flat_map(|i| i.names.iter().cloned())
.collect();
if names.is_empty() {
return Some(BindingEffect::Inject(vec![]));
}
return Some(BindingEffect::Inject(names));
}
None
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The empty-names branch is redundant.

If names is empty, Some(BindingEffect::Inject(vec![])) and Some(BindingEffect::Inject(names)) are the same value. Lines 98-100 add a branch with no effect.

♻️ Proposed simplification
 fn convert_binding_effect(sig: &FunctionSig) -> Option<BindingEffect> {
-    if !sig.injects.is_empty() {
-        let names: Vec<String> = sig
-            .injects
-            .iter()
-            .flat_map(|i| i.names.iter().cloned())
-            .collect();
-        if names.is_empty() {
-            return Some(BindingEffect::Inject(vec![]));
-        }
-        return Some(BindingEffect::Inject(names));
-    }
-    None
+    if sig.injects.is_empty() {
+        return None;
+    }
+    let names: Vec<String> = sig
+        .injects
+        .iter()
+        .flat_map(|i| i.names.iter().cloned())
+        .collect();
+    Some(BindingEffect::Inject(names))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn convert_binding_effect(sig: &FunctionSig) -> Option<BindingEffect> {
if !sig.injects.is_empty() {
let names: Vec<String> = sig
.injects
.iter()
.flat_map(|i| i.names.iter().cloned())
.collect();
if names.is_empty() {
return Some(BindingEffect::Inject(vec![]));
}
return Some(BindingEffect::Inject(names));
}
None
}
fn convert_binding_effect(sig: &FunctionSig) -> Option<BindingEffect> {
if sig.injects.is_empty() {
return None;
}
let names: Vec<String> = sig
.injects
.iter()
.flat_map(|i| i.names.iter().cloned())
.collect();
Some(BindingEffect::Inject(names))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-analysis/src/catalog_adapter.rs` around lines 91 - 104, Remove the
redundant names.is_empty() conditional in convert_binding_effect and return
Some(BindingEffect::Inject(names)) directly after collecting the names,
preserving the existing None behavior when sig.injects is empty.

Comment on lines +86 to +100
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReturnRule {
/// Always returns the given type.
Fixed(String),
/// Returns the type of the Nth argument (1-indexed).
NthArg(usize),
/// Returns the first non-null argument.
FirstNonNull,
/// Higher-order: maps over the Nth argument.
HigherOrder { map_over: usize },
/// Conditional return based on arguments.
Conditional,
/// Cannot determine statically.
Unknown,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the index base of HigherOrder::map_over, and consider a non-zero type.

NthArg documents "(1-indexed)". HigherOrder { map_over: usize } carries the same kind of argument position but documents no index base. A consumer that reads return_rule through crates/ry-analysis/src/effect.rs has no way to know which convention applies, which invites an off-by-one.

Lines 7-8 also state the design goal: "making impossible states unrepresentable". A 1-indexed position typed as usize still permits 0, which is not a valid argument position. std::num::NonZeroUsize encodes that constraint.

crates/ry-analysis/src/catalog_adapter.rs never constructs either variant today, so no current caller is affected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-analysis/src/catalog.rs` around lines 86 - 100, Update
ReturnRule::HigherOrder so map_over uses NonZeroUsize and explicitly documents
that the argument position is 1-indexed, matching NthArg. Import the type as
needed and adjust any affected pattern matches or consumers, while preserving
the existing return-rule behavior.

Comment on lines +3032 to +3054
fn extract_prefix(text: &str, position: tower_lsp::lsp_types::Position) -> Option<String> {
let line = text.lines().nth(position.line as usize)?;
let byte_col = line
.char_indices()
.take(position.character as usize)
.last()
.map(|(i, _)| i)
.unwrap_or(0);
let before = &line[..byte_col];
let prefix: String = before
.chars()
.rev()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.')
.collect::<String>()
.chars()
.rev()
.collect();
if prefix.is_empty() {
None
} else {
Some(prefix)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

extract_prefix drops the last character and uses the wrong position unit.

Two defects:

  1. Off-by-one. line.char_indices().take(position.character as usize).last().map(|(i, _)| i) yields the byte index of the last included character, not the index after it. For the line unique_ with character = 7, byte_col is 6, so before is "unique" and the prefix loses the trailing _. Worse, after typing a single character character is 1, byte_col is 0, before is empty, and the function returns None. Completion then applies no prefix filter and offers every project function in the workspace.
  2. Wrong unit. position.character is a UTF-16 code unit count, as advertised at line 751. This code counts char values. Any line containing an astral character before the cursor produces a wrong offset.

Reuse the existing conversion used everywhere else in this file, position_to_byte_offset_pos, which already handles UTF-16 and returns None for invalid positions.

🐛 Proposed fix
 fn extract_prefix(text: &str, position: tower_lsp::lsp_types::Position) -> Option<String> {
-    let line = text.lines().nth(position.line as usize)?;
-    let byte_col = line
-        .char_indices()
-        .take(position.character as usize)
-        .last()
-        .map(|(i, _)| i)
-        .unwrap_or(0);
-    let before = &line[..byte_col];
+    let offset = position_to_byte_offset_pos(text, position)?;
+    let before = &text[..offset];
     let prefix: String = before
         .chars()
         .rev()
         .take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.')
         .collect::<String>()
         .chars()
         .rev()
         .collect();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn extract_prefix(text: &str, position: tower_lsp::lsp_types::Position) -> Option<String> {
let line = text.lines().nth(position.line as usize)?;
let byte_col = line
.char_indices()
.take(position.character as usize)
.last()
.map(|(i, _)| i)
.unwrap_or(0);
let before = &line[..byte_col];
let prefix: String = before
.chars()
.rev()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.')
.collect::<String>()
.chars()
.rev()
.collect();
if prefix.is_empty() {
None
} else {
Some(prefix)
}
}
fn extract_prefix(text: &str, position: tower_lsp::lsp_types::Position) -> Option<String> {
let offset = position_to_byte_offset_pos(text, position)?;
let before = &text[..offset];
let prefix: String = before
.chars()
.rev()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.')
.collect::<String>()
.chars()
.rev()
.collect();
if prefix.is_empty() {
None
} else {
Some(prefix)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-lsp/src/backend.rs` around lines 3032 - 3054, Update extract_prefix
to use the existing position_to_byte_offset_pos conversion for the cursor
position, preserving UTF-16 handling and invalid-position behavior. Use the
returned byte offset directly as the slice boundary so the character at the
cursor is excluded while all preceding characters, including a trailing
underscore, remain in the extracted prefix.

Comment thread crates/ry-lsp/src/lib.rs
// tests can assert that the publish/hover/completion hot path performs no
// baseline file I/O.
pub use backend::baseline_disk_reads;
pub use backend::{COMPILE_DURING_LAST_PUBLISH, FILTER_COMPILE_COUNT};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Export accessor functions instead of mutable statics.

This re-export makes two AtomicU64 statics part of the public API of ry-lsp. Any consumer can call store on them and change the values the test gate reads. The existing instrumentation in this crate uses an accessor for exactly this reason: baseline_disk_reads() (crates/ry-lsp/src/backend.rs lines 46-48) returns the value and keeps the static private.

Add read-only accessors and export those. This also composes with the per-server scoping requested on crates/ry-lsp/src/backend.rs lines 200-211.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-lsp/src/lib.rs` at line 154, Replace the public re-export of
COMPILE_DURING_LAST_PUBLISH and FILTER_COMPILE_COUNT with read-only accessor
functions in the backend module, following the existing baseline_disk_reads()
pattern. Re-export the accessors from lib.rs and keep the AtomicU64 statics
private so consumers can read values without mutating the test-gate state;
ensure the accessors support the requested per-server scoping.

Comment on lines +32 to +44
async fn spawn_session(root: &Path) -> (Session, tokio::task::JoinHandle<()>) {
let (client_stream, server_stream) = tokio::io::duplex(128 * 1024);
let (client_reader, client_writer) = tokio::io::split(client_stream);
let (server_reader, server_writer) = tokio::io::split(server_stream);
let server = tokio::spawn(async move {
let _ = ry_lsp::run_with(server_reader, server_writer).await;
});
let mut session = LspSession::new(client_reader, client_writer);
session.initialize(root).await.unwrap();
// Wait for background indexing to populate disk_files.
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
(session, server)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace the fixed indexing sleep with a bounded poll.

spawn_session sleeps one second unconditionally to wait for spawn_background_index to populate disk_files. Six tests call it, so the suite pays six seconds even when indexing finishes immediately. More importantly the sleep is an assumption, not a synchronization point: on a loaded CI runner indexing can take longer, and every test in this file then fails for a reason unrelated to the feature under test.

Poll the observable condition instead. Retry the request under a deadline until it returns a non-null result, or wait for the first textDocument/publishDiagnostics for an indexed file, which the server sends after the index completes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-lsp/tests/p38_feature_diff.rs` around lines 32 - 44, Replace the
unconditional one-second sleep in spawn_session with bounded polling of the
indexing completion signal. Repeatedly issue the relevant observable request, or
await the first textDocument/publishDiagnostics notification for an indexed
file, until it returns a non-null result; enforce a deadline and fail clearly if
indexing does not complete in time.

Comment on lines +176 to +191
let locations = match &def {
Value::Array(arr) => arr.clone(),
Value::Object(obj) => vec![Value::Object(obj.clone())],
_ => Vec::new(),
};

let target_uris: Vec<&str> = locations
.iter()
.filter_map(|l| l.get("uri").and_then(|u| u.as_str()))
.collect();

assert!(
target_uris.iter().all(|uri| *uri != a_uri),
"definition should resolve to the local b.R helper, not a.R with the same spelling"
);
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

This assertion passes when no definition is returned.

locations is empty when textDocument/definition returns null, and Iterator::all on an empty iterator is true. So the test passes both when the definition correctly resolves to R/b.R and when the server returns no definition at all. It cannot detect a regression that breaks go-to-definition entirely.

Assert that at least one location exists and that it points at b_uri.

💚 Proposed fix
+        assert!(
+            !target_uris.is_empty(),
+            "definition must resolve to at least one location"
+        );
         assert!(
             target_uris.iter().all(|uri| *uri != a_uri),
             "definition should resolve to the local b.R helper, not a.R with the same spelling"
         );
+        assert!(
+            target_uris.iter().any(|uri| *uri == b_uri),
+            "definition should point at the local b.R helper"
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let locations = match &def {
Value::Array(arr) => arr.clone(),
Value::Object(obj) => vec![Value::Object(obj.clone())],
_ => Vec::new(),
};
let target_uris: Vec<&str> = locations
.iter()
.filter_map(|l| l.get("uri").and_then(|u| u.as_str()))
.collect();
assert!(
target_uris.iter().all(|uri| *uri != a_uri),
"definition should resolve to the local b.R helper, not a.R with the same spelling"
);
})
let locations = match &def {
Value::Array(arr) => arr.clone(),
Value::Object(obj) => vec![Value::Object(obj.clone())],
_ => Vec::new(),
};
let target_uris: Vec<&str> = locations
.iter()
.filter_map(|l| l.get("uri").and_then(|u| u.as_str()))
.collect();
assert!(
!target_uris.is_empty(),
"definition must resolve to at least one location"
);
assert!(
target_uris.iter().all(|uri| *uri != a_uri),
"definition should resolve to the local b.R helper, not a.R with the same spelling"
);
assert!(
target_uris.iter().any(|uri| *uri == b_uri),
"definition should point at the local b.R helper"
);
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-lsp/tests/p38_feature_diff.rs` around lines 176 - 191, Update the
definition-result assertions around locations and target_uris to require at
least one returned location, then verify that a returned URI equals b_uri.
Preserve the existing exclusion of a_uri while ensuring the test fails when
textDocument/definition returns null or an empty array.

Comment on lines +580 to +593
let mut skipped_count: u32 = 0;
for (step, operation) in operations.into_iter().enumerate() {
// P37-W7c: Track operations skipped due to invalid state.
// After precomputation, the generator should only produce
// valid operations, so this should remain zero.
let operation = if !model.is_valid(&operation) {
skipped_count += 1;
match model.valid_alternative(&operation) {
Some(valid) => valid,
None => continue,
}
} else {
operation
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The counter name and the report disagree with what is counted.

skipped_count increments once for every invalid operation, whether valid_alternative returns a replacement (line 588) or the operation is dropped (line 589). The report at line 950 attributes all of them to corrections. So a run in which every invalid operation was dropped reports them as corrected.

Track the two outcomes separately and report both. Also note the string literal at line 950 contains a run of spaces from the line break, which reaches the output as valid alternatives.

♻️ Proposed refactor
-    let mut skipped_count: u32 = 0;
+    let mut corrected_count: u32 = 0;
+    let mut dropped_count: u32 = 0;
     for (step, operation) in operations.into_iter().enumerate() {
         let operation = if !model.is_valid(&operation) {
-            skipped_count += 1;
             match model.valid_alternative(&operation) {
-                Some(valid) => valid,
-                None => continue,
+                Some(valid) => {
+                    corrected_count += 1;
+                    valid
+                }
+                None => {
+                    dropped_count += 1;
+                    continue;
+                }
             }
         } else {
             operation
         };
-    if skipped_count > 0 {
-        eprintln!(
-            "P37-W7c: {skipped_count} operations were corrected to valid              alternatives (coverage could be improved with state-aware generation)"
-        );
-    }
+    if corrected_count > 0 || dropped_count > 0 {
+        eprintln!(
+            "P37-W7c: {corrected_count} operations corrected, {dropped_count} dropped; \
+             coverage could be improved with state-aware generation"
+        );
+    }

Also applies to: 947-952

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ry-lsp/tests/w8_session.rs` around lines 580 - 593, Separate the
invalid-operation accounting in the operations loop from the `skipped_count`
variable into distinct counts for replacements and dropped operations: increment
the replacement count only when `valid_alternative` returns `Some`, and
increment the dropped count before `continue` for `None`. Update the report
around the existing reporting logic near `valid_alternative` to include both
outcomes with accurate labels, and remove the unintended whitespace from the
multiline string so it does not output padding between words.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants