From fa516589da941c31a152d4a4e65dfd9684464e0c Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Sun, 16 Aug 2026 00:35:24 -0700 Subject: [PATCH 1/4] docs: add feature-parity contract for other languages Document the v1 bar (module graph, tests plan, and named route/queue extractors) for Python/Django/Celery, Go/Asynq, Kafka, Rust, Rails, and PHP relative to the TS/JS surface. --- README.md | 1 + crates/no-mistakes/tests/docs_coverage.rs | 25 ++ cspell.config.yaml | 2 + docs/README.md | 4 + docs/feature-parity.md | 344 ++++++++++++++++++++++ 5 files changed, 376 insertions(+) create mode 100644 docs/feature-parity.md diff --git a/README.md b/README.md index a39d36dfe..936a71354 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ cargo run -p no-mistakes -- dependents src/utils.mts --format paths - [Node/N-API guide](docs/node-api.md) - [Configuration](docs/configuration/README.md) - [Graph edge types](docs/graph-edges.md) +- [Feature parity](docs/feature-parity.md) - [no-mistakes rules](docs/rules/README.md) - [ESLint rules](docs/eslint-rules/README.md) - [Agent guide](docs/agent-guide.md) diff --git a/crates/no-mistakes/tests/docs_coverage.rs b/crates/no-mistakes/tests/docs_coverage.rs index 2e9ff4d19..84450c2ac 100644 --- a/crates/no-mistakes/tests/docs_coverage.rs +++ b/crates/no-mistakes/tests/docs_coverage.rs @@ -419,4 +419,29 @@ fn review_found_doc_regressions_stay_fixed() { assert!(limits.contains("spawn(scriptName, [])")); assert!(limits.contains(r#"page.locator('[data-testid="submit"]').click()"#)); assert!(limits.contains(r#"page.locator(`[data-testid="${id}"]`).click()"#)); + + let feature_parity = read_root("docs/feature-parity.md"); + for needle in [ + "Python", + "Django", + "Celery", + "Go", + "Asynq", + "Kafka", + "Rust", + "Ruby on Rails", + "PHP", + "Counterexample:", + "not started", + ] { + assert!( + feature_parity.contains(needle), + "docs/feature-parity.md must document `{needle}`" + ); + } + let docs_index = read_root("docs/README.md"); + assert!( + docs_index.contains("(feature-parity.md)"), + "docs/README.md must link feature-parity.md" + ); } diff --git a/cspell.config.yaml b/cspell.config.yaml index 8f713acd4..704a8b96a 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -13,6 +13,7 @@ ignorePaths: - "crates/**/target/**" words: - arm64 + - asynq - Basenames - callees - callsite @@ -64,3 +65,4 @@ words: - vitest - walkdir - worktree + - zeitwerk diff --git a/docs/README.md b/docs/README.md index 57f1a356f..573c807c1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,6 +16,7 @@ request structured output, understand config, and keep source code analyzable. | Keep file-local code analyzable | [ESLint rules](eslint-rules/README.md) | | Understand static-analysis limits | [AST analysis behavior](ast-analysis.md) | | Use the tool as an AI agent | [Agent guide](agent-guide.md) | +| Compare language and framework coverage | [Feature parity](feature-parity.md) | ## Reference @@ -25,6 +26,9 @@ request structured output, understand config, and keep source code analyzable. would bypass the one-pass gateways. - [Graph edges](graph-edges.md) lists dependency edge kinds with fixture-backed examples, counterexamples, relationship filters, and caveats. +- [Feature parity](feature-parity.md) is the contract for Python, Django, + Celery, Go, Asynq, Kafka, Rust, Rails, and PHP support relative to the TS/JS + surface. - [Test planning](test-plan.md) explains configured test selection in more depth. - [Legacy CLI reference](cli-reference.md) and [legacy ESLint reference](eslint-plugin.md) diff --git a/docs/feature-parity.md b/docs/feature-parity.md new file mode 100644 index 000000000..a612b6257 --- /dev/null +++ b/docs/feature-parity.md @@ -0,0 +1,344 @@ +# Feature Parity + +`no-mistakes` is a local, deterministic codebase-intelligence engine. TypeScript +and JavaScript are the reference implementation for the full product surface. +This page is the contract for bringing other languages and their key frameworks +up to that surface. + +A language or framework is supported when an agent can ask the same structural +questions it already asks of TS/JS, get deterministic structured output, and +do so without shelling out to `rg` for the graph itself. + +v1 is the Swift/.NET bar plus the named key feature for each stack: a module +graph, `tests plan`, and either HTTP routes or queues. Playwright, React, +Next.js fetches, call-sites, dead-exports, ecosystem lockfile diffs, and +dedicated `no-mistakes python|go|rust|rails|php` CLIs are later work. Agents +use `dependents --relationship ` and `tests plan ` once those +edges and planners ship. + +## Current Status + +| Domain | Module graph | Test plan | HTTP routes | Queues | Status | +| --- | --- | --- | --- | --- | --- | +| TypeScript / JavaScript | yes | Vitest, Playwright | Express, Hono, Koa, Next.js | BullMQ, glide-mq | shipped | +| Swift | `swift-import`, `swift-ref`, `swift-package` | `tests plan swift` | Swift `Endpoint` literals via `http` | no | shipped, narrower | +| .NET / C# | `dotnet-using`, `dotnet-ref`, `dotnet-project` | `tests plan dotnet` | no | no | shipped, narrower | +| Rust | no | `--test cargo` globs only | no | no | partial: project type, check rules, CI Cargo edges | +| Python, Django, Celery | no | no | no | no | not started | +| Go, Asynq | no | no | no | no | not started | +| Kafka | n/a | n/a | n/a | no | not started | +| Ruby on Rails | no | no | no | no | not started | +| PHP | no | no | no | no | not started | + +CI workflows and Terraform/OpenTofu are adjacent graph domains, not language +frontends. They stay available to every language once files are tracked. + +Rust today is not a language frontend. `projects.*.type: rust` exists, +[`rust-max-lines-per-file`](rules/rust-max-lines-per-file.md), +[`rust-no-inline-allows`](rules/rust-no-inline-allows.md), and +[`rust-no-inline-tests`](rules/rust-no-inline-tests.md) run as filesystem +checks, `--test cargo` filters `**/tests/**/*.rs` and `src/**/*_test.rs`, and +`ci` edges connect GitHub Actions workflows to Rust binaries invoked by +supported Cargo commands. There is no `use`/`mod` graph, no `tests plan cargo`, +and no Rust CLI. + +## Canonical Feature Set + +These are the features a new language must cover, or explicitly decline with a +documented limit. The TS/JS command is the reference behavior. + +**Module graph.** `dependencies`, `dependents`, `related`, and `importers` +over typed edges in the canonical `DepGraph`. TS/JS uses `import`, +`type-import`, `dynamic-import`, `require`, `workspace`, and `package`. Swift +and .NET already show the non-TS pattern: language-specific edge kinds behind +`--relationship swift` or `--relationship dotnet`. + +**Symbols.** Named declarations and who references them. TS/JS has `symbols`, +`exports-of`, `dead-exports`, and `call-sites`. Swift and .NET collect +declarations and references into facts and project them as `*-ref` edges. + +**Test impact.** `tests plan`, `tests why`, `tests impact`, and +`impacted-checks` must select native tests from changed files once the language +graph exists. Full-suite fallback remains explicit opt-in. + +**HTTP routes.** `server routes`, `server edges`, `server related`, and +`server contracts` list configured route definitions and static client calls. +Do not invent a second route graph. + +**Queues.** `queues edges`, `queues related`, and `queues check` connect +producers to virtual job nodes to workers. Celery, Asynq, and Kafka extend this +domain. They do not get private graph shapes. + +**HTTP clients.** Static client calls produce `http` edges to matching route +files, the same way TS `fetch` and Swift `Endpoint` literals do. + +**Lockfiles.** `lockfile diff` today parses npm-family lockfiles. Language +support adds the ecosystem lockfile when agents need package-change impact: +`poetry.lock` / `uv.lock`, `go.sum`, `Cargo.lock`, `Gemfile.lock`, +`composer.lock`. + +**Checks, CLI, and N-API.** Every stable CLI capability needs an async N-API +equivalent, fixture-backed tests, and docs. Language-specific check rules +belong in `no-mistakes check` when they are repository-wide, not file-local. + +TS/JS-only product surfaces stay TS/JS-only: Playwright coverage, React +traits, Next.js fetches, RSC callers, and ESLint/Oxlint rules. A Django or +Rails app does not need React analysis to be considered at parity. + +## How A Language Lands + +Follow the Swift and .NET adapter shape, not a second analysis session. + +1. Explicit config names the packages, modules, or apps to analyze. No + repository-wide inference. +2. One request-scoped session still owns inventory and `SourceStore`. The + language collector borrows those inputs. +3. A fact pass extracts imports, declarations, references, and domain + occurrences (routes, enqueue sites, workers) from each matching file. + Heuristic extractors that strip comments/strings first, matching + `codebase/swift` and `codebase/dotnet`, are the expected first + implementation. A full grammar is allowed later only if it stays in-process, + deterministic, and one-pass. +4. Relationship analyzers emit typed edges into `DepGraph`. Commands and + reports project those edges; they do not rebuild a private index. +5. Test discovery reads the language's explicit package/project config and + emits `TestExecutionTarget` rows (`pytest`, `go test`, `cargo test`, + `bin/rails test` / `rspec`, `phpunit` / `artisan test`). +6. Ship CLI, N-API, `docs/cli/*`, `docs/graph-edges.md`, + `docs/configuration/tests.md`, and fixtures in the same change. + +Counterexample: a `no-mistakes python` command that walks the tree, parses +files again, and builds a standalone import graph. That violates prepared +analysis ownership. + +## Python, Django, Celery + +Python support is the language frontend. Django and Celery are configured +domain extractors on top of it. + +| Feature | TS/JS reference | Python equivalent | +| --- | --- | --- | +| Module graph | `import` / `require` | `import`, `from … import`, relative `.` / `..` imports | +| Package identity | `package.json` workspaces | configured package roots; `pyproject.toml` / `setup.cfg` names | +| Symbols | exports and importers | module-level `def` / `class` and qualified references | +| Tests | `tests plan vitest` | `tests plan python` over pytest / unittest files | +| HTTP routes | Express / Hono / Koa | Django URLconf → view, plus Flask / FastAPI if configured | +| Queues | BullMQ / glide-mq | Celery `@shared_task` / `@app.task`, `.delay(` / `.apply_async(` | +| Lockfile | pnpm / npm / yarn / bun | `poetry.lock`, `uv.lock`, `Pipfile.lock` | + +Configure package roots the way Swift configures `tests.swift.packages`. Route +and queue paths stay under `projects.*.routes` and `projects.*.queues`. Do not +hardcode `urls.py`, `tasks.py`, or `settings.py` locations. + +Static forms produce edges: + +```python +from app.users import views +from .models import User + +@shared_task(name="mail.send_welcome") +def send_welcome(user_id: int) -> None: ... + +send_welcome.delay(user_id) +``` + +```python +urlpatterns = [ + path("api/users/", views.user_list), +] +``` + +Dynamic forms do not: + +```python +importlib.import_module(module_name) +celery_app.send_task(task_name, args=args) +path(prefix + "/users/", views.user_list) +``` + +Django settings, middleware, and model graphs are out of scope for the first +cut unless they are needed to resolve a static import or route. + +## Go, Asynq + +Go support is the language frontend. Asynq is the queue extractor. + +| Feature | TS/JS reference | Go equivalent | +| --- | --- | --- | +| Module graph | file imports | `import` of local packages from configured `go.mod` modules | +| Package identity | workspace packages | `go.mod` module path plus configured package directories | +| Symbols | named exports | exported (`Uppercase`) funcs/types and references | +| Tests | `tests plan vitest` | `tests plan go` → `go test` in owning packages | +| HTTP routes | `server routes` | configured `net/http`, Chi, Gin, Echo, or Fiber registrations | +| Queues | BullMQ job name | Asynq `NewTask("mail:welcome", …)` / `HandleFunc("mail:welcome", …)` | +| Lockfile | npm-family | `go.sum` | + +Asynq task type strings are the virtual job identity, same as a BullMQ job +name. A producer file gets `queue-enqueue`; the handler file gets +`queue-worker`. + +```go +client.Enqueue(asynq.NewTask("mail:welcome", payload), asynq.Queue("default")) +mux.HandleFunc("mail:welcome", HandleWelcome) +``` + +Computed task types and `http.Handle(pattern, …)` where `pattern` is not a +string literal produce no edge. `go/ast` in-process is acceptable; invoking the +`go` tool for analysis is not. + +## Kafka + +Kafka is a queue backend, not a language. Producers and consumers in any +supported language emit the existing `queue-enqueue` and `queue-worker` edges. +The virtual node identity is the static topic (and optional static group id), +not a language-specific edge kind. + +```ts +producer.send({ topic: "mail.welcome", messages }); +consumer.subscribe({ topic: "mail.welcome" }); +``` + +```python +producer.send("mail.welcome", value=payload) +consumer.subscribe(["mail.welcome"]) +``` + +Topic names built at runtime, regex subscriptions, and broker admin APIs are +out of scope. Configure producer and consumer file globs through +`projects.*.queues` the same way BullMQ enqueue/worker globs are configured. + +## Rust + +Rust should become a language frontend at Swift/.NET depth, then pick up +routes and queues from the shared domains. + +| Feature | TS/JS reference | Rust equivalent | +| --- | --- | --- | +| Module graph | `import` | `mod`, `use crate::…`, `use super::…`, path attrs | +| Package identity | workspace packages | configured `Cargo.toml` packages and path deps | +| Symbols | named exports | `pub fn` / `pub struct` / `pub enum` and `use` paths | +| Tests | `tests plan vitest` | `tests plan cargo` → `cargo test -p --test ` | +| HTTP routes | `server routes` | configured Axum, Actix, or Rocket registrations | +| Queues | BullMQ | configured enqueue/worker globs; Kafka when present | +| Lockfile | npm-family | `Cargo.lock` | +| Checks | `unique-exports` | keep the existing `rust-*` filesystem rules | + +`ci` remains the narrow workflow-file → Rust-binary Cargo edge. Do not overload +it with `use`/`mod` edges. `workflow` already resolves supported Cargo +`run:` lines. + +Inline `#[cfg(test)]` modules are banned by `rust-no-inline-tests` in this +repo; discovery should prefer `tests/**/*.rs` and sibling `tests.rs` files. + +## Ruby on Rails + +Rails support is Ruby module facts plus configured route and Active Job +extractors. + +| Feature | TS/JS reference | Rails equivalent | +| --- | --- | --- | +| Module graph | `import` | `require`, `require_relative`, Zeitwerk-constant references inside configured app roots | +| Package identity | workspace packages | configured engine/app roots and `Gemfile` path gems | +| Tests | `tests plan vitest` | `tests plan rails` over Minitest / RSpec files | +| HTTP routes | `server routes` | configured `config/routes.rb` (and engine routes) → controller#action | +| Queues | BullMQ | Active Job `SomeJob.perform_later` / `perform_async` → job class | +| Lockfile | npm-family | `Gemfile.lock` | + +Zeitwerk inference is heuristic and must stay inside configured roots. Do not +scan the whole repository for `app/models`. Dynamic `constantize`, +`send(:"#{name}_path")`, and `perform_later` on a computed job class produce +no edge. + +```ruby +get "/api/users", to: "users#index" +WelcomeJob.perform_later(user) +``` + +## PHP + +PHP support is Composer/PSR-4 facts plus configured framework extractors +(Laravel or Symfony, not both inferred). + +| Feature | TS/JS reference | PHP equivalent | +| --- | --- | --- | +| Module graph | `import` | `use`, `require`/`include` of local files, PSR-4 from configured `composer.json` | +| Package identity | workspace packages | configured Composer packages / path repositories | +| Tests | `tests plan vitest` | `tests plan php` over PHPUnit / Pest files | +| HTTP routes | `server routes` | configured Laravel `Route::` or Symfony attribute/YAML routes | +| Queues | BullMQ | Laravel `SomeJob::dispatch()` / `ShouldQueue`, or Symfony Messenger handlers | +| Lockfile | npm-family | `composer.lock` | + +```php +Route::get('/api/users', [UserController::class, 'index']); +SomeJob::dispatch($user); +``` + +`__DIR__ . '/' . $name` and `app($abstract)` lookups are non-edges. + +## Shared Domain Rules + +Route, queue, and Kafka extractors are language-specific visitors that emit +the same public edge kinds (`server-route`, `client-call`, `http`, +`queue-enqueue`, `queue-worker`). A Celery producer in Python and an Asynq +producer in Go must be traversable with `--relationship queue`. + +Relationship filters for the language graph itself follow Swift/.NET: + +- `python` — Python import and reference edges +- `go` — Go import and reference edges +- `rust` — Rust `use`/`mod` and path-dep edges +- `ruby` — Ruby require and configured constant edges +- `php` — PHP `use` / Composer edges + +Additive language flags must not change existing TS/JS report fields. When a +broader resolver catalog is needed (for example tests that live outside the +app package), add a fixture-backed parity test that compares baseline fields +with the flag off and on. + +## Configuration Shape + +New knobs are explicit and scoped. The names below are the intended contract; +they do not exist until the corresponding implementation ships. + +```yaml +projects: + api: + type: server + root: backend + routes: ["config/urls.py", "routes/**/*.py"] + queues: + enqueues: ["app/**/tasks.py"] + workers: ["app/**/tasks.py", "workers/**/*.py"] +tests: + python: + packages: + - backend + go: + modules: + - services/worker + rust: + packages: + - crates/api + rails: + apps: + - apps/web + php: + apps: + - services/api +``` + +Counterexample: defaulting to “every `urls.py`, every `go.mod`, every Rails +`app/`” when the lists are omitted. Full-suite and repository-wide scans stay +opt-in. + +## Agent Fallback + +Until a row in the status table is `shipped`, agents should keep using `rg` +for that language. The shipped `no-mistakes` skill already says Go and Rust +sources have no import-graph domain. That remains correct until the graph +edges and test planner land. + +See [Architecture](architecture.md) for the one-pass session rules, +[Graph edges](graph-edges.md) for the current edge kinds, and +[Tests and selectors](configuration/tests.md) for how Swift and .NET +already require explicit package/project lists. From 6c04b15cc565394fb49d4965006c3fde8db76e8c Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Sun, 16 Aug 2026 00:39:27 -0700 Subject: [PATCH 2/4] chore: allow feature-parity domain words in cspell CI lint:spell rejected framework names and identifiers used in the new language-parity contract page. --- cspell.config.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cspell.config.yaml b/cspell.config.yaml index 704a8b96a..13ccf3aa7 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -12,12 +12,14 @@ ignorePaths: - "test-cases/**" - "crates/**/target/**" words: + - Actix - arm64 - asynq - Basenames - callees - callsite - callsites + - constantize - cooldown - dedup - dtolnay @@ -27,6 +29,8 @@ words: - eprintln - errexit - extglob + - frontends + - funcs - gitignore - glibc - globset @@ -34,6 +38,7 @@ words: - guardrailsrc - invalidators - jsonc + - Laravel - linecount - libuv - lockfiles @@ -45,8 +50,12 @@ words: - Ong - oxlint - PCRE + - phpunit - pipefail + - Pipfile - pnpm + - pytest + - pyproject - refspec - refspecs - redirections @@ -55,11 +64,13 @@ words: - sgconfig - shellcheck - shasum + - Symfony - subdirs - subpaths - tokei - tsconfig - ungated + - urlpatterns - unmodeled - valkey - vitest From 6b05e62f0ec6782b7c72ffd54ec396972fb2b390 Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Sun, 16 Aug 2026 00:43:34 -0700 Subject: [PATCH 3/4] docs: address feature-parity review comments Align lockfile, Kafka identity, Cargo test targets, Rails queues, PHP framework selection, Swift route status, and importers scope with the review threads. Guard the root README link in coverage. --- crates/no-mistakes/tests/docs_coverage.rs | 6 +++ cspell.config.yaml | 1 + docs/feature-parity.md | 45 ++++++++++++++--------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/crates/no-mistakes/tests/docs_coverage.rs b/crates/no-mistakes/tests/docs_coverage.rs index 84450c2ac..d88163874 100644 --- a/crates/no-mistakes/tests/docs_coverage.rs +++ b/crates/no-mistakes/tests/docs_coverage.rs @@ -444,4 +444,10 @@ fn review_found_doc_regressions_stay_fixed() { docs_index.contains("(feature-parity.md)"), "docs/README.md must link feature-parity.md" ); + + let root_readme = read_root("README.md"); + assert!( + root_readme.contains("(docs/feature-parity.md)"), + "README.md must link docs/feature-parity.md" + ); } diff --git a/cspell.config.yaml b/cspell.config.yaml index 13ccf3aa7..9933d768f 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -64,6 +64,7 @@ words: - sgconfig - shellcheck - shasum + - Sidekiq - Symfony - subdirs - subpaths diff --git a/docs/feature-parity.md b/docs/feature-parity.md index a612b6257..4867067d1 100644 --- a/docs/feature-parity.md +++ b/docs/feature-parity.md @@ -21,7 +21,7 @@ edges and planners ship. | Domain | Module graph | Test plan | HTTP routes | Queues | Status | | --- | --- | --- | --- | --- | --- | | TypeScript / JavaScript | yes | Vitest, Playwright | Express, Hono, Koa, Next.js | BullMQ, glide-mq | shipped | -| Swift | `swift-import`, `swift-ref`, `swift-package` | `tests plan swift` | Swift `Endpoint` literals via `http` | no | shipped, narrower | +| Swift | `swift-import`, `swift-ref`, `swift-package` | `tests plan swift` | no (client `http` edges only) | no | shipped, narrower | | .NET / C# | `dotnet-using`, `dotnet-ref`, `dotnet-project` | `tests plan dotnet` | no | no | shipped, narrower | | Rust | no | `--test cargo` globs only | no | no | partial: project type, check rules, CI Cargo edges | | Python, Django, Celery | no | no | no | no | not started | @@ -47,11 +47,13 @@ and no Rust CLI. These are the features a new language must cover, or explicitly decline with a documented limit. The TS/JS command is the reference behavior. -**Module graph.** `dependencies`, `dependents`, `related`, and `importers` -over typed edges in the canonical `DepGraph`. TS/JS uses `import`, -`type-import`, `dynamic-import`, `require`, `workspace`, and `package`. Swift -and .NET already show the non-TS pattern: language-specific edge kinds behind -`--relationship swift` or `--relationship dotnet`. +**Module graph.** `dependencies`, `dependents`, and `related` over typed +edges in the canonical `DepGraph`. TS/JS uses `import`, `type-import`, +`dynamic-import`, `require`, `workspace`, and `package`. Swift and .NET +already show the non-TS pattern: language-specific edge kinds behind +`--relationship swift` or `--relationship dotnet`. `importers` stays the +fast TS/JS-only reverse static-import scan; it does not walk language +graphs. Use `dependents --relationship ` for those. **Symbols.** Named declarations and who references them. TS/JS has `symbols`, `exports-of`, `dead-exports`, and `call-sites`. Swift and .NET collect @@ -74,8 +76,9 @@ files, the same way TS `fetch` and Swift `Endpoint` literals do. **Lockfiles.** `lockfile diff` today parses npm-family lockfiles. Language support adds the ecosystem lockfile when agents need package-change impact: -`poetry.lock` / `uv.lock`, `go.sum`, `Cargo.lock`, `Gemfile.lock`, -`composer.lock`. +`poetry.lock` / `uv.lock` / `Pipfile.lock`, `go.mod`, `Cargo.lock`, +`Gemfile.lock`, `composer.lock`. Go package-change impact reads the selected +module graph from `go.mod`, not authentication hashes in `go.sum`. **Checks, CLI, and N-API.** Every stable CLI capability needs an async N-API equivalent, fixture-backed tests, and docs. Language-specific check rules @@ -95,10 +98,12 @@ Follow the Swift and .NET adapter shape, not a second analysis session. language collector borrows those inputs. 3. A fact pass extracts imports, declarations, references, and domain occurrences (routes, enqueue sites, workers) from each matching file. - Heuristic extractors that strip comments/strings first, matching + Heuristic extractors that strip comments first, matching `codebase/swift` and `codebase/dotnet`, are the expected first - implementation. A full grammar is allowed later only if it stays in-process, - deterministic, and one-pass. + implementation. String literals stay in the source used for route, queue, + and topic facts; stripping strings would erase those identities. A full + grammar is allowed later only if it stays in-process, deterministic, and + one-pass. 4. Relationship analyzers emit typed edges into `DepGraph`. Commands and reports project those edges; they do not rebuild a private index. 5. Test discovery reads the language's explicit package/project config and @@ -171,7 +176,7 @@ Go support is the language frontend. Asynq is the queue extractor. | Tests | `tests plan vitest` | `tests plan go` → `go test` in owning packages | | HTTP routes | `server routes` | configured `net/http`, Chi, Gin, Echo, or Fiber registrations | | Queues | BullMQ job name | Asynq `NewTask("mail:welcome", …)` / `HandleFunc("mail:welcome", …)` | -| Lockfile | npm-family | `go.sum` | +| Lockfile | npm-family | `go.mod` (selected module graph, not `go.sum`) | Asynq task type strings are the virtual job identity, same as a BullMQ job name. A producer file gets `queue-enqueue`; the handler file gets @@ -190,8 +195,10 @@ string literal produce no edge. `go/ast` in-process is acceptable; invoking the Kafka is a queue backend, not a language. Producers and consumers in any supported language emit the existing `queue-enqueue` and `queue-worker` edges. -The virtual node identity is the static topic (and optional static group id), -not a language-specific edge kind. +The virtual node identity is the static topic string, not a language-specific +edge kind. A consumer group id is edge metadata (or a downstream node), never +part of the shared topic identity, so producer enqueue and consumer worker +edges meet on the same node. ```ts producer.send({ topic: "mail.welcome", messages }); @@ -217,7 +224,7 @@ routes and queues from the shared domains. | Module graph | `import` | `mod`, `use crate::…`, `use super::…`, path attrs | | Package identity | workspace packages | configured `Cargo.toml` packages and path deps | | Symbols | named exports | `pub fn` / `pub struct` / `pub enum` and `use` paths | -| Tests | `tests plan vitest` | `tests plan cargo` → `cargo test -p --test ` | +| Tests | `tests plan vitest` | `tests plan cargo` → `cargo test -p ` for sibling `tests.rs`; `cargo test -p --test ` only for `tests/` integration targets | | HTTP routes | `server routes` | configured Axum, Actix, or Rocket registrations | | Queues | BullMQ | configured enqueue/worker globs; Kafka when present | | Lockfile | npm-family | `Cargo.lock` | @@ -241,7 +248,7 @@ extractors. | Package identity | workspace packages | configured engine/app roots and `Gemfile` path gems | | Tests | `tests plan vitest` | `tests plan rails` over Minitest / RSpec files | | HTTP routes | `server routes` | configured `config/routes.rb` (and engine routes) → controller#action | -| Queues | BullMQ | Active Job `SomeJob.perform_later` / `perform_async` → job class | +| Queues | BullMQ | Active Job `SomeJob.perform_later` → job class. Sidekiq `perform_async` is a later extractor, not v1. | | Lockfile | npm-family | `Gemfile.lock` | Zeitwerk inference is heuristic and must stay inside configured roots. Do not @@ -256,8 +263,9 @@ WelcomeJob.perform_later(user) ## PHP -PHP support is Composer/PSR-4 facts plus configured framework extractors -(Laravel or Symfony, not both inferred). +PHP support is Composer/PSR-4 facts plus one configured framework extractor. +Set `tests.php.framework` to `laravel` or `symfony`. Do not infer the +framework from files, and do not enable both extractors from a missing value. | Feature | TS/JS reference | PHP equivalent | | --- | --- | --- | @@ -323,6 +331,7 @@ tests: apps: - apps/web php: + framework: laravel apps: - services/api ``` From 0a44d13f2428caabcfcca3bb696d3d9097dff124 Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Sun, 16 Aug 2026 00:50:37 -0700 Subject: [PATCH 4/4] docs: namespace Kafka topic identity by cluster Topic names are cluster-scoped. The contract now uses : so reused names on different brokers do not collapse into one virtual queue node. --- docs/feature-parity.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/feature-parity.md b/docs/feature-parity.md index 4867067d1..4b2f2740e 100644 --- a/docs/feature-parity.md +++ b/docs/feature-parity.md @@ -195,10 +195,11 @@ string literal produce no edge. `go/ast` in-process is acceptable; invoking the Kafka is a queue backend, not a language. Producers and consumers in any supported language emit the existing `queue-enqueue` and `queue-worker` edges. -The virtual node identity is the static topic string, not a language-specific -edge kind. A consumer group id is edge metadata (or a downstream node), never -part of the shared topic identity, so producer enqueue and consumer worker -edges meet on the same node. +The virtual node identity is `:`: an explicit configured +logical cluster or broker namespace plus the static topic string. Topic names +are cluster-scoped, so two clients that reuse `mail.welcome` on different +clusters stay distinct. A consumer group id is edge metadata (or a downstream +node), never part of that identity. ```ts producer.send({ topic: "mail.welcome", messages }); @@ -315,6 +316,7 @@ projects: root: backend routes: ["config/urls.py", "routes/**/*.py"] queues: + cluster: orders enqueues: ["app/**/tasks.py"] workers: ["app/**/tasks.py", "workers/**/*.py"] tests: