@1aboveio/skills 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +25 -8
  2. package/bin/select-core.mjs +182 -50
  3. package/extensions/hide-copies.ts +21 -0
  4. package/package.json +116 -3
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/android-foundation/SKILL.md +1 -1
  7. package/skills/app-dev/app-debug/SKILL.md +1 -1
  8. package/skills/app-dev/better-auth/SKILL.md +1 -1
  9. package/skills/app-dev/data-catalog/SKILL.md +2 -1
  10. package/skills/app-dev/data-catalog/references/data-catalog-conventions.md +2 -1
  11. package/skills/app-dev/data-catalog/references/field-definitions.md +2 -1
  12. package/skills/app-dev/data-catalog/references/format.md +6 -2
  13. package/skills/app-dev/data-catalog/scripts/data_catalog.py +15 -3
  14. package/skills/app-dev/data-catalog/scripts/persist-conventions-core.mjs +3 -2
  15. package/skills/app-dev/data-catalog/scripts/test_data_catalog.py +15 -0
  16. package/skills/app-dev/data-quality/LICENSE +3 -0
  17. package/skills/app-dev/data-quality/SKILL.md +96 -0
  18. package/skills/app-dev/data-quality/agents/openai.yaml +3 -0
  19. package/skills/app-dev/data-quality/references/checks.md +56 -0
  20. package/skills/app-dev/data-quality/release-check.mjs +93 -0
  21. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +12 -0
  22. package/skills/app-dev/data-quality/scripts/data_quality.py +405 -0
  23. package/skills/app-dev/data-quality/scripts/test_data_quality.py +277 -0
  24. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  25. package/skills/app-dev/monorepo/SKILL.md +1 -1
  26. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  27. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  28. package/skills/app-dev/pyspark/SKILL.md +1 -1
  29. package/skills/app-dev/python-backend/SKILL.md +1 -1
  30. package/skills/app-dev/shadcn/SKILL.md +1 -1
  31. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  32. package/skills/cicd/cloud-build/SKILL.md +1 -1
  33. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  34. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  35. package/skills/cicd/docker/SKILL.md +1 -1
  36. package/skills/cicd/github-actions/SKILL.md +1 -1
  37. package/skills/cicd/github-flow/SKILL.md +1 -1
  38. package/skills/cicd/github-flow/scripts/persist-conventions-core.mjs +3 -2
  39. package/skills/cicd/google-cloud/SKILL.md +1 -1
  40. package/skills/cicd/mergify/SKILL.md +1 -1
  41. package/skills/cicd/podman/SKILL.md +1 -1
  42. package/skills/content/article-review/SKILL.md +1 -1
  43. package/skills/content/article-to-image/SKILL.md +1 -1
  44. package/skills/content/flowchart-svg/SKILL.md +1 -1
  45. package/skills/content/image-gen/SKILL.md +1 -1
  46. package/skills/content/mermaid/SKILL.md +1 -1
  47. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  48. package/skills/content/wechat-publisher/SKILL.md +1 -1
  49. package/skills/content/wechat-search/SKILL.md +1 -1
  50. package/skills/content/xai-search/SKILL.md +1 -1
  51. package/skills/core/distill-writing/SKILL.md +1 -1
  52. package/skills/core/doc-to-md/SKILL.md +1 -1
  53. package/skills/core/editorial-review/SKILL.md +1 -1
  54. package/skills/core/house-style/SKILL.md +1 -1
  55. package/skills/core/plain-language/LICENSE +3 -0
  56. package/skills/core/plain-language/SKILL.md +57 -0
  57. package/skills/core/plain-language/release-check.mjs +93 -0
  58. package/skills/core/polish-cn/SKILL.md +1 -1
  59. package/skills/core/reg-parser/SKILL.md +1 -1
  60. package/skills/core/terminology/SKILL.md +1 -1
  61. package/skills/core/write-doc/SKILL.md +1 -1
  62. package/skills/engineering/better-review/SKILL.md +1 -1
  63. package/skills/engineering/contributing/LICENSE +3 -0
  64. package/skills/engineering/contributing/SKILL.md +70 -0
  65. package/skills/engineering/contributing/references/tracker-conventions.md +18 -0
  66. package/skills/engineering/contributing/release-check.mjs +93 -0
  67. package/skills/engineering/contributing/scripts/persist-conventions-core.mjs +118 -0
  68. package/skills/engineering/contributing/scripts/persist-conventions.mjs +84 -0
  69. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +118 -0
  70. package/skills/engineering/e2e-test/SKILL.md +1 -1
  71. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  72. package/skills/engineering/engineering-runtime/coherence/workflow.json +29 -14
  73. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -0
  74. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  75. package/skills/engineering/golden-path/SKILL.md +1 -1
  76. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  77. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  78. package/skills/engineering/resolve-release/SKILL.md +1 -1
  79. package/skills/engineering/rush-issues/SKILL.md +1 -1
  80. package/skills/engineering/rush-release/SKILL.md +1 -1
  81. package/skills/engineering/test-quality/SKILL.md +1 -1
  82. package/skills/engineering/test-quality/scripts/persist-conventions-core.mjs +3 -2
  83. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  84. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  85. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  86. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  87. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  88. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  89. package/skills/risk-compliance/report-writing/SKILL.md +1 -1
  90. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  91. 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
- npx @1aboveio/skills # choose packages interactively
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
- The command writes a Pi package filter (`skills/<package>/**`) to `~/.pi/agent/settings.json`
14
- (`--local`: the project's `.pi/settings.json`), adds core when a chosen package uses it, and
15
- hides Skills CLI copies of the loaded skills from Pi so these versions win. Until you run it,
16
- Pi loads every package. `pi config` toggles single skills.
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 |
25
- | `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 |
30
+ | `core` | `@1aboveio/core-skills` | distill-writing, doc-to-md, editorial-review, house-style, plain-language, polish-cn, reg-parser, terminology, write-doc |
31
+ | `engineering` | `@1aboveio/engineering-skills` | better-review, contributing, 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.
@@ -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. In personal settings it also hides the Skills CLI copies of selected skills
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
- // Ships in the package as bin/select-core.mjs, an import-only core run by bin/cli.mjs; it has no
11
- // dependencies.
12
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
13
- import { homedir } from 'node:os';
14
- import { dirname, join, resolve } from 'node:path';
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 it
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
- // The package ids an existing settings entry loads; every id when it has no skills filter.
55
- export function currentSelection(entry, meta) {
56
- const all = meta.groups.map((group) => group.id);
57
- if (typeof entry === 'string' || !Array.isArray(entry.skills)) return all;
58
- return all.filter((id) => entry.skills.includes(filterFor(id)));
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
- // The ids to load, in package order, with core added when a chosen package needs it.
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((group) => [group.id, group]));
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.groups.map((group) => group.id).filter((id) => chosen.has(id)), added };
111
+ return { ids: allIds(meta).filter((id) => chosen.has(id)), added };
74
112
  }
75
113
 
76
- // Skills CLI copies of these skills: ~/.agents/skills/<name> whose lock entry names 1aboveio/ai.
77
- export function skillsCliCopies(home, names) {
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
- if (!existsSync(lockPath)) return [];
80
- const lock = JSON.parse(readFileSync(lockPath, 'utf8')).skills ?? {};
81
- return names.filter((name) => lock[name]?.source === '1aboveio/ai' && existsSync(join(home, '.agents/skills', name, 'SKILL.md')));
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
- // Returns { settings, notes } with the package entry narrowed to `ids` and, when `hide` is given
85
- // (personal settings), the Skills CLI copies of the loaded skills hidden from Pi. Hide entries this
86
- // command wrote before are recomputed, so deselecting a package shows its copies again.
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 everything = ids.length === meta.groups.length;
94
- if (typeof packages[index] === 'string') packages[index] = everything ? source : { source, skills: ids.map(filterFor) };
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 = ids.map(filterFor);
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 agentsSkills = join(home, '.agents/skills');
105
- const ours = new Set(meta.groups.flatMap((group) => group.skills).map((name) => `-${join(agentsSkills, name)}`));
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(process.env.PI_CODING_AGENT_DIR ? resolve(process.env.PI_CODING_AGENT_DIR) : join(home, '.pi/agent'), 'settings.json');
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 total = meta.groups.reduce((sum, group) => sum + group.skills.length, 0);
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 === meta.groups.length)} ${'all'.padEnd(16)} ${total} skills — every package`,
126
- ...meta.groups.map((group, i) => ` ${i + 1}. ${mark(ids.includes(group.id))} ${group.id.padEnd(16)} ${group.skills.length} skills — ${group.description}`),
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 : meta.groups.map((group) => group.id);
139
- if (tokens.some((token) => token === '0' || token === 'all')) return meta.groups.map((group) => group.id);
140
- return tokens.map((token) => (/^\d+$/.test(token) ? meta.groups[Number(token) - 1]?.id ?? token : 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
- const entry = (settings.packages ?? []).find((e) => matchesSource(entrySource(e) ?? '', meta.name));
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, entry ? currentSelection(entry, meta) : [], input, output);
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.1",
3
+ "version": "1.0.3",
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",
@@ -54,6 +59,7 @@
54
59
  "needsCore": false,
55
60
  "skills": [
56
61
  "better-review",
62
+ "contributing",
57
63
  "e2e-test",
58
64
  "engineering-runtime",
59
65
  "ensure-coverage",
@@ -94,6 +100,7 @@
94
100
  "app-debug",
95
101
  "better-auth",
96
102
  "data-catalog",
103
+ "data-quality",
97
104
  "ios-foundation",
98
105
  "monorepo",
99
106
  "nextjs-fullstack",
@@ -139,7 +146,113 @@
139
146
  ]
140
147
  }
141
148
  ],
142
- "digest": "0ff8c0fb72780cd0c50dfbeb86b50015c24ea65a76541075531b52e627d221a8"
149
+ "external": [
150
+ {
151
+ "id": "matt-pocock",
152
+ "description": "Matt Pocock's engineering skills: TDD, domain modeling, codebase design, grilling, triage and specs.",
153
+ "repository": "https://github.com/mattpocock/skills",
154
+ "lockSource": "mattpocock/skills",
155
+ "revision": "ed37663cc5fbef691ddfecd080dff42f7e7e350d",
156
+ "archive": "https://codeload.github.com/mattpocock/skills/tar.gz/ed37663cc5fbef691ddfecd080dff42f7e7e350d",
157
+ "root": "skills-ed37663cc5fbef691ddfecd080dff42f7e7e350d",
158
+ "license": "LICENSE",
159
+ "skills": [
160
+ {
161
+ "name": "batch-grill-me",
162
+ "path": "skills/in-progress/batch-grill-me"
163
+ },
164
+ {
165
+ "name": "code-review",
166
+ "path": "skills/engineering/code-review"
167
+ },
168
+ {
169
+ "name": "codebase-design",
170
+ "path": "skills/engineering/codebase-design"
171
+ },
172
+ {
173
+ "name": "diagnosing-bugs",
174
+ "path": "skills/engineering/diagnosing-bugs"
175
+ },
176
+ {
177
+ "name": "domain-modeling",
178
+ "path": "skills/engineering/domain-modeling"
179
+ },
180
+ {
181
+ "name": "grill-me",
182
+ "path": "skills/productivity/grill-me"
183
+ },
184
+ {
185
+ "name": "grill-with-docs",
186
+ "path": "skills/engineering/grill-with-docs"
187
+ },
188
+ {
189
+ "name": "grilling",
190
+ "path": "skills/productivity/grilling"
191
+ },
192
+ {
193
+ "name": "handoff",
194
+ "path": "skills/productivity/handoff"
195
+ },
196
+ {
197
+ "name": "improve-codebase-architecture",
198
+ "path": "skills/engineering/improve-codebase-architecture"
199
+ },
200
+ {
201
+ "name": "prototype",
202
+ "path": "skills/engineering/prototype"
203
+ },
204
+ {
205
+ "name": "research",
206
+ "path": "skills/engineering/research"
207
+ },
208
+ {
209
+ "name": "resolving-merge-conflicts",
210
+ "path": "skills/engineering/resolving-merge-conflicts"
211
+ },
212
+ {
213
+ "name": "setup-matt-pocock-skills",
214
+ "path": "skills/engineering/setup-matt-pocock-skills"
215
+ },
216
+ {
217
+ "name": "tdd",
218
+ "path": "skills/engineering/tdd"
219
+ },
220
+ {
221
+ "name": "to-spec",
222
+ "path": "skills/engineering/to-spec"
223
+ },
224
+ {
225
+ "name": "to-tickets",
226
+ "path": "skills/engineering/to-tickets"
227
+ },
228
+ {
229
+ "name": "triage",
230
+ "path": "skills/engineering/triage"
231
+ },
232
+ {
233
+ "name": "wayfinder",
234
+ "path": "skills/engineering/wayfinder"
235
+ }
236
+ ]
237
+ },
238
+ {
239
+ "id": "impeccable",
240
+ "description": "impeccable frontend design skill: design, critique, audit and polish interfaces.",
241
+ "repository": "https://github.com/pbakaus/impeccable",
242
+ "lockSource": "pbakaus/impeccable",
243
+ "revision": "5a149f3fdb1b5793f10567233b1dcab98fc305fd",
244
+ "archive": "https://codeload.github.com/pbakaus/impeccable/tar.gz/5a149f3fdb1b5793f10567233b1dcab98fc305fd",
245
+ "root": "impeccable-5a149f3fdb1b5793f10567233b1dcab98fc305fd",
246
+ "license": "LICENSE",
247
+ "skills": [
248
+ {
249
+ "name": "impeccable",
250
+ "path": ".agents/skills/impeccable"
251
+ }
252
+ ]
253
+ }
254
+ ],
255
+ "digest": "2fd562d9ed92abc75cc42a5011e004bc254744244b9ea99a99df8d62461afd40"
143
256
  },
144
257
  "publishConfig": {
145
258
  "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.38.0
5
+ release-version: 0.40.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.38.0
4
+ release-version: 0.40.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.38.0
4
+ release-version: 0.40.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.38.0
4
+ release-version: 0.40.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.38.0
4
+ release-version: 0.40.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