{"owner":"libnyanpasu","repo":"clash-nyanpasu","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","CLAUDE.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\nThis repository is migrating away from `::global()` singletons and Tauri-coupled services toward explicit dependency injection, actor-owned state, and pure domain services.\n\nBehavioral guidelines reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 0. Synchronization Policy\n\nKeep `CLAUDE.md` and `AGENTS.md` synchronized as much as possible.\n\n- When changing an architectural rule in one file, mirror it in the other file.\n- Differences should be limited to tool-specific wording, if any.\n- Prefer the same section order, same terminology, and same examples.\n- Do not create a Claude-only or agent-only exception unless the tool truly requires it.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\nFor small, obvious tasks, do this briefly. For architecture, migration, or cross-module work, be explicit.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\nThis rule does not override the architectural migration direction. Do not use `::global()` or hidden mutable process state merely because it is fewer lines.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\nFor actor/DI migration work, the allowed scope is the smallest call path needed to migrate the touched service or API without leaving a hidden compatibility layer behind.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" -> \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" -> \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" -> \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```text\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## 5. Target Architecture\n\nThe target architecture is:\n\n```text\nTauri commands / UI adapters\n    -> NyanpasuClient\n        -> typed actor clients\n        -> pure services\n        -> adapter traits\n            -> concrete Tauri / OS / filesystem / network implementations\n```\n\nUse these terms consistently:\n\n- **Dependency Injection / Pure DI**: dependencies are passed explicitly through constructors, builders, function arguments, or actor startup arguments. Do not look them up through globals.\n- **Composition Root**: the bootstrap / supervisor location that builds the full object graph and actor graph.\n- **Ports and Adapters**: core/application code depends on traits; Tauri, filesystem, OS, network, and process implementations live behind adapters.\n- **Actor service**: a ractor actor that owns mutable state, serializes commands, manages long-running resources, or supervises background work.\n- **Pure service**: a stateless or short-lived service that performs deterministic computation, validation, conversion, config generation, serialization, or patch application without IPC or background lifecycle.\n- **Adapter / port**: a narrow trait and concrete boundary implementation for infrastructure such as Tauri, filesystem, OS APIs, process spawning, HTTP, logging sinks, or storage.\n\n`NyanpasuClient` is the application facade. The application bootstrap / supervisor is the composition root. It constructs concrete services, spawns actors, wires dependencies, and returns a ready-to-use `NyanpasuClient`.\n\n## 6. ractor Primer for Agents\n\nIn this repository, `ractor` means the Rust `ractor` crate. It is an in-process actor framework used for long-lived services that own state and communicate through typed messages. It is not Tauri IPC and not Ruby Ractor.\n\nUse this mental model:\n\n```text\nActor = private mutable state + typed message enum + sequential message handling + lifecycle hooks\n```\n\nCore concepts:\n\n- `Actor`: the implementation trait. An actor defines its `Msg`, `State`, `Arguments`, and lifecycle/message-handling methods.\n- `ActorRef<Msg>`: a typed address used to send messages to an actor. Hide raw `ActorRef` values behind typed clients such as `StateClient` or `CoreClient`.\n- Message enum: the actor's domain protocol. Prefer explicit messages such as `PatchAppConfig`, `RestartCore`, or `SelectProxy` over generic commands.\n- `RpcReplyPort<T>`: the usual request/reply mechanism for queries and fallible operations that must return a value.\n- Fire-and-forget messages: use only for notifications, invalidations, events, or best-effort work where the caller does not need a result.\n- Startup arguments: the actor's dependency injection boundary. Pass dependencies when spawning the actor; do not fetch them from globals in actor code.\n\nProject rules:\n\n- Use actors for services with long-lived mutable state, serialized commands, background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, downloads, or child supervision.\n- Do not use actors for deterministic computation. Use pure services for validation, schema conversion, patch application, runtime config building, serialization, and merge/enhance logic when it can be deterministic.\n- Put infrastructure access behind adapter traits. Inject Tauri, OS, filesystem, network, process, and logging adapters into the actor or pure service that needs them.\n- The composition root spawns actors, wires dependencies, and returns `NyanpasuClient`. Do not use a ractor registry, actor name lookup, or raw `ActorRef` map as a replacement for dependency injection.\n- `NyanpasuClient` and typed actor clients should expose ordinary async Rust methods. Most callers should not use ractor APIs directly.\n- Prefer finite timeouts for cross-actor request/reply calls when the caller can report failure or degraded state.\n- Avoid synchronous cross-actor cycles such as `StateActor -> CoreActor -> StateActor`.\n\nIf a mature ractor actor client already exists for a capability, use it instead of adding a new global singleton, raw channel loop, or direct Tauri-coupled service call.\n\n## 7. Mandatory Architecture Rules\n\n### Do not add new global service singletons\n\nDo not introduce new service accessors such as:\n\n```rust\nService::global()\nget_global_service()\nstatic SERVICE: OnceCell<Service>\nstatic SERVICE: OnceLock<Service>\nstatic SERVICE: Lazy<Service>\n```\n\nExceptions are allowed only for immutable constants, static lookup tables, feature flags, or values that are truly process-wide and have no lifecycle, no mutable state, and no dependency graph.\n\nIf an existing `::global()` service must still be used during migration, isolate it at the edge of a migration step and add an explicit comment:\n\n```rust\n// TODO(actor-migration): temporary bridge to the legacy global service.\n// Reason: <why full migration is blocked>.\n// Remove when: <service-name> is injected through NyanpasuClient.\n```\n\n### Prefer explicit construction\n\nServices must be constructed through one of these forms:\n\n```rust\nService::new(dependency_a, dependency_b)\nServiceBuilder::default().with_dependency(...).build()\nAppSupervisor::start(args).await\n```\n\nDependencies should be visible in struct fields, constructor parameters, builder parameters, function parameters, or actor startup arguments. Hidden dependencies are not allowed.\n\n### Keep `NyanpasuClient` as a facade, not a service locator\n\n`NyanpasuClient` may expose stable application APIs, for example:\n\n```rust\nclient.get_app_config().await?;\nclient.patch_app_config(patch).await?;\nclient.get_profiles().await?;\nclient.restart_core().await?;\nclient.select_proxy(group, name).await?;\n```\n\nIt must not expose arbitrary internal lookup APIs such as:\n\n```rust\nclient.get_any_service::<T>()\nclient.resolve::<T>()\nclient.resolve(\"service-name\")\nclient.get_service(\"name\")\nclient.actor_registry()\nclient.get_actor_ref(\"state\")\n```\n\nInternally, `NyanpasuClient` may hold typed clients such as `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient`, and pure services or adapter trait objects. Callers should not depend on ractor `ActorRef` directly unless they are part of the actor layer itself.\n\n### Keep Tauri at the boundary\n\nBusiness logic must not depend directly on Tauri types such as `AppHandle`, `Window`, `Manager`, tray handles, global command state, or Tauri event emitters. Use adapter traits instead.\n\n## 8. Service Classification\n\nBefore adding or migrating a service, classify it as an actor service, pure service, or adapter/port.\n\n### Use an actor service when the service:\n\n- owns long-lived mutable state;\n- must serialize commands to avoid races;\n- manages background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, or downloads;\n- supervises child tasks or child actors;\n- needs request/reply or fire-and-forget messaging;\n- coordinates side effects after state commits.\n\nExpected examples:\n\n- app/config state;\n- core process lifecycle;\n- system proxy state;\n- hotkey registration;\n- proxy cache and subscriptions;\n- updater downloads;\n- websocket connection managers;\n- server lifecycle.\n\nActor implementation rules:\n\n- Use a typed message enum.\n- Keep owned mutable state inside the actor state.\n- Use typed actor client wrappers for public calls.\n- Do not expose raw `ActorRef` outside actor/application internals.\n- Use request/reply for fallible operations and queries.\n- Use fire-and-forget only for events, notifications, or best-effort work.\n- Avoid cross-actor synchronous cycles.\n- Prefer finite timeouts for cross-actor RPCs where a caller can recover or report degradation.\n- Actor startup arguments must contain all dependencies required to build the actor state.\n- Do not share actor-owned mutable state with `Arc<Mutex<_>>` or `Arc<RwLock<_>>` unless it is a narrowly scoped implementation detail with a clear comment.\n\n### Use a pure service when the service:\n\n- performs deterministic computation;\n- validates input;\n- converts schemas;\n- applies patches to owned data passed as parameters;\n- builds runtime configuration from snapshots;\n- serializes or deserializes data without owning long-lived state;\n- has no background task and no independent lifecycle.\n\nExpected examples:\n\n- config validation;\n- patch application;\n- profile ordering;\n- runtime config building;\n- legacy schema conversion;\n- serialization helpers;\n- merge/enhance logic when it can be made deterministic.\n\nPure service rules:\n\n- No global state.\n- No background tasks.\n- No hidden filesystem/network/Tauri access.\n- All inputs must be explicit parameters.\n- Return values or domain errors instead of mutating external state.\n\n### Use an adapter / port when the service touches infrastructure:\n\n- Tauri events, windows, tray, dialogs, clipboard;\n- filesystem and app directories;\n- OS proxy APIs;\n- global shortcuts;\n- HTTP clients;\n- child process spawning;\n- logging sinks;\n- persistent storage backends.\n\nAdapter rules:\n\n- Core/application code depends on traits.\n- Concrete adapters live at the boundary crate/module.\n- Keep adapter traits narrow and task-oriented.\n- Prefer mockable traits for tests.\n- Prefer traits owned by the consuming crate/module when that improves boundary clarity.\n\n## 9. When You Touch Legacy Global Code\n\nIf you see patterns such as:\n\n```rust\nConfig::global()\nConfig::verge()\nConfig::clash()\nConfig::profiles()\nConfig::runtime()\nCoreManager::global()\nSysopt::global()\nHotkey::global()\nLogger::global()\nHandle::global()\nProxiesGuard::global()\nUpdaterManager::global()\nWindowManager::global()\nconsts::app_handle()\n```\n\nprefer replacing the call path with one of:\n\n```rust\nclient.some_domain_operation(...).await?;\nstate_client.some_state_operation(...).await?;\ncore_client.some_core_operation(...).await?;\nsystem_proxy_client.some_system_operation(...).await?;\nservice.method(...)?;\n```\n\nDo not add a new wrapper that simply hides the global unless full migration is blocked. If blocked, document it:\n\n```rust\n// TODO(actor-migration): temporary bridge to <legacy global>.\n// Reason: <specific blocker>.\n// Remove when: <specific migration step>.\n```\n\n## 10. State and Configuration Migration\n\nConfiguration must be migrated before dependent services whenever possible.\n\nPreferred direction:\n\n1. Move state ownership into `StateActor` or a state manager owned by `StateActor`.\n2. Keep schema and patch operations in pure services or domain types.\n3. Generate runtime config from snapshots rather than mutating runtime globals.\n4. Commit state first, then trigger side effects through actor messages.\n5. Report post-commit side-effect failures as degraded results instead of silently rolling back persisted state.\n\nAvoid preserving old global configuration APIs. Prefer a migratable breaking change that updates callers to the new injected client/service API.\n\n## 11. Migration Policy: Prefer Migratable Breaking Changes\n\nWhen refactoring or migrating services:\n\n- Prefer fully migrating callers to the new injected/actor/pure-service API.\n- Prefer migratable breaking changes over compatibility layers.\n- Do not add a compatibility layer simply to avoid updating call sites.\n- Add a compatibility or migration layer only when a full migration is not currently possible due to cyclic dependencies, public API constraints, external plugin behavior, large cross-cutting risk, platform limitation, or staged release requirements.\n- Every compatibility layer must be explicitly marked with `TODO(actor-migration)` or `FIXME(actor-migration)` and must explain the reason and removal condition.\n- New code must not call compatibility APIs unless the call site is itself part of a documented migration step.\n\nDo this:\n\n```rust\nclient.patch_app_config(patch).await?;\n```\n\nDo not do this unless blocked:\n\n```rust\nLegacyConfigCompat::patch_verge(patch).await?;\n```\n\nRequired comment format:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nor:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 12. Tauri Command Rules\n\nTauri commands should be thin adapters. They should:\n\n- parse request DTOs;\n- call `NyanpasuClient`;\n- map domain errors into command errors;\n- never perform business orchestration directly;\n- never read or mutate config through globals;\n- never spawn core/service background tasks directly.\n\nAllowed shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(\n    client: tauri::State<'_, NyanpasuClient>,\n    patch: NyanpasuAppConfigPatch,\n) -> Result<()> {\n    client.patch_app_config(patch).await?;\n    Ok(())\n}\n```\n\nAvoid shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(patch: IVerge) -> Result<()> {\n    Config::verge().draft().patch_config(patch)?;\n    CoreManager::global().update_config().await?;\n    Config::verge().apply();\n    Ok(())\n}\n```\n\n## 13. Testing and Mocking\n\n- Prefer testing pure services directly with plain values.\n- For infrastructure dependencies, define narrow traits and inject them.\n- Traits that are intended to be mocked should be compatible with `mockall` / `automock` where practical.\n- Keep mock-only APIs behind `#[cfg(test)]` or test-support modules.\n- Do not use global test fixtures for application services. Construct a test `NyanpasuClient` or test-specific service graph.\n- Actor tests should spawn the actor with fake adapters and send typed messages through its typed client.\n- Avoid sleeping in actor tests. Prefer explicit acknowledgements, request/reply messages, or test hooks.\n\nExample mockable trait:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait UiEventSink: Send + Sync + 'static {\n    fn emit_state_changed(&self, event: StateChanged) -> anyhow::Result<()>;\n}\n```\n\nAnother acceptable trait shape:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait ConfigStore: Send + Sync + 'static {\n    fn load(&self) -> anyhow::Result<Vec<u8>>;\n    fn save(&self, bytes: &[u8]) -> anyhow::Result<()>;\n}\n```\n\n## 14. Naming Guidelines\n\nUse names that reveal the role:\n\n- `StateActor`, `CoreActor`, `SystemProxyActor`, `HotkeyActor`, `ProxiesActor`, `UpdaterActor` for actors.\n- `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient` for typed actor clients.\n- `RuntimeBuilder`, `ProfileMerger`, `ConfigMigrator`, `PatchValidator` for pure services.\n- `TauriUiEventSink`, `FsConfigStore`, `OsProxyBackend`, `ProcessRunner` for adapters.\n- `AppSupervisor` or `NyanpasuBootstrap` for the composition root.\n\n## 15. Comment Requirements\n\nUse comments only where they clarify migration state, invariants, or actor lifecycle assumptions.\n\nRequired compatibility-layer comment:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nRequired temporary legacy behavior comment:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 16. Final Review Checklist\n\nBefore finishing a change, check:\n\n- assumptions were stated when relevant;\n- success criteria were verified;\n- every changed line traces to the request;\n- no new global singleton service was added;\n- no new mutable static service state was added;\n- dependencies are explicit;\n- service classification is clear;\n- actor state is not leaked through shared locks;\n- Tauri is isolated behind adapters;\n- compatibility layers are exceptional and documented;\n- tests use injection, fakes, mocks, or pure values;\n- `NyanpasuClient` remains a facade, not a service locator.\n\n## 17. Worktree Setup and Resource Reuse\n\nFeature/migration work runs in isolated git worktrees. The worktree location is the developer's choice (any path outside the repo tree); this section only fixes the reuse policy, not where worktrees live. Worktrees share the main `.git`. The rule: reuse expensive **branch-independent** assets from the main checkout via symlink, and regenerate everything **branch-dependent** per worktree.\n\n### Reuse policy\n\n| Path (repo-relative)       | Approx size  | Policy                          | Reason                                                                                                                                    |\n| -------------------------- | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `backend/tauri/sidecar/`   | ~213M        | **Symlink → main**              | gitignored downloaded cores (mihomo / clash-rs / clash / nyanpasu-service); branch-independent; re-fetch via `pnpm prepare:check` is slow |\n| `backend/tauri/resources/` | ~21M         | **Symlink → main**              | gitignored static assets (`geoip.dat`, `geosite.dat`, `Country.mmdb`, `wintun.dll`, service exes); branch-independent                     |\n| `node_modules/`            | ~1.5G        | **Independent `pnpm install`**  | pnpm global store already hardlink-dedupes; sharing risks concurrent lock conflicts                                                       |\n| `backend/target/`          | ~50G         | **Independent — never symlink** | sharing causes Cargo incremental-fingerprint churn + concurrent build-lock waits across diverged source trees                             |\n| `backend/tauri/tmp/dist/`  | build output | **Independent — never symlink** | branch-dependent frontend build; `emptyOutDir: true` means one worktree's `web:build` wipes the shared dir                                |\n\nOnly `sidecar/` and `resources/` are symlink candidates.\n\n### Gitignored build prerequisites a fresh worktree lacks\n\n- **`frontend/interface/dist`** — `@nyanpasu/interface` (`main` → `./dist/index.js`) is consumed by `@nyanpasu/nyanpasu`. Produce with `pnpm -F interface build`.\n- **`backend/tauri/tmp/dist`** — `backend/tauri/build.rs` calls `tauri_build::build()`, which validates `frontendDist: ./tmp/dist` **at compile time**. When missing, every `cargo build` / `clippy` / `cargo test --all-features` / rust-analyzer run on the tauri crate fails. Resolve one of:\n  - Rust-only worktree → drop a placeholder (cheapest, no vite build).\n  - Runnable UI → `pnpm web:build` (build `interface` first; it clears and refills `tmp/dist`).\n\n`backend/tauri/tmp/git-info.json` is optional (`build.rs` guards it with `exists()`); run `pnpm generate:git-info` only if accurate commit metadata must be baked in.\n\n### Create a worktree\n\nCommands shown for Windows / PowerShell (dir symlinks need Developer Mode, no elevation). `<worktree-path>` and `<type>/<name>` are yours to choose.\n\n```powershell\n$main = git rev-parse --show-toplevel                 # capture main checkout root\ngit worktree add <worktree-path> -b <type>/<name>\ncd <worktree-path>\n\n# Reuse branch-independent downloads (symlink back to main)\nNew-Item -ItemType SymbolicLink backend/tauri/sidecar   -Target \"$main/backend/tauri/sidecar\"\nNew-Item -ItemType SymbolicLink backend/tauri/resources -Target \"$main/backend/tauri/resources\"\n\npnpm install\npnpm -F interface build                               # -> frontend/interface/dist (gitignored)\n\n# Satisfy tauri-build's frontendDist check — pick one:\nNew-Item -ItemType Directory -Force backend/tauri/tmp/dist | Out-Null            # A) Rust-only placeholder\nSet-Content backend/tauri/tmp/dist/index.html '<!doctype html><title>dev</title>'\n# pnpm web:build                                      # B) real UI (replaces tmp/dist)\n```\n\n### Remove a worktree\n\n`git worktree remove` on Windows can fail with `Filename too long` because per-worktree `node_modules` / `target` hold paths over MAX_PATH. Force-delete with the extended-length prefix, then reconcile git:\n\n```powershell\nRemove-Item -LiteralPath \"\\\\?\\<absolute-worktree-path>\" -Recurse -Force\ngit worktree prune\ngit worktree list\n```\n\nRemoval reclaims only the worktree's own files and its symlinks (pointers back to main) — it never touches the main checkout's real `sidecar/` / `resources/`.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, clarifying questions come before implementation rather than after mistakes, and new code moves away from global singletons toward injected actor/pure-service composition.\n","CLAUDE.md":"# CLAUDE.md\n\nThis repository is migrating away from `::global()` singletons and Tauri-coupled services toward explicit dependency injection, actor-owned state, and pure domain services.\n\nBehavioral guidelines reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 0. Synchronization Policy\n\nKeep `CLAUDE.md` and `AGENTS.md` synchronized as much as possible.\n\n- When changing an architectural rule in one file, mirror it in the other file.\n- Differences should be limited to tool-specific wording, if any.\n- Prefer the same section order, same terminology, and same examples.\n- Do not create a Claude-only or agent-only exception unless the tool truly requires it.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\nFor small, obvious tasks, do this briefly. For architecture, migration, or cross-module work, be explicit.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\nThis rule does not override the architectural migration direction. Do not use `::global()` or hidden mutable process state merely because it is fewer lines.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\nFor actor/DI migration work, the allowed scope is the smallest call path needed to migrate the touched service or API without leaving a hidden compatibility layer behind.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" -> \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" -> \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" -> \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```text\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## 5. Target Architecture\n\nThe target architecture is:\n\n```text\nTauri commands / UI adapters\n    -> NyanpasuClient\n        -> typed actor clients\n        -> pure services\n        -> adapter traits\n            -> concrete Tauri / OS / filesystem / network implementations\n```\n\nUse these terms consistently:\n\n- **Dependency Injection / Pure DI**: dependencies are passed explicitly through constructors, builders, function arguments, or actor startup arguments. Do not look them up through globals.\n- **Composition Root**: the bootstrap / supervisor location that builds the full object graph and actor graph.\n- **Ports and Adapters**: core/application code depends on traits; Tauri, filesystem, OS, network, and process implementations live behind adapters.\n- **Actor service**: a ractor actor that owns mutable state, serializes commands, manages long-running resources, or supervises background work.\n- **Pure service**: a stateless or short-lived service that performs deterministic computation, validation, conversion, config generation, serialization, or patch application without IPC or background lifecycle.\n- **Adapter / port**: a narrow trait and concrete boundary implementation for infrastructure such as Tauri, filesystem, OS APIs, process spawning, HTTP, logging sinks, or storage.\n\n`NyanpasuClient` is the application facade. The application bootstrap / supervisor is the composition root. It constructs concrete services, spawns actors, wires dependencies, and returns a ready-to-use `NyanpasuClient`.\n\n## 6. ractor Primer for Agents\n\nIn this repository, `ractor` means the Rust `ractor` crate. It is an in-process actor framework used for long-lived services that own state and communicate through typed messages. It is not Tauri IPC and not Ruby Ractor.\n\nUse this mental model:\n\n```text\nActor = private mutable state + typed message enum + sequential message handling + lifecycle hooks\n```\n\nCore concepts:\n\n- `Actor`: the implementation trait. An actor defines its `Msg`, `State`, `Arguments`, and lifecycle/message-handling methods.\n- `ActorRef<Msg>`: a typed address used to send messages to an actor. Hide raw `ActorRef` values behind typed clients such as `StateClient` or `CoreClient`.\n- Message enum: the actor's domain protocol. Prefer explicit messages such as `PatchAppConfig`, `RestartCore`, or `SelectProxy` over generic commands.\n- `RpcReplyPort<T>`: the usual request/reply mechanism for queries and fallible operations that must return a value.\n- Fire-and-forget messages: use only for notifications, invalidations, events, or best-effort work where the caller does not need a result.\n- Startup arguments: the actor's dependency injection boundary. Pass dependencies when spawning the actor; do not fetch them from globals in actor code.\n\nProject rules:\n\n- Use actors for services with long-lived mutable state, serialized commands, background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, downloads, or child supervision.\n- Do not use actors for deterministic computation. Use pure services for validation, schema conversion, patch application, runtime config building, serialization, and merge/enhance logic when it can be deterministic.\n- Put infrastructure access behind adapter traits. Inject Tauri, OS, filesystem, network, process, and logging adapters into the actor or pure service that needs them.\n- The composition root spawns actors, wires dependencies, and returns `NyanpasuClient`. Do not use a ractor registry, actor name lookup, or raw `ActorRef` map as a replacement for dependency injection.\n- `NyanpasuClient` and typed actor clients should expose ordinary async Rust methods. Most callers should not use ractor APIs directly.\n- Prefer finite timeouts for cross-actor request/reply calls when the caller can report failure or degraded state.\n- Avoid synchronous cross-actor cycles such as `StateActor -> CoreActor -> StateActor`.\n\nIf a mature ractor actor client already exists for a capability, use it instead of adding a new global singleton, raw channel loop, or direct Tauri-coupled service call.\n\n## 7. Mandatory Architecture Rules\n\n### Do not add new global service singletons\n\nDo not introduce new service accessors such as:\n\n```rust\nService::global()\nget_global_service()\nstatic SERVICE: OnceCell<Service>\nstatic SERVICE: OnceLock<Service>\nstatic SERVICE: Lazy<Service>\n```\n\nExceptions are allowed only for immutable constants, static lookup tables, feature flags, or values that are truly process-wide and have no lifecycle, no mutable state, and no dependency graph.\n\nIf an existing `::global()` service must still be used during migration, isolate it at the edge of a migration step and add an explicit comment:\n\n```rust\n// TODO(actor-migration): temporary bridge to the legacy global service.\n// Reason: <why full migration is blocked>.\n// Remove when: <service-name> is injected through NyanpasuClient.\n```\n\n### Prefer explicit construction\n\nServices must be constructed through one of these forms:\n\n```rust\nService::new(dependency_a, dependency_b)\nServiceBuilder::default().with_dependency(...).build()\nAppSupervisor::start(args).await\n```\n\nDependencies should be visible in struct fields, constructor parameters, builder parameters, function parameters, or actor startup arguments. Hidden dependencies are not allowed.\n\n### Keep `NyanpasuClient` as a facade, not a service locator\n\n`NyanpasuClient` may expose stable application APIs, for example:\n\n```rust\nclient.get_app_config().await?;\nclient.patch_app_config(patch).await?;\nclient.get_profiles().await?;\nclient.restart_core().await?;\nclient.select_proxy(group, name).await?;\n```\n\nIt must not expose arbitrary internal lookup APIs such as:\n\n```rust\nclient.get_any_service::<T>()\nclient.resolve::<T>()\nclient.resolve(\"service-name\")\nclient.get_service(\"name\")\nclient.actor_registry()\nclient.get_actor_ref(\"state\")\n```\n\nInternally, `NyanpasuClient` may hold typed clients such as `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient`, and pure services or adapter trait objects. Callers should not depend on ractor `ActorRef` directly unless they are part of the actor layer itself.\n\n### Keep Tauri at the boundary\n\nBusiness logic must not depend directly on Tauri types such as `AppHandle`, `Window`, `Manager`, tray handles, global command state, or Tauri event emitters. Use adapter traits instead.\n\n## 8. Service Classification\n\nBefore adding or migrating a service, classify it as an actor service, pure service, or adapter/port.\n\n### Use an actor service when the service:\n\n- owns long-lived mutable state;\n- must serialize commands to avoid races;\n- manages background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, or downloads;\n- supervises child tasks or child actors;\n- needs request/reply or fire-and-forget messaging;\n- coordinates side effects after state commits.\n\nExpected examples:\n\n- app/config state;\n- core process lifecycle;\n- system proxy state;\n- hotkey registration;\n- proxy cache and subscriptions;\n- updater downloads;\n- websocket connection managers;\n- server lifecycle.\n\nActor implementation rules:\n\n- Use a typed message enum.\n- Keep owned mutable state inside the actor state.\n- Use typed actor client wrappers for public calls.\n- Do not expose raw `ActorRef` outside actor/application internals.\n- Use request/reply for fallible operations and queries.\n- Use fire-and-forget only for events, notifications, or best-effort work.\n- Avoid cross-actor synchronous cycles.\n- Prefer finite timeouts for cross-actor RPCs where a caller can recover or report degradation.\n- Actor startup arguments must contain all dependencies required to build the actor state.\n- Do not share actor-owned mutable state with `Arc<Mutex<_>>` or `Arc<RwLock<_>>` unless it is a narrowly scoped implementation detail with a clear comment.\n\n### Use a pure service when the service:\n\n- performs deterministic computation;\n- validates input;\n- converts schemas;\n- applies patches to owned data passed as parameters;\n- builds runtime configuration from snapshots;\n- serializes or deserializes data without owning long-lived state;\n- has no background task and no independent lifecycle.\n\nExpected examples:\n\n- config validation;\n- patch application;\n- profile ordering;\n- runtime config building;\n- legacy schema conversion;\n- serialization helpers;\n- merge/enhance logic when it can be made deterministic.\n\nPure service rules:\n\n- No global state.\n- No background tasks.\n- No hidden filesystem/network/Tauri access.\n- All inputs must be explicit parameters.\n- Return values or domain errors instead of mutating external state.\n\n### Use an adapter / port when the service touches infrastructure:\n\n- Tauri events, windows, tray, dialogs, clipboard;\n- filesystem and app directories;\n- OS proxy APIs;\n- global shortcuts;\n- HTTP clients;\n- child process spawning;\n- logging sinks;\n- persistent storage backends.\n\nAdapter rules:\n\n- Core/application code depends on traits.\n- Concrete adapters live at the boundary crate/module.\n- Keep adapter traits narrow and task-oriented.\n- Prefer mockable traits for tests.\n- Prefer traits owned by the consuming crate/module when that improves boundary clarity.\n\n## 9. When You Touch Legacy Global Code\n\nIf you see patterns such as:\n\n```rust\nConfig::global()\nConfig::verge()\nConfig::clash()\nConfig::profiles()\nConfig::runtime()\nCoreManager::global()\nSysopt::global()\nHotkey::global()\nLogger::global()\nHandle::global()\nProxiesGuard::global()\nUpdaterManager::global()\nWindowManager::global()\nconsts::app_handle()\n```\n\nprefer replacing the call path with one of:\n\n```rust\nclient.some_domain_operation(...).await?;\nstate_client.some_state_operation(...).await?;\ncore_client.some_core_operation(...).await?;\nsystem_proxy_client.some_system_operation(...).await?;\nservice.method(...)?;\n```\n\nDo not add a new wrapper that simply hides the global unless full migration is blocked. If blocked, document it:\n\n```rust\n// TODO(actor-migration): temporary bridge to <legacy global>.\n// Reason: <specific blocker>.\n// Remove when: <specific migration step>.\n```\n\n## 10. State and Configuration Migration\n\nConfiguration must be migrated before dependent services whenever possible.\n\nPreferred direction:\n\n1. Move state ownership into `StateActor` or a state manager owned by `StateActor`.\n2. Keep schema and patch operations in pure services or domain types.\n3. Generate runtime config from snapshots rather than mutating runtime globals.\n4. Commit state first, then trigger side effects through actor messages.\n5. Report post-commit side-effect failures as degraded results instead of silently rolling back persisted state.\n\nAvoid preserving old global configuration APIs. Prefer a migratable breaking change that updates callers to the new injected client/service API.\n\n## 11. Migration Policy: Prefer Migratable Breaking Changes\n\nWhen refactoring or migrating services:\n\n- Prefer fully migrating callers to the new injected/actor/pure-service API.\n- Prefer migratable breaking changes over compatibility layers.\n- Do not add a compatibility layer simply to avoid updating call sites.\n- Add a compatibility or migration layer only when a full migration is not currently possible due to cyclic dependencies, public API constraints, external plugin behavior, large cross-cutting risk, platform limitation, or staged release requirements.\n- Every compatibility layer must be explicitly marked with `TODO(actor-migration)` or `FIXME(actor-migration)` and must explain the reason and removal condition.\n- New code must not call compatibility APIs unless the call site is itself part of a documented migration step.\n\nDo this:\n\n```rust\nclient.patch_app_config(patch).await?;\n```\n\nDo not do this unless blocked:\n\n```rust\nLegacyConfigCompat::patch_verge(patch).await?;\n```\n\nRequired comment format:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nor:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 12. Tauri Command Rules\n\nTauri commands should be thin adapters. They should:\n\n- parse request DTOs;\n- call `NyanpasuClient`;\n- map domain errors into command errors;\n- never perform business orchestration directly;\n- never read or mutate config through globals;\n- never spawn core/service background tasks directly.\n\nAllowed shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(\n    client: tauri::State<'_, NyanpasuClient>,\n    patch: NyanpasuAppConfigPatch,\n) -> Result<()> {\n    client.patch_app_config(patch).await?;\n    Ok(())\n}\n```\n\nAvoid shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(patch: IVerge) -> Result<()> {\n    Config::verge().draft().patch_config(patch)?;\n    CoreManager::global().update_config().await?;\n    Config::verge().apply();\n    Ok(())\n}\n```\n\n## 13. Testing and Mocking\n\n- Prefer testing pure services directly with plain values.\n- For infrastructure dependencies, define narrow traits and inject them.\n- Traits that are intended to be mocked should be compatible with `mockall` / `automock` where practical.\n- Keep mock-only APIs behind `#[cfg(test)]` or test-support modules.\n- Do not use global test fixtures for application services. Construct a test `NyanpasuClient` or test-specific service graph.\n- Actor tests should spawn the actor with fake adapters and send typed messages through its typed client.\n- Avoid sleeping in actor tests. Prefer explicit acknowledgements, request/reply messages, or test hooks.\n\nExample mockable trait:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait UiEventSink: Send + Sync + 'static {\n    fn emit_state_changed(&self, event: StateChanged) -> anyhow::Result<()>;\n}\n```\n\nAnother acceptable trait shape:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait ConfigStore: Send + Sync + 'static {\n    fn load(&self) -> anyhow::Result<Vec<u8>>;\n    fn save(&self, bytes: &[u8]) -> anyhow::Result<()>;\n}\n```\n\n## 14. Naming Guidelines\n\nUse names that reveal the role:\n\n- `StateActor`, `CoreActor`, `SystemProxyActor`, `HotkeyActor`, `ProxiesActor`, `UpdaterActor` for actors.\n- `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient` for typed actor clients.\n- `RuntimeBuilder`, `ProfileMerger`, `ConfigMigrator`, `PatchValidator` for pure services.\n- `TauriUiEventSink`, `FsConfigStore`, `OsProxyBackend`, `ProcessRunner` for adapters.\n- `AppSupervisor` or `NyanpasuBootstrap` for the composition root.\n\n## 15. Comment Requirements\n\nUse comments only where they clarify migration state, invariants, or actor lifecycle assumptions.\n\nRequired compatibility-layer comment:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nRequired temporary legacy behavior comment:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 16. Final Review Checklist\n\nBefore finishing a change, check:\n\n- assumptions were stated when relevant;\n- success criteria were verified;\n- every changed line traces to the request;\n- no new global singleton service was added;\n- no new mutable static service state was added;\n- dependencies are explicit;\n- service classification is clear;\n- actor state is not leaked through shared locks;\n- Tauri is isolated behind adapters;\n- compatibility layers are exceptional and documented;\n- tests use injection, fakes, mocks, or pure values;\n- `NyanpasuClient` remains a facade, not a service locator.\n\n## 17. Worktree Setup and Resource Reuse\n\nFeature/migration work runs in isolated git worktrees. The worktree location is the developer's choice (any path outside the repo tree); this section only fixes the reuse policy, not where worktrees live. Worktrees share the main `.git`. The rule: reuse expensive **branch-independent** assets from the main checkout via symlink, and regenerate everything **branch-dependent** per worktree.\n\n### Reuse policy\n\n| Path (repo-relative)       | Approx size  | Policy                          | Reason                                                                                                                                    |\n| -------------------------- | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `backend/tauri/sidecar/`   | ~213M        | **Symlink → main**              | gitignored downloaded cores (mihomo / clash-rs / clash / nyanpasu-service); branch-independent; re-fetch via `pnpm prepare:check` is slow |\n| `backend/tauri/resources/` | ~21M         | **Symlink → main**              | gitignored static assets (`geoip.dat`, `geosite.dat`, `Country.mmdb`, `wintun.dll`, service exes); branch-independent                     |\n| `node_modules/`            | ~1.5G        | **Independent `pnpm install`**  | pnpm global store already hardlink-dedupes; sharing risks concurrent lock conflicts                                                       |\n| `backend/target/`          | ~50G         | **Independent — never symlink** | sharing causes Cargo incremental-fingerprint churn + concurrent build-lock waits across diverged source trees                             |\n| `backend/tauri/tmp/dist/`  | build output | **Independent — never symlink** | branch-dependent frontend build; `emptyOutDir: true` means one worktree's `web:build` wipes the shared dir                                |\n\nOnly `sidecar/` and `resources/` are symlink candidates.\n\n### Gitignored build prerequisites a fresh worktree lacks\n\n- **`frontend/interface/dist`** — `@nyanpasu/interface` (`main` → `./dist/index.js`) is consumed by `@nyanpasu/nyanpasu`. Produce with `pnpm -F interface build`.\n- **`backend/tauri/tmp/dist`** — `backend/tauri/build.rs` calls `tauri_build::build()`, which validates `frontendDist: ./tmp/dist` **at compile time**. When missing, every `cargo build` / `clippy` / `cargo test --all-features` / rust-analyzer run on the tauri crate fails. Resolve one of:\n  - Rust-only worktree → drop a placeholder (cheapest, no vite build).\n  - Runnable UI → `pnpm web:build` (build `interface` first; it clears and refills `tmp/dist`).\n\n`backend/tauri/tmp/git-info.json` is optional (`build.rs` guards it with `exists()`); run `pnpm generate:git-info` only if accurate commit metadata must be baked in.\n\n### Create a worktree\n\nCommands shown for Windows / PowerShell (dir symlinks need Developer Mode, no elevation). `<worktree-path>` and `<type>/<name>` are yours to choose.\n\n```powershell\n$main = git rev-parse --show-toplevel                 # capture main checkout root\ngit worktree add <worktree-path> -b <type>/<name>\ncd <worktree-path>\n\n# Reuse branch-independent downloads (symlink back to main)\nNew-Item -ItemType SymbolicLink backend/tauri/sidecar   -Target \"$main/backend/tauri/sidecar\"\nNew-Item -ItemType SymbolicLink backend/tauri/resources -Target \"$main/backend/tauri/resources\"\n\npnpm install\npnpm -F interface build                               # -> frontend/interface/dist (gitignored)\n\n# Satisfy tauri-build's frontendDist check — pick one:\nNew-Item -ItemType Directory -Force backend/tauri/tmp/dist | Out-Null            # A) Rust-only placeholder\nSet-Content backend/tauri/tmp/dist/index.html '<!doctype html><title>dev</title>'\n# pnpm web:build                                      # B) real UI (replaces tmp/dist)\n```\n\n### Remove a worktree\n\n`git worktree remove` on Windows can fail with `Filename too long` because per-worktree `node_modules` / `target` hold paths over MAX_PATH. Force-delete with the extended-length prefix, then reconcile git:\n\n```powershell\nRemove-Item -LiteralPath \"\\\\?\\<absolute-worktree-path>\" -Recurse -Force\ngit worktree prune\ngit worktree list\n```\n\nRemoval reclaims only the worktree's own files and its symlinks (pointers back to main) — it never touches the main checkout's real `sidecar/` / `resources/`.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, clarifying questions come before implementation rather than after mistakes, and new code moves away from global singletons toward injected actor/pure-service composition.\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nThis repository is migrating away from `::global()` singletons and Tauri-coupled services toward explicit dependency injection, actor-owned state, and pure domain services.\n\nBehavioral guidelines reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 0. Synchronization Policy\n\nKeep `CLAUDE.md` and `AGENTS.md` synchronized as much as possible.\n\n- When changing an architectural rule in one file, mirror it in the other file.\n- Differences should be limited to tool-specific wording, if any.\n- Prefer the same section order, same terminology, and same examples.\n- Do not create a Claude-only or agent-only exception unless the tool truly requires it.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\nFor small, obvious tasks, do this briefly. For architecture, migration, or cross-module work, be explicit.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\nThis rule does not override the architectural migration direction. Do not use `::global()` or hidden mutable process state merely because it is fewer lines.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\nFor actor/DI migration work, the allowed scope is the smallest call path needed to migrate the touched service or API without leaving a hidden compatibility layer behind.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" -> \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" -> \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" -> \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```text\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## 5. Target Architecture\n\nThe target architecture is:\n\n```text\nTauri commands / UI adapters\n    -> NyanpasuClient\n        -> typed actor clients\n        -> pure services\n        -> adapter traits\n            -> concrete Tauri / OS / filesystem / network implementations\n```\n\nUse these terms consistently:\n\n- **Dependency Injection / Pure DI**: dependencies are passed explicitly through constructors, builders, function arguments, or actor startup arguments. Do not look them up through globals.\n- **Composition Root**: the bootstrap / supervisor location that builds the full object graph and actor graph.\n- **Ports and Adapters**: core/application code depends on traits; Tauri, filesystem, OS, network, and process implementations live behind adapters.\n- **Actor service**: a ractor actor that owns mutable state, serializes commands, manages long-running resources, or supervises background work.\n- **Pure service**: a stateless or short-lived service that performs deterministic computation, validation, conversion, config generation, serialization, or patch application without IPC or background lifecycle.\n- **Adapter / port**: a narrow trait and concrete boundary implementation for infrastructure such as Tauri, filesystem, OS APIs, process spawning, HTTP, logging sinks, or storage.\n\n`NyanpasuClient` is the application facade. The application bootstrap / supervisor is the composition root. It constructs concrete services, spawns actors, wires dependencies, and returns a ready-to-use `NyanpasuClient`.\n\n## 6. ractor Primer for Agents\n\nIn this repository, `ractor` means the Rust `ractor` crate. It is an in-process actor framework used for long-lived services that own state and communicate through typed messages. It is not Tauri IPC and not Ruby Ractor.\n\nUse this mental model:\n\n```text\nActor = private mutable state + typed message enum + sequential message handling + lifecycle hooks\n```\n\nCore concepts:\n\n- `Actor`: the implementation trait. An actor defines its `Msg`, `State`, `Arguments`, and lifecycle/message-handling methods.\n- `ActorRef<Msg>`: a typed address used to send messages to an actor. Hide raw `ActorRef` values behind typed clients such as `StateClient` or `CoreClient`.\n- Message enum: the actor's domain protocol. Prefer explicit messages such as `PatchAppConfig`, `RestartCore`, or `SelectProxy` over generic commands.\n- `RpcReplyPort<T>`: the usual request/reply mechanism for queries and fallible operations that must return a value.\n- Fire-and-forget messages: use only for notifications, invalidations, events, or best-effort work where the caller does not need a result.\n- Startup arguments: the actor's dependency injection boundary. Pass dependencies when spawning the actor; do not fetch them from globals in actor code.\n\nProject rules:\n\n- Use actors for services with long-lived mutable state, serialized commands, background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, downloads, or child supervision.\n- Do not use actors for deterministic computation. Use pure services for validation, schema conversion, patch application, runtime config building, serialization, and merge/enhance logic when it can be deterministic.\n- Put infrastructure access behind adapter traits. Inject Tauri, OS, filesystem, network, process, and logging adapters into the actor or pure service that needs them.\n- The composition root spawns actors, wires dependencies, and returns `NyanpasuClient`. Do not use a ractor registry, actor name lookup, or raw `ActorRef` map as a replacement for dependency injection.\n- `NyanpasuClient` and typed actor clients should expose ordinary async Rust methods. Most callers should not use ractor APIs directly.\n- Prefer finite timeouts for cross-actor request/reply calls when the caller can report failure or degraded state.\n- Avoid synchronous cross-actor cycles such as `StateActor -> CoreActor -> StateActor`.\n\nIf a mature ractor actor client already exists for a capability, use it instead of adding a new global singleton, raw channel loop, or direct Tauri-coupled service call.\n\n## 7. Mandatory Architecture Rules\n\n### Do not add new global service singletons\n\nDo not introduce new service accessors such as:\n\n```rust\nService::global()\nget_global_service()\nstatic SERVICE: OnceCell<Service>\nstatic SERVICE: OnceLock<Service>\nstatic SERVICE: Lazy<Service>\n```\n\nExceptions are allowed only for immutable constants, static lookup tables, feature flags, or values that are truly process-wide and have no lifecycle, no mutable state, and no dependency graph.\n\nIf an existing `::global()` service must still be used during migration, isolate it at the edge of a migration step and add an explicit comment:\n\n```rust\n// TODO(actor-migration): temporary bridge to the legacy global service.\n// Reason: <why full migration is blocked>.\n// Remove when: <service-name> is injected through NyanpasuClient.\n```\n\n### Prefer explicit construction\n\nServices must be constructed through one of these forms:\n\n```rust\nService::new(dependency_a, dependency_b)\nServiceBuilder::default().with_dependency(...).build()\nAppSupervisor::start(args).await\n```\n\nDependencies should be visible in struct fields, constructor parameters, builder parameters, function parameters, or actor startup arguments. Hidden dependencies are not allowed.\n\n### Keep `NyanpasuClient` as a facade, not a service locator\n\n`NyanpasuClient` may expose stable application APIs, for example:\n\n```rust\nclient.get_app_config().await?;\nclient.patch_app_config(patch).await?;\nclient.get_profiles().await?;\nclient.restart_core().await?;\nclient.select_proxy(group, name).await?;\n```\n\nIt must not expose arbitrary internal lookup APIs such as:\n\n```rust\nclient.get_any_service::<T>()\nclient.resolve::<T>()\nclient.resolve(\"service-name\")\nclient.get_service(\"name\")\nclient.actor_registry()\nclient.get_actor_ref(\"state\")\n```\n\nInternally, `NyanpasuClient` may hold typed clients such as `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient`, and pure services or adapter trait objects. Callers should not depend on ractor `ActorRef` directly unless they are part of the actor layer itself.\n\n### Keep Tauri at the boundary\n\nBusiness logic must not depend directly on Tauri types such as `AppHandle`, `Window`, `Manager`, tray handles, global command state, or Tauri event emitters. Use adapter traits instead.\n\n## 8. Service Classification\n\nBefore adding or migrating a service, classify it as an actor service, pure service, or adapter/port.\n\n### Use an actor service when the service:\n\n- owns long-lived mutable state;\n- must serialize commands to avoid races;\n- manages background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, or downloads;\n- supervises child tasks or child actors;\n- needs request/reply or fire-and-forget messaging;\n- coordinates side effects after state commits.\n\nExpected examples:\n\n- app/config state;\n- core process lifecycle;\n- system proxy state;\n- hotkey registration;\n- proxy cache and subscriptions;\n- updater downloads;\n- websocket connection managers;\n- server lifecycle.\n\nActor implementation rules:\n\n- Use a typed message enum.\n- Keep owned mutable state inside the actor state.\n- Use typed actor client wrappers for public calls.\n- Do not expose raw `ActorRef` outside actor/application internals.\n- Use request/reply for fallible operations and queries.\n- Use fire-and-forget only for events, notifications, or best-effort work.\n- Avoid cross-actor synchronous cycles.\n- Prefer finite timeouts for cross-actor RPCs where a caller can recover or report degradation.\n- Actor startup arguments must contain all dependencies required to build the actor state.\n- Do not share actor-owned mutable state with `Arc<Mutex<_>>` or `Arc<RwLock<_>>` unless it is a narrowly scoped implementation detail with a clear comment.\n\n### Use a pure service when the service:\n\n- performs deterministic computation;\n- validates input;\n- converts schemas;\n- applies patches to owned data passed as parameters;\n- builds runtime configuration from snapshots;\n- serializes or deserializes data without owning long-lived state;\n- has no background task and no independent lifecycle.\n\nExpected examples:\n\n- config validation;\n- patch application;\n- profile ordering;\n- runtime config building;\n- legacy schema conversion;\n- serialization helpers;\n- merge/enhance logic when it can be made deterministic.\n\nPure service rules:\n\n- No global state.\n- No background tasks.\n- No hidden filesystem/network/Tauri access.\n- All inputs must be explicit parameters.\n- Return values or domain errors instead of mutating external state.\n\n### Use an adapter / port when the service touches infrastructure:\n\n- Tauri events, windows, tray, dialogs, clipboard;\n- filesystem and app directories;\n- OS proxy APIs;\n- global shortcuts;\n- HTTP clients;\n- child process spawning;\n- logging sinks;\n- persistent storage backends.\n\nAdapter rules:\n\n- Core/application code depends on traits.\n- Concrete adapters live at the boundary crate/module.\n- Keep adapter traits narrow and task-oriented.\n- Prefer mockable traits for tests.\n- Prefer traits owned by the consuming crate/module when that improves boundary clarity.\n\n## 9. When You Touch Legacy Global Code\n\nIf you see patterns such as:\n\n```rust\nConfig::global()\nConfig::verge()\nConfig::clash()\nConfig::profiles()\nConfig::runtime()\nCoreManager::global()\nSysopt::global()\nHotkey::global()\nLogger::global()\nHandle::global()\nProxiesGuard::global()\nUpdaterManager::global()\nWindowManager::global()\nconsts::app_handle()\n```\n\nprefer replacing the call path with one of:\n\n```rust\nclient.some_domain_operation(...).await?;\nstate_client.some_state_operation(...).await?;\ncore_client.some_core_operation(...).await?;\nsystem_proxy_client.some_system_operation(...).await?;\nservice.method(...)?;\n```\n\nDo not add a new wrapper that simply hides the global unless full migration is blocked. If blocked, document it:\n\n```rust\n// TODO(actor-migration): temporary bridge to <legacy global>.\n// Reason: <specific blocker>.\n// Remove when: <specific migration step>.\n```\n\n## 10. State and Configuration Migration\n\nConfiguration must be migrated before dependent services whenever possible.\n\nPreferred direction:\n\n1. Move state ownership into `StateActor` or a state manager owned by `StateActor`.\n2. Keep schema and patch operations in pure services or domain types.\n3. Generate runtime config from snapshots rather than mutating runtime globals.\n4. Commit state first, then trigger side effects through actor messages.\n5. Report post-commit side-effect failures as degraded results instead of silently rolling back persisted state.\n\nAvoid preserving old global configuration APIs. Prefer a migratable breaking change that updates callers to the new injected client/service API.\n\n## 11. Migration Policy: Prefer Migratable Breaking Changes\n\nWhen refactoring or migrating services:\n\n- Prefer fully migrating callers to the new injected/actor/pure-service API.\n- Prefer migratable breaking changes over compatibility layers.\n- Do not add a compatibility layer simply to avoid updating call sites.\n- Add a compatibility or migration layer only when a full migration is not currently possible due to cyclic dependencies, public API constraints, external plugin behavior, large cross-cutting risk, platform limitation, or staged release requirements.\n- Every compatibility layer must be explicitly marked with `TODO(actor-migration)` or `FIXME(actor-migration)` and must explain the reason and removal condition.\n- New code must not call compatibility APIs unless the call site is itself part of a documented migration step.\n\nDo this:\n\n```rust\nclient.patch_app_config(patch).await?;\n```\n\nDo not do this unless blocked:\n\n```rust\nLegacyConfigCompat::patch_verge(patch).await?;\n```\n\nRequired comment format:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nor:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 12. Tauri Command Rules\n\nTauri commands should be thin adapters. They should:\n\n- parse request DTOs;\n- call `NyanpasuClient`;\n- map domain errors into command errors;\n- never perform business orchestration directly;\n- never read or mutate config through globals;\n- never spawn core/service background tasks directly.\n\nAllowed shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(\n    client: tauri::State<'_, NyanpasuClient>,\n    patch: NyanpasuAppConfigPatch,\n) -> Result<()> {\n    client.patch_app_config(patch).await?;\n    Ok(())\n}\n```\n\nAvoid shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(patch: IVerge) -> Result<()> {\n    Config::verge().draft().patch_config(patch)?;\n    CoreManager::global().update_config().await?;\n    Config::verge().apply();\n    Ok(())\n}\n```\n\n## 13. Testing and Mocking\n\n- Prefer testing pure services directly with plain values.\n- For infrastructure dependencies, define narrow traits and inject them.\n- Traits that are intended to be mocked should be compatible with `mockall` / `automock` where practical.\n- Keep mock-only APIs behind `#[cfg(test)]` or test-support modules.\n- Do not use global test fixtures for application services. Construct a test `NyanpasuClient` or test-specific service graph.\n- Actor tests should spawn the actor with fake adapters and send typed messages through its typed client.\n- Avoid sleeping in actor tests. Prefer explicit acknowledgements, request/reply messages, or test hooks.\n\nExample mockable trait:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait UiEventSink: Send + Sync + 'static {\n    fn emit_state_changed(&self, event: StateChanged) -> anyhow::Result<()>;\n}\n```\n\nAnother acceptable trait shape:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait ConfigStore: Send + Sync + 'static {\n    fn load(&self) -> anyhow::Result<Vec<u8>>;\n    fn save(&self, bytes: &[u8]) -> anyhow::Result<()>;\n}\n```\n\n## 14. Naming Guidelines\n\nUse names that reveal the role:\n\n- `StateActor`, `CoreActor`, `SystemProxyActor`, `HotkeyActor`, `ProxiesActor`, `UpdaterActor` for actors.\n- `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient` for typed actor clients.\n- `RuntimeBuilder`, `ProfileMerger`, `ConfigMigrator`, `PatchValidator` for pure services.\n- `TauriUiEventSink`, `FsConfigStore`, `OsProxyBackend`, `ProcessRunner` for adapters.\n- `AppSupervisor` or `NyanpasuBootstrap` for the composition root.\n\n## 15. Comment Requirements\n\nUse comments only where they clarify migration state, invariants, or actor lifecycle assumptions.\n\nRequired compatibility-layer comment:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nRequired temporary legacy behavior comment:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 16. Final Review Checklist\n\nBefore finishing a change, check:\n\n- assumptions were stated when relevant;\n- success criteria were verified;\n- every changed line traces to the request;\n- no new global singleton service was added;\n- no new mutable static service state was added;\n- dependencies are explicit;\n- service classification is clear;\n- actor state is not leaked through shared locks;\n- Tauri is isolated behind adapters;\n- compatibility layers are exceptional and documented;\n- tests use injection, fakes, mocks, or pure values;\n- `NyanpasuClient` remains a facade, not a service locator.\n\n## 17. Worktree Setup and Resource Reuse\n\nFeature/migration work runs in isolated git worktrees. The worktree location is the developer's choice (any path outside the repo tree); this section only fixes the reuse policy, not where worktrees live. Worktrees share the main `.git`. The rule: reuse expensive **branch-independent** assets from the main checkout via symlink, and regenerate everything **branch-dependent** per worktree.\n\n### Reuse policy\n\n| Path (repo-relative)       | Approx size  | Policy                          | Reason                                                                                                                                    |\n| -------------------------- | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `backend/tauri/sidecar/`   | ~213M        | **Symlink → main**              | gitignored downloaded cores (mihomo / clash-rs / clash / nyanpasu-service); branch-independent; re-fetch via `pnpm prepare:check` is slow |\n| `backend/tauri/resources/` | ~21M         | **Symlink → main**              | gitignored static assets (`geoip.dat`, `geosite.dat`, `Country.mmdb`, `wintun.dll`, service exes); branch-independent                     |\n| `node_modules/`            | ~1.5G        | **Independent `pnpm install`**  | pnpm global store already hardlink-dedupes; sharing risks concurrent lock conflicts                                                       |\n| `backend/target/`          | ~50G         | **Independent — never symlink** | sharing causes Cargo incremental-fingerprint churn + concurrent build-lock waits across diverged source trees                             |\n| `backend/tauri/tmp/dist/`  | build output | **Independent — never symlink** | branch-dependent frontend build; `emptyOutDir: true` means one worktree's `web:build` wipes the shared dir                                |\n\nOnly `sidecar/` and `resources/` are symlink candidates.\n\n### Gitignored build prerequisites a fresh worktree lacks\n\n- **`frontend/interface/dist`** — `@nyanpasu/interface` (`main` → `./dist/index.js`) is consumed by `@nyanpasu/nyanpasu`. Produce with `pnpm -F interface build`.\n- **`backend/tauri/tmp/dist`** — `backend/tauri/build.rs` calls `tauri_build::build()`, which validates `frontendDist: ./tmp/dist` **at compile time**. When missing, every `cargo build` / `clippy` / `cargo test --all-features` / rust-analyzer run on the tauri crate fails. Resolve one of:\n  - Rust-only worktree → drop a placeholder (cheapest, no vite build).\n  - Runnable UI → `pnpm web:build` (build `interface` first; it clears and refills `tmp/dist`).\n\n`backend/tauri/tmp/git-info.json` is optional (`build.rs` guards it with `exists()`); run `pnpm generate:git-info` only if accurate commit metadata must be baked in.\n\n### Create a worktree\n\nCommands shown for Windows / PowerShell (dir symlinks need Developer Mode, no elevation). `<worktree-path>` and `<type>/<name>` are yours to choose.\n\n```powershell\n$main = git rev-parse --show-toplevel                 # capture main checkout root\ngit worktree add <worktree-path> -b <type>/<name>\ncd <worktree-path>\n\n# Reuse branch-independent downloads (symlink back to main)\nNew-Item -ItemType SymbolicLink backend/tauri/sidecar   -Target \"$main/backend/tauri/sidecar\"\nNew-Item -ItemType SymbolicLink backend/tauri/resources -Target \"$main/backend/tauri/resources\"\n\npnpm install\npnpm -F interface build                               # -> frontend/interface/dist (gitignored)\n\n# Satisfy tauri-build's frontendDist check — pick one:\nNew-Item -ItemType Directory -Force backend/tauri/tmp/dist | Out-Null            # A) Rust-only placeholder\nSet-Content backend/tauri/tmp/dist/index.html '<!doctype html><title>dev</title>'\n# pnpm web:build                                      # B) real UI (replaces tmp/dist)\n```\n\n### Remove a worktree\n\n`git worktree remove` on Windows can fail with `Filename too long` because per-worktree `node_modules` / `target` hold paths over MAX_PATH. Force-delete with the extended-length prefix, then reconcile git:\n\n```powershell\nRemove-Item -LiteralPath \"\\\\?\\<absolute-worktree-path>\" -Recurse -Force\ngit worktree prune\ngit worktree list\n```\n\nRemoval reclaims only the worktree's own files and its symlinks (pointers back to main) — it never touches the main checkout's real `sidecar/` / `resources/`.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, clarifying questions come before implementation rather than after mistakes, and new code moves away from global singletons toward injected actor/pure-service composition.\n","CLAUDE.md":"# CLAUDE.md\n\nThis repository is migrating away from `::global()` singletons and Tauri-coupled services toward explicit dependency injection, actor-owned state, and pure domain services.\n\nBehavioral guidelines reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 0. Synchronization Policy\n\nKeep `CLAUDE.md` and `AGENTS.md` synchronized as much as possible.\n\n- When changing an architectural rule in one file, mirror it in the other file.\n- Differences should be limited to tool-specific wording, if any.\n- Prefer the same section order, same terminology, and same examples.\n- Do not create a Claude-only or agent-only exception unless the tool truly requires it.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\nFor small, obvious tasks, do this briefly. For architecture, migration, or cross-module work, be explicit.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\nThis rule does not override the architectural migration direction. Do not use `::global()` or hidden mutable process state merely because it is fewer lines.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\nFor actor/DI migration work, the allowed scope is the smallest call path needed to migrate the touched service or API without leaving a hidden compatibility layer behind.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" -> \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" -> \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" -> \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```text\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## 5. Target Architecture\n\nThe target architecture is:\n\n```text\nTauri commands / UI adapters\n    -> NyanpasuClient\n        -> typed actor clients\n        -> pure services\n        -> adapter traits\n            -> concrete Tauri / OS / filesystem / network implementations\n```\n\nUse these terms consistently:\n\n- **Dependency Injection / Pure DI**: dependencies are passed explicitly through constructors, builders, function arguments, or actor startup arguments. Do not look them up through globals.\n- **Composition Root**: the bootstrap / supervisor location that builds the full object graph and actor graph.\n- **Ports and Adapters**: core/application code depends on traits; Tauri, filesystem, OS, network, and process implementations live behind adapters.\n- **Actor service**: a ractor actor that owns mutable state, serializes commands, manages long-running resources, or supervises background work.\n- **Pure service**: a stateless or short-lived service that performs deterministic computation, validation, conversion, config generation, serialization, or patch application without IPC or background lifecycle.\n- **Adapter / port**: a narrow trait and concrete boundary implementation for infrastructure such as Tauri, filesystem, OS APIs, process spawning, HTTP, logging sinks, or storage.\n\n`NyanpasuClient` is the application facade. The application bootstrap / supervisor is the composition root. It constructs concrete services, spawns actors, wires dependencies, and returns a ready-to-use `NyanpasuClient`.\n\n## 6. ractor Primer for Agents\n\nIn this repository, `ractor` means the Rust `ractor` crate. It is an in-process actor framework used for long-lived services that own state and communicate through typed messages. It is not Tauri IPC and not Ruby Ractor.\n\nUse this mental model:\n\n```text\nActor = private mutable state + typed message enum + sequential message handling + lifecycle hooks\n```\n\nCore concepts:\n\n- `Actor`: the implementation trait. An actor defines its `Msg`, `State`, `Arguments`, and lifecycle/message-handling methods.\n- `ActorRef<Msg>`: a typed address used to send messages to an actor. Hide raw `ActorRef` values behind typed clients such as `StateClient` or `CoreClient`.\n- Message enum: the actor's domain protocol. Prefer explicit messages such as `PatchAppConfig`, `RestartCore`, or `SelectProxy` over generic commands.\n- `RpcReplyPort<T>`: the usual request/reply mechanism for queries and fallible operations that must return a value.\n- Fire-and-forget messages: use only for notifications, invalidations, events, or best-effort work where the caller does not need a result.\n- Startup arguments: the actor's dependency injection boundary. Pass dependencies when spawning the actor; do not fetch them from globals in actor code.\n\nProject rules:\n\n- Use actors for services with long-lived mutable state, serialized commands, background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, downloads, or child supervision.\n- Do not use actors for deterministic computation. Use pure services for validation, schema conversion, patch application, runtime config building, serialization, and merge/enhance logic when it can be deterministic.\n- Put infrastructure access behind adapter traits. Inject Tauri, OS, filesystem, network, process, and logging adapters into the actor or pure service that needs them.\n- The composition root spawns actors, wires dependencies, and returns `NyanpasuClient`. Do not use a ractor registry, actor name lookup, or raw `ActorRef` map as a replacement for dependency injection.\n- `NyanpasuClient` and typed actor clients should expose ordinary async Rust methods. Most callers should not use ractor APIs directly.\n- Prefer finite timeouts for cross-actor request/reply calls when the caller can report failure or degraded state.\n- Avoid synchronous cross-actor cycles such as `StateActor -> CoreActor -> StateActor`.\n\nIf a mature ractor actor client already exists for a capability, use it instead of adding a new global singleton, raw channel loop, or direct Tauri-coupled service call.\n\n## 7. Mandatory Architecture Rules\n\n### Do not add new global service singletons\n\nDo not introduce new service accessors such as:\n\n```rust\nService::global()\nget_global_service()\nstatic SERVICE: OnceCell<Service>\nstatic SERVICE: OnceLock<Service>\nstatic SERVICE: Lazy<Service>\n```\n\nExceptions are allowed only for immutable constants, static lookup tables, feature flags, or values that are truly process-wide and have no lifecycle, no mutable state, and no dependency graph.\n\nIf an existing `::global()` service must still be used during migration, isolate it at the edge of a migration step and add an explicit comment:\n\n```rust\n// TODO(actor-migration): temporary bridge to the legacy global service.\n// Reason: <why full migration is blocked>.\n// Remove when: <service-name> is injected through NyanpasuClient.\n```\n\n### Prefer explicit construction\n\nServices must be constructed through one of these forms:\n\n```rust\nService::new(dependency_a, dependency_b)\nServiceBuilder::default().with_dependency(...).build()\nAppSupervisor::start(args).await\n```\n\nDependencies should be visible in struct fields, constructor parameters, builder parameters, function parameters, or actor startup arguments. Hidden dependencies are not allowed.\n\n### Keep `NyanpasuClient` as a facade, not a service locator\n\n`NyanpasuClient` may expose stable application APIs, for example:\n\n```rust\nclient.get_app_config().await?;\nclient.patch_app_config(patch).await?;\nclient.get_profiles().await?;\nclient.restart_core().await?;\nclient.select_proxy(group, name).await?;\n```\n\nIt must not expose arbitrary internal lookup APIs such as:\n\n```rust\nclient.get_any_service::<T>()\nclient.resolve::<T>()\nclient.resolve(\"service-name\")\nclient.get_service(\"name\")\nclient.actor_registry()\nclient.get_actor_ref(\"state\")\n```\n\nInternally, `NyanpasuClient` may hold typed clients such as `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient`, and pure services or adapter trait objects. Callers should not depend on ractor `ActorRef` directly unless they are part of the actor layer itself.\n\n### Keep Tauri at the boundary\n\nBusiness logic must not depend directly on Tauri types such as `AppHandle`, `Window`, `Manager`, tray handles, global command state, or Tauri event emitters. Use adapter traits instead.\n\n## 8. Service Classification\n\nBefore adding or migrating a service, classify it as an actor service, pure service, or adapter/port.\n\n### Use an actor service when the service:\n\n- owns long-lived mutable state;\n- must serialize commands to avoid races;\n- manages background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, or downloads;\n- supervises child tasks or child actors;\n- needs request/reply or fire-and-forget messaging;\n- coordinates side effects after state commits.\n\nExpected examples:\n\n- app/config state;\n- core process lifecycle;\n- system proxy state;\n- hotkey registration;\n- proxy cache and subscriptions;\n- updater downloads;\n- websocket connection managers;\n- server lifecycle.\n\nActor implementation rules:\n\n- Use a typed message enum.\n- Keep owned mutable state inside the actor state.\n- Use typed actor client wrappers for public calls.\n- Do not expose raw `ActorRef` outside actor/application internals.\n- Use request/reply for fallible operations and queries.\n- Use fire-and-forget only for events, notifications, or best-effort work.\n- Avoid cross-actor synchronous cycles.\n- Prefer finite timeouts for cross-actor RPCs where a caller can recover or report degradation.\n- Actor startup arguments must contain all dependencies required to build the actor state.\n- Do not share actor-owned mutable state with `Arc<Mutex<_>>` or `Arc<RwLock<_>>` unless it is a narrowly scoped implementation detail with a clear comment.\n\n### Use a pure service when the service:\n\n- performs deterministic computation;\n- validates input;\n- converts schemas;\n- applies patches to owned data passed as parameters;\n- builds runtime configuration from snapshots;\n- serializes or deserializes data without owning long-lived state;\n- has no background task and no independent lifecycle.\n\nExpected examples:\n\n- config validation;\n- patch application;\n- profile ordering;\n- runtime config building;\n- legacy schema conversion;\n- serialization helpers;\n- merge/enhance logic when it can be made deterministic.\n\nPure service rules:\n\n- No global state.\n- No background tasks.\n- No hidden filesystem/network/Tauri access.\n- All inputs must be explicit parameters.\n- Return values or domain errors instead of mutating external state.\n\n### Use an adapter / port when the service touches infrastructure:\n\n- Tauri events, windows, tray, dialogs, clipboard;\n- filesystem and app directories;\n- OS proxy APIs;\n- global shortcuts;\n- HTTP clients;\n- child process spawning;\n- logging sinks;\n- persistent storage backends.\n\nAdapter rules:\n\n- Core/application code depends on traits.\n- Concrete adapters live at the boundary crate/module.\n- Keep adapter traits narrow and task-oriented.\n- Prefer mockable traits for tests.\n- Prefer traits owned by the consuming crate/module when that improves boundary clarity.\n\n## 9. When You Touch Legacy Global Code\n\nIf you see patterns such as:\n\n```rust\nConfig::global()\nConfig::verge()\nConfig::clash()\nConfig::profiles()\nConfig::runtime()\nCoreManager::global()\nSysopt::global()\nHotkey::global()\nLogger::global()\nHandle::global()\nProxiesGuard::global()\nUpdaterManager::global()\nWindowManager::global()\nconsts::app_handle()\n```\n\nprefer replacing the call path with one of:\n\n```rust\nclient.some_domain_operation(...).await?;\nstate_client.some_state_operation(...).await?;\ncore_client.some_core_operation(...).await?;\nsystem_proxy_client.some_system_operation(...).await?;\nservice.method(...)?;\n```\n\nDo not add a new wrapper that simply hides the global unless full migration is blocked. If blocked, document it:\n\n```rust\n// TODO(actor-migration): temporary bridge to <legacy global>.\n// Reason: <specific blocker>.\n// Remove when: <specific migration step>.\n```\n\n## 10. State and Configuration Migration\n\nConfiguration must be migrated before dependent services whenever possible.\n\nPreferred direction:\n\n1. Move state ownership into `StateActor` or a state manager owned by `StateActor`.\n2. Keep schema and patch operations in pure services or domain types.\n3. Generate runtime config from snapshots rather than mutating runtime globals.\n4. Commit state first, then trigger side effects through actor messages.\n5. Report post-commit side-effect failures as degraded results instead of silently rolling back persisted state.\n\nAvoid preserving old global configuration APIs. Prefer a migratable breaking change that updates callers to the new injected client/service API.\n\n## 11. Migration Policy: Prefer Migratable Breaking Changes\n\nWhen refactoring or migrating services:\n\n- Prefer fully migrating callers to the new injected/actor/pure-service API.\n- Prefer migratable breaking changes over compatibility layers.\n- Do not add a compatibility layer simply to avoid updating call sites.\n- Add a compatibility or migration layer only when a full migration is not currently possible due to cyclic dependencies, public API constraints, external plugin behavior, large cross-cutting risk, platform limitation, or staged release requirements.\n- Every compatibility layer must be explicitly marked with `TODO(actor-migration)` or `FIXME(actor-migration)` and must explain the reason and removal condition.\n- New code must not call compatibility APIs unless the call site is itself part of a documented migration step.\n\nDo this:\n\n```rust\nclient.patch_app_config(patch).await?;\n```\n\nDo not do this unless blocked:\n\n```rust\nLegacyConfigCompat::patch_verge(patch).await?;\n```\n\nRequired comment format:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nor:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 12. Tauri Command Rules\n\nTauri commands should be thin adapters. They should:\n\n- parse request DTOs;\n- call `NyanpasuClient`;\n- map domain errors into command errors;\n- never perform business orchestration directly;\n- never read or mutate config through globals;\n- never spawn core/service background tasks directly.\n\nAllowed shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(\n    client: tauri::State<'_, NyanpasuClient>,\n    patch: NyanpasuAppConfigPatch,\n) -> Result<()> {\n    client.patch_app_config(patch).await?;\n    Ok(())\n}\n```\n\nAvoid shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(patch: IVerge) -> Result<()> {\n    Config::verge().draft().patch_config(patch)?;\n    CoreManager::global().update_config().await?;\n    Config::verge().apply();\n    Ok(())\n}\n```\n\n## 13. Testing and Mocking\n\n- Prefer testing pure services directly with plain values.\n- For infrastructure dependencies, define narrow traits and inject them.\n- Traits that are intended to be mocked should be compatible with `mockall` / `automock` where practical.\n- Keep mock-only APIs behind `#[cfg(test)]` or test-support modules.\n- Do not use global test fixtures for application services. Construct a test `NyanpasuClient` or test-specific service graph.\n- Actor tests should spawn the actor with fake adapters and send typed messages through its typed client.\n- Avoid sleeping in actor tests. Prefer explicit acknowledgements, request/reply messages, or test hooks.\n\nExample mockable trait:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait UiEventSink: Send + Sync + 'static {\n    fn emit_state_changed(&self, event: StateChanged) -> anyhow::Result<()>;\n}\n```\n\nAnother acceptable trait shape:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait ConfigStore: Send + Sync + 'static {\n    fn load(&self) -> anyhow::Result<Vec<u8>>;\n    fn save(&self, bytes: &[u8]) -> anyhow::Result<()>;\n}\n```\n\n## 14. Naming Guidelines\n\nUse names that reveal the role:\n\n- `StateActor`, `CoreActor`, `SystemProxyActor`, `HotkeyActor`, `ProxiesActor`, `UpdaterActor` for actors.\n- `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient` for typed actor clients.\n- `RuntimeBuilder`, `ProfileMerger`, `ConfigMigrator`, `PatchValidator` for pure services.\n- `TauriUiEventSink`, `FsConfigStore`, `OsProxyBackend`, `ProcessRunner` for adapters.\n- `AppSupervisor` or `NyanpasuBootstrap` for the composition root.\n\n## 15. Comment Requirements\n\nUse comments only where they clarify migration state, invariants, or actor lifecycle assumptions.\n\nRequired compatibility-layer comment:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nRequired temporary legacy behavior comment:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 16. Final Review Checklist\n\nBefore finishing a change, check:\n\n- assumptions were stated when relevant;\n- success criteria were verified;\n- every changed line traces to the request;\n- no new global singleton service was added;\n- no new mutable static service state was added;\n- dependencies are explicit;\n- service classification is clear;\n- actor state is not leaked through shared locks;\n- Tauri is isolated behind adapters;\n- compatibility layers are exceptional and documented;\n- tests use injection, fakes, mocks, or pure values;\n- `NyanpasuClient` remains a facade, not a service locator.\n\n## 17. Worktree Setup and Resource Reuse\n\nFeature/migration work runs in isolated git worktrees. The worktree location is the developer's choice (any path outside the repo tree); this section only fixes the reuse policy, not where worktrees live. Worktrees share the main `.git`. The rule: reuse expensive **branch-independent** assets from the main checkout via symlink, and regenerate everything **branch-dependent** per worktree.\n\n### Reuse policy\n\n| Path (repo-relative)       | Approx size  | Policy                          | Reason                                                                                                                                    |\n| -------------------------- | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `backend/tauri/sidecar/`   | ~213M        | **Symlink → main**              | gitignored downloaded cores (mihomo / clash-rs / clash / nyanpasu-service); branch-independent; re-fetch via `pnpm prepare:check` is slow |\n| `backend/tauri/resources/` | ~21M         | **Symlink → main**              | gitignored static assets (`geoip.dat`, `geosite.dat`, `Country.mmdb`, `wintun.dll`, service exes); branch-independent                     |\n| `node_modules/`            | ~1.5G        | **Independent `pnpm install`**  | pnpm global store already hardlink-dedupes; sharing risks concurrent lock conflicts                                                       |\n| `backend/target/`          | ~50G         | **Independent — never symlink** | sharing causes Cargo incremental-fingerprint churn + concurrent build-lock waits across diverged source trees                             |\n| `backend/tauri/tmp/dist/`  | build output | **Independent — never symlink** | branch-dependent frontend build; `emptyOutDir: true` means one worktree's `web:build` wipes the shared dir                                |\n\nOnly `sidecar/` and `resources/` are symlink candidates.\n\n### Gitignored build prerequisites a fresh worktree lacks\n\n- **`frontend/interface/dist`** — `@nyanpasu/interface` (`main` → `./dist/index.js`) is consumed by `@nyanpasu/nyanpasu`. Produce with `pnpm -F interface build`.\n- **`backend/tauri/tmp/dist`** — `backend/tauri/build.rs` calls `tauri_build::build()`, which validates `frontendDist: ./tmp/dist` **at compile time**. When missing, every `cargo build` / `clippy` / `cargo test --all-features` / rust-analyzer run on the tauri crate fails. Resolve one of:\n  - Rust-only worktree → drop a placeholder (cheapest, no vite build).\n  - Runnable UI → `pnpm web:build` (build `interface` first; it clears and refills `tmp/dist`).\n\n`backend/tauri/tmp/git-info.json` is optional (`build.rs` guards it with `exists()`); run `pnpm generate:git-info` only if accurate commit metadata must be baked in.\n\n### Create a worktree\n\nCommands shown for Windows / PowerShell (dir symlinks need Developer Mode, no elevation). `<worktree-path>` and `<type>/<name>` are yours to choose.\n\n```powershell\n$main = git rev-parse --show-toplevel                 # capture main checkout root\ngit worktree add <worktree-path> -b <type>/<name>\ncd <worktree-path>\n\n# Reuse branch-independent downloads (symlink back to main)\nNew-Item -ItemType SymbolicLink backend/tauri/sidecar   -Target \"$main/backend/tauri/sidecar\"\nNew-Item -ItemType SymbolicLink backend/tauri/resources -Target \"$main/backend/tauri/resources\"\n\npnpm install\npnpm -F interface build                               # -> frontend/interface/dist (gitignored)\n\n# Satisfy tauri-build's frontendDist check — pick one:\nNew-Item -ItemType Directory -Force backend/tauri/tmp/dist | Out-Null            # A) Rust-only placeholder\nSet-Content backend/tauri/tmp/dist/index.html '<!doctype html><title>dev</title>'\n# pnpm web:build                                      # B) real UI (replaces tmp/dist)\n```\n\n### Remove a worktree\n\n`git worktree remove` on Windows can fail with `Filename too long` because per-worktree `node_modules` / `target` hold paths over MAX_PATH. Force-delete with the extended-length prefix, then reconcile git:\n\n```powershell\nRemove-Item -LiteralPath \"\\\\?\\<absolute-worktree-path>\" -Recurse -Force\ngit worktree prune\ngit worktree list\n```\n\nRemoval reclaims only the worktree's own files and its symlinks (pointers back to main) — it never touches the main checkout's real `sidecar/` / `resources/`.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, clarifying questions come before implementation rather than after mistakes, and new code moves away from global singletons toward injected actor/pure-service composition.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nThis repository is migrating away from `::global()` singletons and Tauri-coupled services toward explicit dependency injection, actor-owned state, and pure domain services.\n\nBehavioral guidelines reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 0. Synchronization Policy\n\nKeep `CLAUDE.md` and `AGENTS.md` synchronized as much as possible.\n\n- When changing an architectural rule in one file, mirror it in the other file.\n- Differences should be limited to tool-specific wording, if any.\n- Prefer the same section order, same terminology, and same examples.\n- Do not create a Claude-only or agent-only exception unless the tool truly requires it.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\nFor small, obvious tasks, do this briefly. For architecture, migration, or cross-module work, be explicit.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\nThis rule does not override the architectural migration direction. Do not use `::global()` or hidden mutable process state merely because it is fewer lines.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\nFor actor/DI migration work, the allowed scope is the smallest call path needed to migrate the touched service or API without leaving a hidden compatibility layer behind.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" -> \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" -> \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" -> \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```text\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## 5. Target Architecture\n\nThe target architecture is:\n\n```text\nTauri commands / UI adapters\n    -> NyanpasuClient\n        -> typed actor clients\n        -> pure services\n        -> adapter traits\n            -> concrete Tauri / OS / filesystem / network implementations\n```\n\nUse these terms consistently:\n\n- **Dependency Injection / Pure DI**: dependencies are passed explicitly through constructors, builders, function arguments, or actor startup arguments. Do not look them up through globals.\n- **Composition Root**: the bootstrap / supervisor location that builds the full object graph and actor graph.\n- **Ports and Adapters**: core/application code depends on traits; Tauri, filesystem, OS, network, and process implementations live behind adapters.\n- **Actor service**: a ractor actor that owns mutable state, serializes commands, manages long-running resources, or supervises background work.\n- **Pure service**: a stateless or short-lived service that performs deterministic computation, validation, conversion, config generation, serialization, or patch application without IPC or background lifecycle.\n- **Adapter / port**: a narrow trait and concrete boundary implementation for infrastructure such as Tauri, filesystem, OS APIs, process spawning, HTTP, logging sinks, or storage.\n\n`NyanpasuClient` is the application facade. The application bootstrap / supervisor is the composition root. It constructs concrete services, spawns actors, wires dependencies, and returns a ready-to-use `NyanpasuClient`.\n\n## 6. ractor Primer for Agents\n\nIn this repository, `ractor` means the Rust `ractor` crate. It is an in-process actor framework used for long-lived services that own state and communicate through typed messages. It is not Tauri IPC and not Ruby Ractor.\n\nUse this mental model:\n\n```text\nActor = private mutable state + typed message enum + sequential message handling + lifecycle hooks\n```\n\nCore concepts:\n\n- `Actor`: the implementation trait. An actor defines its `Msg`, `State`, `Arguments`, and lifecycle/message-handling methods.\n- `ActorRef<Msg>`: a typed address used to send messages to an actor. Hide raw `ActorRef` values behind typed clients such as `StateClient` or `CoreClient`.\n- Message enum: the actor's domain protocol. Prefer explicit messages such as `PatchAppConfig`, `RestartCore`, or `SelectProxy` over generic commands.\n- `RpcReplyPort<T>`: the usual request/reply mechanism for queries and fallible operations that must return a value.\n- Fire-and-forget messages: use only for notifications, invalidations, events, or best-effort work where the caller does not need a result.\n- Startup arguments: the actor's dependency injection boundary. Pass dependencies when spawning the actor; do not fetch them from globals in actor code.\n\nProject rules:\n\n- Use actors for services with long-lived mutable state, serialized commands, background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, downloads, or child supervision.\n- Do not use actors for deterministic computation. Use pure services for validation, schema conversion, patch application, runtime config building, serialization, and merge/enhance logic when it can be deterministic.\n- Put infrastructure access behind adapter traits. Inject Tauri, OS, filesystem, network, process, and logging adapters into the actor or pure service that needs them.\n- The composition root spawns actors, wires dependencies, and returns `NyanpasuClient`. Do not use a ractor registry, actor name lookup, or raw `ActorRef` map as a replacement for dependency injection.\n- `NyanpasuClient` and typed actor clients should expose ordinary async Rust methods. Most callers should not use ractor APIs directly.\n- Prefer finite timeouts for cross-actor request/reply calls when the caller can report failure or degraded state.\n- Avoid synchronous cross-actor cycles such as `StateActor -> CoreActor -> StateActor`.\n\nIf a mature ractor actor client already exists for a capability, use it instead of adding a new global singleton, raw channel loop, or direct Tauri-coupled service call.\n\n## 7. Mandatory Architecture Rules\n\n### Do not add new global service singletons\n\nDo not introduce new service accessors such as:\n\n```rust\nService::global()\nget_global_service()\nstatic SERVICE: OnceCell<Service>\nstatic SERVICE: OnceLock<Service>\nstatic SERVICE: Lazy<Service>\n```\n\nExceptions are allowed only for immutable constants, static lookup tables, feature flags, or values that are truly process-wide and have no lifecycle, no mutable state, and no dependency graph.\n\nIf an existing `::global()` service must still be used during migration, isolate it at the edge of a migration step and add an explicit comment:\n\n```rust\n// TODO(actor-migration): temporary bridge to the legacy global service.\n// Reason: <why full migration is blocked>.\n// Remove when: <service-name> is injected through NyanpasuClient.\n```\n\n### Prefer explicit construction\n\nServices must be constructed through one of these forms:\n\n```rust\nService::new(dependency_a, dependency_b)\nServiceBuilder::default().with_dependency(...).build()\nAppSupervisor::start(args).await\n```\n\nDependencies should be visible in struct fields, constructor parameters, builder parameters, function parameters, or actor startup arguments. Hidden dependencies are not allowed.\n\n### Keep `NyanpasuClient` as a facade, not a service locator\n\n`NyanpasuClient` may expose stable application APIs, for example:\n\n```rust\nclient.get_app_config().await?;\nclient.patch_app_config(patch).await?;\nclient.get_profiles().await?;\nclient.restart_core().await?;\nclient.select_proxy(group, name).await?;\n```\n\nIt must not expose arbitrary internal lookup APIs such as:\n\n```rust\nclient.get_any_service::<T>()\nclient.resolve::<T>()\nclient.resolve(\"service-name\")\nclient.get_service(\"name\")\nclient.actor_registry()\nclient.get_actor_ref(\"state\")\n```\n\nInternally, `NyanpasuClient` may hold typed clients such as `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient`, and pure services or adapter trait objects. Callers should not depend on ractor `ActorRef` directly unless they are part of the actor layer itself.\n\n### Keep Tauri at the boundary\n\nBusiness logic must not depend directly on Tauri types such as `AppHandle`, `Window`, `Manager`, tray handles, global command state, or Tauri event emitters. Use adapter traits instead.\n\n## 8. Service Classification\n\nBefore adding or migrating a service, classify it as an actor service, pure service, or adapter/port.\n\n### Use an actor service when the service:\n\n- owns long-lived mutable state;\n- must serialize commands to avoid races;\n- manages background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, or downloads;\n- supervises child tasks or child actors;\n- needs request/reply or fire-and-forget messaging;\n- coordinates side effects after state commits.\n\nExpected examples:\n\n- app/config state;\n- core process lifecycle;\n- system proxy state;\n- hotkey registration;\n- proxy cache and subscriptions;\n- updater downloads;\n- websocket connection managers;\n- server lifecycle.\n\nActor implementation rules:\n\n- Use a typed message enum.\n- Keep owned mutable state inside the actor state.\n- Use typed actor client wrappers for public calls.\n- Do not expose raw `ActorRef` outside actor/application internals.\n- Use request/reply for fallible operations and queries.\n- Use fire-and-forget only for events, notifications, or best-effort work.\n- Avoid cross-actor synchronous cycles.\n- Prefer finite timeouts for cross-actor RPCs where a caller can recover or report degradation.\n- Actor startup arguments must contain all dependencies required to build the actor state.\n- Do not share actor-owned mutable state with `Arc<Mutex<_>>` or `Arc<RwLock<_>>` unless it is a narrowly scoped implementation detail with a clear comment.\n\n### Use a pure service when the service:\n\n- performs deterministic computation;\n- validates input;\n- converts schemas;\n- applies patches to owned data passed as parameters;\n- builds runtime configuration from snapshots;\n- serializes or deserializes data without owning long-lived state;\n- has no background task and no independent lifecycle.\n\nExpected examples:\n\n- config validation;\n- patch application;\n- profile ordering;\n- runtime config building;\n- legacy schema conversion;\n- serialization helpers;\n- merge/enhance logic when it can be made deterministic.\n\nPure service rules:\n\n- No global state.\n- No background tasks.\n- No hidden filesystem/network/Tauri access.\n- All inputs must be explicit parameters.\n- Return values or domain errors instead of mutating external state.\n\n### Use an adapter / port when the service touches infrastructure:\n\n- Tauri events, windows, tray, dialogs, clipboard;\n- filesystem and app directories;\n- OS proxy APIs;\n- global shortcuts;\n- HTTP clients;\n- child process spawning;\n- logging sinks;\n- persistent storage backends.\n\nAdapter rules:\n\n- Core/application code depends on traits.\n- Concrete adapters live at the boundary crate/module.\n- Keep adapter traits narrow and task-oriented.\n- Prefer mockable traits for tests.\n- Prefer traits owned by the consuming crate/module when that improves boundary clarity.\n\n## 9. When You Touch Legacy Global Code\n\nIf you see patterns such as:\n\n```rust\nConfig::global()\nConfig::verge()\nConfig::clash()\nConfig::profiles()\nConfig::runtime()\nCoreManager::global()\nSysopt::global()\nHotkey::global()\nLogger::global()\nHandle::global()\nProxiesGuard::global()\nUpdaterManager::global()\nWindowManager::global()\nconsts::app_handle()\n```\n\nprefer replacing the call path with one of:\n\n```rust\nclient.some_domain_operation(...).await?;\nstate_client.some_state_operation(...).await?;\ncore_client.some_core_operation(...).await?;\nsystem_proxy_client.some_system_operation(...).await?;\nservice.method(...)?;\n```\n\nDo not add a new wrapper that simply hides the global unless full migration is blocked. If blocked, document it:\n\n```rust\n// TODO(actor-migration): temporary bridge to <legacy global>.\n// Reason: <specific blocker>.\n// Remove when: <specific migration step>.\n```\n\n## 10. State and Configuration Migration\n\nConfiguration must be migrated before dependent services whenever possible.\n\nPreferred direction:\n\n1. Move state ownership into `StateActor` or a state manager owned by `StateActor`.\n2. Keep schema and patch operations in pure services or domain types.\n3. Generate runtime config from snapshots rather than mutating runtime globals.\n4. Commit state first, then trigger side effects through actor messages.\n5. Report post-commit side-effect failures as degraded results instead of silently rolling back persisted state.\n\nAvoid preserving old global configuration APIs. Prefer a migratable breaking change that updates callers to the new injected client/service API.\n\n## 11. Migration Policy: Prefer Migratable Breaking Changes\n\nWhen refactoring or migrating services:\n\n- Prefer fully migrating callers to the new injected/actor/pure-service API.\n- Prefer migratable breaking changes over compatibility layers.\n- Do not add a compatibility layer simply to avoid updating call sites.\n- Add a compatibility or migration layer only when a full migration is not currently possible due to cyclic dependencies, public API constraints, external plugin behavior, large cross-cutting risk, platform limitation, or staged release requirements.\n- Every compatibility layer must be explicitly marked with `TODO(actor-migration)` or `FIXME(actor-migration)` and must explain the reason and removal condition.\n- New code must not call compatibility APIs unless the call site is itself part of a documented migration step.\n\nDo this:\n\n```rust\nclient.patch_app_config(patch).await?;\n```\n\nDo not do this unless blocked:\n\n```rust\nLegacyConfigCompat::patch_verge(patch).await?;\n```\n\nRequired comment format:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nor:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 12. Tauri Command Rules\n\nTauri commands should be thin adapters. They should:\n\n- parse request DTOs;\n- call `NyanpasuClient`;\n- map domain errors into command errors;\n- never perform business orchestration directly;\n- never read or mutate config through globals;\n- never spawn core/service background tasks directly.\n\nAllowed shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(\n    client: tauri::State<'_, NyanpasuClient>,\n    patch: NyanpasuAppConfigPatch,\n) -> Result<()> {\n    client.patch_app_config(patch).await?;\n    Ok(())\n}\n```\n\nAvoid shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(patch: IVerge) -> Result<()> {\n    Config::verge().draft().patch_config(patch)?;\n    CoreManager::global().update_config().await?;\n    Config::verge().apply();\n    Ok(())\n}\n```\n\n## 13. Testing and Mocking\n\n- Prefer testing pure services directly with plain values.\n- For infrastructure dependencies, define narrow traits and inject them.\n- Traits that are intended to be mocked should be compatible with `mockall` / `automock` where practical.\n- Keep mock-only APIs behind `#[cfg(test)]` or test-support modules.\n- Do not use global test fixtures for application services. Construct a test `NyanpasuClient` or test-specific service graph.\n- Actor tests should spawn the actor with fake adapters and send typed messages through its typed client.\n- Avoid sleeping in actor tests. Prefer explicit acknowledgements, request/reply messages, or test hooks.\n\nExample mockable trait:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait UiEventSink: Send + Sync + 'static {\n    fn emit_state_changed(&self, event: StateChanged) -> anyhow::Result<()>;\n}\n```\n\nAnother acceptable trait shape:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait ConfigStore: Send + Sync + 'static {\n    fn load(&self) -> anyhow::Result<Vec<u8>>;\n    fn save(&self, bytes: &[u8]) -> anyhow::Result<()>;\n}\n```\n\n## 14. Naming Guidelines\n\nUse names that reveal the role:\n\n- `StateActor`, `CoreActor`, `SystemProxyActor`, `HotkeyActor`, `ProxiesActor`, `UpdaterActor` for actors.\n- `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient` for typed actor clients.\n- `RuntimeBuilder`, `ProfileMerger`, `ConfigMigrator`, `PatchValidator` for pure services.\n- `TauriUiEventSink`, `FsConfigStore`, `OsProxyBackend`, `ProcessRunner` for adapters.\n- `AppSupervisor` or `NyanpasuBootstrap` for the composition root.\n\n## 15. Comment Requirements\n\nUse comments only where they clarify migration state, invariants, or actor lifecycle assumptions.\n\nRequired compatibility-layer comment:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nRequired temporary legacy behavior comment:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 16. Final Review Checklist\n\nBefore finishing a change, check:\n\n- assumptions were stated when relevant;\n- success criteria were verified;\n- every changed line traces to the request;\n- no new global singleton service was added;\n- no new mutable static service state was added;\n- dependencies are explicit;\n- service classification is clear;\n- actor state is not leaked through shared locks;\n- Tauri is isolated behind adapters;\n- compatibility layers are exceptional and documented;\n- tests use injection, fakes, mocks, or pure values;\n- `NyanpasuClient` remains a facade, not a service locator.\n\n## 17. Worktree Setup and Resource Reuse\n\nFeature/migration work runs in isolated git worktrees. The worktree location is the developer's choice (any path outside the repo tree); this section only fixes the reuse policy, not where worktrees live. Worktrees share the main `.git`. The rule: reuse expensive **branch-independent** assets from the main checkout via symlink, and regenerate everything **branch-dependent** per worktree.\n\n### Reuse policy\n\n| Path (repo-relative)       | Approx size  | Policy                          | Reason                                                                                                                                    |\n| -------------------------- | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `backend/tauri/sidecar/`   | ~213M        | **Symlink → main**              | gitignored downloaded cores (mihomo / clash-rs / clash / nyanpasu-service); branch-independent; re-fetch via `pnpm prepare:check` is slow |\n| `backend/tauri/resources/` | ~21M         | **Symlink → main**              | gitignored static assets (`geoip.dat`, `geosite.dat`, `Country.mmdb`, `wintun.dll`, service exes); branch-independent                     |\n| `node_modules/`            | ~1.5G        | **Independent `pnpm install`**  | pnpm global store already hardlink-dedupes; sharing risks concurrent lock conflicts                                                       |\n| `backend/target/`          | ~50G         | **Independent — never symlink** | sharing causes Cargo incremental-fingerprint churn + concurrent build-lock waits across diverged source trees                             |\n| `backend/tauri/tmp/dist/`  | build output | **Independent — never symlink** | branch-dependent frontend build; `emptyOutDir: true` means one worktree's `web:build` wipes the shared dir                                |\n\nOnly `sidecar/` and `resources/` are symlink candidates.\n\n### Gitignored build prerequisites a fresh worktree lacks\n\n- **`frontend/interface/dist`** — `@nyanpasu/interface` (`main` → `./dist/index.js`) is consumed by `@nyanpasu/nyanpasu`. Produce with `pnpm -F interface build`.\n- **`backend/tauri/tmp/dist`** — `backend/tauri/build.rs` calls `tauri_build::build()`, which validates `frontendDist: ./tmp/dist` **at compile time**. When missing, every `cargo build` / `clippy` / `cargo test --all-features` / rust-analyzer run on the tauri crate fails. Resolve one of:\n  - Rust-only worktree → drop a placeholder (cheapest, no vite build).\n  - Runnable UI → `pnpm web:build` (build `interface` first; it clears and refills `tmp/dist`).\n\n`backend/tauri/tmp/git-info.json` is optional (`build.rs` guards it with `exists()`); run `pnpm generate:git-info` only if accurate commit metadata must be baked in.\n\n### Create a worktree\n\nCommands shown for Windows / PowerShell (dir symlinks need Developer Mode, no elevation). `<worktree-path>` and `<type>/<name>` are yours to choose.\n\n```powershell\n$main = git rev-parse --show-toplevel                 # capture main checkout root\ngit worktree add <worktree-path> -b <type>/<name>\ncd <worktree-path>\n\n# Reuse branch-independent downloads (symlink back to main)\nNew-Item -ItemType SymbolicLink backend/tauri/sidecar   -Target \"$main/backend/tauri/sidecar\"\nNew-Item -ItemType SymbolicLink backend/tauri/resources -Target \"$main/backend/tauri/resources\"\n\npnpm install\npnpm -F interface build                               # -> frontend/interface/dist (gitignored)\n\n# Satisfy tauri-build's frontendDist check — pick one:\nNew-Item -ItemType Directory -Force backend/tauri/tmp/dist | Out-Null            # A) Rust-only placeholder\nSet-Content backend/tauri/tmp/dist/index.html '<!doctype html><title>dev</title>'\n# pnpm web:build                                      # B) real UI (replaces tmp/dist)\n```\n\n### Remove a worktree\n\n`git worktree remove` on Windows can fail with `Filename too long` because per-worktree `node_modules` / `target` hold paths over MAX_PATH. Force-delete with the extended-length prefix, then reconcile git:\n\n```powershell\nRemove-Item -LiteralPath \"\\\\?\\<absolute-worktree-path>\" -Recurse -Force\ngit worktree prune\ngit worktree list\n```\n\nRemoval reclaims only the worktree's own files and its symlinks (pointers back to main) — it never touches the main checkout's real `sidecar/` / `resources/`.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, clarifying questions come before implementation rather than after mistakes, and new code moves away from global singletons toward injected actor/pure-service composition.\n","category":"root","tokens":5911},{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nThis repository is migrating away from `::global()` singletons and Tauri-coupled services toward explicit dependency injection, actor-owned state, and pure domain services.\n\nBehavioral guidelines reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 0. Synchronization Policy\n\nKeep `CLAUDE.md` and `AGENTS.md` synchronized as much as possible.\n\n- When changing an architectural rule in one file, mirror it in the other file.\n- Differences should be limited to tool-specific wording, if any.\n- Prefer the same section order, same terminology, and same examples.\n- Do not create a Claude-only or agent-only exception unless the tool truly requires it.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\nFor small, obvious tasks, do this briefly. For architecture, migration, or cross-module work, be explicit.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\nThis rule does not override the architectural migration direction. Do not use `::global()` or hidden mutable process state merely because it is fewer lines.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\nFor actor/DI migration work, the allowed scope is the smallest call path needed to migrate the touched service or API without leaving a hidden compatibility layer behind.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n\n- \"Add validation\" -> \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" -> \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" -> \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n\n```text\n1. [Step] -> verify: [check]\n2. [Step] -> verify: [check]\n3. [Step] -> verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n## 5. Target Architecture\n\nThe target architecture is:\n\n```text\nTauri commands / UI adapters\n    -> NyanpasuClient\n        -> typed actor clients\n        -> pure services\n        -> adapter traits\n            -> concrete Tauri / OS / filesystem / network implementations\n```\n\nUse these terms consistently:\n\n- **Dependency Injection / Pure DI**: dependencies are passed explicitly through constructors, builders, function arguments, or actor startup arguments. Do not look them up through globals.\n- **Composition Root**: the bootstrap / supervisor location that builds the full object graph and actor graph.\n- **Ports and Adapters**: core/application code depends on traits; Tauri, filesystem, OS, network, and process implementations live behind adapters.\n- **Actor service**: a ractor actor that owns mutable state, serializes commands, manages long-running resources, or supervises background work.\n- **Pure service**: a stateless or short-lived service that performs deterministic computation, validation, conversion, config generation, serialization, or patch application without IPC or background lifecycle.\n- **Adapter / port**: a narrow trait and concrete boundary implementation for infrastructure such as Tauri, filesystem, OS APIs, process spawning, HTTP, logging sinks, or storage.\n\n`NyanpasuClient` is the application facade. The application bootstrap / supervisor is the composition root. It constructs concrete services, spawns actors, wires dependencies, and returns a ready-to-use `NyanpasuClient`.\n\n## 6. ractor Primer for Agents\n\nIn this repository, `ractor` means the Rust `ractor` crate. It is an in-process actor framework used for long-lived services that own state and communicate through typed messages. It is not Tauri IPC and not Ruby Ractor.\n\nUse this mental model:\n\n```text\nActor = private mutable state + typed message enum + sequential message handling + lifecycle hooks\n```\n\nCore concepts:\n\n- `Actor`: the implementation trait. An actor defines its `Msg`, `State`, `Arguments`, and lifecycle/message-handling methods.\n- `ActorRef<Msg>`: a typed address used to send messages to an actor. Hide raw `ActorRef` values behind typed clients such as `StateClient` or `CoreClient`.\n- Message enum: the actor's domain protocol. Prefer explicit messages such as `PatchAppConfig`, `RestartCore`, or `SelectProxy` over generic commands.\n- `RpcReplyPort<T>`: the usual request/reply mechanism for queries and fallible operations that must return a value.\n- Fire-and-forget messages: use only for notifications, invalidations, events, or best-effort work where the caller does not need a result.\n- Startup arguments: the actor's dependency injection boundary. Pass dependencies when spawning the actor; do not fetch them from globals in actor code.\n\nProject rules:\n\n- Use actors for services with long-lived mutable state, serialized commands, background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, downloads, or child supervision.\n- Do not use actors for deterministic computation. Use pure services for validation, schema conversion, patch application, runtime config building, serialization, and merge/enhance logic when it can be deterministic.\n- Put infrastructure access behind adapter traits. Inject Tauri, OS, filesystem, network, process, and logging adapters into the actor or pure service that needs them.\n- The composition root spawns actors, wires dependencies, and returns `NyanpasuClient`. Do not use a ractor registry, actor name lookup, or raw `ActorRef` map as a replacement for dependency injection.\n- `NyanpasuClient` and typed actor clients should expose ordinary async Rust methods. Most callers should not use ractor APIs directly.\n- Prefer finite timeouts for cross-actor request/reply calls when the caller can report failure or degraded state.\n- Avoid synchronous cross-actor cycles such as `StateActor -> CoreActor -> StateActor`.\n\nIf a mature ractor actor client already exists for a capability, use it instead of adding a new global singleton, raw channel loop, or direct Tauri-coupled service call.\n\n## 7. Mandatory Architecture Rules\n\n### Do not add new global service singletons\n\nDo not introduce new service accessors such as:\n\n```rust\nService::global()\nget_global_service()\nstatic SERVICE: OnceCell<Service>\nstatic SERVICE: OnceLock<Service>\nstatic SERVICE: Lazy<Service>\n```\n\nExceptions are allowed only for immutable constants, static lookup tables, feature flags, or values that are truly process-wide and have no lifecycle, no mutable state, and no dependency graph.\n\nIf an existing `::global()` service must still be used during migration, isolate it at the edge of a migration step and add an explicit comment:\n\n```rust\n// TODO(actor-migration): temporary bridge to the legacy global service.\n// Reason: <why full migration is blocked>.\n// Remove when: <service-name> is injected through NyanpasuClient.\n```\n\n### Prefer explicit construction\n\nServices must be constructed through one of these forms:\n\n```rust\nService::new(dependency_a, dependency_b)\nServiceBuilder::default().with_dependency(...).build()\nAppSupervisor::start(args).await\n```\n\nDependencies should be visible in struct fields, constructor parameters, builder parameters, function parameters, or actor startup arguments. Hidden dependencies are not allowed.\n\n### Keep `NyanpasuClient` as a facade, not a service locator\n\n`NyanpasuClient` may expose stable application APIs, for example:\n\n```rust\nclient.get_app_config().await?;\nclient.patch_app_config(patch).await?;\nclient.get_profiles().await?;\nclient.restart_core().await?;\nclient.select_proxy(group, name).await?;\n```\n\nIt must not expose arbitrary internal lookup APIs such as:\n\n```rust\nclient.get_any_service::<T>()\nclient.resolve::<T>()\nclient.resolve(\"service-name\")\nclient.get_service(\"name\")\nclient.actor_registry()\nclient.get_actor_ref(\"state\")\n```\n\nInternally, `NyanpasuClient` may hold typed clients such as `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient`, and pure services or adapter trait objects. Callers should not depend on ractor `ActorRef` directly unless they are part of the actor layer itself.\n\n### Keep Tauri at the boundary\n\nBusiness logic must not depend directly on Tauri types such as `AppHandle`, `Window`, `Manager`, tray handles, global command state, or Tauri event emitters. Use adapter traits instead.\n\n## 8. Service Classification\n\nBefore adding or migrating a service, classify it as an actor service, pure service, or adapter/port.\n\n### Use an actor service when the service:\n\n- owns long-lived mutable state;\n- must serialize commands to avoid races;\n- manages background tasks, streams, timers, file watchers, process lifecycles, sockets, subscriptions, or downloads;\n- supervises child tasks or child actors;\n- needs request/reply or fire-and-forget messaging;\n- coordinates side effects after state commits.\n\nExpected examples:\n\n- app/config state;\n- core process lifecycle;\n- system proxy state;\n- hotkey registration;\n- proxy cache and subscriptions;\n- updater downloads;\n- websocket connection managers;\n- server lifecycle.\n\nActor implementation rules:\n\n- Use a typed message enum.\n- Keep owned mutable state inside the actor state.\n- Use typed actor client wrappers for public calls.\n- Do not expose raw `ActorRef` outside actor/application internals.\n- Use request/reply for fallible operations and queries.\n- Use fire-and-forget only for events, notifications, or best-effort work.\n- Avoid cross-actor synchronous cycles.\n- Prefer finite timeouts for cross-actor RPCs where a caller can recover or report degradation.\n- Actor startup arguments must contain all dependencies required to build the actor state.\n- Do not share actor-owned mutable state with `Arc<Mutex<_>>` or `Arc<RwLock<_>>` unless it is a narrowly scoped implementation detail with a clear comment.\n\n### Use a pure service when the service:\n\n- performs deterministic computation;\n- validates input;\n- converts schemas;\n- applies patches to owned data passed as parameters;\n- builds runtime configuration from snapshots;\n- serializes or deserializes data without owning long-lived state;\n- has no background task and no independent lifecycle.\n\nExpected examples:\n\n- config validation;\n- patch application;\n- profile ordering;\n- runtime config building;\n- legacy schema conversion;\n- serialization helpers;\n- merge/enhance logic when it can be made deterministic.\n\nPure service rules:\n\n- No global state.\n- No background tasks.\n- No hidden filesystem/network/Tauri access.\n- All inputs must be explicit parameters.\n- Return values or domain errors instead of mutating external state.\n\n### Use an adapter / port when the service touches infrastructure:\n\n- Tauri events, windows, tray, dialogs, clipboard;\n- filesystem and app directories;\n- OS proxy APIs;\n- global shortcuts;\n- HTTP clients;\n- child process spawning;\n- logging sinks;\n- persistent storage backends.\n\nAdapter rules:\n\n- Core/application code depends on traits.\n- Concrete adapters live at the boundary crate/module.\n- Keep adapter traits narrow and task-oriented.\n- Prefer mockable traits for tests.\n- Prefer traits owned by the consuming crate/module when that improves boundary clarity.\n\n## 9. When You Touch Legacy Global Code\n\nIf you see patterns such as:\n\n```rust\nConfig::global()\nConfig::verge()\nConfig::clash()\nConfig::profiles()\nConfig::runtime()\nCoreManager::global()\nSysopt::global()\nHotkey::global()\nLogger::global()\nHandle::global()\nProxiesGuard::global()\nUpdaterManager::global()\nWindowManager::global()\nconsts::app_handle()\n```\n\nprefer replacing the call path with one of:\n\n```rust\nclient.some_domain_operation(...).await?;\nstate_client.some_state_operation(...).await?;\ncore_client.some_core_operation(...).await?;\nsystem_proxy_client.some_system_operation(...).await?;\nservice.method(...)?;\n```\n\nDo not add a new wrapper that simply hides the global unless full migration is blocked. If blocked, document it:\n\n```rust\n// TODO(actor-migration): temporary bridge to <legacy global>.\n// Reason: <specific blocker>.\n// Remove when: <specific migration step>.\n```\n\n## 10. State and Configuration Migration\n\nConfiguration must be migrated before dependent services whenever possible.\n\nPreferred direction:\n\n1. Move state ownership into `StateActor` or a state manager owned by `StateActor`.\n2. Keep schema and patch operations in pure services or domain types.\n3. Generate runtime config from snapshots rather than mutating runtime globals.\n4. Commit state first, then trigger side effects through actor messages.\n5. Report post-commit side-effect failures as degraded results instead of silently rolling back persisted state.\n\nAvoid preserving old global configuration APIs. Prefer a migratable breaking change that updates callers to the new injected client/service API.\n\n## 11. Migration Policy: Prefer Migratable Breaking Changes\n\nWhen refactoring or migrating services:\n\n- Prefer fully migrating callers to the new injected/actor/pure-service API.\n- Prefer migratable breaking changes over compatibility layers.\n- Do not add a compatibility layer simply to avoid updating call sites.\n- Add a compatibility or migration layer only when a full migration is not currently possible due to cyclic dependencies, public API constraints, external plugin behavior, large cross-cutting risk, platform limitation, or staged release requirements.\n- Every compatibility layer must be explicitly marked with `TODO(actor-migration)` or `FIXME(actor-migration)` and must explain the reason and removal condition.\n- New code must not call compatibility APIs unless the call site is itself part of a documented migration step.\n\nDo this:\n\n```rust\nclient.patch_app_config(patch).await?;\n```\n\nDo not do this unless blocked:\n\n```rust\nLegacyConfigCompat::patch_verge(patch).await?;\n```\n\nRequired comment format:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nor:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 12. Tauri Command Rules\n\nTauri commands should be thin adapters. They should:\n\n- parse request DTOs;\n- call `NyanpasuClient`;\n- map domain errors into command errors;\n- never perform business orchestration directly;\n- never read or mutate config through globals;\n- never spawn core/service background tasks directly.\n\nAllowed shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(\n    client: tauri::State<'_, NyanpasuClient>,\n    patch: NyanpasuAppConfigPatch,\n) -> Result<()> {\n    client.patch_app_config(patch).await?;\n    Ok(())\n}\n```\n\nAvoid shape:\n\n```rust\n#[tauri::command]\npub async fn patch_verge_config(patch: IVerge) -> Result<()> {\n    Config::verge().draft().patch_config(patch)?;\n    CoreManager::global().update_config().await?;\n    Config::verge().apply();\n    Ok(())\n}\n```\n\n## 13. Testing and Mocking\n\n- Prefer testing pure services directly with plain values.\n- For infrastructure dependencies, define narrow traits and inject them.\n- Traits that are intended to be mocked should be compatible with `mockall` / `automock` where practical.\n- Keep mock-only APIs behind `#[cfg(test)]` or test-support modules.\n- Do not use global test fixtures for application services. Construct a test `NyanpasuClient` or test-specific service graph.\n- Actor tests should spawn the actor with fake adapters and send typed messages through its typed client.\n- Avoid sleeping in actor tests. Prefer explicit acknowledgements, request/reply messages, or test hooks.\n\nExample mockable trait:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait UiEventSink: Send + Sync + 'static {\n    fn emit_state_changed(&self, event: StateChanged) -> anyhow::Result<()>;\n}\n```\n\nAnother acceptable trait shape:\n\n```rust\n#[cfg_attr(test, mockall::automock)]\npub trait ConfigStore: Send + Sync + 'static {\n    fn load(&self) -> anyhow::Result<Vec<u8>>;\n    fn save(&self, bytes: &[u8]) -> anyhow::Result<()>;\n}\n```\n\n## 14. Naming Guidelines\n\nUse names that reveal the role:\n\n- `StateActor`, `CoreActor`, `SystemProxyActor`, `HotkeyActor`, `ProxiesActor`, `UpdaterActor` for actors.\n- `StateClient`, `CoreClient`, `SystemProxyClient`, `HotkeyClient`, `ProxiesClient` for typed actor clients.\n- `RuntimeBuilder`, `ProfileMerger`, `ConfigMigrator`, `PatchValidator` for pure services.\n- `TauriUiEventSink`, `FsConfigStore`, `OsProxyBackend`, `ProcessRunner` for adapters.\n- `AppSupervisor` or `NyanpasuBootstrap` for the composition root.\n\n## 15. Comment Requirements\n\nUse comments only where they clarify migration state, invariants, or actor lifecycle assumptions.\n\nRequired compatibility-layer comment:\n\n```rust\n// TODO(actor-migration): compatibility bridge for <legacy API>.\n// Reason: <why full migration is blocked>.\n// Remove when: <specific condition or tracking issue>.\n```\n\nRequired temporary legacy behavior comment:\n\n```rust\n// FIXME(actor-migration): legacy behavior kept temporarily for <reason>.\n// New code must use <new API>. Remove after <condition>.\n```\n\n## 16. Final Review Checklist\n\nBefore finishing a change, check:\n\n- assumptions were stated when relevant;\n- success criteria were verified;\n- every changed line traces to the request;\n- no new global singleton service was added;\n- no new mutable static service state was added;\n- dependencies are explicit;\n- service classification is clear;\n- actor state is not leaked through shared locks;\n- Tauri is isolated behind adapters;\n- compatibility layers are exceptional and documented;\n- tests use injection, fakes, mocks, or pure values;\n- `NyanpasuClient` remains a facade, not a service locator.\n\n## 17. Worktree Setup and Resource Reuse\n\nFeature/migration work runs in isolated git worktrees. The worktree location is the developer's choice (any path outside the repo tree); this section only fixes the reuse policy, not where worktrees live. Worktrees share the main `.git`. The rule: reuse expensive **branch-independent** assets from the main checkout via symlink, and regenerate everything **branch-dependent** per worktree.\n\n### Reuse policy\n\n| Path (repo-relative)       | Approx size  | Policy                          | Reason                                                                                                                                    |\n| -------------------------- | ------------ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `backend/tauri/sidecar/`   | ~213M        | **Symlink → main**              | gitignored downloaded cores (mihomo / clash-rs / clash / nyanpasu-service); branch-independent; re-fetch via `pnpm prepare:check` is slow |\n| `backend/tauri/resources/` | ~21M         | **Symlink → main**              | gitignored static assets (`geoip.dat`, `geosite.dat`, `Country.mmdb`, `wintun.dll`, service exes); branch-independent                     |\n| `node_modules/`            | ~1.5G        | **Independent `pnpm install`**  | pnpm global store already hardlink-dedupes; sharing risks concurrent lock conflicts                                                       |\n| `backend/target/`          | ~50G         | **Independent — never symlink** | sharing causes Cargo incremental-fingerprint churn + concurrent build-lock waits across diverged source trees                             |\n| `backend/tauri/tmp/dist/`  | build output | **Independent — never symlink** | branch-dependent frontend build; `emptyOutDir: true` means one worktree's `web:build` wipes the shared dir                                |\n\nOnly `sidecar/` and `resources/` are symlink candidates.\n\n### Gitignored build prerequisites a fresh worktree lacks\n\n- **`frontend/interface/dist`** — `@nyanpasu/interface` (`main` → `./dist/index.js`) is consumed by `@nyanpasu/nyanpasu`. Produce with `pnpm -F interface build`.\n- **`backend/tauri/tmp/dist`** — `backend/tauri/build.rs` calls `tauri_build::build()`, which validates `frontendDist: ./tmp/dist` **at compile time**. When missing, every `cargo build` / `clippy` / `cargo test --all-features` / rust-analyzer run on the tauri crate fails. Resolve one of:\n  - Rust-only worktree → drop a placeholder (cheapest, no vite build).\n  - Runnable UI → `pnpm web:build` (build `interface` first; it clears and refills `tmp/dist`).\n\n`backend/tauri/tmp/git-info.json` is optional (`build.rs` guards it with `exists()`); run `pnpm generate:git-info` only if accurate commit metadata must be baked in.\n\n### Create a worktree\n\nCommands shown for Windows / PowerShell (dir symlinks need Developer Mode, no elevation). `<worktree-path>` and `<type>/<name>` are yours to choose.\n\n```powershell\n$main = git rev-parse --show-toplevel                 # capture main checkout root\ngit worktree add <worktree-path> -b <type>/<name>\ncd <worktree-path>\n\n# Reuse branch-independent downloads (symlink back to main)\nNew-Item -ItemType SymbolicLink backend/tauri/sidecar   -Target \"$main/backend/tauri/sidecar\"\nNew-Item -ItemType SymbolicLink backend/tauri/resources -Target \"$main/backend/tauri/resources\"\n\npnpm install\npnpm -F interface build                               # -> frontend/interface/dist (gitignored)\n\n# Satisfy tauri-build's frontendDist check — pick one:\nNew-Item -ItemType Directory -Force backend/tauri/tmp/dist | Out-Null            # A) Rust-only placeholder\nSet-Content backend/tauri/tmp/dist/index.html '<!doctype html><title>dev</title>'\n# pnpm web:build                                      # B) real UI (replaces tmp/dist)\n```\n\n### Remove a worktree\n\n`git worktree remove` on Windows can fail with `Filename too long` because per-worktree `node_modules` / `target` hold paths over MAX_PATH. Force-delete with the extended-length prefix, then reconcile git:\n\n```powershell\nRemove-Item -LiteralPath \"\\\\?\\<absolute-worktree-path>\" -Recurse -Force\ngit worktree prune\ngit worktree list\n```\n\nRemoval reclaims only the worktree's own files and its symlinks (pointers back to main) — it never touches the main checkout's real `sidecar/` / `resources/`.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, clarifying questions come before implementation rather than after mistakes, and new code moves away from global singletons toward injected actor/pure-service composition.\n","category":"root","tokens":5911}]}