dev-playbooks-cn 1.0.0
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/LICENSE +21 -0
- package/README.md +466 -0
- package/bin/devbooks.js +987 -0
- package/package.json +43 -0
- package/skills/Skills/344/275/277/347/224/250/350/257/264/346/230/216.md +446 -0
- package/skills/Skill/345/274/200/345/217/221/346/214/207/345/215/227.md +248 -0
- package/skills/_shared/context-detection-template.md +315 -0
- package/skills/_shared/mcp-enhancement-template.md +144 -0
- package/skills/_shared/references//351/200/232/347/224/250/345/256/210/351/227/250/345/215/217/350/256/256.md +114 -0
- package/skills/_template/config-discovery-template.md +126 -0
- package/skills/devbooks-brownfield-bootstrap/SKILL.md +167 -0
- package/skills/devbooks-brownfield-bootstrap/references//344/273/243/347/240/201/345/257/274/350/210/252/347/255/226/347/225/245.md +203 -0
- package/skills/devbooks-brownfield-bootstrap/references//345/255/230/351/207/217/351/241/271/347/233/256/345/210/235/345/247/213/345/214/226.md +96 -0
- package/skills/devbooks-brownfield-bootstrap/references//345/255/230/351/207/217/351/241/271/347/233/256/345/210/235/345/247/213/345/214/226/346/217/220/347/244/272/350/257/215.md +115 -0
- package/skills/devbooks-brownfield-bootstrap/references//346/234/257/350/257/255/350/241/250/346/250/241/346/235/277.md +42 -0
- package/skills/devbooks-brownfield-bootstrap/scripts/cod-update.sh +357 -0
- package/skills/devbooks-brownfield-bootstrap/templates/project-profile-template.md +172 -0
- package/skills/devbooks-c4-map/SKILL.md +151 -0
- package/skills/devbooks-c4-map/references/C4/346/236/266/346/236/204/345/234/260/345/233/276/346/217/220/347/244/272/350/257/215.md +33 -0
- package/skills/devbooks-c4-map/references//345/210/206/345/261/202/347/272/246/346/235/237/346/243/200/346/237/245/346/270/205/345/215/225.md +185 -0
- package/skills/devbooks-code-review/SKILL.md +175 -0
- package/skills/devbooks-code-review/references/PR/346/250/241/346/235/277/344/270/216/346/214/207/345/215/227.md +321 -0
- package/skills/devbooks-code-review/references//344/273/243/347/240/201/350/257/204/345/256/241/346/217/220/347/244/272/350/257/215.md +100 -0
- package/skills/devbooks-code-review/references//345/235/217/345/221/263/351/201/223/351/200/237/346/237/245/350/241/250.md +495 -0
- package/skills/devbooks-code-review/references//350/265/204/346/272/220/347/256/241/347/220/206/345/256/241/346/237/245/346/270/205/345/215/225.md +311 -0
- package/skills/devbooks-coder/SKILL.md +219 -0
- package/skills/devbooks-coder/references//344/273/243/347/240/201/345/256/236/347/216/260/346/217/220/347/244/272/350/257/215.md +70 -0
- package/skills/devbooks-coder/references//344/275/216/351/243/216/351/231/251/346/224/271/345/212/250/346/212/200/346/234/257.md +275 -0
- package/skills/devbooks-coder/references//346/227/245/345/277/227/350/247/204/350/214/203.md +329 -0
- package/skills/devbooks-coder/references//347/274/226/347/240/201/351/243/216/346/240/274/347/273/206/345/210/231.md +351 -0
- package/skills/devbooks-coder/references//351/224/231/350/257/257/347/240/201/350/247/204/350/214/203.md +463 -0
- package/skills/devbooks-delivery-workflow/SKILL.md +217 -0
- package/skills/devbooks-delivery-workflow/references//344/272/244/344/273/230/351/252/214/346/224/266/345/267/245/344/275/234/346/265/201.md +256 -0
- package/skills/devbooks-delivery-workflow/references//345/216/237/345/236/213-/347/224/237/344/272/247/345/217/214/350/275/250/346/250/241/345/274/217.md +168 -0
- package/skills/devbooks-delivery-workflow/references//345/217/230/346/233/264/351/252/214/350/257/201/344/270/216/350/277/275/346/272/257/346/250/241/346/235/277.md +133 -0
- package/skills/devbooks-delivery-workflow/scripts/ac-trace-check.sh +330 -0
- package/skills/devbooks-delivery-workflow/scripts/audit-scope.sh +262 -0
- package/skills/devbooks-delivery-workflow/scripts/change-check.sh +1040 -0
- package/skills/devbooks-delivery-workflow/scripts/change-codemod-scaffold.sh +135 -0
- package/skills/devbooks-delivery-workflow/scripts/change-evidence.sh +152 -0
- package/skills/devbooks-delivery-workflow/scripts/change-scaffold.sh +442 -0
- package/skills/devbooks-delivery-workflow/scripts/change-spec-delta-scaffold.sh +136 -0
- package/skills/devbooks-delivery-workflow/scripts/constitution-check.sh +237 -0
- package/skills/devbooks-delivery-workflow/scripts/env-match-check.sh +128 -0
- package/skills/devbooks-delivery-workflow/scripts/fitness-check.sh +387 -0
- package/skills/devbooks-delivery-workflow/scripts/guardrail-check.sh +519 -0
- package/skills/devbooks-delivery-workflow/scripts/handoff-check.sh +141 -0
- package/skills/devbooks-delivery-workflow/scripts/hygiene-check.sh +340 -0
- package/skills/devbooks-delivery-workflow/scripts/migrate-from-openspec.sh +385 -0
- package/skills/devbooks-delivery-workflow/scripts/migrate-to-v2-gates.sh +202 -0
- package/skills/devbooks-delivery-workflow/scripts/progress-dashboard.sh +319 -0
- package/skills/devbooks-delivery-workflow/scripts/prototype-promote.sh +341 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-preview.sh +203 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-promote.sh +118 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-rollback.sh +124 -0
- package/skills/devbooks-delivery-workflow/scripts/spec-stage.sh +117 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-all.sh +78 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-npm-package.sh +123 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-openspec-free.sh +81 -0
- package/skills/devbooks-delivery-workflow/scripts/verify-slash-commands.sh +146 -0
- package/skills/devbooks-delivery-workflow/templates/handoff.md +50 -0
- package/skills/devbooks-design-backport/SKILL.md +73 -0
- package/skills/devbooks-design-backport/references//345/233/236/345/206/231/350/256/276/350/256/241/346/226/207/346/241/243/346/217/220/347/244/272/350/257/215.md +196 -0
- package/skills/devbooks-design-doc/SKILL.md +121 -0
- package/skills/devbooks-design-doc/references//345/276/256/346/234/215/345/212/241/350/256/276/350/256/241/346/270/205/345/215/225.md +149 -0
- package/skills/devbooks-design-doc/references//350/256/276/350/256/241/346/226/207/346/241/243/346/217/220/347/244/272/350/257/215.md +189 -0
- package/skills/devbooks-design-doc/references//351/232/220/347/247/201/345/220/210/350/247/204/346/243/200/346/237/245/346/270/205/345/215/225.md +240 -0
- package/skills/devbooks-entropy-monitor/SKILL.md +188 -0
- package/skills/devbooks-entropy-monitor/references//347/206/265/345/272/246/351/207/217/346/226/271/346/263/225/350/256/272.md +223 -0
- package/skills/devbooks-entropy-monitor/scripts/entropy-measure.sh +449 -0
- package/skills/devbooks-entropy-monitor/scripts/entropy-report.sh +303 -0
- package/skills/devbooks-entropy-monitor/templates/thresholds.json +99 -0
- package/skills/devbooks-federation/SKILL.md +264 -0
- package/skills/devbooks-federation/scripts/federation-check.sh +144 -0
- package/skills/devbooks-federation/templates/federation.yaml +89 -0
- package/skills/devbooks-impact-analysis/SKILL.md +135 -0
- package/skills/devbooks-impact-analysis/references//345/275/261/345/223/215/345/210/206/346/236/220/346/217/220/347/244/272/350/257/215.md +82 -0
- package/skills/devbooks-impact-analysis/scripts/graph-cache.sh +214 -0
- package/skills/devbooks-implementation-plan/SKILL.md +83 -0
- package/skills/devbooks-implementation-plan/references//347/274/226/347/240/201/350/256/241/345/210/222/346/217/220/347/244/272/350/257/215.md +99 -0
- package/skills/devbooks-index-bootstrap/SKILL.md +240 -0
- package/skills/devbooks-proposal-author/SKILL.md +83 -0
- package/skills/devbooks-proposal-author/references//346/217/220/346/241/210/346/222/260/345/206/231/346/217/220/347/244/272/350/257/215.md +66 -0
- package/skills/devbooks-proposal-challenger/SKILL.md +86 -0
- package/skills/devbooks-proposal-challenger/references//344/274/246/347/220/206/344/270/216/345/220/210/350/247/204/346/243/200/346/237/245/346/270/205/345/215/225.md +176 -0
- package/skills/devbooks-proposal-challenger/references//346/217/220/346/241/210/350/264/250/347/226/221/346/217/220/347/244/272/350/257/215.md +57 -0
- package/skills/devbooks-proposal-debate-workflow/SKILL.md +78 -0
- package/skills/devbooks-proposal-debate-workflow/references//346/217/220/346/241/210/345/257/271/350/276/251/345/267/245/344/275/234/346/265/201.md +24 -0
- package/skills/devbooks-proposal-debate-workflow/references//346/217/220/346/241/210/345/257/271/350/276/251/346/250/241/346/235/277.md +35 -0
- package/skills/devbooks-proposal-debate-workflow/scripts/proposal-debate-check.sh +102 -0
- package/skills/devbooks-proposal-judge/SKILL.md +78 -0
- package/skills/devbooks-proposal-judge/references//346/217/220/346/241/210/350/243/201/345/206/263/346/217/220/347/244/272/350/257/215.md +37 -0
- package/skills/devbooks-router/SKILL.md +346 -0
- package/skills/devbooks-spec-contract/SKILL.md +191 -0
- package/skills/devbooks-spec-contract/references/API/350/256/276/350/256/241/346/214/207/345/215/227.md +349 -0
- package/skills/devbooks-spec-contract/references//345/245/221/347/272/246/344/270/216/346/225/260/346/215/256/345/256/232/344/271/211/346/217/220/347/244/272/350/257/215.md +85 -0
- package/skills/devbooks-spec-contract/references//350/247/204/346/240/274/345/217/230/346/233/264/346/217/220/347/244/272/350/257/215.md +63 -0
- package/skills/devbooks-spec-contract/references//351/232/220/345/274/217/345/217/230/346/233/264/346/243/200/346/265/213/346/217/220/347/244/272/350/257/215.md +183 -0
- package/skills/devbooks-spec-contract/scripts/implicit-change-detect.sh +378 -0
- package/skills/devbooks-spec-gardener/SKILL.md +72 -0
- package/skills/devbooks-spec-gardener/references//350/247/204/346/240/274/345/233/255/344/270/201/346/217/220/347/244/272/350/257/215.md +41 -0
- package/skills/devbooks-test-owner/SKILL.md +172 -0
- package/skills/devbooks-test-owner/references//345/217/230/346/233/264/351/252/214/350/257/201/344/270/216/350/277/275/346/272/257/346/250/241/346/235/277.md +228 -0
- package/skills/devbooks-test-owner/references//345/274/202/346/255/245/347/263/273/347/273/237/346/265/213/350/257/225/347/255/226/347/225/245.md +316 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/344/273/243/347/240/201/346/217/220/347/244/272/350/257/215.md +208 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/345/210/206/345/261/202/347/255/226/347/225/245.md +281 -0
- package/skills/devbooks-test-owner/references//346/265/213/350/257/225/351/251/261/345/212/250.md +394 -0
- package/skills/devbooks-test-owner/references//350/247/243/344/276/235/350/265/226/346/212/200/346/234/257/351/200/237/346/237/245/350/241/250.md +432 -0
- package/skills/devbooks-test-reviewer/SKILL.md +189 -0
- package/templates/.devbooks/config.yaml +88 -0
- package/templates/claude-commands/devbooks/apply.md +38 -0
- package/templates/claude-commands/devbooks/archive.md +33 -0
- package/templates/claude-commands/devbooks/backport.md +19 -0
- package/templates/claude-commands/devbooks/bootstrap.md +19 -0
- package/templates/claude-commands/devbooks/c4.md +19 -0
- package/templates/claude-commands/devbooks/challenger.md +19 -0
- package/templates/claude-commands/devbooks/code.md +19 -0
- package/templates/claude-commands/devbooks/debate.md +19 -0
- package/templates/claude-commands/devbooks/delivery.md +19 -0
- package/templates/claude-commands/devbooks/design.md +19 -0
- package/templates/claude-commands/devbooks/entropy.md +19 -0
- package/templates/claude-commands/devbooks/federation.md +19 -0
- package/templates/claude-commands/devbooks/gardener.md +19 -0
- package/templates/claude-commands/devbooks/impact.md +19 -0
- package/templates/claude-commands/devbooks/index.md +19 -0
- package/templates/claude-commands/devbooks/judge.md +19 -0
- package/templates/claude-commands/devbooks/plan.md +19 -0
- package/templates/claude-commands/devbooks/proposal.md +19 -0
- package/templates/claude-commands/devbooks/quick.md +42 -0
- package/templates/claude-commands/devbooks/review.md +19 -0
- package/templates/claude-commands/devbooks/router.md +19 -0
- package/templates/claude-commands/devbooks/spec.md +19 -0
- package/templates/claude-commands/devbooks/test-review.md +19 -0
- package/templates/claude-commands/devbooks/test.md +19 -0
- package/templates/dev-playbooks/README.md +458 -0
- package/templates/dev-playbooks/changes/.gitkeep +1 -0
- package/templates/dev-playbooks/constitution.md +116 -0
- package/templates/dev-playbooks/project.md +96 -0
- package/templates/dev-playbooks/scripts/.gitkeep +1 -0
- package/templates/dev-playbooks/specs/_meta/anti-patterns/.gitkeep +2 -0
- package/templates/dev-playbooks/specs/_meta/glossary.md +47 -0
- package/templates/dev-playbooks/specs/_meta/project-profile.md +79 -0
- package/templates/dev-playbooks/specs/architecture/fitness-rules.md +95 -0
package/bin/devbooks.js
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DevBooks CLI
|
|
5
|
+
*
|
|
6
|
+
* AI-agnostic spec-driven development workflow
|
|
7
|
+
*
|
|
8
|
+
* 用法:
|
|
9
|
+
* dev-playbooks-cn init [path] [options]
|
|
10
|
+
* dev-playbooks-cn update [path]
|
|
11
|
+
*
|
|
12
|
+
* 选项:
|
|
13
|
+
* --tools <tools> 非交互式指定 AI 工具:all, none, 或逗号分隔的列表
|
|
14
|
+
* --help 显示帮助信息
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import os from 'os';
|
|
20
|
+
import { fileURLToPath } from 'url';
|
|
21
|
+
import { checkbox, confirm } from '@inquirer/prompts';
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
import ora from 'ora';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
27
|
+
|
|
28
|
+
const CLI_COMMAND = 'dev-playbooks-cn';
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Skills 支持级别定义
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
const SKILLS_SUPPORT = {
|
|
35
|
+
FULL: 'full', // 完整 Skills 系统(可独立调用、有独立上下文)
|
|
36
|
+
RULES: 'rules', // Rules 类似系统(自动应用的规则)
|
|
37
|
+
AGENTS: 'agents', // Agents/自定义指令(项目级指令文件)
|
|
38
|
+
BASIC: 'basic' // 仅基础指令(无独立 Skills 概念)
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// AI 工具配置
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
const AI_TOOLS = [
|
|
46
|
+
// === 完整 Skills 支持 ===
|
|
47
|
+
{
|
|
48
|
+
id: 'claude',
|
|
49
|
+
name: 'Claude Code',
|
|
50
|
+
description: 'Anthropic Claude Code CLI',
|
|
51
|
+
skillsSupport: SKILLS_SUPPORT.FULL,
|
|
52
|
+
slashDir: '.claude/commands/devbooks',
|
|
53
|
+
skillsDir: path.join(os.homedir(), '.claude', 'skills'),
|
|
54
|
+
instructionFile: 'CLAUDE.md',
|
|
55
|
+
available: true
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'qoder',
|
|
59
|
+
name: 'Qoder CLI',
|
|
60
|
+
description: 'Qoder AI Coding Assistant',
|
|
61
|
+
skillsSupport: SKILLS_SUPPORT.FULL,
|
|
62
|
+
slashDir: '.qoder/commands/devbooks',
|
|
63
|
+
agentsDir: 'agents',
|
|
64
|
+
globalDir: path.join(os.homedir(), '.qoder'),
|
|
65
|
+
instructionFile: 'AGENTS.md',
|
|
66
|
+
available: true
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// === Rules 类似系统 ===
|
|
70
|
+
{
|
|
71
|
+
id: 'cursor',
|
|
72
|
+
name: 'Cursor',
|
|
73
|
+
description: 'Cursor AI IDE',
|
|
74
|
+
skillsSupport: SKILLS_SUPPORT.RULES,
|
|
75
|
+
slashDir: '.cursor/commands/devbooks',
|
|
76
|
+
rulesDir: '.cursor/rules',
|
|
77
|
+
instructionFile: null,
|
|
78
|
+
available: true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'windsurf',
|
|
82
|
+
name: 'Windsurf',
|
|
83
|
+
description: 'Codeium Windsurf IDE',
|
|
84
|
+
skillsSupport: SKILLS_SUPPORT.RULES,
|
|
85
|
+
slashDir: '.windsurf/commands/devbooks',
|
|
86
|
+
rulesDir: '.windsurf/rules',
|
|
87
|
+
instructionFile: null,
|
|
88
|
+
available: true
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'gemini',
|
|
92
|
+
name: 'Gemini CLI',
|
|
93
|
+
description: 'Google Gemini CLI',
|
|
94
|
+
skillsSupport: SKILLS_SUPPORT.RULES,
|
|
95
|
+
slashDir: '.gemini/commands/devbooks',
|
|
96
|
+
rulesDir: '.gemini',
|
|
97
|
+
globalDir: path.join(os.homedir(), '.gemini'),
|
|
98
|
+
instructionFile: 'GEMINI.md',
|
|
99
|
+
available: true
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'antigravity',
|
|
103
|
+
name: 'Antigravity',
|
|
104
|
+
description: 'Google Antigravity (VS Code)',
|
|
105
|
+
skillsSupport: SKILLS_SUPPORT.RULES,
|
|
106
|
+
slashDir: '.agent/workflows/devbooks',
|
|
107
|
+
rulesDir: '.agent/rules',
|
|
108
|
+
globalDir: path.join(os.homedir(), '.gemini', 'antigravity'),
|
|
109
|
+
instructionFile: 'GEMINI.md',
|
|
110
|
+
available: true
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'opencode',
|
|
114
|
+
name: 'OpenCode',
|
|
115
|
+
description: 'OpenCode AI CLI',
|
|
116
|
+
skillsSupport: SKILLS_SUPPORT.RULES,
|
|
117
|
+
slashDir: '.opencode/commands/devbooks',
|
|
118
|
+
agentsDir: '.opencode/agent',
|
|
119
|
+
globalDir: path.join(os.homedir(), '.config', 'opencode'),
|
|
120
|
+
instructionFile: 'AGENTS.md',
|
|
121
|
+
available: true
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// === Agents/自定义指令 ===
|
|
125
|
+
{
|
|
126
|
+
id: 'github-copilot',
|
|
127
|
+
name: 'GitHub Copilot',
|
|
128
|
+
description: 'GitHub Copilot (VS Code / JetBrains)',
|
|
129
|
+
skillsSupport: SKILLS_SUPPORT.AGENTS,
|
|
130
|
+
instructionsDir: '.github/instructions',
|
|
131
|
+
instructionFile: '.github/copilot-instructions.md',
|
|
132
|
+
available: true
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'continue',
|
|
136
|
+
name: 'Continue',
|
|
137
|
+
description: 'Continue (VS Code / JetBrains)',
|
|
138
|
+
skillsSupport: SKILLS_SUPPORT.AGENTS,
|
|
139
|
+
slashDir: '.continue/prompts/devbooks',
|
|
140
|
+
instructionFile: null,
|
|
141
|
+
available: true
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// === 基础指令 ===
|
|
145
|
+
{
|
|
146
|
+
id: 'codex',
|
|
147
|
+
name: 'Codex CLI',
|
|
148
|
+
description: 'OpenAI Codex CLI',
|
|
149
|
+
skillsSupport: SKILLS_SUPPORT.BASIC,
|
|
150
|
+
slashDir: null,
|
|
151
|
+
globalSlashDir: path.join(os.homedir(), '.codex', 'prompts'),
|
|
152
|
+
instructionFile: 'AGENTS.md',
|
|
153
|
+
available: true
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const DEVBOOKS_MARKERS = {
|
|
158
|
+
start: '<!-- DEVBOOKS:START -->',
|
|
159
|
+
end: '<!-- DEVBOOKS:END -->'
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// 辅助函数
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
function expandPath(p) {
|
|
167
|
+
if (p.startsWith('~')) {
|
|
168
|
+
return path.join(os.homedir(), p.slice(1));
|
|
169
|
+
}
|
|
170
|
+
return p;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function copyDirSync(src, dest) {
|
|
174
|
+
if (!fs.existsSync(src)) return 0;
|
|
175
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
176
|
+
let count = 0;
|
|
177
|
+
|
|
178
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
const srcPath = path.join(src, entry.name);
|
|
181
|
+
const destPath = path.join(dest, entry.name);
|
|
182
|
+
|
|
183
|
+
if (entry.isDirectory()) {
|
|
184
|
+
count += copyDirSync(srcPath, destPath);
|
|
185
|
+
} else if (entry.isSymbolicLink()) {
|
|
186
|
+
// Skip symlinks to avoid broken links
|
|
187
|
+
continue;
|
|
188
|
+
} else {
|
|
189
|
+
fs.copyFileSync(srcPath, destPath);
|
|
190
|
+
count++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return count;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function copyCodexPromptsSync(srcDir, destDir) {
|
|
197
|
+
if (!fs.existsSync(srcDir)) return 0;
|
|
198
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
199
|
+
|
|
200
|
+
let count = 0;
|
|
201
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
if (!entry.isFile()) continue;
|
|
204
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
205
|
+
|
|
206
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
207
|
+
const destName = entry.name.startsWith('devbooks-') ? entry.name : `devbooks-${entry.name}`;
|
|
208
|
+
const destPath = path.join(destDir, destName);
|
|
209
|
+
fs.copyFileSync(srcPath, destPath);
|
|
210
|
+
count++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return count;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getSkillsSupportLabel(level) {
|
|
217
|
+
switch (level) {
|
|
218
|
+
case SKILLS_SUPPORT.FULL:
|
|
219
|
+
return chalk.green('★ 完整 Skills');
|
|
220
|
+
case SKILLS_SUPPORT.RULES:
|
|
221
|
+
return chalk.blue('◆ Rules 系统');
|
|
222
|
+
case SKILLS_SUPPORT.AGENTS:
|
|
223
|
+
return chalk.yellow('● 自定义指令');
|
|
224
|
+
case SKILLS_SUPPORT.BASIC:
|
|
225
|
+
return chalk.gray('○ 基础支持');
|
|
226
|
+
default:
|
|
227
|
+
return chalk.gray('○ 未知');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getSkillsSupportDescription(level) {
|
|
232
|
+
switch (level) {
|
|
233
|
+
case SKILLS_SUPPORT.FULL:
|
|
234
|
+
return '支持独立 Skills/Agents,可按需调用';
|
|
235
|
+
case SKILLS_SUPPORT.RULES:
|
|
236
|
+
return '支持 Rules 规则系统,自动应用';
|
|
237
|
+
case SKILLS_SUPPORT.AGENTS:
|
|
238
|
+
return '支持项目级自定义指令';
|
|
239
|
+
case SKILLS_SUPPORT.BASIC:
|
|
240
|
+
return '仅支持全局提示词';
|
|
241
|
+
default:
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ============================================================================
|
|
247
|
+
// Skills 支持说明
|
|
248
|
+
// ============================================================================
|
|
249
|
+
|
|
250
|
+
function printSkillsSupportInfo() {
|
|
251
|
+
console.log();
|
|
252
|
+
console.log(chalk.bold('📚 Skills 支持级别说明'));
|
|
253
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
254
|
+
console.log();
|
|
255
|
+
|
|
256
|
+
console.log(chalk.green('★ 完整 Skills') + chalk.gray(' - Claude Code, Qoder'));
|
|
257
|
+
console.log(chalk.gray(' └ 独立的 Skills/Agents 系统,可按需调用,有独立上下文'));
|
|
258
|
+
console.log();
|
|
259
|
+
|
|
260
|
+
console.log(chalk.blue('◆ Rules 系统') + chalk.gray(' - Cursor, Windsurf, Gemini, Antigravity, OpenCode'));
|
|
261
|
+
console.log(chalk.gray(' └ 规则自动应用于匹配的文件/场景,功能接近 Skills'));
|
|
262
|
+
console.log();
|
|
263
|
+
|
|
264
|
+
console.log(chalk.yellow('● 自定义指令') + chalk.gray(' - GitHub Copilot, Continue'));
|
|
265
|
+
console.log(chalk.gray(' └ 项目级指令文件,AI 会参考但无法主动调用'));
|
|
266
|
+
console.log();
|
|
267
|
+
|
|
268
|
+
console.log(chalk.gray('○ 基础支持') + chalk.gray(' - Codex'));
|
|
269
|
+
console.log(chalk.gray(' └ 仅支持全局提示词,通过 AGENTS.md 模拟'));
|
|
270
|
+
console.log();
|
|
271
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
272
|
+
console.log();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// 交互式选择(inquirer)
|
|
277
|
+
// ============================================================================
|
|
278
|
+
|
|
279
|
+
async function promptToolSelection() {
|
|
280
|
+
printSkillsSupportInfo();
|
|
281
|
+
|
|
282
|
+
const choices = AI_TOOLS.filter(t => t.available).map(tool => ({
|
|
283
|
+
name: `${tool.name} ${chalk.gray(`(${tool.description})`)} ${getSkillsSupportLabel(tool.skillsSupport)}`,
|
|
284
|
+
value: tool.id,
|
|
285
|
+
checked: tool.id === 'claude' // 默认选中 Claude Code
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
const selectedTools = await checkbox({
|
|
289
|
+
message: '选择要配置的 AI 工具(空格选择,回车确认)',
|
|
290
|
+
choices,
|
|
291
|
+
pageSize: 12,
|
|
292
|
+
instructions: false
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (selectedTools.length === 0) {
|
|
296
|
+
const continueWithoutTools = await confirm({
|
|
297
|
+
message: '未选择任何工具,是否继续(仅创建项目结构)?',
|
|
298
|
+
default: false
|
|
299
|
+
});
|
|
300
|
+
if (!continueWithoutTools) {
|
|
301
|
+
console.log(chalk.yellow('已取消初始化。'));
|
|
302
|
+
process.exit(0);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return selectedTools;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// 安装 Slash 命令
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
function installSlashCommands(toolIds, projectDir) {
|
|
314
|
+
const slashSrcDir = path.join(__dirname, '..', 'templates', 'claude-commands', 'devbooks');
|
|
315
|
+
|
|
316
|
+
if (!fs.existsSync(slashSrcDir)) {
|
|
317
|
+
return { results: [], total: 0 };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const results = [];
|
|
321
|
+
|
|
322
|
+
for (const toolId of toolIds) {
|
|
323
|
+
const tool = AI_TOOLS.find(t => t.id === toolId);
|
|
324
|
+
if (!tool) continue;
|
|
325
|
+
|
|
326
|
+
let destDir;
|
|
327
|
+
if (tool.slashDir) {
|
|
328
|
+
destDir = path.join(projectDir, tool.slashDir);
|
|
329
|
+
} else if (tool.globalSlashDir) {
|
|
330
|
+
destDir = expandPath(tool.globalSlashDir);
|
|
331
|
+
} else {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const count = toolId === 'codex'
|
|
336
|
+
? copyCodexPromptsSync(slashSrcDir, destDir)
|
|
337
|
+
: copyDirSync(slashSrcDir, destDir);
|
|
338
|
+
results.push({ tool: tool.name, count, path: destDir });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return { results, total: results.length };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ============================================================================
|
|
345
|
+
// 安装 Skills(仅 Claude Code 和 Qoder)
|
|
346
|
+
// ============================================================================
|
|
347
|
+
|
|
348
|
+
function installSkills(toolIds, update = false) {
|
|
349
|
+
const results = [];
|
|
350
|
+
|
|
351
|
+
for (const toolId of toolIds) {
|
|
352
|
+
const tool = AI_TOOLS.find(t => t.id === toolId);
|
|
353
|
+
if (!tool || tool.skillsSupport !== SKILLS_SUPPORT.FULL) continue;
|
|
354
|
+
|
|
355
|
+
if (toolId === 'claude' && tool.skillsDir) {
|
|
356
|
+
const skillsSrcDir = path.join(__dirname, '..', 'skills');
|
|
357
|
+
const skillsDestDir = tool.skillsDir;
|
|
358
|
+
|
|
359
|
+
if (!fs.existsSync(skillsSrcDir)) continue;
|
|
360
|
+
|
|
361
|
+
const skillDirs = fs.readdirSync(skillsSrcDir)
|
|
362
|
+
.filter(name => name.startsWith('devbooks-'))
|
|
363
|
+
.filter(name => fs.statSync(path.join(skillsSrcDir, name)).isDirectory());
|
|
364
|
+
|
|
365
|
+
if (skillDirs.length === 0) continue;
|
|
366
|
+
|
|
367
|
+
fs.mkdirSync(skillsDestDir, { recursive: true });
|
|
368
|
+
|
|
369
|
+
let installedCount = 0;
|
|
370
|
+
for (const skillName of skillDirs) {
|
|
371
|
+
const srcPath = path.join(skillsSrcDir, skillName);
|
|
372
|
+
const destPath = path.join(skillsDestDir, skillName);
|
|
373
|
+
|
|
374
|
+
if (fs.existsSync(destPath) && !update) continue;
|
|
375
|
+
if (fs.existsSync(destPath)) {
|
|
376
|
+
fs.rmSync(destPath, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
copyDirSync(srcPath, destPath);
|
|
380
|
+
installedCount++;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
results.push({ tool: 'Claude Code', type: 'skills', count: installedCount, total: skillDirs.length });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Qoder: 创建 agents 目录结构(但不复制 Skills,因为格式不同)
|
|
387
|
+
if (toolId === 'qoder') {
|
|
388
|
+
results.push({ tool: 'Qoder', type: 'agents', count: 0, total: 0, note: '需要手动创建 agents/' });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return results;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// 安装 Rules(Cursor, Windsurf, Gemini, Antigravity, OpenCode)
|
|
397
|
+
// ============================================================================
|
|
398
|
+
|
|
399
|
+
function installRules(toolIds, projectDir) {
|
|
400
|
+
const results = [];
|
|
401
|
+
|
|
402
|
+
for (const toolId of toolIds) {
|
|
403
|
+
const tool = AI_TOOLS.find(t => t.id === toolId);
|
|
404
|
+
if (!tool || tool.skillsSupport !== SKILLS_SUPPORT.RULES) continue;
|
|
405
|
+
|
|
406
|
+
if (tool.rulesDir) {
|
|
407
|
+
const rulesDestDir = path.join(projectDir, tool.rulesDir);
|
|
408
|
+
fs.mkdirSync(rulesDestDir, { recursive: true });
|
|
409
|
+
|
|
410
|
+
// 创建 devbooks.md 规则文件
|
|
411
|
+
const ruleContent = generateRuleContent(toolId);
|
|
412
|
+
const ruleFileName = toolId === 'gemini' ? 'GEMINI.md' : 'devbooks.md';
|
|
413
|
+
const rulePath = path.join(rulesDestDir, ruleFileName);
|
|
414
|
+
|
|
415
|
+
if (!fs.existsSync(rulePath)) {
|
|
416
|
+
fs.writeFileSync(rulePath, ruleContent);
|
|
417
|
+
results.push({ tool: tool.name, type: 'rules', path: rulePath });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return results;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function generateRuleContent(toolId) {
|
|
426
|
+
const frontmatter = {
|
|
427
|
+
cursor: `---
|
|
428
|
+
description: DevBooks 工作流规则
|
|
429
|
+
globs: ["**/*"]
|
|
430
|
+
---`,
|
|
431
|
+
windsurf: `---
|
|
432
|
+
trigger: model_decision
|
|
433
|
+
description: DevBooks 工作流规则 - 在处理功能开发、架构变更时自动应用
|
|
434
|
+
---`,
|
|
435
|
+
gemini: '',
|
|
436
|
+
antigravity: `---
|
|
437
|
+
description: DevBooks 工作流规则
|
|
438
|
+
---`,
|
|
439
|
+
opencode: ''
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
return `${frontmatter[toolId] || ''}
|
|
443
|
+
${DEVBOOKS_MARKERS.start}
|
|
444
|
+
# DevBooks 工作流规则
|
|
445
|
+
|
|
446
|
+
## 协议发现
|
|
447
|
+
|
|
448
|
+
在回答任何问题或写任何代码前,按以下顺序查找配置:
|
|
449
|
+
1. \`.devbooks/config.yaml\`(如存在)→ 解析并使用其中的映射
|
|
450
|
+
2. \`dev-playbooks/project.md\`(如存在)→ DevBooks 协议
|
|
451
|
+
|
|
452
|
+
## 核心约束
|
|
453
|
+
|
|
454
|
+
- Test Owner 与 Coder 必须独立对话/独立实例
|
|
455
|
+
- Coder 禁止修改 tests/
|
|
456
|
+
- 任何新功能/破坏性变更/架构改动:必须先创建 \`dev-playbooks/changes/<id>/\`
|
|
457
|
+
|
|
458
|
+
## 工作流命令
|
|
459
|
+
|
|
460
|
+
| 命令 | 说明 |
|
|
461
|
+
|------|------|
|
|
462
|
+
| \`/devbooks:proposal\` | 创建变更提案 |
|
|
463
|
+
| \`/devbooks:design\` | 创建设计文档 |
|
|
464
|
+
| \`/devbooks:apply <role>\` | 执行实现 |
|
|
465
|
+
| \`/devbooks:archive\` | 归档变更包 |
|
|
466
|
+
|
|
467
|
+
${DEVBOOKS_MARKERS.end}
|
|
468
|
+
`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ============================================================================
|
|
472
|
+
// 安装自定义指令文件
|
|
473
|
+
// ============================================================================
|
|
474
|
+
|
|
475
|
+
function installInstructionFiles(toolIds, projectDir) {
|
|
476
|
+
const results = [];
|
|
477
|
+
|
|
478
|
+
for (const toolId of toolIds) {
|
|
479
|
+
const tool = AI_TOOLS.find(t => t.id === toolId);
|
|
480
|
+
if (!tool) continue;
|
|
481
|
+
|
|
482
|
+
// GitHub Copilot 特殊处理
|
|
483
|
+
if (toolId === 'github-copilot') {
|
|
484
|
+
const instructionsDir = path.join(projectDir, '.github', 'instructions');
|
|
485
|
+
fs.mkdirSync(instructionsDir, { recursive: true });
|
|
486
|
+
|
|
487
|
+
const copilotInstructionPath = path.join(projectDir, '.github', 'copilot-instructions.md');
|
|
488
|
+
if (!fs.existsSync(copilotInstructionPath)) {
|
|
489
|
+
fs.writeFileSync(copilotInstructionPath, generateCopilotInstructions());
|
|
490
|
+
results.push({ tool: 'GitHub Copilot', type: 'instructions', path: copilotInstructionPath });
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// 创建 devbooks.instructions.md
|
|
494
|
+
const devbooksInstructionPath = path.join(instructionsDir, 'devbooks.instructions.md');
|
|
495
|
+
if (!fs.existsSync(devbooksInstructionPath)) {
|
|
496
|
+
fs.writeFileSync(devbooksInstructionPath, generateCopilotDevbooksInstructions());
|
|
497
|
+
results.push({ tool: 'GitHub Copilot', type: 'instructions', path: devbooksInstructionPath });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// 创建 AGENTS.md / CLAUDE.md / GEMINI.md
|
|
502
|
+
if (tool.instructionFile && !tool.instructionFile.includes('/')) {
|
|
503
|
+
const instructionPath = path.join(projectDir, tool.instructionFile);
|
|
504
|
+
if (!fs.existsSync(instructionPath)) {
|
|
505
|
+
fs.writeFileSync(instructionPath, generateAgentsContent(tool.instructionFile));
|
|
506
|
+
results.push({ tool: tool.name, type: 'instruction', path: instructionPath });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return results;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function generateCopilotInstructions() {
|
|
515
|
+
return `${DEVBOOKS_MARKERS.start}
|
|
516
|
+
# GitHub Copilot 项目指令
|
|
517
|
+
|
|
518
|
+
## DevBooks 协议
|
|
519
|
+
|
|
520
|
+
本项目使用 DevBooks 工作流进行开发。
|
|
521
|
+
|
|
522
|
+
### 协议发现
|
|
523
|
+
|
|
524
|
+
在回答问题或写代码前,检查:
|
|
525
|
+
1. \`.devbooks/config.yaml\` - DevBooks 配置
|
|
526
|
+
2. \`dev-playbooks/project.md\` - 项目规范
|
|
527
|
+
|
|
528
|
+
### 核心约束
|
|
529
|
+
|
|
530
|
+
- 新功能/架构变更需先创建提案
|
|
531
|
+
- Test Owner 与 Coder 角色分离
|
|
532
|
+
- 禁止在 coder 角色时修改 tests/
|
|
533
|
+
|
|
534
|
+
${DEVBOOKS_MARKERS.end}
|
|
535
|
+
`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function generateCopilotDevbooksInstructions() {
|
|
539
|
+
return `---
|
|
540
|
+
applyTo: "dev-playbooks/**/*"
|
|
541
|
+
description: "DevBooks 工作流文件处理规则"
|
|
542
|
+
---
|
|
543
|
+
${DEVBOOKS_MARKERS.start}
|
|
544
|
+
# DevBooks 文件处理规则
|
|
545
|
+
|
|
546
|
+
当编辑 dev-playbooks/ 目录下的文件时:
|
|
547
|
+
|
|
548
|
+
1. **proposal.md**: 只写 Why/What/Impact,不写实现细节
|
|
549
|
+
2. **design.md**: 写 What/Constraints + AC-xxx,不写函数体代码
|
|
550
|
+
3. **tasks.md**: 可跟踪的任务项,绑定验收锚点
|
|
551
|
+
4. **verification.md**: 追溯矩阵,记录 Red/Green 证据
|
|
552
|
+
|
|
553
|
+
${DEVBOOKS_MARKERS.end}
|
|
554
|
+
`;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function generateAgentsContent(filename) {
|
|
558
|
+
const toolHint = filename === 'CLAUDE.md' ? 'Claude Code'
|
|
559
|
+
: filename === 'GEMINI.md' ? 'Gemini CLI / Antigravity'
|
|
560
|
+
: '兼容 AGENTS.md 的 AI 工具';
|
|
561
|
+
|
|
562
|
+
return `${DEVBOOKS_MARKERS.start}
|
|
563
|
+
# DevBooks 使用说明
|
|
564
|
+
|
|
565
|
+
这些说明适用于 ${toolHint}。
|
|
566
|
+
|
|
567
|
+
## DevBooks 协议发现与约束
|
|
568
|
+
|
|
569
|
+
- **配置发现**:在回答任何问题或写任何代码前,按以下顺序查找配置:
|
|
570
|
+
1. \`.devbooks/config.yaml\`(如存在)→ 解析并使用其中的映射
|
|
571
|
+
2. \`dev-playbooks/project.md\`(如存在)→ DevBooks 协议
|
|
572
|
+
- 找到配置后,先阅读 \`agents_doc\`(规则文档),再执行任何操作。
|
|
573
|
+
- Test Owner 与 Coder 必须独立对话/独立实例;Coder 禁止修改 tests/。
|
|
574
|
+
- 任何新功能/破坏性变更/架构改动:必须先创建 \`dev-playbooks/changes/<id>/\`。
|
|
575
|
+
|
|
576
|
+
## 工作流命令
|
|
577
|
+
|
|
578
|
+
| 命令 | 说明 |
|
|
579
|
+
|------|------|
|
|
580
|
+
| \`/devbooks:proposal\` | 创建变更提案 |
|
|
581
|
+
| \`/devbooks:design\` | 创建设计文档 |
|
|
582
|
+
| \`/devbooks:apply <role>\` | 执行实现(test-owner/coder/reviewer) |
|
|
583
|
+
| \`/devbooks:archive\` | 归档变更包 |
|
|
584
|
+
|
|
585
|
+
${DEVBOOKS_MARKERS.end}
|
|
586
|
+
`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ============================================================================
|
|
590
|
+
// 创建项目结构
|
|
591
|
+
// ============================================================================
|
|
592
|
+
|
|
593
|
+
function createProjectStructure(projectDir) {
|
|
594
|
+
const templateDir = path.join(__dirname, '..', 'templates');
|
|
595
|
+
|
|
596
|
+
const dirs = [
|
|
597
|
+
'dev-playbooks/specs/_meta/anti-patterns',
|
|
598
|
+
'dev-playbooks/specs/architecture',
|
|
599
|
+
'dev-playbooks/changes',
|
|
600
|
+
'dev-playbooks/scripts',
|
|
601
|
+
'.devbooks'
|
|
602
|
+
];
|
|
603
|
+
|
|
604
|
+
for (const dir of dirs) {
|
|
605
|
+
fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const templateFiles = [
|
|
609
|
+
{ src: 'dev-playbooks/README.md', dest: 'dev-playbooks/README.md' },
|
|
610
|
+
{ src: 'dev-playbooks/constitution.md', dest: 'dev-playbooks/constitution.md' },
|
|
611
|
+
{ src: 'dev-playbooks/project.md', dest: 'dev-playbooks/project.md' },
|
|
612
|
+
{ src: 'dev-playbooks/specs/_meta/project-profile.md', dest: 'dev-playbooks/specs/_meta/project-profile.md' },
|
|
613
|
+
{ src: 'dev-playbooks/specs/_meta/glossary.md', dest: 'dev-playbooks/specs/_meta/glossary.md' },
|
|
614
|
+
{ src: 'dev-playbooks/specs/architecture/fitness-rules.md', dest: 'dev-playbooks/specs/architecture/fitness-rules.md' },
|
|
615
|
+
{ src: '.devbooks/config.yaml', dest: '.devbooks/config.yaml' }
|
|
616
|
+
];
|
|
617
|
+
|
|
618
|
+
let copiedCount = 0;
|
|
619
|
+
for (const { src, dest } of templateFiles) {
|
|
620
|
+
const srcPath = path.join(templateDir, src);
|
|
621
|
+
const destPath = path.join(projectDir, dest);
|
|
622
|
+
|
|
623
|
+
if (fs.existsSync(srcPath) && !fs.existsSync(destPath)) {
|
|
624
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
625
|
+
fs.copyFileSync(srcPath, destPath);
|
|
626
|
+
copiedCount++;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return copiedCount;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ============================================================================
|
|
634
|
+
// 保存配置
|
|
635
|
+
// ============================================================================
|
|
636
|
+
|
|
637
|
+
function saveConfig(toolIds, projectDir) {
|
|
638
|
+
const configPath = path.join(projectDir, '.devbooks', 'config.yaml');
|
|
639
|
+
|
|
640
|
+
// 读取现有配置或创建新配置
|
|
641
|
+
let configContent = '';
|
|
642
|
+
if (fs.existsSync(configPath)) {
|
|
643
|
+
configContent = fs.readFileSync(configPath, 'utf-8');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// 更新 ai_tools 部分
|
|
647
|
+
const toolsYaml = `ai_tools:\n${toolIds.map(id => ` - ${id}`).join('\n')}`;
|
|
648
|
+
|
|
649
|
+
if (configContent.includes('ai_tools:')) {
|
|
650
|
+
// 替换现有的 ai_tools 部分
|
|
651
|
+
configContent = configContent.replace(/ai_tools:[\s\S]*?(?=\n\w|\n$|$)/, toolsYaml + '\n');
|
|
652
|
+
} else {
|
|
653
|
+
// 追加 ai_tools 部分
|
|
654
|
+
configContent = configContent.trimEnd() + '\n\n' + toolsYaml + '\n';
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
fs.writeFileSync(configPath, configContent);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function loadConfig(projectDir) {
|
|
661
|
+
const configPath = path.join(projectDir, '.devbooks', 'config.yaml');
|
|
662
|
+
|
|
663
|
+
if (!fs.existsSync(configPath)) {
|
|
664
|
+
return { aiTools: [] };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
668
|
+
const match = content.match(/ai_tools:\s*([\s\S]*?)(?=\n\w|\n$|$)/);
|
|
669
|
+
|
|
670
|
+
if (!match) {
|
|
671
|
+
return { aiTools: [] };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const tools = match[1]
|
|
675
|
+
.split('\n')
|
|
676
|
+
.map(line => line.trim())
|
|
677
|
+
.filter(line => line.startsWith('-'))
|
|
678
|
+
.map(line => line.replace(/^-\s*/, '').trim());
|
|
679
|
+
|
|
680
|
+
return { aiTools: tools };
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// ============================================================================
|
|
684
|
+
// Init 命令
|
|
685
|
+
// ============================================================================
|
|
686
|
+
|
|
687
|
+
async function initCommand(projectDir, options) {
|
|
688
|
+
console.log();
|
|
689
|
+
console.log(chalk.cyan('╔══════════════════════════════════════╗'));
|
|
690
|
+
console.log(chalk.cyan('║') + chalk.bold(' DevBooks 初始化向导 ') + chalk.cyan('║'));
|
|
691
|
+
console.log(chalk.cyan('╚══════════════════════════════════════╝'));
|
|
692
|
+
console.log();
|
|
693
|
+
|
|
694
|
+
// 确定选择的工具
|
|
695
|
+
let selectedTools;
|
|
696
|
+
|
|
697
|
+
if (options.tools) {
|
|
698
|
+
if (options.tools === 'all') {
|
|
699
|
+
selectedTools = AI_TOOLS.filter(t => t.available).map(t => t.id);
|
|
700
|
+
} else if (options.tools === 'none') {
|
|
701
|
+
selectedTools = [];
|
|
702
|
+
} else {
|
|
703
|
+
selectedTools = options.tools.split(',').map(t => t.trim()).filter(t =>
|
|
704
|
+
AI_TOOLS.some(tool => tool.id === t)
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
console.log(chalk.blue('ℹ') + ` 非交互式模式:${selectedTools.length > 0 ? selectedTools.join(', ') : '无'}`);
|
|
708
|
+
} else {
|
|
709
|
+
selectedTools = await promptToolSelection();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// 创建项目结构
|
|
713
|
+
const spinner = ora('创建项目结构...').start();
|
|
714
|
+
const templateCount = createProjectStructure(projectDir);
|
|
715
|
+
spinner.succeed(`创建了 ${templateCount} 个模板文件`);
|
|
716
|
+
|
|
717
|
+
// 保存配置
|
|
718
|
+
saveConfig(selectedTools, projectDir);
|
|
719
|
+
|
|
720
|
+
if (selectedTools.length === 0) {
|
|
721
|
+
console.log();
|
|
722
|
+
console.log(chalk.green('✓') + ' DevBooks 项目结构已创建!');
|
|
723
|
+
console.log(chalk.gray(` 运行 \`${CLI_COMMAND} init\` 并选择 AI 工具来配置集成。`));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// 安装 Slash 命令
|
|
728
|
+
const slashSpinner = ora('安装 Slash 命令...').start();
|
|
729
|
+
const slashResults = installSlashCommands(selectedTools, projectDir);
|
|
730
|
+
slashSpinner.succeed(`安装了 ${slashResults.results.length} 个工具的 Slash 命令`);
|
|
731
|
+
|
|
732
|
+
for (const result of slashResults.results) {
|
|
733
|
+
console.log(chalk.gray(` └ ${result.tool}: ${result.count} 个命令`));
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// 安装 Skills(仅完整支持的工具)
|
|
737
|
+
const fullSupportTools = selectedTools.filter(id => {
|
|
738
|
+
const tool = AI_TOOLS.find(t => t.id === id);
|
|
739
|
+
return tool && tool.skillsSupport === SKILLS_SUPPORT.FULL;
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
if (fullSupportTools.length > 0) {
|
|
743
|
+
const skillsSpinner = ora('安装 Skills...').start();
|
|
744
|
+
const skillsResults = installSkills(fullSupportTools);
|
|
745
|
+
skillsSpinner.succeed('Skills 安装完成');
|
|
746
|
+
|
|
747
|
+
for (const result of skillsResults) {
|
|
748
|
+
if (result.count > 0) {
|
|
749
|
+
console.log(chalk.gray(` └ ${result.tool}: ${result.count}/${result.total} 个 ${result.type}`));
|
|
750
|
+
} else if (result.note) {
|
|
751
|
+
console.log(chalk.gray(` └ ${result.tool}: ${result.note}`));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// 安装 Rules(Rules 类似系统的工具)
|
|
757
|
+
const rulesTools = selectedTools.filter(id => {
|
|
758
|
+
const tool = AI_TOOLS.find(t => t.id === id);
|
|
759
|
+
return tool && tool.skillsSupport === SKILLS_SUPPORT.RULES;
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
if (rulesTools.length > 0) {
|
|
763
|
+
const rulesSpinner = ora('安装 Rules...').start();
|
|
764
|
+
const rulesResults = installRules(rulesTools, projectDir);
|
|
765
|
+
rulesSpinner.succeed(`创建了 ${rulesResults.length} 个规则文件`);
|
|
766
|
+
|
|
767
|
+
for (const result of rulesResults) {
|
|
768
|
+
console.log(chalk.gray(` └ ${result.tool}: ${path.relative(projectDir, result.path)}`));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 安装指令文件
|
|
773
|
+
const instructionSpinner = ora('创建指令文件...').start();
|
|
774
|
+
const instructionResults = installInstructionFiles(selectedTools, projectDir);
|
|
775
|
+
instructionSpinner.succeed(`创建了 ${instructionResults.length} 个指令文件`);
|
|
776
|
+
|
|
777
|
+
for (const result of instructionResults) {
|
|
778
|
+
console.log(chalk.gray(` └ ${result.tool}: ${path.relative(projectDir, result.path)}`));
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// 完成
|
|
782
|
+
console.log();
|
|
783
|
+
console.log(chalk.green('══════════════════════════════════════'));
|
|
784
|
+
console.log(chalk.green('✓') + chalk.bold(' DevBooks 初始化完成!'));
|
|
785
|
+
console.log(chalk.green('══════════════════════════════════════'));
|
|
786
|
+
console.log();
|
|
787
|
+
|
|
788
|
+
// 显示已配置的工具
|
|
789
|
+
console.log(chalk.white('已配置的 AI 工具:'));
|
|
790
|
+
for (const toolId of selectedTools) {
|
|
791
|
+
const tool = AI_TOOLS.find(t => t.id === toolId);
|
|
792
|
+
if (tool) {
|
|
793
|
+
console.log(` ${chalk.cyan('▸')} ${tool.name} ${getSkillsSupportLabel(tool.skillsSupport)}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
console.log();
|
|
797
|
+
|
|
798
|
+
// 下一步提示
|
|
799
|
+
console.log(chalk.bold('下一步:'));
|
|
800
|
+
console.log(` 1. 编辑 ${chalk.cyan('dev-playbooks/project.md')} 添加项目信息`);
|
|
801
|
+
console.log(` 2. 使用 ${chalk.cyan('/devbooks:proposal')} 创建第一个变更提案`);
|
|
802
|
+
console.log();
|
|
803
|
+
console.log(chalk.yellow('重要提示:'));
|
|
804
|
+
console.log(' Slash 命令在 IDE 启动时加载,请重启你的 AI 工具以使命令生效。');
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// ============================================================================
|
|
808
|
+
// Update 命令
|
|
809
|
+
// ============================================================================
|
|
810
|
+
|
|
811
|
+
async function updateCommand(projectDir) {
|
|
812
|
+
console.log();
|
|
813
|
+
console.log(chalk.bold('DevBooks 更新'));
|
|
814
|
+
console.log();
|
|
815
|
+
|
|
816
|
+
// 检查是否已初始化
|
|
817
|
+
const configPath = path.join(projectDir, '.devbooks', 'config.yaml');
|
|
818
|
+
if (!fs.existsSync(configPath)) {
|
|
819
|
+
console.log(chalk.red('✗') + ` 未找到 DevBooks 配置。请先运行 \`${CLI_COMMAND} init\`。`);
|
|
820
|
+
process.exit(1);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// 加载配置
|
|
824
|
+
const config = loadConfig(projectDir);
|
|
825
|
+
const configuredTools = config.aiTools;
|
|
826
|
+
|
|
827
|
+
if (configuredTools.length === 0) {
|
|
828
|
+
console.log(chalk.yellow('⚠') + ` 未配置任何 AI 工具。运行 \`${CLI_COMMAND} init\` 进行配置。`);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const toolNames = configuredTools.map(id => {
|
|
833
|
+
const tool = AI_TOOLS.find(t => t.id === id);
|
|
834
|
+
return tool ? tool.name : id;
|
|
835
|
+
});
|
|
836
|
+
console.log(chalk.blue('ℹ') + ` 检测到已配置的工具: ${toolNames.join(', ')}`);
|
|
837
|
+
|
|
838
|
+
// 更新 Slash 命令
|
|
839
|
+
const slashResults = installSlashCommands(configuredTools, projectDir);
|
|
840
|
+
for (const result of slashResults.results) {
|
|
841
|
+
console.log(chalk.green('✓') + ` ${result.tool}: 更新了 ${result.count} 个 slash 命令`);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// 更新 Skills
|
|
845
|
+
const skillsResults = installSkills(configuredTools, true);
|
|
846
|
+
for (const result of skillsResults) {
|
|
847
|
+
if (result.count > 0) {
|
|
848
|
+
console.log(chalk.green('✓') + ` ${result.tool} ${result.type}: 更新了 ${result.count}/${result.total} 个`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// 更新 Rules
|
|
853
|
+
const rulesTools = configuredTools.filter(id => {
|
|
854
|
+
const tool = AI_TOOLS.find(t => t.id === id);
|
|
855
|
+
return tool && tool.skillsSupport === SKILLS_SUPPORT.RULES;
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
if (rulesTools.length > 0) {
|
|
859
|
+
const rulesResults = installRules(rulesTools, projectDir);
|
|
860
|
+
for (const result of rulesResults) {
|
|
861
|
+
console.log(chalk.green('✓') + ` ${result.tool}: 更新了规则文件`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
console.log();
|
|
866
|
+
console.log(chalk.green('✓') + ' 更新完成!');
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// ============================================================================
|
|
870
|
+
// 帮助信息
|
|
871
|
+
// ============================================================================
|
|
872
|
+
|
|
873
|
+
function showHelp() {
|
|
874
|
+
console.log();
|
|
875
|
+
console.log(chalk.bold('DevBooks') + ' - AI-agnostic spec-driven development workflow');
|
|
876
|
+
console.log();
|
|
877
|
+
console.log(chalk.cyan('用法:'));
|
|
878
|
+
console.log(` ${CLI_COMMAND} init [path] [options] 初始化 DevBooks`);
|
|
879
|
+
console.log(` ${CLI_COMMAND} update [path] 更新已配置的工具`);
|
|
880
|
+
console.log();
|
|
881
|
+
console.log(chalk.cyan('选项:'));
|
|
882
|
+
console.log(' --tools <tools> 非交互式指定 AI 工具');
|
|
883
|
+
console.log(' 可用值: all, none, 或逗号分隔的工具 ID');
|
|
884
|
+
console.log(' -h, --help 显示此帮助信息');
|
|
885
|
+
console.log();
|
|
886
|
+
console.log(chalk.cyan('支持的 AI 工具:'));
|
|
887
|
+
|
|
888
|
+
// 按 Skills 支持级别分组显示
|
|
889
|
+
const groupedTools = {
|
|
890
|
+
[SKILLS_SUPPORT.FULL]: [],
|
|
891
|
+
[SKILLS_SUPPORT.RULES]: [],
|
|
892
|
+
[SKILLS_SUPPORT.AGENTS]: [],
|
|
893
|
+
[SKILLS_SUPPORT.BASIC]: []
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
for (const tool of AI_TOOLS.filter(t => t.available)) {
|
|
897
|
+
groupedTools[tool.skillsSupport].push(tool);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
console.log();
|
|
901
|
+
console.log(chalk.green(' ★ 完整 Skills 支持:'));
|
|
902
|
+
for (const tool of groupedTools[SKILLS_SUPPORT.FULL]) {
|
|
903
|
+
console.log(` ${tool.id.padEnd(15)} ${tool.name}`);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
console.log();
|
|
907
|
+
console.log(chalk.blue(' ◆ Rules 系统支持:'));
|
|
908
|
+
for (const tool of groupedTools[SKILLS_SUPPORT.RULES]) {
|
|
909
|
+
console.log(` ${tool.id.padEnd(15)} ${tool.name}`);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
console.log();
|
|
913
|
+
console.log(chalk.yellow(' ● 自定义指令支持:'));
|
|
914
|
+
for (const tool of groupedTools[SKILLS_SUPPORT.AGENTS]) {
|
|
915
|
+
console.log(` ${tool.id.padEnd(15)} ${tool.name}`);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
console.log();
|
|
919
|
+
console.log(chalk.gray(' ○ 基础支持:'));
|
|
920
|
+
for (const tool of groupedTools[SKILLS_SUPPORT.BASIC]) {
|
|
921
|
+
console.log(` ${tool.id.padEnd(15)} ${tool.name}`);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
console.log();
|
|
925
|
+
console.log(chalk.cyan('示例:'));
|
|
926
|
+
console.log(` ${CLI_COMMAND} init # 交互式初始化`);
|
|
927
|
+
console.log(` ${CLI_COMMAND} init my-project # 在 my-project 目录初始化`);
|
|
928
|
+
console.log(` ${CLI_COMMAND} init --tools claude,cursor # 非交互式`);
|
|
929
|
+
console.log(` ${CLI_COMMAND} update # 更新已配置的工具`);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// ============================================================================
|
|
933
|
+
// 主入口
|
|
934
|
+
// ============================================================================
|
|
935
|
+
|
|
936
|
+
async function main() {
|
|
937
|
+
const args = process.argv.slice(2);
|
|
938
|
+
|
|
939
|
+
// 解析参数
|
|
940
|
+
let command = null;
|
|
941
|
+
let projectPath = null;
|
|
942
|
+
const options = {};
|
|
943
|
+
|
|
944
|
+
for (let i = 0; i < args.length; i++) {
|
|
945
|
+
const arg = args[i];
|
|
946
|
+
|
|
947
|
+
if (arg === '-h' || arg === '--help') {
|
|
948
|
+
showHelp();
|
|
949
|
+
process.exit(0);
|
|
950
|
+
} else if (arg === '--tools') {
|
|
951
|
+
options.tools = args[++i];
|
|
952
|
+
} else if (!arg.startsWith('-')) {
|
|
953
|
+
if (!command) {
|
|
954
|
+
command = arg;
|
|
955
|
+
} else if (!projectPath) {
|
|
956
|
+
projectPath = arg;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// 确定项目目录
|
|
962
|
+
const projectDir = projectPath ? path.resolve(projectPath) : process.cwd();
|
|
963
|
+
|
|
964
|
+
// 执行命令
|
|
965
|
+
try {
|
|
966
|
+
if (command === 'init' || !command) {
|
|
967
|
+
await initCommand(projectDir, options);
|
|
968
|
+
} else if (command === 'update') {
|
|
969
|
+
await updateCommand(projectDir);
|
|
970
|
+
} else {
|
|
971
|
+
console.log(chalk.red(`未知命令: ${command}`));
|
|
972
|
+
showHelp();
|
|
973
|
+
process.exit(1);
|
|
974
|
+
}
|
|
975
|
+
} catch (error) {
|
|
976
|
+
if (error.name === 'ExitPromptError') {
|
|
977
|
+
console.log(chalk.yellow('\n已取消。'));
|
|
978
|
+
process.exit(0);
|
|
979
|
+
}
|
|
980
|
+
throw error;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
main().catch(error => {
|
|
985
|
+
console.error(chalk.red('✗'), error.message);
|
|
986
|
+
process.exit(1);
|
|
987
|
+
});
|