superpowers-zh 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/superpowers-zh.js +238 -16
  2. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, lstatSync, realpathSync } from 'fs';
3
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, lstatSync, realpathSync, rmSync } from 'fs';
4
4
  import { resolve, dirname, join } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
+ import { homedir } from 'os';
6
7
 
7
8
  // 手动递归复制:跨 Node 版本和操作系统行为一致
8
9
  // 不使用 cpSync —— 在 Windows + npx 缓存(含 junction)+ Node 16.7-18 下不稳定
@@ -14,6 +15,7 @@ function copyDirSync(src, dest) {
14
15
  mkdirSync(dest, { recursive: true });
15
16
  const entries = readdirSync(realSrc, { withFileTypes: true });
16
17
  for (const entry of entries) {
18
+ if (entry.name === '.DS_Store') continue;
17
19
  const srcPath = join(realSrc, entry.name);
18
20
  const destPath = join(dest, entry.name);
19
21
  let stat;
@@ -86,6 +88,15 @@ function scanSkillEntries(skillsDir) {
86
88
  return entries;
87
89
  }
88
90
 
91
+ // 段落哨兵:v1.2.1+ 安装时把追加内容包在两条 HTML 注释之间,
92
+ // 让卸载可以精确切除,无需依赖标题层级猜测段尾。
93
+ const SENTINEL_BEGIN = '<!-- superpowers-zh:begin (do not edit between these markers) -->';
94
+ const SENTINEL_END = '<!-- superpowers-zh:end -->';
95
+
96
+ function wrapWithSentinel(body) {
97
+ return `${SENTINEL_BEGIN}\n${body.replace(/\n+$/, '')}\n${SENTINEL_END}\n`;
98
+ }
99
+
89
100
  function generateTraeBootstrapRule(projectDir) {
90
101
  const rulesDir = resolve(projectDir, '.trae', 'rules');
91
102
  mkdirSync(rulesDir, { recursive: true });
@@ -190,13 +201,13 @@ ${skillList}
190
201
  if (existsSync(convPath)) {
191
202
  const existing = readFileSync(convPath, 'utf8');
192
203
  if (!existing.includes('superpowers-zh')) {
193
- writeFileSync(convPath, existing + '\n\n' + content, 'utf8');
204
+ writeFileSync(convPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
194
205
  console.log(` ✅ Aider: 追加 skills 引用 -> ${convPath}`);
195
206
  } else {
196
207
  console.log(` ✅ Aider: CONVENTIONS.md 已包含 superpowers-zh 引用`);
197
208
  }
198
209
  } else {
199
- writeFileSync(convPath, content, 'utf8');
210
+ writeFileSync(convPath, wrapWithSentinel(content), 'utf8');
200
211
  console.log(` ✅ Aider: bootstrap -> ${convPath}`);
201
212
  }
202
213
  }
@@ -232,13 +243,13 @@ ${skillList}
232
243
  if (existsSync(geminiPath)) {
233
244
  const existing = readFileSync(geminiPath, 'utf8');
234
245
  if (!existing.includes('superpowers-zh')) {
235
- writeFileSync(geminiPath, existing + '\n\n' + content, 'utf8');
246
+ writeFileSync(geminiPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
236
247
  console.log(` ✅ Gemini CLI: 追加 skills 引用 -> ${geminiPath}`);
237
248
  } else {
238
249
  console.log(` ✅ Gemini CLI: GEMINI.md 已包含 superpowers-zh 引用`);
239
250
  }
240
251
  } else {
241
- writeFileSync(geminiPath, content, 'utf8');
252
+ writeFileSync(geminiPath, wrapWithSentinel(content), 'utf8');
242
253
  console.log(` ✅ Gemini CLI: bootstrap -> ${geminiPath}`);
243
254
  }
244
255
  }
@@ -288,13 +299,13 @@ ${skillList}
288
299
  if (existsSync(hermesPath)) {
289
300
  const existing = readFileSync(hermesPath, 'utf8');
290
301
  if (!existing.includes('superpowers-zh')) {
291
- writeFileSync(hermesPath, existing + '\n\n' + content, 'utf8');
302
+ writeFileSync(hermesPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
292
303
  console.log(` ✅ Hermes Agent: 追加 skills 引用 -> ${hermesPath}`);
293
304
  } else {
294
305
  console.log(` ✅ Hermes Agent: HERMES.md 已包含 superpowers-zh 引用`);
295
306
  }
296
307
  } else {
297
- writeFileSync(hermesPath, content, 'utf8');
308
+ writeFileSync(hermesPath, wrapWithSentinel(content), 'utf8');
298
309
  console.log(` ✅ Hermes Agent: bootstrap -> ${hermesPath}`);
299
310
  }
300
311
  }
@@ -331,13 +342,13 @@ ${skillList}
331
342
  if (existsSync(mdPath)) {
332
343
  const existing = readFileSync(mdPath, 'utf8');
333
344
  if (!existing.includes('superpowers-zh')) {
334
- writeFileSync(mdPath, existing + '\n\n' + content, 'utf8');
345
+ writeFileSync(mdPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
335
346
  console.log(` ✅ Claude Code: 追加 skills 引用 -> ${mdPath}`);
336
347
  } else {
337
348
  console.log(` ✅ Claude Code: CLAUDE.md 已包含 superpowers-zh 引用`);
338
349
  }
339
350
  } else {
340
- writeFileSync(mdPath, content, 'utf8');
351
+ writeFileSync(mdPath, wrapWithSentinel(content), 'utf8');
341
352
  console.log(` ✅ Claude Code: bootstrap -> ${mdPath}`);
342
353
  }
343
354
  }
@@ -380,6 +391,8 @@ function showHelp() {
380
391
  用法:
381
392
  npx superpowers-zh 自动检测工具并安装
382
393
  npx superpowers-zh --tool cursor 指定工具安装(检测不到时使用)
394
+ npx superpowers-zh --uninstall 卸载当前目录下的 superpowers-zh
395
+ npx superpowers-zh --force 允许在用户主目录(~)安装(默认拒绝)
383
396
  npx superpowers-zh --help 显示帮助
384
397
  npx superpowers-zh --version 显示版本
385
398
 
@@ -392,6 +405,9 @@ function showHelp() {
392
405
  npx superpowers-zh --tool cursor
393
406
  npx superpowers-zh --tool trae
394
407
 
408
+ 误装到主目录可以这样清理:
409
+ cd ~ && npx superpowers-zh --uninstall
410
+
395
411
  项目:https://github.com/jnMetaCode/superpowers-zh
396
412
  `);
397
413
  }
@@ -401,8 +417,8 @@ function installForTarget(target) {
401
417
  const srcCount = countDirs(SKILLS_SRC);
402
418
  mkdirSync(dest, { recursive: true });
403
419
  copyDirSync(SKILLS_SRC, dest);
404
- const count = countDirs(dest);
405
- if (srcCount > 0 && count === 0) {
420
+ const totalAfter = countDirs(dest);
421
+ if (srcCount > 0 && totalAfter === 0) {
406
422
  throw new Error(
407
423
  `复制 skills 失败:源目录 ${SKILLS_SRC} 有 ${srcCount} 个 skill,但目标 ${dest} 为空。` +
408
424
  `\n 这通常是 npx 缓存目录权限或路径问题。请尝试:\n` +
@@ -411,7 +427,7 @@ function installForTarget(target) {
411
427
  ` 3. 或手动克隆复制: 见 https://github.com/jnMetaCode/superpowers-zh#方式二手动安装`
412
428
  );
413
429
  }
414
- console.log(` ✅ ${target.name}: ${count} 个 skills -> ${dest}`);
430
+ console.log(` ✅ ${target.name}: ${srcCount} 个 skills -> ${dest}`);
415
431
 
416
432
  if (target.name === 'Trae') {
417
433
  generateTraeBootstrapRule(PROJECT_DIR);
@@ -443,7 +459,187 @@ function installForTarget(target) {
443
459
  }
444
460
  }
445
461
 
446
- function install(forceToolName) {
462
+ function isHomeDir(p) {
463
+ const home = homedir();
464
+ if (!home) return false;
465
+ try {
466
+ return realpathSync(p) === realpathSync(home);
467
+ } catch { return resolve(p) === resolve(home); }
468
+ }
469
+
470
+ // 卸载支持:完整删除的 bootstrap 文件、需要清理段落的 bootstrap 文件
471
+ const BOOTSTRAP_DELETE = [
472
+ '.trae/rules/superpowers-zh.md',
473
+ '.antigravity/rules.md',
474
+ ];
475
+ const BOOTSTRAP_CLEAN_SECTION = [
476
+ 'CLAUDE.md',
477
+ 'GEMINI.md',
478
+ 'HERMES.md',
479
+ 'CONVENTIONS.md',
480
+ ];
481
+ const BOOTSTRAP_SECTION_MARKERS = [
482
+ '# Superpowers-ZH 中文增强版',
483
+ '# Superpowers-ZH 工作方法论',
484
+ ];
485
+
486
+ // v1.1.x 安装的旧 bootstrap 没有 sentinel,只能凭模板末尾固定句子识别段尾。
487
+ // 这些短语必须出现在 superpowers 段最后一行,且足够独特不易在用户内容里重合。
488
+ const FALLBACK_TAIL_HINTS = [
489
+ '你必须调用该 skill 检查。',
490
+ '严格遵循其流程。',
491
+ ];
492
+
493
+ function writeOrDelete(filePath, head, tail) {
494
+ const headTrim = head.replace(/\s+$/, '');
495
+ const tailTrim = tail.replace(/^\s+/, '');
496
+ let body = headTrim;
497
+ if (headTrim && tailTrim) body += '\n\n' + tailTrim;
498
+ else body += tailTrim;
499
+ body = body.replace(/\s+$/, '');
500
+ if (body.length === 0) {
501
+ rmSync(filePath);
502
+ } else {
503
+ writeFileSync(filePath, body + '\n', 'utf8');
504
+ }
505
+ }
506
+
507
+ function cleanBootstrapSection(filePath) {
508
+ if (!existsSync(filePath)) return false;
509
+ const content = readFileSync(filePath, 'utf8');
510
+
511
+ // 1. 哨兵模式(v1.2.1+)— 精确切除
512
+ const sBegin = content.indexOf(SENTINEL_BEGIN);
513
+ if (sBegin !== -1) {
514
+ const sEnd = content.indexOf(SENTINEL_END, sBegin + SENTINEL_BEGIN.length);
515
+ if (sEnd !== -1) {
516
+ writeOrDelete(filePath, content.slice(0, sBegin), content.slice(sEnd + SENTINEL_END.length));
517
+ return true;
518
+ }
519
+ }
520
+
521
+ // 2. 标题 marker(v1.1.x 安装的)— 找下一个 \n# 一级标题做段尾
522
+ let idx = -1;
523
+ for (const marker of BOOTSTRAP_SECTION_MARKERS) {
524
+ const i = content.indexOf(marker);
525
+ if (i !== -1 && (idx === -1 || i < idx)) idx = i;
526
+ }
527
+ if (idx === -1) return false;
528
+
529
+ let end = -1;
530
+ const nextHeading = content.indexOf('\n# ', idx + 1);
531
+ if (nextHeading !== -1) end = nextHeading + 1;
532
+
533
+ // 3. 一级标题找不到 — 用末尾固定短语做兜底
534
+ if (end === -1) {
535
+ for (const hint of FALLBACK_TAIL_HINTS) {
536
+ const i = content.lastIndexOf(hint);
537
+ if (i > idx) {
538
+ const nl = content.indexOf('\n', i + hint.length);
539
+ const after = nl !== -1 ? nl + 1 : content.length;
540
+ if (after > end) end = after;
541
+ }
542
+ }
543
+ }
544
+
545
+ // 4. 都找不到 — 数据安全,跳过 + 警告
546
+ if (end === -1) {
547
+ console.warn(` ⚠️ ${filePath}: 无法可靠识别 superpowers-zh 段尾,已跳过以避免数据丢失。`);
548
+ console.warn(` 请手动编辑此文件并删除以 "${BOOTSTRAP_SECTION_MARKERS[0]}" 开头的整段。`);
549
+ return false;
550
+ }
551
+
552
+ writeOrDelete(filePath, content.slice(0, idx), content.slice(end));
553
+ return true;
554
+ }
555
+
556
+ function uninstallForTarget(target, srcSkillNames) {
557
+ const dest = resolve(PROJECT_DIR, target.dir);
558
+ if (!existsSync(dest)) return 0;
559
+ let removed = 0;
560
+ for (const entry of readdirSync(dest, { withFileTypes: true })) {
561
+ if (entry.isDirectory() && srcSkillNames.has(entry.name)) {
562
+ rmSync(resolve(dest, entry.name), { recursive: true, force: true });
563
+ removed++;
564
+ }
565
+ }
566
+ // 如果目录已空(或仅剩 .DS_Store),顺手清掉,避免留下空骨架
567
+ try {
568
+ if (existsSync(dest)) {
569
+ const left = readdirSync(dest).filter(n => n !== '.DS_Store');
570
+ if (left.length === 0) rmSync(dest, { recursive: true, force: true });
571
+ }
572
+ } catch {}
573
+ return removed;
574
+ }
575
+
576
+ function uninstall() {
577
+ console.log(`\n superpowers-zh v${PKG.version} — 卸载\n`);
578
+ console.log(` 目标项目: ${PROJECT_DIR}\n`);
579
+
580
+ if (!existsSync(SKILLS_SRC)) {
581
+ console.error(' ❌ 错误:skills 源目录不存在,无法识别要卸载的 skill 名单。');
582
+ process.exit(1);
583
+ }
584
+
585
+ const srcSkillNames = new Set(
586
+ readdirSync(SKILLS_SRC, { withFileTypes: true })
587
+ .filter(e => e.isDirectory())
588
+ .map(e => e.name)
589
+ );
590
+
591
+ let totalSkills = 0;
592
+ for (const target of TARGETS) {
593
+ const removed = uninstallForTarget(target, srcSkillNames);
594
+ if (removed > 0) {
595
+ console.log(` ✅ ${target.name}: 移除 ${removed} 个 skills <- ${resolve(PROJECT_DIR, target.dir)}`);
596
+ totalSkills += removed;
597
+ }
598
+ }
599
+
600
+ // 顺带清理 .claude/agents 下我们装过的 agent(agents 源是平铺的文件,例如 code-reviewer.md)
601
+ const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents');
602
+ if (existsSync(AGENTS_SRC) && existsSync(agentsDest)) {
603
+ const srcAgentNames = new Set(readdirSync(AGENTS_SRC).filter(n => n !== '.DS_Store'));
604
+ let agentsRemoved = 0;
605
+ for (const entry of readdirSync(agentsDest)) {
606
+ if (srcAgentNames.has(entry)) {
607
+ rmSync(resolve(agentsDest, entry), { recursive: true, force: true });
608
+ agentsRemoved++;
609
+ }
610
+ }
611
+ if (agentsRemoved > 0) console.log(` ✅ Claude Code agents: 移除 ${agentsRemoved} 个 -> ${agentsDest}`);
612
+ try {
613
+ const left = readdirSync(agentsDest).filter(n => n !== '.DS_Store');
614
+ if (left.length === 0) rmSync(agentsDest, { recursive: true, force: true });
615
+ } catch {}
616
+ }
617
+
618
+ let bootstrapsRemoved = 0;
619
+ for (const rel of BOOTSTRAP_DELETE) {
620
+ const full = resolve(PROJECT_DIR, rel);
621
+ if (existsSync(full)) {
622
+ rmSync(full);
623
+ console.log(` ✅ 删除 bootstrap: ${full}`);
624
+ bootstrapsRemoved++;
625
+ }
626
+ }
627
+ for (const rel of BOOTSTRAP_CLEAN_SECTION) {
628
+ const full = resolve(PROJECT_DIR, rel);
629
+ if (cleanBootstrapSection(full)) {
630
+ console.log(` ✅ 清理 bootstrap: ${full}`);
631
+ bootstrapsRemoved++;
632
+ }
633
+ }
634
+
635
+ if (totalSkills === 0 && bootstrapsRemoved === 0) {
636
+ console.log(' ⚠️ 未在当前目录找到 superpowers-zh 安装痕迹。');
637
+ } else {
638
+ console.log(`\n 卸载完成。共移除 ${totalSkills} 个 skill 目录、${bootstrapsRemoved} 个 bootstrap 文件。\n`);
639
+ }
640
+ }
641
+
642
+ function install(forceToolName, force) {
447
643
  try {
448
644
  console.log(`\n superpowers-zh v${PKG.version} — AI 编程超能力中文版\n`);
449
645
 
@@ -452,6 +648,27 @@ function install(forceToolName) {
452
648
  process.exit(1);
453
649
  }
454
650
 
651
+ if (!force && isHomeDir(PROJECT_DIR)) {
652
+ console.error(
653
+ ` ⚠️ 当前目录是用户主目录: ${PROJECT_DIR}
654
+
655
+ superpowers-zh 应该装到具体项目目录,而不是 ~/。
656
+ 在主目录安装会把 skills 和 bootstrap 文件(CLAUDE.md / HERMES.md 等)
657
+ 写入你的 home,污染所有项目。
658
+
659
+ 请先 cd 到项目目录:
660
+ cd /path/to/your/project
661
+ npx superpowers-zh
662
+
663
+ 如果你确实要在主目录安装(不推荐),加 --force:
664
+ npx superpowers-zh --force
665
+
666
+ 如果你已经在主目录误装过,可以用 --uninstall 清理:
667
+ npx superpowers-zh --uninstall
668
+ `);
669
+ process.exit(1);
670
+ }
671
+
455
672
  console.log(` 源: ${countDirs(SKILLS_SRC)} 个 skills`);
456
673
  console.log(` 目标项目: ${PROJECT_DIR}\n`);
457
674
 
@@ -512,11 +729,16 @@ const args = process.argv.slice(2);
512
729
  const helpIdx = args.findIndex(a => a === '--help' || a === '-h');
513
730
  const versionIdx = args.findIndex(a => a === '--version' || a === '-v');
514
731
  const toolIdx = args.findIndex(a => a === '--tool' || a === '-t');
732
+ const uninstallIdx = args.findIndex(a => a === '--uninstall' || a === '-u');
733
+ const forceIdx = args.findIndex(a => a === '--force' || a === '-f');
734
+ const force = forceIdx !== -1;
515
735
 
516
736
  if (helpIdx !== -1) {
517
737
  showHelp();
518
738
  } else if (versionIdx !== -1) {
519
739
  console.log(PKG.version);
740
+ } else if (uninstallIdx !== -1) {
741
+ uninstall();
520
742
  } else if (toolIdx !== -1) {
521
743
  const toolArg = args[toolIdx + 1];
522
744
  if (!toolArg) {
@@ -530,11 +752,11 @@ if (helpIdx !== -1) {
530
752
  console.error(` 支持的工具: ${Object.keys(TOOL_ALIASES).join(', ')}\n`);
531
753
  process.exit(1);
532
754
  }
533
- install(toolName);
534
- } else if (args.length > 0 && args[0].startsWith('-')) {
755
+ install(toolName, force);
756
+ } else if (args.length > 0 && args[0].startsWith('-') && forceIdx === -1) {
535
757
  console.warn(` 未知参数: ${args[0]}\n`);
536
758
  showHelp();
537
759
  process.exit(1);
538
760
  } else {
539
- install();
761
+ install(undefined, force);
540
762
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superpowers-zh",
3
- "version": "1.1.9",
3
+ "version": "1.2.1",
4
4
  "engines": {
5
5
  "node": ">=20.0.0"
6
6
  },