@1aboveio/skills 1.0.1 → 1.0.2
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/README.md +24 -7
- package/bin/select-core.mjs +182 -50
- package/extensions/hide-copies.ts +21 -0
- package/package.json +115 -3
- 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 +2 -1
- package/skills/app-dev/data-catalog/references/data-catalog-conventions.md +2 -1
- package/skills/app-dev/data-catalog/references/field-definitions.md +2 -1
- package/skills/app-dev/data-catalog/references/format.md +6 -2
- package/skills/app-dev/data-catalog/scripts/data_catalog.py +15 -3
- package/skills/app-dev/data-catalog/scripts/test_data_catalog.py +15 -0
- package/skills/app-dev/data-quality/LICENSE +3 -0
- package/skills/app-dev/data-quality/SKILL.md +96 -0
- package/skills/app-dev/data-quality/agents/openai.yaml +3 -0
- package/skills/app-dev/data-quality/references/checks.md +56 -0
- package/skills/app-dev/data-quality/release-check.mjs +93 -0
- package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +12 -0
- package/skills/app-dev/data-quality/scripts/data_quality.py +405 -0
- package/skills/app-dev/data-quality/scripts/test_data_quality.py +277 -0
- 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/plain-language/LICENSE +3 -0
- package/skills/core/plain-language/SKILL.md +57 -0
- package/skills/core/plain-language/release-check.mjs +93 -0
- 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/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/resolve-issues/SKILL.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-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/README.md
CHANGED
|
@@ -6,14 +6,20 @@ Every 1aboveio skill package in one: install once, then choose which packages Pi
|
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
pi install npm:@1aboveio/skills
|
|
9
|
-
|
|
9
|
+
pi config # turn single skills on or off
|
|
10
|
+
npx @1aboveio/skills # or choose whole packages interactively
|
|
10
11
|
npx @1aboveio/skills engineering cicd # or name them
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
(
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Pi loads every package after install. `pi config` toggles single skills. The select command
|
|
15
|
+
writes a Pi package filter (`skills/<package>/**`) to `~/.pi/agent/settings.json` (`--local`:
|
|
16
|
+
the project's `.pi/settings.json`), adds core when a chosen package uses it, and installs the
|
|
17
|
+
third-party sources you pick.
|
|
18
|
+
|
|
19
|
+
At startup the package's extension hides from Pi the Skills CLI copies in `~/.agents/skills` of
|
|
20
|
+
the skills it loads (Pi ranks package skills last, so those copies would win). It writes
|
|
21
|
+
`-<path>` entries to Pi's personal settings and deletes nothing: Claude Code and Codex keep
|
|
22
|
+
using the copies. Run `/reload` after the first start.
|
|
17
23
|
|
|
18
24
|
Each package also publishes on its own; install either this bundle or the packages, not both.
|
|
19
25
|
|
|
@@ -21,11 +27,22 @@ Each package also publishes on its own; install either this bundle or the packag
|
|
|
21
27
|
|
|
22
28
|
| Package | Also published as | Skills |
|
|
23
29
|
|---|---|---|
|
|
24
|
-
| `core` | `@1aboveio/core-skills` | distill-writing, doc-to-md, editorial-review, house-style, polish-cn, reg-parser, terminology, write-doc |
|
|
30
|
+
| `core` | `@1aboveio/core-skills` | distill-writing, doc-to-md, editorial-review, house-style, plain-language, polish-cn, reg-parser, terminology, write-doc |
|
|
25
31
|
| `engineering` | `@1aboveio/engineering-skills` | better-review, e2e-test, engineering-runtime, ensure-coverage, golden-path, harness-runtime, resolve-issues, resolve-release, rush-issues, rush-release, test-quality |
|
|
26
32
|
| `cicd` | `@1aboveio/cicd-skills` | cloud-build, cloud-debug, cloud-deploy, docker, github-actions, github-flow, google-cloud, mergify, podman |
|
|
27
|
-
| `app-dev` | `@1aboveio/app-dev-skills` | airflow, android-foundation, app-debug, better-auth, data-catalog, ios-foundation, monorepo, nextjs-fullstack, prisma-setup, pyspark, python-backend, shadcn, zod-v4 |
|
|
33
|
+
| `app-dev` | `@1aboveio/app-dev-skills` | airflow, android-foundation, app-debug, better-auth, data-catalog, data-quality, ios-foundation, monorepo, nextjs-fullstack, prisma-setup, pyspark, python-backend, shadcn, zod-v4 |
|
|
28
34
|
| `risk-compliance` | `@1aboveio/risk-compliance-skills` | compliance-audit, contract-compare, contract-review, doc-authenticity, fraud-analysis, payment-analysis, report-writing, similar-domain, tld-detection |
|
|
29
35
|
| `content` | `@1aboveio/content-skills` | article-review, article-to-image, flowchart-svg, image-gen, mermaid, wechat-article-writer, wechat-publisher, wechat-search, xai-search |
|
|
30
36
|
|
|
37
|
+
## Third-party sources
|
|
38
|
+
|
|
39
|
+
Not shipped in this package. Selecting one downloads it from its repository at the revision
|
|
40
|
+
1aboveio/ai reviewed (one archive, no git history), keeps only the skills below and its
|
|
41
|
+
license, and installs them as a local Pi package in `~/.pi/agent/1aboveio-skills/<id>/`.
|
|
42
|
+
|
|
43
|
+
| Source | Repository | Skills |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `matt-pocock` | https://github.com/mattpocock/skills | batch-grill-me, code-review, codebase-design, diagnosing-bugs, domain-modeling, grill-me, grill-with-docs, grilling, handoff, improve-codebase-architecture, prototype, research, resolving-merge-conflicts, setup-matt-pocock-skills, tdd, to-spec, to-tickets, triage, wayfinder |
|
|
46
|
+
| `impeccable` | https://github.com/pbakaus/impeccable | impeccable |
|
|
47
|
+
|
|
31
48
|
Generated from [1aboveio/ai](https://github.com/1aboveio/ai) `skills/distribution/packages/`. Do not edit.
|
package/bin/select-core.mjs
CHANGED
|
@@ -3,15 +3,26 @@
|
|
|
3
3
|
// The package ships every skill under skills/<package-id>/<skill>/, so a selection is one Pi
|
|
4
4
|
// package filter per package id (`skills/<id>/**`); a skill added to a selected package later loads
|
|
5
5
|
// on the next `pi update` without selecting again. Selecting a package that uses core skills adds
|
|
6
|
-
// core.
|
|
7
|
-
// (~/.agents/skills/<name> installed from 1aboveio/ai): Pi keeps the first skill of a name and
|
|
8
|
-
// ranks package skills last, so those copies would otherwise win. Claude Code still reads them.
|
|
6
|
+
// core.
|
|
9
7
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
// Third-party sources (meta.external: Matt Pocock's skills, impeccable) are not shipped. Selecting
|
|
9
|
+
// one downloads the source at the revision the repository reviewed, as one archive without git
|
|
10
|
+
// history, keeps only the reviewed skill folders and the LICENSE, and installs them as a local Pi
|
|
11
|
+
// package in <agent dir>/1aboveio-skills/<id>/, the way the Skills CLI copies a skill out of a
|
|
12
|
+
// shallow clone. Pi's own git install keeps the full clone and finds no skills in impeccable.
|
|
13
|
+
//
|
|
14
|
+
// Pi keeps the first skill of a name and ranks package skills last, so copies in ~/.agents/skills
|
|
15
|
+
// (Skills CLI installs, also read by Claude Code and Codex) would win. They are hidden from Pi with
|
|
16
|
+
// `-<path>` entries in the personal settings, never deleted: a copy whose lock entry names the
|
|
17
|
+
// skill's own source, or that has no lock entry. The bundle's hide-copies extension runs the same
|
|
18
|
+
// hiding at Pi startup, so a plain `pi install` with `pi config` needs no select run.
|
|
19
|
+
//
|
|
20
|
+
// Ships in the package as bin/select-core.mjs, an import-only core run by bin/cli.mjs and
|
|
21
|
+
// extensions/hide-copies.ts; it has no dependencies.
|
|
22
|
+
import { execFileSync } from 'node:child_process';
|
|
23
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
24
|
+
import { homedir, tmpdir } from 'node:os';
|
|
25
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
15
26
|
import { createInterface } from 'node:readline/promises';
|
|
16
27
|
|
|
17
28
|
const USAGE = `Usage: npx @1aboveio/skills [select] [<package>... | --all] [--local] [--dry-run]
|
|
@@ -19,11 +30,13 @@ const USAGE = `Usage: npx @1aboveio/skills [select] [<package>... | --all] [--lo
|
|
|
19
30
|
|
|
20
31
|
(no arguments) choose interactively which packages Pi loads
|
|
21
32
|
<id>... load only these packages (core is added when they need it)
|
|
22
|
-
--all load every package
|
|
33
|
+
--all load every package, third-party sources included
|
|
23
34
|
--local write the project's .pi/settings.json instead of ~/.pi/agent/settings.json
|
|
24
|
-
--dry-run print the settings change without writing
|
|
35
|
+
--dry-run print the settings change without writing or downloading anything
|
|
25
36
|
list print the packages and their skills`;
|
|
26
37
|
|
|
38
|
+
export const FIRST_PARTY_SOURCE = '1aboveio/ai';
|
|
39
|
+
|
|
27
40
|
// select is the default command, so a bare `npx @1aboveio/skills` asks interactively.
|
|
28
41
|
export function parseArgs(argv) {
|
|
29
42
|
const [command, ...rest] = ['select', 'list'].includes(argv[0]) ? argv : ['select', ...argv];
|
|
@@ -39,6 +52,15 @@ export function parseArgs(argv) {
|
|
|
39
52
|
return options;
|
|
40
53
|
}
|
|
41
54
|
|
|
55
|
+
function externals(meta) {
|
|
56
|
+
return meta.external ?? [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Every selectable id: the bundle's packages, then the third-party sources.
|
|
60
|
+
export function allIds(meta) {
|
|
61
|
+
return [...meta.groups.map((group) => group.id), ...externals(meta).map((ext) => ext.id)];
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
function filterFor(id) {
|
|
43
65
|
return `skills/${id}/**`;
|
|
44
66
|
}
|
|
@@ -51,16 +73,32 @@ function entrySource(entry) {
|
|
|
51
73
|
return typeof entry === 'string' ? entry : entry?.source;
|
|
52
74
|
}
|
|
53
75
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
export function agentDir(home) {
|
|
77
|
+
return process.env.PI_CODING_AGENT_DIR ? resolve(process.env.PI_CODING_AGENT_DIR) : join(home, '.pi/agent');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Where a third-party source is installed: personal, whichever settings file loads it.
|
|
81
|
+
export function externalDir(home, id) {
|
|
82
|
+
return join(agentDir(home), '1aboveio-skills', id);
|
|
59
83
|
}
|
|
60
84
|
|
|
61
|
-
|
|
85
|
+
function bundleEntry(settings, meta) {
|
|
86
|
+
return (settings.packages ?? []).find((entry) => matchesSource(entrySource(entry) ?? '', meta.name));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The ids a settings file loads: bundle packages by its skills filter (every package when it has
|
|
90
|
+
// none), third-party sources by their local package entry.
|
|
91
|
+
export function currentSelection(settings, meta, home) {
|
|
92
|
+
const entry = bundleEntry(settings, meta);
|
|
93
|
+
const groups = meta.groups.map((group) => group.id);
|
|
94
|
+
const loaded = !entry ? [] : typeof entry === 'string' || !Array.isArray(entry.skills) ? groups : groups.filter((id) => entry.skills.includes(filterFor(id)));
|
|
95
|
+
const sources = new Set((settings.packages ?? []).map(entrySource));
|
|
96
|
+
return [...loaded, ...externals(meta).filter((ext) => sources.has(externalDir(home, ext.id))).map((ext) => ext.id)];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The ids to load, in list order, with core added when a chosen package needs it.
|
|
62
100
|
export function planSelection(meta, ids) {
|
|
63
|
-
const known = new Map(meta.groups.map((
|
|
101
|
+
const known = new Map([...meta.groups, ...externals(meta)].map((item) => [item.id, item]));
|
|
64
102
|
const unknown = ids.filter((id) => !known.has(id));
|
|
65
103
|
if (unknown.length) throw new Error(`Unknown package: ${unknown.join(', ')}. Packages: ${[...known.keys()].join(', ')}`);
|
|
66
104
|
if (!ids.length) throw new Error('Select at least one package.');
|
|
@@ -70,92 +108,182 @@ export function planSelection(meta, ids) {
|
|
|
70
108
|
chosen.add(meta.core);
|
|
71
109
|
added.push(meta.core);
|
|
72
110
|
}
|
|
73
|
-
return { ids: meta
|
|
111
|
+
return { ids: allIds(meta).filter((id) => chosen.has(id)), added };
|
|
74
112
|
}
|
|
75
113
|
|
|
76
|
-
//
|
|
77
|
-
|
|
114
|
+
// [{ name, source }] for every skill the selected ids load; source is the Skills CLI lock source.
|
|
115
|
+
function loadedSkills(meta, ids) {
|
|
116
|
+
return [
|
|
117
|
+
...meta.groups.filter((group) => ids.includes(group.id)).flatMap((group) => group.skills.map((name) => ({ name, source: FIRST_PARTY_SOURCE }))),
|
|
118
|
+
...externals(meta).filter((ext) => ids.includes(ext.id)).flatMap((ext) => ext.skills.map((skill) => ({ name: skill.name, source: ext.lockSource }))),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Copies in ~/.agents/skills that duplicate a loaded skill: the lock entry names the skill's own
|
|
123
|
+
// source, or there is none (an older installer). A lock entry from another source is a different
|
|
124
|
+
// skill that shares the name, and stays.
|
|
125
|
+
export function skillsCliCopies(home, skills) {
|
|
78
126
|
const lockPath = join(home, '.agents/.skill-lock.json');
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
127
|
+
const lock = existsSync(lockPath) ? JSON.parse(readFileSync(lockPath, 'utf8')).skills ?? {} : {};
|
|
128
|
+
return skills
|
|
129
|
+
.filter(({ name, source }) => !lock[name] || lock[name].source === source)
|
|
130
|
+
.map(({ name }) => name)
|
|
131
|
+
.filter((name) => existsSync(join(home, '.agents/skills', name, 'SKILL.md')));
|
|
82
132
|
}
|
|
83
133
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
134
|
+
// Recomputes the `-<path>` entries this package owns in the personal `skills` list, so
|
|
135
|
+
// deselecting a package shows its copies again. Returns the copy names now hidden.
|
|
136
|
+
function hideCopies(next, meta, ids, home) {
|
|
137
|
+
const agentsSkills = join(home, '.agents/skills');
|
|
138
|
+
const hideEntry = (name) => `-${join(agentsSkills, name)}`;
|
|
139
|
+
const ours = new Set(loadedSkills(meta, allIds(meta)).map(({ name }) => hideEntry(name)));
|
|
140
|
+
const kept = (next.skills ?? []).filter((entry) => !ours.has(entry));
|
|
141
|
+
const copies = [...new Set(skillsCliCopies(home, loadedSkills(meta, ids)))];
|
|
142
|
+
const skills = [...kept, ...copies.map(hideEntry)];
|
|
143
|
+
if (skills.length) next.skills = skills;
|
|
144
|
+
else delete next.skills;
|
|
145
|
+
return copies;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hiddenNote(home, copies) {
|
|
149
|
+
return `Hid ${copies.length} Skills CLI cop${copies.length === 1 ? 'y' : 'ies'} in ${join(home, '.agents/skills')} from Pi so the package versions load (Claude Code and Codex still use them): ${copies.join(', ')}.`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Returns { settings, notes } with the bundle entry narrowed to the selected packages, one local
|
|
153
|
+
// package entry per selected third-party source, and, when `hide` is given (personal settings),
|
|
154
|
+
// the Skills CLI copies of the loaded skills hidden from Pi.
|
|
87
155
|
export function applySelection(settings, meta, ids, { home, hide } = {}) {
|
|
88
156
|
const next = structuredClone(settings);
|
|
89
157
|
const packages = next.packages ?? [];
|
|
90
158
|
const index = packages.findIndex((entry) => matchesSource(entrySource(entry) ?? '', meta.name));
|
|
91
159
|
if (index < 0) throw new Error(`${meta.name} is not installed here. Run: pi install npm:${meta.name}${next.packages ? '' : ' (or pass --local for a project install)'}`);
|
|
92
160
|
const source = entrySource(packages[index]);
|
|
93
|
-
const
|
|
94
|
-
|
|
161
|
+
const groupIds = meta.groups.map((group) => group.id).filter((id) => ids.includes(id));
|
|
162
|
+
const everything = groupIds.length === meta.groups.length;
|
|
163
|
+
if (typeof packages[index] === 'string') packages[index] = everything ? source : { source, skills: groupIds.map(filterFor) };
|
|
95
164
|
else if (everything) delete packages[index].skills;
|
|
96
|
-
else packages[index].skills =
|
|
165
|
+
else packages[index].skills = groupIds.map(filterFor);
|
|
97
166
|
if (typeof packages[index] === 'object' && Object.keys(packages[index]).length === 1) packages[index] = source;
|
|
98
167
|
|
|
168
|
+
for (const ext of externals(meta)) {
|
|
169
|
+
const dir = externalDir(home, ext.id);
|
|
170
|
+
const at = packages.findIndex((entry) => entrySource(entry) === dir);
|
|
171
|
+
if (ids.includes(ext.id) && at < 0) packages.push(dir);
|
|
172
|
+
if (!ids.includes(ext.id) && at >= 0) packages.splice(at, 1);
|
|
173
|
+
}
|
|
174
|
+
|
|
99
175
|
const notes = [];
|
|
100
176
|
const domain = packages.map(entrySource).filter((s) => s && meta.groups.some((group) => matchesSource(s, group.packageName)));
|
|
101
177
|
if (domain.length) notes.push(`Also installed: ${domain.join(', ')}. Their skills duplicate this package's; remove them with pi remove, or Pi keeps whichever loads first.`);
|
|
102
|
-
|
|
103
178
|
if (hide) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const kept = (next.skills ?? []).filter((entry) => !ours.has(entry));
|
|
107
|
-
const loaded = meta.groups.filter((group) => ids.includes(group.id)).flatMap((group) => group.skills);
|
|
108
|
-
const copies = skillsCliCopies(home, loaded);
|
|
109
|
-
const skills = [...kept, ...copies.map((name) => `-${join(agentsSkills, name)}`)];
|
|
110
|
-
if (skills.length) next.skills = skills;
|
|
111
|
-
else delete next.skills;
|
|
112
|
-
if (copies.length) notes.push(`Hid ${copies.length} Skills CLI cop${copies.length === 1 ? 'y' : 'ies'} in ${agentsSkills} from Pi so this package's versions load; Claude Code still uses them.`);
|
|
179
|
+
const copies = hideCopies(next, meta, ids, home);
|
|
180
|
+
if (copies.length) notes.push(hiddenNote(home, copies));
|
|
113
181
|
}
|
|
114
182
|
return { settings: next, notes };
|
|
115
183
|
}
|
|
116
184
|
|
|
185
|
+
// Run by the hide-copies extension at Pi startup: when the personal settings load this bundle,
|
|
186
|
+
// hide the Skills CLI copies of what it loads. Returns a note when the settings changed, else null.
|
|
187
|
+
export function syncHiddenCopies(meta, { home }) {
|
|
188
|
+
const path = settingsPath({ local: false, home });
|
|
189
|
+
if (!existsSync(path)) return null;
|
|
190
|
+
const settings = JSON.parse(readFileSync(path, 'utf8'));
|
|
191
|
+
if (!bundleEntry(settings, meta)) return null;
|
|
192
|
+
const next = structuredClone(settings);
|
|
193
|
+
const copies = hideCopies(next, meta, currentSelection(settings, meta, home), home);
|
|
194
|
+
if (JSON.stringify(next.skills ?? []) === JSON.stringify(settings.skills ?? [])) return null;
|
|
195
|
+
writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`);
|
|
196
|
+
return copies.length ? hiddenNote(home, copies) : 'Showed Skills CLI copies again for packages no longer loaded.';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function download(url, file) {
|
|
200
|
+
const response = await fetch(url);
|
|
201
|
+
if (!response.ok) throw new Error(`Download failed: ${url} (HTTP ${response.status})`);
|
|
202
|
+
writeFileSync(file, Buffer.from(await response.arrayBuffer()));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Installs a third-party source at its reviewed revision in dir: the reviewed skill folders under
|
|
206
|
+
// skills/<name>/, its LICENSE, and a package.json that makes the folder a Pi package. Skips the
|
|
207
|
+
// download when dir already holds that revision.
|
|
208
|
+
export async function installExternal(ext, dir, { fetchArchive = download } = {}) {
|
|
209
|
+
const manifestPath = join(dir, 'package.json');
|
|
210
|
+
if (existsSync(manifestPath) && JSON.parse(readFileSync(manifestPath, 'utf8')).skillsSource?.revision === ext.revision) return false;
|
|
211
|
+
const work = mkdtempSync(join(tmpdir(), `1aboveio-skills-${ext.id}-`));
|
|
212
|
+
try {
|
|
213
|
+
const archive = join(work, 'source.tar.gz');
|
|
214
|
+
await fetchArchive(ext.archive, archive);
|
|
215
|
+
const members = [...ext.skills.map((skill) => `${ext.root}/${skill.path}`), ...(ext.license ? [`${ext.root}/${ext.license}`] : [])];
|
|
216
|
+
execFileSync('tar', ['-xzf', archive, '-C', work, ...members], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
217
|
+
const staged = join(work, 'package');
|
|
218
|
+
for (const skill of ext.skills) {
|
|
219
|
+
mkdirSync(join(staged, 'skills'), { recursive: true });
|
|
220
|
+
renameSync(join(work, ext.root, skill.path), join(staged, 'skills', skill.name));
|
|
221
|
+
}
|
|
222
|
+
if (ext.license) renameSync(join(work, ext.root, ext.license), join(staged, basename(ext.license)));
|
|
223
|
+
const manifest = {
|
|
224
|
+
name: `${ext.id}-skills`,
|
|
225
|
+
private: true,
|
|
226
|
+
description: `${ext.description} Installed by @1aboveio/skills from ${ext.repository} at ${ext.revision}.`,
|
|
227
|
+
pi: { skills: ['./skills'] },
|
|
228
|
+
skillsSource: { repository: ext.repository, revision: ext.revision },
|
|
229
|
+
};
|
|
230
|
+
writeFileSync(join(staged, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
231
|
+
rmSync(dir, { recursive: true, force: true });
|
|
232
|
+
mkdirSync(dirname(dir), { recursive: true });
|
|
233
|
+
renameSync(staged, dir);
|
|
234
|
+
return true;
|
|
235
|
+
} finally {
|
|
236
|
+
rmSync(work, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
117
240
|
export function settingsPath({ local, home, cwd }) {
|
|
118
|
-
return local ? join(cwd, '.pi/settings.json') : join(
|
|
241
|
+
return local ? join(cwd, '.pi/settings.json') : join(agentDir(home), 'settings.json');
|
|
119
242
|
}
|
|
120
243
|
|
|
121
244
|
function describe(meta, ids) {
|
|
122
245
|
const mark = (on) => `[${on ? 'x' : ' '}]`;
|
|
123
|
-
const
|
|
246
|
+
const items = [
|
|
247
|
+
...meta.groups.map((group) => ({ id: group.id, count: group.skills.length, description: group.description })),
|
|
248
|
+
...externals(meta).map((ext) => ({ id: ext.id, count: ext.skills.length, description: `${ext.description} (downloaded from ${ext.repository})` })),
|
|
249
|
+
];
|
|
250
|
+
const total = items.reduce((sum, item) => sum + item.count, 0);
|
|
124
251
|
return [
|
|
125
|
-
` 0. ${mark(ids.length ===
|
|
126
|
-
...
|
|
252
|
+
` 0. ${mark(ids.length === items.length)} ${'all'.padEnd(16)} ${total} skills — every package`,
|
|
253
|
+
...items.map((item, i) => ` ${i + 1}. ${mark(ids.includes(item.id))} ${item.id.padEnd(16)} ${item.count} skills — ${item.description}`),
|
|
127
254
|
].join('\n');
|
|
128
255
|
}
|
|
129
256
|
|
|
130
257
|
// Reads a selection from the terminal: numbers or ids, 0 or "all", or nothing to keep the current one.
|
|
131
258
|
async function ask(meta, current, input, output) {
|
|
132
259
|
const rl = createInterface({ input, output });
|
|
260
|
+
const ids = allIds(meta);
|
|
133
261
|
try {
|
|
134
262
|
output.write(`${meta.name}: choose which packages Pi loads\n${describe(meta, current)}\n`);
|
|
135
263
|
const keep = current.length ? 'Enter keeps the current selection' : 'Enter loads all';
|
|
136
264
|
const answer = (await rl.question(`Packages to load (numbers or ids, 0 for all; ${keep}): `)).trim();
|
|
137
265
|
const tokens = answer.split(/[\s,]+/).filter(Boolean);
|
|
138
|
-
if (!tokens.length) return current.length ? current :
|
|
139
|
-
if (tokens.some((token) => token === '0' || token === 'all')) return
|
|
140
|
-
return tokens.map((token) => (/^\d+$/.test(token) ?
|
|
266
|
+
if (!tokens.length) return current.length ? current : ids;
|
|
267
|
+
if (tokens.some((token) => token === '0' || token === 'all')) return ids;
|
|
268
|
+
return tokens.map((token) => (/^\d+$/.test(token) ? ids[Number(token) - 1] ?? token : token));
|
|
141
269
|
} finally {
|
|
142
270
|
rl.close();
|
|
143
271
|
}
|
|
144
272
|
}
|
|
145
273
|
|
|
146
|
-
export async function main(argv, { meta, log = console.log, home = homedir(), cwd = process.cwd(), input = process.stdin, output = process.stdout } = {}) {
|
|
274
|
+
export async function main(argv, { meta, log = console.log, home = homedir(), cwd = process.cwd(), input = process.stdin, output = process.stdout, fetchArchive } = {}) {
|
|
147
275
|
const options = parseArgs(argv);
|
|
148
276
|
if (options.command === 'list') {
|
|
149
277
|
for (const group of meta.groups) log(`${group.id} (${group.packageName}): ${group.skills.join(', ')}`);
|
|
278
|
+
for (const ext of externals(meta)) log(`${ext.id} (${ext.repository} at ${ext.revision.slice(0, 12)}): ${ext.skills.map((skill) => skill.name).join(', ')}`);
|
|
150
279
|
return 0;
|
|
151
280
|
}
|
|
152
281
|
const path = settingsPath({ local: options.local, home, cwd });
|
|
153
282
|
const settings = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : {};
|
|
154
|
-
|
|
155
|
-
let ids = options.all ? meta.groups.map((group) => group.id) : options.ids;
|
|
283
|
+
let ids = options.all ? allIds(meta) : options.ids;
|
|
156
284
|
if (!ids.length) {
|
|
157
285
|
if (!input.isTTY) throw new Error(`No packages given and no terminal to ask.\n\n${USAGE}`);
|
|
158
|
-
ids = await ask(meta,
|
|
286
|
+
ids = await ask(meta, bundleEntry(settings, meta) ? currentSelection(settings, meta, home) : [], input, output);
|
|
159
287
|
}
|
|
160
288
|
const plan = planSelection(meta, ids);
|
|
161
289
|
const { settings: next, notes } = applySelection(settings, meta, plan.ids, { home, hide: !options.local });
|
|
@@ -163,6 +291,10 @@ export async function main(argv, { meta, log = console.log, home = homedir(), cw
|
|
|
163
291
|
if (options.dryRun) {
|
|
164
292
|
log(JSON.stringify(next, null, 2));
|
|
165
293
|
} else {
|
|
294
|
+
for (const ext of externals(meta).filter((item) => plan.ids.includes(item.id))) {
|
|
295
|
+
const dir = externalDir(home, ext.id);
|
|
296
|
+
if (await installExternal(ext, dir, { fetchArchive })) log(`Installed ${ext.id} (${ext.skills.length} skills) from ${ext.repository} at ${ext.revision.slice(0, 12)} in ${dir}.`);
|
|
297
|
+
}
|
|
166
298
|
mkdirSync(dirname(path), { recursive: true });
|
|
167
299
|
writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`);
|
|
168
300
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Shipped in @1aboveio/skills. At Pi startup, hides from Pi the Skills CLI copies in ~/.agents/skills
|
|
2
|
+
// of the skills this bundle loads, so the package versions win without running the select
|
|
3
|
+
// command. Writes only Pi's personal settings; the copies stay for Claude Code and Codex.
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { syncHiddenCopies } from "../bin/select-core.mjs";
|
|
8
|
+
|
|
9
|
+
const manifest = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
10
|
+
const meta = { name: manifest.name, ...manifest.skillsPackage };
|
|
11
|
+
|
|
12
|
+
export default function (pi: ExtensionAPI) {
|
|
13
|
+
pi.on("session_start", (_event, ctx) => {
|
|
14
|
+
try {
|
|
15
|
+
const note = syncHiddenCopies(meta, { home: process.env.HOME || homedir() });
|
|
16
|
+
if (note) ctx.ui.notify(`${manifest.name}: ${note} Run /reload to apply.`, "info");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
ctx.ui.notify(`${manifest.name}: could not hide Skills CLI copies: ${(error as Error).message}`, "warning");
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1aboveio/skills",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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": {
|
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"skills",
|
|
23
23
|
"bin",
|
|
24
|
+
"extensions",
|
|
24
25
|
"README.md"
|
|
25
26
|
],
|
|
26
27
|
"pi": {
|
|
27
28
|
"skills": [
|
|
28
29
|
"./skills"
|
|
30
|
+
],
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./extensions/hide-copies.ts"
|
|
29
33
|
]
|
|
30
34
|
},
|
|
31
35
|
"skillsPackage": {
|
|
@@ -34,13 +38,14 @@
|
|
|
34
38
|
{
|
|
35
39
|
"id": "core",
|
|
36
40
|
"packageName": "@1aboveio/core-skills",
|
|
37
|
-
"description": "Shared writing and document foundation: house style, editorial review, Chinese polish, terminology, write-doc, distill-writing, doc-to-md and reg-parser.",
|
|
41
|
+
"description": "Shared writing and document foundation: house style, plain-language replies, editorial review, Chinese polish, terminology, write-doc, distill-writing, doc-to-md and reg-parser.",
|
|
38
42
|
"needsCore": false,
|
|
39
43
|
"skills": [
|
|
40
44
|
"distill-writing",
|
|
41
45
|
"doc-to-md",
|
|
42
46
|
"editorial-review",
|
|
43
47
|
"house-style",
|
|
48
|
+
"plain-language",
|
|
44
49
|
"polish-cn",
|
|
45
50
|
"reg-parser",
|
|
46
51
|
"terminology",
|
|
@@ -94,6 +99,7 @@
|
|
|
94
99
|
"app-debug",
|
|
95
100
|
"better-auth",
|
|
96
101
|
"data-catalog",
|
|
102
|
+
"data-quality",
|
|
97
103
|
"ios-foundation",
|
|
98
104
|
"monorepo",
|
|
99
105
|
"nextjs-fullstack",
|
|
@@ -139,7 +145,113 @@
|
|
|
139
145
|
]
|
|
140
146
|
}
|
|
141
147
|
],
|
|
142
|
-
"
|
|
148
|
+
"external": [
|
|
149
|
+
{
|
|
150
|
+
"id": "matt-pocock",
|
|
151
|
+
"description": "Matt Pocock's engineering skills: TDD, domain modeling, codebase design, grilling, triage and specs.",
|
|
152
|
+
"repository": "https://github.com/mattpocock/skills",
|
|
153
|
+
"lockSource": "mattpocock/skills",
|
|
154
|
+
"revision": "ed37663cc5fbef691ddfecd080dff42f7e7e350d",
|
|
155
|
+
"archive": "https://codeload.github.com/mattpocock/skills/tar.gz/ed37663cc5fbef691ddfecd080dff42f7e7e350d",
|
|
156
|
+
"root": "skills-ed37663cc5fbef691ddfecd080dff42f7e7e350d",
|
|
157
|
+
"license": "LICENSE",
|
|
158
|
+
"skills": [
|
|
159
|
+
{
|
|
160
|
+
"name": "batch-grill-me",
|
|
161
|
+
"path": "skills/in-progress/batch-grill-me"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "code-review",
|
|
165
|
+
"path": "skills/engineering/code-review"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "codebase-design",
|
|
169
|
+
"path": "skills/engineering/codebase-design"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "diagnosing-bugs",
|
|
173
|
+
"path": "skills/engineering/diagnosing-bugs"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "domain-modeling",
|
|
177
|
+
"path": "skills/engineering/domain-modeling"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "grill-me",
|
|
181
|
+
"path": "skills/productivity/grill-me"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "grill-with-docs",
|
|
185
|
+
"path": "skills/engineering/grill-with-docs"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "grilling",
|
|
189
|
+
"path": "skills/productivity/grilling"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "handoff",
|
|
193
|
+
"path": "skills/productivity/handoff"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"name": "improve-codebase-architecture",
|
|
197
|
+
"path": "skills/engineering/improve-codebase-architecture"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "prototype",
|
|
201
|
+
"path": "skills/engineering/prototype"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "research",
|
|
205
|
+
"path": "skills/engineering/research"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "resolving-merge-conflicts",
|
|
209
|
+
"path": "skills/engineering/resolving-merge-conflicts"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "setup-matt-pocock-skills",
|
|
213
|
+
"path": "skills/engineering/setup-matt-pocock-skills"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "tdd",
|
|
217
|
+
"path": "skills/engineering/tdd"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "to-spec",
|
|
221
|
+
"path": "skills/engineering/to-spec"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"name": "to-tickets",
|
|
225
|
+
"path": "skills/engineering/to-tickets"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"name": "triage",
|
|
229
|
+
"path": "skills/engineering/triage"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"name": "wayfinder",
|
|
233
|
+
"path": "skills/engineering/wayfinder"
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"id": "impeccable",
|
|
239
|
+
"description": "impeccable frontend design skill: design, critique, audit and polish interfaces.",
|
|
240
|
+
"repository": "https://github.com/pbakaus/impeccable",
|
|
241
|
+
"lockSource": "pbakaus/impeccable",
|
|
242
|
+
"revision": "5a149f3fdb1b5793f10567233b1dcab98fc305fd",
|
|
243
|
+
"archive": "https://codeload.github.com/pbakaus/impeccable/tar.gz/5a149f3fdb1b5793f10567233b1dcab98fc305fd",
|
|
244
|
+
"root": "impeccable-5a149f3fdb1b5793f10567233b1dcab98fc305fd",
|
|
245
|
+
"license": "LICENSE",
|
|
246
|
+
"skills": [
|
|
247
|
+
{
|
|
248
|
+
"name": "impeccable",
|
|
249
|
+
"path": ".agents/skills/impeccable"
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
"digest": "f9f34590a0c5208860a58070173d3e23e37e7e2d07d6b3113a30b68dc3b30dde"
|
|
143
255
|
},
|
|
144
256
|
"publishConfig": {
|
|
145
257
|
"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.39.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.39.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.39.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.39.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.39.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Data catalog
|
|
@@ -42,6 +42,7 @@ Never document tables during setup unless the task asks for it.
|
|
|
42
42
|
| CI says the catalog is stale | `python3 <engine> generate`, commit the pages |
|
|
43
43
|
| Before committing a table-script change, or reviewing one | `python3 <engine> drift` (`--base origin/main` for a whole branch) |
|
|
44
44
|
| `Field`/`Table` arguments, the SQL block, config keys, exit codes | [references/format.md](references/format.md) |
|
|
45
|
+
| Does the table's data match its definitions? | the data-quality skill |
|
|
45
46
|
|
|
46
47
|
`<engine>` is `scripts/data-catalog/data_catalog.py` in the repo, or
|
|
47
48
|
`<skill-base-dir>/scripts/data_catalog.py` before the repo has a copy. When
|
|
@@ -52,7 +52,8 @@ table. `docs/catalog/` is generated from those definitions; never edit it by han
|
|
|
52
52
|
collections.", not "0 when none".
|
|
53
53
|
9. Never restate the column name or write "same as above".
|
|
54
54
|
10. Cover every case: what the column holds for each kind of row, including when it
|
|
55
|
-
is empty; list an enumerated column's values
|
|
55
|
+
is empty; list an enumerated column's values in the text and in `values=(...)`;
|
|
56
|
+
say so if it is always empty today.
|
|
56
57
|
11. Write related columns as a set: a count and its amount, a value and its missing
|
|
57
58
|
flag, a legacy column and its replacement use the same scope and wording, and any
|
|
58
59
|
difference is stated in both rows.
|