@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.
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/resources.json +145 -142
- package/README.md +1 -1
- package/package.json +5 -5
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/android-development/SKILL.md +126 -0
- package/skills/app-dev/android-development/references/android-conventions.md +117 -0
- package/skills/app-dev/{android-foundation → android-development}/references/ci.md +10 -6
- package/skills/app-dev/{android-foundation → android-development}/references/diagnosis.md +39 -15
- package/skills/app-dev/android-development/references/feature-work.md +171 -0
- package/skills/app-dev/android-development/references/framework-choice.md +88 -0
- package/skills/app-dev/{android-foundation → android-development}/references/gotchas.md +43 -6
- package/skills/app-dev/android-development/references/init.md +104 -0
- package/skills/app-dev/android-development/references/setup.md +389 -0
- package/skills/app-dev/android-development/references/workspace.md +156 -0
- package/skills/app-dev/android-development/scripts/init.mjs +380 -0
- package/skills/app-dev/app-debug/SKILL.md +1 -1
- package/skills/app-dev/better-auth/SKILL.md +1 -1
- package/skills/app-dev/data-catalog/SKILL.md +1 -1
- package/skills/app-dev/data-quality/SKILL.md +1 -1
- package/skills/app-dev/ios-foundation/SKILL.md +2 -2
- package/skills/app-dev/monorepo/SKILL.md +1 -1
- package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
- package/skills/app-dev/prisma-setup/SKILL.md +1 -1
- package/skills/app-dev/pyspark/SKILL.md +1 -1
- package/skills/app-dev/python-backend/SKILL.md +1 -1
- package/skills/app-dev/shadcn/SKILL.md +1 -1
- package/skills/app-dev/zod-v4/SKILL.md +1 -1
- package/skills/cicd/cloud-build/SKILL.md +1 -1
- package/skills/cicd/cloud-debug/SKILL.md +1 -1
- package/skills/cicd/cloud-deploy/SKILL.md +1 -1
- package/skills/cicd/docker/SKILL.md +1 -1
- package/skills/cicd/github-actions/SKILL.md +1 -1
- package/skills/cicd/github-flow/SKILL.md +1 -1
- package/skills/cicd/google-cloud/SKILL.md +1 -1
- package/skills/cicd/mergify/SKILL.md +1 -1
- package/skills/cicd/podman/SKILL.md +1 -1
- package/skills/content/article-review/SKILL.md +1 -1
- package/skills/content/article-to-image/SKILL.md +1 -1
- package/skills/content/flowchart-svg/SKILL.md +1 -1
- package/skills/content/image-gen/SKILL.md +1 -1
- package/skills/content/mermaid/SKILL.md +1 -1
- package/skills/content/wechat-article-writer/SKILL.md +1 -1
- package/skills/content/wechat-publisher/SKILL.md +1 -1
- package/skills/content/wechat-search/SKILL.md +1 -1
- package/skills/content/xai-search/SKILL.md +1 -1
- package/skills/core/distill-writing/SKILL.md +1 -1
- package/skills/core/doc-to-md/SKILL.md +1 -1
- package/skills/core/editorial-review/SKILL.md +1 -1
- package/skills/core/house-style/SKILL.md +1 -1
- package/skills/core/plain-language/SKILL.md +1 -1
- package/skills/core/polish-cn/SKILL.md +1 -1
- package/skills/core/reg-parser/SKILL.md +1 -1
- package/skills/core/terminology/SKILL.md +1 -1
- package/skills/core/write-doc/SKILL.md +1 -1
- package/skills/core/write-report/SKILL.md +1 -1
- package/skills/engineering/better-review/SKILL.md +1 -1
- package/skills/engineering/e2e-test/SKILL.md +1 -1
- package/skills/engineering/engineering/SKILL.md +9 -3
- package/skills/engineering/engineering/references/init-scope.md +7 -3
- package/skills/engineering/engineering/references/scope-management.md +9 -0
- package/skills/engineering/engineering/scripts/init.mjs +40 -17
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
- package/skills/engineering/ensure-coverage/SKILL.md +1 -1
- package/skills/engineering/golden-path/SKILL.md +1 -1
- package/skills/engineering/harness-runtime/SKILL.md +1 -1
- package/skills/engineering/resolve-issues/SKILL.md +1 -1
- package/skills/engineering/resolve-issues/references/exploration.md +1 -1
- package/skills/engineering/resolve-release/SKILL.md +1 -1
- package/skills/engineering/rush-issues/SKILL.md +1 -1
- package/skills/engineering/rush-release/SKILL.md +1 -1
- package/skills/engineering/test-quality/SKILL.md +1 -1
- package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
- package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
- package/skills/risk-compliance/contract-review/SKILL.md +1 -1
- package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
- package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
- package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
- package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
- package/skills/app-dev/android-foundation/SKILL.md +0 -248
- package/skills/app-dev/android-foundation/references/delivery-focus.md +0 -31
- package/skills/app-dev/android-foundation/references/feature-work.md +0 -50
- package/skills/app-dev/android-foundation/references/setup.md +0 -188
- package/skills/app-dev/android-foundation/references/workspace.md +0 -81
- /package/skills/app-dev/{android-foundation → android-development}/LICENSE +0 -0
- /package/skills/app-dev/{android-foundation → android-development}/assets/android-ci-job.yml +0 -0
- /package/skills/app-dev/{android-foundation → android-development}/release-check.mjs +0 -0
- /package/skills/app-dev/{android-foundation → android-development}/scripts/device-run.sh +0 -0
- /package/skills/app-dev/{android-foundation → android-development}/scripts/workspace-runtime/bin/workspaces.mjs +0 -0
- /package/skills/app-dev/{android-foundation → android-development}/scripts/workspace-runtime/native-workspace.mjs +0 -0
- /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.
|
|
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.
|
|
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.
|
|
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.
|
|
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-
|
|
4
|
-
release-version: 0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|