diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml
new file mode 100644
index 0000000..f745133
--- /dev/null
+++ b/.github/workflows/sonar.yml
@@ -0,0 +1,64 @@
+name: Sonar
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+permissions:
+ contents: read
+ pull-requests: read
+
+concurrency:
+ group: sonar-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+ sonar:
+ runs-on: ubuntu-latest
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ with:
+ # Full history so SonarQube Cloud can attribute new code / blame.
+ fetch-depth: 0
+
+ # --- Rust coverage (coverage/rust-lcov.info) ---
+ # rust-toolchain.toml uses `profile = "minimal"`, so the llvm-tools-preview
+ # component (required by cargo-llvm-cov) is not present by default.
+ - name: Add llvm-tools-preview component
+ run: rustup component add llvm-tools-preview
+
+ - name: Install cargo-llvm-cov
+ uses: taiki-e/install-action@9e1e5806d4a4822de933115878265be9aaa786d9 # v2
+ with:
+ tool: cargo-llvm-cov
+
+ - name: Test with coverage (Rust)
+ # #[ignore] (network-gated) tests are excluded by default, keeping this
+ # offline (see ADR-0004). --all-features mirrors the Rust CI gate.
+ run: |
+ mkdir -p coverage
+ cargo llvm-cov --workspace --locked --all-features --lcov --output-path coverage/rust-lcov.info
+
+ - name: Upload Rust coverage to Codecov
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
+ with:
+ files: ./coverage/rust-lcov.info
+ flags: rust
+ fail_ci_if_error: false
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+
+ # --- SonarQube Cloud analysis ---
+ # On pull_request runs from forks the SONAR_TOKEN secret isn't exposed, so
+ # skip the scan there instead of blocking external PRs. On push (main) the
+ # scan always runs — a missing token then fails loudly so a misconfigured
+ # Sonar setup is surfaced rather than silently skipped. (env is checked
+ # because the `secrets` context isn't available in step `if:` conditions.)
+ - name: SonarQube Cloud scan
+ if: ${{ github.event_name != 'pull_request' || env.SONAR_TOKEN != '' }}
+ uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0
diff --git a/README.ko.md b/README.ko.md
index 0d9c747..b8e526c 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -9,6 +9,7 @@
+
diff --git a/README.md b/README.md
index 2076871..45072dd 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
+
diff --git a/codecov.yml b/codecov.yml
index ec0e8f5..3586f8a 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -16,8 +16,26 @@ coverage:
informational: true
comment:
- layout: 'reach, diff, flags, files'
+ layout: 'reach, diff, flags, components, files'
require_changes: false
-# Coverage is generated by `bun test --coverage` (lcov). Test files are already
-# excluded from the lcov report, so only product source is measured.
+# Upload-time flag. sonar.yml uploads Rust coverage with `flags: rust`.
+# carryforward keeps the last known coverage when the workflow doesn't run on a
+# given commit. (The TypeScript implementation under src/ has been removed.)
+flags:
+ rust:
+ paths:
+ - crates/
+ carryforward: true
+
+# Component grouping by path at display time (independent of upload flags),
+# surfacing the Rust crate coverage as a named component in the Codecov UI.
+component_management:
+ individual_components:
+ - component_id: rust
+ name: Rust
+ paths:
+ - crates/**
+
+# Coverage is generated by `cargo llvm-cov` (Rust, lcov). Test code is excluded
+# from the lcov report, so only product source is measured.
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000..084719d
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,18 @@
+# SonarQube Cloud (sonarcloud.io) configuration.
+#
+# Analysis runs from CI (.github/workflows/sonar.yml). For this to work the
+# SonarCloud project must have Automatic Analysis DISABLED and a SONAR_TOKEN
+# secret configured in the repository (or org) settings.
+sonar.projectKey=pleaseai_code-search
+sonar.organization=pleaseai
+
+# The implementation is the Rust workspace under crates/ (the deprecated TS port
+# that lived in src/ has been removed).
+sonar.sources=crates
+
+# Keep the npm launcher, build output, and dependency/target dirs out of analysis
+# (npm/** mirrors the .codacy.yaml exclusion).
+sonar.exclusions=npm/**,dist/**,target/**,node_modules/**
+
+# Coverage report (LCOV), generated by `cargo llvm-cov` in CI before the scan step.
+sonar.rust.lcov.reportPaths=coverage/rust-lcov.info