@1aboveio/skills 1.0.0 → 1.0.1
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/package.json +2 -2
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/android-foundation/SKILL.md +1 -1
- 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 +9 -6
- package/skills/app-dev/data-catalog/references/data-catalog-conventions.md +3 -2
- package/skills/app-dev/data-catalog/references/field-definitions.md +13 -6
- package/skills/app-dev/data-catalog/references/format.md +15 -6
- package/skills/app-dev/data-catalog/scripts/data_catalog.py +72 -29
- package/skills/app-dev/data-catalog/scripts/test_data_catalog.py +43 -11
- package/skills/app-dev/ios-foundation/SKILL.md +1 -1
- 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/polish-cn/SKILL.md +1 -1
- package/skills/core/polish-cn/references/models.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/engineering/better-review/SKILL.md +1 -1
- package/skills/engineering/e2e-test/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +12 -12
- 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/harness-runtime/model-catalog-seed.test.mjs +4 -4
- package/skills/engineering/harness-runtime/references/model-catalog-seed.json +4 -4
- package/skills/engineering/resolve-issues/SKILL.md +1 -1
- package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +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-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/report-writing/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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1aboveio/skills",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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": {
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
]
|
|
140
140
|
}
|
|
141
141
|
],
|
|
142
|
-
"digest": "
|
|
142
|
+
"digest": "0ff8c0fb72780cd0c50dfbeb86b50015c24ea65a76541075531b52e627d221a8"
|
|
143
143
|
},
|
|
144
144
|
"publishConfig": {
|
|
145
145
|
"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.
|
|
5
|
+
release-version: 0.38.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-foundation
|
|
3
3
|
description: "Prove an Expo / React Native app on a native Android emulator, and fix it when that proof fails. Use before you fix and re-run or re-dispatch a failed Android CI job (`gh workflow run android.yml`, `gh run rerun`): a re-run is not a debugger, so reproduce the failing journey checkpoint off CI first. Also use when a UiAutomator journey or its Python harness tests fail (a CHECK/FAIL line, a wait that timed out, a control not found, adb offline, a lost Metro bridge, a fixture failing on the runner), to build the debug APK and run the device journey, to set up a fresh Android workspace (SDK/NDK/JDK, Gradle caches, private AVD, disposable PostgreSQL), or to add the emulator job to CI. NOT for workflow YAML, CI speed or runner design (github-actions), browser tests (e2e-test), iOS (ios-foundation), plain Jest component tests, or signing and store release."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.38.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Android foundation
|
|
@@ -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.38.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.38.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.38.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Data catalog
|
|
@@ -68,7 +68,8 @@ Never document tables during setup unless the task asks for it.
|
|
|
68
68
|
Example: adding `refund_cnt_30d` to the SELECT means adding its `Field` in the
|
|
69
69
|
same commit, then running `generate`.
|
|
70
70
|
2. **Each row says only what is particular to its column, in business words.**
|
|
71
|
-
Shared filters, window anchor and currency go once in the table notes
|
|
71
|
+
Shared filters, window anchor and currency go once in the table notes, in every
|
|
72
|
+
language; the row
|
|
72
73
|
gives what one value measures, its unit, and 0/null meaning; `source` gives
|
|
73
74
|
lineage in one line. Follow the eleven principles in
|
|
74
75
|
[references/field-definitions.md](references/field-definitions.md).
|
|
@@ -178,16 +179,18 @@ script is clean when its columns were `unknown`.
|
|
|
178
179
|
|
|
179
180
|
## Backfill one table
|
|
180
181
|
|
|
181
|
-
1. Pick the script that builds the table from the audit.
|
|
182
|
-
|
|
182
|
+
1. Pick the script that builds the table from the audit. Backfill, migration, and
|
|
183
|
+
copy scripts that write the same table share its definitions and need none of
|
|
184
|
+
their own.
|
|
183
185
|
2. Read the SQL or DataFrame code for every output column. Reuse an existing
|
|
184
186
|
hand-written dictionary as a draft, and check every row against the code.
|
|
185
187
|
3. Add `CATALOG_TABLE` and `CATALOG_FIELDS` in contract order. Keep the literal
|
|
186
188
|
contract while you write.
|
|
187
189
|
4. Run `generate` until it passes, then derive the contract and run the repo's
|
|
188
190
|
schema tests (rule 5).
|
|
189
|
-
5. Rerun the audit for that script
|
|
190
|
-
`thin-definition
|
|
191
|
+
5. Rerun the audit for that script and fix its writing findings (`repeated-text`,
|
|
192
|
+
`code-in-description`, `long-definition`, `thin-definition`,
|
|
193
|
+
`relative-definition`), or leave one only when the text really belongs in that row.
|
|
191
194
|
6. Commit the script and pages together. Replace the old hand-written dictionary
|
|
192
195
|
with a link to the generated page.
|
|
193
196
|
|
|
@@ -7,7 +7,8 @@ table. `docs/catalog/` is generated from those definitions; never edit it by han
|
|
|
7
7
|
|
|
8
8
|
**Where definitions live**
|
|
9
9
|
|
|
10
|
-
- Python: `CATALOG_TABLE = Table("schema.table",
|
|
10
|
+
- Python: `CATALOG_TABLE = Table("schema.table", <language>=..., grain_<language>=...,
|
|
11
|
+
notes_<language>=(...))` (grain and notes in every language) and
|
|
11
12
|
`CATALOG_FIELDS = (Field("column", <language>=..., source=..., window=..., unit=...), ...)`,
|
|
12
13
|
imported `from catalog_fields import Field, Table`. Use plain string literals.
|
|
13
14
|
- SQL: a comment block above the first statement: `-- @table schema.table`, then one
|
|
@@ -30,7 +31,7 @@ table. `docs/catalog/` is generated from those definitions; never edit it by han
|
|
|
30
31
|
|
|
31
32
|
1. Say each fact once, at the widest level where it is true. Filters, the window
|
|
32
33
|
anchor, currency, time zone, terms many columns use ("personal", "platform") and
|
|
33
|
-
list cleaning rules go in the table's
|
|
34
|
+
list cleaning rules go in the table's grain or notes; a row says only what
|
|
34
35
|
differs for that column.
|
|
35
36
|
2. The description is meaning in business words: what one value measures, its unit
|
|
36
37
|
and form (a ratio says 0–1 or percent; a time says date or timestamp and its
|
|
@@ -8,7 +8,7 @@ column that says only what is particular to that column.
|
|
|
8
8
|
|
|
9
9
|
1. **Say each fact once, at the widest level where it is true.** A filter, window
|
|
10
10
|
anchor, currency rule, or row-inclusion rule that holds for every column goes in
|
|
11
|
-
the table's
|
|
11
|
+
the table's grain or notes, never in each row. So does the meaning of a term
|
|
12
12
|
many columns use ("personal", "platform", "own name") and how lists are cleaned
|
|
13
13
|
(case, sorting, empty items). A row carries only what differs for that column.
|
|
14
14
|
Bad (every row): `"Posted collections only, test customers excluded, [dt-7d, dt), excluding dt; ..."`
|
|
@@ -66,10 +66,10 @@ column that says only what is particular to that column.
|
|
|
66
66
|
|
|
67
67
|
| Fact | Goes in |
|
|
68
68
|
|---|---|
|
|
69
|
-
| What one row is, which rows exist | `Table
|
|
69
|
+
| What one row is, which rows exist | `Table` `grain_<lang>` |
|
|
70
70
|
| What the table is for, its main source | `Table` language text |
|
|
71
|
-
| Filters, window anchor, currency, time zone shared by all columns | `Table
|
|
72
|
-
| Meaning of a term many columns use, list cleaning rules | `Table
|
|
71
|
+
| Filters, window anchor, currency, time zone shared by all columns | `Table` `notes_<lang>` |
|
|
72
|
+
| Meaning of a term many columns use, list cleaning rules | `Table` `notes_<lang>` |
|
|
73
73
|
| What this column measures, its unit in words, 0/null meaning | `Field` language text |
|
|
74
74
|
| Upstream column, aggregation, filters only this column adds | `Field.source` |
|
|
75
75
|
| Exact interval / unit code | `Field.window` / `Field.unit` |
|
|
@@ -81,15 +81,22 @@ Table:
|
|
|
81
81
|
```python
|
|
82
82
|
CATALOG_TABLE = Table(
|
|
83
83
|
"dwd.customer_daily",
|
|
84
|
-
grain="One row per (dt, cust_id) for each customer with a posted collection before dt.",
|
|
85
84
|
en="Daily customer collection metrics for risk features.",
|
|
86
85
|
zh="客户每日收款指标,供风险特征使用。",
|
|
87
|
-
|
|
86
|
+
grain_en="One row per (dt, cust_id) for each customer with a posted collection before dt.",
|
|
87
|
+
grain_zh="每个 (dt, cust_id) 一行;只有 dt 之前有已入账收款的客户才有行。",
|
|
88
|
+
notes_en=(
|
|
88
89
|
"All metrics count posted collections (status 'POSTED') from non-test customers.",
|
|
89
90
|
"Windows end the day before dt: 'last 7 days' is [dt-7d, dt).",
|
|
90
91
|
"Amounts are converted to USD at the transaction date's rate; collections with no rate are left out of every metric.",
|
|
91
92
|
"Every metric is 0, not empty, if the customer had no collections in its window.",
|
|
92
93
|
),
|
|
94
|
+
notes_zh=(
|
|
95
|
+
"所有指标只计非测试客户的已入账(status 为 'POSTED')收款。",
|
|
96
|
+
"窗口截至 dt 前一天:「近 7 天」指 [dt-7d, dt),含 dt-7d,不含 dt。",
|
|
97
|
+
"金额按交易日汇率换算为美元;若某笔收款当天没有汇率,则所有指标都不计这笔收款。",
|
|
98
|
+
"若客户在窗口内没有收款,则指标为 0,而不是空值。",
|
|
99
|
+
),
|
|
93
100
|
)
|
|
94
101
|
```
|
|
95
102
|
|
|
@@ -7,13 +7,18 @@ from catalog_fields import Field, Table
|
|
|
7
7
|
|
|
8
8
|
CATALOG_TABLE = Table(
|
|
9
9
|
"dwd.customer_daily",
|
|
10
|
-
grain="One row per (dt, cust_id) for each customer with a posted transaction that day.",
|
|
11
10
|
en="Daily customer collection rollup used by risk features.",
|
|
12
11
|
zh="客户每日收款汇总,供风险特征使用。",
|
|
13
|
-
|
|
12
|
+
grain_en="One row per (dt, cust_id) for each customer with a posted transaction that day.",
|
|
13
|
+
grain_zh="每个 (dt, cust_id) 一行;只有当天有已入账交易的客户才有行。",
|
|
14
|
+
notes_en=(
|
|
14
15
|
"All metrics count posted collections (status 'POSTED').",
|
|
15
16
|
"Amounts are converted to USD at the transaction date's rate.",
|
|
16
17
|
),
|
|
18
|
+
notes_zh=(
|
|
19
|
+
"所有指标只计已入账(status 为 'POSTED')的收款。",
|
|
20
|
+
"金额按交易日汇率换算为美元。",
|
|
21
|
+
),
|
|
17
22
|
)
|
|
18
23
|
|
|
19
24
|
KEYS = (
|
|
@@ -37,7 +42,9 @@ PUBLISHED_COLUMNS = tuple(f.name for f in CATALOG_FIELDS)
|
|
|
37
42
|
```
|
|
38
43
|
|
|
39
44
|
- `Field(name, <one keyword per language>, source, window=None, unit=None)`.
|
|
40
|
-
- `Table(name,
|
|
45
|
+
- `Table(name, <one keyword per language>, grain_<lang>=..., notes_<lang>=(...))`: a
|
|
46
|
+
grain for every language, and the same number of notes in every language (notes are
|
|
47
|
+
optional).
|
|
41
48
|
- Pass only the name positionally; everything else by keyword.
|
|
42
49
|
- The language keywords come from `languages` in `data-catalog.json`. `init`
|
|
43
50
|
regenerates `catalog_fields.py` with exactly those, so a missing language is a
|
|
@@ -49,10 +56,12 @@ PUBLISHED_COLUMNS = tuple(f.name for f in CATALOG_FIELDS)
|
|
|
49
56
|
|
|
50
57
|
```sql
|
|
51
58
|
-- @table ads.merchant_monthly
|
|
52
|
-
--
|
|
59
|
+
-- grain_en: One row per (month, merchant_id).
|
|
60
|
+
-- grain_zh: 每个 (month, merchant_id) 一行。
|
|
53
61
|
-- en: Monthly settled volume per merchant.
|
|
54
62
|
-- zh: 商户月度结算金额。
|
|
55
|
-
--
|
|
63
|
+
-- notes_en: Built on the 1st of each month; Counts settled rows (status 'SETTLED') only; Excludes test merchants
|
|
64
|
+
-- notes_zh: 每月 1 日构建;只计已结算(status 为 'SETTLED')的记录;不含测试商户
|
|
56
65
|
-- @field merchant_id
|
|
57
66
|
-- en: Merchant that settled the volume; key of dim_merchant.
|
|
58
67
|
-- zh: 结算该金额的商户,dim_merchant 主键。
|
|
@@ -70,7 +79,7 @@ SELECT ...
|
|
|
70
79
|
- A directive line is `-- @table <name>` or `-- @field <name>`.
|
|
71
80
|
- A key line is exactly two spaces after `-- `, then `key: value`.
|
|
72
81
|
- A continuation line has four or more spaces and joins the previous key with a space.
|
|
73
|
-
- `
|
|
82
|
+
- `notes_<lang>` on `@table` splits on `;` or `;`.
|
|
74
83
|
- The block ends at the first non-comment line. One `@table` per file.
|
|
75
84
|
|
|
76
85
|
## data-catalog.json
|
|
@@ -157,9 +157,9 @@ class FieldDoc:
|
|
|
157
157
|
class TableDoc:
|
|
158
158
|
name: str
|
|
159
159
|
script: str
|
|
160
|
-
grain:
|
|
160
|
+
grain: dict # language -> what one row is
|
|
161
161
|
text: dict
|
|
162
|
-
notes:
|
|
162
|
+
notes: dict # language -> [shared rules]
|
|
163
163
|
fields: list
|
|
164
164
|
line: int
|
|
165
165
|
contracts: dict = field(default_factory=dict) # name -> list[str] | "derived" | "unchecked"
|
|
@@ -263,21 +263,54 @@ def build_field(values: dict, languages: list, where: str, line: int) -> FieldDo
|
|
|
263
263
|
|
|
264
264
|
def build_table(values: dict, fields: list, languages: list, where: str, line: int, script: str) -> TableDoc:
|
|
265
265
|
name = require_text(values, "name", where, line, "Table")
|
|
266
|
-
|
|
266
|
+
if "grain" in values or "notes" in values:
|
|
267
|
+
keys = ", ".join(f"grain_{lang}=..., notes_{lang}=(...)" for lang in languages)
|
|
268
|
+
raise CatalogError(f"{where}:{line}: Table {name!r}: grain and notes are written per language: {keys}")
|
|
269
|
+
known = {"name", *languages, *(f"grain_{lang}" for lang in languages), *(f"notes_{lang}" for lang in languages)}
|
|
267
270
|
unknown = sorted(set(values) - known)
|
|
268
271
|
if unknown:
|
|
269
272
|
raise CatalogError(f"{where}:{line}: Table {name!r} has unknown argument(s) {unknown}")
|
|
270
|
-
grain = require_text(values, "grain", where, line, f"Table {name!r}")
|
|
271
273
|
text = {lang: require_text(values, lang, where, line, f"Table {name!r}") for lang in languages}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
grain = {lang: require_text(values, f"grain_{lang}", where, line, f"Table {name!r}") for lang in languages}
|
|
275
|
+
notes = {}
|
|
276
|
+
for lang in languages:
|
|
277
|
+
raw = values.get(f"notes_{lang}") or ()
|
|
278
|
+
raw = (raw,) if isinstance(raw, str) else raw
|
|
279
|
+
notes[lang] = [n.strip() for n in raw if n and n.strip()]
|
|
280
|
+
counts = {lang: len(n) for lang, n in notes.items()}
|
|
281
|
+
if len(set(counts.values())) > 1:
|
|
282
|
+
raise CatalogError(f"{where}:{line}: Table {name!r}: every language needs the same notes, got {counts}")
|
|
283
|
+
return TableDoc(name, script, grain, text, notes, fields, line)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def struct_fields(node: ast.AST, names: dict):
|
|
287
|
+
"""Column names of a literal StructType([StructField("name", ...), ...]), or None."""
|
|
288
|
+
if call_name(node) != "StructType" or not node.args:
|
|
289
|
+
return None
|
|
290
|
+
try:
|
|
291
|
+
items = resolve_sequence(node.args[0], names, "", frozenset())
|
|
292
|
+
except CatalogError:
|
|
293
|
+
return None
|
|
294
|
+
out = []
|
|
295
|
+
for item in items:
|
|
296
|
+
if call_name(item) != "StructField" or not item.args:
|
|
297
|
+
return None
|
|
298
|
+
try:
|
|
299
|
+
value = eval_text(item.args[0], names, "")
|
|
300
|
+
except CatalogError:
|
|
301
|
+
return None
|
|
302
|
+
if not isinstance(value, str):
|
|
303
|
+
return None
|
|
304
|
+
out.append(value)
|
|
305
|
+
return out
|
|
276
306
|
|
|
277
307
|
|
|
278
308
|
def contract_state(node: ast.AST, names: dict):
|
|
279
309
|
if any(isinstance(n, ast.Name) and n.id == FIELDS_VAR for n in ast.walk(node)):
|
|
280
310
|
return "derived"
|
|
311
|
+
columns = struct_fields(node, names)
|
|
312
|
+
if columns is not None:
|
|
313
|
+
return columns
|
|
281
314
|
try:
|
|
282
315
|
items = resolve_sequence(node, names, "", frozenset())
|
|
283
316
|
values = [eval_text(item, names, "") for item in items]
|
|
@@ -363,14 +396,12 @@ def parse_sql(text: str, where: str, languages: list) -> TableDoc | None:
|
|
|
363
396
|
fields = []
|
|
364
397
|
for kind, name, values, line, _ in entries:
|
|
365
398
|
if kind == "field":
|
|
366
|
-
if "notes" in values:
|
|
367
|
-
raise CatalogError(f"{where}:{line}: `
|
|
399
|
+
if any(k.startswith(("notes", "grain")) for k in values):
|
|
400
|
+
raise CatalogError(f"{where}:{line}: `grain_*` and `notes_*` belong on @table, not @field")
|
|
368
401
|
fields.append(build_field({"name": name, **values}, languages, where, line))
|
|
369
402
|
_, name, values, line, _ = tables[0]
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if notes:
|
|
373
|
-
values["notes"] = tuple(n.strip() for n in notes.split(";"))
|
|
403
|
+
values = {"name": name, **{k: tuple(n.strip() for n in re.split(r"[;;]", v)) if k.startswith("notes_") else v
|
|
404
|
+
for k, v in values.items()}}
|
|
374
405
|
return build_table(values, fields, languages, where, line, where)
|
|
375
406
|
|
|
376
407
|
|
|
@@ -467,6 +498,9 @@ def cell(text: str | None) -> str:
|
|
|
467
498
|
return text.replace("\\", "\\\\").replace("|", "\\|").replace("\r\n", "\n").replace("\n", "<br>")
|
|
468
499
|
|
|
469
500
|
|
|
501
|
+
SECTION_LABELS = {"en": ("Grain:", "Notes:"), "zh": ("粒度:", "说明:"), "zh_hant": ("粒度:", "說明:")}
|
|
502
|
+
|
|
503
|
+
|
|
470
504
|
def label(lang: str) -> str:
|
|
471
505
|
return LANGUAGE_LABELS.get(lang, lang)
|
|
472
506
|
|
|
@@ -496,17 +530,19 @@ def render_table(table: TableDoc, root: Path, config: dict) -> str:
|
|
|
496
530
|
langs = config["languages"]
|
|
497
531
|
out = [GENERATED_MARK, "", f"# `{table.name}`", ""]
|
|
498
532
|
out.append(f"- **Script:** [{table.script}]({link_to(root, config['output'], table.script)})")
|
|
499
|
-
out.append(f"- **Grain:** {cell(table.grain)}")
|
|
500
533
|
out.append(f"- **Fields:** {len(table.fields)}")
|
|
501
534
|
out.append("")
|
|
502
535
|
for lang in langs:
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
out.
|
|
507
|
-
|
|
508
|
-
|
|
536
|
+
grain_label, notes_label = SECTION_LABELS.get(lang, SECTION_LABELS["en"])
|
|
537
|
+
if len(langs) > 1:
|
|
538
|
+
out += [f"## {label(lang)}", ""]
|
|
539
|
+
out += [cell(table.text[lang]), "", f"- **{grain_label}** {cell(table.grain[lang])}"]
|
|
540
|
+
if table.notes[lang]:
|
|
541
|
+
out.append(f"- **{notes_label}**")
|
|
542
|
+
out.extend(f" - {cell(note)}" for note in table.notes[lang])
|
|
509
543
|
out.append("")
|
|
544
|
+
if len(langs) > 1:
|
|
545
|
+
out += ["## Fields", ""]
|
|
510
546
|
has_window = any(f.window for f in table.fields)
|
|
511
547
|
has_unit = any(f.unit for f in table.fields)
|
|
512
548
|
head = ["Field", *(label(lang) for lang in langs), "Source"]
|
|
@@ -539,7 +575,7 @@ def render_index(tables: list, root: Path, config: dict) -> str:
|
|
|
539
575
|
out += ["| Table | Summary | Grain | Fields | Script |", "|---|---|---|---|---|"]
|
|
540
576
|
for t in tables:
|
|
541
577
|
script = f"[{t.script}]({link_to(root, config['output'], t.script)})"
|
|
542
|
-
out.append(f"| [`{t.name}`]({page_name(t)}) | {cell(t.text[lang])} | {cell(t.grain)} | {len(t.fields)} | {script} |")
|
|
578
|
+
out.append(f"| [`{t.name}`]({page_name(t)}) | {cell(t.text[lang])} | {cell(t.grain[lang])} | {len(t.fields)} | {script} |")
|
|
543
579
|
return "\n".join(out) + "\n"
|
|
544
580
|
|
|
545
581
|
|
|
@@ -1019,11 +1055,13 @@ def preview(items: list, limit: int = 12) -> str:
|
|
|
1019
1055
|
return shown + (f", … (+{len(items) - limit})" if len(items) > limit else "")
|
|
1020
1056
|
|
|
1021
1057
|
|
|
1022
|
-
def cmd_audit(root: Path, globs: list, as_json: bool, verbose: bool, table_names: list, schema_names: list
|
|
1058
|
+
def cmd_audit(root: Path, globs: list, as_json: bool, verbose: bool, table_names: list, schema_names: list,
|
|
1059
|
+
contracts: list) -> int:
|
|
1023
1060
|
config_path = root / CONFIG_FILE
|
|
1024
1061
|
config = load_config(root) if config_path.exists() else dict(DEFAULT_CONFIG)
|
|
1025
1062
|
config = {**config, "tableNames": [*config["tableNames"], *table_names],
|
|
1026
|
-
"schemaNames": [*config["schemaNames"], *schema_names]
|
|
1063
|
+
"schemaNames": [*config["schemaNames"], *schema_names],
|
|
1064
|
+
"contracts": [*config["contracts"], *contracts]}
|
|
1027
1065
|
if globs:
|
|
1028
1066
|
patterns, exclude = globs, config.get("exclude", [])
|
|
1029
1067
|
elif config_path.exists():
|
|
@@ -1138,7 +1176,8 @@ def definitions(table: TableDoc | None) -> tuple[dict, tuple]:
|
|
|
1138
1176
|
if table is None:
|
|
1139
1177
|
return {}, ()
|
|
1140
1178
|
fields = {f.name: (tuple(sorted(f.text.items())), f.source, f.window, f.unit) for f in table.fields}
|
|
1141
|
-
|
|
1179
|
+
notes = tuple((lang, tuple(n)) for lang, n in sorted(table.notes.items()))
|
|
1180
|
+
return fields, (tuple(sorted(table.grain.items())), tuple(sorted(table.text.items())), notes)
|
|
1142
1181
|
|
|
1143
1182
|
|
|
1144
1183
|
def drift_items(root: Path, config: dict, base: str) -> list:
|
|
@@ -1212,12 +1251,14 @@ def cmd_drift(root: Path, base: str) -> int:
|
|
|
1212
1251
|
|
|
1213
1252
|
def render_fields_module(languages: list) -> str:
|
|
1214
1253
|
lang_fields = "\n".join(f" {lang}: str" for lang in languages)
|
|
1254
|
+
grain_fields = "\n".join(f" grain_{lang}: str" for lang in languages)
|
|
1255
|
+
notes_fields = "\n".join(f" notes_{lang}: tuple[str, ...] = ()" for lang in languages)
|
|
1215
1256
|
return f'''"""Column metadata types for the data catalog.
|
|
1216
1257
|
|
|
1217
1258
|
Generated by data_catalog.py init from data-catalog.json; rerun init after changing
|
|
1218
1259
|
`languages` there, never edit this file. Table scripts declare:
|
|
1219
1260
|
|
|
1220
|
-
CATALOG_TABLE = Table("schema.table",
|
|
1261
|
+
CATALOG_TABLE = Table("schema.table", {", ".join(f"{lang}=..., grain_{lang}=..." for lang in languages)})
|
|
1221
1262
|
{FIELDS_VAR} = (Field("column", {", ".join(f"{lang}=..." for lang in languages)}, source=...), ...)
|
|
1222
1263
|
PUBLISHED_COLUMNS = tuple(f.name for f in {FIELDS_VAR})
|
|
1223
1264
|
"""
|
|
@@ -1238,9 +1279,9 @@ class Field:
|
|
|
1238
1279
|
@dataclass(frozen=True)
|
|
1239
1280
|
class Table:
|
|
1240
1281
|
name: str
|
|
1241
|
-
grain: str
|
|
1242
1282
|
{lang_fields}
|
|
1243
|
-
|
|
1283
|
+
{grain_fields}
|
|
1284
|
+
{notes_fields}
|
|
1244
1285
|
'''
|
|
1245
1286
|
|
|
1246
1287
|
|
|
@@ -1367,6 +1408,8 @@ def main(argv: list | None = None) -> int:
|
|
|
1367
1408
|
help="extra variable/attribute whose string value is the table written; repeatable")
|
|
1368
1409
|
p_audit.add_argument("--schema-name", action="append", default=[], metavar="NAME",
|
|
1369
1410
|
help="extra variable/attribute holding that table's schema; repeatable")
|
|
1411
|
+
p_audit.add_argument("--contract", action="append", default=[], metavar="NAME",
|
|
1412
|
+
help="extra column-contract name (tuple of names or StructType); repeatable")
|
|
1370
1413
|
p_drift = sub.add_parser("drift", help="local check: code changed since a git base while its definitions did not")
|
|
1371
1414
|
p_drift.add_argument("--base", default="HEAD", help="git ref to compare with (default HEAD: uncommitted work)")
|
|
1372
1415
|
sub.add_parser("version", help="print the engine identity")
|
|
@@ -1382,7 +1425,7 @@ def main(argv: list | None = None) -> int:
|
|
|
1382
1425
|
return cmd_generate(root, args.check)
|
|
1383
1426
|
if args.command == "drift":
|
|
1384
1427
|
return cmd_drift(root, args.base)
|
|
1385
|
-
return cmd_audit(root, args.globs, args.json, args.verbose, args.table_name, args.schema_name)
|
|
1428
|
+
return cmd_audit(root, args.globs, args.json, args.verbose, args.table_name, args.schema_name, args.contract)
|
|
1386
1429
|
except CatalogError as exc:
|
|
1387
1430
|
print(f"data-catalog: {exc}", file=sys.stderr)
|
|
1388
1431
|
return 2
|
|
@@ -25,10 +25,12 @@ WINDOW_NOTE = "[dt-7d, dt)"
|
|
|
25
25
|
|
|
26
26
|
CATALOG_TABLE = Table(
|
|
27
27
|
"dwd.customer_daily",
|
|
28
|
-
grain="one row per (dt, cust_id)",
|
|
29
28
|
en="Daily customer collection rollup.",
|
|
30
29
|
zh="客户每日收款汇总。",
|
|
31
|
-
|
|
30
|
+
grain_en="one row per (dt, cust_id)",
|
|
31
|
+
grain_zh="每个 (dt, cust_id) 一行",
|
|
32
|
+
notes_en=("Amounts in USD via dim_forex.",),
|
|
33
|
+
notes_zh=("金额经 dim_forex 换算为 USD。",),
|
|
32
34
|
)
|
|
33
35
|
KEYS = (
|
|
34
36
|
Field("dt", en="Snapshot date.", zh="快照日期。", source="Run parameter `dt`."),
|
|
@@ -50,10 +52,12 @@ PUBLISHED_COLUMNS = tuple(f.name for f in CATALOG_FIELDS)
|
|
|
50
52
|
|
|
51
53
|
SQL_SCRIPT = """\
|
|
52
54
|
-- @table ads.merchant_monthly
|
|
53
|
-
-- grain: one row per (month, merchant_id)
|
|
54
55
|
-- en: Monthly merchant volume.
|
|
55
56
|
-- zh: 商户月度交易量。
|
|
56
|
-
--
|
|
57
|
+
-- grain_en: one row per (month, merchant_id)
|
|
58
|
+
-- grain_zh: 每个 (month, merchant_id) 一行
|
|
59
|
+
-- notes_en: Built monthly; Excludes test merchants
|
|
60
|
+
-- notes_zh: 每月构建;不含测试商户
|
|
57
61
|
-- @field merchant_id
|
|
58
62
|
-- en: Merchant id.
|
|
59
63
|
-- zh: 商户 ID。
|
|
@@ -111,7 +115,10 @@ class GenerateTest(unittest.TestCase):
|
|
|
111
115
|
self.assertIn("| `volume_usd` | Settled volume in the month. | 当月结算金额。 | "
|
|
112
116
|
"dwd_settlement settled rows, summed amount_usd | USD |", sql_page)
|
|
113
117
|
self.assertNotIn("Window", sql_page)
|
|
114
|
-
self.assertIn("- Excludes test merchants", sql_page)
|
|
118
|
+
self.assertIn(" - Excludes test merchants", sql_page)
|
|
119
|
+
self.assertIn("## 中文", sql_page)
|
|
120
|
+
self.assertIn("- **粒度:** 每个 (month, merchant_id) 一行", sql_page)
|
|
121
|
+
self.assertIn(" - 不含测试商户", sql_page)
|
|
115
122
|
index = (root / "docs/catalog/README.md").read_text(encoding="utf-8")
|
|
116
123
|
self.assertLess(index.index("ads.merchant_monthly"), index.index("dwd.customer_daily"))
|
|
117
124
|
self.assertIn("| [`dwd.customer_daily`](dwd.customer_daily.md) | Daily customer collection rollup. | "
|
|
@@ -192,6 +199,18 @@ SELECT dt, SUM(amt) AS coll_amt_7d_usd, COUNT(*) AS coll_cnt_7d FROM dwd.txn GRO
|
|
|
192
199
|
root = make_repo({"jobs/a.py": imported}, BILINGUAL)
|
|
193
200
|
self.assertIn("imported lists cannot be read", run(root, "generate")[2])
|
|
194
201
|
|
|
202
|
+
def test_table_grain_and_notes_are_per_language_with_matching_notes(self):
|
|
203
|
+
old = PY_SCRIPT.replace('grain_en="one row per (dt, cust_id)",', 'grain="one row per (dt, cust_id)",')
|
|
204
|
+
root = make_repo({"jobs/a.py": old}, BILINGUAL)
|
|
205
|
+
code, _, err = run(root, "generate")
|
|
206
|
+
self.assertEqual(code, 1)
|
|
207
|
+
self.assertIn("grain and notes are written per language: grain_en=..., notes_en=(...), grain_zh=...", err)
|
|
208
|
+
uneven = PY_SCRIPT.replace('notes_zh=("金额经 dim_forex 换算为 USD。",),', 'notes_zh=(),')
|
|
209
|
+
root = make_repo({"jobs/a.py": uneven}, BILINGUAL)
|
|
210
|
+
code, _, err = run(root, "generate")
|
|
211
|
+
self.assertEqual(code, 1)
|
|
212
|
+
self.assertIn("every language needs the same notes, got {'en': 1, 'zh': 0}", err)
|
|
213
|
+
|
|
195
214
|
def test_same_table_in_two_scripts_is_an_error(self):
|
|
196
215
|
root = make_repo({"jobs/a.py": PY_SCRIPT, "jobs/b.py": PY_SCRIPT}, BILINGUAL)
|
|
197
216
|
code, _, err = run(root, "generate")
|
|
@@ -238,6 +257,18 @@ class AuditTest(unittest.TestCase):
|
|
|
238
257
|
"writes dwd.undocumented but carries no CATALOG_TABLE/CATALOG_FIELDS (or -- @table block); "
|
|
239
258
|
"also written by jobs/backfill_b.py (put the metadata in the script that builds the table)")
|
|
240
259
|
|
|
260
|
+
def test_a_structtype_contract_is_read_for_its_column_names(self):
|
|
261
|
+
script = '''
|
|
262
|
+
L1A_PUBLISHED_SCHEMA = StructType([
|
|
263
|
+
StructField("dt", DateType(), False),
|
|
264
|
+
StructField("cust_id", StringType(), False),
|
|
265
|
+
])
|
|
266
|
+
TARGET_TABLE = "dwd.flow"
|
|
267
|
+
'''
|
|
268
|
+
root = make_repo({"jobs/flow.py": script})
|
|
269
|
+
[entry] = json.loads(run(root, "audit", "--json", "--contract", "L1A_PUBLISHED_SCHEMA", "jobs/*.py")[1])["scripts"]
|
|
270
|
+
self.assertEqual((entry["columns"], entry["columnsFrom"]), (["dt", "cust_id"], "L1A_PUBLISHED_SCHEMA"))
|
|
271
|
+
|
|
241
272
|
def test_class_attributes_name_the_table_with_their_schema(self):
|
|
242
273
|
script = '''
|
|
243
274
|
class OrdersEtl(Etl):
|
|
@@ -311,7 +342,7 @@ class AuditTest(unittest.TestCase):
|
|
|
311
342
|
("c_amt", " ".join(["word"] * 31), "近 7 天收款总额。", "sum of amount"),
|
|
312
343
|
])
|
|
313
344
|
script = f'''
|
|
314
|
-
CATALOG_TABLE = Table("dwd.t",
|
|
345
|
+
CATALOG_TABLE = Table("dwd.t", en="Table.", zh="表。", grain_en="One row per id.", grain_zh="每个 id 一行。")
|
|
315
346
|
CATALOG_FIELDS = (
|
|
316
347
|
{rows}
|
|
317
348
|
)
|
|
@@ -329,8 +360,9 @@ CATALOG_FIELDS = (
|
|
|
329
360
|
|
|
330
361
|
def test_shared_vocabulary_and_plain_units_are_not_flagged(self):
|
|
331
362
|
script = '''
|
|
332
|
-
CATALOG_TABLE = Table("dwd.t",
|
|
333
|
-
|
|
363
|
+
CATALOG_TABLE = Table("dwd.t", en="Table.", zh="表。", grain_en="One row per id.", grain_zh="每个 id 一行。",
|
|
364
|
+
notes_en=("All metrics count posted collections.", "Every metric is 0 if there were no collections."),
|
|
365
|
+
notes_zh=("所有指标只计已入账收款。", "若窗口内无收款,则指标为 0。"))
|
|
334
366
|
CATALOG_FIELDS = (
|
|
335
367
|
Field("a", en="Number of collections in the last 7 days.", zh="近 7 天收款笔数。",
|
|
336
368
|
source="count of dwd.transaction rows", window="[dt-7d, dt)"),
|
|
@@ -390,9 +422,9 @@ class ReviewFixTest(unittest.TestCase):
|
|
|
390
422
|
self.assertIn("no column contract and no readable SELECT writing ads.merchant_monthly", err)
|
|
391
423
|
|
|
392
424
|
def test_tables_mapping_to_one_page_or_the_index_are_errors(self):
|
|
393
|
-
a = PY_SCRIPT.replace('"dwd.customer_daily",\n
|
|
394
|
-
b = PY_SCRIPT.replace('"dwd.customer_daily",\n
|
|
395
|
-
readme = PY_SCRIPT.replace('"dwd.customer_daily",\n
|
|
425
|
+
a = PY_SCRIPT.replace('"dwd.customer_daily",\n en=', '"ads.a_b",\n en=')
|
|
426
|
+
b = PY_SCRIPT.replace('"dwd.customer_daily",\n en=', '"ads.a b",\n en=')
|
|
427
|
+
readme = PY_SCRIPT.replace('"dwd.customer_daily",\n en=', '"README",\n en=')
|
|
396
428
|
root = make_repo({"jobs/a.py": a, "jobs/b.py": b, "jobs/c.py": readme}, BILINGUAL)
|
|
397
429
|
code, _, err = run(root, "generate")
|
|
398
430
|
self.assertEqual(code, 1)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ios-foundation
|
|
3
3
|
description: "Set up and prove an Expo / React Native native iOS workspace from a clean checkout to a manual Login smoke on an owned iOS Simulator: isolated worktree 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.
|
|
4
|
+
release-version: 0.38.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.38.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.38.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.38.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.38.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.38.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.38.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.38.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Zod v3 to v4 Migration
|