{"owner":"gkd-kit","repo":"gkd","hasSkills":true,"totalSkillsCount":3,"totalTokensCount":8559,"categories":["anthropic-skill","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/android-api-diff/agents/openai.yaml",".agents/skills/android-api-diff/SKILL.md","AGENTS.md"],"skills":{".agents/skills/android-api-diff/agents/openai.yaml":"interface:\n  display_name: 'Android API Diff'\n  short_description: 'Query Android API changes and generate hidden API code'\n  default_prompt: 'Use $android-api-diff to inspect an Android framework API across versions.'\n",".agents/skills/android-api-diff/SKILL.md":"---\nname: android-api-diff\ndescription: Inspect Android framework Java and AIDL APIs across versions with the android-api-diff CLI. Use when resolving an Android API to frameworks/base source, fetching the complete content of a tagged source file, comparing signatures or availability by Android version, explaining missing APIs, generating Java hidden-API access code, or explicitly prewarming the local query cache.\n---\n\n# Android API Diff\n\nUse the `android-api-diff` CLI as the only tool interface. Keep its default JSON\noutput so results remain machine-readable.\n\n## Route the request\n\n- Run `generate` when the user wants Java hidden-API access code. It performs the\n  version query internally, so never call `query` first.\n- Run `query` when the user wants signatures, version ranges, availability,\n  missing reasons, or source metadata.\n- Run `resolve` when the user only needs the matching `frameworks/base` file and\n  target kind.\n- Run `source` when the user wants the complete content of a known source path\n  at an exact Android release tag. If only an API name is known, run `resolve`\n  first and use the chosen `result.candidates[].source.path`.\n- Run `preload` only when the user explicitly asks to preload several APIs.\n\nAlways quote API names because constructors and member references can contain\nshell-sensitive characters.\n\n```sh\nandroid-api-diff resolve \"ContentObserver()\"\nandroid-api-diff source android-17.0.0_r1 core/java/android/accessibilityservice/AccessibilityButtonController.java\nandroid-api-diff query \"IActivityManager.getTasks\" --min-sdk 28\nandroid-api-diff generate \"ActivityThread.currentApplication\" --min-sdk 28\nandroid-api-diff preload \"ContentObserver()\" \"IActivityManager.getTasks\" --min-sdk 28\n```\n\nOmit `--min-sdk` unless the user provides a minimum Android API level. The CLI\nthen uses its built-in default.\n\n## Interpret results\n\nTreat exit code `0` together with top-level `ok: true` as success. Read the\noperation result from the top-level `result` field.\n\n`resolve` returns `result.candidates` in recommended order, or an empty array\nwhen no source matches. `query` and `generate` include the same candidate list\nand inspect the recommended source by default. When the user specifies another\nmatching source, pass `--source-path <path>` to `query` or `generate`; retain the\noriginal API reference to preserve its member or constructor target. Candidate\nmetadata alone does not prove that a member exists in a particular version.\n\nFor `query`, describe range endpoints as the first or last tag checked in the\ncurrent snapshot. A `last-checked` endpoint is not a promise that no later\nrevision exists.\n\nFor member queries, treat `result.overloads` as the source of truth. Each item\nhas a stable `overloadId`, its latest checked `signature` and `member`, and\nindependent `ranges`. A range with `missingReason: \"overload-not-found\"` means\nthe member name exists in that source revision but that specific parameter\nsignature does not. Use top-level `result.ranges[].overloadIds` only when\ndescribing which overloads were available together.\n\nOn a nonzero exit code, read the structured JSON error from stderr and report its\n`error.code` and `error.message`. Do not add an automatic retry: the CLI network\nlayer already retries transient fetches.\n\n## Choose an interface constant access strategy\n\nFour strategies are supported and have already been validated in the companion\nremap repository. Choose based on the user's build and value requirements;\ndo not require another feasibility experiment unless the user requests one.\n\n| Strategy                | When to use it                                                                                                 | Declaration or access                                                                      |\n| ----------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| Reflection              | Read the device's value without a remapped field access                                                        | Read the original interface field reflectively.                                            |\n| Interface stub          | Preserve runtime field access in a compatible remap build                                                      | Initialize the interface field with `RemapStub.value()`.                                   |\n| Consistent constant     | Embed a supported primitive expression that agrees across checked versions                                     | Keep the original initializer in the generated interface.                                  |\n| Implementing-class stub | Read the device's value through a known platform class implementing the interface, without `RemapStub.value()` | Declare a `public static`, non-final field with no initializer in that class's remap stub. |\n\nThe current `generate` command automatically chooses the consistent-constant\nstrategy for supported primitive initializers whose token sequences match across\nchecked definitions, ignoring whitespace and comments. It does not evaluate\nexpressions: `0x1` and `(1 << 0)` differ. Constant references, unsupported\nexpressions, missing initializers, differing definitions, and non-primitive\ntypes fall back to `RemapStub.value()`. Agreement covers only checked versions;\ndo not download all versions to strengthen this conclusion.\n\nReflection and implementing-class stubs are adaptations of the generated code,\nnot CLI flags or automatic generator fallbacks. Honor an explicitly selected\nstrategy. For an interface/R8 incompatibility, use a consistent constant when\nthe generated result supports it; otherwise prefer a known suitable platform\nimplementing class, or reflection when no suitable class is known. Do not\npresent the `RemapStub.value()` fallback as resolving that incompatibility.\n\nFor example, access `IBinder.SHELL_COMMAND_TRANSACTION` through a stub mapped\nto the platform's `Binder` implementation:\n\n```java\n@li.songe.remap.RemapType(android.os.Binder.class)\npublic class BinderHidden {\n    public static int SHELL_COMMAND_TRANSACTION;\n}\n```\n\nRead `BinderHidden.SHELL_COMMAND_TRANSACTION` at the call site. Keep the field\nname and type matching the original interface declaration; omit both `final`\nand an initializer. This strategy also applies to reference types and values\nthat differ between versions. Map to the real platform implementing class;\nmerely declaring a field in an unmapped application subclass creates a separate\nfield. Use these declarations for reads, since the original interface field is\nfinal.\n\n## Handle a missing CLI\n\nIf `android-api-diff` is not installed, stop and tell the user to run:\n\n```sh\nnpm install --global android-api-diff@latest\nandroid-api-diff skill install\n```\n\nRun the Skill command from the root of the current Git or Gradle project so it\nremains project-scoped. Do not implement or simulate version checks. The\npackage manager owns CLI update detection and upgrades.\n","AGENTS.md":"# Repository Instructions\n\n设计与依赖见 [应用架构](docs/architecture.md)，启动和 HTTP 调试见 [Desktop 开发](gkd-app/README.md)，固定目录见 [本地产物](docs/local-development.md)。本文件只维护开发约束。\n\n## 模块与平台\n\n- 模块统一使用 `gkd-` 前缀，依赖方向为 `gkd-android` → `gkd-app` → `gkd-db` / `gkd-selector`，禁止反向依赖。gkd-app 承载完整应用：commonMain 放业务、UI、ViewModel、导航；androidMain/jvmMain 放平台实现。gkd-android 仅负责 Manifest、原生 res、渠道、签名、打包和设备测试；JVM 启动/打包位于 gkd-app，TypeScript 工具位于根 `scripts/desktop`。\n- Desktop 仅为开发宿主，不另建产品后端、复制页面或仅为它拆共享模块/空壳 Repository；数据库职责留在 gkd-db。领域逻辑不得依赖 Compose UI/运行时、Android Context 或宿主；允许引用官方 Compose Resources 资源对象，不将资源对象视为 UI 依赖。Android/JVM 共用实现可直接在 commonMain 使用两端支持的 Java API，出现实际差异或非 JVM 目标时再隔离，不增中间源码集。\n- 公共根包及 gkd-app Android namespace 为 `li.gkd.app`，UI/导航/ViewModel 位于 `li.gkd.app.ui.*`；Desktop 同样使用该包层级，以 jvmMain 和职责文件名区分，不设 desktop 包。源码目录与包名一致。gkd-android namespace 为 `li.gkd.android`，Manifest 使用完整组件名。\n- androidMain 普通工具、适配、UI 接入及基类按职责归入 `li.gkd.app.*`；已注册 Application、Activity、Service、磁贴、特权入口及紧密关联实现保留原身份。`com.google.android.accessibility.selecttospeak.SelectToSpeakService` 的包名、路径和注册不变。包名整理不得改变组件、路由序列化名称/参数、AIDL 或持久化兼容标识。\n- Android 权限、Activity Result、通知、悬浮窗、Service 和特权进程位于 gkd-app/src/androidMain，由真实组件管理生命周期。gkd-aidl 只编译 Binder 协议；gkd-hidden-api 只提供 stub。引用 stub 的模块必须独立完成对应字节码的 remap。\n- ViewModel、Repository、业务状态和流程在 commonMain 保持唯一实现，ViewModel 使用 `XxxViewModel` 命名。禁止同名宿主子类、空壳继承、旧路径 typealias 或纯转发包装；共享类默认不可继承，宿主在原 ViewModelStoreOwner 直接创建，复杂/复用构造可用明确工厂。\n- 平台差异优先使用对应功能包的函数级 `expect/actual`，实现文件为 `.android.kt` / `.jvm.kt`；禁止整份 ViewModel、Repository 或业务流程 actual 化。能力签名只用公共类型，不导航、弹 Toast 或读取 UI ViewModel；仅 UI 组装可用 Composable expect/actual，需平台 UI 能力时显式接收所属 UiHost。无生命周期工具只持有 Application Context，Activity/窗口引用限于生命周期绑定的 UI 会话。\n- 不支持的平台动作统一返回 `PlatformResult<T>`，无数据成功为 `Unit`；不得另造 Unsupported 类型或用 Boolean/null 暗示不支持。契约明确成功含义，保留异常/取消传播，不替代业务结果、用户取消或权限状态。\n- 应用仓库由应用生命周期唯一持有，禁止重复创建、可替换全局实例和无意义转发。无独立会话需求时优先使用 object；需要独立生命周期或明确依赖时允许普通类及构造注入。平台依赖先于消费者初始化，所属生命周期结束时清理协程和资源，不为测试增加生产重置接口。\n\n## UI 与生命周期\n\n- 两端共用 commonMain 的生产页面、组件、业务和事件链，模拟输入也经过生产 Repository/ViewModel；共享代码不得直接依赖 Android Application、平台专属 Store/Service 或伪造全局实例；共享 MainViewModel 的访问遵循下述单主界面规则。Desktop 默认四标签，组件目录仅显式诊断。新增入口同时覆盖双端及下游弹窗、面板、返回路径，不用整页 Toast/占位掩盖缺失；交付需业务、读写、生命周期和验证证据。\n- 两端共用 `GkAppNavigation` 注册及 `GkNavigation` 保存状态/条目生命周期；禁止 Desktop 字符串分支注册生产路由、复制平台路由或另写保存/导航规则。普通/编辑页面过渡统一声明，公共页面只注册一次；HTTP 标签等状态读取生产来源。\n- MainViewModel 属于主界面的 ViewModelStore，页面 ViewModel 属于具体导航条目；有需要保留的页面状态或事件流程时按需创建，不为纯展示或组件瞬时状态创建。根不持有其他 ViewModel 或子 ViewModelStore；子路由用条目标准 API 获取，多次入栈实例独立、出栈清理，不预建、缓存或跨路由借用。导航不重建仓库，宿主退出清理 ViewModel/协程；Desktop 场景重载只重置页面/草稿，不清空数据或模拟配置。\n- 路由及私有 Composable 可获取页面 ViewModel、处理平台 UI；主题、通用布局、可复用组件和工具仅接收状态/事件，不通过默认参数、工厂、CompositionLocal 或全局入口隐藏获取。Service、悬浮窗、后台任务使用自身作用域和应用仓库，不借用或补建页面 ViewModel。\n- Android/Desktop 均采用单主界面设计，同一应用运行实例只存在一个根 MainViewModel，其生命周期覆盖所有子页面 ViewModel。生产页面、其私有 Composable 及所属子页面 ViewModel（含 commonMain）可通过 `MainViewModel.requireCurrent()` 获取已注册的根实例，无需逐层传递。宿主完成初始化和必要绑定后注册，根实例清理时注销；静态入口只提供访问，不创建实例或延长其生命周期。主题、通用布局、可复用组件、工具、Repository、Service、悬浮窗和后台任务不得通过该入口获取，也不引入 LocalMainViewModel。\n- 页面 ViewModel 可组织校验、保存、确认弹窗及后续导航等事件流程，按需使用 MainViewModel 的页面能力；Compose 负责展示、收集状态、转发事件及 IME、焦点等组合/宿主相关操作。不为缩短调用增加纯转发方法，不把页面流程转移到根 ViewModel。平台 UI 操作显式接收所属 UiHost 或事件回调，宿主引用绑定所属生命周期，不由页面 ViewModel 长期持有。\n- 自定义页面业务状态，包括编辑草稿及基准、搜索/筛选条件、分页、保存冲突和页面切换后需要保留的状态，优先由所属页面 ViewModel 使用 `MutableStateFlow<T>` 管理，对外暴露只读 `StateFlow<T>` 和明确修改方法，默认不使用 `rememberSaveable` 或自定义 Saver。页面状态随所属导航条目清理，不为保留它而提升到 MainViewModel；StateFlow 本身不提供落盘或进程重启恢复。\n- 滚动、焦点、动画、拖拽及简单组件展开等纯 UI 工具状态留在 Compose，允许使用官方 `rememberXxxState`；确需恢复的简单工具/组件状态可使用 `rememberSaveable`，无需恢复的瞬时状态使用 `remember`。不为规避 rememberSaveable 将这些工具状态搬入 ViewModel，也不以工具状态名义承载页面业务对象。`rememberXxxState` 不访问 ViewModel、数据库、Store、Service 或导航。组合工作使用 rememberCoroutineScope，稳定且绑定同一组合的 scope 不作冗余 remember key。主题状态、时序及深色优先级双端共用，宿主只给系统主题/色板。\n- `XxxUiState/Actions` 仅用于确有需要的复用、预览或复杂契约；UiState 是不可变快照，不含 Flow、Paging 或高频状态；多个构造路径重复映射时才提取私有构建函数。业务状态不通过 CompositionLocal 传递。\n- 应用/仓库只读 Flow 在实际消费处用 `collectAsStateWithLifecycle` 收集，Paging 用专用 API，高频状态放最小子树；不为转发而复制进 UiState/ViewModel；生产路由页面可获取所属 ViewModel，可复用组件接收不可变值和明确事件。ViewModel 可管理业务查询聚合、Paging 缓存、加载/失败和保存冲突基准，不因 map/combine 一律迁出。\n- ViewModel 状态仅在所属 VM 内修改，对外只暴露只读状态及明确事件方法；可变字段保持私有，只读 StateFlow 使用 Explicit Backing Fields 持有 MutableStateFlow，禁止 `_xxxFlow` 双属性及 `.asStateFlow()`。Composable 条件输出可用条件区块或清晰的提前返回，优先沿用邻近风格，不为形式增加嵌套或无关重构。\n- 共享页面用 `GkScaffold` 接收宿主 Insets（Android 原生，Desktop 模拟），不重复加系统留白。滚动内容末尾用 `GkPageBottomSpace()` / LazyColumn 的 `gkPageBottomSpace()`，高度由 GkPageBottomSpaceDefaults 统一；已有末尾 item 可内嵌但不重复添加，不替代系统栏或 IME Insets。\n- 动画不阻塞交互：禁止因过渡、图标变形、普通开关短暂保存或假设快速点击而禁用、节流、延时解锁或吞点击。禁用仅对应数据、输入、权限等业务前提，写入一致性交业务层处理。退场重复内容可隐藏于无障碍导航，但不改变颜色/增加等待；检查覆盖过渡中点击和切换。\n\n## 最小实现与防御边界\n\n- 按明确需求和已确认的问题实现最小改动。不得为假设中的异常、竞态或未来扩展，新增状态、缓存、刷新机制、抽象层或生命周期联动。\n- 添加校验、重试、兜底或异常捕获前，先核对依赖 API 和现有调用链的契约；已有保证不得重复实现。不确定契约时先查源码，不用防御代码代替确认。\n- 操作只检查自身必需的前提，不因单项操作刷新全部能力，不在 UI、ViewModel、服务层重复执行同一检查。UI 提示与执行前校验确有不同职责时可分别保留。\n- 不把未知、加载中或查询失败等同于权限拒绝；不得据此扩大为停服、断连、切换模式或修改用户设置，除非需求明确要求。\n- 异常在能实际处理它的边界捕获；禁止仅为返回默认值、重复记录日志或捕获后原样抛出而增加包装。\n- 抽象应消除已有重复或隔离真实差异。简单逻辑优先直接调用，不引入仅转发、预留扩展或另建同义状态的封装。\n- 保留外部输入校验、权限边界、事务一致性和资源释放等必要保护；新增保护应能说明具体失败场景，以及现有机制为何不足。\n\n## 状态、存储与副作用\n\n- Room 查询保持冷 Flow，在所属 ViewModel 按一致性边界聚合后转为 `StateFlow<Loadable<XxxUiState>>`；Loading 表示首发未完整，Ready(emptyList()) 表示已加载为空，禁止空集合初值、计数器或 attachLoad 推断加载完成。\n- 数据库/文件/网络写入和 Service 启停由明确用户、系统事件或领域方法触发，在 Repository/Store 完成一致性处理；禁止用派生 map/combine/stateIn 的 collect/onEach/watch 或 Composable 状态监听驱动。允许单一权威状态同步到幂等外部投影，但回调不再读取其他状态拼装写入。\n- 一致读取字段由事实源提供同一不可变快照；debounce、conflate、collectLatest 等调度机制不保证一致性；锁或事务必须覆盖全部相关读写，禁止仅锁消费端便宣称多个独立状态源原子一致。Repository 不读取资源/弹 Toast，错误保留原因交 UI 显示。生产设置文件名、字段、默认值是契约，迁移验证旧数据加载、失败重试、并发写入和恢复回滚。\n- 两端复用 AppStorageLayout 与真实 Repository/DAO；文件事务、归档、Cookie、网络配置和维护放共享层，宿主仅适配根目录、编解码、文件选择及系统动作。Desktop profile 只给平台输入，不另存统计、记录、排序或备份恢复事实源。\n- 宿主只注入 files/cache/privateFiles 根目录，布局遵循 [本地产物](docs/local-development.md)。图片缓存、网页用户数据、归档、临时文件须实际接入，不能仅创建目录；引擎安装可用工具缓存。Desktop privateFiles 与 files 同根，Android 保留系统私有目录，profile/导入标记/模拟配置在 data 外。\n- 自定义产物仅放根 `.local/`，工具标准目录不变。日常数据固定 `.local/desktop/`，禁止数据路径环境变量或 build 目录；`--test` 使用 `.local/tests/desktop/<随机标识>/`，不访问日常、真机或生产数据。写入检查先核对隔离标记，报告/截图在会话内；可复用工具提交源码目录。不得随缓存清理日常数据、备份或未处理恢复记录；不增旧目录回退/双写。\n\n## 资源与 Kotlin 规范\n\n- 禁止新增 XML，例外仅 Compose strings/plurals、Android 原生字符串及语言限定资源、app_icon/service 等平台配置。可用 Kotlin 表达的 UI/图标用 `.kt`；页面图标必须 ImageVector，drawable XML 仅限 Manifest 等平台配置及其依赖，即使同图标双用，页面仍用 ImageVector。不确定例外时先问用户。\n- 原生 res 归 gkd-android，gkd-app 不引用其 R；通知小图标通过 Application Manifest `notificationSmallIcon` 的 `android:resource` 注入，运行时校验非零，不持久化 ID。共享颜色用 Kotlin，Compose Resources 位于 gkd-app。\n- 共享及普通 Android Kotlin 文案统一官方 Compose Resources（`Res.string/plurals`），不建自定义 UiString/Desktop 文案表或原生副本；只有 Manifest/平台配置/变体标签保留原生 ID，Kotlin 可按需读取平台标签。\n- 官方资源对象（如 `StringResource`、`PluralStringResource`）不限于 UI 用途，允许枚举、业务状态、异常、Repository 等对象持有和传递，供 UI、HTTP、后台任务和导出等场景使用；在实际消费或输出边界按需调用官方 API 解析。资源对象与已解析的文案字符串分开处理：不在枚举、单例或静态初始化中解析、缓存文案字符串；语义/参数与持久化数据分离，语言切换不改写用户模板。\n- 字符串及复数资源统一使用 `import li.gkd.app.resources.*` 导入，不逐项导入 `Res` 或资源访问器；调用保持 `Res.string.xxx` / `Res.plurals.xxx`，文案解析继续使用官方 API。\n- 参数只用 `%1$s`、`%2$s` 等位置文本占位符；禁止数值/精度/补零格式指令，Kotlin 按明确 Locale/精度先格式化。Compose 用官方 Composable API，协程用 suspend getString/getPluralString；getSync 仅限低频同步边界，不用于静态初始化、高频或新增 Compose 路径，不用 runBlocking(Dispatchers.Main)。新增翻译/复数/限定符验证双端选择替换，校验任务不生成平台副本，访问器/打包交官方插件。\n- Kotlin 可见性按实际访问需求收窄，优先使用 private；gkd-app 模块禁止使用 internal 关键字，需要跨文件或类访问的声明使用默认 public，其他模块允许 internal。仅为公开属性收窄可见性/可变性的 `_xxx` 字段改用 Explicit Backing Fields；普通私有缓存、生成命名及 Lambda `_` 不受限。避免循环静态初始化。\n- 工具按职责组织并优先沿用邻近代码风格；无状态工具可用顶级函数，有共享状态或明确命名空间需求时使用 object，不为形式新增包装。XxxExt.kt 只放扩展，普通工具放职责明确的文件；不借规范调整进行无关重构。\n- 跨文件/页面复用 UI 统一 `Gk` + PascalCase，名称描述用途，禁用 Perf/Custom 泛化前缀。单组件文件同名，组件族及配套声明可同文件，文件以 Gk 开头并描述组件族；配置用 GkAbcDefaults/Colors，图标用 GkIcon/GkIcons。页面、私有 Composable、Preview、工具、Modifier 扩展、独立状态及其 Render() 不强制此前缀。\n\n## Desktop 运行时\n\n- 模拟配置、开发 HTTP 和工具不承担跨版本兼容，模型变更同步消费者，可重置，不增迁移/旧格式适配；生产数据兼容不受影响。开发工具统一 TypeScript、根 scripts/desktop 与 `pnpm app:tools`，复用公共 HTTP/控件逻辑，不另建同类 PowerShell/Python 脚本。\n- 预览/控制窗口同进程，关闭控制窗口不退出 App 且可重新打开（F12）。模拟器为单一状态源，控件和 HTTP 同命令路径。系统栏由 jvmMain 窗口模拟层实时绘制，时间取当前时钟，系统栏/挖孔统一计算 Insets；不以静态或整页截图替代 UI，不把模拟网络/电量冒充真机。未实现内容如实标识，不宣称模拟成功。\n- 不支持仅针对具体动作，以非模态 Toast 显示“当前平台不支持”，不替代整页或伪造成功；编辑/列表/文件业务继续真实执行。用户明确要求的 priv-ui 模拟可修改 Desktop 授权/启动/停止状态，日志标记 simulation，不执行真实特权命令。\n- priv-ui 只用公开 PrivilegeScreen、状态/actions，禁用内部 renderer/模拟器或 INVISIBLE_MEMBER；页面及系统提示 Insets 分别注入，宿主模拟状态机与控制窗口特权可用状态同步。使用版本目录的远程稳定版，升级验证双端编译和实际页面。\n- HTTP 仅在 jvmMain、绑定 127.0.0.1，不进入 Android 或混用业务服务；状态修改在 UI 线程。语义树/操作/截图明确 app|controls，各窗口节点快照独立；操作执行真实控件，不用直接改状态冒充点击。模拟返回先收 IME，再处理弹窗/编辑/页面。\n- 截图区分实时捕获和重新组合渲染，后者不证明当前焦点/滚动/弹窗。Desktop 不替代 Android 像素验收；对照须同页面状态、dp、fontScale、Insets、字体和颜色。设备导出仅 UI 所需输入，设备资料/字体不提交。\n\n## 验证与 Git\n\n- 按改动做最小必要验证：共享逻辑/UI、Desktop 只执行对应 JVM 编译及相关业务测试，不因 commonMain 或文档含双端命令追加 Android。仅 Android 实现、资源、Manifest、AIDL、隐藏 API、依赖/构建链路、专项规则或用户要求才做 Android 验证；默认仅 gkd 渠道，未经明确要求禁止 play。\n- UI 开发用 `./gradlew.bat :gkd-app:run`，构建前停止本任务启动的 Desktop JVM，避免覆盖运行中 JAR。网页使用版本目录 WebView2 和系统 Runtime。UI 默认使用对应编译及人工/HTTP 检查，不要求自动 UI 套件；仅为已发生、难以稳定人工复查的重要回归增加小范围自动测试，禁止仅复述静态布局或实现细节。业务状态机、持久化、事务恢复、协议/兼容测试保留，不按 ui 包名删除。\n- 新测试说明输入、预期及回归，优先边界/异常/兼容行为；存储测试验证实际读写恢复，不仅路径。测试遵循生产生命周期，可用独立会话和构造依赖实现隔离；全局单例的集成测试复用一次真实应用生命周期及隔离存储。禁止为测试拆散生产逻辑、扩大可见性、增加专用 API、ViewModel、嵌套 Store 或生产重置/替换接口；不以逐类工厂、fork 或 ClassLoader 掩盖生命周期缺陷。\n- 不测试枚举/常量集合、连续编号、同注册表推导关系或类型系统保证的静态声明；外部协议/持久化/跨版本标识例外，名称或注释说明保护的契约。\n- Android 权限、IME、系统栏、预测返回、无障碍/Service 按需 adb 验证。真机/模拟器交互前记录自动化开关与运行模式，关闭 enableAutomator 并退出自动化模式，核对后再检查；finally 在成功/失败/中断后仅恢复本次字段并核对，不用旧整份配置覆盖。仅 Desktop 检查不改真机。\n- 用户仅要求提交/推送时，只做轻量核对及 Git 操作；未经明确要求，不扩展为深度审查、临时 worktree 验证、全量构建/测试、Release Gate 或发布检查。\n\n## 依赖与 Android 专项\n\n- 外部 npm 依赖/版本集中根 package.json，private 子包所有 dependencies/devDependencies/optionalDependencies/peerDependencies 仅用 workspace:。可发布子包自行声明独立安装所需运行时/optional/peer 依赖，公共开发工具仍在根；变更同步清单和 pnpm 锁文件。\n- Android framework Java/AIDL 源码定位、跨版本签名/可用性、缺失分析及 Java hidden-API 生成必须使用 [android-api-diff skill](.agents/skills/android-api-diff/SKILL.md) 与其 CLI，保留默认 JSON，不自行模拟版本检查。安装/更新 skill 在根执行 `android-api-diff skill install`。\n- 修改隐藏接口/父类实现、系统回调、remap 或相关模块依赖/R8 配置，以及排查相关混淆 Release 故障时，必须使用 [android-hidden-api-r8 skill](.agents/skills/android-hidden-api-r8/SKILL.md)。检查最终应用模块 `gkd-android` 的实际 R8 输入及优化后的运行时契约，不以源码 override、编译成功或 Debug 正常代替；`gkd-app` 可见的 `gkd-hidden-api` stub 不代表最终 R8 可见。默认仅验证受影响的 gkd Release 路径，未经用户要求或证据支持不扩大审计范围。\n- 特权进程 UserService 的可能失败 Binder 方法在最外层末端 `catch (e: Throwable)` 捕获可恢复错误，通过 Binder 可传输异常/结果保留原类型、消息、堆栈；不只在主进程捕获，不让 NoSuchMethodError 等 LinkageError 逃逸崩溃。VirtualMachineError/ThreadDeath 等终止错误可原样抛出，不伪装普通失败。\n"},"files":{".agents/skills/android-api-diff/agents/openai.yaml":"interface:\n  display_name: 'Android API Diff'\n  short_description: 'Query Android API changes and generate hidden API code'\n  default_prompt: 'Use $android-api-diff to inspect an Android framework API across versions.'\n",".agents/skills/android-api-diff/SKILL.md":"---\nname: android-api-diff\ndescription: Inspect Android framework Java and AIDL APIs across versions with the android-api-diff CLI. Use when resolving an Android API to frameworks/base source, fetching the complete content of a tagged source file, comparing signatures or availability by Android version, explaining missing APIs, generating Java hidden-API access code, or explicitly prewarming the local query cache.\n---\n\n# Android API Diff\n\nUse the `android-api-diff` CLI as the only tool interface. Keep its default JSON\noutput so results remain machine-readable.\n\n## Route the request\n\n- Run `generate` when the user wants Java hidden-API access code. It performs the\n  version query internally, so never call `query` first.\n- Run `query` when the user wants signatures, version ranges, availability,\n  missing reasons, or source metadata.\n- Run `resolve` when the user only needs the matching `frameworks/base` file and\n  target kind.\n- Run `source` when the user wants the complete content of a known source path\n  at an exact Android release tag. If only an API name is known, run `resolve`\n  first and use the chosen `result.candidates[].source.path`.\n- Run `preload` only when the user explicitly asks to preload several APIs.\n\nAlways quote API names because constructors and member references can contain\nshell-sensitive characters.\n\n```sh\nandroid-api-diff resolve \"ContentObserver()\"\nandroid-api-diff source android-17.0.0_r1 core/java/android/accessibilityservice/AccessibilityButtonController.java\nandroid-api-diff query \"IActivityManager.getTasks\" --min-sdk 28\nandroid-api-diff generate \"ActivityThread.currentApplication\" --min-sdk 28\nandroid-api-diff preload \"ContentObserver()\" \"IActivityManager.getTasks\" --min-sdk 28\n```\n\nOmit `--min-sdk` unless the user provides a minimum Android API level. The CLI\nthen uses its built-in default.\n\n## Interpret results\n\nTreat exit code `0` together with top-level `ok: true` as success. Read the\noperation result from the top-level `result` field.\n\n`resolve` returns `result.candidates` in recommended order, or an empty array\nwhen no source matches. `query` and `generate` include the same candidate list\nand inspect the recommended source by default. When the user specifies another\nmatching source, pass `--source-path <path>` to `query` or `generate`; retain the\noriginal API reference to preserve its member or constructor target. Candidate\nmetadata alone does not prove that a member exists in a particular version.\n\nFor `query`, describe range endpoints as the first or last tag checked in the\ncurrent snapshot. A `last-checked` endpoint is not a promise that no later\nrevision exists.\n\nFor member queries, treat `result.overloads` as the source of truth. Each item\nhas a stable `overloadId`, its latest checked `signature` and `member`, and\nindependent `ranges`. A range with `missingReason: \"overload-not-found\"` means\nthe member name exists in that source revision but that specific parameter\nsignature does not. Use top-level `result.ranges[].overloadIds` only when\ndescribing which overloads were available together.\n\nOn a nonzero exit code, read the structured JSON error from stderr and report its\n`error.code` and `error.message`. Do not add an automatic retry: the CLI network\nlayer already retries transient fetches.\n\n## Choose an interface constant access strategy\n\nFour strategies are supported and have already been validated in the companion\nremap repository. Choose based on the user's build and value requirements;\ndo not require another feasibility experiment unless the user requests one.\n\n| Strategy                | When to use it                                                                                                 | Declaration or access                                                                      |\n| ----------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| Reflection              | Read the device's value without a remapped field access                                                        | Read the original interface field reflectively.                                            |\n| Interface stub          | Preserve runtime field access in a compatible remap build                                                      | Initialize the interface field with `RemapStub.value()`.                                   |\n| Consistent constant     | Embed a supported primitive expression that agrees across checked versions                                     | Keep the original initializer in the generated interface.                                  |\n| Implementing-class stub | Read the device's value through a known platform class implementing the interface, without `RemapStub.value()` | Declare a `public static`, non-final field with no initializer in that class's remap stub. |\n\nThe current `generate` command automatically chooses the consistent-constant\nstrategy for supported primitive initializers whose token sequences match across\nchecked definitions, ignoring whitespace and comments. It does not evaluate\nexpressions: `0x1` and `(1 << 0)` differ. Constant references, unsupported\nexpressions, missing initializers, differing definitions, and non-primitive\ntypes fall back to `RemapStub.value()`. Agreement covers only checked versions;\ndo not download all versions to strengthen this conclusion.\n\nReflection and implementing-class stubs are adaptations of the generated code,\nnot CLI flags or automatic generator fallbacks. Honor an explicitly selected\nstrategy. For an interface/R8 incompatibility, use a consistent constant when\nthe generated result supports it; otherwise prefer a known suitable platform\nimplementing class, or reflection when no suitable class is known. Do not\npresent the `RemapStub.value()` fallback as resolving that incompatibility.\n\nFor example, access `IBinder.SHELL_COMMAND_TRANSACTION` through a stub mapped\nto the platform's `Binder` implementation:\n\n```java\n@li.songe.remap.RemapType(android.os.Binder.class)\npublic class BinderHidden {\n    public static int SHELL_COMMAND_TRANSACTION;\n}\n```\n\nRead `BinderHidden.SHELL_COMMAND_TRANSACTION` at the call site. Keep the field\nname and type matching the original interface declaration; omit both `final`\nand an initializer. This strategy also applies to reference types and values\nthat differ between versions. Map to the real platform implementing class;\nmerely declaring a field in an unmapped application subclass creates a separate\nfield. Use these declarations for reads, since the original interface field is\nfinal.\n\n## Handle a missing CLI\n\nIf `android-api-diff` is not installed, stop and tell the user to run:\n\n```sh\nnpm install --global android-api-diff@latest\nandroid-api-diff skill install\n```\n\nRun the Skill command from the root of the current Git or Gradle project so it\nremains project-scoped. Do not implement or simulate version checks. The\npackage manager owns CLI update detection and upgrades.\n","AGENTS.md":"# Repository Instructions\n\n设计与依赖见 [应用架构](docs/architecture.md)，启动和 HTTP 调试见 [Desktop 开发](gkd-app/README.md)，固定目录见 [本地产物](docs/local-development.md)。本文件只维护开发约束。\n\n## 模块与平台\n\n- 模块统一使用 `gkd-` 前缀，依赖方向为 `gkd-android` → `gkd-app` → `gkd-db` / `gkd-selector`，禁止反向依赖。gkd-app 承载完整应用：commonMain 放业务、UI、ViewModel、导航；androidMain/jvmMain 放平台实现。gkd-android 仅负责 Manifest、原生 res、渠道、签名、打包和设备测试；JVM 启动/打包位于 gkd-app，TypeScript 工具位于根 `scripts/desktop`。\n- Desktop 仅为开发宿主，不另建产品后端、复制页面或仅为它拆共享模块/空壳 Repository；数据库职责留在 gkd-db。领域逻辑不得依赖 Compose UI/运行时、Android Context 或宿主；允许引用官方 Compose Resources 资源对象，不将资源对象视为 UI 依赖。Android/JVM 共用实现可直接在 commonMain 使用两端支持的 Java API，出现实际差异或非 JVM 目标时再隔离，不增中间源码集。\n- 公共根包及 gkd-app Android namespace 为 `li.gkd.app`，UI/导航/ViewModel 位于 `li.gkd.app.ui.*`；Desktop 同样使用该包层级，以 jvmMain 和职责文件名区分，不设 desktop 包。源码目录与包名一致。gkd-android namespace 为 `li.gkd.android`，Manifest 使用完整组件名。\n- androidMain 普通工具、适配、UI 接入及基类按职责归入 `li.gkd.app.*`；已注册 Application、Activity、Service、磁贴、特权入口及紧密关联实现保留原身份。`com.google.android.accessibility.selecttospeak.SelectToSpeakService` 的包名、路径和注册不变。包名整理不得改变组件、路由序列化名称/参数、AIDL 或持久化兼容标识。\n- Android 权限、Activity Result、通知、悬浮窗、Service 和特权进程位于 gkd-app/src/androidMain，由真实组件管理生命周期。gkd-aidl 只编译 Binder 协议；gkd-hidden-api 只提供 stub。引用 stub 的模块必须独立完成对应字节码的 remap。\n- ViewModel、Repository、业务状态和流程在 commonMain 保持唯一实现，ViewModel 使用 `XxxViewModel` 命名。禁止同名宿主子类、空壳继承、旧路径 typealias 或纯转发包装；共享类默认不可继承，宿主在原 ViewModelStoreOwner 直接创建，复杂/复用构造可用明确工厂。\n- 平台差异优先使用对应功能包的函数级 `expect/actual`，实现文件为 `.android.kt` / `.jvm.kt`；禁止整份 ViewModel、Repository 或业务流程 actual 化。能力签名只用公共类型，不导航、弹 Toast 或读取 UI ViewModel；仅 UI 组装可用 Composable expect/actual，需平台 UI 能力时显式接收所属 UiHost。无生命周期工具只持有 Application Context，Activity/窗口引用限于生命周期绑定的 UI 会话。\n- 不支持的平台动作统一返回 `PlatformResult<T>`，无数据成功为 `Unit`；不得另造 Unsupported 类型或用 Boolean/null 暗示不支持。契约明确成功含义，保留异常/取消传播，不替代业务结果、用户取消或权限状态。\n- 应用仓库由应用生命周期唯一持有，禁止重复创建、可替换全局实例和无意义转发。无独立会话需求时优先使用 object；需要独立生命周期或明确依赖时允许普通类及构造注入。平台依赖先于消费者初始化，所属生命周期结束时清理协程和资源，不为测试增加生产重置接口。\n\n## UI 与生命周期\n\n- 两端共用 commonMain 的生产页面、组件、业务和事件链，模拟输入也经过生产 Repository/ViewModel；共享代码不得直接依赖 Android Application、平台专属 Store/Service 或伪造全局实例；共享 MainViewModel 的访问遵循下述单主界面规则。Desktop 默认四标签，组件目录仅显式诊断。新增入口同时覆盖双端及下游弹窗、面板、返回路径，不用整页 Toast/占位掩盖缺失；交付需业务、读写、生命周期和验证证据。\n- 两端共用 `GkAppNavigation` 注册及 `GkNavigation` 保存状态/条目生命周期；禁止 Desktop 字符串分支注册生产路由、复制平台路由或另写保存/导航规则。普通/编辑页面过渡统一声明，公共页面只注册一次；HTTP 标签等状态读取生产来源。\n- MainViewModel 属于主界面的 ViewModelStore，页面 ViewModel 属于具体导航条目；有需要保留的页面状态或事件流程时按需创建，不为纯展示或组件瞬时状态创建。根不持有其他 ViewModel 或子 ViewModelStore；子路由用条目标准 API 获取，多次入栈实例独立、出栈清理，不预建、缓存或跨路由借用。导航不重建仓库，宿主退出清理 ViewModel/协程；Desktop 场景重载只重置页面/草稿，不清空数据或模拟配置。\n- 路由及私有 Composable 可获取页面 ViewModel、处理平台 UI；主题、通用布局、可复用组件和工具仅接收状态/事件，不通过默认参数、工厂、CompositionLocal 或全局入口隐藏获取。Service、悬浮窗、后台任务使用自身作用域和应用仓库，不借用或补建页面 ViewModel。\n- Android/Desktop 均采用单主界面设计，同一应用运行实例只存在一个根 MainViewModel，其生命周期覆盖所有子页面 ViewModel。生产页面、其私有 Composable 及所属子页面 ViewModel（含 commonMain）可通过 `MainViewModel.requireCurrent()` 获取已注册的根实例，无需逐层传递。宿主完成初始化和必要绑定后注册，根实例清理时注销；静态入口只提供访问，不创建实例或延长其生命周期。主题、通用布局、可复用组件、工具、Repository、Service、悬浮窗和后台任务不得通过该入口获取，也不引入 LocalMainViewModel。\n- 页面 ViewModel 可组织校验、保存、确认弹窗及后续导航等事件流程，按需使用 MainViewModel 的页面能力；Compose 负责展示、收集状态、转发事件及 IME、焦点等组合/宿主相关操作。不为缩短调用增加纯转发方法，不把页面流程转移到根 ViewModel。平台 UI 操作显式接收所属 UiHost 或事件回调，宿主引用绑定所属生命周期，不由页面 ViewModel 长期持有。\n- 自定义页面业务状态，包括编辑草稿及基准、搜索/筛选条件、分页、保存冲突和页面切换后需要保留的状态，优先由所属页面 ViewModel 使用 `MutableStateFlow<T>` 管理，对外暴露只读 `StateFlow<T>` 和明确修改方法，默认不使用 `rememberSaveable` 或自定义 Saver。页面状态随所属导航条目清理，不为保留它而提升到 MainViewModel；StateFlow 本身不提供落盘或进程重启恢复。\n- 滚动、焦点、动画、拖拽及简单组件展开等纯 UI 工具状态留在 Compose，允许使用官方 `rememberXxxState`；确需恢复的简单工具/组件状态可使用 `rememberSaveable`，无需恢复的瞬时状态使用 `remember`。不为规避 rememberSaveable 将这些工具状态搬入 ViewModel，也不以工具状态名义承载页面业务对象。`rememberXxxState` 不访问 ViewModel、数据库、Store、Service 或导航。组合工作使用 rememberCoroutineScope，稳定且绑定同一组合的 scope 不作冗余 remember key。主题状态、时序及深色优先级双端共用，宿主只给系统主题/色板。\n- `XxxUiState/Actions` 仅用于确有需要的复用、预览或复杂契约；UiState 是不可变快照，不含 Flow、Paging 或高频状态；多个构造路径重复映射时才提取私有构建函数。业务状态不通过 CompositionLocal 传递。\n- 应用/仓库只读 Flow 在实际消费处用 `collectAsStateWithLifecycle` 收集，Paging 用专用 API，高频状态放最小子树；不为转发而复制进 UiState/ViewModel；生产路由页面可获取所属 ViewModel，可复用组件接收不可变值和明确事件。ViewModel 可管理业务查询聚合、Paging 缓存、加载/失败和保存冲突基准，不因 map/combine 一律迁出。\n- ViewModel 状态仅在所属 VM 内修改，对外只暴露只读状态及明确事件方法；可变字段保持私有，只读 StateFlow 使用 Explicit Backing Fields 持有 MutableStateFlow，禁止 `_xxxFlow` 双属性及 `.asStateFlow()`。Composable 条件输出可用条件区块或清晰的提前返回，优先沿用邻近风格，不为形式增加嵌套或无关重构。\n- 共享页面用 `GkScaffold` 接收宿主 Insets（Android 原生，Desktop 模拟），不重复加系统留白。滚动内容末尾用 `GkPageBottomSpace()` / LazyColumn 的 `gkPageBottomSpace()`，高度由 GkPageBottomSpaceDefaults 统一；已有末尾 item 可内嵌但不重复添加，不替代系统栏或 IME Insets。\n- 动画不阻塞交互：禁止因过渡、图标变形、普通开关短暂保存或假设快速点击而禁用、节流、延时解锁或吞点击。禁用仅对应数据、输入、权限等业务前提，写入一致性交业务层处理。退场重复内容可隐藏于无障碍导航，但不改变颜色/增加等待；检查覆盖过渡中点击和切换。\n\n## 最小实现与防御边界\n\n- 按明确需求和已确认的问题实现最小改动。不得为假设中的异常、竞态或未来扩展，新增状态、缓存、刷新机制、抽象层或生命周期联动。\n- 添加校验、重试、兜底或异常捕获前，先核对依赖 API 和现有调用链的契约；已有保证不得重复实现。不确定契约时先查源码，不用防御代码代替确认。\n- 操作只检查自身必需的前提，不因单项操作刷新全部能力，不在 UI、ViewModel、服务层重复执行同一检查。UI 提示与执行前校验确有不同职责时可分别保留。\n- 不把未知、加载中或查询失败等同于权限拒绝；不得据此扩大为停服、断连、切换模式或修改用户设置，除非需求明确要求。\n- 异常在能实际处理它的边界捕获；禁止仅为返回默认值、重复记录日志或捕获后原样抛出而增加包装。\n- 抽象应消除已有重复或隔离真实差异。简单逻辑优先直接调用，不引入仅转发、预留扩展或另建同义状态的封装。\n- 保留外部输入校验、权限边界、事务一致性和资源释放等必要保护；新增保护应能说明具体失败场景，以及现有机制为何不足。\n\n## 状态、存储与副作用\n\n- Room 查询保持冷 Flow，在所属 ViewModel 按一致性边界聚合后转为 `StateFlow<Loadable<XxxUiState>>`；Loading 表示首发未完整，Ready(emptyList()) 表示已加载为空，禁止空集合初值、计数器或 attachLoad 推断加载完成。\n- 数据库/文件/网络写入和 Service 启停由明确用户、系统事件或领域方法触发，在 Repository/Store 完成一致性处理；禁止用派生 map/combine/stateIn 的 collect/onEach/watch 或 Composable 状态监听驱动。允许单一权威状态同步到幂等外部投影，但回调不再读取其他状态拼装写入。\n- 一致读取字段由事实源提供同一不可变快照；debounce、conflate、collectLatest 等调度机制不保证一致性；锁或事务必须覆盖全部相关读写，禁止仅锁消费端便宣称多个独立状态源原子一致。Repository 不读取资源/弹 Toast，错误保留原因交 UI 显示。生产设置文件名、字段、默认值是契约，迁移验证旧数据加载、失败重试、并发写入和恢复回滚。\n- 两端复用 AppStorageLayout 与真实 Repository/DAO；文件事务、归档、Cookie、网络配置和维护放共享层，宿主仅适配根目录、编解码、文件选择及系统动作。Desktop profile 只给平台输入，不另存统计、记录、排序或备份恢复事实源。\n- 宿主只注入 files/cache/privateFiles 根目录，布局遵循 [本地产物](docs/local-development.md)。图片缓存、网页用户数据、归档、临时文件须实际接入，不能仅创建目录；引擎安装可用工具缓存。Desktop privateFiles 与 files 同根，Android 保留系统私有目录，profile/导入标记/模拟配置在 data 外。\n- 自定义产物仅放根 `.local/`，工具标准目录不变。日常数据固定 `.local/desktop/`，禁止数据路径环境变量或 build 目录；`--test` 使用 `.local/tests/desktop/<随机标识>/`，不访问日常、真机或生产数据。写入检查先核对隔离标记，报告/截图在会话内；可复用工具提交源码目录。不得随缓存清理日常数据、备份或未处理恢复记录；不增旧目录回退/双写。\n\n## 资源与 Kotlin 规范\n\n- 禁止新增 XML，例外仅 Compose strings/plurals、Android 原生字符串及语言限定资源、app_icon/service 等平台配置。可用 Kotlin 表达的 UI/图标用 `.kt`；页面图标必须 ImageVector，drawable XML 仅限 Manifest 等平台配置及其依赖，即使同图标双用，页面仍用 ImageVector。不确定例外时先问用户。\n- 原生 res 归 gkd-android，gkd-app 不引用其 R；通知小图标通过 Application Manifest `notificationSmallIcon` 的 `android:resource` 注入，运行时校验非零，不持久化 ID。共享颜色用 Kotlin，Compose Resources 位于 gkd-app。\n- 共享及普通 Android Kotlin 文案统一官方 Compose Resources（`Res.string/plurals`），不建自定义 UiString/Desktop 文案表或原生副本；只有 Manifest/平台配置/变体标签保留原生 ID，Kotlin 可按需读取平台标签。\n- 官方资源对象（如 `StringResource`、`PluralStringResource`）不限于 UI 用途，允许枚举、业务状态、异常、Repository 等对象持有和传递，供 UI、HTTP、后台任务和导出等场景使用；在实际消费或输出边界按需调用官方 API 解析。资源对象与已解析的文案字符串分开处理：不在枚举、单例或静态初始化中解析、缓存文案字符串；语义/参数与持久化数据分离，语言切换不改写用户模板。\n- 字符串及复数资源统一使用 `import li.gkd.app.resources.*` 导入，不逐项导入 `Res` 或资源访问器；调用保持 `Res.string.xxx` / `Res.plurals.xxx`，文案解析继续使用官方 API。\n- 参数只用 `%1$s`、`%2$s` 等位置文本占位符；禁止数值/精度/补零格式指令，Kotlin 按明确 Locale/精度先格式化。Compose 用官方 Composable API，协程用 suspend getString/getPluralString；getSync 仅限低频同步边界，不用于静态初始化、高频或新增 Compose 路径，不用 runBlocking(Dispatchers.Main)。新增翻译/复数/限定符验证双端选择替换，校验任务不生成平台副本，访问器/打包交官方插件。\n- Kotlin 可见性按实际访问需求收窄，优先使用 private；gkd-app 模块禁止使用 internal 关键字，需要跨文件或类访问的声明使用默认 public，其他模块允许 internal。仅为公开属性收窄可见性/可变性的 `_xxx` 字段改用 Explicit Backing Fields；普通私有缓存、生成命名及 Lambda `_` 不受限。避免循环静态初始化。\n- 工具按职责组织并优先沿用邻近代码风格；无状态工具可用顶级函数，有共享状态或明确命名空间需求时使用 object，不为形式新增包装。XxxExt.kt 只放扩展，普通工具放职责明确的文件；不借规范调整进行无关重构。\n- 跨文件/页面复用 UI 统一 `Gk` + PascalCase，名称描述用途，禁用 Perf/Custom 泛化前缀。单组件文件同名，组件族及配套声明可同文件，文件以 Gk 开头并描述组件族；配置用 GkAbcDefaults/Colors，图标用 GkIcon/GkIcons。页面、私有 Composable、Preview、工具、Modifier 扩展、独立状态及其 Render() 不强制此前缀。\n\n## Desktop 运行时\n\n- 模拟配置、开发 HTTP 和工具不承担跨版本兼容，模型变更同步消费者，可重置，不增迁移/旧格式适配；生产数据兼容不受影响。开发工具统一 TypeScript、根 scripts/desktop 与 `pnpm app:tools`，复用公共 HTTP/控件逻辑，不另建同类 PowerShell/Python 脚本。\n- 预览/控制窗口同进程，关闭控制窗口不退出 App 且可重新打开（F12）。模拟器为单一状态源，控件和 HTTP 同命令路径。系统栏由 jvmMain 窗口模拟层实时绘制，时间取当前时钟，系统栏/挖孔统一计算 Insets；不以静态或整页截图替代 UI，不把模拟网络/电量冒充真机。未实现内容如实标识，不宣称模拟成功。\n- 不支持仅针对具体动作，以非模态 Toast 显示“当前平台不支持”，不替代整页或伪造成功；编辑/列表/文件业务继续真实执行。用户明确要求的 priv-ui 模拟可修改 Desktop 授权/启动/停止状态，日志标记 simulation，不执行真实特权命令。\n- priv-ui 只用公开 PrivilegeScreen、状态/actions，禁用内部 renderer/模拟器或 INVISIBLE_MEMBER；页面及系统提示 Insets 分别注入，宿主模拟状态机与控制窗口特权可用状态同步。使用版本目录的远程稳定版，升级验证双端编译和实际页面。\n- HTTP 仅在 jvmMain、绑定 127.0.0.1，不进入 Android 或混用业务服务；状态修改在 UI 线程。语义树/操作/截图明确 app|controls，各窗口节点快照独立；操作执行真实控件，不用直接改状态冒充点击。模拟返回先收 IME，再处理弹窗/编辑/页面。\n- 截图区分实时捕获和重新组合渲染，后者不证明当前焦点/滚动/弹窗。Desktop 不替代 Android 像素验收；对照须同页面状态、dp、fontScale、Insets、字体和颜色。设备导出仅 UI 所需输入，设备资料/字体不提交。\n\n## 验证与 Git\n\n- 按改动做最小必要验证：共享逻辑/UI、Desktop 只执行对应 JVM 编译及相关业务测试，不因 commonMain 或文档含双端命令追加 Android。仅 Android 实现、资源、Manifest、AIDL、隐藏 API、依赖/构建链路、专项规则或用户要求才做 Android 验证；默认仅 gkd 渠道，未经明确要求禁止 play。\n- UI 开发用 `./gradlew.bat :gkd-app:run`，构建前停止本任务启动的 Desktop JVM，避免覆盖运行中 JAR。网页使用版本目录 WebView2 和系统 Runtime。UI 默认使用对应编译及人工/HTTP 检查，不要求自动 UI 套件；仅为已发生、难以稳定人工复查的重要回归增加小范围自动测试，禁止仅复述静态布局或实现细节。业务状态机、持久化、事务恢复、协议/兼容测试保留，不按 ui 包名删除。\n- 新测试说明输入、预期及回归，优先边界/异常/兼容行为；存储测试验证实际读写恢复，不仅路径。测试遵循生产生命周期，可用独立会话和构造依赖实现隔离；全局单例的集成测试复用一次真实应用生命周期及隔离存储。禁止为测试拆散生产逻辑、扩大可见性、增加专用 API、ViewModel、嵌套 Store 或生产重置/替换接口；不以逐类工厂、fork 或 ClassLoader 掩盖生命周期缺陷。\n- 不测试枚举/常量集合、连续编号、同注册表推导关系或类型系统保证的静态声明；外部协议/持久化/跨版本标识例外，名称或注释说明保护的契约。\n- Android 权限、IME、系统栏、预测返回、无障碍/Service 按需 adb 验证。真机/模拟器交互前记录自动化开关与运行模式，关闭 enableAutomator 并退出自动化模式，核对后再检查；finally 在成功/失败/中断后仅恢复本次字段并核对，不用旧整份配置覆盖。仅 Desktop 检查不改真机。\n- 用户仅要求提交/推送时，只做轻量核对及 Git 操作；未经明确要求，不扩展为深度审查、临时 worktree 验证、全量构建/测试、Release Gate 或发布检查。\n\n## 依赖与 Android 专项\n\n- 外部 npm 依赖/版本集中根 package.json，private 子包所有 dependencies/devDependencies/optionalDependencies/peerDependencies 仅用 workspace:。可发布子包自行声明独立安装所需运行时/optional/peer 依赖，公共开发工具仍在根；变更同步清单和 pnpm 锁文件。\n- Android framework Java/AIDL 源码定位、跨版本签名/可用性、缺失分析及 Java hidden-API 生成必须使用 [android-api-diff skill](.agents/skills/android-api-diff/SKILL.md) 与其 CLI，保留默认 JSON，不自行模拟版本检查。安装/更新 skill 在根执行 `android-api-diff skill install`。\n- 修改隐藏接口/父类实现、系统回调、remap 或相关模块依赖/R8 配置，以及排查相关混淆 Release 故障时，必须使用 [android-hidden-api-r8 skill](.agents/skills/android-hidden-api-r8/SKILL.md)。检查最终应用模块 `gkd-android` 的实际 R8 输入及优化后的运行时契约，不以源码 override、编译成功或 Debug 正常代替；`gkd-app` 可见的 `gkd-hidden-api` stub 不代表最终 R8 可见。默认仅验证受影响的 gkd Release 路径，未经用户要求或证据支持不扩大审计范围。\n- 特权进程 UserService 的可能失败 Binder 方法在最外层末端 `catch (e: Throwable)` 捕获可恢复错误，通过 Binder 可传输异常/结果保留原类型、消息、堆栈；不只在主进程捕获，不让 NoSuchMethodError 等 LinkageError 逃逸崩溃。VirtualMachineError/ThreadDeath 等终止错误可原样抛出，不伪装普通失败。\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/gkd-kit/gkd/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Repository Instructions\n\n设计与依赖见 [应用架构](docs/architecture.md)，启动和 HTTP 调试见 [Desktop 开发](gkd-app/README.md)，固定目录见 [本地产物](docs/local-development.md)。本文件只维护开发约束。\n\n## 模块与平台\n\n- 模块统一使用 `gkd-` 前缀，依赖方向为 `gkd-android` → `gkd-app` → `gkd-db` / `gkd-selector`，禁止反向依赖。gkd-app 承载完整应用：commonMain 放业务、UI、ViewModel、导航；androidMain/jvmMain 放平台实现。gkd-android 仅负责 Manifest、原生 res、渠道、签名、打包和设备测试；JVM 启动/打包位于 gkd-app，TypeScript 工具位于根 `scripts/desktop`。\n- Desktop 仅为开发宿主，不另建产品后端、复制页面或仅为它拆共享模块/空壳 Repository；数据库职责留在 gkd-db。领域逻辑不得依赖 Compose UI/运行时、Android Context 或宿主；允许引用官方 Compose Resources 资源对象，不将资源对象视为 UI 依赖。Android/JVM 共用实现可直接在 commonMain 使用两端支持的 Java API，出现实际差异或非 JVM 目标时再隔离，不增中间源码集。\n- 公共根包及 gkd-app Android namespace 为 `li.gkd.app`，UI/导航/ViewModel 位于 `li.gkd.app.ui.*`；Desktop 同样使用该包层级，以 jvmMain 和职责文件名区分，不设 desktop 包。源码目录与包名一致。gkd-android namespace 为 `li.gkd.android`，Manifest 使用完整组件名。\n- androidMain 普通工具、适配、UI 接入及基类按职责归入 `li.gkd.app.*`；已注册 Application、Activity、Service、磁贴、特权入口及紧密关联实现保留原身份。`com.google.android.accessibility.selecttospeak.SelectToSpeakService` 的包名、路径和注册不变。包名整理不得改变组件、路由序列化名称/参数、AIDL 或持久化兼容标识。\n- Android 权限、Activity Result、通知、悬浮窗、Service 和特权进程位于 gkd-app/src/androidMain，由真实组件管理生命周期。gkd-aidl 只编译 Binder 协议；gkd-hidden-api 只提供 stub。引用 stub 的模块必须独立完成对应字节码的 remap。\n- ViewModel、Repository、业务状态和流程在 commonMain 保持唯一实现，ViewModel 使用 `XxxViewModel` 命名。禁止同名宿主子类、空壳继承、旧路径 typealias 或纯转发包装；共享类默认不可继承，宿主在原 ViewModelStoreOwner 直接创建，复杂/复用构造可用明确工厂。\n- 平台差异优先使用对应功能包的函数级 `expect/actual`，实现文件为 `.android.kt` / `.jvm.kt`；禁止整份 ViewModel、Repository 或业务流程 actual 化。能力签名只用公共类型，不导航、弹 Toast 或读取 UI ViewModel；仅 UI 组装可用 Composable expect/actual，需平台 UI 能力时显式接收所属 UiHost。无生命周期工具只持有 Application Context，Activity/窗口引用限于生命周期绑定的 UI 会话。\n- 不支持的平台动作统一返回 `PlatformResult<T>`，无数据成功为 `Unit`；不得另造 Unsupported 类型或用 Boolean/null 暗示不支持。契约明确成功含义，保留异常/取消传播，不替代业务结果、用户取消或权限状态。\n- 应用仓库由应用生命周期唯一持有，禁止重复创建、可替换全局实例和无意义转发。无独立会话需求时优先使用 object；需要独立生命周期或明确依赖时允许普通类及构造注入。平台依赖先于消费者初始化，所属生命周期结束时清理协程和资源，不为测试增加生产重置接口。\n\n## UI 与生命周期\n\n- 两端共用 commonMain 的生产页面、组件、业务和事件链，模拟输入也经过生产 Repository/ViewModel；共享代码不得直接依赖 Android Application、平台专属 Store/Service 或伪造全局实例；共享 MainViewModel 的访问遵循下述单主界面规则。Desktop 默认四标签，组件目录仅显式诊断。新增入口同时覆盖双端及下游弹窗、面板、返回路径，不用整页 Toast/占位掩盖缺失；交付需业务、读写、生命周期和验证证据。\n- 两端共用 `GkAppNavigation` 注册及 `GkNavigation` 保存状态/条目生命周期；禁止 Desktop 字符串分支注册生产路由、复制平台路由或另写保存/导航规则。普通/编辑页面过渡统一声明，公共页面只注册一次；HTTP 标签等状态读取生产来源。\n- MainViewModel 属于主界面的 ViewModelStore，页面 ViewModel 属于具体导航条目；有需要保留的页面状态或事件流程时按需创建，不为纯展示或组件瞬时状态创建。根不持有其他 ViewModel 或子 ViewModelStore；子路由用条目标准 API 获取，多次入栈实例独立、出栈清理，不预建、缓存或跨路由借用。导航不重建仓库，宿主退出清理 ViewModel/协程；Desktop 场景重载只重置页面/草稿，不清空数据或模拟配置。\n- 路由及私有 Composable 可获取页面 ViewModel、处理平台 UI；主题、通用布局、可复用组件和工具仅接收状态/事件，不通过默认参数、工厂、CompositionLocal 或全局入口隐藏获取。Service、悬浮窗、后台任务使用自身作用域和应用仓库，不借用或补建页面 ViewModel。\n- Android/Desktop 均采用单主界面设计，同一应用运行实例只存在一个根 MainViewModel，其生命周期覆盖所有子页面 ViewModel。生产页面、其私有 Composable 及所属子页面 ViewModel（含 commonMain）可通过 `MainViewModel.requireCurrent()` 获取已注册的根实例，无需逐层传递。宿主完成初始化和必要绑定后注册，根实例清理时注销；静态入口只提供访问，不创建实例或延长其生命周期。主题、通用布局、可复用组件、工具、Repository、Service、悬浮窗和后台任务不得通过该入口获取，也不引入 LocalMainViewModel。\n- 页面 ViewModel 可组织校验、保存、确认弹窗及后续导航等事件流程，按需使用 MainViewModel 的页面能力；Compose 负责展示、收集状态、转发事件及 IME、焦点等组合/宿主相关操作。不为缩短调用增加纯转发方法，不把页面流程转移到根 ViewModel。平台 UI 操作显式接收所属 UiHost 或事件回调，宿主引用绑定所属生命周期，不由页面 ViewModel 长期持有。\n- 自定义页面业务状态，包括编辑草稿及基准、搜索/筛选条件、分页、保存冲突和页面切换后需要保留的状态，优先由所属页面 ViewModel 使用 `MutableStateFlow<T>` 管理，对外暴露只读 `StateFlow<T>` 和明确修改方法，默认不使用 `rememberSaveable` 或自定义 Saver。页面状态随所属导航条目清理，不为保留它而提升到 MainViewModel；StateFlow 本身不提供落盘或进程重启恢复。\n- 滚动、焦点、动画、拖拽及简单组件展开等纯 UI 工具状态留在 Compose，允许使用官方 `rememberXxxState`；确需恢复的简单工具/组件状态可使用 `rememberSaveable`，无需恢复的瞬时状态使用 `remember`。不为规避 rememberSaveable 将这些工具状态搬入 ViewModel，也不以工具状态名义承载页面业务对象。`rememberXxxState` 不访问 ViewModel、数据库、Store、Service 或导航。组合工作使用 rememberCoroutineScope，稳定且绑定同一组合的 scope 不作冗余 remember key。主题状态、时序及深色优先级双端共用，宿主只给系统主题/色板。\n- `XxxUiState/Actions` 仅用于确有需要的复用、预览或复杂契约；UiState 是不可变快照，不含 Flow、Paging 或高频状态；多个构造路径重复映射时才提取私有构建函数。业务状态不通过 CompositionLocal 传递。\n- 应用/仓库只读 Flow 在实际消费处用 `collectAsStateWithLifecycle` 收集，Paging 用专用 API，高频状态放最小子树；不为转发而复制进 UiState/ViewModel；生产路由页面可获取所属 ViewModel，可复用组件接收不可变值和明确事件。ViewModel 可管理业务查询聚合、Paging 缓存、加载/失败和保存冲突基准，不因 map/combine 一律迁出。\n- ViewModel 状态仅在所属 VM 内修改，对外只暴露只读状态及明确事件方法；可变字段保持私有，只读 StateFlow 使用 Explicit Backing Fields 持有 MutableStateFlow，禁止 `_xxxFlow` 双属性及 `.asStateFlow()`。Composable 条件输出可用条件区块或清晰的提前返回，优先沿用邻近风格，不为形式增加嵌套或无关重构。\n- 共享页面用 `GkScaffold` 接收宿主 Insets（Android 原生，Desktop 模拟），不重复加系统留白。滚动内容末尾用 `GkPageBottomSpace()` / LazyColumn 的 `gkPageBottomSpace()`，高度由 GkPageBottomSpaceDefaults 统一；已有末尾 item 可内嵌但不重复添加，不替代系统栏或 IME Insets。\n- 动画不阻塞交互：禁止因过渡、图标变形、普通开关短暂保存或假设快速点击而禁用、节流、延时解锁或吞点击。禁用仅对应数据、输入、权限等业务前提，写入一致性交业务层处理。退场重复内容可隐藏于无障碍导航，但不改变颜色/增加等待；检查覆盖过渡中点击和切换。\n\n## 最小实现与防御边界\n\n- 按明确需求和已确认的问题实现最小改动。不得为假设中的异常、竞态或未来扩展，新增状态、缓存、刷新机制、抽象层或生命周期联动。\n- 添加校验、重试、兜底或异常捕获前，先核对依赖 API 和现有调用链的契约；已有保证不得重复实现。不确定契约时先查源码，不用防御代码代替确认。\n- 操作只检查自身必需的前提，不因单项操作刷新全部能力，不在 UI、ViewModel、服务层重复执行同一检查。UI 提示与执行前校验确有不同职责时可分别保留。\n- 不把未知、加载中或查询失败等同于权限拒绝；不得据此扩大为停服、断连、切换模式或修改用户设置，除非需求明确要求。\n- 异常在能实际处理它的边界捕获；禁止仅为返回默认值、重复记录日志或捕获后原样抛出而增加包装。\n- 抽象应消除已有重复或隔离真实差异。简单逻辑优先直接调用，不引入仅转发、预留扩展或另建同义状态的封装。\n- 保留外部输入校验、权限边界、事务一致性和资源释放等必要保护；新增保护应能说明具体失败场景，以及现有机制为何不足。\n\n## 状态、存储与副作用\n\n- Room 查询保持冷 Flow，在所属 ViewModel 按一致性边界聚合后转为 `StateFlow<Loadable<XxxUiState>>`；Loading 表示首发未完整，Ready(emptyList()) 表示已加载为空，禁止空集合初值、计数器或 attachLoad 推断加载完成。\n- 数据库/文件/网络写入和 Service 启停由明确用户、系统事件或领域方法触发，在 Repository/Store 完成一致性处理；禁止用派生 map/combine/stateIn 的 collect/onEach/watch 或 Composable 状态监听驱动。允许单一权威状态同步到幂等外部投影，但回调不再读取其他状态拼装写入。\n- 一致读取字段由事实源提供同一不可变快照；debounce、conflate、collectLatest 等调度机制不保证一致性；锁或事务必须覆盖全部相关读写，禁止仅锁消费端便宣称多个独立状态源原子一致。Repository 不读取资源/弹 Toast，错误保留原因交 UI 显示。生产设置文件名、字段、默认值是契约，迁移验证旧数据加载、失败重试、并发写入和恢复回滚。\n- 两端复用 AppStorageLayout 与真实 Repository/DAO；文件事务、归档、Cookie、网络配置和维护放共享层，宿主仅适配根目录、编解码、文件选择及系统动作。Desktop profile 只给平台输入，不另存统计、记录、排序或备份恢复事实源。\n- 宿主只注入 files/cache/privateFiles 根目录，布局遵循 [本地产物](docs/local-development.md)。图片缓存、网页用户数据、归档、临时文件须实际接入，不能仅创建目录；引擎安装可用工具缓存。Desktop privateFiles 与 files 同根，Android 保留系统私有目录，profile/导入标记/模拟配置在 data 外。\n- 自定义产物仅放根 `.local/`，工具标准目录不变。日常数据固定 `.local/desktop/`，禁止数据路径环境变量或 build 目录；`--test` 使用 `.local/tests/desktop/<随机标识>/`，不访问日常、真机或生产数据。写入检查先核对隔离标记，报告/截图在会话内；可复用工具提交源码目录。不得随缓存清理日常数据、备份或未处理恢复记录；不增旧目录回退/双写。\n\n## 资源与 Kotlin 规范\n\n- 禁止新增 XML，例外仅 Compose strings/plurals、Android 原生字符串及语言限定资源、app_icon/service 等平台配置。可用 Kotlin 表达的 UI/图标用 `.kt`；页面图标必须 ImageVector，drawable XML 仅限 Manifest 等平台配置及其依赖，即使同图标双用，页面仍用 ImageVector。不确定例外时先问用户。\n- 原生 res 归 gkd-android，gkd-app 不引用其 R；通知小图标通过 Application Manifest `notificationSmallIcon` 的 `android:resource` 注入，运行时校验非零，不持久化 ID。共享颜色用 Kotlin，Compose Resources 位于 gkd-app。\n- 共享及普通 Android Kotlin 文案统一官方 Compose Resources（`Res.string/plurals`），不建自定义 UiString/Desktop 文案表或原生副本；只有 Manifest/平台配置/变体标签保留原生 ID，Kotlin 可按需读取平台标签。\n- 官方资源对象（如 `StringResource`、`PluralStringResource`）不限于 UI 用途，允许枚举、业务状态、异常、Repository 等对象持有和传递，供 UI、HTTP、后台任务和导出等场景使用；在实际消费或输出边界按需调用官方 API 解析。资源对象与已解析的文案字符串分开处理：不在枚举、单例或静态初始化中解析、缓存文案字符串；语义/参数与持久化数据分离，语言切换不改写用户模板。\n- 字符串及复数资源统一使用 `import li.gkd.app.resources.*` 导入，不逐项导入 `Res` 或资源访问器；调用保持 `Res.string.xxx` / `Res.plurals.xxx`，文案解析继续使用官方 API。\n- 参数只用 `%1$s`、`%2$s` 等位置文本占位符；禁止数值/精度/补零格式指令，Kotlin 按明确 Locale/精度先格式化。Compose 用官方 Composable API，协程用 suspend getString/getPluralString；getSync 仅限低频同步边界，不用于静态初始化、高频或新增 Compose 路径，不用 runBlocking(Dispatchers.Main)。新增翻译/复数/限定符验证双端选择替换，校验任务不生成平台副本，访问器/打包交官方插件。\n- Kotlin 可见性按实际访问需求收窄，优先使用 private；gkd-app 模块禁止使用 internal 关键字，需要跨文件或类访问的声明使用默认 public，其他模块允许 internal。仅为公开属性收窄可见性/可变性的 `_xxx` 字段改用 Explicit Backing Fields；普通私有缓存、生成命名及 Lambda `_` 不受限。避免循环静态初始化。\n- 工具按职责组织并优先沿用邻近代码风格；无状态工具可用顶级函数，有共享状态或明确命名空间需求时使用 object，不为形式新增包装。XxxExt.kt 只放扩展，普通工具放职责明确的文件；不借规范调整进行无关重构。\n- 跨文件/页面复用 UI 统一 `Gk` + PascalCase，名称描述用途，禁用 Perf/Custom 泛化前缀。单组件文件同名，组件族及配套声明可同文件，文件以 Gk 开头并描述组件族；配置用 GkAbcDefaults/Colors，图标用 GkIcon/GkIcons。页面、私有 Composable、Preview、工具、Modifier 扩展、独立状态及其 Render() 不强制此前缀。\n\n## Desktop 运行时\n\n- 模拟配置、开发 HTTP 和工具不承担跨版本兼容，模型变更同步消费者，可重置，不增迁移/旧格式适配；生产数据兼容不受影响。开发工具统一 TypeScript、根 scripts/desktop 与 `pnpm app:tools`，复用公共 HTTP/控件逻辑，不另建同类 PowerShell/Python 脚本。\n- 预览/控制窗口同进程，关闭控制窗口不退出 App 且可重新打开（F12）。模拟器为单一状态源，控件和 HTTP 同命令路径。系统栏由 jvmMain 窗口模拟层实时绘制，时间取当前时钟，系统栏/挖孔统一计算 Insets；不以静态或整页截图替代 UI，不把模拟网络/电量冒充真机。未实现内容如实标识，不宣称模拟成功。\n- 不支持仅针对具体动作，以非模态 Toast 显示“当前平台不支持”，不替代整页或伪造成功；编辑/列表/文件业务继续真实执行。用户明确要求的 priv-ui 模拟可修改 Desktop 授权/启动/停止状态，日志标记 simulation，不执行真实特权命令。\n- priv-ui 只用公开 PrivilegeScreen、状态/actions，禁用内部 renderer/模拟器或 INVISIBLE_MEMBER；页面及系统提示 Insets 分别注入，宿主模拟状态机与控制窗口特权可用状态同步。使用版本目录的远程稳定版，升级验证双端编译和实际页面。\n- HTTP 仅在 jvmMain、绑定 127.0.0.1，不进入 Android 或混用业务服务；状态修改在 UI 线程。语义树/操作/截图明确 app|controls，各窗口节点快照独立；操作执行真实控件，不用直接改状态冒充点击。模拟返回先收 IME，再处理弹窗/编辑/页面。\n- 截图区分实时捕获和重新组合渲染，后者不证明当前焦点/滚动/弹窗。Desktop 不替代 Android 像素验收；对照须同页面状态、dp、fontScale、Insets、字体和颜色。设备导出仅 UI 所需输入，设备资料/字体不提交。\n\n## 验证与 Git\n\n- 按改动做最小必要验证：共享逻辑/UI、Desktop 只执行对应 JVM 编译及相关业务测试，不因 commonMain 或文档含双端命令追加 Android。仅 Android 实现、资源、Manifest、AIDL、隐藏 API、依赖/构建链路、专项规则或用户要求才做 Android 验证；默认仅 gkd 渠道，未经明确要求禁止 play。\n- UI 开发用 `./gradlew.bat :gkd-app:run`，构建前停止本任务启动的 Desktop JVM，避免覆盖运行中 JAR。网页使用版本目录 WebView2 和系统 Runtime。UI 默认使用对应编译及人工/HTTP 检查，不要求自动 UI 套件；仅为已发生、难以稳定人工复查的重要回归增加小范围自动测试，禁止仅复述静态布局或实现细节。业务状态机、持久化、事务恢复、协议/兼容测试保留，不按 ui 包名删除。\n- 新测试说明输入、预期及回归，优先边界/异常/兼容行为；存储测试验证实际读写恢复，不仅路径。测试遵循生产生命周期，可用独立会话和构造依赖实现隔离；全局单例的集成测试复用一次真实应用生命周期及隔离存储。禁止为测试拆散生产逻辑、扩大可见性、增加专用 API、ViewModel、嵌套 Store 或生产重置/替换接口；不以逐类工厂、fork 或 ClassLoader 掩盖生命周期缺陷。\n- 不测试枚举/常量集合、连续编号、同注册表推导关系或类型系统保证的静态声明；外部协议/持久化/跨版本标识例外，名称或注释说明保护的契约。\n- Android 权限、IME、系统栏、预测返回、无障碍/Service 按需 adb 验证。真机/模拟器交互前记录自动化开关与运行模式，关闭 enableAutomator 并退出自动化模式，核对后再检查；finally 在成功/失败/中断后仅恢复本次字段并核对，不用旧整份配置覆盖。仅 Desktop 检查不改真机。\n- 用户仅要求提交/推送时，只做轻量核对及 Git 操作；未经明确要求，不扩展为深度审查、临时 worktree 验证、全量构建/测试、Release Gate 或发布检查。\n\n## 依赖与 Android 专项\n\n- 外部 npm 依赖/版本集中根 package.json，private 子包所有 dependencies/devDependencies/optionalDependencies/peerDependencies 仅用 workspace:。可发布子包自行声明独立安装所需运行时/optional/peer 依赖，公共开发工具仍在根；变更同步清单和 pnpm 锁文件。\n- Android framework Java/AIDL 源码定位、跨版本签名/可用性、缺失分析及 Java hidden-API 生成必须使用 [android-api-diff skill](.agents/skills/android-api-diff/SKILL.md) 与其 CLI，保留默认 JSON，不自行模拟版本检查。安装/更新 skill 在根执行 `android-api-diff skill install`。\n- 修改隐藏接口/父类实现、系统回调、remap 或相关模块依赖/R8 配置，以及排查相关混淆 Release 故障时，必须使用 [android-hidden-api-r8 skill](.agents/skills/android-hidden-api-r8/SKILL.md)。检查最终应用模块 `gkd-android` 的实际 R8 输入及优化后的运行时契约，不以源码 override、编译成功或 Debug 正常代替；`gkd-app` 可见的 `gkd-hidden-api` stub 不代表最终 R8 可见。默认仅验证受影响的 gkd Release 路径，未经用户要求或证据支持不扩大审计范围。\n- 特权进程 UserService 的可能失败 Binder 方法在最外层末端 `catch (e: Throwable)` 捕获可恢复错误，通过 Binder 可传输异常/结果保留原类型、消息、堆栈；不只在主进程捕获，不让 NoSuchMethodError 等 LinkageError 逃逸崩溃。VirtualMachineError/ThreadDeath 等终止错误可原样抛出，不伪装普通失败。\n","isInternal":false,"tokens":7147,"sizeBytes":21537},{"name":"openai.yaml","path":".agents/skills/android-api-diff/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/gkd-kit/gkd/HEAD/.agents/skills/android-api-diff/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: 'Android API Diff'\n  short_description: 'Query Android API changes and generate hidden API code'\n  default_prompt: 'Use $android-api-diff to inspect an Android framework API across versions.'\n","isInternal":false,"tokens":46,"sizeBytes":219},{"name":"SKILL.md","path":".agents/skills/android-api-diff/SKILL.md","rawUrl":"https://raw.githubusercontent.com/gkd-kit/gkd/HEAD/.agents/skills/android-api-diff/SKILL.md","title":"android-api-diff","category":"anthropic-skill","format":"markdown","content":"---\nname: android-api-diff\ndescription: Inspect Android framework Java and AIDL APIs across versions with the android-api-diff CLI. Use when resolving an Android API to frameworks/base source, fetching the complete content of a tagged source file, comparing signatures or availability by Android version, explaining missing APIs, generating Java hidden-API access code, or explicitly prewarming the local query cache.\n---\n\n# Android API Diff\n\nUse the `android-api-diff` CLI as the only tool interface. Keep its default JSON\noutput so results remain machine-readable.\n\n## Route the request\n\n- Run `generate` when the user wants Java hidden-API access code. It performs the\n  version query internally, so never call `query` first.\n- Run `query` when the user wants signatures, version ranges, availability,\n  missing reasons, or source metadata.\n- Run `resolve` when the user only needs the matching `frameworks/base` file and\n  target kind.\n- Run `source` when the user wants the complete content of a known source path\n  at an exact Android release tag. If only an API name is known, run `resolve`\n  first and use the chosen `result.candidates[].source.path`.\n- Run `preload` only when the user explicitly asks to preload several APIs.\n\nAlways quote API names because constructors and member references can contain\nshell-sensitive characters.\n\n```sh\nandroid-api-diff resolve \"ContentObserver()\"\nandroid-api-diff source android-17.0.0_r1 core/java/android/accessibilityservice/AccessibilityButtonController.java\nandroid-api-diff query \"IActivityManager.getTasks\" --min-sdk 28\nandroid-api-diff generate \"ActivityThread.currentApplication\" --min-sdk 28\nandroid-api-diff preload \"ContentObserver()\" \"IActivityManager.getTasks\" --min-sdk 28\n```\n\nOmit `--min-sdk` unless the user provides a minimum Android API level. The CLI\nthen uses its built-in default.\n\n## Interpret results\n\nTreat exit code `0` together with top-level `ok: true` as success. Read the\noperation result from the top-level `result` field.\n\n`resolve` returns `result.candidates` in recommended order, or an empty array\nwhen no source matches. `query` and `generate` include the same candidate list\nand inspect the recommended source by default. When the user specifies another\nmatching source, pass `--source-path <path>` to `query` or `generate`; retain the\noriginal API reference to preserve its member or constructor target. Candidate\nmetadata alone does not prove that a member exists in a particular version.\n\nFor `query`, describe range endpoints as the first or last tag checked in the\ncurrent snapshot. A `last-checked` endpoint is not a promise that no later\nrevision exists.\n\nFor member queries, treat `result.overloads` as the source of truth. Each item\nhas a stable `overloadId`, its latest checked `signature` and `member`, and\nindependent `ranges`. A range with `missingReason: \"overload-not-found\"` means\nthe member name exists in that source revision but that specific parameter\nsignature does not. Use top-level `result.ranges[].overloadIds` only when\ndescribing which overloads were available together.\n\nOn a nonzero exit code, read the structured JSON error from stderr and report its\n`error.code` and `error.message`. Do not add an automatic retry: the CLI network\nlayer already retries transient fetches.\n\n## Choose an interface constant access strategy\n\nFour strategies are supported and have already been validated in the companion\nremap repository. Choose based on the user's build and value requirements;\ndo not require another feasibility experiment unless the user requests one.\n\n| Strategy                | When to use it                                                                                                 | Declaration or access                                                                      |\n| ----------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| Reflection              | Read the device's value without a remapped field access                                                        | Read the original interface field reflectively.                                            |\n| Interface stub          | Preserve runtime field access in a compatible remap build                                                      | Initialize the interface field with `RemapStub.value()`.                                   |\n| Consistent constant     | Embed a supported primitive expression that agrees across checked versions                                     | Keep the original initializer in the generated interface.                                  |\n| Implementing-class stub | Read the device's value through a known platform class implementing the interface, without `RemapStub.value()` | Declare a `public static`, non-final field with no initializer in that class's remap stub. |\n\nThe current `generate` command automatically chooses the consistent-constant\nstrategy for supported primitive initializers whose token sequences match across\nchecked definitions, ignoring whitespace and comments. It does not evaluate\nexpressions: `0x1` and `(1 << 0)` differ. Constant references, unsupported\nexpressions, missing initializers, differing definitions, and non-primitive\ntypes fall back to `RemapStub.value()`. Agreement covers only checked versions;\ndo not download all versions to strengthen this conclusion.\n\nReflection and implementing-class stubs are adaptations of the generated code,\nnot CLI flags or automatic generator fallbacks. Honor an explicitly selected\nstrategy. For an interface/R8 incompatibility, use a consistent constant when\nthe generated result supports it; otherwise prefer a known suitable platform\nimplementing class, or reflection when no suitable class is known. Do not\npresent the `RemapStub.value()` fallback as resolving that incompatibility.\n\nFor example, access `IBinder.SHELL_COMMAND_TRANSACTION` through a stub mapped\nto the platform's `Binder` implementation:\n\n```java\n@li.songe.remap.RemapType(android.os.Binder.class)\npublic class BinderHidden {\n    public static int SHELL_COMMAND_TRANSACTION;\n}\n```\n\nRead `BinderHidden.SHELL_COMMAND_TRANSACTION` at the call site. Keep the field\nname and type matching the original interface declaration; omit both `final`\nand an initializer. This strategy also applies to reference types and values\nthat differ between versions. Map to the real platform implementing class;\nmerely declaring a field in an unmapped application subclass creates a separate\nfield. Use these declarations for reads, since the original interface field is\nfinal.\n\n## Handle a missing CLI\n\nIf `android-api-diff` is not installed, stop and tell the user to run:\n\n```sh\nnpm install --global android-api-diff@latest\nandroid-api-diff skill install\n```\n\nRun the Skill command from the root of the current Git or Gradle project so it\nremains project-scoped. Do not implement or simulate version checks. The\npackage manager owns CLI update detection and upgrades.\n","frontmatter":{"name":"android-api-diff","description":"Inspect Android framework Java and AIDL APIs across versions with the android-api-diff CLI. Use when resolving an Android API to frameworks/base source, fetching the complete content of a tagged source file, comparing signatures or availability by Android version, explaining missing APIs, generating Java hidden-API access code, or explicitly prewarming the local query cache."},"isInternal":false,"tokens":1366,"sizeBytes":7095}],"systemPromptSnippet":"<yakaai_skills repo=\"gkd-kit/gkd\">\n<!-- File: AGENTS.md (Tokens: ~7147 | Category: root-instruction) -->\n# Repository Instructions\n\n设计与依赖见 [应用架构](docs/architecture.md)，启动和 HTTP 调试见 [Desktop 开发](gkd-app/README.md)，固定目录见 [本地产物](docs/local-development.md)。本文件只维护开发约束。\n\n## 模块与平台\n\n- 模块统一使用 `gkd-` 前缀，依赖方向为 `gkd-android` → `gkd-app` → `gkd-db` / `gkd-selector`，禁止反向依赖。gkd-app 承载完整应用：commonMain 放业务、UI、ViewModel、导航；androidMain/jvmMain 放平台实现。gkd-android 仅负责 Manifest、原生 res、渠道、签名、打包和设备测试；JVM 启动/打包位于 gkd-app，TypeScript 工具位于根 `scripts/desktop`。\n- Desktop 仅为开发宿主，不另建产品后端、复制页面或仅为它拆共享模块/空壳 Repository；数据库职责留在 gkd-db。领域逻辑不得依赖 Compose UI/运行时、Android Context 或宿主；允许引用官方 Compose Resources 资源对象，不将资源对象视为 UI 依赖。Android/JVM 共用实现可直接在 commonMain 使用两端支持的 Java API，出现实际差异或非 JVM 目标时再隔离，不增中间源码集。\n- 公共根包及 gkd-app Android namespace 为 `li.gkd.app`，UI/导航/ViewModel 位于 `li.gkd.app.ui.*`；Desktop 同样使用该包层级，以 jvmMain 和职责文件名区分，不设 desktop 包。源码目录与包名一致。gkd-android namespace 为 `li.gkd.android`，Manifest 使用完整组件名。\n- androidMain 普通工具、适配、UI 接入及基类按职责归入 `li.gkd.app.*`；已注册 Application、Activity、Service、磁贴、特权入口及紧密关联实现保留原身份。`com.google.android.accessibility.selecttospeak.SelectToSpeakService` 的包名、路径和注册不变。包名整理不得改变组件、路由序列化名称/参数、AIDL 或持久化兼容标识。\n- Android 权限、Activity Result、通知、悬浮窗、Service 和特权进程位于 gkd-app/src/androidMain，由真实组件管理生命周期。gkd-aidl 只编译 Binder 协议；gkd-hidden-api 只提供 stub。引用 stub 的模块必须独立完成对应字节码的 remap。\n- ViewModel、Repository、业务状态和流程在 commonMain 保持唯一实现，ViewModel 使用 `XxxViewModel` 命名。禁止同名宿主子类、空壳继承、旧路径 typealias 或纯转发包装；共享类默认不可继承，宿主在原 ViewModelStoreOwner 直接创建，复杂/复用构造可用明确工厂。\n- 平台差异优先使用对应功能包的函数级 `expect/actual`，实现文件为 `.android.kt` / `.jvm.kt`；禁止整份 ViewModel、Repository 或业务流程 actual 化。能力签名只用公共类型，不导航、弹 Toast 或读取 UI ViewModel；仅 UI 组装可用 Composable expect/actual，需平台 UI 能力时显式接收所属 UiHost。无生命周期工具只持有 Application Context，Activity/窗口引用限于生命周期绑定的 UI 会话。\n- 不支持的平台动作统一返回 `PlatformResult<T>`，无数据成功为 `Unit`；不得另造 Unsupported 类型或用 Boolean/null 暗示不支持。契约明确成功含义，保留异常/取消传播，不替代业务结果、用户取消或权限状态。\n- 应用仓库由应用生命周期唯一持有，禁止重复创建、可替换全局实例和无意义转发。无独立会话需求时优先使用 object；需要独立生命周期或明确依赖时允许普通类及构造注入。平台依赖先于消费者初始化，所属生命周期结束时清理协程和资源，不为测试增加生产重置接口。\n\n## UI 与生命周期\n\n- 两端共用 commonMain 的生产页面、组件、业务和事件链，模拟输入也经过生产 Repository/ViewModel；共享代码不得直接依赖 Android Application、平台专属 Store/Service 或伪造全局实例；共享 MainViewModel 的访问遵循下述单主界面规则。Desktop 默认四标签，组件目录仅显式诊断。新增入口同时覆盖双端及下游弹窗、面板、返回路径，不用整页 Toast/占位掩盖缺失；交付需业务、读写、生命周期和验证证据。\n- 两端共用 `GkAppNavigation` 注册及 `GkNavigation` 保存状态/条目生命周期；禁止 Desktop 字符串分支注册生产路由、复制平台路由或另写保存/导航规则。普通/编辑页面过渡统一声明，公共页面只注册一次；HTTP 标签等状态读取生产来源。\n- MainViewModel 属于主界面的 ViewModelStore，页面 ViewModel 属于具体导航条目；有需要保留的页面状态或事件流程时按需创建，不为纯展示或组件瞬时状态创建。根不持有其他 ViewModel 或子 ViewModelStore；子路由用条目标准 API 获取，多次入栈实例独立、出栈清理，不预建、缓存或跨路由借用。导航不重建仓库，宿主退出清理 ViewModel/协程；Desktop 场景重载只重置页面/草稿，不清空数据或模拟配置。\n- 路由及私有 Composable 可获取页面 ViewModel、处理平台 UI；主题、通用布局、可复用组件和工具仅接收状态/事件，不通过默认参数、工厂、CompositionLocal 或全局入口隐藏获取。Service、悬浮窗、后台任务使用自身作用域和应用仓库，不借用或补建页面 ViewModel。\n- Android/Desktop 均采用单主界面设计，同一应用运行实例只存在一个根 MainViewModel，其生命周期覆盖所有子页面 ViewModel。生产页面、其私有 Composable 及所属子页面 ViewModel（含 commonMain）可通过 `MainViewModel.requireCurrent()` 获取已注册的根实例，无需逐层传递。宿主完成初始化和必要绑定后注册，根实例清理时注销；静态入口只提供访问，不创建实例或延长其生命周期。主题、通用布局、可复用组件、工具、Repository、Service、悬浮窗和后台任务不得通过该入口获取，也不引入 LocalMainViewModel。\n- 页面 ViewModel 可组织校验、保存、确认弹窗及后续导航等事件流程，按需使用 MainViewModel 的页面能力；Compose 负责展示、收集状态、转发事件及 IME、焦点等组合/宿主相关操作。不为缩短调用增加纯转发方法，不把页面流程转移到根 ViewModel。平台 UI 操作显式接收所属 UiHost 或事件回调，宿主引用绑定所属生命周期，不由页面 ViewModel 长期持有。\n- 自定义页面业务状态，包括编辑草稿及基准、搜索/筛选条件、分页、保存冲突和页面切换后需要保留的状态，优先由所属页面 ViewModel 使用 `MutableStateFlow<T>` 管理，对外暴露只读 `StateFlow<T>` 和明确修改方法，默认不使用 `rememberSaveable` 或自定义 Saver。页面状态随所属导航条目清理，不为保留它而提升到 MainViewModel；StateFlow 本身不提供落盘或进程重启恢复。\n- 滚动、焦点、动画、拖拽及简单组件展开等纯 UI 工具状态留在 Compose，允许使用官方 `rememberXxxState`；确需恢复的简单工具/组件状态可使用 `rememberSaveable`，无需恢复的瞬时状态使用 `remember`。不为规避 rememberSaveable 将这些工具状态搬入 ViewModel，也不以工具状态名义承载页面业务对象。`rememberXxxState` 不访问 ViewModel、数据库、Store、Service 或导航。组合工作使用 rememberCoroutineScope，稳定且绑定同一组合的 scope 不作冗余 remember key。主题状态、时序及深色优先级双端共用，宿主只给系统主题/色板。\n- `XxxUiState/Actions` 仅用于确有需要的复用、预览或复杂契约；UiState 是不可变快照，不含 Flow、Paging 或高频状态；多个构造路径重复映射时才提取私有构建函数。业务状态不通过 CompositionLocal 传递。\n- 应用/仓库只读 Flow 在实际消费处用 `collectAsStateWithLifecycle` 收集，Paging 用专用 API，高频状态放最小子树；不为转发而复制进 UiState/ViewModel；生产路由页面可获取所属 ViewModel，可复用组件接收不可变值和明确事件。ViewModel 可管理业务查询聚合、Paging 缓存、加载/失败和保存冲突基准，不因 map/combine 一律迁出。\n- ViewModel 状态仅在所属 VM 内修改，对外只暴露只读状态及明确事件方法；可变字段保持私有，只读 StateFlow 使用 Explicit Backing Fields 持有 MutableStateFlow，禁止 `_xxxFlow` 双属性及 `.asStateFlow()`。Composable 条件输出可用条件区块或清晰的提前返回，优先沿用邻近风格，不为形式增加嵌套或无关重构。\n- 共享页面用 `GkScaffold` 接收宿主 Insets（Android 原生，Desktop 模拟），不重复加系统留白。滚动内容末尾用 `GkPageBottomSpace()` / LazyColumn 的 `gkPageBottomSpace()`，高度由 GkPageBottomSpaceDefaults 统一；已有末尾 item 可内嵌但不重复添加，不替代系统栏或 IME Insets。\n- 动画不阻塞交互：禁止因过渡、图标变形、普通开关短暂保存或假设快速点击而禁用、节流、延时解锁或吞点击。禁用仅对应数据、输入、权限等业务前提，写入一致性交业务层处理。退场重复内容可隐藏于无障碍导航，但不改变颜色/增加等待；检查覆盖过渡中点击和切换。\n\n## 最小实现与防御边界\n\n- 按明确需求和已确认的问题实现最小改动。不得为假设中的异常、竞态或未来扩展，新增状态、缓存、刷新机制、抽象层或生命周期联动。\n- 添加校验、重试、兜底或异常捕获前，先核对依赖 API 和现有调用链的契约；已有保证不得重复实现。不确定契约时先查源码，不用防御代码代替确认。\n- 操作只检查自身必需的前提，不因单项操作刷新全部能力，不在 UI、ViewModel、服务层重复执行同一检查。UI 提示与执行前校验确有不同职责时可分别保留。\n- 不把未知、加载中或查询失败等同于权限拒绝；不得据此扩大为停服、断连、切换模式或修改用户设置，除非需求明确要求。\n- 异常在能实际处理它的边界捕获；禁止仅为返回默认值、重复记录日志或捕获后原样抛出而增加包装。\n- 抽象应消除已有重复或隔离真实差异。简单逻辑优先直接调用，不引入仅转发、预留扩展或另建同义状态的封装。\n- 保留外部输入校验、权限边界、事务一致性和资源释放等必要保护；新增保护应能说明具体失败场景，以及现有机制为何不足。\n\n## 状态、存储与副作用\n\n- Room 查询保持冷 Flow，在所属 ViewModel 按一致性边界聚合后转为 `StateFlow<Loadable<XxxUiState>>`；Loading 表示首发未完整，Ready(emptyList()) 表示已加载为空，禁止空集合初值、计数器或 attachLoad 推断加载完成。\n- 数据库/文件/网络写入和 Service 启停由明确用户、系统事件或领域方法触发，在 Repository/Store 完成一致性处理；禁止用派生 map/combine/stateIn 的 collect/onEach/watch 或 Composable 状态监听驱动。允许单一权威状态同步到幂等外部投影，但回调不再读取其他状态拼装写入。\n- 一致读取字段由事实源提供同一不可变快照；debounce、conflate、collectLatest 等调度机制不保证一致性；锁或事务必须覆盖全部相关读写，禁止仅锁消费端便宣称多个独立状态源原子一致。Repository 不读取资源/弹 Toast，错误保留原因交 UI 显示。生产设置文件名、字段、默认值是契约，迁移验证旧数据加载、失败重试、并发写入和恢复回滚。\n- 两端复用 AppStorageLayout 与真实 Repository/DAO；文件事务、归档、Cookie、网络配置和维护放共享层，宿主仅适配根目录、编解码、文件选择及系统动作。Desktop profile 只给平台输入，不另存统计、记录、排序或备份恢复事实源。\n- 宿主只注入 files/cache/privateFiles 根目录，布局遵循 [本地产物](docs/local-development.md)。图片缓存、网页用户数据、归档、临时文件须实际接入，不能仅创建目录；引擎安装可用工具缓存。Desktop privateFiles 与 files 同根，Android 保留系统私有目录，profile/导入标记/模拟配置在 data 外。\n- 自定义产物仅放根 `.local/`，工具标准目录不变。日常数据固定 `.local/desktop/`，禁止数据路径环境变量或 build 目录；`--test` 使用 `.local/tests/desktop/<随机标识>/`，不访问日常、真机或生产数据。写入检查先核对隔离标记，报告/截图在会话内；可复用工具提交源码目录。不得随缓存清理日常数据、备份或未处理恢复记录；不增旧目录回退/双写。\n\n## 资源与 Kotlin 规范\n\n- 禁止新增 XML，例外仅 Compose strings/plurals、Android 原生字符串及语言限定资源、app_icon/service 等平台配置。可用 Kotlin 表达的 UI/图标用 `.kt`；页面图标必须 ImageVector，drawable XML 仅限 Manifest 等平台配置及其依赖，即使同图标双用，页面仍用 ImageVector。不确定例外时先问用户。\n- 原生 res 归 gkd-android，gkd-app 不引用其 R；通知小图标通过 Application Manifest `notificationSmallIcon` 的 `android:resource` 注入，运行时校验非零，不持久化 ID。共享颜色用 Kotlin，Compose Resources 位于 gkd-app。\n- 共享及普通 Android Kotlin 文案统一官方 Compose Resources（`Res.string/plurals`），不建自定义 UiString/Desktop 文案表或原生副本；只有 Manifest/平台配置/变体标签保留原生 ID，Kotlin 可按需读取平台标签。\n- 官方资源对象（如 `StringResource`、`PluralStringResource`）不限于 UI 用途，允许枚举、业务状态、异常、Repository 等对象持有和传递，供 UI、HTTP、后台任务和导出等场景使用；在实际消费或输出边界按需调用官方 API 解析。资源对象与已解析的文案字符串分开处理：不在枚举、单例或静态初始化中解析、缓存文案字符串；语义/参数与持久化数据分离，语言切换不改写用户模板。\n- 字符串及复数资源统一使用 `import li.gkd.app.resources.*` 导入，不逐项导入 `Res` 或资源访问器；调用保持 `Res.string.xxx` / `Res.plurals.xxx`，文案解析继续使用官方 API。\n- 参数只用 `%1$s`、`%2$s` 等位置文本占位符；禁止数值/精度/补零格式指令，Kotlin 按明确 Locale/精度先格式化。Compose 用官方 Composable API，协程用 suspend getString/getPluralString；getSync 仅限低频同步边界，不用于静态初始化、高频或新增 Compose 路径，不用 runBlocking(Dispatchers.Main)。新增翻译/复数/限定符验证双端选择替换，校验任务不生成平台副本，访问器/打包交官方插件。\n- Kotlin 可见性按实际访问需求收窄，优先使用 private；gkd-app 模块禁止使用 internal 关键字，需要跨文件或类访问的声明使用默认 public，其他模块允许 internal。仅为公开属性收窄可见性/可变性的 `_xxx` 字段改用 Explicit Backing Fields；普通私有缓存、生成命名及 Lambda `_` 不受限。避免循环静态初始化。\n- 工具按职责组织并优先沿用邻近代码风格；无状态工具可用顶级函数，有共享状态或明确命名空间需求时使用 object，不为形式新增包装。XxxExt.kt 只放扩展，普通工具放职责明确的文件；不借规范调整进行无关重构。\n- 跨文件/页面复用 UI 统一 `Gk` + PascalCase，名称描述用途，禁用 Perf/Custom 泛化前缀。单组件文件同名，组件族及配套声明可同文件，文件以 Gk 开头并描述组件族；配置用 GkAbcDefaults/Colors，图标用 GkIcon/GkIcons。页面、私有 Composable、Preview、工具、Modifier 扩展、独立状态及其 Render() 不强制此前缀。\n\n## Desktop 运行时\n\n- 模拟配置、开发 HTTP 和工具不承担跨版本兼容，模型变更同步消费者，可重置，不增迁移/旧格式适配；生产数据兼容不受影响。开发工具统一 TypeScript、根 scripts/desktop 与 `pnpm app:tools`，复用公共 HTTP/控件逻辑，不另建同类 PowerShell/Python 脚本。\n- 预览/控制窗口同进程，关闭控制窗口不退出 App 且可重新打开（F12）。模拟器为单一状态源，控件和 HTTP 同命令路径。系统栏由 jvmMain 窗口模拟层实时绘制，时间取当前时钟，系统栏/挖孔统一计算 Insets；不以静态或整页截图替代 UI，不把模拟网络/电量冒充真机。未实现内容如实标识，不宣称模拟成功。\n- 不支持仅针对具体动作，以非模态 Toast 显示“当前平台不支持”，不替代整页或伪造成功；编辑/列表/文件业务继续真实执行。用户明确要求的 priv-ui 模拟可修改 Desktop 授权/启动/停止状态，日志标记 simulation，不执行真实特权命令。\n- priv-ui 只用公开 PrivilegeScreen、状态/actions，禁用内部 renderer/模拟器或 INVISIBLE_MEMBER；页面及系统提示 Insets 分别注入，宿主模拟状态机与控制窗口特权可用状态同步。使用版本目录的远程稳定版，升级验证双端编译和实际页面。\n- HTTP 仅在 jvmMain、绑定 127.0.0.1，不进入 Android 或混用业务服务；状态修改在 UI 线程。语义树/操作/截图明确 app|controls，各窗口节点快照独立；操作执行真实控件，不用直接改状态冒充点击。模拟返回先收 IME，再处理弹窗/编辑/页面。\n- 截图区分实时捕获和重新组合渲染，后者不证明当前焦点/滚动/弹窗。Desktop 不替代 Android 像素验收；对照须同页面状态、dp、fontScale、Insets、字体和颜色。设备导出仅 UI 所需输入，设备资料/字体不提交。\n\n## 验证与 Git\n\n- 按改动做最小必要验证：共享逻辑/UI、Desktop 只执行对应 JVM 编译及相关业务测试，不因 commonMain 或文档含双端命令追加 Android。仅 Android 实现、资源、Manifest、AIDL、隐藏 API、依赖/构建链路、专项规则或用户要求才做 Android 验证；默认仅 gkd 渠道，未经明确要求禁止 play。\n- UI 开发用 `./gradlew.bat :gkd-app:run`，构建前停止本任务启动的 Desktop JVM，避免覆盖运行中 JAR。网页使用版本目录 WebView2 和系统 Runtime。UI 默认使用对应编译及人工/HTTP 检查，不要求自动 UI 套件；仅为已发生、难以稳定人工复查的重要回归增加小范围自动测试，禁止仅复述静态布局或实现细节。业务状态机、持久化、事务恢复、协议/兼容测试保留，不按 ui 包名删除。\n- 新测试说明输入、预期及回归，优先边界/异常/兼容行为；存储测试验证实际读写恢复，不仅路径。测试遵循生产生命周期，可用独立会话和构造依赖实现隔离；全局单例的集成测试复用一次真实应用生命周期及隔离存储。禁止为测试拆散生产逻辑、扩大可见性、增加专用 API、ViewModel、嵌套 Store 或生产重置/替换接口；不以逐类工厂、fork 或 ClassLoader 掩盖生命周期缺陷。\n- 不测试枚举/常量集合、连续编号、同注册表推导关系或类型系统保证的静态声明；外部协议/持久化/跨版本标识例外，名称或注释说明保护的契约。\n- Android 权限、IME、系统栏、预测返回、无障碍/Service 按需 adb 验证。真机/模拟器交互前记录自动化开关与运行模式，关闭 enableAutomator 并退出自动化模式，核对后再检查；finally 在成功/失败/中断后仅恢复本次字段并核对，不用旧整份配置覆盖。仅 Desktop 检查不改真机。\n- 用户仅要求提交/推送时，只做轻量核对及 Git 操作；未经明确要求，不扩展为深度审查、临时 worktree 验证、全量构建/测试、Release Gate 或发布检查。\n\n## 依赖与 Android 专项\n\n- 外部 npm 依赖/版本集中根 package.json，private 子包所有 dependencies/devDependencies/optionalDependencies/peerDependencies 仅用 workspace:。可发布子包自行声明独立安装所需运行时/optional/peer 依赖，公共开发工具仍在根；变更同步清单和 pnpm 锁文件。\n- Android framework Java/AIDL 源码定位、跨版本签名/可用性、缺失分析及 Java hidden-API 生成必须使用 [android-api-diff skill](.agents/skills/android-api-diff/SKILL.md) 与其 CLI，保留默认 JSON，不自行模拟版本检查。安装/更新 skill 在根执行 `android-api-diff skill install`。\n- 修改隐藏接口/父类实现、系统回调、remap 或相关模块依赖/R8 配置，以及排查相关混淆 Release 故障时，必须使用 [android-hidden-api-r8 skill](.agents/skills/android-hidden-api-r8/SKILL.md)。检查最终应用模块 `gkd-android` 的实际 R8 输入及优化后的运行时契约，不以源码 override、编译成功或 Debug 正常代替；`gkd-app` 可见的 `gkd-hidden-api` stub 不代表最终 R8 可见。默认仅验证受影响的 gkd Release 路径，未经用户要求或证据支持不扩大审计范围。\n- 特权进程 UserService 的可能失败 Binder 方法在最外层末端 `catch (e: Throwable)` 捕获可恢复错误，通过 Binder 可传输异常/结果保留原类型、消息、堆栈；不只在主进程捕获，不让 NoSuchMethodError 等 LinkageError 逃逸崩溃。VirtualMachineError/ThreadDeath 等终止错误可原样抛出，不伪装普通失败。\n\n<!-- File: .agents/skills/android-api-diff/agents/openai.yaml (Tokens: ~46 | Category: anthropic-skill) -->\ninterface:\n  display_name: 'Android API Diff'\n  short_description: 'Query Android API changes and generate hidden API code'\n  default_prompt: 'Use $android-api-diff to inspect an Android framework API across versions.'\n\n<!-- File: .agents/skills/android-api-diff/SKILL.md (Tokens: ~1366 | Category: anthropic-skill) -->\n---\nname: android-api-diff\ndescription: Inspect Android framework Java and AIDL APIs across versions with the android-api-diff CLI. Use when resolving an Android API to frameworks/base source, fetching the complete content of a tagged source file, comparing signatures or availability by Android version, explaining missing APIs, generating Java hidden-API access code, or explicitly prewarming the local query cache.\n---\n\n# Android API Diff\n\nUse the `android-api-diff` CLI as the only tool interface. Keep its default JSON\noutput so results remain machine-readable.\n\n## Route the request\n\n- Run `generate` when the user wants Java hidden-API access code. It performs the\n  version query internally, so never call `query` first.\n- Run `query` when the user wants signatures, version ranges, availability,\n  missing reasons, or source metadata.\n- Run `resolve` when the user only needs the matching `frameworks/base` file and\n  target kind.\n- Run `source` when the user wants the complete content of a known source path\n  at an exact Android release tag. If only an API name is known, run `resolve`\n  first and use the chosen `result.candidates[].source.path`.\n- Run `preload` only when the user explicitly asks to preload several APIs.\n\nAlways quote API names because constructors and member references can contain\nshell-sensitive characters.\n\n```sh\nandroid-api-diff resolve \"ContentObserver()\"\nandroid-api-diff source android-17.0.0_r1 core/java/android/accessibilityservice/AccessibilityButtonController.java\nandroid-api-diff query \"IActivityManager.getTasks\" --min-sdk 28\nandroid-api-diff generate \"ActivityThread.currentApplication\" --min-sdk 28\nandroid-api-diff preload \"ContentObserver()\" \"IActivityManager.getTasks\" --min-sdk 28\n```\n\nOmit `--min-sdk` unless the user provides a minimum Android API level. The CLI\nthen uses its built-in default.\n\n## Interpret results\n\nTreat exit code `0` together with top-level `ok: true` as success. Read the\noperation result from the top-level `result` field.\n\n`resolve` returns `result.candidates` in recommended order, or an empty array\nwhen no source matches. `query` and `generate` include the same candidate list\nand inspect the recommended source by default. When the user specifies another\nmatching source, pass `--source-path <path>` to `query` or `generate`; retain the\noriginal API reference to preserve its member or constructor target. Candidate\nmetadata alone does not prove that a member exists in a particular version.\n\nFor `query`, describe range endpoints as the first or last tag checked in the\ncurrent snapshot. A `last-checked` endpoint is not a promise that no later\nrevision exists.\n\nFor member queries, treat `result.overloads` as the source of truth. Each item\nhas a stable `overloadId`, its latest checked `signature` and `member`, and\nindependent `ranges`. A range with `missingReason: \"overload-not-found\"` means\nthe member name exists in that source revision but that specific parameter\nsignature does not. Use top-level `result.ranges[].overloadIds` only when\ndescribing which overloads were available together.\n\nOn a nonzero exit code, read the structured JSON error from stderr and report its\n`error.code` and `error.message`. Do not add an automatic retry: the CLI network\nlayer already retries transient fetches.\n\n## Choose an interface constant access strategy\n\nFour strategies are supported and have already been validated in the companion\nremap repository. Choose based on the user's build and value requirements;\ndo not require another feasibility experiment unless the user requests one.\n\n| Strategy                | When to use it                                                                                                 | Declaration or access                                                                      |\n| ----------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| Reflection              | Read the device's value without a remapped field access                                                        | Read the original interface field reflectively.                                            |\n| Interface stub          | Preserve runtime field access in a compatible remap build                                                      | Initialize the interface field with `RemapStub.value()`.                                   |\n| Consistent constant     | Embed a supported primitive expression that agrees across checked versions                                     | Keep the original initializer in the generated interface.                                  |\n| Implementing-class stub | Read the device's value through a known platform class implementing the interface, without `RemapStub.value()` | Declare a `public static`, non-final field with no initializer in that class's remap stub. |\n\nThe current `generate` command automatically chooses the consistent-constant\nstrategy for supported primitive initializers whose token sequences match across\nchecked definitions, ignoring whitespace and comments. It does not evaluate\nexpressions: `0x1` and `(1 << 0)` differ. Constant references, unsupported\nexpressions, missing initializers, differing definitions, and non-primitive\ntypes fall back to `RemapStub.value()`. Agreement covers only checked versions;\ndo not download all versions to strengthen this conclusion.\n\nReflection and implementing-class stubs are adaptations of the generated code,\nnot CLI flags or automatic generator fallbacks. Honor an explicitly selected\nstrategy. For an interface/R8 incompatibility, use a consistent constant when\nthe generated result supports it; otherwise prefer a known suitable platform\nimplementing class, or reflection when no suitable class is known. Do not\npresent the `RemapStub.value()` fallback as resolving that incompatibility.\n\nFor example, access `IBinder.SHELL_COMMAND_TRANSACTION` through a stub mapped\nto the platform's `Binder` implementation:\n\n```java\n@li.songe.remap.RemapType(android.os.Binder.class)\npublic class BinderHidden {\n    public static int SHELL_COMMAND_TRANSACTION;\n}\n```\n\nRead `BinderHidden.SHELL_COMMAND_TRANSACTION` at the call site. Keep the field\nname and type matching the original interface declaration; omit both `final`\nand an initializer. This strategy also applies to reference types and values\nthat differ between versions. Map to the real platform implementing class;\nmerely declaring a field in an unmapped application subclass creates a separate\nfield. Use these declarations for reads, since the original interface field is\nfinal.\n\n## Handle a missing CLI\n\nIf `android-api-diff` is not installed, stop and tell the user to run:\n\n```sh\nnpm install --global android-api-diff@latest\nandroid-api-diff skill install\n```\n\nRun the Skill command from the root of the current Git or Gradle project so it\nremains project-scoped. Do not implement or simulate version checks. The\npackage manager owns CLI update detection and upgrades.\n</yakaai_skills>"}