claude-sdlc 1.0.2 → 1.0.3
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/README.md +22 -8
- package/bin/cli.js +33 -18
- package/lib/installer.js +237 -142
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo-banner.svg" alt="claude-sdlc" width="700"/>
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>让 Claude Code 严格按 SDLC 规范开发 — 一条命令安装,零配置开箱即用。</strong>
|
|
7
|
+
</p>
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/claude-sdlc"><img src="https://img.shields.io/npm/v/claude-sdlc.svg" alt="npm version"/></a>
|
|
11
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/npm/l/claude-sdlc.svg" alt="license"/></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/claude-sdlc"><img src="https://img.shields.io/npm/dm/claude-sdlc.svg" alt="downloads"/></a>
|
|
13
|
+
</p>
|
|
7
14
|
|
|
8
15
|
---
|
|
9
16
|
|
|
@@ -21,6 +28,12 @@ npx claude-sdlc ./my-project
|
|
|
21
28
|
|
|
22
29
|
安装完成后,在项目目录启动 `claude` 即可自动加载全部规范。**每个项目只需安装一次。**
|
|
23
30
|
|
|
31
|
+
一键卸载:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx claude-sdlc uninstall
|
|
35
|
+
```
|
|
36
|
+
|
|
24
37
|
---
|
|
25
38
|
|
|
26
39
|
## 解决什么问题
|
|
@@ -145,19 +158,20 @@ cd claude-sdlc
|
|
|
145
158
|
## 常见问题
|
|
146
159
|
|
|
147
160
|
**已有 CLAUDE.md 会被覆盖吗?**
|
|
148
|
-
|
|
161
|
+
会直接覆盖为最新版本。
|
|
149
162
|
|
|
150
163
|
**已有 settings.json 怎么办?**
|
|
151
|
-
自动智能合并 hooks
|
|
164
|
+
自动智能合并 hooks 配置(去重),保留原有配置不丢失。
|
|
152
165
|
|
|
153
166
|
**可以跳过阶段吗?**
|
|
154
167
|
可以,Claude 会先说明风险,确认后记录跳过原因并推进。
|
|
155
168
|
|
|
156
169
|
**如何卸载?**
|
|
157
170
|
```bash
|
|
158
|
-
|
|
159
|
-
|
|
171
|
+
npx claude-sdlc uninstall # 卸载当前目录
|
|
172
|
+
npx claude-sdlc uninstall ./my-project # 卸载指定目录
|
|
160
173
|
```
|
|
174
|
+
自动清除 CLAUDE.md、.claude/rules/、hooks/、commands/、reviews/、settings.json 中的 hooks 配置。
|
|
161
175
|
|
|
162
176
|
---
|
|
163
177
|
|
package/bin/cli.js
CHANGED
|
@@ -2,24 +2,34 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { install } = require('../lib/installer');
|
|
5
|
+
const { install, uninstall } = require('../lib/installer');
|
|
6
6
|
|
|
7
7
|
const pkg = require('../package.json');
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
让 Claude Code 严格按照 SDLC 规范开发 — 一条命令安装
|
|
13
|
-
|
|
14
|
-
用法:
|
|
15
|
-
claude-sdlc [目标路径] 安装 SDLC 规范到目标项目(默认当前目录)
|
|
16
|
-
claude-sdlc --help 显示帮助
|
|
17
|
-
claude-sdlc --version 显示版本
|
|
9
|
+
// 颜色
|
|
10
|
+
const C = '\x1b[36m', B = '\x1b[1m', D = '\x1b[2m', R = '\x1b[0m';
|
|
11
|
+
const M = '\x1b[35m', BL = '\x1b[34m', G = '\x1b[32m', RD = '\x1b[31m';
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
const HELP = `
|
|
14
|
+
${C}┌─────┐${R}
|
|
15
|
+
${C}┌┘${R} ${D}P1→P2${R} ${C}└┐${R} ${C}${B}claude-sdlc${R} ${D}v${pkg.version}${R}
|
|
16
|
+
${M}│${R} ${D}↑${R} ${G}✔${R} ${D}↓${R} ${BL}│${R} ${D}SDLC Enforcer for Claude Code${R}
|
|
17
|
+
${M}│${R} ${D}P6 P3${R} ${BL}│${R} ${D}by 沐谦${R}
|
|
18
|
+
${M}└┐${R} ${D}P5←P4${R} ${BL}┌┘${R}
|
|
19
|
+
${BL}└──▽──┘${R}
|
|
20
|
+
|
|
21
|
+
${B}用法${R}
|
|
22
|
+
claude-sdlc ${D}[目标路径]${R} 安装到目标项目
|
|
23
|
+
claude-sdlc uninstall ${D}[目标路径]${R} 卸载全部 SDLC 文件
|
|
24
|
+
claude-sdlc --help 显示帮助
|
|
25
|
+
claude-sdlc --version 显示版本
|
|
26
|
+
|
|
27
|
+
${B}示例${R}
|
|
28
|
+
${C}$${R} npx claude-sdlc ${D}# 安装到当前目录${R}
|
|
29
|
+
${C}$${R} npx claude-sdlc ./myapp ${D}# 安装到指定目录${R}
|
|
30
|
+
${C}$${R} npx claude-sdlc uninstall ${D}# 从当前目录卸载${R}
|
|
31
|
+
${C}$${R} npx claude-sdlc uninstall ./myapp ${D}# 从指定目录卸载${R}
|
|
32
|
+
`.trimEnd();
|
|
23
33
|
|
|
24
34
|
const args = process.argv.slice(2);
|
|
25
35
|
|
|
@@ -33,7 +43,12 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
33
43
|
process.exit(0);
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
if (args[0] === 'uninstall') {
|
|
47
|
+
const targetArg = args[1] || '.';
|
|
48
|
+
const targetDir = path.resolve(targetArg);
|
|
49
|
+
uninstall(targetDir);
|
|
50
|
+
} else {
|
|
51
|
+
const targetArg = args[0] || '.';
|
|
52
|
+
const targetDir = path.resolve(targetArg);
|
|
53
|
+
install(targetDir);
|
|
54
|
+
}
|
package/lib/installer.js
CHANGED
|
@@ -3,59 +3,84 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
// ── 颜色 & 样式 ──────────────────────────────────────
|
|
7
|
+
const RESET = '\x1b[0m';
|
|
8
|
+
const BOLD = '\x1b[1m';
|
|
9
|
+
const DIM = '\x1b[2m';
|
|
10
|
+
const RED = '\x1b[31m';
|
|
11
|
+
const GREEN = '\x1b[32m';
|
|
12
|
+
const YELLOW = '\x1b[33m';
|
|
13
|
+
const BLUE = '\x1b[34m';
|
|
14
|
+
const MAGENTA = '\x1b[35m';
|
|
15
|
+
const CYAN = '\x1b[36m';
|
|
16
|
+
const WHITE = '\x1b[37m';
|
|
17
|
+
const BG_GREEN = '\x1b[42m';
|
|
18
|
+
const BG_RED = '\x1b[41m';
|
|
19
|
+
|
|
20
|
+
// ── 符号 ──────────────────────────────────────────────
|
|
21
|
+
const SYM = {
|
|
22
|
+
check: `${GREEN}✔${RESET}`,
|
|
23
|
+
cross: `${RED}✘${RESET}`,
|
|
24
|
+
warn: `${YELLOW}⚠${RESET}`,
|
|
25
|
+
arrow: `${CYAN}▸${RESET}`,
|
|
26
|
+
dot: `${DIM}·${RESET}`,
|
|
27
|
+
bar: `${DIM}│${RESET}`,
|
|
28
|
+
corner: `${DIM}└─${RESET}`,
|
|
29
|
+
tee: `${DIM}├─${RESET}`,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// ── 输出函数 ──────────────────────────────────────────
|
|
33
|
+
function step(num, total, msg) {
|
|
34
|
+
console.log(` ${DIM}[${num}/${total}]${RESET} ${msg}`);
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
function copyFiles(srcDir, destDir, ext, label) {
|
|
32
|
-
if (!fs.existsSync(srcDir)) return [];
|
|
33
|
-
const files = fs.readdirSync(srcDir).filter(f => f.endsWith(ext));
|
|
34
|
-
const copied = [];
|
|
35
|
-
for (const file of files) {
|
|
36
|
-
fs.copyFileSync(path.join(srcDir, file), path.join(destDir, file));
|
|
37
|
-
success(`已安装${label}:${file}`);
|
|
38
|
-
copied.push(file);
|
|
39
|
-
}
|
|
40
|
-
return copied;
|
|
37
|
+
function fileLog(symbol, filePath) {
|
|
38
|
+
console.log(` ${symbol} ${filePath}`);
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
function blank() { console.log(''); }
|
|
42
|
+
|
|
43
|
+
function asciiLogo(mode = 'install') {
|
|
44
|
+
const ver = require('../package.json').version;
|
|
45
|
+
const c1 = CYAN, c2 = BLUE, c3 = MAGENTA, c4 = GREEN;
|
|
46
|
+
const accent = mode === 'install' ? CYAN : RED;
|
|
47
|
+
blank();
|
|
48
|
+
console.log(` ${c1}┌─────┐${RESET}`);
|
|
49
|
+
console.log(` ${c1}┌┘${RESET} ${DIM}P1→P2${RESET} ${c1}└┐${RESET} ${accent}${BOLD}claude-sdlc${RESET} ${DIM}v${ver}${RESET}`);
|
|
50
|
+
console.log(` ${c3}│${RESET} ${DIM}↑${RESET} ${c4}✔${RESET} ${DIM}↓${RESET} ${c2}│${RESET} ${DIM}SDLC Enforcer for Claude Code${RESET}`);
|
|
51
|
+
console.log(` ${c3}│${RESET} ${DIM}P6 P3${RESET} ${c2}│${RESET} ${DIM}by 沐谦${RESET}`);
|
|
52
|
+
console.log(` ${c3}└┐${RESET} ${DIM}P5←P4${RESET} ${c2}┌┘${RESET}`);
|
|
53
|
+
console.log(` ${c2}└──▽──┘${RESET}`);
|
|
54
|
+
blank();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function banner(title, color = CYAN) {
|
|
58
|
+
const line = '─'.repeat(44);
|
|
59
|
+
blank();
|
|
60
|
+
console.log(` ${color}${BOLD}┌${line}┐${RESET}`);
|
|
61
|
+
console.log(` ${color}${BOLD}│${RESET} ${color}${BOLD}${title.padEnd(42)}${RESET}${color}${BOLD}│${RESET}`);
|
|
62
|
+
console.log(` ${color}${BOLD}└${line}┘${RESET}`);
|
|
63
|
+
blank();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resultBanner(text, ok = true) {
|
|
67
|
+
const color = ok ? GREEN : RED;
|
|
68
|
+
const bg = ok ? BG_GREEN : BG_RED;
|
|
69
|
+
const icon = ok ? ' ✔ ' : ' ✘ ';
|
|
70
|
+
blank();
|
|
71
|
+
console.log(` ${bg}${BOLD}${WHITE}${icon}${text} ${RESET}`);
|
|
72
|
+
blank();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── 工具函数 ──────────────────────────────────────────
|
|
76
|
+
|
|
47
77
|
function hookGroupKey(group) {
|
|
48
78
|
if (group.hooks && Array.isArray(group.hooks)) {
|
|
49
79
|
return group.hooks.map(h => h.command || h.prompt).join('|');
|
|
50
80
|
}
|
|
51
|
-
// 兼容旧格式
|
|
52
81
|
return group.command || group.prompt || JSON.stringify(group);
|
|
53
82
|
}
|
|
54
83
|
|
|
55
|
-
/**
|
|
56
|
-
* 智能合并 settings.json — 合并 hooks 数组(去重)
|
|
57
|
-
* 格式:{ matcher: "Write|Edit", hooks: [{ type, command/prompt }] }
|
|
58
|
-
*/
|
|
59
84
|
function mergeSettings(existing, template) {
|
|
60
85
|
const hookTypes = ['PreToolUse', 'PostToolUse', 'Stop', 'PreCompact'];
|
|
61
86
|
const result = JSON.parse(JSON.stringify(existing));
|
|
@@ -82,159 +107,229 @@ function mergeSettings(existing, template) {
|
|
|
82
107
|
return result;
|
|
83
108
|
}
|
|
84
109
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
function copyFilesQuiet(srcDir, destDir, ext) {
|
|
111
|
+
if (!fs.existsSync(srcDir)) return [];
|
|
112
|
+
const files = fs.readdirSync(srcDir).filter(f => f.endsWith(ext));
|
|
113
|
+
for (const file of files) {
|
|
114
|
+
fs.copyFileSync(path.join(srcDir, file), path.join(destDir, file));
|
|
115
|
+
}
|
|
116
|
+
return files;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── 安装 ──────────────────────────────────────────────
|
|
120
|
+
|
|
88
121
|
function install(targetDir) {
|
|
89
|
-
// 模板目录位于包的 template/ 下
|
|
90
122
|
const templateDir = path.join(__dirname, '..', 'template');
|
|
123
|
+
const STEPS = 6;
|
|
91
124
|
|
|
92
|
-
//
|
|
125
|
+
// 验证
|
|
93
126
|
if (!fs.existsSync(targetDir)) {
|
|
94
|
-
error(
|
|
127
|
+
console.error(`\n ${SYM.cross} 目标目录不存在:${targetDir}\n`);
|
|
95
128
|
process.exit(1);
|
|
96
129
|
}
|
|
97
|
-
|
|
98
130
|
if (!fs.statSync(targetDir).isDirectory()) {
|
|
99
|
-
error(
|
|
131
|
+
console.error(`\n ${SYM.cross} 目标路径不是目录:${targetDir}\n`);
|
|
100
132
|
process.exit(1);
|
|
101
133
|
}
|
|
102
|
-
|
|
103
|
-
// 验证模板目录
|
|
104
134
|
if (!fs.existsSync(templateDir)) {
|
|
105
|
-
error(
|
|
135
|
+
console.error(`\n ${SYM.cross} 找不到 template 目录:${templateDir}\n`);
|
|
106
136
|
process.exit(1);
|
|
107
137
|
}
|
|
108
138
|
|
|
109
|
-
|
|
110
|
-
console.log('========================================');
|
|
111
|
-
console.log(' SDLC Enforcer 安装程序');
|
|
112
|
-
console.log('========================================');
|
|
113
|
-
console.log('');
|
|
114
|
-
info(`模板目录:${templateDir}`);
|
|
115
|
-
info(`目标项目:${targetDir}`);
|
|
116
|
-
console.log('');
|
|
117
|
-
|
|
118
|
-
// === Step 1: 备份已有的 CLAUDE.md ===
|
|
119
|
-
const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
|
|
120
|
-
if (fs.existsSync(claudeMdPath)) {
|
|
121
|
-
const backupName = `CLAUDE.md.bak.${timestamp()}`;
|
|
122
|
-
const backupPath = path.join(targetDir, backupName);
|
|
123
|
-
warn(`目标项目已有 CLAUDE.md,备份为:${backupName}`);
|
|
124
|
-
fs.copyFileSync(claudeMdPath, backupPath);
|
|
125
|
-
success('已备份 CLAUDE.md');
|
|
126
|
-
}
|
|
139
|
+
asciiLogo('install');
|
|
127
140
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
const installLine = '─'.repeat(44);
|
|
142
|
+
console.log(` ${CYAN}${BOLD}┌${installLine}┐${RESET}`);
|
|
143
|
+
console.log(` ${CYAN}${BOLD}│${RESET} ${CYAN}${BOLD}${'安 装'.padEnd(41)}${RESET}${CYAN}${BOLD}│${RESET}`);
|
|
144
|
+
console.log(` ${CYAN}${BOLD}└${installLine}┘${RESET}`);
|
|
145
|
+
blank();
|
|
146
|
+
console.log(` ${SYM.arrow} 目标 ${BOLD}${targetDir}${RESET}`);
|
|
147
|
+
blank();
|
|
131
148
|
|
|
132
|
-
//
|
|
149
|
+
// Step 1: CLAUDE.md
|
|
150
|
+
step(1, STEPS, '安装核心控制文件');
|
|
151
|
+
fs.copyFileSync(
|
|
152
|
+
path.join(templateDir, 'CLAUDE.md'),
|
|
153
|
+
path.join(targetDir, 'CLAUDE.md')
|
|
154
|
+
);
|
|
155
|
+
fileLog(SYM.check, 'CLAUDE.md');
|
|
156
|
+
|
|
157
|
+
// Step 2: 目录结构
|
|
158
|
+
step(2, STEPS, '创建目录结构');
|
|
133
159
|
const dirs = ['rules', 'hooks', 'commands', 'reviews'];
|
|
134
160
|
for (const dir of dirs) {
|
|
135
161
|
fs.mkdirSync(path.join(targetDir, '.claude', dir), { recursive: true });
|
|
162
|
+
fileLog(SYM.check, `.claude/${dir}/`);
|
|
136
163
|
}
|
|
137
|
-
success('已创建 .claude/ 目录结构');
|
|
138
164
|
|
|
139
|
-
//
|
|
140
|
-
|
|
165
|
+
// Step 3: 规则文件
|
|
166
|
+
step(3, STEPS, '安装规则文件');
|
|
167
|
+
const rules = copyFilesQuiet(
|
|
141
168
|
path.join(templateDir, '.claude', 'rules'),
|
|
142
169
|
path.join(targetDir, '.claude', 'rules'),
|
|
143
|
-
'.md'
|
|
144
|
-
'规则'
|
|
170
|
+
'.md'
|
|
145
171
|
);
|
|
172
|
+
fileLog(SYM.check, `${rules.length} 个规则文件`);
|
|
146
173
|
|
|
147
|
-
//
|
|
174
|
+
// Step 4: Hook 脚本
|
|
175
|
+
step(4, STEPS, '安装 Hook 脚本');
|
|
148
176
|
const hooksSrcDir = path.join(templateDir, '.claude', 'hooks');
|
|
149
177
|
const hooksDestDir = path.join(targetDir, '.claude', 'hooks');
|
|
178
|
+
let hookCount = 0;
|
|
150
179
|
if (fs.existsSync(hooksSrcDir)) {
|
|
151
180
|
const hookFiles = fs.readdirSync(hooksSrcDir).filter(f => f.endsWith('.sh'));
|
|
152
181
|
for (const file of hookFiles) {
|
|
153
182
|
const destPath = path.join(hooksDestDir, file);
|
|
154
183
|
fs.copyFileSync(path.join(hooksSrcDir, file), destPath);
|
|
155
|
-
try {
|
|
156
|
-
|
|
157
|
-
} catch (_) {
|
|
158
|
-
// Windows 下 chmod 可能不支持,忽略
|
|
159
|
-
}
|
|
160
|
-
success(`已安装 Hook:${file}`);
|
|
184
|
+
try { fs.chmodSync(destPath, 0o755); } catch (_) {}
|
|
185
|
+
hookCount++;
|
|
161
186
|
}
|
|
162
187
|
}
|
|
188
|
+
fileLog(SYM.check, `${hookCount} 个 Hook 脚本`);
|
|
163
189
|
|
|
164
|
-
//
|
|
165
|
-
|
|
190
|
+
// Step 5: 斜杠命令
|
|
191
|
+
step(5, STEPS, '安装斜杠命令');
|
|
192
|
+
const cmds = copyFilesQuiet(
|
|
166
193
|
path.join(templateDir, '.claude', 'commands'),
|
|
167
194
|
path.join(targetDir, '.claude', 'commands'),
|
|
168
|
-
'.md'
|
|
169
|
-
'命令'
|
|
195
|
+
'.md'
|
|
170
196
|
);
|
|
197
|
+
fileLog(SYM.check, `${cmds.length} 个命令 — ${DIM}/phase /status /checkpoint /review${RESET}`);
|
|
171
198
|
|
|
172
|
-
//
|
|
199
|
+
// Step 6: settings.json
|
|
200
|
+
step(6, STEPS, '配置 Hooks');
|
|
173
201
|
const targetSettings = path.join(targetDir, '.claude', 'settings.json');
|
|
174
202
|
const sourceSettings = path.join(templateDir, '.claude', 'settings.json');
|
|
175
203
|
|
|
176
204
|
if (fs.existsSync(targetSettings)) {
|
|
177
|
-
warn('目标项目已有 .claude/settings.json');
|
|
178
|
-
|
|
179
205
|
try {
|
|
180
206
|
const existing = JSON.parse(fs.readFileSync(targetSettings, 'utf-8'));
|
|
181
207
|
const template = JSON.parse(fs.readFileSync(sourceSettings, 'utf-8'));
|
|
182
|
-
|
|
183
|
-
// 备份原文件
|
|
184
|
-
const backupName = `settings.json.bak.${timestamp()}`;
|
|
185
|
-
const backupPath = path.join(targetDir, '.claude', backupName);
|
|
186
|
-
fs.copyFileSync(targetSettings, backupPath);
|
|
187
|
-
warn(`已备份原 settings.json 为:${backupName}`);
|
|
188
|
-
|
|
189
|
-
// 智能合并
|
|
190
208
|
const merged = mergeSettings(existing, template);
|
|
191
209
|
fs.writeFileSync(targetSettings, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
192
|
-
|
|
210
|
+
fileLog(SYM.check, `settings.json ${DIM}(智能合并,保留原有配置)${RESET}`);
|
|
193
211
|
} catch (e) {
|
|
194
|
-
warn(`合并失败(${e.message}),将覆盖安装 settings.json`);
|
|
195
212
|
fs.copyFileSync(sourceSettings, targetSettings);
|
|
196
|
-
|
|
213
|
+
fileLog(SYM.warn, `settings.json ${DIM}(合并失败,已覆盖安装)${RESET}`);
|
|
197
214
|
}
|
|
198
215
|
} else {
|
|
199
216
|
fs.copyFileSync(sourceSettings, targetSettings);
|
|
200
|
-
|
|
217
|
+
fileLog(SYM.check, 'settings.json');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 完成
|
|
221
|
+
resultBanner('安装完成');
|
|
222
|
+
|
|
223
|
+
// 文件树
|
|
224
|
+
console.log(` ${DIM}已安装 ${rules.length + hookCount + cmds.length + 2} 个文件:${RESET}`);
|
|
225
|
+
blank();
|
|
226
|
+
console.log(` ${BOLD}${path.basename(targetDir)}/${RESET}`);
|
|
227
|
+
console.log(` ${SYM.tee} CLAUDE.md ${DIM}核心控制文件${RESET}`);
|
|
228
|
+
console.log(` ${SYM.corner} ${BOLD}.claude/${RESET}`);
|
|
229
|
+
console.log(` ${SYM.tee} settings.json ${DIM}Hooks 配置${RESET}`);
|
|
230
|
+
console.log(` ${SYM.tee} ${BOLD}rules/${RESET} ${DIM}${rules.length} 个规则 (自动加载)${RESET}`);
|
|
231
|
+
console.log(` ${SYM.tee} ${BOLD}hooks/${RESET} ${DIM}${hookCount} 个拦截脚本${RESET}`);
|
|
232
|
+
console.log(` ${SYM.tee} ${BOLD}commands/${RESET} ${DIM}${cmds.length} 个斜杠命令${RESET}`);
|
|
233
|
+
console.log(` ${SYM.corner} ${BOLD}reviews/${RESET} ${DIM}审查报告${RESET}`);
|
|
234
|
+
blank();
|
|
235
|
+
|
|
236
|
+
// 使用提示
|
|
237
|
+
const line = '─'.repeat(44);
|
|
238
|
+
console.log(` ${DIM}${line}${RESET}`);
|
|
239
|
+
console.log(` ${CYAN}使用方法${RESET}`);
|
|
240
|
+
console.log(` ${DIM}${line}${RESET}`);
|
|
241
|
+
blank();
|
|
242
|
+
console.log(` ${BOLD}1.${RESET} cd ${CYAN}${targetDir}${RESET}`);
|
|
243
|
+
console.log(` ${BOLD}2.${RESET} 启动 ${CYAN}claude${RESET} 即可自动加载 SDLC 规范`);
|
|
244
|
+
console.log(` ${BOLD}3.${RESET} 使用 ${CYAN}/phase${RESET} 查看当前阶段`);
|
|
245
|
+
console.log(` ${BOLD}4.${RESET} 使用 ${CYAN}/status${RESET} 查看项目状态`);
|
|
246
|
+
blank();
|
|
247
|
+
console.log(` ${DIM}卸载:npx claude-sdlc uninstall${RESET}`);
|
|
248
|
+
blank();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── 卸载 ──────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
function uninstall(targetDir) {
|
|
254
|
+
if (!fs.existsSync(targetDir)) {
|
|
255
|
+
console.error(`\n ${SYM.cross} 目标目录不存在:${targetDir}\n`);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
asciiLogo('uninstall');
|
|
260
|
+
|
|
261
|
+
const uninstallLine = '─'.repeat(44);
|
|
262
|
+
console.log(` ${RED}${BOLD}┌${uninstallLine}┐${RESET}`);
|
|
263
|
+
console.log(` ${RED}${BOLD}│${RESET} ${RED}${BOLD}${'卸 载'.padEnd(41)}${RESET}${RED}${BOLD}│${RESET}`);
|
|
264
|
+
console.log(` ${RED}${BOLD}└${uninstallLine}┘${RESET}`);
|
|
265
|
+
blank();
|
|
266
|
+
console.log(` ${SYM.arrow} 目标 ${BOLD}${targetDir}${RESET}`);
|
|
267
|
+
blank();
|
|
268
|
+
|
|
269
|
+
const removed = [];
|
|
270
|
+
|
|
271
|
+
// CLAUDE.md
|
|
272
|
+
const claudeMd = path.join(targetDir, 'CLAUDE.md');
|
|
273
|
+
if (fs.existsSync(claudeMd)) {
|
|
274
|
+
fs.unlinkSync(claudeMd);
|
|
275
|
+
removed.push('CLAUDE.md');
|
|
276
|
+
fileLog(SYM.check, `${DIM}删除${RESET} CLAUDE.md`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// .claude 子目录
|
|
280
|
+
const removeDirs = ['rules', 'hooks', 'commands', 'reviews'];
|
|
281
|
+
for (const dir of removeDirs) {
|
|
282
|
+
const dirPath = path.join(targetDir, '.claude', dir);
|
|
283
|
+
if (fs.existsSync(dirPath)) {
|
|
284
|
+
const count = fs.readdirSync(dirPath).length;
|
|
285
|
+
fs.rmSync(dirPath, { recursive: true });
|
|
286
|
+
removed.push(`.claude/${dir}/`);
|
|
287
|
+
fileLog(SYM.check, `${DIM}删除${RESET} .claude/${dir}/ ${DIM}(${count} 个文件)${RESET}`);
|
|
288
|
+
}
|
|
201
289
|
}
|
|
202
290
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
291
|
+
// settings.json
|
|
292
|
+
const settingsPath = path.join(targetDir, '.claude', 'settings.json');
|
|
293
|
+
if (fs.existsSync(settingsPath)) {
|
|
294
|
+
try {
|
|
295
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
296
|
+
if (settings.hooks) {
|
|
297
|
+
delete settings.hooks;
|
|
298
|
+
}
|
|
299
|
+
if (Object.keys(settings).length === 0) {
|
|
300
|
+
fs.unlinkSync(settingsPath);
|
|
301
|
+
removed.push('settings.json');
|
|
302
|
+
fileLog(SYM.check, `${DIM}删除${RESET} .claude/settings.json`);
|
|
303
|
+
} else {
|
|
304
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
305
|
+
removed.push('settings.json (hooks)');
|
|
306
|
+
fileLog(SYM.check, `${DIM}清理${RESET} settings.json ${DIM}(仅移除 hooks,保留其他配置)${RESET}`);
|
|
307
|
+
}
|
|
308
|
+
} catch (_) {
|
|
309
|
+
fs.unlinkSync(settingsPath);
|
|
310
|
+
removed.push('settings.json');
|
|
311
|
+
fileLog(SYM.check, `${DIM}删除${RESET} .claude/settings.json`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 清理空 .claude 目录
|
|
316
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
317
|
+
if (fs.existsSync(claudeDir)) {
|
|
318
|
+
const remaining = fs.readdirSync(claudeDir);
|
|
319
|
+
if (remaining.length === 0) {
|
|
320
|
+
fs.rmdirSync(claudeDir);
|
|
321
|
+
fileLog(SYM.check, `${DIM}删除${RESET} .claude/ ${DIM}(空目录)${RESET}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (removed.length > 0) {
|
|
326
|
+
resultBanner(`卸载完成 — 已清理 ${removed.length} 项`);
|
|
327
|
+
console.log(` ${DIM}重新安装:npx claude-sdlc${RESET}`);
|
|
328
|
+
} else {
|
|
329
|
+
blank();
|
|
330
|
+
console.log(` ${SYM.warn} 未找到 SDLC Enforcer 安装的文件`);
|
|
331
|
+
}
|
|
332
|
+
blank();
|
|
238
333
|
}
|
|
239
334
|
|
|
240
|
-
module.exports = { install, mergeSettings };
|
|
335
|
+
module.exports = { install, uninstall, mergeSettings };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-sdlc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "让 Claude Code 严格按 SDLC 规范开发 — 一条命令安装",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude-sdlc": "./bin/cli.js"
|
|
@@ -16,5 +16,6 @@
|
|
|
16
16
|
"development-workflow",
|
|
17
17
|
"ai-coding"
|
|
18
18
|
],
|
|
19
|
+
"author": "沐谦",
|
|
19
20
|
"license": "MIT"
|
|
20
21
|
}
|