prizmkit 1.0.17 → 1.0.19

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.17",
3
- "bundledAt": "2026-03-15T14:53:53.793Z",
4
- "bundledFrom": "527811d"
2
+ "frameworkVersion": "1.0.19",
3
+ "bundledAt": "2026-03-15T15:17:35.921Z",
4
+ "bundledFrom": "7fb7d0a"
5
5
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.17",
2
+ "version": "1.0.19",
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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
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": {
@@ -1,36 +1,18 @@
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
+ * 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.
9
9
  */
10
10
 
11
11
  import { execSync } from 'node:child_process';
12
12
  import path from 'path';
13
13
  import fs from 'fs-extra';
14
14
 
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
- ];
23
-
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
- };
30
-
31
15
  /**
32
- * Install an external skill via `npx skills add`, then prune unwanted platform symlinks.
33
- *
34
16
  * @param {Object} skill - Skill definition from external_skills.known
35
17
  * skill.repo {string} - GitHub URL
36
18
  * skill.name {string} - Skill name as listed in the repo
@@ -46,35 +28,112 @@ export async function installExternalSkill(skill, platform, projectRoot, dryRun)
46
28
  const cmd = `npx skills add ${skill.repo} --skill ${skill.name} --yes`;
47
29
 
48
30
  if (dryRun) {
49
- const keepDirs = KEEP_DIRS[platform] || KEEP_DIRS.claude;
31
+ const targets = getTargetDirs(platform, projectRoot)
32
+ .map(d => path.relative(projectRoot, path.join(d, skill.name, 'SKILL.md')));
50
33
  console.log(` [dry-run] ${cmd}`);
51
- console.log(` [dry-run] keep symlinks: ${keepDirs.map(d => path.join(d, skill.name)).join(', ')}`);
34
+ for (const t of targets) console.log(` [dry-run] ${t}`);
52
35
  return;
53
36
  }
54
37
 
55
- execSync(cmd, {
56
- cwd: projectRoot,
57
- stdio: 'pipe',
58
- timeout: 60000,
59
- });
38
+ // stdio: 'pipe' → npx skills runs non-interactively, writes only .agents/skills/<name>/SKILL.md
39
+ execSync(cmd, { cwd: projectRoot, stdio: 'pipe', timeout: 60000 });
60
40
 
61
- // Prune symlinks for platforms not selected
62
- const keepDirs = new Set(KEEP_DIRS[platform] || KEEP_DIRS.claude);
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
+ }
58
+
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(() => {});
62
+ }
63
+
64
+ /**
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'
70
+ */
71
+ export async function cleanExternalSkillArtifacts(projectRoot, platform) {
72
+ const keepDirs = new Set(
73
+ getTargetDirs(platform, projectRoot).map(d => path.resolve(d))
74
+ );
63
75
 
64
- for (const dir of ALL_PLATFORM_DIRS) {
65
- const linkPath = path.join(projectRoot, dir, skill.name);
66
- if (keepDirs.has(dir)) continue;
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
+ }
106
+ }
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;
67
117
  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);
118
+ const entries = await fs.readdir(abs);
119
+ if (entries.length === 0) {
120
+ await fs.remove(abs);
75
121
  }
76
- } catch {
77
- // not present, skip
78
- }
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
+ }
135
+ if (platform === 'codebuddy') {
136
+ return [path.join(projectRoot, '.codebuddy', 'skills')];
79
137
  }
138
+ return [path.join(projectRoot, '.claude', 'skills')];
80
139
  }
package/src/index.js CHANGED
@@ -86,11 +86,46 @@ export async function runScaffold(directory, options) {
86
86
  });
87
87
 
88
88
  // 1.5. AI CLI 命令配置(独立于平台,决定实际运行的可执行命令)
89
- const aiCli = await input({
90
- message: '底层 AI CLI 可执行命令 (可自定义,如 claude-internal --dangerously-skip-permissions):',
91
- default: detected.suggestedCli,
89
+ const cliChoices = [
90
+ {
91
+ name: `cbc — CodeBuddy CLI${detected.cbc ? chalk.green(' ✓ 已安装') : chalk.gray(' (未检测到)')}`,
92
+ value: 'cbc',
93
+ },
94
+ {
95
+ name: `claude — Claude Code${detected.claude ? chalk.green(' ✓ 已安装') : chalk.gray(' (未检测到)')}`,
96
+ value: 'claude',
97
+ },
98
+ {
99
+ name: '自定义 — 输入其他命令',
100
+ value: '__custom__',
101
+ },
102
+ {
103
+ name: '跳过 — 稍后手动配置',
104
+ value: '',
105
+ },
106
+ ];
107
+
108
+ // 如果检测到的推荐命令不在固定列表里,默认选"自定义"
109
+ const knownCliValues = ['cbc', 'claude'];
110
+ const cliDefault = knownCliValues.includes(detected.suggestedCli)
111
+ ? detected.suggestedCli
112
+ : (detected.suggestedCli ? '__custom__' : '');
113
+
114
+ const cliSelection = await select({
115
+ message: '选择底层 AI CLI 可执行命令:',
116
+ choices: cliChoices,
117
+ default: cliDefault,
92
118
  });
93
119
 
120
+ let aiCli = cliSelection;
121
+ if (cliSelection === '__custom__') {
122
+ aiCli = await input({
123
+ message: '输入自定义 AI CLI 命令:',
124
+ default: knownCliValues.includes(detected.suggestedCli) ? '' : detected.suggestedCli,
125
+ validate: (v) => v.trim() ? true : '请输入命令',
126
+ });
127
+ }
128
+
94
129
  // 2. 选择技能套件
95
130
  const skillSuiteChoice = await select({
96
131
  message: '选择技能套件:',
package/src/scaffold.js CHANGED
@@ -697,7 +697,7 @@ 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 外部技能:'));
@@ -705,7 +705,6 @@ export async function scaffold(config) {
705
705
  const skillDef = knownSkills.find(s => s.name === name);
706
706
  if (skillDef) {
707
707
  try {
708
- // npx skills add handles installation; we pass platform to prune unwanted symlinks
709
708
  await installExternalSkill(skillDef, platform, projectRoot, dryRun);
710
709
  console.log(chalk.green(` ✓ ${name} (external)`));
711
710
  } catch (e) {
@@ -713,6 +712,9 @@ export async function scaffold(config) {
713
712
  }
714
713
  }
715
714
  }
715
+ if (!dryRun) {
716
+ await cleanExternalSkillArtifacts(projectRoot, platform);
717
+ }
716
718
  }
717
719
 
718
720
  // 10. Git pre-commit hook