@1aboveio/skills 1.0.14 → 1.0.16

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 (105) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.codex-plugin/resources.json +164 -160
  3. package/package.json +4 -4
  4. package/skills/app-dev/airflow/SKILL.md +1 -1
  5. package/skills/app-dev/android-development/SKILL.md +30 -2
  6. package/skills/app-dev/android-development/references/diagnosis.md +55 -0
  7. package/skills/app-dev/android-development/references/feature-work.md +15 -0
  8. package/skills/app-dev/android-development/references/init.md +57 -6
  9. package/skills/app-dev/android-development/references/setup.md +8 -2
  10. package/skills/app-dev/android-development/references/ui-journeys.md +58 -0
  11. package/skills/app-dev/android-development/scripts/device-run.sh +93 -2
  12. package/skills/app-dev/android-development/scripts/failure_capture.py +331 -0
  13. package/skills/app-dev/android-development/scripts/init.mjs +234 -17
  14. package/skills/app-dev/android-development/scripts/maestro_failure.py +401 -0
  15. package/skills/app-dev/android-development/scripts/test_failure_capture.py +378 -0
  16. package/skills/app-dev/android-development/scripts/test_maestro_failure.py +462 -0
  17. package/skills/app-dev/app-debug/SKILL.md +1 -1
  18. package/skills/app-dev/better-auth/SKILL.md +1 -1
  19. package/skills/app-dev/data-catalog/SKILL.md +53 -16
  20. package/skills/app-dev/data-catalog/scripts/persist-conventions-core.mjs +231 -21
  21. package/skills/app-dev/data-catalog/scripts/persist-conventions.mjs +12 -1
  22. package/skills/app-dev/data-quality/SKILL.md +1 -1
  23. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  24. package/skills/app-dev/monorepo/SKILL.md +1 -1
  25. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  26. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  27. package/skills/app-dev/pyspark/SKILL.md +1 -1
  28. package/skills/app-dev/python-backend/SKILL.md +1 -1
  29. package/skills/app-dev/shadcn/SKILL.md +1 -1
  30. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  31. package/skills/cicd/cloud-build/SKILL.md +1 -1
  32. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  33. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  34. package/skills/cicd/docker/SKILL.md +1 -1
  35. package/skills/cicd/github-actions/SKILL.md +1 -1
  36. package/skills/cicd/github-flow/SKILL.md +10 -1
  37. package/skills/cicd/github-flow/scripts/persist-conventions-core.mjs +231 -21
  38. package/skills/cicd/github-flow/scripts/persist-conventions.mjs +18 -1
  39. package/skills/cicd/google-cloud/SKILL.md +1 -1
  40. package/skills/cicd/mergify/SKILL.md +1 -1
  41. package/skills/cicd/podman/SKILL.md +1 -1
  42. package/skills/content/article-review/SKILL.md +1 -1
  43. package/skills/content/article-to-image/SKILL.md +1 -1
  44. package/skills/content/flowchart-svg/SKILL.md +1 -1
  45. package/skills/content/image-gen/SKILL.md +1 -1
  46. package/skills/content/mermaid/SKILL.md +1 -1
  47. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  48. package/skills/content/wechat-publisher/SKILL.md +1 -1
  49. package/skills/content/wechat-search/SKILL.md +1 -1
  50. package/skills/content/xai-search/SKILL.md +1 -1
  51. package/skills/core/distill-writing/SKILL.md +7 -1
  52. package/skills/core/doc-to-md/SKILL.md +1 -1
  53. package/skills/core/editorial-review/SKILL.md +1 -1
  54. package/skills/core/house-style/SKILL.md +1 -1
  55. package/skills/core/house-style/references/persistence.md +8 -3
  56. package/skills/core/house-style/scripts/init-guidance.mjs +1 -1
  57. package/skills/core/plain-language/SKILL.md +1 -1
  58. package/skills/core/polish-cn/SKILL.md +1 -1
  59. package/skills/core/reg-parser/SKILL.md +1 -1
  60. package/skills/core/terminology/SKILL.md +1 -1
  61. package/skills/core/terminology/references/init.md +11 -0
  62. package/skills/core/write-doc/SKILL.md +1 -1
  63. package/skills/core/write-report/SKILL.md +1 -1
  64. package/skills/engineering/better-review/SKILL.md +1 -1
  65. package/skills/engineering/e2e-test/SKILL.md +1 -1
  66. package/skills/engineering/engineering/SKILL.md +19 -4
  67. package/skills/engineering/engineering/references/init-scope.md +5 -2
  68. package/skills/engineering/engineering/references/scope-management.md +4 -2
  69. package/skills/engineering/engineering/scripts/init.mjs +34 -9
  70. package/skills/engineering/engineering/scripts/persist-conventions-core.mjs +231 -21
  71. package/skills/engineering/engineering/scripts/persist-conventions.mjs +48 -21
  72. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  73. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  74. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  75. package/skills/engineering/golden-path/SKILL.md +1 -1
  76. package/skills/engineering/harness-runtime/SKILL.md +4 -3
  77. package/skills/engineering/harness-runtime/agent-catalog.mjs +928 -0
  78. package/skills/engineering/harness-runtime/contracts.md +2 -0
  79. package/skills/engineering/harness-runtime/references/model-catalog.md +7 -1
  80. package/skills/engineering/resolve-issues/SKILL.md +9 -6
  81. package/skills/engineering/resolve-issues/references/delivery-progress.md +2 -0
  82. package/skills/engineering/resolve-issues/references/expire.md +6 -3
  83. package/skills/engineering/resolve-issues/references/implementation.md +6 -1
  84. package/skills/engineering/resolve-issues/references/preflight.md +26 -17
  85. package/skills/engineering/resolve-issues/references/review.md +39 -4
  86. package/skills/engineering/resolve-issues/references/shared-modules.md +88 -20
  87. package/skills/engineering/resolve-issues/references/task-plan.md +35 -1
  88. package/skills/engineering/resolve-issues/scripts/preflight-models.mjs +382 -592
  89. package/skills/engineering/resolve-release/SKILL.md +1 -1
  90. package/skills/engineering/rush-issues/SKILL.md +4 -4
  91. package/skills/engineering/rush-release/SKILL.md +1 -1
  92. package/skills/engineering/test-quality/SKILL.md +3 -1
  93. package/skills/engineering/test-quality/scripts/persist-conventions-core.mjs +231 -21
  94. package/skills/engineering/test-quality/scripts/persist-conventions.mjs +13 -1
  95. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  96. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  97. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  98. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  99. package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
  100. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  101. package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
  102. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  103. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  104. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
  105. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1aboveio/skills",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Every 1aboveio skill package in one: install once, then choose which packages Pi loads with npx @1aboveio/skills select.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,8 +28,8 @@
28
28
  "nativeCodex": {
29
29
  "schemaVersion": 1,
30
30
  "metadataPath": ".codex-plugin/resources.json",
31
- "metadataDigest": "5a89642fb1549c82420cbba78a75eaff01509d14cbf556480130a688e7ca1efe",
32
- "releaseIdentity": "1d0affb17cf284c052ad00266f7d5ea31a7f7f9bb7f8ae6435b200835710e123"
31
+ "metadataDigest": "efea727f2b53be74c1dad921e4d976e9a4bf22054a5461217bea9a5983de48cc",
32
+ "releaseIdentity": "357a73a8819a5c43f9e6d935ece7e5efc38519d0b83e0984798677c4cb9dfc0d"
33
33
  },
34
34
  "pi": {
35
35
  "skills": [
@@ -260,7 +260,7 @@
260
260
  ]
261
261
  }
262
262
  ],
263
- "digest": "ffe17fe4c3bfd5076e842358a22f58b116f060bd779f788f4ba3fd7b814f8b0e"
263
+ "digest": "26eb35331224e4dd2e0e8ef13bafd6383af21ae416bcf9e5acc28e68c6b45221"
264
264
  },
265
265
  "publishConfig": {
266
266
  "registry": "https://registry.npmjs.org/",
@@ -2,7 +2,7 @@
2
2
  name: airflow
3
3
  description: Airflow DAG architecture and conventions. Use when designing, writing, reviewing, testing, or debugging Airflow DAGs and tasks, including TaskGroups, data intervals, timezone-aware date parameters, CLI validation, environment-based scheduling, and pytest integrity tests.
4
4
  user-invocable: true
5
- release-version: 0.46.0
5
+ release-version: 0.48.0
6
6
  ---
7
7
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
8
8
  # Airflow DAG Development Patterns
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: android-development
3
3
  description: "Prove and debug Expo / React Native apps on an owned Android emulator. Use for Android startup/rendering failures, Metro or ADB problems, UiAutomator/harness failures, APK builds, SDK/NDK/JDK workspace setup, native journey validation, and choosing Kotlin/Compose versus React Native for Android flows. Reproduce failed Android CI checkpoints off CI before fixing and redispatching. Standalone invocation offers Project or Session; init persists repo conventions. NOT for workflow YAML, CI speed or runners (github-actions), browser tests (e2e-test), iOS (ios-foundation), plain Jest tests, or signing/store release."
4
- release-version: 0.46.0
4
+ release-version: 0.48.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Android development
@@ -18,7 +18,8 @@ Project rules and required CI remain binding; this skill does not add acceptance
18
18
  leaves files unchanged. Do not offer global persistence.
19
19
  - **Project**, `/android-development init`, or an explicit request to save repo conventions:
20
20
  follow [initialization](references/init.md). Preview, then apply the authorized missing
21
- `CONTRIBUTING.md` section and `AGENTS.md` pointer. No second approval is needed unless
21
+ `CONTRIBUTING.md` section and `AGENTS.md` task-entry loader and guide pointer. Check
22
+ the skill loader separately from an existing guide link. No second approval is needed unless
22
23
  existing policy conflicts. Initialization is not a policy refresh or delivery permission.
23
24
  - **Session:** apply this workflow without writing guidance or agent settings. Retain the
24
25
  choice through reload/compaction in this session; a new session makes its own choice.
@@ -42,6 +43,31 @@ Choose the task, not a fresh environment for every attempt:
42
43
  repo-specific stages in order and record proof. Do not add Expo or a new harness merely
43
44
  to match the examples, or repeat cold setup for ordinary fixes.
44
45
 
46
+ ## Device failure record
47
+
48
+ A failed device case leaves one record and one line, whichever layer saw the failure:
49
+
50
+ - `failure.json` holding `case`, `step`, `error`, `screenshot`, `uiDump`, `logcatTail`,
51
+ `evidenceDir` and `cleanupErrors`, captured at the failing step before teardown.
52
+ - One printed line `NATIVE_FAILURE {"case":…,"step":…,"error":…,"evidenceDir":…}`.
53
+
54
+ Journey code wraps its `wait_for`/`tap` helpers in
55
+ [failure_capture.py](scripts/failure_capture.py), `with recorder.step("tap Pay"):`. Without
56
+ that, [device-run.sh](scripts/device-run.sh) writes a partial record with `step: null` when
57
+ the journey fails and left none. The first failure is never replaced by a cleanup error.
58
+ The contract, other languages and the Maestro/Detox mapping are in
59
+ [diagnosis](references/diagnosis.md#record-the-failed-device-case).
60
+
61
+ ## UI journey tool
62
+
63
+ The skill references Maestro; it does not bundle or install it. A repo with no journey tool
64
+ is offered Maestro for new single-device journeys; a repo with a working harness keeps it
65
+ and adds Maestro flows alongside. Run it on the session's own emulator
66
+ (`maestro --device <serial>`) and convert its output with
67
+ [maestro_failure.py](scripts/maestro_failure.py), which writes the record above. Read
68
+ [UI journeys](references/ui-journeys.md) for the device-ownership cost, evidence layout, MCP
69
+ use and the cases that stay custom.
70
+
45
71
  ## Validation ladder
46
72
 
47
73
  Read [feature work](references/feature-work.md) for selection, ownership and handoff.
@@ -122,5 +148,7 @@ higher levels still pending rather than claiming acceptance.
122
148
  | Parallel warm servers and test-first provenance | [Parallel sessions](references/workspace.md#parallel-development-sessions) and [test-first provenance](references/feature-work.md#run-tests-before-chasing-commit-labels) |
123
149
  | Builds, provisioning, device execution, cleanup and setup proof | [setup.md](references/setup.md); use [device-run.sh](scripts/device-run.sh) only without an existing resource owner |
124
150
  | Feature/fix validation and handoff | [feature-work.md](references/feature-work.md) |
151
+ | Device failure record and evidence capture | [diagnosis.md](references/diagnosis.md#record-the-failed-device-case) |
152
+ | UI journey tool (Maestro) and its evidence converter | [UI journeys](references/ui-journeys.md) |
125
153
  | Failure reproduction and correction | [diagnosis.md](references/diagnosis.md), then the relevant [gotcha](references/gotchas.md) |
126
154
  | CI integration | [ci.md](references/ci.md), [template](assets/android-ci-job.yml), and `github-actions` |
@@ -94,6 +94,56 @@ reproduction command, controlled change, observations, evidence locations, prima
94
94
  cleanup outcomes, and remaining uncertainty. Do not require an APK hash for a compiler
95
95
  failure that produced no APK.
96
96
 
97
+ ## Record the failed device case
98
+
99
+ Capture the failing step where it fails, not after cleanup. One contract covers every
100
+ harness, so a coordinator, a fix brief and a later diagnosis read the same thing.
101
+
102
+ `failure.json` in the evidence directory holds `case`, `step`, `error`, `screenshot`,
103
+ `uiDump`, `logcatTail`, `evidenceDir` and `cleanupErrors`. The three artifact fields are
104
+ file names relative to `evidenceDir`, or `null` when that capture failed; the reason then
105
+ goes to `cleanupErrors` and never replaces `error`. Write the record once: the first
106
+ failure is kept, and neither a later failure nor a cleanup problem overwrites it. Print
107
+ exactly one line, `NATIVE_FAILURE {"case":…,"step":…,"error":…,"evidenceDir":…}`, so a
108
+ reader that only sees the run output still learns the cause and where the evidence is.
109
+
110
+ In Python journeys wrap the step helpers in [failure_capture.py](../scripts/failure_capture.py):
111
+
112
+ ```python
113
+ from failure_capture import FailureRecorder
114
+
115
+ recorder = FailureRecorder(case=os.environ["DEVICE_CASE"], evidence_dir=os.environ["LOG_DIR"])
116
+
117
+ with recorder.step("tap Pay"):
118
+ tap("Pay")
119
+ with recorder.step("wait for receipt"):
120
+ wait_for("Thank you")
121
+ ```
122
+
123
+ `recorder.step` is a context manager and a decorator. On the first exception in a step it
124
+ captures `adb exec-out screencap -p`, `uiautomator dump` and a bounded logcat tail on the
125
+ injected serial, writes the record, prints the line and re-raises the original exception.
126
+ Captures are bounded; a capture failure is a `cleanupErrors` entry. A cancelled run
127
+ (`KeyboardInterrupt`, `SystemExit`) is not a failed case.
128
+
129
+ Other languages implement the same two shapes. When the journey harness cannot report a
130
+ step, fix the harness in the PR so every case records one; do not wrap it in a run-only
131
+ script, and do not add a second failure format.
132
+
133
+ Without an in-journey helper, [device-run.sh](../scripts/device-run.sh) falls back: after a
134
+ non-zero journey exit with no `failure.json` in `LOG_DIR` it captures the screen, hierarchy
135
+ and logcat tail before tearing the device down, writes a partial record with `step: null`,
136
+ and prints `NATIVE_FAILURE`. Its `original_stage_exit=<n> cleanup_exit=<n>` line and exit
137
+ status are unchanged, and a record the journey already wrote is left untouched.
138
+
139
+ A repo that already runs Maestro or Detox maps its failing artifacts to this record instead
140
+ of adding the helper: Maestro's failing-step screenshot, view hierarchy and logcat plus its
141
+ `commands.json` step, and Detox's `failing` artifact preset output. For Maestro, run
142
+ [maestro_failure.py](../scripts/maestro_failure.py) over the flow folder or the session folder
143
+ it sits in; it writes the record and prints the line, and [UI journeys](ui-journeys.md#evidence)
144
+ has the evidence layout. A record is written even when the source is the framework's artifact
145
+ set, and the printed line still has the four fields.
146
+
97
147
  ## Bound work and distinguish progress from waiting
98
148
 
99
149
  Give commands and child waits explicit deadlines appropriate to their purpose. Reserve
@@ -103,6 +153,11 @@ worker's "running" status does not impose a timeout or demonstrate progress. The
103
153
  whole journey. Set a positive value for unattended runs; its backward-compatible
104
154
  zero default disables that deadline. Allow time for the owner to finish cleanup.
105
155
 
156
+ Use the harness's background-task mechanism when a journey can exceed the foreground
157
+ command limit. Budget for the selected journey and owned cleanup; feedback-time targets
158
+ are not kill deadlines. Continue independent work and inspect the terminal result when
159
+ notified, rather than waiting or polling in the foreground. Do not add another watcher.
160
+
106
161
  For a hung test, identify the actual outstanding command and last completed assertion.
107
162
  If the test waits for a child to exit, check whether its input actually requires exit.
108
163
  Reduce that question to a bounded host test before launching another emulator.
@@ -30,6 +30,21 @@ Native/build-input changes invalidate that reuse; unknown compatibility calls fo
30
30
  rebuild, not an assertion that the old APK is current. Do not run `expo prebuild` routinely
31
31
  against tracked native projects.
32
32
 
33
+ ### Check journey admission before reserving devices
34
+
35
+ Before an expensive native journey, verify the exact selected case and supported arguments
36
+ against the existing runner, required installation/resource owners, effective test flags,
37
+ and device settings. Use existing help, parser tests or preflight commands when available;
38
+ do not invent a selector or build another preflight framework. Missing admission support
39
+ is a harness limitation to resolve, not a reason to allocate devices and discover it later.
40
+ Continue independent source tests while device prerequisites are checked.
41
+
42
+ Apply lifecycle-changing display size, density and other required presentation settings
43
+ before app launch when the journey relies on retaining that Activity or observation scope.
44
+ Read back the effective settings through the existing owner. A later setting change may
45
+ recreate the Activity; do not hide scope retirement with registration retries or relaxed
46
+ identity checks. Keep this check scoped to the selected case, not every possible device cell.
47
+
33
48
  ### Verify command expansion
34
49
 
35
50
  Before adopting a native fast path, trace its package script, wrapper, Gradle tasks and
@@ -7,15 +7,18 @@ whether anything is persisted.
7
7
  ## Usage
8
8
 
9
9
  ```sh
10
- node {baseDir}/scripts/init.mjs [--repo-root <dir>] [--write]
10
+ node {baseDir}/scripts/init.mjs [--repo-root <dir>] [--install-scripts [<dir>]] [--write]
11
11
  ```
12
12
 
13
13
  - Preview is the default: it prints the planned action per file and writes nothing.
14
14
  - `--write` applies the plan. `--repo-root` selects the project; without it the nearest Git root
15
15
  from the current directory is used, falling back to the current directory when there is none.
16
+ - `--install-scripts` additionally copies the generic script layer into the repo; see
17
+ [Skill scripts](#skill-scripts-reference-or-copy) for the target and the rules.
16
18
  - `--help` prints the usage line.
17
- - An unknown argument, an unresolvable root, a malformed marker or an unsafe target exits
18
- non-zero with a message and no writes.
19
+ - An unknown argument, an unresolvable root, a malformed marker, an unsafe target or a file that
20
+ ends inside an unclosed fenced code block or HTML comment exits non-zero with a message and no
21
+ writes, in preview and with `--write` alike.
19
22
 
20
23
  ## Files and ownership
21
24
 
@@ -27,19 +30,36 @@ to a newly created Android section; report unresolved commands instead of guessi
27
30
  | File | Write |
28
31
  | --- | --- |
29
32
  | `CONTRIBUTING.md` | The `## Android development` section, wrapped in `<!-- conventions:android:start -->` / `<!-- conventions:android:end -->`, with `references/android-conventions.md` as its body. |
30
- | `AGENTS.md` | A short pointer to the selected `CONTRIBUTING.md`, added only when no correct loader link exists yet. |
33
+ | `AGENTS.md` | An `Android task entry` skill-loading block between `conventions:android-task-entry` markers, plus a pointer to the selected `CONTRIBUTING.md` when missing. Check these independently: a contributor-guide link alone is not a skill loader. |
34
+ | `<dir>/device-run.sh`, `<dir>/failure_capture.py` | Only with `--install-scripts`: the generic layer copied with a version header. |
31
35
 
32
36
  Each file is created only when it is absent. The helper never edits `CLAUDE.md`, never writes into
33
37
  a parent project, a home directory or global agent configuration, and never reaches outside the
34
38
  selected root.
35
39
 
36
40
  After applying authorized additions, verify that each existing supported agent entrypoint
37
- reaches the Android section, directly or through its instruction chain. The helper configures
41
+ reaches both the skill task-entry instruction and the Android contributor-guide section,
42
+ directly or through its instruction chain. A direct link to `CONTRIBUTING.md` does not
43
+ establish that the entrypoint loads the skill rule in `AGENTS.md`. The helper configures
38
44
  `AGENTS.md`; it does not certify an independent `CLAUDE.md` loader. Preserve existing chains
39
45
  and do not edit that entrypoint under this init permission. If one does not reach the guide,
40
46
  report the files configured and the unresolved loading path as partial setup. Obtain separate
41
47
  authorization for any additional loader edit instead of silently claiming complete setup.
42
48
 
49
+ The task-entry block requires reading and applying the installed `android-development`
50
+ skill before Android-specific commands, during issue-unit preflight, in delegated Android
51
+ workers, and again after compaction when its rules are lost. Keep commands and validation
52
+ policy in `CONTRIBUTING.md`, not this loader. An existing direct read/load-before rule or
53
+ managed block is preserved. A managed block is preserved only while it still holds an active
54
+ loader: an empty block, or one whose only loader is commented out or written as a code example,
55
+ is refused rather than counted as configured. A direct `do not load`-style rule that conflicts
56
+ with the loader is refused too, wherever it sits in the file. Fenced code, indented code, HTML
57
+ comments and inline code are documentation, so an example never counts as an active loader or as
58
+ a conflict. An unrecognized `Android task entry` section requires manual reconciliation rather
59
+ than a competing block. Inspect existing rules for conflicts before applying the helper; it
60
+ cannot interpret every natural-language policy. This missing-loader addition is part of explicit
61
+ init even in previously initialized repos, not an automatic update.
62
+
43
63
  ## Resolve the repo command map
44
64
 
45
65
  The initializer, not the deterministic writer, inspects existing scripts and guidance.
@@ -81,10 +101,40 @@ Existing guidance wins over the template:
81
101
  - Refuse a mutation of a multiply hard-linked file or of an alias to the protected
82
102
  `CLAUDE.md` entrypoint. This includes a shared AGENTS/CLAUDE target: preserve it and
83
103
  report the boundary rather than silently granting a new write permission.
104
+ - Refuse an addition to a file that ends inside an unclosed fenced code block or HTML comment.
105
+ Appended guidance would land inside that construct, be inactive, and be appended again on
106
+ every run. The helper never closes it for you: preserve the manual text, repair the fence or
107
+ comment, and rerun. A file that needs no addition is reported unchanged, so an
108
+ already-configured project behind such a construct is not blocked.
84
109
  - An existing pointer is reused only when its link resolves to the selected `CONTRIBUTING.md`,
85
110
  and a new pointer is written relative to the real location of `AGENTS.md`. A link to some other
86
111
  file, such as a nested package guide, does not count as this project's loader.
87
112
 
113
+ ## Skill scripts: reference or copy
114
+
115
+ `device-run.sh` and `failure_capture.py` are referenced from the installed skill by
116
+ default: nothing is copied, and the run reads them from the skill directory. Copy them
117
+ into the repo only on an explicit request, with `--install-scripts` (default target
118
+ `scripts/android/`) or `--install-scripts <dir>` for another relative directory inside
119
+ the project.
120
+
121
+ A copy carries one header line naming the skill release and the shipped body's hash, for
122
+ example `# android-development skill 0.47.0 sha256:<64 hex> device-run.sh`. The header
123
+ follows a shebang or a module docstring, so neither stops being first; the rest of the file
124
+ is the shipped script byte for byte. That makes the three outcomes unambiguous:
125
+
126
+ - Missing file: `create`. An unmodified copy of the current skill: `unchanged`, so a rerun
127
+ writes nothing. An unmodified copy of an older skill release: `update` re-stamps the
128
+ header and rewrites nothing a caller added.
129
+ - Any other content, including a copy without the header, is `drift`. It is reported in
130
+ preview and with `--write`, the file is preserved, and the run still succeeds. Refresh it
131
+ by hand; initialization never replaces a local edit.
132
+ - Unsafe targets are refused before anything is written: an absolute path, a `..` segment,
133
+ or a directory whose nearest existing ancestor is a file or resolves outside the root.
134
+
135
+ This is the contract's preservation rule, not an update path: a new skill release never
136
+ rewrites project files, and drift is reported rather than resolved.
137
+
88
138
  ## What it does not do
89
139
 
90
140
  Initialization is not installation or delivery. It does not install dependencies, SDK components
@@ -92,7 +142,8 @@ or devices, provision an emulator, generate CI, open issues, push or release. It
92
142
  discover commands automatically: the initializer follows the command map above and reports
93
143
  what it could not resolve. Adding verified repository-specific bindings to the new section is
94
144
  part of that explicit initialization; a
95
- conflicting existing rule is reported rather than overwritten.
145
+ conflicting existing rule is reported rather than overwritten. Copying the generic scripts
146
+ does not make the repo's existing harness or CI use them.
96
147
 
97
148
  ## Scope and overrides
98
149
 
@@ -211,7 +211,12 @@ Pass each command as separate words, not as one quoted string. The script:
211
211
  uses the separate `JOURNEY_SECONDS` deadline. Leave time for cleanup after that deadline.
212
212
  5. Exports `$DB_URL_VAR`, `$DB_NAME_VAR` (`$DB_NAME_PREFIX` plus a unique suffix) and
213
213
  `ANDROID_EMULATOR_SERIAL`, then runs the journey.
214
- 6. Drops the database, stops the emulator PID (`TERM`, then `KILL` after 10 seconds),
214
+ 6. On a failing journey that left no record, captures the screenshot, UI dump and logcat
215
+ tail into `LOG_DIR` while the device is still up, writes `failure.json` with `step: null`
216
+ and prints one `NATIVE_FAILURE {...}` line. A record the journey's own helper wrote is
217
+ kept; capture problems are listed in its `cleanupErrors`. See the
218
+ [failure record contract](diagnosis.md#record-the-failed-device-case).
219
+ 7. Drops the database, stops the emulator PID (`TERM`, then `KILL` after 10 seconds),
215
220
  removes the container by its CID, and prints `original_stage_exit=<n> cleanup_exit=<n>`.
216
221
  It exits with the journey's status if the journey failed, else the cleanup status.
217
222
 
@@ -220,7 +225,8 @@ group; a wrapper that deliberately creates detached services must stop those ser
220
225
  through its own owner. Send TERM to the launcher and allow its cleanup to finish rather
221
226
  than killing it immediately. The generic runner inherits the caller's ADB endpoint and
222
227
  does not stop that server: use a verified private endpoint on a shared host and close it
223
- through the caller's owner. Capture the command's exit status and final summary line.
228
+ through the caller's owner. Capture the command's exit status, the final summary line and,
229
+ when the journey failed, the `NATIVE_FAILURE` line and its `failure.json`.
224
230
 
225
231
  Expect every selected scenario's `PASS` marker, screenshots, and backend and Metro logs
226
232
  when the selected variant uses those services. Preserve the failed checkpoint's evidence
@@ -0,0 +1,58 @@
1
+ # UI journey tool: Maestro
2
+
3
+ Maestro writes a single-device UI flow as YAML and records per-step failure evidence. The
4
+ skill references it; it does not bundle or install it, and adding it is the repo's decision.
5
+
6
+ ## When to use it
7
+
8
+ | Situation | Recommendation |
9
+ | --- | --- |
10
+ | Repo has no UI journey tool | Offer Maestro for new single-device journeys. It replaces hand-written adb/UiAutomator launch, poll, capture and assertion code. |
11
+ | Repo has a working harness | Keep the harness. Add Maestro flows for new journeys alongside it; do not migrate existing cases. |
12
+ | Coordinated multi-device case | Keep the custom harness. Maestro sharding runs independent flows, and a shard cannot depend on another. |
13
+
14
+ ## Device ownership
15
+
16
+ Run against the emulator the session owns and name it, so Maestro never starts or picks its
17
+ own device:
18
+
19
+ ```bash
20
+ maestro --device "$ANDROID_SERIAL" test --test-output-dir "$LOG_DIR" ./maestro-flows
21
+ ```
22
+
23
+ Each session installs and starts Maestro's driver packages (`dev.mobile.maestro`,
24
+ `dev.mobile.maestro.test`) through `am instrument`. That cost lands on the session's first
25
+ flow, not on every step, so run every flow of the case in one session instead of one session
26
+ per flow.
27
+
28
+ ## Evidence
29
+
30
+ `--test-output-dir` receives one session folder holding one self-contained folder per flow:
31
+ `manifest.json`, `commands.json` (one entry per executed step), the failing step's
32
+ `screenshots/` image and `screen-hierarchy/` JSON, and `logs/device-logcat.txt`. Convert that
33
+ into the [device failure record](diagnosis.md#record-the-failed-device-case) instead of
34
+ adding the journey helper:
35
+
36
+ ```bash
37
+ python3 <skill-base-dir>/scripts/maestro_failure.py --maestro-dir "$LOG_DIR/2026-07-31_173617" \
38
+ --evidence-dir "$LOG_DIR" --case "$DEVICE_CASE"
39
+ ```
40
+
41
+ It takes the session folder or one flow folder, writes `failure.json` into `--evidence-dir`
42
+ (the flow folder by default) and prints the one `NATIVE_FAILURE` line for the first failed
43
+ step. A passing flow writes nothing, and an existing record is never replaced. The artifact
44
+ fields name the Maestro files (`screenshots/…`, `screen-hierarchy/…`, `logs/…`) rather than
45
+ copying them into the adb names, so the failing frame and its hierarchy stay paired.
46
+
47
+ ## Agents
48
+
49
+ `maestro mcp` gives an agent `list_devices`, `inspect_screen`, `take_screenshot` and `run`.
50
+ Explore the screen and draft a flow with it; the committed YAML flow is the proof, not the
51
+ MCP session that found the selectors.
52
+
53
+ ## Limits
54
+
55
+ - Coordinated multi-device cases, exact data or payment proofs, and process-death checks stay
56
+ on custom harness code.
57
+ - Authentication codes still come from the harness's disposable database, passed into a flow
58
+ with `maestro test -e` or resolved by its `runScript`; do not add a second fixture path.
@@ -17,6 +17,13 @@
17
17
  # POSTGRES_IMAGE (postgres:17) DB_URL_VAR (SHARD_ADMIN_URL) DB_NAME_VAR (SHARD_DB_NAME)
18
18
  # LOG_DIR (.cache/logs) BOUNDED_SECONDS (30) BOOT_ATTEMPTS (60) KILL_GRACE_SECONDS (10)
19
19
  # JOURNEY_SECONDS (0 = no deadline; caller chooses a positive whole-journey limit)
20
+ # DEVICE_CASE (unset) LOGCAT_LINES (400)
21
+ #
22
+ # A journey that fails and left no record of its own gets one before teardown:
23
+ # $LOG_DIR/screenshot.png, $LOG_DIR/ui-dump.xml, $LOG_DIR/logcat-tail.txt and a
24
+ # partial $LOG_DIR/failure.json with step null, plus one `NATIVE_FAILURE {json}`
25
+ # line. A record the journey's own helper wrote is kept, and capture problems go
26
+ # to that record's cleanupErrors instead of replacing the original failure.
20
27
  #
21
28
  # ADB_SERVER_SOCKET / ANDROID_ADB_SERVER_PORT are inherited unchanged. The caller
22
29
  # selects a private ADB endpoint before entry; this script never stops that server.
@@ -55,6 +62,7 @@ BOUNDED_SECONDS="${BOUNDED_SECONDS:-30}"
55
62
  BOOT_ATTEMPTS="${BOOT_ATTEMPTS:-60}"
56
63
  KILL_GRACE_SECONDS="${KILL_GRACE_SECONDS:-10}"
57
64
  JOURNEY_SECONDS="${JOURNEY_SECONDS:-0}"
65
+ LOGCAT_LINES="${LOGCAT_LINES:-400}"
58
66
 
59
67
  # Reject invalid limits before creating any owned resources.
60
68
  python3 -c 'import math, sys
@@ -62,8 +70,9 @@ for value in sys.argv[1:4]:
62
70
  number = float(value)
63
71
  if not math.isfinite(number) or number < 0: sys.exit("limits must be finite and non-negative")
64
72
  if float(sys.argv[1]) == 0: sys.exit("BOUNDED_SECONDS must be positive")
65
- if not sys.argv[4].isdigit() or int(sys.argv[4]) < 1: sys.exit("BOOT_ATTEMPTS must be a positive integer")' \
66
- "$BOUNDED_SECONDS" "$KILL_GRACE_SECONDS" "$JOURNEY_SECONDS" "$BOOT_ATTEMPTS" || exit 2
73
+ if not sys.argv[4].isdigit() or int(sys.argv[4]) < 1: sys.exit("BOOT_ATTEMPTS must be a positive integer")
74
+ if not sys.argv[5].isdigit() or int(sys.argv[5]) < 1: sys.exit("LOGCAT_LINES must be a positive integer")' \
75
+ "$BOUNDED_SECONDS" "$KILL_GRACE_SECONDS" "$JOURNEY_SECONDS" "$BOOT_ATTEMPTS" "$LOGCAT_LINES" || exit 2
67
76
  for variable in "$DB_URL_VAR" "$DB_NAME_VAR"; do
68
77
  if ! [[ "$variable" =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]]; then
69
78
  echo "invalid database environment variable: $variable" >&2
@@ -88,6 +97,8 @@ db_ready=""
88
97
  container_started=""
89
98
  launch_in_progress=0
90
99
  pending_signal=0
100
+ journey_started=0
101
+ cleanup_errors=()
91
102
  handle_signal() {
92
103
  pending_signal="$1"
93
104
  # Do not abandon a spawned supervisor before its PID has been recorded.
@@ -174,8 +185,86 @@ managed() {
174
185
 
175
186
  bounded() { managed "$BOUNDED_SECONDS" "$@"; }
176
187
 
188
+ capture_artifact() {
189
+ # capture_artifact <file-name> <adb args...>; an empty capture is not evidence.
190
+ local name="$1" file="$LOG_DIR/$1"
191
+ shift
192
+ if bounded adb -s "$ANDROID_EMULATOR_SERIAL" "$@" > "$file" 2>/dev/null && test -s "$file"; then
193
+ return 0
194
+ fi
195
+ cleanup_errors+=("$name: adb -s $ANDROID_EMULATOR_SERIAL $* produced no evidence")
196
+ rm -f "$file"
197
+ }
198
+
199
+ capture_ui_dump() {
200
+ local remote=/sdcard/android-failure.xml file="$LOG_DIR/ui-dump.xml"
201
+ if bounded adb -s "$ANDROID_EMULATOR_SERIAL" shell uiautomator dump "$remote" >/dev/null 2>&1 \
202
+ && bounded adb -s "$ANDROID_EMULATOR_SERIAL" exec-out cat "$remote" > "$file" 2>/dev/null \
203
+ && test -s "$file"; then
204
+ return 0
205
+ fi
206
+ cleanup_errors+=("ui-dump.xml: adb -s $ANDROID_EMULATOR_SERIAL shell uiautomator dump failed")
207
+ rm -f "$file"
208
+ }
209
+
210
+ capture_logcat() {
211
+ # An empty tail is still a capture; a failed one is not.
212
+ local file="$LOG_DIR/logcat-tail.txt"
213
+ if bounded adb -s "$ANDROID_EMULATOR_SERIAL" logcat -d -t "$LOGCAT_LINES" > "$file" 2>/dev/null; then
214
+ return 0
215
+ fi
216
+ cleanup_errors+=("logcat-tail.txt: adb -s $ANDROID_EMULATOR_SERIAL logcat -d -t $LOGCAT_LINES failed")
217
+ rm -f "$file"
218
+ }
219
+
220
+ write_failure_record() {
221
+ local error
222
+ case "$1" in
223
+ 124) error="journey exceeded ${JOURNEY_SECONDS}s (status 124): ${journey_cmd[*]}" ;;
224
+ *) error="journey exited with status $1: ${journey_cmd[*]}" ;;
225
+ esac
226
+ # One writer for the record and its line, so their shapes cannot drift.
227
+ python3 - "$LOG_DIR" "${DEVICE_CASE:-}" "$error" \
228
+ ${cleanup_errors[@]+"${cleanup_errors[@]}"} >&3 <<'PY' || {
229
+ import json, os, sys
230
+ evidence, case, error = os.path.abspath(sys.argv[1]), sys.argv[2] or None, sys.argv[3]
231
+ record = {"case": case, "step": None, "error": error}
232
+ for key, name in (("screenshot", "screenshot.png"), ("uiDump", "ui-dump.xml"), ("logcatTail", "logcat-tail.txt")):
233
+ path = os.path.join(evidence, name)
234
+ record[key] = name if os.path.isfile(path) and os.path.getsize(path) else None
235
+ record["evidenceDir"] = evidence
236
+ record["cleanupErrors"] = sys.argv[4:]
237
+ path = os.path.join(evidence, "failure.json")
238
+ temporary = path + ".tmp"
239
+ with open(temporary, "w", encoding="utf-8") as stream:
240
+ stream.write(json.dumps(record, indent=2) + "\n")
241
+ os.replace(temporary, path)
242
+ print("NATIVE_FAILURE " + json.dumps(
243
+ {key: record[key] for key in ("case", "step", "error", "evidenceDir")}, separators=(",", ":")))
244
+ PY
245
+ echo "failure record not written; keep the evidence in $LOG_DIR" >&3
246
+ return 0
247
+ }
248
+ }
249
+
250
+ capture_failure() {
251
+ # Called before teardown: the emulator and app are still in the failing state.
252
+ local status="$1" cancelled="$2"
253
+ test "$status" -ne 0 || return 0
254
+ test "$journey_started" -eq 1 || return 0
255
+ # A requested stop is not a failed case.
256
+ test "$cancelled" -eq 0 || return 0
257
+ # The journey's own helper already recorded the failing step; keep that record.
258
+ test ! -e "$LOG_DIR/failure.json" || return 0
259
+ capture_artifact screenshot.png exec-out screencap -p
260
+ capture_ui_dump
261
+ capture_logcat
262
+ write_failure_record "$status"
263
+ }
264
+
177
265
  cleanup() {
178
266
  original_status=$?
267
+ cancelled_signal="$pending_signal"
179
268
  trap - EXIT
180
269
  trap '' INT TERM
181
270
  pending_signal=0
@@ -187,6 +276,7 @@ cleanup() {
187
276
  wait "$active_pid" || :
188
277
  active_pid=""
189
278
  fi
279
+ capture_failure "$original_status" "$cancelled_signal"
190
280
  if test -n "$db_ready"; then
191
281
  bounded "${drop_cmd[@]}" || cleanup_status=1
192
282
  fi
@@ -279,4 +369,5 @@ export "$DB_URL_VAR=postgresql://postgres:postgres@127.0.0.1:$db_port/postgres"
279
369
  export "$DB_NAME_VAR=${DB_NAME_PREFIX}$(date +%s)_$$"
280
370
  db_ready=1
281
371
 
372
+ journey_started=1
282
373
  managed "$JOURNEY_SECONDS" "${journey_cmd[@]}"