prizmkit 1.0.16 → 1.0.17
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/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/lib/common.sh +13 -10
- package/bundled/skills/_metadata.json +6 -25
- package/package.json +1 -1
- package/src/clean.js +108 -9
- package/src/external-skills.js +61 -52
- package/src/scaffold.js +6 -7
package/bundled/VERSION.json
CHANGED
|
@@ -32,9 +32,10 @@ log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"
|
|
|
32
32
|
# PLATFORM
|
|
33
33
|
# PRIZMKIT_PLATFORM
|
|
34
34
|
prizm_detect_cli_and_platform() {
|
|
35
|
+
local _raw_cli=""
|
|
36
|
+
|
|
35
37
|
if [[ -n "${AI_CLI:-}" ]]; then
|
|
36
|
-
|
|
37
|
-
# Read from .prizmkit/config.json if present
|
|
38
|
+
_raw_cli="$AI_CLI"
|
|
38
39
|
elif [[ -f ".prizmkit/config.json" ]]; then
|
|
39
40
|
_config_ai_cli=$(python3 -c "
|
|
40
41
|
import json, sys
|
|
@@ -46,31 +47,33 @@ try:
|
|
|
46
47
|
except: pass
|
|
47
48
|
" 2>/dev/null || true)
|
|
48
49
|
if [[ -n "$_config_ai_cli" ]]; then
|
|
49
|
-
|
|
50
|
+
_raw_cli="$_config_ai_cli"
|
|
50
51
|
elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
|
|
51
|
-
|
|
52
|
+
_raw_cli="$CODEBUDDY_CLI"
|
|
52
53
|
elif command -v cbc &>/dev/null; then
|
|
53
|
-
|
|
54
|
+
_raw_cli="cbc"
|
|
54
55
|
elif command -v claude &>/dev/null; then
|
|
55
|
-
|
|
56
|
+
_raw_cli="claude"
|
|
56
57
|
else
|
|
57
58
|
echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
|
|
58
59
|
exit 1
|
|
59
60
|
fi
|
|
60
61
|
elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
|
|
61
|
-
|
|
62
|
+
_raw_cli="$CODEBUDDY_CLI"
|
|
62
63
|
elif command -v cbc &>/dev/null; then
|
|
63
|
-
|
|
64
|
+
_raw_cli="cbc"
|
|
64
65
|
elif command -v claude &>/dev/null; then
|
|
65
|
-
|
|
66
|
+
_raw_cli="claude"
|
|
66
67
|
else
|
|
67
68
|
echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
|
|
68
69
|
exit 1
|
|
69
70
|
fi
|
|
70
71
|
|
|
72
|
+
CLI_CMD="$_raw_cli"
|
|
73
|
+
|
|
71
74
|
if [[ -n "${PRIZMKIT_PLATFORM:-}" ]]; then
|
|
72
75
|
PLATFORM="$PRIZMKIT_PLATFORM"
|
|
73
|
-
elif [[ "$
|
|
76
|
+
elif [[ "$_raw_cli" == *"claude"* ]]; then
|
|
74
77
|
PLATFORM="claude"
|
|
75
78
|
else
|
|
76
79
|
PLATFORM="codebuddy"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.17",
|
|
3
3
|
"skills": {
|
|
4
4
|
"prizm-kit": {
|
|
5
5
|
"description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
|
|
@@ -335,43 +335,24 @@
|
|
|
335
335
|
}
|
|
336
336
|
},
|
|
337
337
|
"external_skills": {
|
|
338
|
-
"registries": [
|
|
339
|
-
{
|
|
340
|
-
"name": "skills.sh",
|
|
341
|
-
"url": "https://skills.sh/registry.json",
|
|
342
|
-
"description": "Community skill registry"
|
|
343
|
-
}
|
|
344
|
-
],
|
|
345
338
|
"known": [
|
|
346
339
|
{
|
|
347
|
-
"name": "find-
|
|
348
|
-
"description": "
|
|
349
|
-
"
|
|
350
|
-
"registry": "skills.sh",
|
|
340
|
+
"name": "find-skills",
|
|
341
|
+
"description": "Helps discover and install agent skills by capability",
|
|
342
|
+
"repo": "https://github.com/vercel-labs/skills",
|
|
351
343
|
"tags": [
|
|
352
344
|
"search",
|
|
353
345
|
"utility"
|
|
354
346
|
]
|
|
355
347
|
},
|
|
356
348
|
{
|
|
357
|
-
"name": "
|
|
349
|
+
"name": "ui-ux-pro-max",
|
|
358
350
|
"description": "UI/UX design review and suggestions",
|
|
359
|
-
"
|
|
360
|
-
"registry": "skills.sh",
|
|
351
|
+
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill",
|
|
361
352
|
"tags": [
|
|
362
353
|
"design",
|
|
363
354
|
"frontend"
|
|
364
355
|
]
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
"name": "thinkthought",
|
|
368
|
-
"description": "Structured reasoning and problem decomposition",
|
|
369
|
-
"source": "https://skills.sh/skills/thinkthought/SKILL.md",
|
|
370
|
-
"registry": "skills.sh",
|
|
371
|
-
"tags": [
|
|
372
|
-
"reasoning",
|
|
373
|
-
"universal"
|
|
374
|
-
]
|
|
375
356
|
}
|
|
376
357
|
]
|
|
377
358
|
}
|
package/package.json
CHANGED
package/src/clean.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
+
import { loadMetadata, loadRulesMetadata } from './metadata.js';
|
|
4
5
|
|
|
5
6
|
async function removePath(targetPath, dryRun) {
|
|
6
7
|
if (!await fs.pathExists(targetPath)) {
|
|
@@ -13,6 +14,38 @@ async function removePath(targetPath, dryRun) {
|
|
|
13
14
|
return { path: targetPath, removed: true };
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Remove known PrizmKit files from a shared directory (skills, agents, rules, commands).
|
|
19
|
+
* Leaves user-created files untouched. Removes the parent dir if it becomes empty.
|
|
20
|
+
*/
|
|
21
|
+
async function removeKnownEntries(parentDir, entries, dryRun) {
|
|
22
|
+
const results = [];
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const abs = path.join(parentDir, entry);
|
|
26
|
+
if (!await fs.pathExists(abs)) continue;
|
|
27
|
+
|
|
28
|
+
if (dryRun) {
|
|
29
|
+
results.push({ path: abs, removed: false, reason: 'dry_run' });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await fs.remove(abs);
|
|
34
|
+
results.push({ path: abs, removed: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Remove parent dir only if it now exists and is empty
|
|
38
|
+
if (!dryRun && await fs.pathExists(parentDir)) {
|
|
39
|
+
const remaining = await fs.readdir(parentDir);
|
|
40
|
+
if (remaining.length === 0) {
|
|
41
|
+
await fs.remove(parentDir);
|
|
42
|
+
results.push({ path: parentDir, removed: true, note: '(empty dir)' });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
|
|
16
49
|
async function cleanGitignore(projectRoot, dryRun) {
|
|
17
50
|
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
18
51
|
if (!await fs.pathExists(gitignorePath)) {
|
|
@@ -48,30 +81,78 @@ export async function runClean(directory, options = {}) {
|
|
|
48
81
|
console.log(` 预览模式: ${dryRun ? chalk.yellow('是') : chalk.green('否')}`);
|
|
49
82
|
console.log('');
|
|
50
83
|
|
|
51
|
-
|
|
84
|
+
// Load metadata to know exactly which files PrizmKit owns
|
|
85
|
+
const [metadata, rulesMeta] = await Promise.all([
|
|
86
|
+
loadMetadata().catch(() => ({ skills: {}, external_skills: { known: [] } })),
|
|
87
|
+
loadRulesMetadata().catch(() => ({ rules: {} })),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const skillNames = Object.keys(metadata.skills);
|
|
91
|
+
const externalSkillNames = (metadata.external_skills?.known || []).map(s => s.name);
|
|
92
|
+
const allSkillNames = [...new Set([...skillNames, ...externalSkillNames])];
|
|
93
|
+
|
|
94
|
+
// Agent file names installed by PrizmKit
|
|
95
|
+
const agentFiles = [
|
|
96
|
+
'prizm-dev-team-coordinator.md',
|
|
97
|
+
'prizm-dev-team-dev.md',
|
|
98
|
+
'prizm-dev-team-pm.md',
|
|
99
|
+
'prizm-dev-team-reviewer.md',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// Rule file names installed by PrizmKit (basename without category prefix)
|
|
103
|
+
const ruleFileNames = Object.keys(metadata_rules_to_filenames(rulesMeta));
|
|
104
|
+
|
|
105
|
+
const results = [];
|
|
106
|
+
|
|
107
|
+
// ── Full removes (100% PrizmKit-owned paths) ──────────────────────────────
|
|
108
|
+
const fullRemoveTargets = [
|
|
52
109
|
'.prizmkit',
|
|
53
110
|
'.dev-team',
|
|
54
111
|
'dev-pipeline',
|
|
112
|
+
'.prizm-docs', // AI-generated project context docs
|
|
55
113
|
'CODEBUDDY.md',
|
|
56
114
|
'CLAUDE.md',
|
|
57
|
-
path.join('.codebuddy', 'skills'),
|
|
58
|
-
path.join('.codebuddy', 'agents'),
|
|
59
115
|
path.join('.codebuddy', 'settings.json'),
|
|
60
|
-
path.join('.claude', 'commands'),
|
|
61
|
-
path.join('.claude', 'agents'),
|
|
62
|
-
path.join('.claude', 'rules'),
|
|
63
116
|
path.join('.claude', 'settings.json'),
|
|
64
117
|
path.join('.claude', 'team-info.json'),
|
|
65
118
|
];
|
|
66
119
|
|
|
67
|
-
const
|
|
68
|
-
for (const rel of targets) {
|
|
120
|
+
for (const rel of fullRemoveTargets) {
|
|
69
121
|
const abs = path.join(projectRoot, rel);
|
|
70
122
|
results.push(await removePath(abs, dryRun));
|
|
71
123
|
}
|
|
72
124
|
|
|
125
|
+
// ── Selective removes (shared dirs — only remove PrizmKit-known entries) ──
|
|
126
|
+
|
|
127
|
+
// .claude/commands/ — skills installed as .md files or subdirs
|
|
128
|
+
const claudeCommandsDir = path.join(projectRoot, '.claude', 'commands');
|
|
129
|
+
const claudeCommandEntries = allSkillNames.flatMap(name => [name, `${name}.md`]);
|
|
130
|
+
results.push(...await removeKnownEntries(claudeCommandsDir, claudeCommandEntries, dryRun));
|
|
131
|
+
|
|
132
|
+
// .codebuddy/skills/ — skills installed as subdirectories
|
|
133
|
+
const cbSkillsDir = path.join(projectRoot, '.codebuddy', 'skills');
|
|
134
|
+
results.push(...await removeKnownEntries(cbSkillsDir, allSkillNames, dryRun));
|
|
135
|
+
|
|
136
|
+
// .claude/agents/ — PrizmKit agent files
|
|
137
|
+
const claudeAgentsDir = path.join(projectRoot, '.claude', 'agents');
|
|
138
|
+
results.push(...await removeKnownEntries(claudeAgentsDir, agentFiles, dryRun));
|
|
139
|
+
|
|
140
|
+
// .codebuddy/agents/ — same agent files
|
|
141
|
+
const cbAgentsDir = path.join(projectRoot, '.codebuddy', 'agents');
|
|
142
|
+
results.push(...await removeKnownEntries(cbAgentsDir, agentFiles, dryRun));
|
|
143
|
+
|
|
144
|
+
// .claude/rules/ — PrizmKit rule .md files
|
|
145
|
+
const claudeRulesDir = path.join(projectRoot, '.claude', 'rules');
|
|
146
|
+
results.push(...await removeKnownEntries(claudeRulesDir, ruleFileNames.map(n => `${n}.md`), dryRun));
|
|
147
|
+
|
|
148
|
+
// .codebuddy/rules/ — PrizmKit rule .mdc files
|
|
149
|
+
const cbRulesDir = path.join(projectRoot, '.codebuddy', 'rules');
|
|
150
|
+
results.push(...await removeKnownEntries(cbRulesDir, ruleFileNames.map(n => `${n}.mdc`), dryRun));
|
|
151
|
+
|
|
152
|
+
// ── .gitignore cleanup ────────────────────────────────────────────────────
|
|
73
153
|
const gitignoreResult = await cleanGitignore(projectRoot, dryRun);
|
|
74
154
|
|
|
155
|
+
// ── Global team (optional) ────────────────────────────────────────────────
|
|
75
156
|
if (cleanGlobalTeam) {
|
|
76
157
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
77
158
|
if (homeDir) {
|
|
@@ -80,13 +161,15 @@ export async function runClean(directory, options = {}) {
|
|
|
80
161
|
}
|
|
81
162
|
}
|
|
82
163
|
|
|
164
|
+
// ── Output ────────────────────────────────────────────────────────────────
|
|
83
165
|
const removed = results.filter(r => r.removed).length;
|
|
84
166
|
const found = results.filter(r => r.reason !== 'not_found').length;
|
|
85
167
|
|
|
86
168
|
console.log(chalk.bold(' 清理结果:'));
|
|
87
169
|
for (const item of results) {
|
|
88
170
|
if (item.removed) {
|
|
89
|
-
|
|
171
|
+
const note = item.note ? chalk.gray(` ${item.note}`) : '';
|
|
172
|
+
console.log(chalk.green(` ✓ 已删除: ${item.path}`) + note);
|
|
90
173
|
} else if (item.reason === 'dry_run') {
|
|
91
174
|
console.log(chalk.gray(` [dry-run] ${item.path}`));
|
|
92
175
|
}
|
|
@@ -107,4 +190,20 @@ export async function runClean(directory, options = {}) {
|
|
|
107
190
|
console.log(chalk.green(` 清理完成:已删除 ${removed} 项。`));
|
|
108
191
|
}
|
|
109
192
|
console.log('');
|
|
193
|
+
console.log(chalk.gray(' 注意:.prizm-docs/ 为 AI 生成的项目上下文文档,已一并清理。'));
|
|
194
|
+
console.log(chalk.gray(' 用户自定义的 agent/skill/rule 文件未受影响。'));
|
|
195
|
+
console.log('');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Extract base file names from rules metadata (strip category prefix).
|
|
200
|
+
* e.g. "prizm/prizm-documentation" -> "prizm-documentation"
|
|
201
|
+
*/
|
|
202
|
+
function metadata_rules_to_filenames(rulesMeta) {
|
|
203
|
+
const names = {};
|
|
204
|
+
for (const key of Object.keys(rulesMeta.rules || {})) {
|
|
205
|
+
const basename = key.split('/').pop();
|
|
206
|
+
names[basename] = true;
|
|
207
|
+
}
|
|
208
|
+
return names;
|
|
110
209
|
}
|
package/src/external-skills.js
CHANGED
|
@@ -1,71 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External Skills Installer
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Delegates to `npx skills add <repo> --skill <name> --yes` for installation,
|
|
5
|
+
* then removes symlinks for platforms not selected by the user.
|
|
6
|
+
*
|
|
7
|
+
* npx skills creates symlinks for ALL platforms (.claude/skills/, .codebuddy/skills/,
|
|
8
|
+
* .trae/skills/, skills/, .agent/skills/). We keep only what the user asked for.
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
|
-
import
|
|
7
|
-
import http from 'node:http';
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
8
12
|
import path from 'path';
|
|
9
13
|
import fs from 'fs-extra';
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const protocol = url.startsWith('https://') ? https : http;
|
|
20
|
-
|
|
21
|
-
const request = protocol.get(url, (res) => {
|
|
22
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
23
|
-
// Follow redirect
|
|
24
|
-
const redirectSkill = { ...skill, source: res.headers.location };
|
|
25
|
-
resolve(fetchRemoteSkill(redirectSkill));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
15
|
+
// All platform symlink dirs that `npx skills` creates (besides .agents/ canonical store)
|
|
16
|
+
const ALL_PLATFORM_DIRS = [
|
|
17
|
+
'.claude/skills',
|
|
18
|
+
'.codebuddy/skills',
|
|
19
|
+
'.trae/skills',
|
|
20
|
+
'.agent/skills',
|
|
21
|
+
'skills',
|
|
22
|
+
];
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
res.on('data', chunk => chunks.push(chunk));
|
|
36
|
-
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
37
|
-
res.on('error', reject);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
request.on('error', reject);
|
|
41
|
-
request.setTimeout(15000, () => {
|
|
42
|
-
request.destroy();
|
|
43
|
-
reject(new Error(`Timeout fetching ${url}`));
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
24
|
+
// Which dirs to KEEP per platform selection
|
|
25
|
+
const KEEP_DIRS = {
|
|
26
|
+
claude: ['.claude/skills'],
|
|
27
|
+
codebuddy: ['.codebuddy/skills'],
|
|
28
|
+
both: ['.claude/skills', '.codebuddy/skills'],
|
|
29
|
+
};
|
|
47
30
|
|
|
48
31
|
/**
|
|
49
|
-
* Install an external skill
|
|
50
|
-
*
|
|
51
|
-
* @param {
|
|
52
|
-
*
|
|
53
|
-
*
|
|
32
|
+
* Install an external skill via `npx skills add`, then prune unwanted platform symlinks.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} skill - Skill definition from external_skills.known
|
|
35
|
+
* skill.repo {string} - GitHub URL
|
|
36
|
+
* skill.name {string} - Skill name as listed in the repo
|
|
37
|
+
* @param {string} platform - 'claude' | 'codebuddy' | 'both'
|
|
38
|
+
* @param {string} projectRoot - Target project root
|
|
39
|
+
* @param {boolean} dryRun
|
|
54
40
|
*/
|
|
55
41
|
export async function installExternalSkill(skill, platform, projectRoot, dryRun) {
|
|
56
|
-
|
|
42
|
+
if (!skill.repo) {
|
|
43
|
+
throw new Error(`Skill "${skill.name}" has no repo URL defined`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const cmd = `npx skills add ${skill.repo} --skill ${skill.name} --yes`;
|
|
57
47
|
|
|
58
48
|
if (dryRun) {
|
|
49
|
+
const keepDirs = KEEP_DIRS[platform] || KEEP_DIRS.claude;
|
|
50
|
+
console.log(` [dry-run] ${cmd}`);
|
|
51
|
+
console.log(` [dry-run] keep symlinks: ${keepDirs.map(d => path.join(d, skill.name)).join(', ')}`);
|
|
59
52
|
return;
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
execSync(cmd, {
|
|
56
|
+
cwd: projectRoot,
|
|
57
|
+
stdio: 'pipe',
|
|
58
|
+
timeout: 60000,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Prune symlinks for platforms not selected
|
|
62
|
+
const keepDirs = new Set(KEEP_DIRS[platform] || KEEP_DIRS.claude);
|
|
63
|
+
|
|
64
|
+
for (const dir of ALL_PLATFORM_DIRS) {
|
|
65
|
+
const linkPath = path.join(projectRoot, dir, skill.name);
|
|
66
|
+
if (keepDirs.has(dir)) continue;
|
|
67
|
+
try {
|
|
68
|
+
const stat = await fs.lstat(linkPath);
|
|
69
|
+
if (stat.isSymbolicLink()) {
|
|
70
|
+
await fs.remove(linkPath);
|
|
71
|
+
// Remove parent dir if now empty
|
|
72
|
+
const parent = path.join(projectRoot, dir);
|
|
73
|
+
const remaining = await fs.readdir(parent).catch(() => ['_']);
|
|
74
|
+
if (remaining.length === 0) await fs.remove(parent);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// not present, skip
|
|
78
|
+
}
|
|
70
79
|
}
|
|
71
80
|
}
|
package/src/scaffold.js
CHANGED
|
@@ -704,13 +704,12 @@ export async function scaffold(config) {
|
|
|
704
704
|
for (const name of externalSkills) {
|
|
705
705
|
const skillDef = knownSkills.find(s => s.name === name);
|
|
706
706
|
if (skillDef) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
707
|
+
try {
|
|
708
|
+
// npx skills add handles installation; we pass platform to prune unwanted symlinks
|
|
709
|
+
await installExternalSkill(skillDef, platform, projectRoot, dryRun);
|
|
710
|
+
console.log(chalk.green(` ✓ ${name} (external)`));
|
|
711
|
+
} catch (e) {
|
|
712
|
+
console.log(chalk.yellow(` ⚠ ${name} 安装失败,跳过: ${e.message}`));
|
|
714
713
|
}
|
|
715
714
|
}
|
|
716
715
|
}
|