@mmerterden/multi-agent-pipeline 20.0.0 → 20.2.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 (90) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +5 -5
  3. package/README.tr.md +5 -5
  4. package/SECURITY.md +3 -3
  5. package/docs/adr/0011-dormant-ci.md +10 -1
  6. package/docs/architecture.md +2 -2
  7. package/docs/ecosystem.md +5 -5
  8. package/docs/facts.json +8 -7
  9. package/install/_codex-agents.mjs +1 -1
  10. package/manifest.json +92 -64
  11. package/package.json +1 -1
  12. package/pipeline/agents/code-reviewer.md +2 -2
  13. package/pipeline/agents/dev-critic.md +5 -5
  14. package/pipeline/agents/security-auditor.md +80 -72
  15. package/pipeline/commands/figma-to-swiftui.md +1 -1
  16. package/pipeline/commands/multi-agent/SKILL.md +1 -1
  17. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  18. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -1
  19. package/pipeline/commands/multi-agent/help/SKILL.md +2 -0
  20. package/pipeline/commands/multi-agent/scan/SKILL.md +2 -2
  21. package/pipeline/commands/multi-agent/security-review/SKILL.md +52 -0
  22. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  23. package/pipeline/multi-agent-refs/component-dispatch.md +5 -5
  24. package/pipeline/multi-agent-refs/cross-cli-contract.md +6 -6
  25. package/pipeline/multi-agent-refs/features/security-audit.md +55 -0
  26. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  27. package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -15
  28. package/pipeline/multi-agent-refs/phases/phase-5-report.md +1 -1
  29. package/pipeline/multi-agent-refs/threat-model.md +39 -0
  30. package/pipeline/schemas/agent-state.schema.json +23 -0
  31. package/pipeline/schemas/phases.json +1 -2
  32. package/pipeline/schemas/prefs.schema.json +0 -4
  33. package/pipeline/schemas/reviewer-output.schema.json +99 -2
  34. package/pipeline/schemas/security-finding.schema.json +144 -0
  35. package/pipeline/scripts/_stack-routing.mjs +1 -0
  36. package/pipeline/scripts/gc-abandoned.sh +16 -9
  37. package/pipeline/scripts/render-work-summary.sh +7 -4
  38. package/pipeline/skills/.skill-manifest.json +47 -23
  39. package/pipeline/skills/.skills-index.json +75 -9
  40. package/pipeline/skills/shared/README.md +13 -7
  41. package/pipeline/skills/shared/core/multi-agent/SKILL.md +3 -4
  42. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -2
  43. package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +29 -0
  44. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
  45. package/pipeline/skills/shared/external/android-architecture/SKILL.md +71 -0
  46. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +142 -0
  47. package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +314 -0
  48. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +432 -0
  49. package/pipeline/skills/shared/external/android-datastore/SKILL.md +236 -0
  50. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +297 -0
  51. package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +249 -0
  52. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +270 -0
  53. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +62 -0
  54. package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +255 -0
  55. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +257 -0
  56. package/pipeline/skills/shared/external/android-performance/SKILL.md +86 -602
  57. package/pipeline/skills/shared/external/android-performance/references/patterns.md +659 -0
  58. package/pipeline/skills/shared/external/android-security/SKILL.md +117 -430
  59. package/pipeline/skills/shared/external/android-security/references/patterns.md +690 -0
  60. package/pipeline/skills/shared/external/{android_ui_verification → android-ui-verification}/SKILL.md +1 -1
  61. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +35 -733
  62. package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +299 -0
  63. package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +255 -0
  64. package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +167 -0
  65. package/pipeline/skills/shared/external/app-intents/SKILL.md +39 -174
  66. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +178 -0
  67. package/pipeline/skills/shared/external/compose-components/SKILL.md +48 -0
  68. package/pipeline/skills/shared/external/compose-components/references/patterns.md +200 -0
  69. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +66 -3
  70. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +191 -0
  71. package/pipeline/skills/shared/external/compose-testing/SKILL.md +107 -397
  72. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +631 -0
  73. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +121 -449
  74. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +715 -0
  75. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +143 -0
  76. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +27 -102
  77. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +42 -0
  78. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +94 -383
  79. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +640 -0
  80. package/pipeline/skills/shared/external/room-database/SKILL.md +101 -440
  81. package/pipeline/skills/shared/external/room-database/references/patterns.md +614 -0
  82. package/pipeline/skills/shared/external/security-review/SKILL.md +64 -0
  83. package/pipeline/skills/shared/external/security-review/references/owasp-mobile-top10-2024.md +53 -0
  84. package/pipeline/skills/shared/external/security-review/references/owasp-web-api-top10-2021.md +56 -0
  85. package/pipeline/skills/shared/external/storekit/SKILL.md +69 -343
  86. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +371 -0
  87. package/pipeline/skills/shared/external/widgetkit/SKILL.md +25 -101
  88. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +107 -0
  89. package/pipeline/skills/skills-index.md +8 -2
  90. package/pipeline/commands/security-review.md +0 -6
@@ -0,0 +1,314 @@
1
+ ---
2
+ name: android-build-quality-gates
3
+ description: "Encode Android module and quality rules as build-failing Gradle tasks: layer-dependency checks, coverage floors, a Compose-stability gate, lint-on-diff and screenshot validation. Use when a multi-module app needs enforceable boundaries and quality gates in the build, not in review."
4
+ risk: safe
5
+ source: multi-agent-pipeline
6
+ date_added: "2026-09-21"
7
+ ---
8
+
9
+ # Android Build Quality Gates
10
+
11
+ A convention holds only while everyone remembers it. A gate holds because the
12
+ build turns red without it. This skill covers the quality rules worth promoting
13
+ from review comments into Gradle tasks that fail: layer boundaries, coverage
14
+ floors, Compose stability, lint on changed files, and screenshot validation.
15
+
16
+ Targets AGP 8.x, Gradle 8.x with the configuration cache, Kover 0.8+, and the
17
+ Compose compiler Gradle plugin (Kotlin 2.0+). Copy-ready, full-length code for
18
+ every gate lives in [references/patterns.md](references/patterns.md); this file
19
+ carries the decisions and short snippets.
20
+
21
+ ## Contents
22
+
23
+ - [Where gates live](#where-gates-live)
24
+ - [Layer-dependency gate](#layer-dependency-gate)
25
+ - [Coverage floor with Kover](#coverage-floor-with-kover)
26
+ - [Compose-stability gate](#compose-stability-gate)
27
+ - [Lint on diff](#lint-on-diff)
28
+ - [Screenshot validation task](#screenshot-validation-task)
29
+ - [Ratchet, do not wall](#ratchet-do-not-wall)
30
+ - [Wiring gates into check](#wiring-gates-into-check)
31
+ - [Do's and Don'ts](#dos-and-donts)
32
+ - [Review Checklist](#review-checklist)
33
+
34
+ ## Where gates live
35
+
36
+ Author gates once in a convention plugin under `build-logic`, then apply that
37
+ plugin from each module. A gate copied into twenty `build.gradle.kts` files
38
+ drifts; a gate in one convention plugin is a single source of truth. See
39
+ `gradle-kotlin-dsl` for how the `build-logic` module and version catalog are
40
+ set up; this skill only adds tasks inside it.
41
+
42
+ Every gate follows the same three-part shape:
43
+
44
+ 1. A task (or a `verify`/`check`-typed configuration) that computes a fact about
45
+ the module: its dependency set, its coverage ratio, its unskippable
46
+ composables.
47
+ 2. An assertion that throws `GradleException` when the fact violates the rule.
48
+ 3. A wire into the `check` lifecycle (or a `finalizedBy` on an existing task) so
49
+ it runs without anyone remembering to call it.
50
+
51
+ Keep the assertion message actionable: name the offending dependency, the
52
+ current versus required number, the exact composable. A gate that fails with
53
+ "verification failed" gets suppressed; one that says "module `feature-x` depends
54
+ on `data-y`, which is not in its allowed set" gets fixed.
55
+
56
+ ## Layer-dependency gate
57
+
58
+ Clean layering (feature -> domain -> data, never feature -> data directly, never
59
+ domain -> feature) is the rule most often stated and least often enforced. Encode
60
+ it by walking a module's resolved project dependencies and asserting each is in
61
+ an allowed set for that module's layer.
62
+
63
+ Register a task that reads the `implementation`/`api` configurations, keeps only
64
+ `ProjectDependency` entries, and compares their paths against an allowlist:
65
+
66
+ ```kotlin
67
+ val checkLayerDependencies by tasks.registering {
68
+ group = "verification"
69
+ val layer = project.moduleLayer()
70
+ val projectDeps = configurations
71
+ .matching { it.name in setOf("implementation", "api") }
72
+ .flatMap { it.dependencies.withType<ProjectDependency>() }
73
+ .map { it.path }
74
+ doLast {
75
+ val forbidden = projectDeps.filterNot { layer.allows(it) }
76
+ if (forbidden.isNotEmpty()) {
77
+ throw GradleException(
78
+ "$path ($layer) declares forbidden dependencies: $forbidden"
79
+ )
80
+ }
81
+ }
82
+ }
83
+ tasks.named("check").configure { dependsOn(checkLayerDependencies) }
84
+ ```
85
+
86
+ Derive the layer from the module path or a convention (`feature-*`, `domain-*`,
87
+ `data-*`) rather than a hand-maintained map, so a new module is governed the day
88
+ it is created. The full version, including the layer enum, its `allows` matrix,
89
+ and configuration-cache-safe capture of dependency paths, is in
90
+ [references/patterns.md#layer-dependency-gate](references/patterns.md#layer-dependency-gate).
91
+
92
+ Read dependency paths at configuration time into a local (as above), not inside
93
+ `doLast` off the live `configurations` object, so the task stays compatible with
94
+ the configuration cache.
95
+
96
+ ## Coverage floor with Kover
97
+
98
+ A coverage number nobody enforces only decorates a report. Kover's verification
99
+ rule turns it into a gate: below the floor, the build fails.
100
+
101
+ Apply `org.jetbrains.kotlinx.kover` and declare a bound:
102
+
103
+ ```kotlin
104
+ kover {
105
+ reports {
106
+ verify {
107
+ rule("Line coverage floor") {
108
+ minBound(60)
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ Split instrumentation from aggregation. Each module applies the Kover plugin so
116
+ its own classes are instrumented; a single aggregation module (or the root)
117
+ depends on every measured module and owns the `verify` rule over the merged
118
+ report. Verifying per module produces noisy, uneven thresholds; verifying the
119
+ aggregate gives one honest project number. The aggregation wiring and per-module
120
+ exclusion of generated code (`*_Factory`, `*Args`, `BuildConfig`, Hilt
121
+ components) are in
122
+ [references/patterns.md#coverage-floor-with-kover](references/patterns.md#coverage-floor-with-kover).
123
+
124
+ Gate the floor behind a property so local `assembleDebug` stays fast and only CI
125
+ (or an explicit run) pays for instrumented coverage:
126
+
127
+ ```kotlin
128
+ tasks.named("check").configure {
129
+ if (providers.gradleProperty("enableCoverageVerification").isPresent) {
130
+ dependsOn("koverVerify")
131
+ }
132
+ }
133
+ ```
134
+
135
+ Run it in CI with `-PenableCoverageVerification`. Set the floor to the current
136
+ measured value, not an aspirational one; see [Ratchet, do not wall](#ratchet-do-not-wall).
137
+
138
+ ## Compose-stability gate
139
+
140
+ Unstable parameters silently defeat recomposition skipping and cause jank that
141
+ no unit test catches. The Compose compiler can emit a stability report; parse it
142
+ and fail when a composable that should skip cannot.
143
+
144
+ First point the compiler's metrics and reports at a build directory via the
145
+ Compose compiler Gradle plugin:
146
+
147
+ ```kotlin
148
+ composeCompiler {
149
+ val dir = layout.buildDirectory.dir("compose-metrics")
150
+ metricsDestination.set(dir)
151
+ reportsDestination.set(dir)
152
+ }
153
+ ```
154
+
155
+ This produces `*-composables.txt` per module. Register a task that parses those
156
+ files, collects every `restartable` composable marked `skippable` as its
157
+ negation (i.e. the `restartable` but NOT `skippable` ones), and fails on any that
158
+ is not in a per-module ignore-list. Finalize each Kotlin compile with it so the
159
+ report is always fresh:
160
+
161
+ ```kotlin
162
+ val checkComposeStability by tasks.registering(ComposeStabilityCheck::class) {
163
+ reportDir.set(layout.buildDirectory.dir("compose-metrics"))
164
+ ignoreFile.set(layout.projectDirectory.file("compose-stability-ignore.txt"))
165
+ }
166
+ tasks.withType<KotlinCompile>().configureEach {
167
+ finalizedBy(checkComposeStability)
168
+ }
169
+ ```
170
+
171
+ The ignore-list file holds fully-qualified composable names that are accepted
172
+ exceptions (a composable that takes a third-party unstable type you cannot
173
+ annotate). Each line is one accepted composable; anything unskippable and not
174
+ listed fails the build. The full `ComposeStabilityCheck` task with the report
175
+ parser and ignore-list matching is in
176
+ [references/patterns.md#compose-stability-gate](references/patterns.md#compose-stability-gate).
177
+
178
+ The report is only emitted when the compiler runs, which is why the check is
179
+ `finalizedBy` the compile rather than a standalone task: a cached compile with no
180
+ report means nothing changed, and the check reads the last report.
181
+
182
+ ## Lint on diff
183
+
184
+ Full lint across a large multi-module project is minutes of wall time; running it
185
+ on every commit trains people to skip the hook. Split it: a fast pre-commit gate
186
+ over only the changed files, and full analysis in CI.
187
+
188
+ At commit time, feed the static analyzer (detekt or Android Lint) the diff:
189
+
190
+ ```bash
191
+ CHANGED=$(git diff --cached --name-only --diff-filter=d -- '*.kt' '*.kts')
192
+ [ -z "$CHANGED" ] && exit 0
193
+ ./gradlew detektDiff -PdetektInput="$CHANGED"
194
+ ```
195
+
196
+ Wire `detektDiff` to accept a file list and set the detekt `source` to just those
197
+ paths, so the analyzer never walks the whole tree at commit time. In CI, run the
198
+ full `detekt` / `lint` task with the baseline so nothing escapes. The pre-commit
199
+ hook and the `detektDiff` task definition are in
200
+ [references/patterns.md#lint-on-diff](references/patterns.md#lint-on-diff).
201
+
202
+ Keep the two consistent: the same ruleset and the same baseline file drive both
203
+ the diff run and the full run, so a commit that passes the hook does not surprise
204
+ the author in CI.
205
+
206
+ ## Screenshot validation task
207
+
208
+ Screenshot tests are only a gate when a mismatch fails the build. Author the
209
+ tests with the mechanics in `compose-testing`; here, register the verification
210
+ task and put it on the `check` path.
211
+
212
+ Expose a single umbrella task so CI and local calls agree:
213
+
214
+ ```kotlin
215
+ tasks.register("verifyScreenshots") {
216
+ group = "verification"
217
+ dependsOn(tasks.matching { it.name == "verifyRoborazziDebug" })
218
+ }
219
+ tasks.named("check").configure { dependsOn("verifyScreenshots") }
220
+ ```
221
+
222
+ Record and verify are separate entry points: `recordScreenshots` regenerates
223
+ golden images (run deliberately, reviewed in the diff), `verifyScreenshots`
224
+ compares and fails. Never let `verify` fall back to recording on a missing
225
+ golden, or the gate passes on the very case it exists to catch. The record/verify
226
+ task pair and the CI upload of diff images on failure are in
227
+ [references/patterns.md#screenshot-validation-task](references/patterns.md#screenshot-validation-task).
228
+
229
+ ## Ratchet, do not wall
230
+
231
+ The failure mode of every gate above is the same: set the bar higher than the
232
+ codebase currently clears, and the team disables the gate instead of meeting it.
233
+ A gate that is always red is worse than no gate, because it also hides the
234
+ regressions it was meant to catch.
235
+
236
+ Seed each gate at the current state and let it only tighten:
237
+
238
+ - Coverage: set `minBound` to the value measured today, then raise it in small
239
+ steps as tests land. Never open at a round aspirational number.
240
+ - Compose stability: seed the ignore-list with every composable currently
241
+ unskippable, so the gate passes on day one and only new offenders fail. Shrink
242
+ the list over time; adding to it needs review.
243
+ - Lint: generate a baseline of existing findings; the gate fails only on new
244
+ ones. Regenerating the baseline to bury a fresh finding is a review smell.
245
+ - Layers: if a forbidden edge exists today, either fix it before turning the gate
246
+ on, or record it as a temporary allowed exception with a tracking reference,
247
+ never widen the whole allow-matrix.
248
+
249
+ A ceiling that only moves down (ignore-lists, baselines) and a floor that only
250
+ moves up (coverage) is the shape that survives contact with a real team.
251
+
252
+ ## Wiring gates into check
253
+
254
+ Gates that are not on a lifecycle path do not run. Attach each to `check` (or a
255
+ custom `qualityGates` aggregate task that `check` depends on) so a single
256
+ `./gradlew check` runs every gate, and CI needs no bespoke task list.
257
+
258
+ ```kotlin
259
+ val qualityGates by tasks.registering {
260
+ group = "verification"
261
+ dependsOn(
262
+ "checkLayerDependencies",
263
+ "checkComposeStability",
264
+ "verifyScreenshots",
265
+ )
266
+ }
267
+ tasks.named("check").configure { dependsOn(qualityGates) }
268
+ ```
269
+
270
+ Keep the expensive, environment-dependent gates (instrumented coverage, full
271
+ lint) behind properties so `check` stays usable locally, and turn them on in CI
272
+ via `-P` flags. The full convention-plugin assembly that registers all gates is
273
+ in [references/patterns.md#wiring-gates-into-check](references/patterns.md#wiring-gates-into-check).
274
+
275
+ ## Do's and Don'ts
276
+
277
+ - Do author every gate in a `build-logic` convention plugin, applied per module.
278
+ - Do make failure messages name the offender, the actual value, and the required
279
+ value.
280
+ - Do capture configuration values (dependency paths, file lists) into locals at
281
+ configuration time for configuration-cache compatibility.
282
+ - Do seed ignore-lists and baselines from the current state so the gate opens
283
+ green.
284
+ - Don't verify coverage per module; aggregate, then verify once.
285
+ - Don't let a screenshot `verify` task record missing goldens.
286
+ - Don't set a floor above what the code clears today; a permanently red gate gets
287
+ switched off.
288
+ - Don't duplicate the gate logic across module build files.
289
+ - Don't run full lint at commit time; run it on the diff, full in CI.
290
+
291
+ ## Ownership Map
292
+
293
+ This skill owns build-failing quality-gate tasks. It does not re-explain:
294
+
295
+ - Typed `BuildConfig`, convention-plugin composition, version catalog, and
296
+ general build setup -> `gradle-kotlin-dsl`.
297
+ - Authoring Compose screenshot tests, semantic matchers, and ViewModel tests ->
298
+ `compose-testing` (this skill only registers the verification task).
299
+ - Codegen `--check` CI verification of generated design-token sources ->
300
+ `android-design-tokens-codegen`.
301
+
302
+ ## Review Checklist
303
+
304
+ - [ ] Each gate lives in a convention plugin, not copied per module.
305
+ - [ ] Layer gate derives layer from convention, governs new modules automatically.
306
+ - [ ] Coverage uses a single aggregation report with one `verify` bound, gated by
307
+ a property.
308
+ - [ ] Compose stability report points at a build dir, check is `finalizedBy` the
309
+ compile, ignore-list holds accepted exceptions only.
310
+ - [ ] Lint runs on the diff at commit time, full with baseline in CI.
311
+ - [ ] Screenshot `verify` fails on missing/changed goldens, never records.
312
+ - [ ] Every gate is reachable from `check`; expensive ones sit behind `-P` flags.
313
+ - [ ] Floors seeded at current state; ignore-lists/baselines only ratchet down.
314
+ - [ ] Failure messages are actionable (offender + actual + required).