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.
This commit is contained in:
eliotcougar authored and GitHub committed 2026-08-29 15:59:49 +08:00
1 parent e2dc37ba26
commit c73498b064
11 files changed
+387 -84

No files matched your search

@@ -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.
@@ -0,0 +1 @@
@AGENTS.md
@@ -0,0 +1 @@
@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.
@@ -0,0 +1 @@
@AGENTS.md
@@ -0,0 +1 @@
@AGENTS.md