@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 +20 -39
- package/bin/skill-boundaries.js +0 -8
- package/bin/skill-config.js +21 -0
- package/bin/skill-selection.js +27 -0
- package/config/required.json +5 -0
- package/package.json +2 -1
- package/scripts/sync-upstream.js +3 -12
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)
|
|
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)
|
|
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
|
|
142
|
+
async function listAvailableSkillNames() {
|
|
160
143
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
161
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
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
|
|
342
|
-
const
|
|
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 =
|
|
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)) {
|
package/bin/skill-boundaries.js
CHANGED
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heihei0299/matt-skills",
|
|
3
|
-
"version": "3.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": {
|
package/scripts/sync-upstream.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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) {
|