@1aboveio/skills 1.0.13 → 1.0.14

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 (94) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.codex-plugin/resources.json +145 -142
  3. package/README.md +1 -1
  4. package/package.json +5 -5
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/android-development/SKILL.md +126 -0
  7. package/skills/app-dev/android-development/references/android-conventions.md +117 -0
  8. package/skills/app-dev/{android-foundation → android-development}/references/ci.md +10 -6
  9. package/skills/app-dev/{android-foundation → android-development}/references/diagnosis.md +39 -15
  10. package/skills/app-dev/android-development/references/feature-work.md +171 -0
  11. package/skills/app-dev/android-development/references/framework-choice.md +88 -0
  12. package/skills/app-dev/{android-foundation → android-development}/references/gotchas.md +43 -6
  13. package/skills/app-dev/android-development/references/init.md +104 -0
  14. package/skills/app-dev/android-development/references/setup.md +389 -0
  15. package/skills/app-dev/android-development/references/workspace.md +156 -0
  16. package/skills/app-dev/android-development/scripts/init.mjs +380 -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 +1 -1
  20. package/skills/app-dev/data-quality/SKILL.md +1 -1
  21. package/skills/app-dev/ios-foundation/SKILL.md +2 -2
  22. package/skills/app-dev/monorepo/SKILL.md +1 -1
  23. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  24. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  25. package/skills/app-dev/pyspark/SKILL.md +1 -1
  26. package/skills/app-dev/python-backend/SKILL.md +1 -1
  27. package/skills/app-dev/shadcn/SKILL.md +1 -1
  28. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  29. package/skills/cicd/cloud-build/SKILL.md +1 -1
  30. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  31. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  32. package/skills/cicd/docker/SKILL.md +1 -1
  33. package/skills/cicd/github-actions/SKILL.md +1 -1
  34. package/skills/cicd/github-flow/SKILL.md +1 -1
  35. package/skills/cicd/google-cloud/SKILL.md +1 -1
  36. package/skills/cicd/mergify/SKILL.md +1 -1
  37. package/skills/cicd/podman/SKILL.md +1 -1
  38. package/skills/content/article-review/SKILL.md +1 -1
  39. package/skills/content/article-to-image/SKILL.md +1 -1
  40. package/skills/content/flowchart-svg/SKILL.md +1 -1
  41. package/skills/content/image-gen/SKILL.md +1 -1
  42. package/skills/content/mermaid/SKILL.md +1 -1
  43. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  44. package/skills/content/wechat-publisher/SKILL.md +1 -1
  45. package/skills/content/wechat-search/SKILL.md +1 -1
  46. package/skills/content/xai-search/SKILL.md +1 -1
  47. package/skills/core/distill-writing/SKILL.md +1 -1
  48. package/skills/core/doc-to-md/SKILL.md +1 -1
  49. package/skills/core/editorial-review/SKILL.md +1 -1
  50. package/skills/core/house-style/SKILL.md +1 -1
  51. package/skills/core/plain-language/SKILL.md +1 -1
  52. package/skills/core/polish-cn/SKILL.md +1 -1
  53. package/skills/core/reg-parser/SKILL.md +1 -1
  54. package/skills/core/terminology/SKILL.md +1 -1
  55. package/skills/core/write-doc/SKILL.md +1 -1
  56. package/skills/core/write-report/SKILL.md +1 -1
  57. package/skills/engineering/better-review/SKILL.md +1 -1
  58. package/skills/engineering/e2e-test/SKILL.md +1 -1
  59. package/skills/engineering/engineering/SKILL.md +9 -3
  60. package/skills/engineering/engineering/references/init-scope.md +7 -3
  61. package/skills/engineering/engineering/references/scope-management.md +9 -0
  62. package/skills/engineering/engineering/scripts/init.mjs +40 -17
  63. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  64. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  65. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  66. package/skills/engineering/golden-path/SKILL.md +1 -1
  67. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  68. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  69. package/skills/engineering/resolve-issues/references/exploration.md +1 -1
  70. package/skills/engineering/resolve-release/SKILL.md +1 -1
  71. package/skills/engineering/rush-issues/SKILL.md +1 -1
  72. package/skills/engineering/rush-release/SKILL.md +1 -1
  73. package/skills/engineering/test-quality/SKILL.md +1 -1
  74. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  75. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  76. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  77. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  78. package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
  79. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  80. package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
  81. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  82. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  83. package/skills/app-dev/android-foundation/SKILL.md +0 -248
  84. package/skills/app-dev/android-foundation/references/delivery-focus.md +0 -31
  85. package/skills/app-dev/android-foundation/references/feature-work.md +0 -50
  86. package/skills/app-dev/android-foundation/references/setup.md +0 -188
  87. package/skills/app-dev/android-foundation/references/workspace.md +0 -81
  88. /package/skills/app-dev/{android-foundation → android-development}/LICENSE +0 -0
  89. /package/skills/app-dev/{android-foundation → android-development}/assets/android-ci-job.yml +0 -0
  90. /package/skills/app-dev/{android-foundation → android-development}/release-check.mjs +0 -0
  91. /package/skills/app-dev/{android-foundation → android-development}/scripts/device-run.sh +0 -0
  92. /package/skills/app-dev/{android-foundation → android-development}/scripts/workspace-runtime/bin/workspaces.mjs +0 -0
  93. /package/skills/app-dev/{android-foundation → android-development}/scripts/workspace-runtime/native-workspace.mjs +0 -0
  94. /package/skills/app-dev/{android-foundation → android-development}/scripts/workspace-runtime/workspace-lifecycle.mjs +0 -0
@@ -0,0 +1,156 @@
1
+ # Android workspace lifecycle
2
+
3
+ The coordinator or caller owns workspace selection. Resolve it once, before any setup stage,
4
+ and record the result. This reference owns workspace and runtime ownership: which resources
5
+ this run owns, which of them persist across attempts and which are per-run. Cache layout, SDK
6
+ isolation and the concrete stop commands stay in [setup.md](setup.md),
7
+ [Owned development session](setup.md#owned-development-session) and
8
+ [Owned-resource cleanup](setup.md#owned-resource-cleanup).
9
+
10
+ ## Precedence
11
+
12
+ 1. **Assigned workspace and candidate HEAD.** When the caller supplies an isolated workspace
13
+ and a candidate HEAD, use that workspace and that revision. Do not create a worktree or
14
+ branch, fetch, reset or switch revisions. Read `git rev-parse HEAD` and report it as the
15
+ source SHA. If it differs from the candidate HEAD, stop and report the mismatch; never
16
+ switch the workspace to match.
17
+ 2. **Existing owned workspace.** Feature work, fixes and diagnosis reuse the workspace this
18
+ run or unit already owns, with its private caches, venv and AVD profile. Do not make a
19
+ second workspace for a retry, a correction or a read-only investigation.
20
+ 3. **Standalone fresh setup.** Only when the coordinator reports no usable assignment. Create
21
+ one managed workspace at
22
+ `<repo>/.resolve-issues/rush/<run-id>/units/native`. Never create a sibling checkout such
23
+ as `../<repo>-android-setup`, and never add a second managed path for a retry.
24
+
25
+ Android and iOS setup in the same run share that one managed workspace; their private cache
26
+ subdirectories and short-lived processes stay separate.
27
+
28
+ An assigned path that is missing, is not a Git worktree, or is at another revision is not an
29
+ invitation to substitute a fresh checkout: stop and report it. For diagnosis, name the actual
30
+ checkout and HEAD and confirm it contains the failing code; the repository root or another
31
+ checkout may lag the candidate.
32
+
33
+ ## Record before creating
34
+
35
+ The bundled `scripts/workspace-runtime` is generated from the shared lifecycle
36
+ engine and ships with this skill. Only for standalone fresh setup, run:
37
+
38
+ ```bash
39
+ node <skill-base-dir>/scripts/workspace-runtime/bin/workspaces.mjs native-start --repo <canonical-root> \
40
+ --platform android --target <verified-target-branch> --base <candidate-SHA> --json
41
+ ```
42
+
43
+ The command records root, owner, exact base, branch, workspace and disposable cache
44
+ paths before creating anything. Use its returned `workspace` as `ASSIGNED_WORKSPACE`
45
+ and `sourceSha` as `CANDIDATE_HEAD` in setup. Keep `run` and `runId`; repeat with
46
+ `--run-id <returned-run-id>` and the same base, target and kind for retries. Never
47
+ invoke it inside a coordinator-assigned unit. It never substitutes `origin/main`
48
+ for the requested source or resets an existing workspace.
49
+ For a package below the root, pass `--native-dir <package-root-containing-android>`.
50
+ Declare other installs or build directories before use with repeated
51
+ `--disposable <exact-workspace-relative-path>`, including package-local `node_modules`.
52
+
53
+ ## Development workspace, owned session runtime and disposable runs
54
+
55
+ The workspace, its private caches under `.cache/`, its venv and its AVD profile are
56
+ development resources: retain them across retries and fixes, and keep them private to this
57
+ workspace. Never relocate them to a global cache or share them with a concurrent owner.
58
+
59
+ The owned **development session** has one lifecycle per owner. Start it once, record its PID
60
+ identity, serial, ports and log paths, and reuse it across every edit, fix and check attempt
61
+ in the session:
62
+
63
+ - the development emulator and the ADB endpoint it publishes, addressed by its owned serial;
64
+ - Metro, or the repo's development server, on its recorded port;
65
+ - one compatible Gradle daemon on the workspace's private `GRADLE_USER_HOME`, honoring the
66
+ repo's JVM and resource-capacity settings;
67
+ - the build outputs and installed app state under the workspace.
68
+
69
+ Within one session, do not start a second emulator, Metro, daemon or cache home because
70
+ the running one is inconvenient: keep the existing owner, do not create a second lifecycle,
71
+ and do not accumulate abandoned services. Long-lived processes stay under the harness's own
72
+ tracking so their exit is observable. Never `nohup`, `setsid` or `disown` a service into a
73
+ state this run cannot signal, and never leave one with no owner at all.
74
+
75
+ **Disposable acceptance and integration resources** follow their checkpoint's contract.
76
+ The `device-run.sh` emulator child, PostgreSQL container and named database exist for one
77
+ journey and are stopped by that run under [cleanup](setup.md#owned-resource-cleanup).
78
+ Do not start that stack for every edit. A fresh install does not automatically require a
79
+ new AVD or reboot: an existing owned emulator may be used when the gate permits it and
80
+ disposable app/test data can be isolated without destroying development state. Do not
81
+ inherit development authentication/database state as proof, or clear a borrowed device.
82
+
83
+ A session ends when the assignment ends, not when one check passes. Stop the services this
84
+ run started and confirm their exit under the existing cleanup rules, or hand off the ones
85
+ still running with their owner, PID identity, serial, ports, log paths and how to stop them.
86
+ A passing check, a reported result or a review handoff is not a session end.
87
+ The stop must match the resource: use the harness owner for supervised groups and a
88
+ verified private stop for daemons; do not shell-wait a nonchild or treat a wrapper's exit
89
+ as proof that its descendants stopped. Record uncertain cleanup as retained, not success.
90
+
91
+ Fast feedback comes from reuse, not from a fresh environment per attempt:
92
+
93
+ - Go incremental: one bounded edit/check cycle, reading the previous result before the next.
94
+ Build only when changed native or packaged inputs require it; refreshable JS needs no build.
95
+ - Do not recreate state for a clean start: no second emulator boot, Metro restart, `adb
96
+ kill-server`, fresh Gradle daemon, `clean`, dependency reinstall or `expo prebuild` of a
97
+ tracked native project for an ordinary fix.
98
+ - Prefer the cheapest check that proves the change (focused test, relevant typecheck, an app
99
+ that is already running) before scheduling the disposable device stack.
100
+ - Add no new pool, lease manager, snapshot-default AVD, provisioning script or second
101
+ launcher to make reuse work; the existing owner, launcher and cleanup rules are the
102
+ mechanism.
103
+
104
+ A read-only diagnosis does not require a clean environment. Reuse the owned workspace, its
105
+ caches and the session this run still owns; create only the services the observation needs,
106
+ and preserve the failed environment's evidence. Do not rebuild, reinstall or re-provision
107
+ merely because the failure is being investigated.
108
+
109
+ ## Parallel development sessions
110
+
111
+ Assign warm servers by independently changing source/configuration, not by agent count.
112
+ Keep each session warm between its own checks:
113
+
114
+ | Work arrangement | Runtime rule |
115
+ | --- | --- |
116
+ | Independent worktrees, branches or runtime configurations | Use separate Metro instances rooted in their actual workspaces, unique host ports and explicit device mappings. Keep writable caches, build outputs and test data private. |
117
+ | Several readers or intentionally integrated checks on the same source/configuration | Reuse one server under a designated owner. Coordinate edits and check snapshots; record that results cover the combined working state, not isolated candidates. |
118
+ | Limited device/host capacity | Run ready host checks concurrently and schedule conflicting device checks through the existing owner. Do not start excess emulators or create a new pool/lease system. |
119
+
120
+ For example, one emulator may map its device port 8081 to session A's private host port,
121
+ while a different emulator maps its device port 8081 to session B's private host port.
122
+ The host ports must differ; the device-local ports need not. Use the caller-selected ADB
123
+ endpoint and exact owned serial. Verify the app's actual bundle source, not only `/status`.
124
+
125
+ Do not point one Metro at alternating candidate trees or mutate another session's reverse
126
+ mapping, installed app or database. Keep device state exclusive during mutating checks.
127
+ If one device must serve different candidates sequentially, end the previous check and
128
+ let its owner explicitly reassign/reconfigure it and verify the new app/source mapping;
129
+ do not silently switch it while a test is running. Sharing a server does not make concurrent
130
+ writes to the same source tree independent or protect tests from mid-run source changes.
131
+
132
+ Do not assume a shared backend is safe for fixtures: share only when its isolation and
133
+ ownership contract explicitly permit it; otherwise keep candidate test data separate.
134
+ Stop shared services only through their owner after all users release them, or explicitly
135
+ hand them off. Do not kill a shared server because one agent finished its task.
136
+
137
+ ## Completion
138
+
139
+ At the end of the assignment, return the workspace, its cache paths and any retained process
140
+ records to the coordinator for final cleanup. The coordinator owns deletion and retention.
141
+ Register the path with the run's cleanup owner so final cleanup can tell a delivered
142
+ standalone run from unfinished or unpublished work.
143
+
144
+ - A successful, delivered standalone run must not retain an unbounded cache: mark the
145
+ workspace and its disposable caches for final cleanup. After stopping its owned
146
+ services, run `node <skill-base-dir>/scripts/workspace-runtime/bin/workspaces.mjs native-finish --run <run> --json`
147
+ from the owner checkout. This uses the same cleanup engine as issue workflows.
148
+ - Unfinished or unpublished standalone work keeps the workspace and caches for the next
149
+ attempt; report what is retained and why.
150
+
151
+ The parent lifecycle record is authoritative for ownership, retention and final cleanup.
152
+ Before `native-finish`, record the session's end: either the services this run started were
153
+ stopped and their exit confirmed, or the ones still running were handed off explicitly as
154
+ described above.
155
+ An explicit handoff does not authorize removing a workspace or cache still used by the
156
+ receiving owner. Retain it until ownership and delivery cleanup checks permit removal.
@@ -0,0 +1,380 @@
1
+ // Explicit Android project setup. Preview by default; adds only missing guidance.
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { spawnSync } from 'node:child_process'
6
+ import { fileURLToPath, pathToFileURL } from 'node:url'
7
+
8
+ const sectionTitle = 'Android development'
9
+ const sectionId = 'android'
10
+ const usage = 'node init.mjs [--repo-root <dir>] [--write]'
11
+
12
+ function parseArgs(argv) {
13
+ const options = { write: false }
14
+ for (let i = 0; i < argv.length; i++) {
15
+ const arg = argv[i]
16
+ if (arg === '--write') options.write = true
17
+ else if (arg === '--help') options.help = true
18
+ else if (arg === '--repo-root') {
19
+ const value = argv[++i]
20
+ if (!value || value.startsWith('--') || /[\r\n]/.test(value)) throw new Error(`missing or invalid value for ${arg}`)
21
+ options.repoRoot = path.resolve(value)
22
+ } else throw new Error(`unknown argument ${arg}`)
23
+ }
24
+ return options
25
+ }
26
+
27
+ function gitOutput(root, args) {
28
+ const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' })
29
+ return result.status === 0 ? result.stdout.trim() : null
30
+ }
31
+
32
+ function contains(outer, inner) {
33
+ const relative = path.relative(outer, inner)
34
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))
35
+ }
36
+
37
+ function selectRoot(options) {
38
+ const selected = options.repoRoot ?? gitOutput(process.cwd(), ['rev-parse', '--show-toplevel']) ?? process.cwd()
39
+ const root = fs.realpathSync(selected)
40
+ if (!fs.statSync(root).isDirectory()) throw new Error('project root is not a directory')
41
+ // A home directory holds global agent configuration, not project guidance.
42
+ if (contains(root, fs.realpathSync(os.homedir()))) {
43
+ throw new Error('selected root is the home directory or one of its ancestors; pass the project root')
44
+ }
45
+ return { root, selected: options.repoRoot ?? null }
46
+ }
47
+
48
+ function sectionBody() {
49
+ return fs.readFileSync(fileURLToPath(new URL('../references/android-conventions.md', import.meta.url)), 'utf8').trim()
50
+ }
51
+
52
+ function resolveTarget(root, name) {
53
+ const file = path.join(root, name)
54
+ const stat = fs.lstatSync(file, { throwIfNoEntry: false })
55
+ if (!stat) return { name, file, target: file, inode: null, nlink: 0, exists: false, link: false }
56
+ let target
57
+ try {
58
+ target = fs.realpathSync(file)
59
+ } catch {
60
+ throw new Error(`${name} is a dangling or inaccessible link; repair it before initializing`)
61
+ }
62
+ if (!contains(root, target)) throw new Error(`${name} resolves outside the selected project`)
63
+ const info = fs.statSync(target)
64
+ if (!info.isFile()) throw new Error(`${name} is not a regular file`)
65
+ return {
66
+ name,
67
+ file,
68
+ target,
69
+ inode: { dev: info.dev, ino: info.ino },
70
+ nlink: info.nlink,
71
+ exists: true,
72
+ link: stat.isSymbolicLink(),
73
+ }
74
+ }
75
+
76
+ function readGuidance(entry) {
77
+ if (!entry.exists) return { ...entry, current: null }
78
+ let bytes
79
+ try {
80
+ fs.accessSync(entry.target, fs.constants.R_OK)
81
+ bytes = fs.readFileSync(entry.target)
82
+ } catch {
83
+ throw new Error(`${entry.name} is unreadable`)
84
+ }
85
+ let current
86
+ try {
87
+ current = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes)
88
+ } catch {
89
+ throw new Error(`${entry.name} is not valid UTF-8 guidance`)
90
+ }
91
+ return { ...entry, current }
92
+ }
93
+
94
+ // CLAUDE.md is a protected entrypoint. Learn only its identity, never its content.
95
+ function inspectClaude(root) {
96
+ try {
97
+ const file = path.join(root, 'CLAUDE.md')
98
+ const stat = fs.lstatSync(file, { throwIfNoEntry: false })
99
+ if (!stat) return null
100
+ const target = fs.realpathSync(file)
101
+ if (!contains(root, target)) return null
102
+ const info = fs.statSync(target)
103
+ if (!info.isFile()) return null
104
+ return { file, target, inode: { dev: info.dev, ino: info.ino } }
105
+ } catch {
106
+ return null
107
+ }
108
+ }
109
+
110
+ function sameFile(left, right) {
111
+ if (!left || !right) return false
112
+ if (left.target === right.target) return true
113
+ return Boolean(left.inode && right.inode
114
+ && left.inode.dev === right.inode.dev && left.inode.ino === right.inode.ino)
115
+ }
116
+
117
+ function indentWidth(line) {
118
+ let width = 0
119
+ for (const char of line) {
120
+ if (char === ' ') width += 1
121
+ else if (char === '\t') width += 4 - (width % 4)
122
+ else break
123
+ }
124
+ return width
125
+ }
126
+
127
+ function stripInlineCode(line) {
128
+ const runs = [...line.matchAll(/`+/g)]
129
+ let result = ''
130
+ let cursor = 0
131
+ for (let i = 0; i < runs.length; i++) {
132
+ const opening = runs[i]
133
+ let closing = i + 1
134
+ while (closing < runs.length && runs[closing][0].length !== opening[0].length) closing++
135
+ if (closing === runs.length) continue
136
+ const end = runs[closing].index + runs[closing][0].length
137
+ result += line.slice(cursor, opening.index) + ' '.repeat(end - opening.index)
138
+ cursor = end
139
+ i = closing
140
+ }
141
+ return result + line.slice(cursor)
142
+ }
143
+
144
+ function stripHtmlComments(line, depth = 0) {
145
+ let text = ''
146
+ let index = 0
147
+ const comments = []
148
+ while (index < line.length) {
149
+ const start = line.indexOf('<!--', index)
150
+ const end = line.indexOf('-->', index)
151
+ if (start !== -1 && (end === -1 || start < end)) {
152
+ if (depth === 0) {
153
+ const close = line.indexOf('-->', start + 4)
154
+ if (close !== -1) comments.push(line.slice(start, close + 3))
155
+ }
156
+ text += depth === 0 ? line.slice(index, start) : ' '.repeat(start - index)
157
+ index = start + 4
158
+ depth += 1
159
+ } else if (end !== -1) {
160
+ if (depth > 0) {
161
+ text += ' '.repeat(end + 3 - index)
162
+ depth -= 1
163
+ } else {
164
+ text += line.slice(index, end + 3)
165
+ }
166
+ index = end + 3
167
+ } else {
168
+ text += depth === 0 ? line.slice(index) : ' '.repeat(line.length - index)
169
+ break
170
+ }
171
+ }
172
+ return { text, depth, comments }
173
+ }
174
+
175
+ // Markdown-aware view: inactive lines are fenced code, indented code or HTML comments.
176
+ // Inline code and HTML comments are blanked from active text so examples never count.
177
+ function markdownView(current) {
178
+ const lines = []
179
+ let fence = null
180
+ let commentDepth = 0
181
+ let indented = false
182
+ let previousBlank = true
183
+ for (const raw of current.split('\n')) {
184
+ const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw
185
+ const blank = line.trim() === ''
186
+ let active = true
187
+ let text = line
188
+ let comments = []
189
+ if (fence) {
190
+ active = false
191
+ const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line)
192
+ if (close && close[1][0] === fence.char && close[1].length >= fence.length) fence = null
193
+ } else if (commentDepth > 0) {
194
+ const comment = stripHtmlComments(line, commentDepth)
195
+ text = comment.text
196
+ commentDepth = comment.depth
197
+ comments = comment.comments
198
+ } else {
199
+ const open = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line)
200
+ if (open && !(open[1][0] === '`' && open[2].includes('`'))) {
201
+ fence = { char: open[1][0], length: open[1].length }
202
+ active = false
203
+ indented = false
204
+ } else if (blank) {
205
+ indented = false
206
+ } else if (indentWidth(line) >= 4 && (previousBlank || indented)) {
207
+ active = false
208
+ indented = true
209
+ } else {
210
+ indented = false
211
+ const comment = stripHtmlComments(stripInlineCode(line))
212
+ text = comment.text
213
+ commentDepth = comment.depth
214
+ comments = comment.comments
215
+ }
216
+ }
217
+ previousBlank = blank
218
+ lines.push({ raw: line, text: active ? text : '', active, comments })
219
+ }
220
+ return lines
221
+ }
222
+
223
+ function append(current, body) {
224
+ const separator = current.endsWith('\n\n') ? '' : current.endsWith('\n') ? '\n' : '\n\n'
225
+ return `${current}${separator}${body.trim()}\n`
226
+ }
227
+
228
+ function hasHeading(current, title) {
229
+ const lines = markdownView(current)
230
+ for (let i = 0; i < lines.length; i++) {
231
+ const line = lines[i]
232
+ if (!line.active) continue
233
+ const atx = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/.exec(line.text)
234
+ if (atx) {
235
+ const content = (atx[2] ?? '').replace(/[ \t]+#+[ \t]*$/, '').trim()
236
+ if (atx[1].length === 2 && content === title) return true
237
+ continue
238
+ }
239
+ const next = lines[i + 1]
240
+ if (line.text.trim() === title && next && next.active && /^ {0,3}-+[ \t]*$/.test(next.text)) {
241
+ return true
242
+ }
243
+ }
244
+ return false
245
+ }
246
+
247
+ function countMarkers(lines, start, end) {
248
+ const starts = []
249
+ const ends = []
250
+ let order = 0
251
+ for (const line of lines) {
252
+ for (const comment of line.comments) {
253
+ if (comment === start) starts.push(order)
254
+ else if (comment === end) ends.push(order)
255
+ order++
256
+ }
257
+ }
258
+ return { starts, ends }
259
+ }
260
+
261
+ function preserveOrAdd(current, title, id, body) {
262
+ const start = `<!-- conventions:${id}:start -->`
263
+ const end = `<!-- conventions:${id}:end -->`
264
+ const { starts, ends } = countMarkers(markdownView(current), start, end)
265
+ if (starts.length !== ends.length || starts.length > 1 || (starts.length && starts[0] > ends[0])) {
266
+ throw new Error(`malformed ${title} section markers; preserve and repair existing guidance first`)
267
+ }
268
+ if (starts.length || hasHeading(current, title)) return current
269
+ return append(current, `${start}\n${body}\n${end}`)
270
+ }
271
+
272
+ function linkFrom(from, to) {
273
+ return path.relative(path.dirname(from.target), to.file).split(path.sep).join('/')
274
+ }
275
+
276
+ function hasLoader(current, from, to) {
277
+ const text = markdownView(current).map(line => line.text).join('\n')
278
+ const links = /\[[^\]\r\n]*\]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^"'\r\n]*["'])?\s*\)/g
279
+ for (const match of text.matchAll(links)) {
280
+ const href = match[1] ?? match[2]
281
+ if (!href || /^[a-z][a-z\d+.-]*:|^\/\//i.test(href)) continue
282
+ let target
283
+ try {
284
+ const link = new URL(href, pathToFileURL(from.target))
285
+ if (link.hash && decodeURIComponent(link.hash) !== '#android-development') continue
286
+ link.hash = ''
287
+ link.search = ''
288
+ target = fileURLToPath(link)
289
+ } catch {
290
+ continue
291
+ }
292
+ if (fs.existsSync(target)) target = fs.realpathSync(target)
293
+ if (target === to.target) return true
294
+ }
295
+ return false
296
+ }
297
+
298
+ function planInitialization(root) {
299
+ const claude = inspectClaude(root)
300
+ const [agents, contributing] = ['AGENTS.md', 'CONTRIBUTING.md'].map(name => resolveTarget(root, name))
301
+ if (agents.target === contributing.target) {
302
+ throw new Error('agent instructions and CONTRIBUTING.md must use distinct files')
303
+ }
304
+ if (agents.inode && contributing.inode && agents.inode.dev === contributing.inode.dev
305
+ && agents.inode.ino === contributing.inode.ino) {
306
+ throw new Error('agent instructions and CONTRIBUTING.md must use distinct files')
307
+ }
308
+
309
+ for (const entry of [agents, contributing]) {
310
+ // Reading through a link to the protected entrypoint would read CLAUDE.md.
311
+ if (claude && entry.link && sameFile(entry, claude)) {
312
+ throw new Error(`${entry.name} resolves to the protected CLAUDE.md entrypoint; initialization never reads or edits CLAUDE.md`)
313
+ }
314
+ }
315
+
316
+ const agentsEntry = readGuidance(agents)
317
+ const contributingEntry = readGuidance(contributing)
318
+
319
+ let agentText = agentsEntry.current ?? '# Agent instructions\n'
320
+ if (!hasLoader(agentText, agentsEntry, contributingEntry)) {
321
+ const heading = hasHeading(agentText, sectionTitle) ? '' : `## ${sectionTitle}\n\n`
322
+ agentText = append(agentText, `${heading}Follow [CONTRIBUTING.md](${linkFrom(agentsEntry, contributingEntry)}) for this project's Android validation ladder, device proof and required gates.`)
323
+ }
324
+
325
+ const contributingText = preserveOrAdd(contributingEntry.current ?? '# Contributing\n', sectionTitle, sectionId, sectionBody())
326
+
327
+ const plan = [
328
+ { ...agentsEntry, next: agentText },
329
+ { ...contributingEntry, next: contributingText },
330
+ ]
331
+
332
+ for (const entry of plan) {
333
+ if (entry.current === entry.next) continue
334
+ if (claude && sameFile(entry, claude)) {
335
+ throw new Error(`${entry.name} shares its file with the protected CLAUDE.md entrypoint; initialization never edits CLAUDE.md`)
336
+ }
337
+ if (entry.exists && entry.nlink > 1) {
338
+ throw new Error(`${entry.name} is hard-linked to another file; initialization will not change a file it does not exclusively own`)
339
+ }
340
+ }
341
+
342
+ return plan
343
+ }
344
+
345
+ function assertWritable(plan) {
346
+ for (const entry of plan) {
347
+ if (entry.current === entry.next) continue
348
+ const target = entry.current === null ? path.dirname(entry.target) : entry.target
349
+ try {
350
+ fs.accessSync(target, fs.constants.W_OK)
351
+ } catch {
352
+ throw new Error(`${entry.name} is not writable; nothing was changed`)
353
+ }
354
+ }
355
+ }
356
+
357
+ function main(argv) {
358
+ const options = parseArgs(argv)
359
+ if (options.help) {
360
+ console.log(usage)
361
+ return
362
+ }
363
+ const { root } = selectRoot(options)
364
+ const plan = planInitialization(root)
365
+ if (options.write) assertWritable(plan)
366
+ console.log(`android-development init: ${options.write ? 'write' : 'preview only'} (${root})`)
367
+ for (const entry of plan) {
368
+ const action = entry.current === entry.next ? 'unchanged' : entry.current === null ? 'create' : 'update'
369
+ if (options.write && action !== 'unchanged') fs.writeFileSync(entry.target, entry.next)
370
+ console.log(` ${entry.name}: ${!options.write && action !== 'unchanged' ? 'would ' : ''}${action}`)
371
+ }
372
+ console.log('Existing sections are preserved; initialization does not refresh project policy, install tools, provision devices or authorize delivery actions.')
373
+ }
374
+
375
+ try {
376
+ main(process.argv.slice(2))
377
+ } catch (error) {
378
+ console.error(`android-development init: ${error.message}`)
379
+ process.exitCode = 2
380
+ }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: app-debug
3
3
  description: "Diagnose runtime failures in deployed Cloud Run services — 5xx/422 errors, tracebacks, Next.js SSR/hydration or Server Action failures, DB/Secret Manager connection errors, cold-start timeouts, container OOM. NOT for build or deploy failures (cloud-debug)."
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # App Debug
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: better-auth
3
3
  description: "Add or debug Better Auth for Next.js App Router: login, sessions, protected routes, middleware, OAuth (including Feishu/Lark), cookies, Prisma, and Auth.js migration."
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Better Auth for Next.js
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: data-catalog
3
3
  description: "Column definitions for warehouse tables, written inside the Spark/PySpark or SQL script that builds each table (CATALOG_TABLE / CATALOG_FIELDS, or a `-- @field` comment block), with a generated markdown catalog in docs/catalog/ and a CI check that fails a PR whose catalog is stale. Use when writing or changing a table script that adds, renames, removes, or changes the meaning of an output column or feature; when asked what a warehouse column or feature means; to set a repo up ('init the data catalog', 'generate the data dictionary', '字段字典', 'add catalog CI'); or to audit existing SQL or Spark scripts for undocumented columns. NOT for implementing or tuning the Spark job itself (pyspark), DAG wiring (airflow), or general workflow YAML (github-actions)."
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Data catalog
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: data-quality
3
3
  description: "Check a published warehouse table's data against its data-catalog definitions with read-only SELECTs: columns never filled or stuck at one value, counts below zero, ratios outside 0–1, declared values that never appear and undeclared values that do. Then classify each finding as a wrong definition, wrong code, or wrong data. Use when asked whether a table's data is right, to profile or sanity-check a dwd/ads table, to find dead or constant columns, to check a column's values against its documented list, after a pipeline change ships, or when a data-catalog definition and the data might disagree. NOT for validating a Spark change before it ships (pyspark canary and reconciliation), writing definitions (data-catalog), or monitoring dashboards."
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Data quality
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ios-foundation
3
- description: "Set up and prove an Expo / React Native native iOS workspace from an assigned or fresh isolated workspace to a manual Login smoke on an owned iOS Simulator: isolated workspace and private caches, Xcode/runtime/CocoaPods preflight, frozen install with `pod install --deployment`, platform-scoped prebuild, an ad-hoc-signed Debug simulator build, a private simulator by exact UDID, Metro on a free port, a disposable PostgreSQL backend with owned cleanup, and the evidence record (setup proof only; feature work reports the smoke result). Use for 'set up iOS', 'build the iOS app', 'run the app on the iOS simulator', 'add iOS to the React Native app', 'prove the iOS build', or when a pod install, xcodebuild, simulator boot, Metro connection or Keychain session fails. NOT for Android (android-foundation), signing, TestFlight or App Store release, or general workflow YAML (github-actions)."
4
- release-version: 0.45.2
3
+ description: "Set up and prove an Expo / React Native native iOS workspace from an assigned or fresh isolated workspace to a manual Login smoke on an owned iOS Simulator: isolated workspace and private caches, Xcode/runtime/CocoaPods preflight, frozen install with `pod install --deployment`, platform-scoped prebuild, an ad-hoc-signed Debug simulator build, a private simulator by exact UDID, Metro on a free port, a disposable PostgreSQL backend with owned cleanup, and the evidence record (setup proof only; feature work reports the smoke result). Use for 'set up iOS', 'build the iOS app', 'run the app on the iOS simulator', 'add iOS to the React Native app', 'prove the iOS build', or when a pod install, xcodebuild, simulator boot, Metro connection or Keychain session fails. NOT for Android (android-development), signing, TestFlight or App Store release, or general workflow YAML (github-actions)."
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # iOS foundation
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: monorepo
3
3
  description: Turborepo architecture for shared packages, pnpm workspaces, DAL boundaries, and pipelines.
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Turborepo Monorepo Architecture
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: nextjs-fullstack
3
3
  description: "Build or debug Next.js App Router features with React 19 — pages/layouts/route handlers, Server/Client Components, Server Actions, forms, middleware, DAL, strict TypeScript, Zod — plus our conventions: nuqs URL state, SWR cache, Skeleton/Progress + Sonner feedback, drag-and-drop upload, the portal app shell, list pages as filter/sort/paginate tables. shadcn component mechanics: see the shadcn skill."
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Next.js 16 Fullstack Development
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: prisma-setup
3
3
  description: Use when adding or debugging Prisma with PostgreSQL, Next.js, or Turborepo, including schema.prisma, prisma.config.ts, client generation, migrations, seeds, relations, adapters, and DAL/database-layer setup. Triggers on phrases like "Prisma schema", "prisma migrate", "schema.prisma", "seed the database", "PrismaClient error", "relation issue", or "database layer".
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Prisma v7 Setup Guide
@@ -12,7 +12,7 @@ description: >
12
12
  Connect, spark-submit, Hudi commits, backfills, validate-only canaries,
13
13
  cluster reconciliation, or Spark event-log profiling — including warehouse
14
14
  job timeouts, empty Hudi writes, or EMR/Dataproc performance regressions.
15
- release-version: 0.45.2
15
+ release-version: 0.46.0
16
16
  ---
17
17
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
18
18
  # PySpark
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: python-backend
3
3
  description: "Python backend architecture for FastAPI, Tortoise ORM, Pydantic Settings, loguru, and uv. Also: uv.lock installs in CI/Docker via a proxy or mirror (`uv sync --frozen` bypasses it), slow uv installs on runners, relocking."
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Python Backend Conventions
@@ -2,7 +2,7 @@
2
2
  name: shadcn
3
3
  description: "Add or debug shadcn/ui in a React/Next.js project: components.json, shadcn init/add, presets, registries, Tailwind styling, base/radix/aria composition, component fixes, and list-index pages — where the default is the DiceUI data table with filter/sort/pagination in the URL via nuqs, not a bare shadcn Table."
4
4
  allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
5
- release-version: 0.45.2
5
+ release-version: 0.46.0
6
6
  ---
7
7
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
8
8
  # shadcn/ui
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: zod-v4
3
3
  description: Zod v4 schema conventions and v3 migration for errors, validators, objects, enums, records, and async parsing.
4
- release-version: 0.45.2
4
+ release-version: 0.46.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Zod v3 to v4 Migration