From c73498b064aea47ffaec58930b0f9adb285c1078 Mon Sep 17 00:00:00 2001 From: eliotcougar Date: Sat, 29 Aug 2026 10:59:49 +0300 Subject: [PATCH] Docs/agent guidelines (#6129) * docs: refresh agent guidelines * docs: add agent harness adapters * Update local-only block markers in AGENTS.md DO NOT COMMIT phrase was confusing most LLMs and preventing them from committing anything without an additional explicit permission. --- .github/copilot-instructions.md | 6 + AGENTS.md | 188 ++++++++++++++++++ CLAUDE.md | 1 + GEMINI.md | 1 + .../main/java/com/v2ray/ang/service/AGENTS.md | 92 +++++++++ .../main/java/com/v2ray/ang/service/CLAUDE.md | 1 + .../main/java/com/v2ray/ang/service/GEMINI.md | 1 + .../src/main/java/com/v2ray/ang/ui/AGENTS.md | 95 +++++++++ .../src/main/java/com/v2ray/ang/ui/CLAUDE.md | 1 + .../src/main/java/com/v2ray/ang/ui/GEMINI.md | 1 + docs/AGENTS.md | 84 -------- 11 files changed, 387 insertions(+), 84 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 GEMINI.md create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/service/AGENTS.md create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/service/CLAUDE.md create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/service/GEMINI.md create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/ui/AGENTS.md create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/ui/CLAUDE.md create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/ui/GEMINI.md delete mode 100644 docs/AGENTS.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..96fa5448 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,6 @@ +# Agent-guide adapter + +Before starting any repository task, open and follow the repository-root `AGENTS.md`. +That guide defines the scoped `AGENTS.md` files that must also be opened before editing +their paths. Treat the root and applicable scoped guides as mandatory. Do not duplicate +or replace their rules in this file. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..a6dbf65f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,188 @@ +# v2rayNG agent guide + +## Scope and precedence + +This file applies to the entire repository. Read the applicable scoped guide before editing any +listed path, including when the agent starts at the repository root: + +- Read `V2rayNG/app/src/main/java/com/v2ray/ang/service/AGENTS.md` for + `V2rayNG/app/src/main/java/com/v2ray/ang/service/`, + `V2rayNG/app/src/test/java/com/v2ray/ang/service/`, and these paths under + `V2rayNG/app/src/main/java/com/v2ray/ang/`: `core/CoreServiceManager.kt`, + `core/LauncherManager.kt`, `root/`, `contracts/ServiceControl.kt`, + `contracts/Tun2SocksControl.kt`, `handler/NotificationManager.kt`, + `helper/MessageHelper.kt`, and `helper/NotificationHelper.kt`. +- Read `V2rayNG/app/src/main/java/com/v2ray/ang/ui/AGENTS.md` for + `V2rayNG/app/src/main/java/com/v2ray/ang/ui/` and + `V2rayNG/app/src/test/java/com/v2ray/ang/ui/`. + +Follow both guides in scoped paths. A scoped rule overrides a root rule only when they require +incompatible actions; every other root rule remains active. + +## Requirement language + +- `must`, `must not`, `only`, and `never` are mandatory. Only the current user request or linked + acceptance criteria can state an exception; agent inference cannot. +- `Task scope` is the named files and behavior plus dependency, test, or build edits that a + compiler or required validation proves necessary. Every other change is outside scope. +- `Changed behavior` includes direct and downstream build-time or runtime effects through callers, + consumers, storage, IPC, and resources. +- `Supported` versions, ABIs, distributions, and locales are exactly those declared by + `V2rayNG/app/build.gradle.kts` and `.github/workflows/build.yml` inputs. +- A behavior is `verified` only when its named check passed on branch HEAD. Otherwise list the + exact unrun check and blocker, and do not call the behavior verified. + +## Existing code is not a compliance baseline + +- These guides define the target state; they do not claim that this checkout, an older commit, or + another worktree complies. Existing code shows current behavior and is not permission to repeat + a pattern. +- A `touched declaration` is any function, property, class, composable, resource entry, manifest + element, or Gradle block containing an added or removed line. Inspect it and every caller or + consumer whose contract changes against all applicable rules before editing. +- Every new declaration and changed behavior must comply. Inspect a helper or pattern before + reusing it. Tests prove only their assertions; names, green builds, and prior approval prove + nothing else. +- Do not repair unrelated violations. Fix a violation left on a task-changed execution path in the + same branch; if that would change behavior outside scope, request approval first. Report any + remaining violation that blocks required validation. + +## Agent-guide synchronization and PR boundary + +- `AGENTS.md` files are the only rule source. `CLAUDE.md`, `GEMINI.md`, and + `.github/copilot-instructions.md` are routing shims and must only load or point to applicable + `AGENTS.md` files. +- Feature, fix, and release branches use instruction files from fetched `upstream/master` as their + baseline. If `upstream` is absent, run + `git remote add upstream https://github.com/2dust/v2rayNG.git`; otherwise require + `git remote get-url upstream` to print exactly that URL. Successfully run + `git fetch upstream master` before comparison. An agent-instruction branch uses its HEAD and + working diff as the proposal. +- At task start and after each upstream merge or rebase, synchronize canonical instructions into + the worktree without deleting local-only text. Compare them with: + + ```sh + git diff upstream/master -- ':(glob)**/AGENTS.md' ':(glob)**/CLAUDE.md' ':(glob)**/GEMINI.md' .github/copilot-instructions.md + ``` + + After synchronization, every displayed difference must be inside a local-only block. If a branch + predates the files, keep copied versions untracked or unstaged; acquire tracked copies only by + merging or rebasing their upstream commit. +- User-, machine-, worktree-, and temporary text is local-only. Put it in `AGENTS.md` between + visible lines `LOCAL INSTRUCTIONS - START` and `LOCAL INSTRUCTIONS - END`. + Unless the task targets agent instructions, treat every pre-existing instruction-file difference + as local-only. +- Keep local-only text unstaged and uncommitted. Never use `git add .`, `git add -A`, or + `git commit -a` in that worktree. Stage task paths explicitly; never delete, restore, or overwrite + local instructions to clean the worktree. +- Before each commit, push, or PR update outside agent-instruction scope, run both commands. Both + must print no paths; otherwise stop and remove the instruction change from the staged or branch + diff without altering its working copy. + + ```sh + git diff --cached --name-only -- ':(glob)**/AGENTS.md' ':(glob)**/CLAUDE.md' ':(glob)**/GEMINI.md' .github/copilot-instructions.md + git diff --name-only upstream/master...HEAD -- ':(glob)**/AGENTS.md' ':(glob)**/CLAUDE.md' ':(glob)**/GEMINI.md' .github/copilot-instructions.md + ``` + +- Commit instruction files only for a task explicitly limited to agent instructions. The branch + and PR may contain only `AGENTS.md` and necessary routing-shim changes; name agent instructions + in the PR title and state that exclusive scope in the body. + +## Project boundaries + +- The Android project is under `V2rayNG/` and uses Kotlin, Gradle Kotlin DSL, Compose, Material 3, + coroutines, and AndroidX lifecycle. Read dependency versions from + `V2rayNG/gradle/libs.versions.toml`, SDK levels from `V2rayNG/app/build.gradle.kts`, and CI tools + from `.github/workflows/build.yml`; never copy those numbers into a guide. +- `AngApplication` initializes MMKV, app locales, WorkManager, defaults, and theme state. Persist + application data through `MmkvManager` or `SettingsManager`; do not create `SharedPreferences` + or another path for data they own. +- `core/` owns native configuration and lifecycle, `handler/` data and application operations, + `service/` Android services, and `ui/` Compose activities, components, and ViewModels. Put new + code in its owner unless a scoped guide names a narrower owner. +- Shared lifecycle and configuration code must retain VPN, proxy-only, and root-mode branches. + Change one mode only when task scope names it; gate the change and preserve the other modes. +- `AndroidLibXrayLite` is native-core source. `V2rayNG/app/libs/libv2ray.aar` and the HEV libraries + are generated inputs; do not modify or commit them unless task scope upgrades a native dependency. + Such a PR must identify the source revision and verify every app-declared ABI. + +## Build and validation + +Before building a clean checkout, initialize submodules recursively and reproduce the HEV and AAR +steps in `.github/workflows/build.yml`. The AAR revision must match the `AndroidLibXrayLite` gitlink +from `git ls-tree HEAD AndroidLibXrayLite`. Take build-tool versions from that workflow and +`V2rayNG/gradle/libs.versions.toml`. + +Run Gradle from `V2rayNG/`, using `gradlew.bat` in Windows PowerShell or `./gradlew` on POSIX. Use +the Play Store debug variant unless task scope names another distribution. + +Apply every matching validation rule: + +- Documentation only: run `git diff --check`; no Gradle task is required. +- Kotlin/Java production code: run the test class that asserts each changed behavior, then + `:app:testPlaystoreDebugUnitTest` and `:app:compilePlaystoreDebugKotlin`. +- Resources, manifest, Gradle, dependencies, or native packaging: run + `:app:assemblePlaystoreDebug`. +- F-Droid only: replace `Playstore` with `Fdroid`. Run both variants when shared code branches on a + flavor, `BuildConfig.DISTRIBUTION`, or a flavor-specific resource or dependency. +- Android service lifecycle, framework callback, native interaction, permission, accessibility, + focus, or state restoration: also run an emulator or physical-device check. + +Add or update JUnit tests under `V2rayNG/app/src/test/java/` for changed deterministic logic. Report +an unavailable required check under the exact label `Not run`, with its command or scenario and +reason. A compile or assembled APK does not verify runtime behavior. + +There is no enforced Kotlin formatter. Preserve local indentation and import order. Do not change +whitespace outside touched declarations or reorder imports except as the diff requires. + +## Repository-wide coding rules + +- Keep the diff inside task scope; exclude unrelated refactors, upgrades, generated files, + translation cleanup, renames, and formatting-only changes. +- Use server GUIDs and group IDs for persistence, asynchronous work, and UI state. Never use a + list index, adapter position, or paging position as server or group identity. +- Run disk, network, package-manager, native, bitmap, and CPU-intensive work off the main thread. + Own each asynchronous operation with a named lifecycle or ViewModel scope and cancel it when the + owner ends or newer work supersedes it. +- Put all visible and accessibility text in Android resources. Update every locale in + `androidResources.localeFilters` for each changed key, preserving placeholders, plurals, and + formatting tags. +- Never rename or delete a persisted, serialized, routing, import, or export field without a + backward-compatible migration and a regression test reading the preceding released format. + Multi-record writes must complete together or restore/remove partial writes on every failure. +- Log recoverable failures through `LogUtil`, including operation, mode/component, non-secret + stable ID when available, and exception. Never log credentials, full proxy URLs, private keys, + or exported configurations. +- Before adding a constant, helper, manager, repository, or state holder, search its owner. Extend + an owner that controls the same data or lifecycle. Create a new abstraction only for different + ownership or lifetime, and state that distinction in code or the PR. +- A platform workaround needs a comment naming its Android/API or vendor boundary, prevented + failure, and exact removal condition. + +## Deprecated, experimental, and version-gated APIs + +- Introduce a deprecated API only for a supported-version fallback lacking the replacement, a + required deprecated callback/interface member, or a documented platform/vendor defect that + blocks acceptance criteria. The defect requires a test or linked upstream issue. +- Isolate each permitted deprecated call in one compatibility function or adapter. Comment the + reason and an exact removal trigger. Use the stable API wherever available. +- Scope `@Suppress("DEPRECATION")` or `@SuppressLint("")` to the containing expression or function. + Class scope is allowed only when every member implements the same required deprecated interface; + file, package, and module scope is forbidden. Never suppress permission, background-execution, + lifecycle, or security requirements. +- Guard every API above `minSdk` by SDK or extension version in the same function, or use + `@RequiresApi` only when every entry point is guarded. Mark reusable guards + `@ChecksSdkIntAtLeast`. Lower versions must use an + existing API, return explicit unsupported status, or hide/disable the feature; never crash or + report false success. +- Add or expand an experimental API only when no stable API meets named acceptance criteria, its + dependency is pinned in `libs.versions.toml`, a project-owned stable interface contains it and + excludes it from persisted data, IPC payloads, and public shared contracts, a regression test + covers it, and a comment names the opt-in marker and exact reevaluation condition. +- Put `@OptIn` on the direct function/property, or on a class only when multiple members need it. + File and module opt-ins are forbidden. Existing experimental use outside scope does not permit a + new call site. +- A preview/canary SDK or alpha dependency is allowed in production only when the request or linked + issue requires it. Keep a stable path for supported non-preview devices, gate by runtime API or + feature availability, and test both paths. After an API guard, opt-in, or lint suppression, run + `:app:lintPlaystoreDebug` and also `:app:lintFdroidDebug` when behavior differs by distribution. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/AGENTS.md b/V2rayNG/app/src/main/java/com/v2ray/ang/service/AGENTS.md new file mode 100644 index 00000000..501f903e --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/AGENTS.md @@ -0,0 +1,92 @@ +# Service code agent guide + +These rules apply to the service and service-adjacent paths listed in the repository-root +`AGENTS.md`. Follow both guides; apply the precedence rule defined by the root guide. + +## Process and lifecycle invariants + +- The daemon process `:RunSoLibV2RayDaemon` is the only authority for whether the native + core is running. Do not use a UI-process singleton, cached Boolean, bound-service + assumption, or successful broadcast send as proof of daemon state. +- Route every app-initiated VPN, proxy-only, and root start through `LauncherManager`. + Route service commands and their results through `MessageHelper`. Put lifecycle state + shared by two or more run modes in `CoreServiceManager` or `ServiceControl`. Do not + load or query the native core from the UI process to decide start, stop, or restart. +- Keep Android-owned `VpnService` entry paths intact. `CoreVpnService` must accept a null + restart intent and the `VpnService.SERVICE_INTERFACE` action without requiring the + app-initiated `LauncherManager` path. +- A start path is idempotent only when a second equivalent command leaves exactly one + native core, tunnel, foreground notification update loop, worker set, and root rule + set. Every edit to start dispatch or `onStartCommand` must preserve that result. +- In each `onStartCommand`, enter foreground before reading configuration, opening a + file or socket, starting a coroutine that performs setup, invoking native code, or + executing a root command. `CoreVpnService`, `CoreProxyOnlyService`, and + `CoreRootService` must call `NotificationManager.ensureForeground()`; + `CoreTestService` and `SubscriptionUpdateService` must call + `NotificationHelper.startForeground()`. +- On setup failure, cancel the setup job, release every resource created by that attempt, + remove partial root or VPN state, and stop the failed service instance. Do not return a + sticky restart mode unless `CoreServiceManager.isRunning()` is true, the service owns + every tunnel or root resource required by its run mode, and the Android restart path + reconstructs configuration, native core, notification, and mode-owned resources + without UI-process state. +- Preserve these ownership boundaries: `CoreVpnService` owns the VPN interface and + socket protection; `CoreProxyOnlyService` owns local-proxy mode; `CoreRootService` + owns root routing. Code used by two or more of these services belongs in their common + lifecycle layer. Code used by one mode remains in that mode's service. + +## Cleanup, concurrency, networking, and IPC + +- Teardown must prevent new work before releasing dependencies. Mark the service as + stopping, reject or invalidate pending start/reload work, cancel and join setup jobs, + then release owned resources. Remove root routing before stopping its core listener. + Close each VPN descriptor and tun2socks resource once on setup failure, revoke, stop, + and destroy; make repeated teardown calls no-ops after the first close. +- Every new coroutine launched by a service must be a child of a job or scope stored by + that service or the shared lifecycle owner. Cancel that owner during stop and + `onDestroy`. Do not use `GlobalScope`, an anonymous standalone `CoroutineScope`, or a + job whose parent outlives the service and later recreates routes, notifications, + files, or native state. +- Do not add root commands, file I/O, network probes, or native calls directly to + `onCreate`, `onStartCommand`, `onRevoke`, or `onDestroy` on the main thread. Perform + the operation in the service-owned scope. If traffic-leak prevention requires the + callback to await completion, the call site must contain an explicit timeout and a + comment naming the leak-prevention ordering; an unbounded wait is prohibited. +- A handover belongs to the `NetworkMonitor` instance that scheduled it. + `NetworkMonitor.unregister()` must cancel pending handover work and prevent its + callback from entering after `unregister()` returns. Immediately before reload, the + handover handler must verify that its monitor instance is still the instance owned by + `CoreServiceManager` and that the core is running. Stop and destroy must unregister + the monitor before clearing it or stopping the core. +- Every app-internal broadcast intent must set its package to `AppConfig.ANG_PACKAGE`; + every app-internal service intent must use an explicit `ComponentName`. Do not add an + implicit broadcast or service intent. Each payload must implement the serialization + contract used by its receiver. When a caller must distinguish `handled by daemon` + from `daemon absent`, return an acknowledgement/result from the daemon. Broadcast + delivery, bind success, and command enqueue success are not acknowledgements. + +## Native resources, logging, and validation + +- `TProxyService` loads the JNI library `libhev-socks5-tunnel.so`; root mode executes + the separately packaged `libhevsockstun.so`. A native or packaging diff must inspect + the produced APK as a ZIP and confirm both HEV files and `libgojni.so` for every ABI + selected by `ABI_FILTERS` or, when that property is absent, every ABI in the app's + default `splits.abi` block. +- A recoverable start, stop, reload, handover, or cleanup failure log must include the + run mode, lifecycle phase, stable non-secret profile/group identifier when one exists, + failed operation, and exception. Apply the root guide's secret-redaction rule. +- Move deterministic lifecycle decisions into pure helpers and add a JVM regression test + for each changed decision. Apply this scenario mapping to service changes: + - Start dispatch, `onStartCommand`, or command deduplication: cold start and two + equivalent consecutive start commands. + - Foreground setup or setup-error handling: successful setup and failure after at + least one resource has been acquired. + - Stop or teardown: normal stop, repeated stop, and stop while setup is in flight. + - Restart or network handover: successful restart/handover and a stop racing the + pending restart/handover. + - Shared lifecycle code: run the mapped scenarios in VPN, proxy-only, and root modes. + Mode-owned code: run them in the owning mode. +- Every service lifecycle scenario above requires an emulator or physical device; a JVM + test or assembled APK is not a substitute. Record each scenario that did not run under + `Not run`, with the device requirement or blocker. Do not claim the service change is + runtime-verified when any mapped scenario is unrun. diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/CLAUDE.md b/V2rayNG/app/src/main/java/com/v2ray/ang/service/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/GEMINI.md b/V2rayNG/app/src/main/java/com/v2ray/ang/service/GEMINI.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/GEMINI.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AGENTS.md b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AGENTS.md new file mode 100644 index 00000000..f50e17ae --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/AGENTS.md @@ -0,0 +1,95 @@ +# UI code agent guide + +These rules apply to the UI paths listed in the repository-root `AGENTS.md`. Follow both +guides; apply the precedence rule defined by the root guide. + +## Compose architecture and state + +- Implement every new screen and component with the existing Compose and Material 3 + stack. A new screen activity must extend `BaseComponentActivity` or + `HelperBaseComponentActivity` and place its screen, ViewModel, and action contract in + the package that owns its navigation entry. Do not add a fragment, ViewBinding layout, + XML screen layout, or RecyclerView adapter. Do not migrate existing legacy UI outside + task scope, and do not extend it or use it as the template for new UI. +- Keep durable screen state and business work in a ViewModel/repository and expose it as + immutable observable state. Collect flows with `collectAsStateWithLifecycle()`. + Compose-local state is only for transient presentation state; use `rememberSaveable` + when that state must survive activity recreation. +- Durable state includes loaded data, selection by domain ID, validation results, + progress, errors, and any value written to settings or a profile. Transient + presentation state is limited to visual expansion, animation, scroll/focus position, + and an uncommitted input value owned solely by the visible component. A composable + must not perform repository, MMKV, import/export, routing, or native-core work. +- Send user operations through the owning feature's action/event contract and + ViewModel. Main-screen operations must use `MainAction` and `MainViewModel`. + Composables must not mutate a repository, `MmkvManager`, or `SettingsManager` + directly. +- Use server GUIDs and group IDs for Compose keys, selection, saved state, and action + parameters. Do not persist or dispatch a visible index, adapter position, or paging + offset as identity. After filtering, sorting, subscription replacement, paging, or an + asynchronous result, resolve the item again by its domain ID. +- Run blocking file, network, package-manager, and bitmap-decoding operations on + `Dispatchers.IO`. Run CPU-bound parsing, sorting, filtering, and transformation on + `Dispatchers.Default`. Launch both from the owning ViewModel or lifecycle scope. + Publish results through a private `MutableStateFlow` exposed as `StateFlow`; do not + read or write Compose snapshot state on either background dispatcher. + +## Interaction, accessibility, and layout + +- Give every icon with a click, long-click, toggle, or custom accessibility action a + localized accessible name that states its action. Set `contentDescription = null` on + every icon with no user action. Do not expose a raw URL, + package ID, GUID, or duplicated descendant label when a row-level semantic node + supplies the name. +- A row with one activation behavior must expose one focusable semantic node, one + localized name, its current selected/on/off state, and one activation action. Merge or + clear descendant semantics so a label, icon, checkbox, and switch do not become + duplicate focus targets. Keep a descendant as a separate node only when it performs a + different user action; give that node its own name and role. TalkBack, touch, Enter, + Space, and D-pad center must invoke the same action for the same node. +- Key each focusable list item by its domain ID. Reordering or recomposition must keep + focus on the node with that ID if it still exists. If a selection change removes or + replaces the focused node, move focus exactly once to the final active item after the + state update; do not announce or focus an intermediate item. +- Use `stringResource` for text created inside a composable and localized + `Context.getString`/resources outside Compose. This rule covers visible labels, + semantic labels, state descriptions, validation messages, errors, dialogs, snackbars, + and toasts. Apply the root guide's locale and placeholder requirements to every key. +- Every form containing a text-editing control must use a vertically scrollable + container. Inside a `Scaffold`, apply its `innerPadding`, call + `consumeWindowInsets(innerPadding)`, and + apply `imePadding()` to that container. Outside a `Scaffold`, apply `imePadding()` and + consume the navigation-bar inset exactly once. With the IME visible, the user must be + able to focus, read, edit, and activate both the first and last form controls without + dismissing the IME. +- A shared component used by server screens must preserve edge-to-edge system-bar + insets, light and dark themes, dynamic color enabled and disabled, and both single- + and double-column layouts. A change to such a component requires rendering and + interaction checks in each of those configurations; dynamic-color checks require a + device or emulator that supports dynamic color. + +## Validation mapping + +Apply every row whose trigger matches the UI diff: + +- ViewModel state, action mapping, validation, filtering, parsing, or selection logic: + add or update a JVM test that asserts the initial state and every success, invalid- + input, empty-input, and failure branch added or modified by the diff. +- `rememberSaveable`, `SavedStateHandle`, activity recreation, or restoration logic: + recreate the activity and verify the final state by domain ID, not list position. +- Loading or data presentation: exercise every state represented by the owning state + model among empty, loading, error, and populated; do not invent states absent from + that model. +- Click, toggle, selection, or navigation behavior: verify touch, keyboard focus and + activation, and D-pad focus and activation. +- Semantics, focus, accessible text, row merging, or icon meaning: inspect the semantics + tree and run TalkBack on an emulator or device. Record the spoken label, state, focus + order, and activation result. +- Form fields, scrolling, or insets: open the IME, traverse from the first control to the + last control, and activate the primary action with the IME still visible. +- A setting, profile, routing rule, or other persisted value: perform the action, close + and reopen the screen, and verify the value through the owning ViewModel/repository. + +A screenshot verifies appearance only. It does not verify semantics, focus, activation, +state ownership, persistence, or recreation. Record every mapped check that did not run +under `Not run`; do not claim the corresponding behavior is verified. diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/CLAUDE.md b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GEMINI.md b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GEMINI.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GEMINI.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/docs/AGENTS.md b/docs/AGENTS.md deleted file mode 100644 index 271a3a5a..00000000 --- a/docs/AGENTS.md +++ /dev/null @@ -1,84 +0,0 @@ -# v2rayNG — Agent guide - -## Build - -```sh -cd V2rayNG -./gradlew assembleFdroidDebug # or assemblePlaystoreDebug -``` - -Kotlin 2.4.0, AGP 9.2.1, Gradle Kotlin DSL. No lint/format/typecheck tasks configured. - -## Test - -```sh -cd V2rayNG && ./gradlew test -``` - -JUnit 4 + Mockito. Unit tests only under `app/src/test/java/`. No instrumented tests beyond defaults. - -## Project structure - -``` -V2rayNG/ - app/ - src/main/java/com/v2ray/ang/ - AngApplication.kt # extends MultiDexApplication, inits MMKV + WorkManager - AppConfig.kt # all constants, pref keys, URLs, ports, tags - core/ # v2ray core integration - CoreServiceManager.kt # start/stop core, traffic stats, delay measurement - CoreConfigManager.kt # generates JSON config for v2ray core - CoreNativeManager.kt # JNI bridge to libv2ray AAR - CoreOutboundBuilder.kt # outbound config construction - CoreConfigContextBuilder.kt - service/ # Android foreground services - CoreVpnService.kt # VPN mode (VpnService) - CoreProxyOnlyService.kt # proxy-only mode (no VPN) - CoreTestService.kt # delay test - TProxyService.kt - DialerNativeService.kt / DialerWebviewService.kt # browser dialer - RealPingWorkerService.kt # WorkManager-based real ping - QSTileService.kt # quick settings tile - ProcessService.kt - handler/ # business logic - MmkvManager.kt # all MMKV CRUD (servers, subs, settings, routing) - SettingsManager.kt # preference defaults, config generation (633 lines) - AngConfigManager.kt # server config operations - NotificationManager.kt - SpeedtestManager.kt - SubscriptionUpdater.kt - WebDavManager.kt - UpdateCheckerManager.kt - CertificateFingerprintManager.kt - SettingsChangeManager.kt - ui/ # activities, adapters, fragments - MainActivity.kt # main screen with drawer + tabs - ServerActivity.kt # edit server config - ServerCustomConfigActivity.kt / ServerGroupActivity.kt / ServerProxyChainActivity.kt - SettingsActivity.kt / PerAppProxyActivity.kt / AppPickerActivity.kt - ScannerActivity.kt / LogcatActivity.kt - RoutingSettingActivity.kt / RoutingEditActivity.kt - SubSettingActivity.kt / SubEditActivity.kt - UserAssetActivity.kt / UserAssetUrlActivity.kt - TaskerActivity.kt / UrlSchemeActivity.kt - BackupActivity.kt / CheckUpdateActivity.kt / AboutActivity.kt - fmt/ # protocol URL parsers (VMESS, VLESS, TROJAN, SS, SOCKS, etc.) - dto/ # data classes + entities/ - enums/ # EConfigType, Language, RoutingType, etc. - extension/_Ext.kt # extension functions (toast, traffic string, etc.) - util/ # Utils, JsonUtil, HttpUtil, LogUtil, etc. - receiver/ # BootReceiver, TaskerReceiver, WidgetProvider - contracts/ # interfaces (ServiceControl, Tun2SocksControl) - helper/ # QRCodeScannerHelper, PermissionHelper, FileChooserHelper etc. -``` - -## Key facts - -- **Storage**: MMKV exclusively — never SharedPreferences. `MmkvManager` is the data layer. -- **Core**: Native AAR (`libv2ray`) from [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite). Prebuilt `.aar` files go in `app/libs/`. -- **Services** run in dedicated process `:RunSoLibV2RayDaemon`. `CoreServiceManager` controls start/stop lifecycle. -- **Two modes**: VPN (`CoreVpnService`, uses `VpnService.Builder`) or proxy-only (`CoreProxyOnlyService`, local SOCKS/HTTP). -- **Flavors**: `fdroid` (suffix `.fdroid`) and `playstore` (no suffix). ABI version codes differ per flavor. -- **hev-socks5-tunnel**: Optional tun2socks binary. Build with `./compile-hevtun.sh` (requires `NDK_HOME`). -- **ViewBinding** enabled, no DataBinding. -- **No CI**, no pre-commit hooks, no lint/format enforcement.