svharness 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +531 -0
  2. package/bin/cli.js +3 -0
  3. package/dist/adapters/_frontmatter.js +24 -0
  4. package/dist/adapters/claude-code.js +12 -0
  5. package/dist/adapters/codechat.js +12 -0
  6. package/dist/adapters/cursor.js +19 -0
  7. package/dist/adapters/generic.js +19 -0
  8. package/dist/adapters/index.js +26 -0
  9. package/dist/adapters/qoder.js +12 -0
  10. package/dist/commands/apply.js +272 -0
  11. package/dist/commands/init.js +420 -0
  12. package/dist/core/agent-injector.js +192 -0
  13. package/dist/core/next-steps.js +91 -0
  14. package/dist/core/render-meta.js +81 -0
  15. package/dist/core/repomix-pack.js +54 -0
  16. package/dist/core/scaffold.js +93 -0
  17. package/dist/core/state.js +80 -0
  18. package/dist/index.js +239 -0
  19. package/dist/types.js +5 -0
  20. package/dist/utils/baseline-copy.js +591 -0
  21. package/dist/utils/baseline-defaults.js +106 -0
  22. package/dist/utils/logger.js +56 -0
  23. package/dist/utils/validate-args.js +132 -0
  24. package/dist/utils/version.js +23 -0
  25. package/dist/wiki/abort.js +30 -0
  26. package/dist/wiki/config.js +79 -0
  27. package/dist/wiki/defaults.js +16 -0
  28. package/dist/wiki/envLoader.js +78 -0
  29. package/dist/wiki/index.js +29 -0
  30. package/dist/wiki/openaiCompat.js +219 -0
  31. package/dist/wiki/repowikiCanonicalSections.js +67 -0
  32. package/dist/wiki/repowikiCheckpoint.js +106 -0
  33. package/dist/wiki/repowikiConfig.js +9 -0
  34. package/dist/wiki/repowikiGit.js +73 -0
  35. package/dist/wiki/repowikiIndexer.js +824 -0
  36. package/dist/wiki/repowikiMarkdownPost.js +123 -0
  37. package/dist/wiki/repowikiMetadataContent.js +64 -0
  38. package/dist/wiki/repowikiMetadataJson.js +15 -0
  39. package/dist/wiki/repowikiScanner.js +156 -0
  40. package/dist/wiki/repowikiStructureNav.js +286 -0
  41. package/dist/wiki/repowikiStructureNormalize.js +218 -0
  42. package/dist/wiki/wikiStructureXml.js +316 -0
  43. package/dist/wiki/wikiTasksWriter.js +127 -0
  44. package/package.json +57 -0
  45. package/templates/_shared/apply-skills/harness-apply-skills-main.md +91 -0
  46. package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +35 -0
  47. package/templates/_shared/build-rules/harness-build-rule-chinese-only.md +49 -0
  48. package/templates/_shared/build-rules/harness-build-rule-memory-write.md +31 -0
  49. package/templates/_shared/build-rules/harness-build-rule-orchestrator-flow.md +25 -0
  50. package/templates/_shared/build-rules/harness-build-rule-skills-tasks-output.md +35 -0
  51. package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +32 -0
  52. package/templates/_shared/build-rules/harness-build-rule-user-interaction.md +63 -0
  53. package/templates/_shared/build-skills/harness-build-skill-knowledge-builder.md +120 -0
  54. package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +87 -0
  55. package/templates/_shared/build-skills/harness-build-skill-spec-builder.md +85 -0
  56. package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +77 -0
  57. package/templates/_shared/meta/AGENTS.md.ejs +53 -0
  58. package/templates/_shared/meta/CHANGELOG.md.ejs +15 -0
  59. package/templates/_shared/meta/README.md.ejs +51 -0
  60. package/templates/_shared/meta/VERSION.ejs +1 -0
  61. package/templates/_shared/meta/harness.yaml.ejs +52 -0
  62. package/templates/_shared/skeleton/agent-env/memory/categories/.gitkeep +1 -0
  63. package/templates/_shared/skeleton/agent-env/memory/inbox/.gitkeep +1 -0
  64. package/templates/_shared/skeleton/agent-env/skills/.gitkeep +1 -0
  65. package/templates/_shared/skeleton/agent-env/tools/.gitkeep +1 -0
  66. package/templates/_shared/skeleton/assets/baseline/code/.gitkeep +1 -0
  67. package/templates/_shared/skeleton/assets/baseline/repomix/.gitkeep +1 -0
  68. package/templates/_shared/skeleton/assets/baseline/wiki/.gitkeep +1 -0
  69. package/templates/_shared/skeleton/assets/raw/.gitkeep +1 -0
  70. package/templates/_shared/skeleton/assets/requirements/.gitkeep +1 -0
  71. package/templates/_shared/skeleton/commands/install/.gitkeep +1 -0
  72. package/templates/_shared/skeleton/commands/update/.gitkeep +1 -0
  73. package/templates/_shared/skeleton/specs/behavior/schema.json +39 -0
  74. package/templates/_shared/skeleton/specs/interfaces/schema.json +38 -0
  75. package/templates/_shared/skeleton/specs/signals/schema.json +37 -0
  76. package/templates/_shared/skeleton/specs/ui/schema.json +44 -0
  77. package/templates/_shared/skeleton/tasks/templates/.gitkeep +0 -0
  78. package/templates/android-compose/skeleton/agent-env/rules/harness-compose-mandatory.mdc +49 -0
  79. package/templates/android-compose/skeleton/agent-env/rules/harness-coroutines-scope.mdc +52 -0
  80. package/templates/android-compose/skeleton/agent-env/rules/harness-hilt-injection.mdc +47 -0
  81. package/templates/android-compose/skeleton/agent-env/rules/harness-mvi-layering.mdc +58 -0
  82. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/SKILL.md +260 -0
  83. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/gradle-module-patterns.md +66 -0
  84. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/implementation-checklist.md +45 -0
  85. package/templates/android-compose/skeleton/agent-env/skills/harness-android-architecture/references/udf-data-flow.md +80 -0
  86. package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/SKILL.md +79 -0
  87. package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/references/interact.md +83 -0
  88. package/templates/android-compose/skeleton/agent-env/skills/harness-android-cli/references/journeys.md +97 -0
  89. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/SKILL.md +162 -0
  90. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/canonical-sources.md +116 -0
  91. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/diagnostics.md +182 -0
  92. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/report-template.md +135 -0
  93. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/scoring.md +277 -0
  94. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/references/search-playbook.md +303 -0
  95. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-audit/scripts/compose-reports.init.gradle +58 -0
  96. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-state/SKILL.md +196 -0
  97. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/SKILL.md +192 -0
  98. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/composable-api-guide.md +123 -0
  99. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/performance-recipes.md +97 -0
  100. package/templates/android-compose/skeleton/agent-env/skills/harness-compose-ui/references/state-patterns.md +93 -0
  101. package/templates/android-compose/skeleton/agent-env/skills/harness-kotlin-coroutines/SKILL.md +167 -0
  102. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/SKILL.md +45 -0
  103. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/CONFIGURATION.md +44 -0
  104. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md +83 -0
  105. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/REDUNDANT-RULES.md +222 -0
  106. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/REFLECTION-GUIDE.md +139 -0
  107. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/android/topic/performance/app-optimization/enable-app-optimization.md +176 -0
  108. package/templates/android-compose/skeleton/agent-env/skills/harness-r8-analyzer/references/android/training/testing/other-components/ui-automator.md +312 -0
  109. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/SKILL.md +87 -0
  110. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/analysis-of-the-project-and-layout.md +42 -0
  111. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/android/develop/ui/compose/designsystems/migrate-xml-theme-to-compose.md +168 -0
  112. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/android/develop/ui/compose/setup-compose-dependencies-and-compiler.md +183 -0
  113. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/identify-optimal-xml-candidate.md +31 -0
  114. package/templates/android-compose/skeleton/agent-env/skills/harness-xml-to-compose/references/xml-layout-migration.md +86 -0
  115. package/templates/android-xml/skeleton/agent-env/rules/seed-aidl-thread.md +29 -0
  116. package/templates/android-xml/skeleton/agent-env/rules/seed-lifecycle-awareness.md +32 -0
  117. package/templates/android-xml/skeleton/agent-env/rules/seed-mvc-layering.md +32 -0
  118. package/templates/android-xml/skeleton/agent-env/rules/seed-view-binding.md +33 -0
  119. package/templates/android-xml/skeleton/agent-env/rules/seed-xml-styling.md +27 -0
  120. package/templates/cpp/skeleton/agent-env/rules/seed-cmake-explicit-sources.md +31 -0
  121. package/templates/cpp/skeleton/agent-env/rules/seed-header-guards.md +34 -0
  122. package/templates/cpp/skeleton/agent-env/rules/seed-include-layering.md +39 -0
  123. package/templates/cpp/skeleton/agent-env/rules/seed-no-cyclic-deps.md +29 -0
  124. package/templates/cpp/skeleton/agent-env/rules/seed-raii.md +30 -0
  125. package/templates/python/skeleton/agent-env/rules/seed-context-managers.md +60 -0
  126. package/templates/python/skeleton/agent-env/rules/seed-docstrings.md +48 -0
  127. package/templates/python/skeleton/agent-env/rules/seed-import-order.md +49 -0
  128. package/templates/python/skeleton/agent-env/rules/seed-pep8-naming.md +45 -0
  129. package/templates/python/skeleton/agent-env/rules/seed-type-annotations.md +43 -0
  130. package/templates/web-react/skeleton/agent-env/rules/seed-controlled-component.md +43 -0
  131. package/templates/web-react/skeleton/agent-env/rules/seed-effect-cleanup.md +43 -0
  132. package/templates/web-react/skeleton/agent-env/rules/seed-hook-rules.md +42 -0
  133. package/templates/web-react/skeleton/agent-env/rules/seed-key-stability.md +39 -0
  134. package/templates/web-react/skeleton/agent-env/rules/seed-no-props-drilling.md +43 -0
@@ -0,0 +1,47 @@
1
+ ---
2
+ description: Hilt 依赖注入约束,确保 ViewModel 和 UseCase 的正确注入方式
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Hilt 依赖注入约束
7
+
8
+ ## ViewModel 注入
9
+
10
+ ### ViewModel 必须使用 @HiltViewModel + @Inject constructor
11
+ - 正例:`@HiltViewModel class MyViewModel @Inject constructor(private val useCase: MyUseCase) : BaseViewModel<...>()`
12
+ - 反例:手动创建 `ViewModelProvider.Factory` 来实例化可由 Hilt 管理的 ViewModel
13
+ - 检测:grep ViewModel 类声明,缺少 `@HiltViewModel` 或 `@Inject constructor`
14
+
15
+ ### Compose 容器层必须使用 hiltViewModel() 获取 ViewModel
16
+ - 正例:`val viewModel: MyViewModel = hiltViewModel()`
17
+ - 反例:`viewModel(factory = MyViewModel.Factory)` — 手动 Factory(除非非 Activity 场景)
18
+ - 检测:grep `viewModel(` 后跟 `factory =` 且非非 Activity 场景
19
+
20
+ ### 禁止为传 UseCase 创建包装接口
21
+ - 正例:ViewModel 构造函数直接注入 `UseCase` 接口
22
+ - 反例:创建 `FeatureDependencies` / `XxxDependencies` 包装接口来传 UseCase
23
+ - 检测:grep `Dependencies` 接口/类名出现于 feature 模块
24
+
25
+ ## Application 配置
26
+
27
+ ### Application 必须使用 @HiltAndroidApp 且 Manifest 配置 android:name
28
+ - 正例:`@HiltAndroidApp class MyApp : Application()` + Manifest `<application android:name=".MyApp">`
29
+ - 反例:Application 未标注 `@HiltAndroidApp` 或 Manifest 缺少 `android:name`
30
+ - 检测:grep Application 类缺少 `@HiltAndroidApp`,或 Manifest `<application>` 缺少 `android:name`
31
+
32
+ ### Activity 必须使用 @AndroidEntryPoint
33
+ - 正例:`@AndroidEntryPoint class MainActivity : ComponentActivity()`
34
+ - 反例:Activity 未标注 `@AndroidEntryPoint`
35
+ - 检测:grep 继承 `ComponentActivity` / `AppCompatActivity` 的类缺少 `@AndroidEntryPoint`
36
+
37
+ ## 依赖方向
38
+
39
+ ### 禁止从 Activity 向 Composable 层层透传 UseCase
40
+ - 正例:Compose 通过 `hiltViewModel()` 获取 ViewModel,只传 UI 回调和页面参数
41
+ - 反例:`MyScreen(useCase = myUseCase)` — 透传 UseCase 到 Composable
42
+ - 检测:grep Composable 函数参数中出现 `UseCase` 类型
43
+
44
+ ### Repository 接口定义在 Domain 层,实现在 Foundation 层
45
+ - 正例:Domain 模块定义 `interface XxxRepository`,Foundation 模块提供 `XxxRepositoryImpl`
46
+ - 反例:Repository 接口和实现都在 Foundation 层 — 违反依赖倒置
47
+ - 检测:grep foundation 模块中定义的 Repository 接口(非 impl 类)
@@ -0,0 +1,58 @@
1
+ ---
2
+ description: MVI 平台化架构分层铁律,约束层级依赖与数据流方向
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # MVI 分层铁律
7
+
8
+ ## 层级依赖规则
9
+
10
+ ### 依赖方向严格向内(向下),禁止反向依赖
11
+ - Feature → Domain → Foundation → 架构层(MVI Core)
12
+ - 正例:Feature 依赖 Domain,Domain 依赖 Foundation
13
+ - 反例:Foundation 依赖 Domain、Domain 依赖 Feature — 反向依赖
14
+ - 检测:grep 各模块 `build.gradle*` 的 `implementation` / `api` 声明,Foundation 不应出现 Domain/Feature 依赖
15
+
16
+ ### Feature 层禁止直接依赖 Foundation 层
17
+ - 正例:Feature → Domain UseCase → Foundation Repository
18
+ - 反例:Feature 直接调用 Repository / Room / Retrofit
19
+ - 检测:grep feature 模块源码中出现 `Repository` / `Dao` / `ApiService` 直接引用
20
+
21
+ ### Domain 层禁止持有 View/Context 引用
22
+ - 正例:UseCase 只注入 Repository 接口
23
+ - 反例:UseCase 构造函数接收 `Context` 或 `View`
24
+ - 检测:grep domain 模块源码中 `import android.view` / `import android.content.Context`
25
+
26
+ ## 模块规则
27
+
28
+ ### Foundation 必须独立仓库
29
+ - 正例:foundation-repo 独立 Git 仓库,发布为 AAR/Maven 坐标
30
+ - 反例:foundation 与 domain/feature 共仓
31
+ - 检测:验证 foundation 目录是否与 domain/feature 在同一 Git 仓库
32
+
33
+ ### Domain 与 Feature 必须是不同 Gradle module
34
+ - 正例:`include(":domain")` 和 `include(":feature")` 分开声明
35
+ - 反例:domain 和 feature 在同一个 module 内
36
+ - 检测:grep `settings.gradle*` 确认 domain/feature 为独立 module
37
+
38
+ ### 跨仓依赖通过 AAR/Maven 坐标,禁止跨仓 project 引用
39
+ - 正例:`implementation("com.xxx:foundation:1.0.0")`
40
+ - 反例:`implementation(project(":foundation"))` — 跨仓不允许
41
+ - 检测:grep 跨仓模块的 `build.gradle*` 出现 `project(":")` 引用其他仓库模块
42
+
43
+ ## MVI 协议规则
44
+
45
+ ### Reducer 是纯函数,只做 state.copy()
46
+ - 正例:`is Event.Loaded -> state.copy(isLoading = false, data = event.data)`
47
+ - 反例:`is Event.Loaded -> { repository.cache(event.data); state.copy(...) }` — Reducer 内有副作用
48
+ - 检测:grep Reducer lambda 内出现 `repository.` / `useCase.` / `viewModelScope` 调用
49
+
50
+ ### EffectHandler 不修改 State,只发射 Effect
51
+ - 正例:`is Event.Error -> emitEffect(Effect.ShowToast(event.message))`
52
+ - 反例:EffectHandler 内调用 `dispatch(Event)` 或修改 `_uiState`
53
+ - 检测:grep EffectHandler lambda 内出现 `dispatch(` / `state.copy(`
54
+
55
+ ### State 为不可变 data class,更新只用 .copy()
56
+ - 正例:`data class UiState(val items: List<Item> = emptyList())`
57
+ - 反例:`data class UiState(var items: MutableList<Item>)` — var + MutableList 可变
58
+ - 检测:grep State data class 中出现 `var` 属性或 `MutableList` / `MutableMap` 类型
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: harness-android-architecture
3
+ description: 设计或审查基于 MVI 的 Android 平台化架构时使用。覆盖四层职责、依赖规则、UDF 数据流、ViewModel/UseCase/Repository 接线模式和实现检查清单。刚性约束由 rules 加载,本技能聚焦实现指引与模板代码。
4
+ ---
5
+
6
+ # MVI 平台化架构 — 实现指引
7
+
8
+ ## 概述
9
+
10
+ 本项目采用 **MVI + 平台化分层** 架构,遵循严格**单向数据流(UDF)**。层间依赖严格向内(向下),禁止反向依赖。
11
+
12
+ > **刚性约束已由 rules 加载,本技能不再重复。** 详见:
13
+ > - `harness-mvi-layering` — 层级依赖、模块规则、MVI 协议约束
14
+ > - `harness-hilt-injection` — Hilt 注入约束
15
+ > - `harness-coroutines-scope` — 协程作用域约束
16
+
17
+ ---
18
+
19
+ ## 分层模型
20
+
21
+ ```
22
+ Feature 层(Compose UI / ViewModel)
23
+ │ 发送 Intent,收集 State/Effect
24
+
25
+ Domain 层(UseCase)
26
+ │ 业务逻辑,通过 Registrable Callback 回传结果
27
+
28
+ Foundation 层(Repository / Room / Retrofit)
29
+ │ 纯技术能力,不含业务状态
30
+
31
+ 架构层(MVI Core)
32
+ 定义 MVI 协议,不含业务逻辑
33
+ ```
34
+
35
+ | 层级 | 职责 | 禁止事项 |
36
+ |------|------|---------|
37
+ | **架构层** | 定义 MVI 协议,提供 BaseViewModel/MviStore | 禁止业务 if-else,禁止持有视图引用 |
38
+ | **基础层** | 网络、持久化、日志等纯技术能力 | 禁止持有业务状态 |
39
+ | **领域层** | UseCase 封装业务逻辑,Registrable 回调异步结果 | 禁止直接依赖 Feature 层,禁止持有 View/Context |
40
+ | **特性层** | Compose UI 渲染 State,发送 Intent,收集 Effect | 禁止直接调用 Foundation 层 |
41
+
42
+ 仓库/模块规则详见 [references/gradle-module-patterns.md](references/gradle-module-patterns.md)。
43
+
44
+ ---
45
+
46
+ ## UDF 数据流
47
+
48
+ ```
49
+ View ──Intent──> ViewModel.process()
50
+
51
+
52
+ handleIntent() --> UseCase 调用
53
+ │ Callback
54
+
55
+ dispatch(Event)
56
+
57
+ ┌────────────────┴────────────────┐
58
+ ▼ ▼
59
+ Reducer EffectHandler
60
+ │ │
61
+ 新 State Effect
62
+ │ │
63
+ ▼ ▼
64
+ StateFlow (持久) SharedFlow (一次性)
65
+ │ │
66
+ collectAsState() LaunchedEffect 收集
67
+ ```
68
+
69
+ 详细数据流图解见 [references/udf-data-flow.md](references/udf-data-flow.md)。
70
+
71
+ ---
72
+
73
+ ## 各层实现模板
74
+
75
+ ### 1. 特性层 — ViewModel + Compose UI
76
+
77
+ ```kotlin
78
+ @HiltViewModel
79
+ class BluetoothViewModel @Inject constructor(
80
+ private val cases: BluetoothUseCase
81
+ ) : BaseViewModel<
82
+ BluetoothContract.State,
83
+ BluetoothContract.Event,
84
+ BluetoothContract.Effect,
85
+ BluetoothContract.Intent,
86
+ BluetoothContract.Callback>(useCase = cases) {
87
+
88
+ override val initialState = BluetoothContract.State()
89
+
90
+ override val useCaseCallback = object : BluetoothContract.Callback {
91
+ override fun onDeviceConnected(device: BluetoothDevice) {
92
+ dispatch(BluetoothContract.Event.Connected(device))
93
+ }
94
+ override fun onOperationFailed(reason: String) {
95
+ dispatch(BluetoothContract.Event.OperationFailed(reason))
96
+ }
97
+ // ... 其他回调
98
+ }
99
+
100
+ override val reducer = Reducer<BluetoothContract.State, BluetoothContract.Event> { state, event ->
101
+ when (event) {
102
+ is Event.Connected -> state.copy(isLoading = false, connectedDevice = event.device)
103
+ is Event.OperationFailed -> state.copy(isLoading = false)
104
+ else -> state
105
+ }
106
+ }
107
+
108
+ override val effectHandler = EffectHandler<BluetoothContract.Event, BluetoothContract.Effect> { event, emitEffect ->
109
+ when (event) {
110
+ is Event.OperationFailed -> emitEffect(Effect.ShowToast(event.reason))
111
+ else -> { /* 无副作用 */ }
112
+ }
113
+ }
114
+
115
+ override fun handleIntent(intent: BluetoothContract.Intent) {
116
+ when (intent) {
117
+ is Intent.ConnectToDevice -> {
118
+ dispatch(Event.ConnectionStarted)
119
+ cases.connect(intent.deviceAddress)
120
+ }
121
+ is Intent.StartScan -> cases.startScan()
122
+ }
123
+ }
124
+
125
+ init { startUseCaseListening() }
126
+ }
127
+ ```
128
+
129
+ Compose 容器层 / 纯 UI 层分离:
130
+
131
+ ```kotlin
132
+ // 容器层:持有 ViewModel,处理 Effect
133
+ @Composable
134
+ fun BluetoothScreen(viewModel: BluetoothViewModel = hiltViewModel()) {
135
+ val state by viewModel.state.collectAsStateWithLifecycle()
136
+ LaunchedEffect(Unit) {
137
+ viewModel.effect.collect { effect ->
138
+ when (effect) {
139
+ is Effect.ShowToast -> Toast.makeText(LocalContext.current, effect.message, Toast.LENGTH_SHORT).show()
140
+ }
141
+ }
142
+ }
143
+ BluetoothContent(
144
+ state = state,
145
+ onIntent = { viewModel.process(it) }
146
+ )
147
+ }
148
+
149
+ // 纯 UI 层:无 ViewModel 依赖
150
+ @Composable
151
+ fun BluetoothContent(
152
+ state: BluetoothContract.State,
153
+ onIntent: (BluetoothContract.Intent) -> Unit
154
+ ) { /* 纯渲染逻辑 */ }
155
+ ```
156
+
157
+ ### 2. 领域层 — UseCase
158
+
159
+ ```kotlin
160
+ interface BluetoothUseCase : Registrable<BluetoothContract.Callback> {
161
+ fun connect(deviceAddress: String)
162
+ fun startScan()
163
+ fun stopScan()
164
+ }
165
+
166
+ class BluetoothUseCaseImpl @Inject constructor(
167
+ private val repository: BluetoothRepository
168
+ ) : BluetoothUseCase {
169
+ private var callback: BluetoothContract.Callback? = null
170
+
171
+ override fun register(callback: BluetoothContract.Callback) {
172
+ this.callback = callback
173
+ startObserving()
174
+ }
175
+ override fun unRegister() { callback = null }
176
+
177
+ override fun connect(deviceAddress: String) {
178
+ repository.connect(deviceAddress) { result ->
179
+ result.fold(
180
+ onSuccess = { callback?.onDeviceConnected(it) },
181
+ onFailure = { callback?.onOperationFailed(it.message ?: "连接失败") }
182
+ )
183
+ }
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### 3. 基础层 — Repository
189
+
190
+ ```kotlin
191
+ // 接口定义在 Domain 层,此处只放实现
192
+ class BluetoothRepositoryImpl @Inject constructor(
193
+ private val bluetoothDao: BluetoothDao,
194
+ private val bluetoothApi: BluetoothApiService
195
+ ) : BluetoothRepository {
196
+ override fun connect(deviceAddress: String, onResult: (Result<BluetoothDevice>) -> Unit) {
197
+ // 实现连接逻辑
198
+ }
199
+ }
200
+ ```
201
+
202
+ Room / Retrofit / 数据映射模板详见 [references/implementation-checklist.md](references/implementation-checklist.md) 的基础层部分。
203
+
204
+ ---
205
+
206
+ ## 多 UseCase 组合
207
+
208
+ ViewModel 可注入多个 UseCase,各自独立注册/注销:
209
+
210
+ ```kotlin
211
+ @HiltViewModel
212
+ class DashboardViewModel @Inject constructor(
213
+ private val bluetoothUseCase: BluetoothUseCase,
214
+ private val settingsUseCase: SettingsUseCase
215
+ ) : BaseViewModel<...>(useCase = bluetoothUseCase) {
216
+ init {
217
+ startUseCaseListening()
218
+ settingsUseCase.register(settingsCallback)
219
+ }
220
+ override fun onCleared() {
221
+ super.onCleared()
222
+ settingsUseCase.unRegister()
223
+ }
224
+ }
225
+ ```
226
+
227
+ ---
228
+
229
+ ## 非 Activity 场景
230
+
231
+ 只有无法使用 `@AndroidEntryPoint` + `hiltViewModel()` 的宿主(Service / 自定义 Window),才使用手动 ViewModelStore:
232
+
233
+ ```kotlin
234
+ class CustomWindow @Inject constructor(
235
+ private val viewModelFactory: BluetoothViewModel.Factory
236
+ ) {
237
+ private val viewModelStore = ViewModelStore()
238
+ private val viewModel: BluetoothViewModel by lazy {
239
+ ViewModelProvider(viewModelStore, viewModelFactory)[BluetoothViewModel::class.java]
240
+ }
241
+ fun onDestroy() { viewModelStore.clear() }
242
+ }
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 实现检查清单
248
+
249
+ 完整清单见 [references/implementation-checklist.md](references/implementation-checklist.md)。
250
+
251
+ ---
252
+
253
+ ## 必须遵守的约束规则
254
+
255
+ > rules 引用路径:`../rules/<rule-name>.mdc`
256
+
257
+ - harness-mvi-layering
258
+ - harness-hilt-injection
259
+ - harness-coroutines-scope
260
+ - harness-compose-mandatory
@@ -0,0 +1,66 @@
1
+ # 仓库与模块模式
2
+
3
+ ## 仓库拓扑(两种合法形态)
4
+
5
+ ### 形态 A:三仓分离
6
+
7
+ ```
8
+ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
9
+ │ foundation-repo │ │ domain-repo │ │ feature-repo │
10
+ │ :foundation │◄──│ :domain │◄──│ :feature │
11
+ └─────────────┘ └──────────────┘ └──────────────┘
12
+ 独立仓库 独立仓库 独立仓库
13
+ 发布为 AAR/Maven 依赖 foundation AAR 依赖 domain AAR
14
+ ```
15
+
16
+ ### 形态 B:domain + feature 同仓(foundation 仍独立)
17
+
18
+ ```
19
+ ┌─────────────┐ ┌──────────────────────────┐
20
+ │ foundation-repo │ │ app-repo │
21
+ │ :foundation │◄──│ :domain :feature │
22
+ └─────────────┘ │ (业务逻辑)──►(UI/ViewModel)│
23
+ 独立仓库 └──────────────────────────┘
24
+ 发布为 AAR/Maven 同仓不同 module,project(":domain") 依赖
25
+ ```
26
+
27
+ ## Gradle 依赖声明
28
+
29
+ ```kotlin
30
+ // :feature 模块的 build.gradle.kts
31
+ dependencies {
32
+ // 同仓场景
33
+ implementation(project(":domain"))
34
+ // 跨仓场景
35
+ // implementation("com.xxx:domain:<version>")
36
+ // 不直接依赖 foundation(通过 domain 传递)
37
+ }
38
+
39
+ // :domain 模块的 build.gradle.kts
40
+ dependencies {
41
+ // foundation 始终通过 AAR 引入
42
+ implementation("com.xxx:foundation:<version>")
43
+ }
44
+ ```
45
+
46
+ ## 目录结构约定
47
+
48
+ ```
49
+ foundation-repo/
50
+ └── :foundation
51
+ ├── data/ ← RepositoryImpl、DAO、ApiService
52
+ └── di/ ← Hilt Module(DatabaseModule、RepositoryModule)
53
+
54
+ domain-repo/(或 app-repo/:domain)
55
+ └── :domain
56
+ ├── contract/ ← State/Event/Effect/Intent/Callback 定义
57
+ ├── usecase/ ← UseCase 接口与实现
58
+ └── di/ ← Hilt Module(DomainModule)
59
+
60
+ app-repo/:feature
61
+ └── :feature
62
+ ├── ui/ ← Composable 函数(Screen + Content)
63
+ ├── viewmodel/ ← ViewModel
64
+ ├── navigation/ ← Navigation Graph
65
+ └── di/ ← 如需 Feature 级 Hilt Module
66
+ ```
@@ -0,0 +1,45 @@
1
+ # MVI 实现检查清单
2
+
3
+ ## 特性层(Feature)
4
+
5
+ - [ ] ViewModel 使用 `@HiltViewModel` + `@Inject constructor(...)` 注入 UseCase
6
+ - [ ] 继承 `BaseViewModel`,不自行声明 `MutableStateFlow` / `MutableSharedFlow`
7
+ - [ ] `initialState` 赋予所有字段默认值,写法统一(`= State()`)
8
+ - [ ] `reducer` 是纯函数,只用 `state.copy()`,不含 IO / UseCase 调用
9
+ - [ ] `effectHandler` 不修改 State,只 `emitEffect()`;无副作用事件空实现
10
+ - [ ] `handleIntent` 中调用 UseCase 方法,需要立即反馈 Loading 时先 `dispatch(LoadingEvent)`
11
+ - [ ] `init { startUseCaseListening() }` 必须调用
12
+ - [ ] Compose 容器层使用 `hiltViewModel()` 获取 ViewModel
13
+ - [ ] Activity 使用 `@AndroidEntryPoint`,Application 使用 `@HiltAndroidApp` 且 Manifest 配置 `android:name`
14
+ - [ ] Composable 分为容器层(ViewModel)和纯 UI 层(参数)
15
+ - [ ] Effect 用 `LaunchedEffect(Unit)` 收集
16
+ - [ ] 用户操作只通过 `viewModel.process(Intent)` 传递
17
+ - [ ] 非 Activity 场景手动调用 `viewModelStore.clear()`
18
+
19
+ ## 领域层(Domain)
20
+
21
+ - [ ] domain 是独立 Gradle module,不与 feature 合并
22
+ - [ ] foundation 通过 AAR 引入:`implementation("com.xxx:foundation:<version>")`
23
+ - [ ] 不依赖 feature 模块
24
+ - [ ] UseCase 接口继承 `Registrable<Callback>`
25
+ - [ ] `register()` 保存 callback 引用并启动监听;`unRegister()` 置 null
26
+ - [ ] 只通过 Callback 接口上报结果,不直接调用 ViewModel
27
+ - [ ] UseCase 不持有 View / Context 引用
28
+ - [ ] 多 UseCase 场景:每个独立注册/注销,`onCleared()` 全部注销
29
+ - [ ] Repository 接口定义在 Domain 层,实现在 Foundation 层
30
+
31
+ ## 基础层(Foundation)
32
+
33
+ - [ ] Foundation 在独立仓库,以 AAR/Maven 坐标发布
34
+ - [ ] DAO 方法返回 `Flow<T>` 实现响应式数据
35
+ - [ ] 网络调用使用 `suspend` 函数,错误用 `Result` 包裹
36
+ - [ ] Repository 不持有业务状态,不实现 MVI 协议(State/Intent/Event/Effect)
37
+ - [ ] Entity / DTO 与 Domain Model 严格分离,转换在 Repository 层完成
38
+ - [ ] Foundation 层不被 Feature 层直接依赖
39
+
40
+ ## Hilt 模块
41
+
42
+ - [ ] DatabaseModule:`@Provides @Singleton` 提供 Room Database 和 DAO
43
+ - [ ] RepositoryModule:`@Binds @Singleton` 绑定 Repository 接口到实现类
44
+ - [ ] DomainModule:`@Binds @Singleton` 绑定 UseCase 接口到实现类
45
+ - [ ] CoroutineModule:`@Provides` 提供注入的 Dispatcher(IO / Default / Main)
@@ -0,0 +1,80 @@
1
+ # UDF 数据流详解
2
+
3
+ ## 完整数据流图
4
+
5
+ ```
6
+ [用户操作]
7
+
8
+
9
+ Composable.onClick / onValueChange
10
+
11
+
12
+ viewModel.process(Intent) ← 统一入口
13
+
14
+
15
+ ViewModel.handleIntent(intent)
16
+
17
+ ├── 需要 UseCase?
18
+ │ │
19
+ │ ▼
20
+ │ UseCase.语义方法() ← 如 cases.connect(addr)
21
+ │ │
22
+ │ ▼
23
+ │ Repository / 数据源
24
+ │ │
25
+ │ ▼
26
+ │ UseCase Callback 回调 ← callback?.onDeviceConnected(device)
27
+ │ │
28
+ │ ▼
29
+ │ ViewModel useCaseCallback ← dispatch(Event.Connected(device))
30
+ │ │
31
+ │ ▼
32
+ │ ┌─── MviStore.dispatch(Event)
33
+ │ │
34
+ │ ├── Reducer ← 纯函数:state.copy(...)
35
+ │ │ │
36
+ │ │ ▼
37
+ │ │ 新 State → StateFlow ← 持久状态,UI collectAsState
38
+ │ │
39
+ │ └── EffectHandler ← 只发射 Effect,不修改 State
40
+ │ │
41
+ │ ▼
42
+ │ Effect → SharedFlow ← 一次性消费,LaunchedEffect 收集
43
+
44
+ └── 无需 UseCase(纯 UI 逻辑)
45
+
46
+
47
+ 直接 dispatch(Event) ← 如 dispatch(Event.Loading)
48
+ ```
49
+
50
+ ## 关键规则
51
+
52
+ | 环节 | 规则 |
53
+ |------|------|
54
+ | Intent | 所有用户操作统一通过 `viewModel.process(Intent)`,禁止直接调用 ViewModel 方法 |
55
+ | handleIntent | 只做 Intent → UseCase 调用映射,可立即 dispatch 加载状态 |
56
+ | UseCase Callback | 只调用 `dispatch(Event)`,不做 State 修改 |
57
+ | Reducer | 纯函数,只 `state.copy()`,禁止 IO / UseCase 调用 |
58
+ | EffectHandler | 只 `emitEffect()`,不修改 State,无副作用事件空实现 |
59
+ | State | 不可变 `data class`,所有字段 `val`,更新只用 `.copy()` |
60
+ | Effect | 一次性消费,`SharedFlow(replay = 0)`,用 `LaunchedEffect(Unit)` 收集 |
61
+
62
+ ## 反向数据流(发送信号到 MCU / 外设)
63
+
64
+ ```
65
+ UI 用户操作
66
+
67
+
68
+ viewModel.process(Intent.SetEnabled(true))
69
+
70
+
71
+ handleIntent → UseCase.setEnabled(true)
72
+
73
+
74
+ Repository.sendSignal(...)
75
+
76
+
77
+ MCU / 外设
78
+ ```
79
+
80
+ 反向流不经过 Reducer / EffectHandler,除非需要更新本地状态(如 Loading)。
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: harness-android-cli
3
+ description: 使用 `android` 命令行工具编排 Android 开发任务,包括项目创建、部署、SDK 管理和环境诊断。
4
+ ---
5
+
6
+ # Android CLI 专家
7
+
8
+ 本技能提供使用 `android` CLI 工具的说明。该工具包含用于创建项目、运行应用、与设备交互以及管理 CLI 环境的各种命令。
9
+
10
+ ## SDK 管理
11
+
12
+ - `android sdk install <package>[@<version>]...`:安装指定的包
13
+ - `android sdk update [<pkg-name>]`:将指定的包或所有包更新到最新版本
14
+ - `android sdk remove <pkg-name>`:从本地 SDK 中移除一个包
15
+ - `android sdk list --all`:列出已安装和可用的 SDK 包
16
+
17
+ ## 项目创建
18
+
19
+ `android create empty-activity --name="My App" --output=./my-app`
20
+
21
+ ## 与设备交互
22
+
23
+ 详见 [references/interact.md](references/interact.md)
24
+
25
+ ## 运行旅程测试
26
+
27
+ 详见 [references/journeys.md](references/journeys.md)
28
+
29
+ ## 文档搜索
30
+
31
+ `android docs` 命令在 Android 知识库中搜索权威、高质量的 Android 开发者文档。始终使用此工具获取有关 Android 概念的最新信息。
32
+
33
+ ## 运行 APK
34
+
35
+ 使用 `android run` 命令运行 Android 应用。
36
+
37
+ ## 管理模拟器
38
+
39
+ 使用 `android emulator` 命令管理 Android 虚拟设备(AVD)
40
+
41
+ ## 截图
42
+
43
+ 使用 `android screenshot` 命令捕获已连接 Android 设备的当前屏幕图像并输出到文件。
44
+
45
+ ## 管理技能
46
+
47
+ 使用 `android skills` 命令管理 Android 的 antigravity agent 技能。
48
+
49
+ ## 检查 UI 布局
50
+
51
+ 使用 `android layout` 命令检查 Android 应用的 UI 布局。它以 JSON 格式返回 Android 应用的布局树。
52
+
53
+ ## 更新 CLI
54
+
55
+ 使用 `android update` 命令更新 Android CLI。
56
+
57
+ ## 命令速查
58
+
59
+ | 命令 | 用途 |
60
+ |------|------|
61
+ | `android create` | 创建新的 Android 项目 |
62
+ | `android describe` | 分析 Android 项目生成描述性元数据 |
63
+ | `android docs` | 搜索/获取 Android 文档 |
64
+ | `android emulator` | 管理虚拟设备 |
65
+ | `android info` | 打印环境信息 |
66
+ | `android init` | 初始化 CLI 环境 |
67
+ | `android layout` | 返回应用的布局树 |
68
+ | `android run` | 部署 Android 应用 |
69
+ | `android sdk` | 下载和列出 SDK 包 |
70
+ | `android skills` | 管理技能 |
71
+ | `android update` | 更新 CLI |
72
+
73
+ ---
74
+
75
+ ## 必须遵守的约束规则
76
+
77
+ > rules 引用路径:`../rules/<rule-name>.mdc`
78
+
79
+ 无(本技能为 CLI 工具操作,不涉及代码生成)