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.
- package/bin/create-prizmkit.js +4 -1
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/command-adapter.js +35 -4
- package/bundled/adapters/claude/rules-adapter.js +6 -58
- package/bundled/adapters/claude/team-adapter.js +2 -2
- package/bundled/adapters/codebuddy/agent-adapter.js +0 -1
- package/bundled/adapters/codebuddy/rules-adapter.js +30 -0
- package/bundled/adapters/shared/frontmatter.js +3 -1
- package/bundled/dev-pipeline/README.md +13 -3
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +10 -0
- package/bundled/dev-pipeline/launch-daemon.sh +18 -4
- package/bundled/dev-pipeline/lib/common.sh +105 -0
- package/bundled/dev-pipeline/retry-bug.sh +20 -2
- package/bundled/dev-pipeline/retry-feature.sh +20 -2
- package/bundled/dev-pipeline/run-bugfix.sh +57 -57
- package/bundled/dev-pipeline/run.sh +75 -59
- package/bundled/dev-pipeline/scripts/check-session-status.py +47 -2
- package/bundled/dev-pipeline/scripts/cleanup-logs.py +192 -0
- package/bundled/dev-pipeline/scripts/detect-stuck.py +15 -3
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +32 -27
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +23 -23
- package/bundled/dev-pipeline/scripts/update-feature-status.py +50 -2
- package/bundled/dev-pipeline/scripts/utils.py +22 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +18 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +19 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +18 -2
- package/bundled/dev-pipeline/templates/session-status-schema.json +7 -1
- package/bundled/dev-pipeline/tests/__init__.py +0 -0
- package/bundled/dev-pipeline/tests/conftest.py +133 -0
- package/bundled/dev-pipeline/tests/test_check_session.py +127 -0
- package/bundled/dev-pipeline/tests/test_cleanup_logs.py +119 -0
- package/bundled/dev-pipeline/tests/test_detect_stuck.py +207 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +181 -0
- package/bundled/dev-pipeline/tests/test_generate_prompt.py +190 -0
- package/bundled/dev-pipeline/tests/test_init_bugfix_pipeline.py +153 -0
- package/bundled/dev-pipeline/tests/test_init_pipeline.py +241 -0
- package/bundled/dev-pipeline/tests/test_update_bug_status.py +142 -0
- package/bundled/dev-pipeline/tests/test_update_feature_status.py +277 -0
- package/bundled/dev-pipeline/tests/test_utils.py +141 -0
- package/bundled/rules/USAGE.md +153 -0
- package/bundled/rules/_rules-metadata.json +43 -0
- package/bundled/rules/general/prefer-linux-commands.md +9 -0
- package/bundled/rules/prizm/prizm-commit-workflow.md +10 -0
- package/bundled/rules/prizm/prizm-documentation.md +80 -0
- package/bundled/rules/prizm/prizm-progressive-loading.md +11 -0
- package/bundled/skills/_metadata.json +162 -67
- package/bundled/skills/app-planner/SKILL.md +252 -499
- package/bundled/skills/app-planner/assets/evaluation-guide.md +44 -0
- package/bundled/skills/app-planner/scripts/validate-and-generate.py +143 -4
- package/bundled/skills/bug-planner/SKILL.md +58 -13
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +5 -7
- package/bundled/skills/dev-pipeline-launcher/SKILL.md +16 -7
- package/bundled/skills/feature-workflow/SKILL.md +175 -234
- package/bundled/skills/prizm-kit/SKILL.md +17 -31
- package/bundled/skills/{prizmkit-adr-manager → prizmkit-tool-adr-manager}/SKILL.md +6 -7
- package/bundled/skills/{prizmkit-api-doc-generator → prizmkit-tool-api-doc-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-bug-reproducer → prizmkit-tool-bug-reproducer}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-ci-cd-generator → prizmkit-tool-ci-cd-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-db-migration → prizmkit-tool-db-migration}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-dependency-health → prizmkit-tool-dependency-health}/SKILL.md +3 -4
- package/bundled/skills/{prizmkit-deployment-strategy → prizmkit-tool-deployment-strategy}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-error-triage → prizmkit-tool-error-triage}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-log-analyzer → prizmkit-tool-log-analyzer}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-monitoring-setup → prizmkit-tool-monitoring-setup}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-onboarding-generator → prizmkit-tool-onboarding-generator}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-perf-profiler → prizmkit-tool-perf-profiler}/SKILL.md +4 -5
- package/bundled/skills/{prizmkit-security-audit → prizmkit-tool-security-audit}/SKILL.md +3 -4
- package/bundled/skills/{prizmkit-tech-debt-tracker → prizmkit-tool-tech-debt-tracker}/SKILL.md +3 -4
- package/bundled/skills/refactor-skill/SKILL.md +371 -0
- package/bundled/skills/refactor-workflow/SKILL.md +17 -119
- package/bundled/templates/hooks/commit-intent-claude.json +1 -1
- package/bundled/templates/hooks/commit-intent-codebuddy.json +1 -1
- package/bundled/templates/hooks/prizm-pre-commit.sh +58 -0
- package/bundled/templates/hooks/validate-prizm-docs.sh +84 -0
- package/package.json +1 -1
- package/src/detect-platform.js +20 -8
- package/src/external-skills.js +71 -0
- package/src/index.js +66 -8
- package/src/metadata.js +36 -0
- package/src/scaffold.js +200 -31
- package/bundled/skills/prizmkit-bug-fix-workflow/SKILL.md +0 -356
- package/bundled/templates/claude-md-template.md +0 -38
- package/bundled/templates/codebuddy-md-template.md +0 -35
- /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
|
|
73
|
+
name: `CodeBuddy (.codebuddy/)${detected.cbc ? chalk.green(' ← 已检测到 cbc') : ''}`,
|
|
71
74
|
value: 'codebuddy',
|
|
72
75
|
},
|
|
73
76
|
{
|
|
74
|
-
name: `Claude Code
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
367
|
-
await
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
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
|
}
|