prizmkit 1.0.13 → 1.0.15

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.
Files changed (84) hide show
  1. package/bin/create-prizmkit.js +4 -1
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/claude/command-adapter.js +35 -4
  4. package/bundled/adapters/claude/rules-adapter.js +6 -58
  5. package/bundled/adapters/claude/team-adapter.js +2 -2
  6. package/bundled/adapters/codebuddy/agent-adapter.js +0 -1
  7. package/bundled/adapters/codebuddy/rules-adapter.js +30 -0
  8. package/bundled/adapters/shared/frontmatter.js +3 -1
  9. package/bundled/dev-pipeline/README.md +13 -3
  10. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +10 -0
  11. package/bundled/dev-pipeline/launch-daemon.sh +18 -4
  12. package/bundled/dev-pipeline/lib/common.sh +105 -0
  13. package/bundled/dev-pipeline/retry-bug.sh +20 -2
  14. package/bundled/dev-pipeline/retry-feature.sh +20 -2
  15. package/bundled/dev-pipeline/run-bugfix.sh +57 -57
  16. package/bundled/dev-pipeline/run.sh +75 -59
  17. package/bundled/dev-pipeline/scripts/check-session-status.py +47 -2
  18. package/bundled/dev-pipeline/scripts/cleanup-logs.py +192 -0
  19. package/bundled/dev-pipeline/scripts/detect-stuck.py +15 -3
  20. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +32 -27
  21. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +23 -23
  22. package/bundled/dev-pipeline/scripts/update-feature-status.py +50 -2
  23. package/bundled/dev-pipeline/scripts/utils.py +22 -0
  24. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +18 -1
  25. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +19 -1
  26. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +18 -2
  27. package/bundled/dev-pipeline/templates/session-status-schema.json +7 -1
  28. package/bundled/dev-pipeline/tests/__init__.py +0 -0
  29. package/bundled/dev-pipeline/tests/conftest.py +133 -0
  30. package/bundled/dev-pipeline/tests/test_check_session.py +127 -0
  31. package/bundled/dev-pipeline/tests/test_cleanup_logs.py +119 -0
  32. package/bundled/dev-pipeline/tests/test_detect_stuck.py +207 -0
  33. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +181 -0
  34. package/bundled/dev-pipeline/tests/test_generate_prompt.py +190 -0
  35. package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +153 -0
  36. package/bundled/dev-pipeline/tests/test_init_pipeline.py +241 -0
  37. package/bundled/dev-pipeline/tests/test_update_bug_status.py +142 -0
  38. package/bundled/dev-pipeline/tests/test_update_feature_status.py +277 -0
  39. package/bundled/dev-pipeline/tests/test_utils.py +141 -0
  40. package/bundled/rules/USAGE.md +153 -0
  41. package/bundled/rules/_rules-metadata.json +43 -0
  42. package/bundled/rules/general/prefer-linux-commands.md +9 -0
  43. package/bundled/rules/prizm/prizm-commit-workflow.md +10 -0
  44. package/bundled/rules/prizm/prizm-documentation.md +80 -0
  45. package/bundled/rules/prizm/prizm-progressive-loading.md +11 -0
  46. package/bundled/skills/_metadata.json +162 -67
  47. package/bundled/skills/app-planner/SKILL.md +252 -499
  48. package/bundled/skills/app-planner/assets/evaluation-guide.md +44 -0
  49. package/bundled/skills/app-planner/scripts/validate-and-generate.py +143 -4
  50. package/bundled/skills/bug-planner/SKILL.md +58 -13
  51. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -7
  52. package/bundled/skills/dev-pipeline-launcher/SKILL.md +16 -7
  53. package/bundled/skills/feature-workflow/SKILL.md +175 -234
  54. package/bundled/skills/prizm-kit/SKILL.md +17 -31
  55. package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/SKILL.md +6 -7
  56. package/bundled/skills/{prizmkit-api-doc-generator → prizmkit-tool-api-doc-generator}/SKILL.md +4 -5
  57. package/bundled/skills/{prizmkit-bug-reproducer → prizmkit-tool-bug-reproducer}/SKILL.md +4 -5
  58. package/bundled/skills/{prizmkit-ci-cd-generator → prizmkit-tool-ci-cd-generator}/SKILL.md +4 -5
  59. package/bundled/skills/{prizmkit-db-migration → prizmkit-tool-db-migration}/SKILL.md +4 -5
  60. package/bundled/skills/{prizmkit-dependency-health → prizmkit-tool-dependency-health}/SKILL.md +3 -4
  61. package/bundled/skills/{prizmkit-deployment-strategy → prizmkit-tool-deployment-strategy}/SKILL.md +4 -5
  62. package/bundled/skills/{prizmkit-error-triage → prizmkit-tool-error-triage}/SKILL.md +4 -5
  63. package/bundled/skills/{prizmkit-log-analyzer → prizmkit-tool-log-analyzer}/SKILL.md +4 -5
  64. package/bundled/skills/{prizmkit-monitoring-setup → prizmkit-tool-monitoring-setup}/SKILL.md +4 -5
  65. package/bundled/skills/{prizmkit-onboarding-generator → prizmkit-tool-onboarding-generator}/SKILL.md +4 -5
  66. package/bundled/skills/{prizmkit-perf-profiler → prizmkit-tool-perf-profiler}/SKILL.md +4 -5
  67. package/bundled/skills/{prizmkit-security-audit → prizmkit-tool-security-audit}/SKILL.md +3 -4
  68. package/bundled/skills/{prizmkit-tech-debt-tracker → prizmkit-tool-tech-debt-tracker}/SKILL.md +3 -4
  69. package/bundled/skills/refactor-skill/SKILL.md +371 -0
  70. package/bundled/skills/refactor-workflow/SKILL.md +17 -119
  71. package/bundled/templates/hooks/commit-intent-claude.json +1 -1
  72. package/bundled/templates/hooks/commit-intent-codebuddy.json +1 -1
  73. package/bundled/templates/hooks/prizm-pre-commit.sh +58 -0
  74. package/bundled/templates/hooks/validate-prizm-docs.sh +84 -0
  75. package/package.json +1 -1
  76. package/src/detect-platform.js +20 -8
  77. package/src/external-skills.js +71 -0
  78. package/src/index.js +66 -8
  79. package/src/metadata.js +36 -0
  80. package/src/scaffold.js +200 -31
  81. package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +0 -356
  82. package/bundled/templates/claude-md-template.md +0 -38
  83. package/bundled/templates/codebuddy-md-template.md +0 -35
  84. /package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/assets/adr-template.md +0 -0
package/src/index.js CHANGED
@@ -5,10 +5,9 @@
5
5
  * 然后调用 scaffold.js 执行纯净安装。
6
6
  */
7
7
 
8
- import { select, confirm } from '@inquirer/prompts';
8
+ import { select, confirm, input } from '@inquirer/prompts';
9
9
  import chalk from 'chalk';
10
10
  import path from 'path';
11
- import fs from 'fs-extra';
12
11
  import { detectPlatform } from './detect-platform.js';
13
12
  import { scaffold } from './scaffold.js';
14
13
  import { loadMetadata } from './metadata.js';
@@ -34,6 +33,7 @@ export async function runScaffold(directory, options) {
34
33
  const cliStatus = [
35
34
  detected.cbc ? chalk.green('cbc ✓') : chalk.gray('cbc ✗'),
36
35
  detected.claude ? chalk.green('claude ✓') : chalk.gray('claude ✗'),
36
+ detected.claudeInternal ? chalk.green('claude-internal ✓') : chalk.gray('claude-internal ✗'),
37
37
  ].join(' ');
38
38
  console.log(` 检测到的 CLI 工具: ${cliStatus}`);
39
39
  console.log(` 目标目录: ${projectRoot}`);
@@ -54,6 +54,9 @@ export async function runScaffold(directory, options) {
54
54
  skills: options.skills || 'full',
55
55
  team: options.team !== false,
56
56
  pipeline: options.pipeline !== false,
57
+ rules: options.rules || 'recommended',
58
+ aiCli: options.aiCli || '',
59
+ externalSkills: options.externalSkills ? options.externalSkills.split(',').map(s => s.trim()).filter(Boolean) : [],
57
60
  projectRoot,
58
61
  dryRun: options.dryRun || false,
59
62
  };
@@ -62,16 +65,16 @@ export async function runScaffold(directory, options) {
62
65
 
63
66
  // === 交互式模式 ===
64
67
  try {
65
- // 1. 选择平台
68
+ // 1. 选择平台(控制安装的目录结构)
66
69
  const platform = await select({
67
- message: '选择目标平台:',
70
+ message: '选择目标平台 (决定安装的目录结构):',
68
71
  choices: [
69
72
  {
70
- name: `CodeBuddy CLI (cbc)${detected.cbc ? chalk.green(' ← 已检测到') : ''}`,
73
+ name: `CodeBuddy (.codebuddy/)${detected.cbc ? chalk.green(' ← 已检测到 cbc') : ''}`,
71
74
  value: 'codebuddy',
72
75
  },
73
76
  {
74
- name: `Claude Code CLI (claude)${detected.claude ? chalk.green(' ← 已检测到') : ''}`,
77
+ name: `Claude Code (.claude/)${(detected.claude || detected.claudeInternal) ? chalk.green(' ← 已检测到') : ''}`,
75
78
  value: 'claude',
76
79
  },
77
80
  {
@@ -82,8 +85,14 @@ export async function runScaffold(directory, options) {
82
85
  default: detected.suggested,
83
86
  });
84
87
 
88
+ // 1.5. AI CLI 命令配置(独立于平台,决定实际运行的可执行命令)
89
+ const aiCli = await input({
90
+ message: '底层 AI CLI 可执行命令 (可自定义,如 claude-internal --dangerously-skip-permissions):',
91
+ default: detected.suggestedCli,
92
+ });
93
+
85
94
  // 2. 选择技能套件
86
- const skillSuite = await select({
95
+ const skillSuiteChoice = await select({
87
96
  message: '选择技能套件:',
88
97
  choices: [
89
98
  {
@@ -98,10 +107,41 @@ export async function runScaffold(directory, options) {
98
107
  name: `Minimal — 最小可用 (${metadata.suites.minimal.skills.length} 个技能)`,
99
108
  value: 'minimal',
100
109
  },
110
+ {
111
+ name: 'Recommended — 按项目类型推荐 ▸',
112
+ value: 'recommended',
113
+ },
101
114
  ],
102
115
  default: 'full',
103
116
  });
104
117
 
118
+ let skillSuite = skillSuiteChoice;
119
+ if (skillSuiteChoice === 'recommended') {
120
+ const projectType = await select({
121
+ message: '选择项目类型:',
122
+ choices: Object.entries(metadata.recommendations || {}).map(([key, val]) => ({
123
+ name: val.label,
124
+ value: `recommended:${key}`,
125
+ })),
126
+ });
127
+ skillSuite = projectType;
128
+ }
129
+
130
+ // 2.5. 外部 Skill 选择(可选)
131
+ let selectedExternalSkills = [];
132
+ const knownExternalSkills = metadata.external_skills?.known || [];
133
+ if (knownExternalSkills.length > 0) {
134
+ const { checkbox } = await import('@inquirer/prompts');
135
+ selectedExternalSkills = await checkbox({
136
+ message: '选择要安装的第三方 Skills(可选,需要网络):',
137
+ choices: knownExternalSkills.map(s => ({
138
+ name: `${s.name} — ${s.description}`,
139
+ value: s.name,
140
+ checked: false,
141
+ })),
142
+ });
143
+ }
144
+
105
145
  // 3. 团队模式
106
146
  const team = await confirm({
107
147
  message: '启用多 Agent 团队模式 (prizm-dev-team)?',
@@ -114,12 +154,24 @@ export async function runScaffold(directory, options) {
114
154
  default: true,
115
155
  });
116
156
 
157
+ // 5. Rules 预设
158
+ const rulesPreset = await select({
159
+ message: '安装 AI 行为规则 (rules):',
160
+ choices: [
161
+ { name: 'Recommended — 全部规则 (推荐)', value: 'recommended' },
162
+ { name: 'Minimal — 仅渐进式加载 + 通用规则', value: 'minimal' },
163
+ { name: 'None — 不安装规则', value: 'none' },
164
+ ],
165
+ default: 'recommended',
166
+ });
167
+
117
168
  // 显示安装摘要
118
169
  const platformLabel = platform === 'both' ? 'CodeBuddy + Claude Code'
119
170
  : platform === 'codebuddy' ? 'CodeBuddy' : 'Claude Code';
120
171
  const suiteLabel = skillSuite === 'full' ? `Full (${totalSkills} 个)`
121
172
  : skillSuite === 'core' ? `Core (${metadata.suites.core.skills.length} 个)`
122
- : `Minimal (${metadata.suites.minimal.skills.length} 个)`;
173
+ : skillSuite === 'minimal' ? `Minimal (${metadata.suites.minimal.skills.length} 个)`
174
+ : `Recommended (${skillSuite})`;
123
175
 
124
176
  console.log('');
125
177
  console.log(chalk.bold(' 安装摘要:'));
@@ -127,6 +179,9 @@ export async function runScaffold(directory, options) {
127
179
  console.log(` 技能: ${chalk.cyan(suiteLabel)}`);
128
180
  console.log(` 团队模式: ${team ? chalk.green('启用') : chalk.gray('禁用')}`);
129
181
  console.log(` 流水线: ${pipeline ? chalk.green('启用') : chalk.gray('禁用')}`);
182
+ console.log(` 规则: ${chalk.cyan(rulesPreset)}`);
183
+ if (aiCli) console.log(` AI CLI: ${chalk.cyan(aiCli)}`);
184
+ if (selectedExternalSkills.length > 0) console.log(` 外部技能: ${chalk.cyan(selectedExternalSkills.join(', '))}`);
130
185
  console.log(` 目标目录: ${projectRoot}`);
131
186
  console.log('');
132
187
 
@@ -146,6 +201,9 @@ export async function runScaffold(directory, options) {
146
201
  skills: skillSuite,
147
202
  team,
148
203
  pipeline,
204
+ rules: rulesPreset,
205
+ aiCli: aiCli || '',
206
+ externalSkills: selectedExternalSkills,
149
207
  projectRoot,
150
208
  dryRun: options.dryRun || false,
151
209
  };
package/src/metadata.js CHANGED
@@ -51,6 +51,7 @@ export async function loadMetadata() {
51
51
 
52
52
  throw new Error(
53
53
  '无法找到 Skill 元数据文件。\n' +
54
+ `尝试路径:\n 1. ${bundledPath}\n 2. ${corePath}\n` +
54
55
  '请确保在 PrizmKit 框架目录中运行,或已执行 `npm run bundle` 打包资源。'
55
56
  );
56
57
  }
@@ -131,4 +132,39 @@ export function getAdaptersDir() {
131
132
  if (fs.pathExistsSync(coreDir)) return coreDir;
132
133
 
133
134
  throw new Error('无法找到 Adapters 目录。');
135
+ }
136
+
137
+ /**
138
+ * 获取 core rules 目录路径
139
+ */
140
+ export function getRulesDir() {
141
+ const bundledDir = path.join(getBundledRoot(), 'rules');
142
+ const coreDir = path.join(getFrameworkRoot(), 'core', 'rules');
143
+
144
+ if (fs.pathExistsSync(bundledDir)) return bundledDir;
145
+ if (fs.pathExistsSync(coreDir)) return coreDir;
146
+
147
+ throw new Error('无法找到 Rules 目录。');
148
+ }
149
+
150
+ /**
151
+ * 加载 Rules 元数据
152
+ * @returns {Promise<Object>} metadata 对象(包含 rules 和 presets)
153
+ */
154
+ export async function loadRulesMetadata() {
155
+ const bundledPath = path.join(getBundledRoot(), 'rules', '_rules-metadata.json');
156
+ const corePath = path.join(getFrameworkRoot(), 'core', 'rules', '_rules-metadata.json');
157
+
158
+ if (await fs.pathExists(bundledPath)) {
159
+ return fs.readJSON(bundledPath);
160
+ }
161
+
162
+ if (await fs.pathExists(corePath)) {
163
+ return fs.readJSON(corePath);
164
+ }
165
+
166
+ throw new Error(
167
+ '无法找到 Rules 元数据文件。\n' +
168
+ `尝试路径:\n 1. ${bundledPath}\n 2. ${corePath}`
169
+ );
134
170
  }
package/src/scaffold.js CHANGED
@@ -18,7 +18,8 @@ import {
18
18
  getTemplatesDir,
19
19
  getPipelineDir,
20
20
  getAdaptersDir,
21
- getFrameworkRoot,
21
+ getRulesDir,
22
+ loadRulesMetadata,
22
23
  } from './metadata.js';
23
24
  import { generateGitignore } from './gitignore-template.js';
24
25
 
@@ -51,6 +52,20 @@ async function loadSharedFrontmatter() {
51
52
  async function resolveSkillList(suite) {
52
53
  const metadata = await loadMetadata();
53
54
 
55
+ // 处理 recommended:<type> 格式
56
+ if (suite && suite.startsWith('recommended:')) {
57
+ const projectType = suite.split(':')[1];
58
+ const rec = metadata.recommendations?.[projectType];
59
+ if (rec) {
60
+ const baseSkills = rec.base === '*'
61
+ ? Object.keys(metadata.skills)
62
+ : [...(metadata.suites[rec.base]?.skills || [])];
63
+ const includeSkills = rec.include || [];
64
+ const excludeSkills = new Set(rec.exclude || []);
65
+ return [...new Set([...baseSkills, ...includeSkills])].filter(s => !excludeSkills.has(s));
66
+ }
67
+ }
68
+
54
69
  const suiteDef = metadata.suites[suite] || metadata.suites.full;
55
70
  if (suiteDef.skills === '*') {
56
71
  return Object.keys(metadata.skills);
@@ -68,6 +83,13 @@ async function resolveSkillList(suite) {
68
83
  async function installSkills(platform, skills, projectRoot, dryRun) {
69
84
  const skillsDir = getSkillsDir();
70
85
  const { parseFrontmatter, buildMarkdown } = await loadSharedFrontmatter();
86
+
87
+ // Load metadata to get skill -> category mapping
88
+ const metadata = await loadMetadata();
89
+ const skillCategories = {};
90
+ for (const [skillName, skillDef] of Object.entries(metadata.skills)) {
91
+ skillCategories[skillName] = skillDef.category;
92
+ }
71
93
 
72
94
  // Load Claude command adapter for skill conversion
73
95
  let convertSkillToCommand;
@@ -77,7 +99,31 @@ async function installSkills(platform, skills, projectRoot, dryRun) {
77
99
  }
78
100
 
79
101
  for (const skillName of skills) {
80
- const corePath = path.join(skillsDir, skillName);
102
+ // Determine the category subdirectory (prizmkit-skill, prizmkit-tool-skill, Custom-skill)
103
+ const category = skillCategories[skillName];
104
+ if (!category) {
105
+ console.warn(` ⚠ Skill ${skillName} has no category mapping in metadata, skipping`);
106
+ continue;
107
+ }
108
+
109
+ // Security: validate category and skillName contain no path traversal sequences.
110
+ if (category.includes('..') || category.includes('/') || skillName.includes('..') || skillName.includes('/')) {
111
+ console.warn(` ⚠ Skill ${skillName} has invalid category or name, skipping`);
112
+ continue;
113
+ }
114
+
115
+ // Build path: prefer hierarchical layout (<skillsDir>/<category>/<skillName>),
116
+ // fallback to flat bundled layout (<skillsDir>/<skillName>) for npm package compatibility.
117
+ let corePath = path.resolve(skillsDir, category, skillName);
118
+ // Ensure resolved path stays within skillsDir
119
+ if (!corePath.startsWith(path.resolve(skillsDir))) {
120
+ console.warn(` ⚠ Skill ${skillName} path escapes skills directory, skipping`);
121
+ continue;
122
+ }
123
+ if (!await fs.pathExists(corePath)) {
124
+ corePath = path.resolve(skillsDir, skillName);
125
+ if (!corePath.startsWith(path.resolve(skillsDir))) continue;
126
+ }
81
127
  if (!await fs.pathExists(corePath)) continue;
82
128
 
83
129
  const stat = await fs.stat(corePath);
@@ -292,11 +338,28 @@ async function installTeamConfig(platform, projectRoot, dryRun) {
292
338
  * 安装平台配置文件(settings/hooks/rules)
293
339
  */
294
340
  async function installSettings(platform, projectRoot, options, dryRun) {
341
+ // 从文件加载 rules
342
+ const rulesMeta = await loadRulesMetadata();
343
+ const rulesPreset = options.rules || 'recommended';
344
+ const preset = rulesMeta.presets[rulesPreset] || rulesMeta.presets.recommended;
345
+ const ruleNames = preset?.rules || [];
346
+ const rulesDir = getRulesDir();
347
+ const ruleFiles = [];
348
+ for (const name of ruleNames) {
349
+ const filePath = path.join(rulesDir, `${name}.md`);
350
+ if (await fs.pathExists(filePath)) {
351
+ ruleFiles.push({ name, content: await fs.readFile(filePath, 'utf8') });
352
+ }
353
+ }
354
+
295
355
  if (platform === 'codebuddy') {
296
356
  const settingsPath = path.join(projectRoot, '.codebuddy', 'settings.json');
297
357
 
298
358
  if (dryRun) {
299
359
  console.log(chalk.gray(` [dry-run] .codebuddy/settings.json`));
360
+ if (ruleFiles.length > 0) {
361
+ console.log(chalk.gray(` [dry-run] .codebuddy/rules/ (${ruleFiles.length} 条规则)`));
362
+ }
300
363
  return;
301
364
  }
302
365
 
@@ -309,12 +372,19 @@ async function installSettings(platform, projectRoot, options, dryRun) {
309
372
  await fs.writeFile(settingsPath, JSON.stringify(hookTemplate, null, 2));
310
373
  console.log(chalk.green(` ✓ .codebuddy/settings.json`));
311
374
 
375
+ // Install rules for CodeBuddy
376
+ if (ruleFiles.length > 0) {
377
+ const { installRules: installCbRules } = await loadAdapter('codebuddy', 'rules-adapter.js');
378
+ await installCbRules(projectRoot, ruleFiles);
379
+ console.log(chalk.green(` ✓ .codebuddy/rules/ (${ruleFiles.length} 条规则)`));
380
+ }
381
+
312
382
  } else if (platform === 'claude') {
313
383
  const claudeDir = path.join(projectRoot, '.claude');
314
384
 
315
385
  if (dryRun) {
316
386
  console.log(chalk.gray(` [dry-run] .claude/settings.json`));
317
- console.log(chalk.gray(` [dry-run] .claude/rules/`));
387
+ console.log(chalk.gray(` [dry-run] .claude/rules/ (${ruleFiles.length} 条规则)`));
318
388
  return;
319
389
  }
320
390
 
@@ -363,29 +433,64 @@ async function installSettings(platform, projectRoot, options, dryRun) {
363
433
  console.log(chalk.green(` ✓ .claude/settings.json`));
364
434
 
365
435
  // Rules
366
- const rulesDir = path.join(claudeDir, 'rules');
367
- await fs.ensureDir(rulesDir);
436
+ const { installRules: installClaudeRules } = await loadAdapter('claude', 'rules-adapter.js');
437
+ await installClaudeRules(projectRoot, ruleFiles);
438
+ console.log(chalk.green(` ✓ .claude/rules/ (${ruleFiles.length} 条规则)`));
439
+ }
440
+ }
368
441
 
369
- const rules = [
370
- {
371
- filename: 'prizm-documentation.md',
372
- content: `---\ndescription: "PrizmKit documentation rules"\nglobs:\n - "**/*.ts"\n - "**/*.tsx"\n - "**/*.js"\n - "**/*.jsx"\n - "**/*.py"\n - "**/*.go"\n - "**/*.rs"\n - "**/*.java"\n---\n\nWhen modifying source files in this project:\n1. Check if \`.prizm-docs/root.prizm\` exists\n2. If it does, read it before making changes to understand project structure\n3. After making changes, update affected \`.prizm-docs/\` files\n4. Follow the Prizm doc format (KEY: value, not prose)\n5. Size limits: L0 = 4KB, L1 = 3KB, L2 = 5KB\n`,
373
- },
374
- {
375
- filename: 'prizm-commit-workflow.md',
376
- content: `---\ndescription: "PrizmKit commit workflow rules"\n---\n\nBefore any git commit in this project:\n1. Update \`.prizm-docs/\` for affected modules\n2. Use Conventional Commits format: type(scope): description\n3. Bug fixes use \`fix()\` prefix, not \`feat()\`\n4. Do NOT create REGISTRY.md entries for bug fixes\n5. Use \`/prizmkit-committer\` command for the complete commit workflow\n`,
377
- },
378
- {
379
- filename: 'prizm-progressive-loading.md',
380
- content: `---\ndescription: "PrizmKit progressive context loading protocol"\n---\n\nThis project uses PrizmKit's progressive loading protocol:\n- ON SESSION START: Read \`.prizm-docs/root.prizm\` (L0 — project map)\n- ON TASK: Read L1 (\`.prizm-docs/<module>.prizm\`) for relevant modules\n- ON FILE EDIT: Read L2 (\`.prizm-docs/<module>/<submodule>.prizm\`) before modifying\n- NEVER load all .prizm docs at once\n- Arrow notation (->) in .prizm files indicates load pointers\n- DECISIONS and CHANGELOG in .prizm files are append-only\n`,
381
- },
382
- ];
442
+ /**
443
+ * 安装 git pre-commit hook(prizm 格式校验)
444
+ */
445
+ async function installGitHook(projectRoot, dryRun) {
446
+ const gitDir = path.join(projectRoot, '.git');
383
447
 
384
- for (const rule of rules) {
385
- await fs.writeFile(path.join(rulesDir, rule.filename), rule.content);
386
- }
387
- console.log(chalk.green(` ✓ .claude/rules/ (${rules.length} 条规则)`));
448
+ if (dryRun) {
449
+ console.log(chalk.gray(' [dry-run] .git/hooks/pre-commit (prizm format check)'));
450
+ return;
451
+ }
452
+
453
+ if (!fs.existsSync(gitDir)) return;
454
+
455
+ const templatePath = path.join(getTemplatesDir(), 'hooks', 'prizm-pre-commit.sh');
456
+ const hooksDir = path.join(gitDir, 'hooks');
457
+ const preCommitPath = path.join(hooksDir, 'pre-commit');
458
+
459
+ fs.mkdirSync(hooksDir, { recursive: true });
460
+
461
+ if (fs.existsSync(preCommitPath)) {
462
+ const existing = fs.readFileSync(preCommitPath, 'utf8');
463
+ if (existing.includes('PrizmKit')) return;
464
+ const hookContent = fs.readFileSync(templatePath, 'utf8');
465
+ fs.writeFileSync(preCommitPath, existing + '\n\n' + hookContent);
466
+ } else {
467
+ await fs.copy(templatePath, preCommitPath);
468
+ fs.chmodSync(preCommitPath, 0o755);
469
+ }
470
+
471
+ console.log(chalk.green(' ✓ .git/hooks/pre-commit (prizm format check)'));
472
+ }
473
+
474
+ /**
475
+ * 安装 validate-prizm-docs.sh 到 dev-pipeline/scripts/
476
+ */
477
+ async function installValidateScript(projectRoot, dryRun) {
478
+ const sourcePath = path.join(getTemplatesDir(), 'hooks', 'validate-prizm-docs.sh');
479
+ if (!fs.existsSync(sourcePath)) return;
480
+
481
+ const targetDir = path.join(projectRoot, 'dev-pipeline', 'scripts');
482
+ const targetPath = path.join(targetDir, 'validate-prizm-docs.sh');
483
+
484
+ if (dryRun) {
485
+ console.log(chalk.gray(' [dry-run] dev-pipeline/scripts/validate-prizm-docs.sh'));
486
+ return;
388
487
  }
488
+
489
+ await fs.ensureDir(targetDir);
490
+ await fs.copy(sourcePath, targetPath);
491
+ fs.chmodSync(targetPath, 0o755);
492
+
493
+ console.log(chalk.green(' ✓ dev-pipeline/scripts/validate-prizm-docs.sh'));
389
494
  }
390
495
 
391
496
  /**
@@ -393,11 +498,19 @@ async function installSettings(platform, projectRoot, options, dryRun) {
393
498
  */
394
499
  async function installProjectMemory(platform, projectRoot, dryRun) {
395
500
  const templatesDir = getTemplatesDir();
501
+ const skillsDir = getSkillsDir();
396
502
  const templateName = platform === 'claude' ? 'claude-md-template.md' : 'codebuddy-md-template.md';
397
503
  const targetName = platform === 'claude' ? 'CLAUDE.md' : 'CODEBUDDY.md';
398
- const templatePath = path.join(templatesDir, templateName);
399
504
  const targetPath = path.join(projectRoot, targetName);
400
505
 
506
+ // Prefer core/templates, fallback to prizm-kit/assets in hierarchical or flat skills layouts.
507
+ const templateCandidates = [
508
+ path.join(templatesDir, templateName),
509
+ path.join(skillsDir, 'prizmkit-skill', 'prizm-kit', 'assets', templateName),
510
+ path.join(skillsDir, 'prizm-kit', 'assets', templateName),
511
+ ];
512
+ const templatePath = templateCandidates.find(candidate => fs.pathExistsSync(candidate));
513
+
401
514
  if (await fs.pathExists(targetPath)) {
402
515
  console.log(chalk.yellow(` ⚠ ${targetName} 已存在,跳过`));
403
516
  return;
@@ -408,7 +521,7 @@ async function installProjectMemory(platform, projectRoot, dryRun) {
408
521
  return;
409
522
  }
410
523
 
411
- if (await fs.pathExists(templatePath)) {
524
+ if (templatePath && await fs.pathExists(templatePath)) {
412
525
  await fs.copy(templatePath, targetPath);
413
526
  console.log(chalk.green(` ✓ ${targetName}`));
414
527
  } else {
@@ -492,14 +605,17 @@ async function installGitignore(projectRoot, options, dryRun) {
492
605
  * 执行纯净安装
493
606
  * @param {Object} config
494
607
  * @param {string} config.platform - 'codebuddy' | 'claude' | 'both'
495
- * @param {string} config.skills - 'full' | 'core' | 'minimal'
608
+ * @param {string} config.skills - 'full' | 'core' | 'minimal' | 'recommended:<type>'
496
609
  * @param {boolean} config.team - 是否启用团队模式
497
610
  * @param {boolean} config.pipeline - 是否安装 dev-pipeline
611
+ * @param {string} [config.rules] - Rules preset: 'recommended' | 'minimal' | 'none'
612
+ * @param {string} [config.aiCli] - AI CLI 可执行命令(写入 .prizmkit/config.json)
613
+ * @param {string[]} [config.externalSkills] - 要安装的外部 skill 名称列表
498
614
  * @param {string} config.projectRoot - 目标项目根目录
499
615
  * @param {boolean} config.dryRun - 是否为预览模式
500
616
  */
501
617
  export async function scaffold(config) {
502
- const { platform, skills, team, pipeline, projectRoot, dryRun } = config;
618
+ const { platform, skills, team, pipeline, rules, aiCli, externalSkills, projectRoot, dryRun } = config;
503
619
  const platforms = platform === 'both' ? ['codebuddy', 'claude'] : [platform];
504
620
 
505
621
  if (dryRun) {
@@ -531,7 +647,7 @@ export async function scaffold(config) {
531
647
 
532
648
  // 4. Settings/Hooks/Rules
533
649
  console.log(chalk.blue('\n 平台配置:'));
534
- await installSettings(p, projectRoot, { pipeline }, dryRun);
650
+ await installSettings(p, projectRoot, { pipeline, rules }, dryRun);
535
651
 
536
652
  // 5. Project Memory
537
653
  console.log(chalk.blue('\n 项目记忆文件:'));
@@ -551,6 +667,56 @@ export async function scaffold(config) {
551
667
  console.log(chalk.blue(' 项目配置:'));
552
668
  await installGitignore(projectRoot, { pipeline }, dryRun);
553
669
 
670
+ // 8. 写入 .prizmkit/config.json(如果指定了 aiCli)
671
+ if (aiCli) {
672
+ const prizmkitDir = path.join(projectRoot, '.prizmkit');
673
+ const configPath = path.join(prizmkitDir, 'config.json');
674
+
675
+ if (dryRun) {
676
+ console.log(chalk.gray(` [dry-run] .prizmkit/config.json (ai_cli: ${aiCli})`));
677
+ } else {
678
+ await fs.ensureDir(prizmkitDir);
679
+ let existingConfig = {};
680
+ if (await fs.pathExists(configPath)) {
681
+ existingConfig = await fs.readJSON(configPath);
682
+ }
683
+ existingConfig.ai_cli = aiCli;
684
+ await fs.writeJSON(configPath, existingConfig, { spaces: 2 });
685
+ console.log(chalk.green(` ✓ .prizmkit/config.json (ai_cli: ${aiCli})`));
686
+ }
687
+ }
688
+
689
+ // 9. 安装外部 Skills(如果选择了)
690
+ if (externalSkills?.length) {
691
+ const { installExternalSkill } = await import('./external-skills.js');
692
+ const metadata = await loadMetadata();
693
+ const knownSkills = metadata.external_skills?.known || [];
694
+ console.log(chalk.blue('\n 外部技能:'));
695
+ for (const name of externalSkills) {
696
+ const skillDef = knownSkills.find(s => s.name === name);
697
+ if (skillDef) {
698
+ for (const p of platforms) {
699
+ try {
700
+ await installExternalSkill(skillDef, p, projectRoot, dryRun);
701
+ console.log(chalk.green(` ✓ ${name} (external)`));
702
+ } catch (e) {
703
+ console.log(chalk.yellow(` ⚠ ${name} 拉取失败,跳过: ${e.message}`));
704
+ }
705
+ }
706
+ }
707
+ }
708
+ }
709
+
710
+ // 10. Git pre-commit hook
711
+ console.log(chalk.blue(' Git Hook:'));
712
+ await installGitHook(projectRoot, dryRun);
713
+
714
+ // 11. Validate script (only if pipeline enabled)
715
+ if (pipeline) {
716
+ console.log(chalk.blue(' 验证脚本:'));
717
+ await installValidateScript(projectRoot, dryRun);
718
+ }
719
+
554
720
  // === 完成 ===
555
721
  console.log('');
556
722
  console.log(chalk.bold(' ════════════════════════════════════════════════'));
@@ -565,7 +731,8 @@ export async function scaffold(config) {
565
731
  if (!dryRun) {
566
732
  // 打印下一步提示
567
733
  const mainPlatform = platforms.includes('claude') ? 'claude' : 'codebuddy';
568
- const cli = mainPlatform === 'claude' ? 'claude' : 'cbc';
734
+ const defaultCli = mainPlatform === 'claude' ? 'claude' : 'cbc';
735
+ const cli = aiCli || defaultCli;
569
736
 
570
737
  console.log('');
571
738
  console.log(' 下一步:');
@@ -581,16 +748,18 @@ export async function scaffold(config) {
581
748
  console.log('');
582
749
 
583
750
  // 安装统计
584
- const metadata = await loadMetadata();
585
751
  const suiteLabel = skills === 'full' ? `全部 ${skillList.length} 个`
586
752
  : skills === 'core' ? `核心 ${skillList.length} 个`
587
- : `最小 ${skillList.length} 个`;
753
+ : skills === 'minimal' ? `最小 ${skillList.length} 个`
754
+ : `推荐 ${skillList.length} 个`;
588
755
 
589
756
  console.log(chalk.gray(` 安装统计:`));
590
757
  console.log(chalk.gray(` 技能: ${suiteLabel}`));
591
758
  console.log(chalk.gray(` 平台: ${platforms.map(p => p === 'codebuddy' ? 'CodeBuddy' : 'Claude Code').join(' + ')}`));
592
759
  console.log(chalk.gray(` 团队: ${team ? '已启用' : '未启用'}`));
593
760
  console.log(chalk.gray(` 流水线: ${pipeline ? '已安装' : '未安装'}`));
761
+ console.log(chalk.gray(` 规则: ${rules || 'recommended'}`));
762
+ if (aiCli) console.log(chalk.gray(` AI CLI: ${aiCli}`));
594
763
  console.log('');
595
764
  }
596
765
  }