ethan-skill 1.14.0 → 1.15.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 (37) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +31 -8
  3. package/dist/agents/orchestrator.test.js +5 -6
  4. package/dist/agents/orchestrator.test.js.map +1 -1
  5. package/dist/cli/index.js +398 -34
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/update-check.d.ts +8 -1
  8. package/dist/cli/update-check.d.ts.map +1 -1
  9. package/dist/cli/update-check.js +86 -11
  10. package/dist/cli/update-check.js.map +1 -1
  11. package/dist/extension/index.d.ts +54 -0
  12. package/dist/extension/index.d.ts.map +1 -0
  13. package/dist/extension/index.js +198 -0
  14. package/dist/extension/index.js.map +1 -0
  15. package/dist/mcp/server.d.ts.map +1 -1
  16. package/dist/mcp/server.js +61 -1
  17. package/dist/mcp/server.js.map +1 -1
  18. package/dist/memory/index.d.ts +42 -0
  19. package/dist/memory/index.d.ts.map +1 -0
  20. package/dist/memory/index.js +320 -0
  21. package/dist/memory/index.js.map +1 -0
  22. package/dist/memory/types.d.ts +47 -0
  23. package/dist/memory/types.d.ts.map +1 -0
  24. package/dist/memory/types.js +7 -0
  25. package/dist/memory/types.js.map +1 -0
  26. package/package.json +1 -1
  27. package/rules/claude-code/CLAUDE.md +2 -2
  28. package/rules/cline/.clinerules +2 -2
  29. package/rules/codebuddy/CODEBUDDY.md +2 -2
  30. package/rules/continue/.continuerules +2 -2
  31. package/rules/copilot/copilot-instructions.md +2 -2
  32. package/rules/cursor/.cursorrules +2 -2
  33. package/rules/cursor/smart-flow.mdc +2 -2
  34. package/rules/jetbrains/smart-flow.md +2 -2
  35. package/rules/lingma/smart-flow.md +2 -2
  36. package/rules/windsurf/.windsurf/rules/smart-flow.md +2 -2
  37. package/rules/zed/smart-flow.rules +1 -1
package/dist/cli/index.js CHANGED
@@ -628,11 +628,11 @@ program
628
628
  const platformDirMap = {
629
629
  'claude-code': path.join(dir, '.claude/commands'),
630
630
  codebuddy: path.join(dir, '.codebuddy/commands'),
631
- cursor: path.join(dir, '.cursor/rules'),
631
+ cursor: path.join(dir, '.cursor/commands'), // v0.50+ 支持独立命令文件
632
+ cline: path.join(dir, '.cline/commands'), // 支持独立命令文件
633
+ windsurf: path.join(dir, '.windsurf/commands'), // Wave 6+ 支持独立命令文件
632
634
  copilot: path.join(dir, '.github'),
633
- cline: dir,
634
- windsurf: path.join(dir, '.windsurf/rules'),
635
- zed: dir,
635
+ zed: path.join(dir, '.zed/prompts'), // /prompt 机制,独立 .md 文件
636
636
  jetbrains: path.join(dir, '.github'),
637
637
  continue: dir,
638
638
  lingma: path.join(dir, '.lingma/rules'),
@@ -642,9 +642,11 @@ program
642
642
  const outDir = platformDirMap[p];
643
643
  if (!fs.existsSync(outDir))
644
644
  fs.mkdirSync(outDir, { recursive: true });
645
- if (p === 'claude-code' || p === 'codebuddy') {
645
+ // 支持独立命令文件的平台:claude-code / codebuddy / cursor / cline / windsurf
646
+ const INDIVIDUAL_FILE_PLATFORMS = ['claude-code', 'codebuddy', 'cursor', 'cline', 'windsurf'];
647
+ if (INDIVIDUAL_FILE_PLATFORMS.includes(p)) {
646
648
  const isClaudeCode = p === 'claude-code';
647
- const cmdDir = isClaudeCode ? '.claude/commands' : '.codebuddy/commands';
649
+ const cmdDir = path.relative(dir, outDir); // 用于日志展示
648
650
  // ── 每个 Skill 生成独立 .md slash 命令文件 ──────────────────────────────
649
651
  for (const skill of skills) {
650
652
  const stepsText = skill.steps
@@ -752,9 +754,35 @@ program
752
754
  console.log(` ✅ claude-code → 工作流: ${cmdDir}/ethan-{cmd}.md × ${WORKFLOW_SLASH_COMMANDS.length}`);
753
755
  console.log(` 使用方式:在 Claude Code 聊天中输入 /ethan-commit、/ethan-auto 等`);
754
756
  console.log(` ⚡ 动态注入:提示词自动注入对话,无需复制粘贴`);
757
+ // ── Agent 命令(Claude Code:每个 Agent 一个动态 .md 文件)─────────────
758
+ const { getActiveAgents } = await Promise.resolve().then(() => __importStar(require('../agents/index')));
759
+ const agents = getActiveAgents(dir);
760
+ // ethan-agent-list.md
761
+ const agentListContent = `---\ndescription: "Ethan — 查看所有可用 Agent 及其 Skill 分配"\n---\n\n` +
762
+ `$(ethan agent list 2>/dev/null)\n`;
763
+ fs.writeFileSync(path.join(outDir, 'ethan-agent-list.md'), agentListContent, 'utf-8');
764
+ // ethan-agent-run.md
765
+ const agentRunContent = `---\ndescription: "Ethan — Multi-Agent 编排:将任务分配给多个专业 Agent 协作执行"\n---\n\n` +
766
+ `$(![ -n "$ARGUMENTS" ] && ethan agent run $ARGUMENTS --no-copy 2>/dev/null || printf "## Ethan Multi-Agent 编排\\n\\n用法: /ethan-agent-run <pipeline> -c \\"任务描述\\"\\n\\n示例:\\n /ethan-agent-run dev-workflow -c \\"实现用户登录功能\\"\\n\\n可用模式: sequential / parallel / review-loop / consensus")\n`;
767
+ fs.writeFileSync(path.join(outDir, 'ethan-agent-run.md'), agentRunContent, 'utf-8');
768
+ // ethan-agent-new.md
769
+ const agentNewContent = `---\ndescription: "Ethan — 创建自定义 Agent,生成 .ethan/agents/<id>.yaml"\n---\n\n` +
770
+ `$(ethan agent new $ARGUMENTS 2>/dev/null)\n`;
771
+ fs.writeFileSync(path.join(outDir, 'ethan-agent-new.md'), agentNewContent, 'utf-8');
772
+ // 每个内置 Agent 生成独立快捷命令(动态)
773
+ let agentFileCount = 3;
774
+ for (const agent of agents) {
775
+ const agentSkillList = agent.skillIds.slice(0, 8).join('、') + (agent.skillIds.length > 8 ? '...' : '');
776
+ const agentContent = `---\ndescription: "Ethan — ${agent.emoji} ${agent.name}:${agent.role}"\n---\n\n` +
777
+ `$(![ -n "$ARGUMENTS" ] && ethan agent run --no-copy -c "$ARGUMENTS" 2>/dev/null || printf "## ${agent.emoji} ${agent.name}\\n\\n**职责**: ${agent.role}\\n\\n**负责 Skill**: ${agentSkillList}\\n\\n用法: /ethan-agent-${agent.id} <任务描述>")\n`;
778
+ fs.writeFileSync(path.join(outDir, `ethan-agent-${agent.id}.md`), agentContent, 'utf-8');
779
+ agentFileCount++;
780
+ total++;
781
+ }
782
+ console.log(` ✅ claude-code → Agents: ${cmdDir}/ethan-agent-*.md × ${agentFileCount}(含 ${agents.length} 个 Agent 快捷键)`);
755
783
  }
756
784
  else {
757
- // CodeBuddy:静态提示词(各 /ethan-xxx 命令文件)
785
+ // codebuddy / cursor / cline / windsurf:静态提示词(各 /ethan-xxx 命令文件)
758
786
  for (const cmd of WORKFLOW_SLASH_COMMANDS) {
759
787
  const content = `---\n` +
760
788
  `description: "Ethan — ${cmd.name}:${cmd.description}"\n` +
@@ -763,8 +791,45 @@ program
763
791
  fs.writeFileSync(path.join(outDir, `ethan-${cmd.id}.md`), content, 'utf-8');
764
792
  total++;
765
793
  }
766
- console.log(` ✅ codebuddy → 工作流: ${cmdDir}/ethan-{cmd}.md × ${WORKFLOW_SLASH_COMMANDS.length}`);
767
- console.log(` 使用方式:在 CodeBuddy 聊天中输入 /ethan-commit、/ethan-auto 等`);
794
+ console.log(` ✅ ${p.padEnd(12)} → 工作流: ${cmdDir}/ethan-{cmd}.md × ${WORKFLOW_SLASH_COMMANDS.length}`);
795
+ console.log(` 使用方式:在 ${p} 聊天中输入 /ethan-commit、/ethan-auto 等`);
796
+ // ── Agent 命令(静态提示词,适用于 codebuddy/cursor/cline/windsurf)──────
797
+ const { getActiveAgents: getAgentsStatic } = await Promise.resolve().then(() => __importStar(require('../agents/index')));
798
+ const agentsStatic = getAgentsStatic(dir);
799
+ // ethan-agent-list.md(静态)
800
+ const agentListRows = agentsStatic
801
+ .map((a) => `| /ethan-agent-${a.id} | ${a.emoji} ${a.name} | ${a.role} |`)
802
+ .join('\n');
803
+ fs.writeFileSync(path.join(outDir, 'ethan-agent-list.md'), `---\ndescription: "Ethan — 查看所有可用 Agent 及其 Skill 分配"\n---\n\n` +
804
+ `# Ethan Agent 列表\n\n` +
805
+ `| 命令 | Agent | 职责 |\n|------|-------|------|\n${agentListRows}\n\n` +
806
+ `运行:\`ethan agent list\`\n`, 'utf-8');
807
+ // ethan-agent-run.md(静态)
808
+ fs.writeFileSync(path.join(outDir, 'ethan-agent-run.md'), `---\ndescription: "Ethan — Multi-Agent 编排:将任务分配给多个专业 Agent 协作执行"\n---\n\n` +
809
+ `# Ethan Multi-Agent 编排\n\n` +
810
+ `用法:\`ethan agent run <pipeline> -c "任务描述"\`\n\n` +
811
+ `示例:\`ethan agent run dev-workflow -c "实现用户登录功能"\`\n\n` +
812
+ `可用模式:sequential / parallel / review-loop / consensus\n`, 'utf-8');
813
+ // ethan-agent-new.md(静态)
814
+ fs.writeFileSync(path.join(outDir, 'ethan-agent-new.md'), `---\ndescription: "Ethan — 创建自定义 Agent,生成 .ethan/agents/<id>.yaml"\n---\n\n` +
815
+ `# 创建自定义 Agent\n\n` +
816
+ `运行:\`ethan agent new\`\n\n` +
817
+ `将在 \`.ethan/agents/\` 目录下生成 Agent 配置文件。\n`, 'utf-8');
818
+ // 每个内置 Agent 生成独立静态命令文件
819
+ let staticAgentFileCount = 3;
820
+ for (const agent of agentsStatic) {
821
+ const agentSkillList = agent.skillIds.slice(0, 8).join('、') + (agent.skillIds.length > 8 ? '...' : '');
822
+ const agentContent = `---\ndescription: "Ethan — ${agent.emoji} ${agent.name}:${agent.role}"\n---\n\n` +
823
+ `# ${agent.emoji} ${agent.name}\n\n` +
824
+ `**职责**: ${agent.role}\n\n` +
825
+ `**负责 Skill**: ${agentSkillList}\n\n` +
826
+ `**运行方式**:\`ethan agent run --no-copy -c "<任务描述>"\`\n\n` +
827
+ `**示例**:\`ethan agent run --no-copy -c "对当前代码进行安全审查"\`\n`;
828
+ fs.writeFileSync(path.join(outDir, `ethan-agent-${agent.id}.md`), agentContent, 'utf-8');
829
+ staticAgentFileCount++;
830
+ total++;
831
+ }
832
+ console.log(` ✅ ${p.padEnd(12)} → Agents: ${cmdDir}/ethan-agent-*.md × ${staticAgentFileCount}(含 ${agentsStatic.length} 个 Agent 快捷键)`);
768
833
  }
769
834
  }
770
835
  else {
@@ -797,14 +862,28 @@ program
797
862
  continue: 'Continue(@ethan 触发)',
798
863
  lingma: '灵码(@ethan 触发)',
799
864
  };
865
+ // ── Agent 速查表章节 ─────────────────────────────────────────────────
866
+ const { getActiveAgents: getAgentsForOther } = await Promise.resolve().then(() => __importStar(require('../agents/index')));
867
+ const otherAgents = getAgentsForOther(dir);
868
+ const agentRows = otherAgents
869
+ .map((a) => `| \`/ethan-agent-${a.id}\` | \`@ethan agent ${a.id}\` | ${a.emoji} ${a.name} | Agent | ${a.role} |`)
870
+ .join('\n');
871
+ const agentSection = `## Multi-Agent 命令\n\n` +
872
+ `| Slash 命令 | @ethan 形式 | Agent | 类型 | 职责 |\n` +
873
+ `|-----------|------------|-------|------|------|\n` +
874
+ `| \`/ethan-agent-list\` | \`@ethan agent list\` | — | Agent | 查看所有可用 Agent |\n` +
875
+ `| \`/ethan-agent-run\` | \`@ethan agent run\` | — | Agent | Multi-Agent 编排执行 |\n` +
876
+ `| \`/ethan-agent-new\` | \`@ethan agent new\` | — | Agent | 创建自定义 Agent |\n` +
877
+ `${agentRows}\n`;
800
878
  const content = `# Ethan Slash 命令速查表 — ${platformLabel[p] ?? p}\n\n` +
801
879
  `> 生成时间:${new Date().toISOString()}\n\n` +
802
- `## Skills 命令(24 个)\n\n` +
880
+ `## Skills 命令(${skills.length} 个)\n\n` +
803
881
  `| Slash 命令 | @ethan 形式 | Skill 名称 | 类型 | 说明 |\n` +
804
882
  `|-----------|------------|-----------|------|------|\n` +
805
883
  `${skillRows}\n\n` +
806
884
  `## 工作流命令\n\n` +
807
885
  `${wfSections}\n\n` +
886
+ `${agentSection}\n` +
808
887
  `## 快速示例\n\n` +
809
888
  `\`\`\`\n` +
810
889
  `/ethan-code-review # Skill:代码审查\n` +
@@ -812,7 +891,8 @@ program
812
891
  `/ethan-commit # 工作流:生成提交信息\n` +
813
892
  `/ethan-review # 工作流:Code Review\n` +
814
893
  `/ethan-auto # 工作流:Auto-Pilot 超级 Prompt\n` +
815
- `/ethan-workflow-start # 工作流:启动有状态工作流\n` +
894
+ `/ethan-agent-run # Agent:Multi-Agent 协作编排\n` +
895
+ `/ethan-agent-architect # Agent:架构师 Agent 直接对话\n` +
816
896
  `\`\`\`\n`;
817
897
  const destFile = path.join(outDir, 'ethan-commands.md');
818
898
  fs.writeFileSync(destFile, content, 'utf-8');
@@ -820,7 +900,8 @@ program
820
900
  total++;
821
901
  }
822
902
  }
823
- const totalFiles = (platform === 'claude-code' || platform === 'codebuddy')
903
+ const INDIVIDUAL_FILE_PLATFORMS_CHECK = ['claude-code', 'codebuddy', 'cursor', 'cline', 'windsurf'];
904
+ const totalFiles = INDIVIDUAL_FILE_PLATFORMS_CHECK.includes(platform)
824
905
  ? skills.length + WORKFLOW_SLASH_COMMANDS.length
825
906
  : total;
826
907
  console.log(`\n共生成 ${totalFiles} 个 Slash 命令文件(目录:${dir})`);
@@ -1254,6 +1335,15 @@ pipelineCmd
1254
1335
  }
1255
1336
  writeStats(stats);
1256
1337
  });
1338
+ // ─── upgrade 命令 ───────────────────────────────────────────────────────────
1339
+ program
1340
+ .command('upgrade')
1341
+ .description('检查并升级到最新版本')
1342
+ .option('--force', '跳过版本检查,强制重新安装最新版本')
1343
+ .action(async (options) => {
1344
+ const { checkAndPrintUpdate } = await Promise.resolve().then(() => __importStar(require('./update-check')));
1345
+ await checkAndPrintUpdate(pkg.version, pkg.name, options.force ?? false);
1346
+ });
1257
1347
  // ─── doctor 命令 ────────────────────────────────────────────────────────────
1258
1348
  program
1259
1349
  .command('doctor')
@@ -1267,7 +1357,27 @@ program
1267
1357
  const nodeOk = nodeMajor >= 18;
1268
1358
  console.log(`\n[环境检查]`);
1269
1359
  console.log(` Node.js: v${nodeVersion} ${nodeOk ? '✅' : '❌ (需要 Node.js >= 18)'}`);
1270
- // 2. MCP SDK check
1360
+ // 2. Windows 专项检查
1361
+ if (process.platform === 'win32') {
1362
+ console.log('\n[Windows 环境检查]');
1363
+ // 检查 PowerShell 执行策略
1364
+ try {
1365
+ const psPolicy = (0, child_process_1.spawnSync)('powershell', ['-Command', 'Get-ExecutionPolicy'], { encoding: 'utf-8' });
1366
+ const policy = (psPolicy.stdout ?? '').trim();
1367
+ const policyOk = !['Restricted', 'AllSigned'].includes(policy);
1368
+ console.log(` PowerShell 执行策略: ${policy} ${policyOk ? '✅' : '⚠️ 需设置(Set-ExecutionPolicy RemoteSigned -Scope CurrentUser)'}`);
1369
+ }
1370
+ catch { /* PowerShell not available */ }
1371
+ // 检查 npm 全局路径是否在 PATH 中
1372
+ const npmGlobal = (0, child_process_1.spawnSync)('npm', ['config', 'get', 'prefix'], { encoding: 'utf-8', shell: true });
1373
+ const npmPrefix = (npmGlobal.stdout ?? '').trim();
1374
+ if (npmPrefix) {
1375
+ const inPath = (process.env.PATH ?? '').includes(npmPrefix);
1376
+ console.log(` npm global prefix: ${npmPrefix} ${inPath ? '✅ (在 PATH 中)' : '⚠️ 不在 PATH 中(需手动添加)'}`);
1377
+ }
1378
+ console.log(` UTF-8 编码: ${process.env.CHCP === '65001' || process.env.LC_ALL?.includes('UTF') ? '✅' : '⚠️ 建议 chcp 65001'}`);
1379
+ }
1380
+ // 3. MCP SDK check
1271
1381
  const sdkOk = fs.existsSync(path.join(__dirname, '../../node_modules/@modelcontextprotocol/sdk'));
1272
1382
  console.log(` @modelcontextprotocol/sdk: ${sdkOk ? '✅ 已安装' : '❌ 未安装'}`);
1273
1383
  // 3. Rules files status
@@ -1312,6 +1422,40 @@ program
1312
1422
  }
1313
1423
  console.log('\n' + '─'.repeat(60));
1314
1424
  const allBuilt = ruleFiles.every(({ file }) => fs.existsSync(path.join(ROOT, file)));
1425
+ // 版本更新状态检查
1426
+ console.log(`\n[版本更新状态]`);
1427
+ console.log(` 当前版本 : v${pkg.version}`);
1428
+ const updateCachePath = path.join(os.homedir(), '.ethan-update-cache.json');
1429
+ if (fs.existsSync(updateCachePath)) {
1430
+ try {
1431
+ const updateCache = JSON.parse(fs.readFileSync(updateCachePath, 'utf-8'));
1432
+ const lastCheckedAgo = Math.round((Date.now() - updateCache.lastChecked) / 1000 / 60);
1433
+ console.log(` npm 最新版 : v${updateCache.latestVersion}`);
1434
+ console.log(` 上次检查 : ${lastCheckedAgo} 分钟前`);
1435
+ const needsUpdate = (() => {
1436
+ const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
1437
+ const [cMaj, cMin, cPat] = parse(pkg.version);
1438
+ const [lMaj, lMin, lPat] = parse(updateCache.latestVersion);
1439
+ if (lMaj !== cMaj)
1440
+ return lMaj > cMaj;
1441
+ if (lMin !== cMin)
1442
+ return lMin > cMin;
1443
+ return lPat > cPat;
1444
+ })();
1445
+ if (needsUpdate) {
1446
+ console.log(` 升级状态 : ⚠️ 有新版本可用,运行 ethan upgrade 升级`);
1447
+ }
1448
+ else {
1449
+ console.log(` 升级状态 : ✅ 已是最新`);
1450
+ }
1451
+ }
1452
+ catch {
1453
+ console.log(` 升级状态 : ⚠️ 缓存文件损坏,将在下次运行时重建`);
1454
+ }
1455
+ }
1456
+ else {
1457
+ console.log(` 升级状态 : ⏳ 尚未检查(下次运行 ethan 命令时自动检查)`);
1458
+ }
1315
1459
  if (!allBuilt) {
1316
1460
  console.log('\n💡 提示:运行 npm run build:rules 生成规则文件\n');
1317
1461
  }
@@ -1471,6 +1615,166 @@ program
1471
1615
  console.log(`\n文件路径:${configPath}`);
1472
1616
  console.log('\n💡 提示:现在运行 ethan install --platform <platform> 将使用此配置\n');
1473
1617
  });
1618
+ // ─── setup 命令(零门槛一键安装向导) ────────────────────────────────────────
1619
+ // 面向产品、测试等非技术用户,自动检测环境,提供最简安装体验
1620
+ program
1621
+ .command('setup')
1622
+ .description('一键安装向导:自动检测编辑器,为非技术用户提供最简配置体验')
1623
+ .option('--role <role>', '用户角色:dev(开发)/ pm(产品)/ qa(测试)/ design(设计)')
1624
+ .option('--platform <platform>', '强制指定平台(跳过自动检测)')
1625
+ .option('--lang <lang>', '界面语言:zh(默认)/ en')
1626
+ .option('-y, --yes', '跳过所有确认,使用推荐配置')
1627
+ .action(async (options) => {
1628
+ const readline = await Promise.resolve().then(() => __importStar(require('readline')));
1629
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1630
+ const ask = (q, def) => new Promise((resolve) => rl.question(def !== undefined ? `${q} [${def}]: ` : `${q}: `, (a) => resolve(a.trim() || def || '')));
1631
+ const isEn = options.lang === 'en';
1632
+ const isAuto = !!options.yes;
1633
+ const t = {
1634
+ title: isEn ? 'Ethan Setup Wizard' : 'Ethan 快速安装向导',
1635
+ welcome: isEn
1636
+ ? 'This wizard will configure Ethan for your editor in 3 steps.'
1637
+ : '3 步完成配置,无需任何编程知识。',
1638
+ step1: isEn ? '[1/3] Detecting your editor...' : '[1/3] 检测你的编辑器...',
1639
+ step2: isEn ? '[2/3] Configuring your role...' : '[2/3] 配置你的角色...',
1640
+ step3: isEn ? '[3/3] Generating slash commands...' : '[3/3] 生成编辑器快捷指令...',
1641
+ done: isEn ? 'Setup complete!' : '安装完成!',
1642
+ };
1643
+ console.log(`\n${'═'.repeat(52)}`);
1644
+ console.log(` ${t.title}`);
1645
+ console.log(` ${t.welcome}`);
1646
+ console.log(`${'═'.repeat(52)}\n`);
1647
+ // ── 步骤 1:自动检测编辑器 ─────────────────────────────────────────────
1648
+ console.log(t.step1);
1649
+ // 检测逻辑:查找 .cursor / .vscode / .windsurf 等目录
1650
+ const detectedPlatforms = [];
1651
+ const checkDir = (d) => fs.existsSync(path.join(process.cwd(), d));
1652
+ if (checkDir('.cursor'))
1653
+ detectedPlatforms.push('cursor');
1654
+ if (checkDir('.vscode') || checkDir('.github'))
1655
+ detectedPlatforms.push('copilot');
1656
+ if (checkDir('.windsurf'))
1657
+ detectedPlatforms.push('windsurf');
1658
+ if (checkDir('.codebuddy'))
1659
+ detectedPlatforms.push('codebuddy');
1660
+ if (checkDir('.claude'))
1661
+ detectedPlatforms.push('claude-code');
1662
+ if (checkDir('.continue'))
1663
+ detectedPlatforms.push('continue');
1664
+ // 如果没有检测到任何编辑器,给出选项列表
1665
+ const ALL_PLATFORMS_SETUP = ['claude-code', 'cursor', 'copilot', 'codebuddy', 'windsurf', 'zed', 'jetbrains', 'continue', 'cline', 'lingma'];
1666
+ let chosenPlatform = options.platform ?? '';
1667
+ if (!chosenPlatform) {
1668
+ if (detectedPlatforms.length === 1 && isAuto) {
1669
+ chosenPlatform = detectedPlatforms[0];
1670
+ console.log(` ✅ 检测到 ${chosenPlatform},将自动配置`);
1671
+ }
1672
+ else if (detectedPlatforms.length > 0) {
1673
+ console.log(` 检测到以下编辑器:${detectedPlatforms.join(', ')}`);
1674
+ const detected = detectedPlatforms.join('/');
1675
+ if (isAuto) {
1676
+ chosenPlatform = detectedPlatforms[0];
1677
+ }
1678
+ else {
1679
+ const choice = await ask(isEn
1680
+ ? ` Which editor? (${ALL_PLATFORMS_SETUP.join('|')}|all)`
1681
+ : ` 选择编辑器(${ALL_PLATFORMS_SETUP.join('|')}|all)`, detectedPlatforms[0]);
1682
+ chosenPlatform = choice || detectedPlatforms[0];
1683
+ }
1684
+ }
1685
+ else {
1686
+ if (isAuto) {
1687
+ chosenPlatform = 'claude-code';
1688
+ }
1689
+ else {
1690
+ console.log(isEn ? ' No editor auto-detected. Common options:' : ' 未检测到编辑器,请选择:');
1691
+ ALL_PLATFORMS_SETUP.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
1692
+ const choice = await ask(isEn ? ' Enter number or name' : ' 输入序号或名称', '1');
1693
+ const num = parseInt(choice, 10);
1694
+ chosenPlatform = (!isNaN(num) && num >= 1 && num <= ALL_PLATFORMS_SETUP.length)
1695
+ ? ALL_PLATFORMS_SETUP[num - 1]
1696
+ : (choice || 'claude-code');
1697
+ }
1698
+ }
1699
+ }
1700
+ console.log(` → 目标平台:${chosenPlatform}\n`);
1701
+ // ── 步骤 2:角色配置 ──────────────────────────────────────────────────
1702
+ console.log(t.step2);
1703
+ const ROLE_PRESETS = {
1704
+ pm: { lang: 'zh', disabledSkills: ['unit-testing', 'docker', 'cicd', 'observability', 'data-pipeline', 'ml-experiment'] },
1705
+ qa: { lang: 'zh', disabledSkills: ['docker', 'cicd', 'ml-experiment', 'green-code', 'api-mock'] },
1706
+ design: { lang: 'zh', disabledSkills: ['unit-testing', 'docker', 'cicd', 'database-optimize', 'data-pipeline', 'ml-experiment', 'observability'] },
1707
+ dev: { lang: 'zh', disabledSkills: [] },
1708
+ };
1709
+ let chosenRole = options.role ?? '';
1710
+ if (!chosenRole && !isAuto) {
1711
+ console.log(isEn ? ' Your role:' : ' 你的角色:');
1712
+ console.log(' 1. dev - 开发工程师(启用所有 36 个 Skill)');
1713
+ console.log(' 2. qa - 测试工程师(测试、审查相关 Skill)');
1714
+ console.log(' 3. pm - 产品经理(需求、设计、周报相关 Skill)');
1715
+ console.log(' 4. design - 设计师(流程、文档类 Skill)');
1716
+ const roleChoice = await ask(isEn ? ' Select (1-4)' : ' 选择(1-4)', '1');
1717
+ const roleMap = { '1': 'dev', '2': 'qa', '3': 'pm', '4': 'design' };
1718
+ chosenRole = roleMap[roleChoice] ?? roleChoice ?? 'dev';
1719
+ }
1720
+ chosenRole = chosenRole || 'dev';
1721
+ console.log(` → 角色:${chosenRole}\n`);
1722
+ const preset = ROLE_PRESETS[chosenRole] ?? ROLE_PRESETS.dev;
1723
+ // 语言选择
1724
+ let chosenLang = preset.lang;
1725
+ if (!isAuto && !options.lang) {
1726
+ const langChoice = await ask(isEn ? ' Output language (zh/en)' : ' 输出语言(zh/en)', 'zh');
1727
+ chosenLang = langChoice === 'en' ? 'en' : 'zh';
1728
+ }
1729
+ rl.close();
1730
+ // 写入 .ethanrc.json
1731
+ const existingConfig = (0, config_1.readConfig)(process.cwd());
1732
+ const newConfig = {
1733
+ ...existingConfig,
1734
+ lang: chosenLang,
1735
+ ...(preset.disabledSkills.length > 0 ? { disabledSkills: preset.disabledSkills } : {}),
1736
+ setup: { role: chosenRole, platform: chosenPlatform, setupAt: new Date().toISOString() },
1737
+ };
1738
+ (0, config_1.writeConfig)(newConfig, process.cwd());
1739
+ console.log(` ✅ .ethanrc.json 已生成(角色:${chosenRole},语言:${chosenLang})`);
1740
+ // ── 步骤 3:生成编辑器快捷指令 ───────────────────────────────────────
1741
+ console.log(`\n${t.step3}`);
1742
+ const slashPlatforms = chosenPlatform === 'all' ? 'all' : chosenPlatform;
1743
+ // 动态调用 slash 命令逻辑(通过 execSync,保持 tty)
1744
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
1745
+ try {
1746
+ execSync(`ethan slash --platform ${slashPlatforms} --dir "${process.cwd()}"`, { stdio: 'inherit' });
1747
+ }
1748
+ catch {
1749
+ // 如果 ethan 还未在全局安装(开发模式),尝试直接调用 ts-node
1750
+ console.log(isEn ? ' Note: Run "ethan slash" manually if above failed.' : ' 提示:如以上失败,请手动运行 ethan slash。');
1751
+ }
1752
+ // ── 完成 ─────────────────────────────────────────────────────────────
1753
+ console.log(`\n${'═'.repeat(52)}`);
1754
+ console.log(` ${t.done}`);
1755
+ console.log('═'.repeat(52));
1756
+ console.log(isEn ? '\nNext steps:' : '\n下一步:');
1757
+ if (chosenPlatform === 'claude-code') {
1758
+ console.log(isEn
1759
+ ? ' 1. Restart Claude Code editor'
1760
+ : ' 1. 重启 Claude Code 编辑器');
1761
+ console.log(isEn
1762
+ ? ' 2. Type /ethan- in chat to see all commands'
1763
+ : ' 2. 在聊天中输入 /ethan- 即可看到所有快捷指令');
1764
+ }
1765
+ else {
1766
+ console.log(isEn
1767
+ ? ` 1. Open ethan-commands.md in your ${chosenPlatform} context`
1768
+ : ` 1. 将 ethan-commands.md 加入你的 ${chosenPlatform} 上下文`);
1769
+ console.log(isEn
1770
+ ? ' 2. Type /ethan-code-review or @ethan code-review to start'
1771
+ : ' 2. 输入 /ethan-code-review 或 @ethan code-review 开始使用');
1772
+ }
1773
+ console.log(isEn
1774
+ ? ' 3. Run "ethan agent run" for multi-agent collaboration'
1775
+ : ' 3. 运行 ethan agent run 体验多 Agent 协作');
1776
+ console.log('');
1777
+ });
1474
1778
  // ─── workflow 命令(有状态一键工作流) ──────────────────────────────────────
1475
1779
  const workflowCmd = program.command('workflow').description('有状态工作流执行:一键推进各阶段任务');
1476
1780
  workflowCmd
@@ -3014,36 +3318,34 @@ memoryCmd
3014
3318
  });
3015
3319
  memoryCmd
3016
3320
  .command('search <keyword>')
3017
- .description('在记忆库中搜索关键词(标题 + 内容 + 标签)')
3321
+ .description('在记忆库中全文检索(加权评分 + 片段高亮)')
3018
3322
  .option('--global', '搜索全局记忆库')
3019
- .option('--tag <tag>', '按标签过滤')
3323
+ .option('--tag <tag>', '按标签过滤(AND 语义)')
3324
+ .option('--type <type>', '按类型过滤:workflow|skill|manual|decision|knowledge')
3020
3325
  .option('-n, --limit <n>', '最多显示 N 条', '10')
3021
- .action((keyword, options) => {
3022
- const dir = getMemoryDir(!!options.global);
3023
- const entries = loadMemoryEntries(dir);
3024
- const kw = keyword.toLowerCase();
3025
- const limit = parseInt(options.limit, 10) || 10;
3026
- let results = entries.filter((e) => {
3027
- const matchKw = e.title.toLowerCase().includes(kw) ||
3028
- e.content.toLowerCase().includes(kw) ||
3029
- e.tags.some((t) => t.toLowerCase().includes(kw));
3030
- const matchTag = options.tag ? e.tags.includes(options.tag) : true;
3031
- return matchKw && matchTag;
3326
+ .action(async (keyword, options) => {
3327
+ const { searchMemory, getMemoryDir: getMemDir } = await Promise.resolve().then(() => __importStar(require('../memory/index')));
3328
+ const dir = getMemDir(!!options.global);
3329
+ const limit = parseInt(options.limit ?? '10', 10) || 10;
3330
+ const results = searchMemory(keyword, dir, {
3331
+ tags: options.tag ? [options.tag] : undefined,
3332
+ type: options.type,
3333
+ limit,
3032
3334
  });
3033
- results = results.slice(0, limit);
3034
3335
  if (results.length === 0) {
3035
3336
  console.log(`\n🔍 未找到匹配 "${keyword}" 的记忆\n`);
3337
+ console.log(' 💡 尝试更宽泛的关键词,或用 ethan memory list 查看所有记忆\n');
3036
3338
  return;
3037
3339
  }
3038
3340
  console.log(`\n🔍 找到 ${results.length} 条记忆(关键词:"${keyword}")\n`);
3039
- console.log('─'.repeat(60));
3040
- for (const e of results) {
3041
- const preview = e.content.length > 100 ? e.content.slice(0, 100) + '…' : e.content;
3042
- console.log(`\n 📌 ${e.title}`);
3043
- console.log(` ID: ${e.id} | ${e.createdAt.slice(0, 10)} | 标签:${e.tags.join(', ') || '无'}`);
3044
- console.log(` ${preview}`);
3341
+ console.log('─'.repeat(70));
3342
+ for (const { entry: e, score, matchedFields, snippet } of results) {
3343
+ console.log(`\n 📌 [${e.type}] ${e.title}`);
3344
+ console.log(` ID: ${e.id} | ${e.createdAt.slice(0, 10)} | 相关度: ${score} | 命中: ${matchedFields.join(', ')}`);
3345
+ console.log(` 标签:${e.tags.join(', ') || '无'}`);
3346
+ console.log(` 摘要:${snippet}`);
3045
3347
  }
3046
- console.log('\n' + '─'.repeat(60));
3348
+ console.log('\n' + '─'.repeat(70));
3047
3349
  console.log(`\n💡 用 ethan memory show <id> 查看完整内容\n`);
3048
3350
  });
3049
3351
  memoryCmd
@@ -5063,5 +5365,67 @@ agentCmd
5063
5365
  console.log(` ethan agent run --context "任务" # 在编排中使用此 Agent`);
5064
5366
  console.log('');
5065
5367
  });
5368
+ // ─── extension 命令(事件钩子 + Webhook 扩展)─────────────────────────────────
5369
+ const extCmd = program
5370
+ .command('extension')
5371
+ .alias('ext')
5372
+ .description('扩展管理:事件钩子、Webhook 集成、Extension SDK');
5373
+ extCmd
5374
+ .command('list')
5375
+ .description('查看当前项目的钩子和 Webhook 配置')
5376
+ .action(async () => {
5377
+ const { readExtensionsConfig } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
5378
+ const cfg = readExtensionsConfig(process.cwd());
5379
+ console.log('\n🔌 Ethan 扩展配置\n');
5380
+ console.log('─'.repeat(50));
5381
+ console.log(`\n钩子文件(${cfg.hooks.length} 个):`);
5382
+ if (cfg.hooks.length === 0)
5383
+ console.log(' (无)');
5384
+ cfg.hooks.forEach((h) => console.log(` ${h.enabled ? '✅' : '⏸️ '} ${h.file}`));
5385
+ console.log(`\nWebhook(${cfg.webhooks.length} 个):`);
5386
+ if (cfg.webhooks.length === 0)
5387
+ console.log(' (无)');
5388
+ cfg.webhooks.forEach((w) => console.log(` ${w.enabled ? '✅' : '⏸️ '} ${w.url} [${w.events.join(', ')}]`));
5389
+ console.log('');
5390
+ });
5391
+ extCmd
5392
+ .command('hook-init [name]')
5393
+ .description('在 .ethan/hooks/ 生成示例钩子文件')
5394
+ .action(async (name) => {
5395
+ const { generateHookTemplate } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
5396
+ const hookName = name || 'my-hook';
5397
+ const file = generateHookTemplate(process.cwd(), hookName);
5398
+ console.log(`\n✅ 钩子文件已生成:${file}`);
5399
+ console.log('\n支持的事件:before:skill | after:skill | before:pipeline | after:pipeline | workflow:done | memory:save\n');
5400
+ });
5401
+ extCmd
5402
+ .command('webhook-add <url>')
5403
+ .description('添加 Webhook(在工作流事件后推送通知)')
5404
+ .option('--events <events>', '监听事件(逗号分隔)', 'after:skill,workflow:done')
5405
+ .option('--secret <secret>', 'HMAC 签名密钥(可选)')
5406
+ .action(async (url, opts) => {
5407
+ const { readExtensionsConfig, writeExtensionsConfig } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
5408
+ const cfg = readExtensionsConfig(process.cwd());
5409
+ const events = opts.events.split(',').map((e) => e.trim());
5410
+ cfg.webhooks.push({ url, events, secret: opts.secret, enabled: true, headers: {} });
5411
+ writeExtensionsConfig(cfg, process.cwd());
5412
+ console.log(`\n✅ Webhook 已添加:${url}`);
5413
+ console.log(` 监听事件:${events.join(', ')}\n`);
5414
+ });
5415
+ extCmd
5416
+ .command('webhook-remove <url>')
5417
+ .description('移除 Webhook')
5418
+ .action(async (url) => {
5419
+ const { readExtensionsConfig, writeExtensionsConfig } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
5420
+ const cfg = readExtensionsConfig(process.cwd());
5421
+ const before = cfg.webhooks.length;
5422
+ cfg.webhooks = cfg.webhooks.filter((w) => w.url !== url);
5423
+ if (cfg.webhooks.length === before) {
5424
+ console.error(`\n❌ 未找到 Webhook: ${url}\n`);
5425
+ process.exit(1);
5426
+ }
5427
+ writeExtensionsConfig(cfg, process.cwd());
5428
+ console.log(`\n✅ Webhook 已移除:${url}\n`);
5429
+ });
5066
5430
  program.parse(process.argv);
5067
5431
  //# sourceMappingURL=index.js.map