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 CHANGED
@@ -1,9 +1,16 @@
1
- # claude-sdlc
1
+ <p align="center">
2
+ <img src="logo-banner.svg" alt="claude-sdlc" width="700"/>
3
+ </p>
2
4
 
3
- **让 Claude Code 严格按 SDLC 规范开发 — 一条命令安装,零配置开箱即用。**
5
+ <p align="center">
6
+ <strong>让 Claude Code 严格按 SDLC 规范开发 — 一条命令安装,零配置开箱即用。</strong>
7
+ </p>
4
8
 
5
- [![npm version](https://img.shields.io/npm/v/claude-sdlc.svg)](https://www.npmjs.com/package/claude-sdlc)
6
- [![license](https://img.shields.io/npm/l/claude-sdlc.svg)](https://opensource.org/licenses/MIT)
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
- 不会,自动备份为 `CLAUDE.md.bak.<时间戳>`。
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
- rm CLAUDE.md
159
- rm -rf .claude/rules/ .claude/hooks/ .claude/commands/
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
- const HELP = `
10
- SDLC Enforcer v${pkg.version}
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
- npx claude-sdlc # 安装到当前目录
21
- npx claude-sdlc ./myapp # 安装到指定目录
22
- `.trim();
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
- const targetArg = args[0] || '.';
37
- const targetDir = path.resolve(targetArg);
38
-
39
- install(targetDir);
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 RED = '\x1b[0;31m';
8
- const GREEN = '\x1b[0;32m';
9
- const YELLOW = '\x1b[1;33m';
10
- const BLUE = '\x1b[0;34m';
11
- const NC = '\x1b[0m';
12
-
13
- function info(msg) { console.log(`${BLUE}[INFO]${NC} ${msg}`); }
14
- function success(msg) { console.log(`${GREEN}[OK]${NC} ${msg}`); }
15
- function warn(msg) { console.log(`${YELLOW}[WARN]${NC} ${msg}`); }
16
- function error(msg) { console.error(`${RED}[ERROR]${NC} ${msg}`); }
17
-
18
- function timestamp() {
19
- const d = new Date();
20
- return d.getFullYear().toString()
21
- + String(d.getMonth() + 1).padStart(2, '0')
22
- + String(d.getDate()).padStart(2, '0')
23
- + String(d.getHours()).padStart(2, '0')
24
- + String(d.getMinutes()).padStart(2, '0')
25
- + String(d.getSeconds()).padStart(2, '0');
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
- * 提取 hook 组的唯一标识(用于去重)
45
- * 格式:{ matcher: "正则字符串", hooks: [{ type, command/prompt }] }
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(`目标目录不存在:${targetDir}`);
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(`目标路径不是目录:${targetDir}`);
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(`找不到 template 目录:${templateDir}`);
135
+ console.error(`\n ${SYM.cross} 找不到 template 目录:${templateDir}\n`);
106
136
  process.exit(1);
107
137
  }
108
138
 
109
- console.log('');
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
- // === Step 2: 复制 CLAUDE.md ===
129
- fs.copyFileSync(path.join(templateDir, 'CLAUDE.md'), claudeMdPath);
130
- success('已安装 CLAUDE.md');
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
- // === Step 3: 创建 .claude 目录结构 ===
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
- // === Step 4: 复制 rules 文件 ===
140
- copyFiles(
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
- // === Step 5: 复制 hooks 脚本 + 设置执行权限 ===
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
- fs.chmodSync(destPath, 0o755);
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
- // === Step 6: 复制 commands ===
165
- copyFiles(
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
- // === Step 7: 智能合并 settings.json ===
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
- success('已智能合并 settings.json(保留原有配置)');
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
- success('已安装 settings.json(覆盖)');
213
+ fileLog(SYM.warn, `settings.json ${DIM}(合并失败,已覆盖安装)${RESET}`);
197
214
  }
198
215
  } else {
199
216
  fs.copyFileSync(sourceSettings, targetSettings);
200
- success('已安装 settings.json');
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
- // === Step 8: 安装完成 ===
204
- console.log('');
205
- console.log('========================================');
206
- console.log(` ${GREEN}安装完成!${NC}`);
207
- console.log('========================================');
208
- console.log('');
209
- console.log('已安装的文件:');
210
- console.log(` ${targetDir}/`);
211
- console.log(' ├── CLAUDE.md (核心控制文件 ~100行,自动加载)');
212
- console.log(' └── .claude/');
213
- console.log(' ├── settings.json (Hooks 配置)');
214
- console.log(' ├── reviews/ (审查报告持久化)');
215
- console.log(' ├── rules/ (详细规则,自动加载)');
216
- console.log(' │ ├── 01-lifecycle-phases.md');
217
- console.log(' │ ├── 02-coding-standards.md');
218
- console.log(' │ ├── 03-testing-standards.md');
219
- console.log(' │ ├── 04-git-workflow.md');
220
- console.log(' │ ├── 05-anti-amnesia.md');
221
- console.log(' │ ├── 06-review-tools.md');
222
- console.log(' │ └── 07-parallel-agents.md');
223
- console.log(' ├── hooks/ (运行时拦截)');
224
- console.log(' │ ├── check-phase-write.sh');
225
- console.log(' │ └── check-phase-test.sh');
226
- console.log(' └── commands/ (斜杠命令)');
227
- console.log(' ├── phase.md (/phase)');
228
- console.log(' ├── checkpoint.md (/checkpoint)');
229
- console.log(' ├── status.md (/status)');
230
- console.log(' └── review.md (/review)');
231
- console.log('');
232
- console.log('使用方法:');
233
- console.log(` 1. cd ${targetDir}`);
234
- console.log(' 2. 启动 claude 即可自动加载 SDLC 规范');
235
- console.log(' 3. 使用 /phase 查看当前阶段');
236
- console.log(' 4. 使用 /status 查看项目状态');
237
- console.log('');
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.2",
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
  }