prizmkit 1.0.16 → 1.0.18

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.0.16",
3
- "bundledAt": "2026-03-15T13:35:03.135Z",
4
- "bundledFrom": "d609245"
2
+ "frameworkVersion": "1.0.18",
3
+ "bundledAt": "2026-03-15T15:11:59.897Z",
4
+ "bundledFrom": "fed9f20"
5
5
  }
@@ -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
- CLI_CMD="$AI_CLI"
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
- CLI_CMD="$_config_ai_cli"
50
+ _raw_cli="$_config_ai_cli"
50
51
  elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
51
- CLI_CMD="$CODEBUDDY_CLI"
52
+ _raw_cli="$CODEBUDDY_CLI"
52
53
  elif command -v cbc &>/dev/null; then
53
- CLI_CMD="cbc"
54
+ _raw_cli="cbc"
54
55
  elif command -v claude &>/dev/null; then
55
- CLI_CMD="claude"
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
- CLI_CMD="$CODEBUDDY_CLI"
62
+ _raw_cli="$CODEBUDDY_CLI"
62
63
  elif command -v cbc &>/dev/null; then
63
- CLI_CMD="cbc"
64
+ _raw_cli="cbc"
64
65
  elif command -v claude &>/dev/null; then
65
- CLI_CMD="claude"
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 [[ "$CLI_CMD" == *"claude"* ]]; then
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.16",
2
+ "version": "1.0.18",
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-skill",
348
- "description": "Intelligent file and code search skill",
349
- "source": "https://skills.sh/skills/find-skill/SKILL.md",
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": "uiuxpromax",
349
+ "name": "ui-ux-pro-max",
358
350
  "description": "UI/UX design review and suggestions",
359
- "source": "https://skills.sh/skills/uiuxpromax/SKILL.md",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {
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
- const targets = [
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 results = [];
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
- console.log(chalk.green(` 已删除: ${item.path}`));
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
  }
@@ -1,71 +1,139 @@
1
1
  /**
2
2
  * External Skills Installer
3
- * Fetches and installs third-party skills from remote URLs.
3
+ *
4
+ * Delegates to `npx skills add <repo> --skill <name> --yes` to fetch the skill.
5
+ * When run non-interactively (stdio: 'pipe'), npx skills only writes the canonical
6
+ * .agents/skills/<name>/SKILL.md without creating any platform symlinks.
7
+ * We read that file, copy it to the selected platform dir(s), then clean up
8
+ * .agents/ and skills-lock.json.
4
9
  */
5
10
 
6
- import https from 'node:https';
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
15
  /**
12
- * Fetch content from a remote URL (supports http and https)
13
- * @param {string} url - Remote URL to fetch
14
- * @returns {Promise<string>} - Content string
16
+ * @param {Object} skill - Skill definition from external_skills.known
17
+ * skill.repo {string} - GitHub URL
18
+ * skill.name {string} - Skill name as listed in the repo
19
+ * @param {string} platform - 'claude' | 'codebuddy' | 'both'
20
+ * @param {string} projectRoot - Target project root
21
+ * @param {boolean} dryRun
15
22
  */
16
- export function fetchRemoteSkill(skill) {
17
- return new Promise((resolve, reject) => {
18
- const url = skill.source;
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
- }
23
+ export async function installExternalSkill(skill, platform, projectRoot, dryRun) {
24
+ if (!skill.repo) {
25
+ throw new Error(`Skill "${skill.name}" has no repo URL defined`);
26
+ }
28
27
 
29
- if (res.statusCode !== 200) {
30
- reject(new Error(`HTTP ${res.statusCode}: ${url}`));
31
- return;
32
- }
28
+ const cmd = `npx skills add ${skill.repo} --skill ${skill.name} --yes`;
29
+
30
+ if (dryRun) {
31
+ const targets = getTargetDirs(platform, projectRoot)
32
+ .map(d => path.relative(projectRoot, path.join(d, skill.name, 'SKILL.md')));
33
+ console.log(` [dry-run] ${cmd}`);
34
+ for (const t of targets) console.log(` [dry-run] → ${t}`);
35
+ return;
36
+ }
37
+
38
+ // stdio: 'pipe' → npx skills runs non-interactively, writes only .agents/skills/<name>/SKILL.md
39
+ execSync(cmd, { cwd: projectRoot, stdio: 'pipe', timeout: 60000 });
40
+
41
+ const canonicalSkillMd = path.join(projectRoot, '.agents', 'skills', skill.name, 'SKILL.md');
42
+ if (!await fs.pathExists(canonicalSkillMd)) {
43
+ throw new Error(`npx skills did not produce .agents/skills/${skill.name}/SKILL.md`);
44
+ }
45
+
46
+ const content = await fs.readFile(canonicalSkillMd, 'utf8');
47
+
48
+ // Copy to selected platform dir(s)
49
+ // npx skills may have created symlinks (e.g. .claude/skills/<name> -> ../../.agents/skills/<name>)
50
+ // We must remove those symlinks first so we can write a real directory with the actual content.
51
+ for (const targetDir of getTargetDirs(platform, projectRoot)) {
52
+ const skillDir = path.join(targetDir, skill.name);
53
+ // Remove any existing symlink or directory at this path before creating a real dir
54
+ await fs.remove(skillDir).catch(() => {});
55
+ await fs.ensureDir(skillDir);
56
+ await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
57
+ }
33
58
 
34
- const chunks = [];
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
- });
59
+ // Clean up npx skills artifacts after copying
60
+ await fs.remove(path.join(projectRoot, '.agents')).catch(() => {});
61
+ await fs.remove(path.join(projectRoot, 'skills-lock.json')).catch(() => {});
46
62
  }
47
63
 
48
64
  /**
49
- * Install an external skill to the target platform directory.
50
- * @param {Object} skill - Skill definition from external_skills.known
51
- * @param {string} platform - 'codebuddy' | 'claude'
52
- * @param {string} projectRoot - Target project root
53
- * @param {boolean} dryRun - If true, don't write files
65
+ * Remove all artifacts created by `npx skills` that are not needed by PrizmKit.
66
+ * Call this once after all external skills have been installed.
67
+ *
68
+ * @param {string} projectRoot
69
+ * @param {string} platform - 'claude' | 'codebuddy' | 'both'
54
70
  */
55
- export async function installExternalSkill(skill, platform, projectRoot, dryRun) {
56
- const content = await fetchRemoteSkill(skill);
71
+ export async function cleanExternalSkillArtifacts(projectRoot, platform) {
72
+ const keepDirs = new Set(
73
+ getTargetDirs(platform, projectRoot).map(d => path.resolve(d))
74
+ );
57
75
 
58
- if (dryRun) {
59
- return;
76
+ const dirsToCheck = [
77
+ '.agents',
78
+ '.agent',
79
+ '.trae',
80
+ 'skills',
81
+ '.claude/skills',
82
+ '.codebuddy/skills',
83
+ ];
84
+
85
+ for (const dir of dirsToCheck) {
86
+ const abs = path.join(projectRoot, dir);
87
+ if (keepDirs.has(path.resolve(abs))) continue;
88
+ await fs.remove(abs).catch(() => {});
89
+ }
90
+
91
+ // Remove any dangling symlinks inside kept platform skill dirs
92
+ // (npx skills creates symlinks like .claude/skills/<name> -> ../../.agents/skills/<name>)
93
+ for (const keepDir of keepDirs) {
94
+ if (!await fs.pathExists(keepDir)) continue;
95
+ let entries;
96
+ try { entries = await fs.readdir(keepDir); } catch { continue; }
97
+ for (const entry of entries) {
98
+ const entryPath = path.join(keepDir, entry);
99
+ try {
100
+ const stat = await fs.lstat(entryPath);
101
+ if (stat.isSymbolicLink()) {
102
+ await fs.remove(entryPath);
103
+ }
104
+ } catch { /* ignore */ }
105
+ }
60
106
  }
61
107
 
108
+ // Remove empty parent dirs left behind (e.g. .codebuddy/ after removing .codebuddy/skills)
109
+ const parentDirs = ['.claude', '.codebuddy', '.trae', '.agent'];
110
+ for (const dir of parentDirs) {
111
+ const abs = path.join(projectRoot, dir);
112
+ if (!await fs.pathExists(abs)) continue;
113
+ // Only remove if this platform dir is entirely unneeded
114
+ const isKept = getTargetDirs(platform, projectRoot)
115
+ .some(d => path.resolve(d).startsWith(path.resolve(abs) + path.sep));
116
+ if (isKept) continue;
117
+ try {
118
+ const entries = await fs.readdir(abs);
119
+ if (entries.length === 0) {
120
+ await fs.remove(abs);
121
+ }
122
+ } catch { /* ignore */ }
123
+ }
124
+
125
+ await fs.remove(path.join(projectRoot, 'skills-lock.json')).catch(() => {});
126
+ }
127
+
128
+ function getTargetDirs(platform, projectRoot) {
129
+ if (platform === 'both') {
130
+ return [
131
+ path.join(projectRoot, '.claude', 'skills'),
132
+ path.join(projectRoot, '.codebuddy', 'skills'),
133
+ ];
134
+ }
62
135
  if (platform === 'codebuddy') {
63
- const targetDir = path.join(projectRoot, '.codebuddy', 'skills', skill.name);
64
- await fs.ensureDir(targetDir);
65
- await fs.writeFile(path.join(targetDir, 'SKILL.md'), content);
66
- } else if (platform === 'claude') {
67
- const targetDir = path.join(projectRoot, '.claude', 'commands');
68
- await fs.ensureDir(targetDir);
69
- await fs.writeFile(path.join(targetDir, `${skill.name}.md`), content);
136
+ return [path.join(projectRoot, '.codebuddy', 'skills')];
70
137
  }
138
+ return [path.join(projectRoot, '.claude', 'skills')];
71
139
  }
package/src/scaffold.js CHANGED
@@ -697,23 +697,24 @@ export async function scaffold(config) {
697
697
 
698
698
  // 9. 安装外部 Skills(如果选择了)
699
699
  if (externalSkills?.length) {
700
- const { installExternalSkill } = await import('./external-skills.js');
700
+ const { installExternalSkill, cleanExternalSkillArtifacts } = await import('./external-skills.js');
701
701
  const metadata = await loadMetadata();
702
702
  const knownSkills = metadata.external_skills?.known || [];
703
703
  console.log(chalk.blue('\n 外部技能:'));
704
704
  for (const name of externalSkills) {
705
705
  const skillDef = knownSkills.find(s => s.name === name);
706
706
  if (skillDef) {
707
- for (const p of platforms) {
708
- try {
709
- await installExternalSkill(skillDef, p, projectRoot, dryRun);
710
- console.log(chalk.green(` ✓ ${name} (external)`));
711
- } catch (e) {
712
- console.log(chalk.yellow(` ⚠ ${name} 拉取失败,跳过: ${e.message}`));
713
- }
707
+ try {
708
+ await installExternalSkill(skillDef, platform, projectRoot, dryRun);
709
+ console.log(chalk.green(` ✓ ${name} (external)`));
710
+ } catch (e) {
711
+ console.log(chalk.yellow(` ⚠ ${name} 安装失败,跳过: ${e.message}`));
714
712
  }
715
713
  }
716
714
  }
715
+ if (!dryRun) {
716
+ await cleanExternalSkillArtifacts(projectRoot, platform);
717
+ }
717
718
  }
718
719
 
719
720
  // 10. Git pre-commit hook