ethan-skill 1.11.0 → 1.12.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.
Files changed (66) hide show
  1. package/dist/cli/index.js +1019 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/mcp/server.d.ts.map +1 -1
  4. package/dist/mcp/server.js +206 -1
  5. package/dist/mcp/server.js.map +1 -1
  6. package/dist/skills/27-tech-debt.d.ts +3 -0
  7. package/dist/skills/27-tech-debt.d.ts.map +1 -0
  8. package/dist/skills/27-tech-debt.js +149 -0
  9. package/dist/skills/27-tech-debt.js.map +1 -0
  10. package/dist/skills/28-api-mock.d.ts +3 -0
  11. package/dist/skills/28-api-mock.d.ts.map +1 -0
  12. package/dist/skills/28-api-mock.js +272 -0
  13. package/dist/skills/28-api-mock.js.map +1 -0
  14. package/dist/skills/29-data-migration.d.ts +3 -0
  15. package/dist/skills/29-data-migration.d.ts.map +1 -0
  16. package/dist/skills/29-data-migration.js +331 -0
  17. package/dist/skills/29-data-migration.js.map +1 -0
  18. package/dist/skills/30-llm-feature.d.ts +3 -0
  19. package/dist/skills/30-llm-feature.d.ts.map +1 -0
  20. package/dist/skills/30-llm-feature.js +328 -0
  21. package/dist/skills/30-llm-feature.js.map +1 -0
  22. package/dist/skills/31-threat-model.d.ts +3 -0
  23. package/dist/skills/31-threat-model.d.ts.map +1 -0
  24. package/dist/skills/31-threat-model.js +240 -0
  25. package/dist/skills/31-threat-model.js.map +1 -0
  26. package/dist/skills/32-green-code.d.ts +3 -0
  27. package/dist/skills/32-green-code.d.ts.map +1 -0
  28. package/dist/skills/32-green-code.js +346 -0
  29. package/dist/skills/32-green-code.js.map +1 -0
  30. package/dist/skills/33-service-catalog.d.ts +3 -0
  31. package/dist/skills/33-service-catalog.d.ts.map +1 -0
  32. package/dist/skills/33-service-catalog.js +334 -0
  33. package/dist/skills/33-service-catalog.js.map +1 -0
  34. package/dist/skills/34-mobile-review.d.ts +3 -0
  35. package/dist/skills/34-mobile-review.d.ts.map +1 -0
  36. package/dist/skills/34-mobile-review.js +390 -0
  37. package/dist/skills/34-mobile-review.js.map +1 -0
  38. package/dist/skills/35-data-pipeline.d.ts +3 -0
  39. package/dist/skills/35-data-pipeline.d.ts.map +1 -0
  40. package/dist/skills/35-data-pipeline.js +392 -0
  41. package/dist/skills/35-data-pipeline.js.map +1 -0
  42. package/dist/skills/36-ml-experiment.d.ts +3 -0
  43. package/dist/skills/36-ml-experiment.d.ts.map +1 -0
  44. package/dist/skills/36-ml-experiment.js +415 -0
  45. package/dist/skills/36-ml-experiment.js.map +1 -0
  46. package/dist/skills/index.d.ts +10 -0
  47. package/dist/skills/index.d.ts.map +1 -1
  48. package/dist/skills/index.js +41 -1
  49. package/dist/skills/index.js.map +1 -1
  50. package/dist/skills/pipeline.d.ts.map +1 -1
  51. package/dist/skills/pipeline.js +35 -0
  52. package/dist/skills/pipeline.js.map +1 -1
  53. package/dist/skills/skills.test.js +3 -3
  54. package/dist/skills/skills.test.js.map +1 -1
  55. package/package.json +1 -1
  56. package/rules/claude-code/CLAUDE.md +2963 -3
  57. package/rules/cline/.clinerules +2805 -2
  58. package/rules/codebuddy/CODEBUDDY.md +2913 -2
  59. package/rules/continue/.continuerules +2805 -2
  60. package/rules/copilot/copilot-instructions.md +2883 -2
  61. package/rules/cursor/.cursorrules +2952 -2
  62. package/rules/cursor/smart-flow.mdc +2952 -2
  63. package/rules/jetbrains/smart-flow.md +2883 -2
  64. package/rules/lingma/smart-flow.md +2904 -3
  65. package/rules/windsurf/.windsurf/rules/smart-flow.md +2884 -3
  66. package/rules/zed/smart-flow.rules +2794 -1
package/dist/cli/index.js CHANGED
@@ -513,6 +513,96 @@ const WORKFLOW_SLASH_COMMANDS = [
513
513
  `输出:问题列表(按严重程度分级)+ 合格/不合格摘要\n\n` +
514
514
  `CLI 用法:\`ethan spec validate [capability]\``,
515
515
  },
516
+ // ── 分析工具 ─────────────────────────────────────────────────
517
+ {
518
+ id: 'dora',
519
+ name: 'DORA 指标分析',
520
+ category: '分析工具',
521
+ description: '基于 git 历史统计 DORA 四键指标(部署频率/变更前置时间/故障率/恢复时间)',
522
+ prompt: `# Ethan — DORA 四键指标\n\n` +
523
+ `请基于 git 历史分析团队的 DORA 四键指标:\n\n` +
524
+ `1. **Deployment Frequency(部署频率)**:多久发布一次\n` +
525
+ `2. **Lead Time for Changes(变更前置时间)**:代码从提交到上线耗时\n` +
526
+ `3. **Change Failure Rate(变更失败率)**:hotfix/revert 占比\n` +
527
+ `4. **Time to Restore(恢复时间)**:故障修复平均耗时\n\n` +
528
+ `分级:Elite / High / Medium / Low\n\n` +
529
+ `CLI 用法:\`ethan dora --since 30\``,
530
+ },
531
+ {
532
+ id: 'diff',
533
+ name: '变更风险分析',
534
+ category: '分析工具',
535
+ description: '读取 git diff,分析变更影响面与风险等级',
536
+ prompt: `# Ethan — 变更风险分析\n\n` +
537
+ `请分析当前 git diff 的变更风险:\n\n` +
538
+ `- **影响面**:涉及的模块/服务/接口\n` +
539
+ `- **风险等级**:🔴 高 / 🟡 中 / 🟢 低(附理由)\n` +
540
+ `- **高风险点**:需要重点测试的代码路径\n` +
541
+ `- **建议**:回滚方案、监控指标、测试 Checklist\n\n` +
542
+ `CLI 用法:\`ethan diff [base] [head]\``,
543
+ },
544
+ {
545
+ id: 'deps',
546
+ name: '依赖健康检查',
547
+ category: '分析工具',
548
+ description: '扫描 package.json,生成依赖过期、漏洞和升级建议报告',
549
+ prompt: `# Ethan — 依赖健康检查\n\n` +
550
+ `请分析项目依赖的健康状态:\n\n` +
551
+ `1. **过期依赖**:主版本落后 2+ 个版本的包\n` +
552
+ `2. **安全漏洞**:High/Critical CVE(npm audit 结果)\n` +
553
+ `3. **升级建议**:按优先级排列,附 breaking change 风险\n` +
554
+ `4. **废弃 API**:使用了 deprecated API 的直接依赖\n\n` +
555
+ `输出:依赖健康评分 + 行动计划\n\n` +
556
+ `CLI 用法:\`ethan deps [--fix]\``,
557
+ },
558
+ {
559
+ id: 'mermaid',
560
+ name: 'Mermaid 图表生成',
561
+ category: '分析工具',
562
+ description: '根据描述生成 Mermaid 流程图/时序图/ER 图/架构图',
563
+ prompt: `# Ethan — Mermaid 图表生成\n\n` +
564
+ `请根据我的描述生成 Mermaid 图表代码:\n\n` +
565
+ `**支持类型**:\n` +
566
+ `- \`flowchart\`:业务流程图、决策树\n` +
567
+ `- \`sequence\`:API 调用时序图\n` +
568
+ `- \`er\`:数据库 ER 图\n` +
569
+ `- \`architecture\`:系统架构图\n` +
570
+ `- \`gantt\`:项目甘特图\n\n` +
571
+ `请描述要绘制的内容,并指定图表类型。\n\n` +
572
+ `CLI 用法:\`ethan mermaid --type sequence --desc "描述"\``,
573
+ },
574
+ {
575
+ id: 'migrate',
576
+ name: '框架迁移助手',
577
+ category: '分析工具',
578
+ description: '生成从旧框架/工具迁移到新版本的分步迁移方案',
579
+ prompt: `# Ethan — 框架迁移助手\n\n` +
580
+ `请生成框架/工具迁移方案,包含:\n\n` +
581
+ `1. **迁移前提**:前置条件与兼容性检查\n` +
582
+ `2. **迁移步骤**:分阶段、可回滚的操作序列\n` +
583
+ `3. **代码变更模式**:Before/After 对照示例\n` +
584
+ `4. **常见陷阱**:迁移过程中的已知问题\n` +
585
+ `5. **验证方案**:每步完成后的检验方法\n\n` +
586
+ `请告知迁移来源和目标(如 CRA → Vite,Webpack 4 → 5,Jest → Vitest)\n\n` +
587
+ `CLI 用法:\`ethan migrate --from cra --to vite\``,
588
+ },
589
+ {
590
+ id: 'postmortem',
591
+ name: '故障复盘报告',
592
+ category: '分析工具',
593
+ description: '根据事故信息生成结构化的 Postmortem 报告',
594
+ prompt: `# Ethan — 故障复盘(Postmortem)\n\n` +
595
+ `请生成标准化的故障复盘报告,格式遵循 Google SRE 最佳实践:\n\n` +
596
+ `## 报告结构\n` +
597
+ `- **事故摘要**:时间线、影响范围、严重程度\n` +
598
+ `- **根因分析**(5 Why)\n` +
599
+ `- **贡献因素**(技术、流程、人员)\n` +
600
+ `- **时间线**:详细事件序列\n` +
601
+ `- **后续行动**:P0/P1/P2 改进项(附责任人和截止日期)\n` +
602
+ `- **经验教训**\n\n` +
603
+ `请描述事故经过,我来整理成规范报告。\n\n` +
604
+ `CLI 用法:\`ethan postmortem --incident INC-001\``,
605
+ },
516
606
  ];
517
607
  program
518
608
  .command('slash')
@@ -591,6 +681,12 @@ program
591
681
  'spec-proposal': 'ethan spec proposal --no-copy $ARGUMENTS',
592
682
  'spec-review': 'ethan spec review --no-copy',
593
683
  'spec-validate': 'ethan spec validate --no-copy $ARGUMENTS',
684
+ 'dora': 'ethan dora --no-copy $ARGUMENTS',
685
+ 'diff': 'ethan diff --no-copy $ARGUMENTS',
686
+ 'deps': 'ethan deps --no-copy',
687
+ 'postmortem': 'ethan postmortem --no-copy $ARGUMENTS',
688
+ 'mermaid': 'ethan mermaid --no-copy $ARGUMENTS',
689
+ 'migrate': 'ethan migrate --no-copy $ARGUMENTS',
594
690
  };
595
691
  // 需要参数的命令:无参调用时显示用法说明(printf 内 \n 为换行)
596
692
  const PIPELINE_LIST = 'dev-workflow: 需求理解->拆解->设计->实现\\n' +
@@ -599,7 +695,10 @@ program
599
695
  '- quality-workflow: 代码审查->故障排查\\n' +
600
696
  '- reporting: 进度跟踪->任务报告->周报\\n' +
601
697
  '- incident-response: 故障排查->技术调研->任务报告\\n' +
602
- '- spec-workflow: Spec提案->方案设计->任务拆解->实现->意图审查';
698
+ '- spec-workflow: Spec提案->方案设计->任务拆解->实现->意图审查\\n' +
699
+ '- bugfix-workflow: 故障排查->变更提案->实现->单元测试->意图审查\\n' +
700
+ '- security-audit-workflow: 威胁建模->安全审查->变更提案->实现->意图审查\\n' +
701
+ '- open-source-release: 技术调研->代码审查->单元测试->部署上线';
603
702
  const SLASH_USAGE_FALLBACKS = {
604
703
  'auto': '## Ethan Auto-Pilot — 缺少参数\\n\\n' +
605
704
  '用法: /ethan-auto <pipeline> [-c 任务描述]\\n\\n' +
@@ -615,6 +714,18 @@ program
615
714
  'test-case': '## Ethan 测试用例 — 缺少参数\\n\\n' +
616
715
  '用法: /ethan-test-case <文件路径> [--framework vitest|jest]\\n\\n' +
617
716
  '示例: /ethan-test-case src/utils.ts --framework vitest',
717
+ 'dora': '## Ethan DORA — 用法\\n\\n' +
718
+ '/ethan-dora [--since 30]\\n\\n' +
719
+ '统计最近 N 天的 DORA 四键指标(部署频率/前置时间/恢复时间/变更失败率)',
720
+ 'diff': '## Ethan Diff — 用法\\n\\n' +
721
+ '/ethan-diff [base] [head]\\n\\n' +
722
+ '分析 git diff 变更风险,输出变更摘要与潜在影响',
723
+ 'mermaid': '## Ethan Mermaid — 用法\\n\\n' +
724
+ '/ethan-mermaid --type <sequence|flowchart|er|gantt> --desc "图表描述"\\n\\n' +
725
+ '生成 Mermaid 图表提示词',
726
+ 'migrate': '## Ethan Migrate — 用法\\n\\n' +
727
+ '/ethan-migrate --from <来源> --to <目标>\\n\\n' +
728
+ '示例: /ethan-migrate --from cra --to vite',
618
729
  };
619
730
  for (const cmd of WORKFLOW_SLASH_COMMANDS) {
620
731
  const invocation = CLI_INVOCATIONS[cmd.id];
@@ -3790,5 +3901,912 @@ specCmd
3790
3901
  console.log(prompt);
3791
3902
  }
3792
3903
  });
3904
+ // ─── diff ─────────────────────────────────────────────────────────────────────
3905
+ program
3906
+ .command('diff [base] [head]')
3907
+ .description('读取 git diff,生成变更风险分析提示词')
3908
+ .option('--no-copy', '不复制到剪贴板', false)
3909
+ .action((base, head, options) => {
3910
+ if (!(0, utils_1.isGitRepo)(process.cwd())) {
3911
+ console.error('\n❌ 当前目录不是 git 仓库\n');
3912
+ process.exit(1);
3913
+ }
3914
+ const ref = base && head ? `${base}..${head}` : (base ?? 'HEAD');
3915
+ const diffArgs = base && head ? [base, head] : base ? [base] : [];
3916
+ const result = (0, child_process_1.spawnSync)('git', ['diff', ...diffArgs, '--stat', '--no-color'], {
3917
+ cwd: process.cwd(), encoding: 'utf-8',
3918
+ });
3919
+ const stat = result.stdout ?? '';
3920
+ const fullDiff = (0, child_process_1.spawnSync)('git', ['diff', ...diffArgs, '--no-color'], {
3921
+ cwd: process.cwd(), encoding: 'utf-8',
3922
+ }).stdout ?? '';
3923
+ const truncated = (0, utils_1.truncateDiff)(fullDiff, 5000);
3924
+ const prompt = `# 变更风险分析\n\n` +
3925
+ `**范围**: \`git diff ${ref}\`\n\n` +
3926
+ `## 变更统计\n\n\`\`\`\n${stat || '(无统计输出)'}\n\`\`\`\n\n` +
3927
+ `## Diff 内容\n\n\`\`\`diff\n${truncated || '(无变更)'}\n\`\`\`\n\n` +
3928
+ `---\n\n` +
3929
+ `请分析上述代码变更,输出:\n` +
3930
+ `1. **变更摘要**:一句话概括本次改动目的\n` +
3931
+ `2. **风险评估**(高/中/低):潜在 Bug、破坏性变更、性能影响\n` +
3932
+ `3. **关注点**:最需要审查的文件/逻辑\n` +
3933
+ `4. **建议**:测试覆盖缺口、文档更新建议`;
3934
+ if (options.copy !== false) {
3935
+ copyToClipboard(prompt);
3936
+ console.log('\n✅ 变更风险分析提示词已复制到剪贴板\n');
3937
+ }
3938
+ else {
3939
+ console.log(prompt);
3940
+ }
3941
+ });
3942
+ // ─── deps ─────────────────────────────────────────────────────────────────────
3943
+ program
3944
+ .command('deps')
3945
+ .description('读取 package.json + npm outdated 生成依赖健康报告提示词')
3946
+ .option('--fix', '同时输出升级命令', false)
3947
+ .option('--no-copy', '不复制到剪贴板', false)
3948
+ .action((options) => {
3949
+ const pkgPath = path.join(process.cwd(), 'package.json');
3950
+ if (!fs.existsSync(pkgPath)) {
3951
+ console.error('\n❌ 未找到 package.json\n');
3952
+ process.exit(1);
3953
+ }
3954
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
3955
+ const outdatedResult = (0, child_process_1.spawnSync)('npm', ['outdated', '--json'], {
3956
+ cwd: process.cwd(), encoding: 'utf-8',
3957
+ });
3958
+ const outdated = outdatedResult.stdout ? JSON.parse(outdatedResult.stdout) : {};
3959
+ const auditResult = (0, child_process_1.spawnSync)('npm', ['audit', '--json'], {
3960
+ cwd: process.cwd(), encoding: 'utf-8',
3961
+ });
3962
+ let auditSummary = '(npm audit 失败)';
3963
+ try {
3964
+ const auditData = JSON.parse(auditResult.stdout ?? '{}');
3965
+ const vuln = auditData.metadata?.vulnerabilities ?? {};
3966
+ auditSummary = `critical: ${vuln.critical ?? 0}, high: ${vuln.high ?? 0}, moderate: ${vuln.moderate ?? 0}, low: ${vuln.low ?? 0}`;
3967
+ }
3968
+ catch { /* ignore */ }
3969
+ const outdatedEntries = Object.entries(outdated).slice(0, 20)
3970
+ .map(([name, info]) => {
3971
+ const d = info;
3972
+ return `- ${name}: ${d.current} → ${d.latest} (wanted: ${d.wanted})`;
3973
+ }).join('\n');
3974
+ const prompt = `# 依赖健康报告\n\n` +
3975
+ `**项目**: ${pkg.name ?? 'unknown'} v${pkg.version ?? '?'}\n\n` +
3976
+ `## 安全漏洞摘要\n\n${auditSummary}\n\n` +
3977
+ `## 过时依赖(前20项)\n\n${outdatedEntries || '(无过时依赖)'}\n\n` +
3978
+ `## 依赖数量\n\n` +
3979
+ `- dependencies: ${Object.keys(pkg.dependencies ?? {}).length}\n` +
3980
+ `- devDependencies: ${Object.keys(pkg.devDependencies ?? {}).length}\n\n` +
3981
+ (options.fix ? `## 建议升级命令\n\nnpm update\nnpm audit fix\n\n` : '') +
3982
+ `---\n\n请分析依赖健康状况,输出优先级排序的升级建议和安全漏洞修复方案。`;
3983
+ if (options.copy !== false) {
3984
+ copyToClipboard(prompt);
3985
+ console.log('\n✅ 依赖健康报告提示词已复制到剪贴板\n');
3986
+ }
3987
+ else {
3988
+ console.log(prompt);
3989
+ }
3990
+ });
3991
+ // ─── dora ─────────────────────────────────────────────────────────────────────
3992
+ program
3993
+ .command('dora')
3994
+ .description('统计 git 历史,计算 DORA 四键指标')
3995
+ .option('--since <days>', '统计最近 N 天', '30')
3996
+ .option('--no-copy', '不复制到剪贴板', false)
3997
+ .action((options) => {
3998
+ if (!(0, utils_1.isGitRepo)(process.cwd())) {
3999
+ console.error('\n❌ 当前目录不是 git 仓库\n');
4000
+ process.exit(1);
4001
+ }
4002
+ const days = parseInt(options.since, 10) || 30;
4003
+ const since = `${days}.days.ago`;
4004
+ // Deploy Frequency: count merges to main
4005
+ const merges = (0, child_process_1.spawnSync)('git', ['log', '--merges', '--oneline', `--since=${since}`], {
4006
+ cwd: process.cwd(), encoding: 'utf-8',
4007
+ }).stdout ?? '';
4008
+ const mergeCount = merges.trim() ? merges.trim().split('\n').length : 0;
4009
+ const deployFreq = (mergeCount / days).toFixed(2);
4010
+ // Tags as releases
4011
+ const tags = (0, child_process_1.spawnSync)('git', ['log', '--tags', '--oneline', `--since=${since}`, '--simplify-by-decoration'], {
4012
+ cwd: process.cwd(), encoding: 'utf-8',
4013
+ }).stdout ?? '';
4014
+ const tagCount = tags.trim() ? tags.trim().split('\n').length : 0;
4015
+ // Commit count for lead time proxy
4016
+ const commits = (0, child_process_1.spawnSync)('git', ['log', '--oneline', `--since=${since}`], {
4017
+ cwd: process.cwd(), encoding: 'utf-8',
4018
+ }).stdout ?? '';
4019
+ const commitCount = commits.trim() ? commits.trim().split('\n').length : 0;
4020
+ const freqLevel = mergeCount / days >= 1 ? 'Elite' : mergeCount / days >= 1 / 7 ? 'High' : mergeCount / days >= 1 / 30 ? 'Medium' : 'Low';
4021
+ const report = `# DORA 四键指标报告\n\n` +
4022
+ `**统计范围**: 最近 ${days} 天\n\n` +
4023
+ `| 指标 | 数值 | 等级 |\n` +
4024
+ `|------|------|------|\n` +
4025
+ `| 部署频率 (Deploy Frequency) | ${deployFreq} 次/天(共 ${mergeCount} 次合并) | **${freqLevel}** |\n` +
4026
+ `| 版本发布 (Tags/Releases) | ${tagCount} 次 | — |\n` +
4027
+ `| 提交总数 (Commit Count) | ${commitCount} 次 | — |\n\n` +
4028
+ `> 等级:Elite(每天多次)> High(每天1次)> Medium(每周1次)> Low(每月1次)\n\n` +
4029
+ `---\n\n` +
4030
+ `请根据以上 DORA 数据,分析工程效能现状,并给出:\n` +
4031
+ `1. **当前等级评估** 及改进空间\n` +
4032
+ `2. **瓶颈分析**:部署频率低的可能原因\n` +
4033
+ `3. **改进建议**:3-5条可操作的效能提升措施`;
4034
+ if (options.copy !== false) {
4035
+ copyToClipboard(report);
4036
+ console.log(`\n✅ DORA 指标报告已复制到剪贴板(最近 ${days} 天)\n`);
4037
+ }
4038
+ else {
4039
+ console.log(report);
4040
+ }
4041
+ });
4042
+ // ─── pr-analytics ─────────────────────────────────────────────────────────────
4043
+ program
4044
+ .command('pr-analytics')
4045
+ .description('分析 PR 合并历史:大小分布、热点文件 Top 10')
4046
+ .option('--days <n>', '统计最近 N 天', '30')
4047
+ .option('--no-copy', '不复制到剪贴板', false)
4048
+ .action((options) => {
4049
+ if (!(0, utils_1.isGitRepo)(process.cwd())) {
4050
+ console.error('\n❌ 当前目录不是 git 仓库\n');
4051
+ process.exit(1);
4052
+ }
4053
+ const days = parseInt(options.days, 10) || 30;
4054
+ const merges = (0, child_process_1.spawnSync)('git', ['log', '--merges', `--since=${days}.days.ago`, '--format=%H %s'], {
4055
+ cwd: process.cwd(), encoding: 'utf-8',
4056
+ }).stdout ?? '';
4057
+ const mergeLines = merges.trim() ? merges.trim().split('\n') : [];
4058
+ // Count changed files across merges (sample up to 10)
4059
+ const fileChanges = {};
4060
+ for (const line of mergeLines.slice(0, 10)) {
4061
+ const hash = line.split(' ')[0];
4062
+ const files = (0, child_process_1.spawnSync)('git', ['diff-tree', '--no-commit-id', '-r', '--name-only', hash], {
4063
+ cwd: process.cwd(), encoding: 'utf-8',
4064
+ }).stdout ?? '';
4065
+ for (const f of files.trim().split('\n').filter(Boolean)) {
4066
+ fileChanges[f] = (fileChanges[f] ?? 0) + 1;
4067
+ }
4068
+ }
4069
+ const hotFiles = Object.entries(fileChanges)
4070
+ .sort((a, b) => b[1] - a[1])
4071
+ .slice(0, 10)
4072
+ .map(([f, n]) => `- ${f} (${n} 次)`)
4073
+ .join('\n');
4074
+ const report = `# PR 分析报告\n\n` +
4075
+ `**统计范围**: 最近 ${days} 天 | **合并 PR 数**: ${mergeLines.length}\n\n` +
4076
+ `## 热点文件 Top 10(变更最频繁)\n\n${hotFiles || '(无数据)'}\n\n` +
4077
+ `---\n\n请分析 PR 模式,给出:\n` +
4078
+ `1. PR 规模是否合理(是否有超大 PR)\n` +
4079
+ `2. 热点文件是否存在过度耦合或技术债\n` +
4080
+ `3. 改进建议`;
4081
+ if (options.copy !== false) {
4082
+ copyToClipboard(report);
4083
+ console.log('\n✅ PR 分析报告已复制到剪贴板\n');
4084
+ }
4085
+ else {
4086
+ console.log(report);
4087
+ }
4088
+ });
4089
+ // ─── adr ──────────────────────────────────────────────────────────────────────
4090
+ program
4091
+ .command('adr <action> [title]')
4092
+ .description('管理 ADR(架构决策记录):new | list | show <n>')
4093
+ .action((action, title) => {
4094
+ const adrDir = path.join(process.cwd(), 'docs', 'adr');
4095
+ if (action === 'list') {
4096
+ if (!fs.existsSync(adrDir)) {
4097
+ console.log('(无 ADR 记录,运行 ethan adr new "标题" 创建)');
4098
+ return;
4099
+ }
4100
+ const files = fs.readdirSync(adrDir).filter((f) => f.endsWith('.md')).sort();
4101
+ if (files.length === 0) {
4102
+ console.log('(无 ADR 记录)');
4103
+ return;
4104
+ }
4105
+ console.log('\n📋 ADR 列表:\n');
4106
+ files.forEach((f) => console.log(` ${f}`));
4107
+ console.log('');
4108
+ }
4109
+ else if (action === 'new') {
4110
+ if (!title) {
4111
+ console.error('\n❌ 用法:ethan adr new "决策标题"\n');
4112
+ process.exit(1);
4113
+ }
4114
+ fs.mkdirSync(adrDir, { recursive: true });
4115
+ const existing = fs.existsSync(adrDir) ? fs.readdirSync(adrDir).filter((f) => /^\d{4}/.test(f)) : [];
4116
+ const num = String(existing.length + 1).padStart(4, '0');
4117
+ const slug = title.toLowerCase().replace(/[\s/]+/g, '-').replace(/[^\w-]/g, '').slice(0, 50);
4118
+ const filename = `${num}-${slug}.md`;
4119
+ const content = `# ${num}. ${title}\n\n` +
4120
+ `**日期**: ${new Date().toISOString().split('T')[0]}\n` +
4121
+ `**状态**: 提议中 (Proposed)\n\n` +
4122
+ `## 背景\n\n[描述需要做出此决策的背景和问题]\n\n` +
4123
+ `## 决策\n\n[描述做出的决策]\n\n` +
4124
+ `## 后果\n\n[描述此决策带来的影响(正面和负面)]\n`;
4125
+ fs.writeFileSync(path.join(adrDir, filename), content, 'utf-8');
4126
+ console.log(`\n✅ ADR 已创建:docs/adr/${filename}\n`);
4127
+ }
4128
+ else if (action === 'show') {
4129
+ if (!title) {
4130
+ console.error('\n❌ 用法:ethan adr show <序号或文件名>\n');
4131
+ process.exit(1);
4132
+ }
4133
+ if (!fs.existsSync(adrDir)) {
4134
+ console.error('\n❌ docs/adr/ 目录不存在\n');
4135
+ process.exit(1);
4136
+ }
4137
+ const files = fs.readdirSync(adrDir).filter((f) => f.startsWith(title.padStart(4, '0')));
4138
+ if (files.length === 0) {
4139
+ console.error(`\n❌ 未找到 ADR #${title}\n`);
4140
+ process.exit(1);
4141
+ }
4142
+ console.log(fs.readFileSync(path.join(adrDir, files[0]), 'utf-8'));
4143
+ }
4144
+ else {
4145
+ console.error(`\n❌ 未知操作: ${action}(支持: new | list | show)\n`);
4146
+ process.exit(1);
4147
+ }
4148
+ });
4149
+ // ─── mermaid ──────────────────────────────────────────────────────────────────
4150
+ program
4151
+ .command('mermaid')
4152
+ .description('生成 Mermaid 图表提示词(flowchart/sequence/er/architecture/gantt)')
4153
+ .option('--type <type>', '图表类型:flowchart | sequence | er | architecture | gantt', 'flowchart')
4154
+ .option('--desc <desc>', '图表描述', '')
4155
+ .option('--no-copy', '不复制到剪贴板', false)
4156
+ .action((options) => {
4157
+ const typeGuide = {
4158
+ flowchart: '流程图(节点 + 判断分支 + 连线)',
4159
+ sequence: '时序图(参与者 + 消息序列)',
4160
+ er: 'ER 图(实体 + 属性 + 关系)',
4161
+ architecture: '架构图(服务/组件 + 依赖关系)',
4162
+ gantt: '甘特图(任务 + 时间轴)',
4163
+ };
4164
+ const guide = typeGuide[options.type] ?? typeGuide.flowchart;
4165
+ const desc = options.desc || '请根据上下文自动推断内容';
4166
+ const prompt = `# Mermaid 图表生成\n\n` +
4167
+ `**类型**: ${options.type}(${guide})\n` +
4168
+ `**描述**: ${desc}\n\n` +
4169
+ `请生成一个标准的 Mermaid ${options.type} 图表,要求:\n` +
4170
+ `1. 语法严格符合 Mermaid 规范,可直接粘贴到 Markdown 中渲染\n` +
4171
+ `2. 节点/参与者命名清晰,使用中文标签\n` +
4172
+ `3. 覆盖主要流程/关系,避免过度简化\n` +
4173
+ `4. 输出格式:\`\`\`mermaid\\n...\\n\`\`\`\n\n` +
4174
+ `同时附上简短说明,解释图表的核心逻辑。`;
4175
+ if (options.copy !== false) {
4176
+ copyToClipboard(prompt);
4177
+ console.log(`\n✅ Mermaid ${options.type} 图表提示词已复制到剪贴板\n`);
4178
+ }
4179
+ else {
4180
+ console.log(prompt);
4181
+ }
4182
+ });
4183
+ // ─── i18n ─────────────────────────────────────────────────────────────────────
4184
+ program
4185
+ .command('i18n [file]')
4186
+ .description('扫描硬编码字符串,生成 i18n key 提取提案')
4187
+ .option('--no-copy', '不复制到剪贴板', false)
4188
+ .action((file, options) => {
4189
+ let fileContent = '';
4190
+ let targetDesc = '项目代码';
4191
+ if (file) {
4192
+ const fp = resolveFilePath(file);
4193
+ if (!fs.existsSync(fp)) {
4194
+ console.error(`\n❌ 文件不存在: ${file}\n`);
4195
+ process.exit(1);
4196
+ }
4197
+ fileContent = fs.readFileSync(fp, 'utf-8');
4198
+ targetDesc = file;
4199
+ }
4200
+ const prompt = `# i18n 国际化提取\n\n` +
4201
+ `**目标**: ${targetDesc}\n\n` +
4202
+ (fileContent ? `## 代码内容\n\n\`\`\`\n${fileContent.slice(0, 3000)}\n\`\`\`\n\n` : '') +
4203
+ `## 任务\n\n` +
4204
+ `1. **扫描硬编码字符串**:识别所有中文/英文界面文本(按钮、标题、提示、错误消息等)\n` +
4205
+ `2. **生成 key 方案**:为每个字符串建议语义化的 i18n key(如 \`common.btn.submit\`)\n` +
4206
+ `3. **输出 locale 文件**:生成 zh-CN.json 和 en-US.json 的初始内容\n` +
4207
+ `4. **代码替换示例**:展示如何将硬编码替换为 i18n 调用(兼容 react-i18next / vue-i18n)\n` +
4208
+ `5. **遗漏风险**:标注可能遗漏的动态字符串或复数形式`;
4209
+ if (options.copy !== false) {
4210
+ copyToClipboard(prompt);
4211
+ console.log('\n✅ i18n 提取提案提示词已复制到剪贴板\n');
4212
+ }
4213
+ else {
4214
+ console.log(prompt);
4215
+ }
4216
+ });
4217
+ // ─── onboard ──────────────────────────────────────────────────────────────────
4218
+ program
4219
+ .command('onboard')
4220
+ .description('生成新成员上手文档提示词')
4221
+ .option('--lang <lang>', '编程语言/技术栈(可选)', '')
4222
+ .option('--no-copy', '不复制到剪贴板', false)
4223
+ .action(async (options) => {
4224
+ const snapshot = await (async () => {
4225
+ try {
4226
+ const { buildProjectSnapshot } = await Promise.resolve().then(() => __importStar(require('../context/builder')));
4227
+ return buildProjectSnapshot(process.cwd());
4228
+ }
4229
+ catch {
4230
+ return null;
4231
+ }
4232
+ })();
4233
+ const pkgPath = path.join(process.cwd(), 'package.json');
4234
+ const pkg = fs.existsSync(pkgPath) ? JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) : {};
4235
+ const prompt = `# 新成员上手文档生成\n\n` +
4236
+ `**项目**: ${pkg.name ?? path.basename(process.cwd())} v${pkg.version ?? '?'}\n` +
4237
+ (options.lang ? `**技术栈**: ${options.lang}\n` : '') +
4238
+ (snapshot ? `**项目快照**: ${JSON.stringify(snapshot).slice(0, 500)}\n` : '') +
4239
+ `\n## 请生成以下上手文档:\n\n` +
4240
+ `1. **项目概述**:用 3 句话解释这个项目是什么、解决什么问题\n` +
4241
+ `2. **快速启动**:本地开发环境配置步骤(含前置条件)\n` +
4242
+ `3. **目录结构**:核心目录说明(只解释重要的)\n` +
4243
+ `4. **开发工作流**:分支策略、提交规范、PR 流程\n` +
4244
+ `5. **关键概念**:新人必须理解的 3-5 个领域概念\n` +
4245
+ `6. **常见问题**:FAQ(本地启动失败、测试怎么跑等)\n` +
4246
+ `7. **联系方式**:谁负责什么模块(留空供补充)`;
4247
+ if (options.copy !== false) {
4248
+ copyToClipboard(prompt);
4249
+ console.log('\n✅ 新成员上手文档提示词已复制到剪贴板\n');
4250
+ }
4251
+ else {
4252
+ console.log(prompt);
4253
+ }
4254
+ });
4255
+ // ─── test-coverage ────────────────────────────────────────────────────────────
4256
+ program
4257
+ .command('test-coverage')
4258
+ .description('读取覆盖率报告,生成覆盖率优化提示词')
4259
+ .option('--threshold <n>', '覆盖率目标(默认 80)', '80')
4260
+ .option('--no-copy', '不复制到剪贴板', false)
4261
+ .action((options) => {
4262
+ const summaryPath = path.join(process.cwd(), 'coverage', 'coverage-summary.json');
4263
+ let summaryText = '(未找到 coverage/coverage-summary.json,请先运行 npm run test:coverage)';
4264
+ if (fs.existsSync(summaryPath)) {
4265
+ try {
4266
+ const data = JSON.parse(fs.readFileSync(summaryPath, 'utf-8'));
4267
+ const total = data.total ?? {};
4268
+ summaryText =
4269
+ `语句: ${total.statements?.pct ?? '?'}% | ` +
4270
+ `分支: ${total.branches?.pct ?? '?'}% | ` +
4271
+ `函数: ${total.functions?.pct ?? '?'}% | ` +
4272
+ `行: ${total.lines?.pct ?? '?'}%`;
4273
+ }
4274
+ catch { /* ignore */ }
4275
+ }
4276
+ const threshold = parseInt(options.threshold, 10) || 80;
4277
+ const prompt = `# 测试覆盖率优化\n\n` +
4278
+ `**当前覆盖率**: ${summaryText}\n` +
4279
+ `**目标覆盖率**: ${threshold}%\n\n` +
4280
+ `## 任务\n\n` +
4281
+ `1. **覆盖率差距分析**:当前覆盖率 vs 目标,差多少\n` +
4282
+ `2. **优先补测区域**:哪些文件/函数覆盖率最低,业务风险最高\n` +
4283
+ `3. **测试补充方案**:为低覆盖区域设计具体的测试用例(AAA 格式)\n` +
4284
+ `4. **Mock 策略**:依赖外部服务的单元如何 Mock\n` +
4285
+ `5. **CI 集成**:如何在 CI 中设置覆盖率门禁`;
4286
+ if (options.copy !== false) {
4287
+ copyToClipboard(prompt);
4288
+ console.log('\n✅ 测试覆盖率优化提示词已复制到剪贴板\n');
4289
+ }
4290
+ else {
4291
+ console.log(prompt);
4292
+ }
4293
+ });
4294
+ // ─── migrate ──────────────────────────────────────────────────────────────────
4295
+ program
4296
+ .command('migrate')
4297
+ .description('生成框架/工具迁移助手提示词')
4298
+ .option('--from <source>', '迁移来源(如 cra, webpack4, jest)', '')
4299
+ .option('--to <target>', '迁移目标(如 vite, webpack5, vitest)', '')
4300
+ .option('--no-copy', '不复制到剪贴板', false)
4301
+ .action((options) => {
4302
+ const from = options.from || '(请指定 --from)';
4303
+ const to = options.to || '(请指定 --to)';
4304
+ const prompt = `# 框架迁移方案:${from} → ${to}\n\n` +
4305
+ `## 任务\n\n` +
4306
+ `请生成从 **${from}** 迁移到 **${to}** 的完整方案:\n\n` +
4307
+ `1. **迁移前提**:前置条件、兼容性检查、破坏性变更清单\n` +
4308
+ `2. **迁移步骤**(分阶段,每步可回滚):\n` +
4309
+ ` - 安装新依赖\n - 配置文件变更\n - 代码变更模式(Before/After 对照)\n` +
4310
+ `3. **常见陷阱**:迁移过程中的已知问题和解决方案\n` +
4311
+ `4. **验证方案**:每步完成后的检验命令/检查项\n` +
4312
+ `5. **回滚方案**:遇到阻塞时如何安全回退\n\n` +
4313
+ `> 请基于最新版本的官方迁移指南,重点关注 Breaking Changes。`;
4314
+ if (options.copy !== false) {
4315
+ copyToClipboard(prompt);
4316
+ console.log(`\n✅ 迁移方案提示词已复制(${from} → ${to})\n`);
4317
+ }
4318
+ else {
4319
+ console.log(prompt);
4320
+ }
4321
+ });
4322
+ // ─── postmortem ───────────────────────────────────────────────────────────────
4323
+ program
4324
+ .command('postmortem')
4325
+ .description('生成结构化故障复盘(Postmortem)提示词')
4326
+ .option('--incident <id>', '事故 ID(如 INC-001)', '')
4327
+ .option('--no-copy', '不复制到剪贴板', false)
4328
+ .action((options) => {
4329
+ const incidentId = options.incident || `INC-${Date.now().toString().slice(-6)}`;
4330
+ const recentCommits = (0, utils_1.isGitRepo)(process.cwd())
4331
+ ? ((0, child_process_1.spawnSync)('git', ['log', '--oneline', '-10'], { cwd: process.cwd(), encoding: 'utf-8' }).stdout ?? '')
4332
+ : '';
4333
+ const prompt = `# 故障复盘(Postmortem)— ${incidentId}\n\n` +
4334
+ (recentCommits ? `## 近期提交记录(参考)\n\n\`\`\`\n${recentCommits}\`\`\`\n\n` : '') +
4335
+ `## 请生成标准化的 Postmortem 报告(Google SRE 格式):\n\n` +
4336
+ `### 1. 事故摘要\n- 开始/结束时间\n- 影响范围(用户/功能/地区)\n- 严重程度(P0-P3)\n- 一句话根因\n\n` +
4337
+ `### 2. 时间线\n(请描述事故经过,我来整理成时间轴格式)\n\n` +
4338
+ `### 3. 根因分析(5 Why)\n追问 5 次 "为什么" 直到找到系统性原因\n\n` +
4339
+ `### 4. 贡献因素\n- 技术因素\n- 流程因素\n- 人员因素\n\n` +
4340
+ `### 5. 后续行动(附责任人 + 截止日期)\n- P0(24h内)\n- P1(1周内)\n- P2(1月内)\n\n` +
4341
+ `### 6. 经验教训\n\n> 请描述事故经过,我来生成完整复盘报告。`;
4342
+ if (options.copy !== false) {
4343
+ copyToClipboard(prompt);
4344
+ console.log(`\n✅ Postmortem 提示词已复制(${incidentId})\n`);
4345
+ }
4346
+ else {
4347
+ console.log(prompt);
4348
+ }
4349
+ });
4350
+ // ─── decision-log ─────────────────────────────────────────────────────────────
4351
+ program
4352
+ .command('decision-log <action> [query]')
4353
+ .description('管理技术决策记录(.ethan/decisions/):new | list | search <关键词>')
4354
+ .action((action, query) => {
4355
+ const decDir = path.join(process.cwd(), '.ethan', 'decisions');
4356
+ if (action === 'list') {
4357
+ if (!fs.existsSync(decDir)) {
4358
+ console.log('(无决策记录,运行 ethan decision-log new 创建)');
4359
+ return;
4360
+ }
4361
+ const files = fs.readdirSync(decDir).filter((f) => f.endsWith('.md')).sort().reverse();
4362
+ if (files.length === 0) {
4363
+ console.log('(无决策记录)');
4364
+ return;
4365
+ }
4366
+ console.log(`\n📋 决策记录(共 ${files.length} 条):\n`);
4367
+ files.forEach((f) => console.log(` ${f}`));
4368
+ console.log('');
4369
+ }
4370
+ else if (action === 'new') {
4371
+ fs.mkdirSync(decDir, { recursive: true });
4372
+ const date = new Date().toISOString().split('T')[0];
4373
+ const filename = `${date}-decision.md`;
4374
+ const fp = path.join(decDir, filename);
4375
+ const content = `# 技术决策记录\n\n` +
4376
+ `**日期**: ${date}\n` +
4377
+ `**状态**: 已决策\n\n` +
4378
+ `## 背景\n\n[描述需要做出此决策的背景]\n\n` +
4379
+ `## 决策\n\n[描述选择了什么方案]\n\n` +
4380
+ `## 理由\n\n[为什么选这个方案而非其他]\n\n` +
4381
+ `## 后果\n\n[此决策带来的影响]\n`;
4382
+ fs.writeFileSync(fp, content, 'utf-8');
4383
+ console.log(`\n✅ 决策记录已创建:.ethan/decisions/${filename}\n`);
4384
+ }
4385
+ else if (action === 'search') {
4386
+ if (!query) {
4387
+ console.error('\n❌ 用法:ethan decision-log search <关键词>\n');
4388
+ process.exit(1);
4389
+ }
4390
+ if (!fs.existsSync(decDir)) {
4391
+ console.log('(无决策记录)');
4392
+ return;
4393
+ }
4394
+ const files = fs.readdirSync(decDir).filter((f) => f.endsWith('.md'));
4395
+ const matched = files.filter((f) => {
4396
+ const content = fs.readFileSync(path.join(decDir, f), 'utf-8');
4397
+ return content.includes(query) || f.includes(query);
4398
+ });
4399
+ if (matched.length === 0) {
4400
+ console.log(`(未找到包含 "${query}" 的决策记录)`);
4401
+ return;
4402
+ }
4403
+ console.log(`\n🔍 匹配 "${query}" 的决策记录:\n`);
4404
+ matched.forEach((f) => console.log(` ${f}`));
4405
+ console.log('');
4406
+ }
4407
+ else {
4408
+ console.error(`\n❌ 未知操作: ${action}(支持: new | list | search)\n`);
4409
+ process.exit(1);
4410
+ }
4411
+ });
4412
+ // ─── knowledge ────────────────────────────────────────────────────────────────
4413
+ program
4414
+ .command('knowledge <action> [entry]')
4415
+ .description('管理团队知识库(.ethan/team-knowledge/):add | list | search <关键词>')
4416
+ .action((action, entry) => {
4417
+ const kbDir = path.join(process.cwd(), '.ethan', 'team-knowledge');
4418
+ if (action === 'list') {
4419
+ if (!fs.existsSync(kbDir)) {
4420
+ console.log('(无知识条目,运行 ethan knowledge add "内容" 添加)');
4421
+ return;
4422
+ }
4423
+ const files = fs.readdirSync(kbDir).filter((f) => f.endsWith('.md')).sort().reverse();
4424
+ if (files.length === 0) {
4425
+ console.log('(无知识条目)');
4426
+ return;
4427
+ }
4428
+ console.log(`\n📚 知识库(共 ${files.length} 条):\n`);
4429
+ files.slice(0, 20).forEach((f) => console.log(` ${f}`));
4430
+ if (files.length > 20)
4431
+ console.log(` ... 还有 ${files.length - 20} 条`);
4432
+ console.log('');
4433
+ }
4434
+ else if (action === 'add') {
4435
+ if (!entry) {
4436
+ console.error('\n❌ 用法:ethan knowledge add "知识内容"\n');
4437
+ process.exit(1);
4438
+ }
4439
+ fs.mkdirSync(kbDir, { recursive: true });
4440
+ const date = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
4441
+ const filename = `${date}-knowledge.md`;
4442
+ const content = `# 知识条目\n\n**日期**: ${date}\n\n${entry}\n`;
4443
+ fs.writeFileSync(path.join(kbDir, filename), content, 'utf-8');
4444
+ console.log(`\n✅ 知识条目已保存:.ethan/team-knowledge/${filename}\n`);
4445
+ }
4446
+ else if (action === 'search') {
4447
+ if (!entry) {
4448
+ console.error('\n❌ 用法:ethan knowledge search <关键词>\n');
4449
+ process.exit(1);
4450
+ }
4451
+ if (!fs.existsSync(kbDir)) {
4452
+ console.log('(无知识条目)');
4453
+ return;
4454
+ }
4455
+ const files = fs.readdirSync(kbDir).filter((f) => f.endsWith('.md'));
4456
+ const matched = [];
4457
+ for (const f of files) {
4458
+ const content = fs.readFileSync(path.join(kbDir, f), 'utf-8');
4459
+ if (content.includes(entry) || f.includes(entry)) {
4460
+ const idx = content.indexOf(entry);
4461
+ const excerpt = content.slice(Math.max(0, idx - 30), idx + 80).replace(/\n/g, ' ');
4462
+ matched.push({ file: f, excerpt });
4463
+ }
4464
+ }
4465
+ if (matched.length === 0) {
4466
+ console.log(`(未找到 "${entry}")`);
4467
+ return;
4468
+ }
4469
+ console.log(`\n🔍 匹配 "${entry}" 的知识条目:\n`);
4470
+ matched.slice(0, 10).forEach(({ file, excerpt }) => {
4471
+ console.log(` 📄 ${file}`);
4472
+ console.log(` ...${excerpt}...`);
4473
+ });
4474
+ console.log('');
4475
+ }
4476
+ else {
4477
+ console.error(`\n❌ 未知操作: ${action}(支持: add | list | search)\n`);
4478
+ process.exit(1);
4479
+ }
4480
+ });
4481
+ // ─── oss ──────────────────────────────────────────────────────────────────────
4482
+ program
4483
+ .command('oss <action>')
4484
+ .description('开源项目工具套件:triage | release-notes | contributor-guide | health-score')
4485
+ .option('--no-copy', '不复制到剪贴板', false)
4486
+ .action((action, options) => {
4487
+ const pkgPath = path.join(process.cwd(), 'package.json');
4488
+ const pkg = fs.existsSync(pkgPath) ? JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) : {};
4489
+ const projName = pkg.name ?? path.basename(process.cwd());
4490
+ const prompts = {
4491
+ triage: `# 开源 Issue Triage — ${projName}\n\n` +
4492
+ `请帮我处理开源项目 Issue 分流:\n` +
4493
+ `1. 对 Issue 进行分类:Bug / Feature Request / Question / Documentation\n` +
4494
+ `2. 评估优先级(P0-P3)并说明理由\n` +
4495
+ `3. 起草回复模板(友好、专业、附后续步骤)\n` +
4496
+ `4. 建议 Label 标签\n\n请粘贴需要处理的 Issue 内容:`,
4497
+ 'release-notes': `# Release Notes 生成 — ${projName}\n\n` +
4498
+ ((0, utils_1.isGitRepo)(process.cwd())
4499
+ ? `## 最近提交\n\n\`\`\`\n${((0, child_process_1.spawnSync)('git', ['log', '--oneline', '-20'], { cwd: process.cwd(), encoding: 'utf-8' }).stdout ?? '').trim()}\n\`\`\`\n\n`
4500
+ : '') +
4501
+ `请基于以上提交记录,生成规范的 Release Notes:\n` +
4502
+ `1. 按类型分组:🚀 New Features / 🐛 Bug Fixes / ⚠️ Breaking Changes / 📚 Documentation\n` +
4503
+ `2. 语言简洁,面向用户而非开发者\n` +
4504
+ `3. 标注 Breaking Changes 的迁移指南\n` +
4505
+ `4. 输出 Markdown 格式`,
4506
+ 'contributor-guide': `# Contributor Guide 生成 — ${projName}\n\n` +
4507
+ `请生成 CONTRIBUTING.md 内容:\n` +
4508
+ `1. 开发环境配置(fork → clone → install → dev)\n` +
4509
+ `2. 代码规范(命名 / 注释 / 测试要求)\n` +
4510
+ `3. PR 提交流程(分支命名 / commit 规范 / PR 描述模板)\n` +
4511
+ `4. Issue 报告规范\n` +
4512
+ `5. 社区行为准则参考`,
4513
+ 'health-score': `# 开源项目健康评分 — ${projName}\n\n` +
4514
+ `请从以下维度评估开源项目健康度(各维度 0-10 分):\n\n` +
4515
+ `| 维度 | 评分 | 说明 |\n|------|------|------|\n` +
4516
+ `| 文档完整性 | | README/CONTRIBUTING/CHANGELOG |\n` +
4517
+ `| 测试覆盖率 | | 是否有 CI + 覆盖率报告 |\n` +
4518
+ `| Issue 响应时间 | | 最近 issue 多久被回复 |\n` +
4519
+ `| 版本发布规律 | | 是否定期发版 |\n` +
4520
+ `| 社区活跃度 | | Star 趋势 / PR 数量 |\n` +
4521
+ `| 依赖安全 | | 是否有高危 CVE |\n\n` +
4522
+ `请基于仓库实际情况给出评分和改进建议。`,
4523
+ };
4524
+ const prompt = prompts[action];
4525
+ if (!prompt) {
4526
+ console.error(`\n❌ 未知操作: ${action}(支持: triage | release-notes | contributor-guide | health-score)\n`);
4527
+ process.exit(1);
4528
+ }
4529
+ if (options.copy !== false) {
4530
+ copyToClipboard(prompt);
4531
+ console.log(`\n✅ OSS ${action} 提示词已复制到剪贴板\n`);
4532
+ }
4533
+ else {
4534
+ console.log(prompt);
4535
+ }
4536
+ });
4537
+ // ─── prompt-lib ───────────────────────────────────────────────────────────────
4538
+ program
4539
+ .command('prompt-lib <action> [name]')
4540
+ .description('管理 Prompt 资产库(.ethan/prompts/):add | list | search | show <名称>')
4541
+ .option('--content <content>', 'Prompt 内容(add 时使用)', '')
4542
+ .action((action, name, options) => {
4543
+ const promptDir = path.join(process.cwd(), '.ethan', 'prompts');
4544
+ if (action === 'list') {
4545
+ if (!fs.existsSync(promptDir)) {
4546
+ console.log('(无 Prompt,运行 ethan prompt-lib add <名称> --content "内容" 添加)');
4547
+ return;
4548
+ }
4549
+ const files = fs.readdirSync(promptDir).filter((f) => f.endsWith('.md')).sort();
4550
+ if (files.length === 0) {
4551
+ console.log('(无 Prompt)');
4552
+ return;
4553
+ }
4554
+ console.log(`\n📝 Prompt 库(共 ${files.length} 条):\n`);
4555
+ files.forEach((f) => console.log(` ${f.replace('.md', '')}`));
4556
+ console.log('');
4557
+ }
4558
+ else if (action === 'add') {
4559
+ if (!name) {
4560
+ console.error('\n❌ 用法:ethan prompt-lib add <名称> --content "Prompt内容"\n');
4561
+ process.exit(1);
4562
+ }
4563
+ if (!options.content) {
4564
+ console.error('\n❌ 缺少 --content 参数\n');
4565
+ process.exit(1);
4566
+ }
4567
+ fs.mkdirSync(promptDir, { recursive: true });
4568
+ const filename = `${name.replace(/[/\\:*?"<>|]/g, '-')}.md`;
4569
+ const content = `# ${name}\n\n**创建时间**: ${new Date().toISOString().split('T')[0]}\n\n## Prompt\n\n${options.content}\n`;
4570
+ fs.writeFileSync(path.join(promptDir, filename), content, 'utf-8');
4571
+ console.log(`\n✅ Prompt 已保存:.ethan/prompts/${filename}\n`);
4572
+ }
4573
+ else if (action === 'show') {
4574
+ if (!name) {
4575
+ console.error('\n❌ 用法:ethan prompt-lib show <名称>\n');
4576
+ process.exit(1);
4577
+ }
4578
+ if (!fs.existsSync(promptDir)) {
4579
+ console.error('\n❌ Prompt 库为空\n');
4580
+ process.exit(1);
4581
+ }
4582
+ const filename = `${name}.md`;
4583
+ const fp = path.join(promptDir, filename);
4584
+ if (!fs.existsSync(fp)) {
4585
+ console.error(`\n❌ 未找到 Prompt: ${name}\n`);
4586
+ process.exit(1);
4587
+ }
4588
+ console.log(fs.readFileSync(fp, 'utf-8'));
4589
+ }
4590
+ else if (action === 'search') {
4591
+ if (!name) {
4592
+ console.error('\n❌ 用法:ethan prompt-lib search <关键词>\n');
4593
+ process.exit(1);
4594
+ }
4595
+ if (!fs.existsSync(promptDir)) {
4596
+ console.log('(Prompt 库为空)');
4597
+ return;
4598
+ }
4599
+ const files = fs.readdirSync(promptDir).filter((f) => f.endsWith('.md'));
4600
+ const matched = files.filter((f) => {
4601
+ const c = fs.readFileSync(path.join(promptDir, f), 'utf-8');
4602
+ return c.includes(name) || f.includes(name);
4603
+ });
4604
+ if (matched.length === 0) {
4605
+ console.log(`(未找到 "${name}")`);
4606
+ return;
4607
+ }
4608
+ console.log(`\n🔍 匹配的 Prompt:\n`);
4609
+ matched.forEach((f) => console.log(` ${f.replace('.md', '')}`));
4610
+ console.log('');
4611
+ }
4612
+ else {
4613
+ console.error(`\n❌ 未知操作: ${action}(支持: add | list | search | show)\n`);
4614
+ process.exit(1);
4615
+ }
4616
+ });
4617
+ // ─── scaffold ─────────────────────────────────────────────────────────────────
4618
+ program
4619
+ .command('scaffold')
4620
+ .description('黄金路径脚手架:生成项目模板结构提示词')
4621
+ .option('--template <name>', '模板名称:react-ts | node-api | cli-tool | monorepo | library', '')
4622
+ .option('--list', '列出所有可用模板', false)
4623
+ .option('--no-copy', '不复制到剪贴板', false)
4624
+ .action((options) => {
4625
+ const templates = {
4626
+ 'react-ts': `React + TypeScript + Vite + Vitest + Tailwind CSS\n` +
4627
+ `目录:src/components/ src/hooks/ src/pages/ src/store/ src/types/ src/utils/`,
4628
+ 'node-api': `Node.js + TypeScript + Express/Fastify + Prisma + Jest\n` +
4629
+ `目录:src/routes/ src/services/ src/models/ src/middleware/ src/utils/ tests/`,
4630
+ 'cli-tool': `Node.js CLI + TypeScript + Commander + Vitest\n` +
4631
+ `目录:src/commands/ src/utils/ src/types/ tests/ dist/`,
4632
+ 'monorepo': `pnpm Monorepo + Turborepo + TypeScript\n` +
4633
+ `目录:packages/ui/ packages/core/ packages/cli/ apps/web/ apps/docs/`,
4634
+ 'library': `TypeScript Library + Vitest + tsup + Changesets\n` +
4635
+ `目录:src/ tests/ examples/ dist/`,
4636
+ };
4637
+ if (options.list) {
4638
+ console.log('\n📦 可用脚手架模板:\n');
4639
+ Object.entries(templates).forEach(([name, desc]) => {
4640
+ console.log(` ${name.padEnd(12)} ${desc.split('\n')[0]}`);
4641
+ });
4642
+ console.log('\n用法:ethan scaffold --template react-ts\n');
4643
+ return;
4644
+ }
4645
+ const tmpl = options.template || 'react-ts';
4646
+ const desc = templates[tmpl] ?? `自定义模板: ${tmpl}`;
4647
+ const prompt = `# 项目脚手架:${tmpl}\n\n` +
4648
+ `**模板**: ${desc}\n\n` +
4649
+ `## 请生成完整的项目初始结构:\n\n` +
4650
+ `1. **目录结构**(tree 格式,含说明注释)\n` +
4651
+ `2. **核心配置文件**:package.json / tsconfig.json / vite.config.ts(或对应工具)\n` +
4652
+ `3. **代码规范配置**:ESLint + Prettier + .editorconfig\n` +
4653
+ `4. **Git 配置**:.gitignore / .husky / commitlint\n` +
4654
+ `5. **CI 配置**:GitHub Actions(lint + test + build)\n` +
4655
+ `6. **入口文件**:最小可运行的 index.ts\n` +
4656
+ `7. **README 模板**:含徽章、快速启动、贡献指南链接\n\n` +
4657
+ `> 基于 2025 年最佳实践,使用最新稳定版本的依赖。`;
4658
+ if (options.copy !== false) {
4659
+ copyToClipboard(prompt);
4660
+ console.log(`\n✅ 脚手架提示词已复制(${tmpl})\n`);
4661
+ }
4662
+ else {
4663
+ console.log(prompt);
4664
+ }
4665
+ });
4666
+ // ─── benchmark ────────────────────────────────────────────────────────────────
4667
+ program
4668
+ .command('benchmark')
4669
+ .description('生成 Skill 质量基准报告')
4670
+ .option('--skill <id>', '指定 Skill ID 分析', '')
4671
+ .option('--no-copy', '不复制到剪贴板', false)
4672
+ .action(async (options) => {
4673
+ const statsPath = path.join(os.homedir(), '.ethan-stats.json');
4674
+ let statsData = {};
4675
+ if (fs.existsSync(statsPath)) {
4676
+ try {
4677
+ statsData = JSON.parse(fs.readFileSync(statsPath, 'utf-8'));
4678
+ }
4679
+ catch { /* ignore */ }
4680
+ }
4681
+ const skills = await getActiveSkills();
4682
+ const ratings = (statsData.ratings ?? {});
4683
+ const usage = (statsData.usage ?? {});
4684
+ const targetSkills = options.skill
4685
+ ? skills.filter((s) => s.id === options.skill)
4686
+ : skills;
4687
+ const rows = targetSkills.map((s) => {
4688
+ const r = ratings[s.id] ?? [];
4689
+ const avg = r.length ? (r.reduce((a, b) => a + b, 0) / r.length).toFixed(1) : 'N/A';
4690
+ const uses = usage[s.id] ?? 0;
4691
+ return `| ${s.id.padEnd(25)} | ${String(uses).padStart(5)} | ${avg.toString().padStart(5)} | ${r.length} |`;
4692
+ });
4693
+ const report = `# Skill 质量基准报告\n\n` +
4694
+ `| Skill ID | 使用次数 | 平均评分 | 评分数 |\n` +
4695
+ `|---------------------------|---------|---------|--------|\n` +
4696
+ rows.join('\n') + '\n\n' +
4697
+ `> 数据来源:~/.ethan-stats.json`;
4698
+ if (options.copy !== false) {
4699
+ copyToClipboard(report);
4700
+ console.log('\n✅ 基准报告已复制到剪贴板\n');
4701
+ }
4702
+ else {
4703
+ console.log(report);
4704
+ }
4705
+ });
4706
+ // ─── sync ─────────────────────────────────────────────────────────────────────
4707
+ program
4708
+ .command('sync <action>')
4709
+ .description('配置同步:push(推送到 git)| pull(从 git 拉取)')
4710
+ .action((action) => {
4711
+ const syncBranch = 'ethan-config';
4712
+ const ethanDir = path.join(process.cwd(), '.ethan');
4713
+ if (!(0, utils_1.isGitRepo)(process.cwd())) {
4714
+ console.error('\n❌ 当前目录不是 git 仓库\n');
4715
+ process.exit(1);
4716
+ }
4717
+ if (action === 'push') {
4718
+ // Add .ethan/ to git and push to a special branch
4719
+ console.log(`\n📤 同步配置到 git 分支 ${syncBranch}...\n`);
4720
+ const r1 = (0, child_process_1.spawnSync)('git', ['add', '.ethan/'], { cwd: process.cwd(), encoding: 'utf-8' });
4721
+ if (r1.status !== 0) {
4722
+ console.error('git add 失败');
4723
+ process.exit(1);
4724
+ }
4725
+ (0, child_process_1.spawnSync)('git', ['commit', '-m', 'chore: sync ethan config', '--allow-empty'], {
4726
+ cwd: process.cwd(), encoding: 'utf-8',
4727
+ });
4728
+ console.log('✅ 已提交 .ethan/ 配置(注意:需手动 git push)\n');
4729
+ }
4730
+ else if (action === 'pull') {
4731
+ console.log(`\n📥 从 git 拉取配置...\n`);
4732
+ const r = (0, child_process_1.spawnSync)('git', ['pull', '--no-rebase'], { cwd: process.cwd(), encoding: 'utf-8' });
4733
+ console.log(r.stdout ?? '');
4734
+ if (r.status !== 0) {
4735
+ console.error(r.stderr ?? 'pull 失败');
4736
+ process.exit(1);
4737
+ }
4738
+ if (fs.existsSync(ethanDir)) {
4739
+ console.log('✅ 配置已同步\n');
4740
+ }
4741
+ else {
4742
+ console.log('ℹ️ .ethan/ 目录不存在,可能尚未有配置\n');
4743
+ }
4744
+ }
4745
+ else {
4746
+ console.error(`\n❌ 未知操作: ${action}(支持: push | pull)\n`);
4747
+ process.exit(1);
4748
+ }
4749
+ });
4750
+ // ─── compliance ───────────────────────────────────────────────────────────────
4751
+ program
4752
+ .command('compliance')
4753
+ .description('生成合规证据收集提示词(soc2 / gdpr / iso27001)')
4754
+ .option('--standard <std>', '合规标准:soc2 | gdpr | iso27001', 'soc2')
4755
+ .option('--no-copy', '不复制到剪贴板', false)
4756
+ .action((options) => {
4757
+ const standards = {
4758
+ soc2: {
4759
+ name: 'SOC 2 Type II',
4760
+ controls: [
4761
+ '**CC6.1** 逻辑访问控制:MFA、最小权限原则、访问审查记录',
4762
+ '**CC6.6** 网络安全控制:防火墙规则、TLS 加密、入侵检测',
4763
+ '**CC7.1** 系统监控:日志集中管理、异常告警、保留 90 天',
4764
+ '**CC7.2** 安全事件响应:Incident Response Plan、演练记录',
4765
+ '**A1.1** 可用性:SLA 目标、备份策略、RTO/RPO 定义',
4766
+ '**C1.1** 保密性:数据分类、加密传输存储、访问控制',
4767
+ ],
4768
+ },
4769
+ gdpr: {
4770
+ name: 'GDPR',
4771
+ controls: [
4772
+ '**Art. 13/14** 隐私政策:数据收集目的、保留期限、用户权利',
4773
+ '**Art. 17** 删除权:用户数据删除接口及执行证明',
4774
+ '**Art. 25** 隐私设计:数据最小化、假名化处理',
4775
+ '**Art. 30** 处理活动记录(ROPA)',
4776
+ '**Art. 32** 安全措施:加密、访问控制、漏洞管理',
4777
+ '**Art. 33** 数据泄露通知流程:72 小时内报告机制',
4778
+ ],
4779
+ },
4780
+ iso27001: {
4781
+ name: 'ISO 27001:2022',
4782
+ controls: [
4783
+ '**A.5** 信息安全策略:书面安全政策、年度审查',
4784
+ '**A.8** 资产管理:信息资产清单、数据分类',
4785
+ '**A.9** 访问控制:用户管理流程、定期权限审查',
4786
+ '**A.12** 运营安全:变更管理、恶意代码防护、日志监控',
4787
+ '**A.16** 信息安全事件管理:事件响应程序',
4788
+ '**A.17** 业务连续性:BCP/DRP 文档及演练',
4789
+ ],
4790
+ },
4791
+ };
4792
+ const std = standards[options.standard] ?? standards.soc2;
4793
+ const controls = std.controls.map((c) => `- ${c}`).join('\n');
4794
+ const prompt = `# 合规证据收集 — ${std.name}\n\n` +
4795
+ `## 需要收集的证据清单\n\n${controls}\n\n` +
4796
+ `## 证据收集任务\n\n` +
4797
+ `请为以上每项控制措施:\n\n` +
4798
+ `1. **识别现有证据**:系统中已有哪些文档/配置可作为证据\n` +
4799
+ `2. **差距分析**:缺少什么证据(❌ 表示缺失)\n` +
4800
+ `3. **证据生成建议**:如何创建缺失证据(文档模板/配置截图/脚本)\n` +
4801
+ `4. **优先级排序**:按审计风险从高到低排列\n\n` +
4802
+ `> 请基于技术实现现状(代码库、基础设施、流程文档)进行分析。`;
4803
+ if (options.copy !== false) {
4804
+ copyToClipboard(prompt);
4805
+ console.log(`\n✅ ${std.name} 合规证据提示词已复制到剪贴板\n`);
4806
+ }
4807
+ else {
4808
+ console.log(prompt);
4809
+ }
4810
+ });
3793
4811
  program.parse(process.argv);
3794
4812
  //# sourceMappingURL=index.js.map