@heihei0299/matt-skills 3.0.0 → 3.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/bin/cli.js CHANGED
@@ -6,12 +6,12 @@ import { fileURLToPath } from 'node:url';
6
6
  import prompts from 'prompts';
7
7
  import {
8
8
  PROPRIETARY_SKILLS,
9
- isDefaultProgrammingSkill,
10
- isDistributableProprietarySkill,
11
9
  isDistributableSkill,
12
10
  isRepoLocalSkill,
13
11
  REPO_LOCAL_SKILLS,
14
12
  } from './skill-boundaries.js';
13
+ import { resolveSkillNames } from './skill-selection.js';
14
+ import { loadSkillSet } from './skill-config.js';
15
15
 
16
16
  const SKILLS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '.agents', 'skills');
17
17
  const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'template');
@@ -19,32 +19,15 @@ const ENGINEERING_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)),
19
19
  const REQUIRED_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'config', 'required.json');
20
20
  let ENGINEERING_SKILLS = null;
21
21
  async function loadEngineeringSkills() {
22
- if (ENGINEERING_SKILLS) return ENGINEERING_SKILLS;
23
- try {
24
- const raw = await readFile(ENGINEERING_PATH, 'utf8');
25
- ENGINEERING_SKILLS = new Set(JSON.parse(raw));
26
- } catch {
27
- ENGINEERING_SKILLS = new Set(['ask-matt','code-review','codebase-design','diagnosing-bugs','domain-modeling','grill-with-docs','implement','improve-codebase-architecture','prototype','research','resolving-merge-conflicts','setup-matt-pocock-skills','tdd','to-spec','to-tickets','triage','wayfinder','wizard']);
28
- }
22
+ if (!ENGINEERING_SKILLS) ENGINEERING_SKILLS = await loadSkillSet(ENGINEERING_PATH, 'engineering');
29
23
  return ENGINEERING_SKILLS;
30
24
  }
25
+
31
26
  let REQUIRED_SKILLS = null;
32
27
  async function loadRequiredSkills() {
33
- if (REQUIRED_SKILLS) return REQUIRED_SKILLS;
34
- try {
35
- const raw = await readFile(REQUIRED_PATH, 'utf8');
36
- REQUIRED_SKILLS = new Set(JSON.parse(raw));
37
- } catch {
38
- REQUIRED_SKILLS = new Set(['grilling', 'grill-me', 'handoff']);
39
- }
28
+ if (!REQUIRED_SKILLS) REQUIRED_SKILLS = await loadSkillSet(REQUIRED_PATH, 'required');
40
29
  return REQUIRED_SKILLS;
41
30
  }
42
- function isProgrammingSkill(name, engineering, required) {
43
- return isDefaultProgrammingSkill(name, engineering, required);
44
- }
45
- function isProgrammingAll(name, engineering) {
46
- return isDistributableProprietarySkill(name) || engineering.has(name);
47
- }
48
31
  process.stdout.on('error', (err) => {
49
32
  if (err.code === 'EPIPE') process.exit(0);
50
33
  throw err;
@@ -156,20 +139,22 @@ function parseFrontmatter(text) {
156
139
  return fields;
157
140
  }
158
141
 
159
- async function listSkillNames({ onlyProgramming = false } = {}) {
142
+ async function listAvailableSkillNames() {
160
143
  const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
161
- let names = entries
144
+ return entries
162
145
  .filter((entry) => entry.isDirectory())
163
146
  .filter((entry) => !entry.name.endsWith('.bak'))
164
147
  .filter((entry) => entry.name !== 'skill-creator' && entry.name !== '.git')
165
- .filter((entry) => !isRepoLocalSkill(entry.name))
166
148
  .map((entry) => entry.name);
167
- if (onlyProgramming) {
168
- const engineering = await loadEngineeringSkills();
169
- const required = await loadRequiredSkills();
170
- names = names.filter((name) => isProgrammingSkill(name, engineering, required));
171
- }
172
- return names.sort();
149
+ }
150
+
151
+ async function listSkillNames({ onlyProgramming = false } = {}) {
152
+ return resolveSkillNames({
153
+ availableNames: await listAvailableSkillNames(),
154
+ mode: onlyProgramming ? 'default' : 'all',
155
+ engineering: onlyProgramming ? await loadEngineeringSkills() : [],
156
+ required: onlyProgramming ? await loadRequiredSkills() : [],
157
+ });
173
158
  }
174
159
 
175
160
  async function listSkills({ onlyProgramming = false } = {}) {
@@ -249,11 +234,8 @@ async function promptSkills(skills) {
249
234
 
250
235
  async function installCommand({ dest, all, force, tools, global }) {
251
236
  const onlyProgramming = !all;
252
- const engineering = onlyProgramming ? await loadEngineeringSkills() : null;
253
- const required = onlyProgramming ? await loadRequiredSkills() : null;
254
237
  const skillNames = await listSkillNames({ onlyProgramming });
255
- const skillsAll = await listSkills({ onlyProgramming: false });
256
- const skills = onlyProgramming ? skillsAll.filter(s => isProgrammingSkill(s.name, engineering, required)) : skillsAll;
238
+ const skills = await listSkills({ onlyProgramming });
257
239
  let targets;
258
240
  if (dest) {
259
241
  targets = [{ tool: null, dir: path.resolve(process.cwd(), dest) }];
@@ -338,11 +320,10 @@ async function initCommand({ dest, all }) {
338
320
  installed = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== '.git' && e.name !== 'skill-creator' && !isRepoLocalSkill(e.name)).length;
339
321
  } catch {}
340
322
  const allSkillsFull = await listSkills({ onlyProgramming: false });
341
- const engineeringForStats = await loadEngineeringSkills();
342
- const requiredForStats = await loadRequiredSkills();
343
- const programmingCount = allSkillsFull.filter(s => isProgrammingSkill(s.name, engineeringForStats, requiredForStats)).length;
323
+ const programmingSkills = await listSkills({ onlyProgramming: true });
324
+ const programmingCount = programmingSkills.length;
344
325
  const upstreamFull = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name)).length;
345
- const upstreamProg = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name) && (engineeringForStats.has(s.name) || requiredForStats.has(s.name))).length;
326
+ const upstreamProg = programmingSkills.filter((s) => !PROPRIETARY_SKILLS.has(s.name)).length;
346
327
  const displayTotal = onlyProgramming ? programmingCount : allSkillsFull.length;
347
328
  const displayUpstream = onlyProgramming ? upstreamProg : upstreamFull;
348
329
  if (path.resolve(skillsDir) === path.resolve(SKILLS_DIR)) {
@@ -46,14 +46,6 @@ export function isDefaultProprietarySkill(name) {
46
46
  return DEFAULT_PROPRIETARY_SKILLS.has(name);
47
47
  }
48
48
 
49
- export function isDefaultProgrammingSkill(name, engineering, required) {
50
- return (
51
- DEFAULT_PROPRIETARY_SKILLS.has(name) ||
52
- engineering.has(name) ||
53
- (required && required.has(name))
54
- );
55
- }
56
-
57
49
  export function isDistributableSkill(name, knownNames) {
58
50
  if (!knownNames) return isDistributableProprietarySkill(name);
59
51
  const known = knownNames instanceof Set ? knownNames : new Set(knownNames);
@@ -0,0 +1,21 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ export async function loadSkillSet(file, label) {
4
+ let raw;
5
+ try {
6
+ raw = await readFile(file, 'utf8');
7
+ } catch (error) {
8
+ throw new Error(`unable to read ${label} skill config: ${error.message}`);
9
+ }
10
+
11
+ let value;
12
+ try {
13
+ value = JSON.parse(raw);
14
+ } catch (error) {
15
+ throw new Error(`invalid ${label} skill config: ${error.message}`);
16
+ }
17
+ if (!Array.isArray(value) || value.some((name) => typeof name !== 'string')) {
18
+ throw new Error(`invalid ${label} skill config: expected an array of strings`);
19
+ }
20
+ return new Set(value);
21
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ DEFAULT_PROPRIETARY_SKILLS,
3
+ isDistributableSkill,
4
+ } from './skill-boundaries.js';
5
+
6
+ function asSet(value) {
7
+ return value instanceof Set ? new Set(value) : new Set(value ?? []);
8
+ }
9
+
10
+ export function resolveSkillNames({ availableNames, mode = 'default', engineering, required }) {
11
+ if (mode !== 'default' && mode !== 'all') {
12
+ throw new Error(`unknown skill selection mode: ${mode}`);
13
+ }
14
+
15
+ const available = asSet(availableNames);
16
+ const candidates = mode === 'all'
17
+ ? available
18
+ : new Set([
19
+ ...asSet(engineering),
20
+ ...asSet(required),
21
+ ...DEFAULT_PROPRIETARY_SKILLS,
22
+ ]);
23
+
24
+ return [...candidates]
25
+ .filter((name) => available.has(name) && isDistributableSkill(name, available))
26
+ .sort();
27
+ }
@@ -0,0 +1,5 @@
1
+ [
2
+ "grilling",
3
+ "grill-me",
4
+ "handoff"
5
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heihei0299/matt-skills",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Agent skills + 项目配置模板:一条命令初始化 opencode / pi-agent 项目(含 mattpocock/skills 上游技能)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "scripts/codex-smoke.js",
15
15
  "config/proprietary.json",
16
16
  "config/engineering.json",
17
+ "config/required.json",
17
18
  "README.md"
18
19
  ],
19
20
  "scripts": {
@@ -6,6 +6,7 @@ import path from 'node:path';
6
6
  import os from 'node:os';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { PROPRIETARY_SKILLS } from '../bin/skill-boundaries.js';
9
+ import { loadSkillSet } from '../bin/skill-config.js';
9
10
 
10
11
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
12
  const LOCAL_SKILLS_DIR = path.join(ROOT, '.agents', 'skills');
@@ -23,20 +24,10 @@ async function loadProprietary() {
23
24
  }
24
25
 
25
26
  async function loadEngineering() {
26
- try {
27
- const raw = await readFile(ENGINEERING_PATH, 'utf8');
28
- return new Set(JSON.parse(raw));
29
- } catch {
30
- return new Set(['ask-matt','code-review','codebase-design','diagnosing-bugs','domain-modeling','grill-with-docs','implement','improve-codebase-architecture','prototype','research','resolving-merge-conflicts','setup-matt-pocock-skills','tdd','to-spec','to-tickets','triage','wayfinder','wizard']);
31
- }
27
+ return loadSkillSet(ENGINEERING_PATH, 'engineering');
32
28
  }
33
29
  async function loadRequired() {
34
- try {
35
- const raw = await readFile(REQUIRED_PATH, 'utf8');
36
- return new Set(JSON.parse(raw));
37
- } catch {
38
- return new Set(['grilling', 'grill-me', 'handoff']);
39
- }
30
+ return loadSkillSet(REQUIRED_PATH, 'required');
40
31
  }
41
32
 
42
33
  async function hashFile(filePath) {