lumencode 1.3.1 → 1.3.2

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/lib/report.js CHANGED
@@ -460,6 +460,195 @@ function buildGitInsight(git) {
460
460
  return insights.length > 0 ? insights.join(';') + '。' : null;
461
461
  }
462
462
 
463
+ /**
464
+ * 效率指标板块——量化投入产出比
465
+ */
466
+ function buildEfficiencyMetrics(usageData, gitData) {
467
+ const lines = [];
468
+ const metrics = [];
469
+ const reqCount = usageData.requestCount || 0;
470
+
471
+ // 样本量不足时不生成效率指标
472
+ if (reqCount < 5) return null;
473
+
474
+ // 1. 交互效率:每次交互的产出
475
+ if (gitData && reqCount > 0) {
476
+ const commits = gitData.commits || 0;
477
+ const linesAdded = gitData.linesAdded || 0;
478
+ const filesChanged = gitData.filesChanged || 0;
479
+
480
+ if (commits > 0) {
481
+ const reqsPerCommit = Math.round(reqCount / commits);
482
+ metrics.push(`- **交互/提交比**:${reqsPerCommit}:1(每 ${reqsPerCommit} 次交互产出 1 次提交)`);
483
+ }
484
+ if (linesAdded > 0) {
485
+ const linesPerReq = (linesAdded / reqCount).toFixed(1);
486
+ metrics.push(`- **每次交互新增**:${linesPerReq} 行`);
487
+ }
488
+ if (filesChanged > 0 && commits > 0) {
489
+ const filesPerCommit = (filesChanged / commits).toFixed(1);
490
+ metrics.push(`- **每次提交变更**:${filesPerCommit} 个文件`);
491
+ }
492
+ }
493
+
494
+ // 2. 成本效率
495
+ if (usageData.estimatedCost && usageData.estimatedCost > 0) {
496
+ if (reqCount > 0) {
497
+ const costPerReq = (usageData.estimatedCost / reqCount).toFixed(3);
498
+ metrics.push(`- **每次交互成本**:$${costPerReq}`);
499
+ }
500
+ if (gitData && gitData.linesAdded > 0) {
501
+ const costPerKLines = (usageData.estimatedCost / (gitData.linesAdded / 1000)).toFixed(2);
502
+ metrics.push(`- **每千行代码成本**:$${costPerKLines}`);
503
+ }
504
+ }
505
+
506
+ // 3. 日均产出
507
+ if (usageData.activeDays > 0) {
508
+ const dailyMetrics = [];
509
+ if (reqCount > 0) {
510
+ dailyMetrics.push(`日均 ${Math.round(reqCount / usageData.activeDays)} 次交互`);
511
+ }
512
+ if (gitData) {
513
+ if (gitData.commits > 0) {
514
+ dailyMetrics.push(`${(gitData.commits / usageData.activeDays).toFixed(1)} 次提交`);
515
+ }
516
+ if (gitData.linesAdded > 0) {
517
+ dailyMetrics.push(`+${Math.round(gitData.linesAdded / usageData.activeDays)} 行`);
518
+ }
519
+ }
520
+ if (dailyMetrics.length > 0) {
521
+ metrics.push(`- **日均产出**:${dailyMetrics.join(',')}`);
522
+ }
523
+ }
524
+
525
+ // 4. Token 效率
526
+ if (usageData.outputTokens > 0 && reqCount > 0) {
527
+ const outputPerReq = (usageData.outputTokens / reqCount).toFixed(0);
528
+ metrics.push(`- **每次交互输出**:${Number(outputPerReq).toLocaleString('zh-CN')} Token`);
529
+ }
530
+
531
+ if (metrics.length === 0) return null;
532
+
533
+ for (const m of metrics) lines.push(m);
534
+
535
+ // 效率洞察
536
+ const insights = [];
537
+ if (gitData && gitData.commits > 0 && reqCount > 0) {
538
+ const ratio = reqCount / gitData.commits;
539
+ if (ratio < 5) {
540
+ insights.push('交互-提交转化率极高,说明需求明确、AI 理解准确,**沟通效率优秀**。');
541
+ } else if (ratio > 20) {
542
+ insights.push('交互-提交比较高,说明有较多的**探索和调试**过程——这在复杂任务中是正常的,但可以评估是否通过更精确的提示词降低无效交互。');
543
+ }
544
+ }
545
+ if (usageData.estimatedCost && gitData?.linesAdded > 0) {
546
+ const costPerLine = usageData.estimatedCost / gitData.linesAdded;
547
+ if (costPerLine > 0.1) {
548
+ insights.push(`每行代码成本 $${costPerLine.toFixed(3)},属于较高水平——通常意味着在做架构设计或复杂逻辑,而非批量生成。`);
549
+ } else if (costPerLine < 0.01 && gitData.linesAdded > 100) {
550
+ insights.push(`每行代码成本仅 $${costPerLine.toFixed(4)},AI 辅助的**批量生成效率很高**。`);
551
+ }
552
+ }
553
+
554
+ if (insights.length > 0) {
555
+ lines.push('');
556
+ for (const ins of insights) {
557
+ lines.push(`> ${ins}`);
558
+ }
559
+ }
560
+
561
+ lines.push('');
562
+ return lines;
563
+ }
564
+
565
+ /**
566
+ * 简报一句话结论——提炼本期最核心的结论
567
+ */
568
+ function buildBriefConclusion(usageData, gitData) {
569
+ const parts = [];
570
+
571
+ // 产出维度
572
+ if (gitData && gitData.commits > 0) {
573
+ const featCount = (gitData.commitList || []).filter(c => {
574
+ const s = (c.subject || c.message || '').toLowerCase();
575
+ return s.startsWith('feat') || s.startsWith('feature');
576
+ }).length;
577
+ const fixCount = (gitData.commitList || []).filter(c => {
578
+ const s = (c.subject || c.message || '').toLowerCase();
579
+ return s.startsWith('fix') || s.startsWith('bug');
580
+ }).length;
581
+
582
+ if (featCount > 0 && fixCount > 0) {
583
+ parts.push(`交付 ${featCount} 个新功能、修复 ${fixCount} 个缺陷`);
584
+ } else if (featCount > 0) {
585
+ parts.push(`交付 ${featCount} 个新功能`);
586
+ } else if (fixCount > 0) {
587
+ parts.push(`修复 ${fixCount} 个缺陷`);
588
+ }
589
+ }
590
+
591
+ // AI 参与维度
592
+ if (gitData?.aiContribution) {
593
+ const aiPct = Math.round(((gitData.aiContribution.aiLineRatio ?? gitData.aiContribution.aiRatio) || 0) * 100);
594
+ if (aiPct >= 80) {
595
+ parts.push('AI 深度参与开发(占比 ' + aiPct + '%)');
596
+ } else if (aiPct >= 50) {
597
+ parts.push('AI 辅助占比 ' + aiPct + '%');
598
+ }
599
+ }
600
+
601
+ // 成本维度
602
+ if (usageData.estimatedCost && usageData.estimatedCost > 0) {
603
+ if (usageData.activeDays > 0) {
604
+ const dailyCost = usageData.estimatedCost / usageData.activeDays;
605
+ if (dailyCost >= 50) {
606
+ parts.push('日均费用 $' + dailyCost.toFixed(0) + ',投入强度高');
607
+ } else if (dailyCost >= 10) {
608
+ parts.push('日均费用 $' + dailyCost.toFixed(0));
609
+ }
610
+ }
611
+ }
612
+
613
+ if (parts.length === 0) return null;
614
+ return '> ' + parts.join(',') + '。';
615
+ }
616
+
617
+ /**
618
+ * 简报环比方向性结论——从数据变化中提炼方向判断
619
+ */
620
+ function buildBriefDirection(usageData, prevData, gitData) {
621
+ if (!prevData) return null;
622
+
623
+ const conclusions = [];
624
+
625
+ // 交互效率:交互量变化 vs 产出变化
626
+ const reqPct = pctChange(usageData.requestCount, prevData.requestCount);
627
+ const costPct = pctChange(usageData.estimatedCost, prevData.estimatedCost);
628
+
629
+ // 如果交互量下降但成本上升 → 单次交互变贵了
630
+ if (reqPct !== null && costPct !== null) {
631
+ const reqDown = reqPct < -10;
632
+ const costUp = costPct > 10;
633
+ if (reqDown && costUp) {
634
+ conclusions.push('交互量下降但费用上升,单次交互成本增加——可能使用了更贵的大模型');
635
+ }
636
+ }
637
+
638
+ // 产出效率
639
+ if (gitData && gitData.commits > 0 && usageData.requestCount > 0) {
640
+ const commitsPerReq = gitData.commits / usageData.requestCount;
641
+ if (commitsPerReq >= 0.1) {
642
+ const reqsPerCommit = Math.round(1 / commitsPerReq);
643
+ const efficiency = reqsPerCommit <= 10 ? '良好' : reqsPerCommit <= 30 ? '中等' : '偏低';
644
+ conclusions.push(`每 ${reqsPerCommit} 次交互产出 1 次提交,交互-产出转化率${efficiency}`);
645
+ }
646
+ }
647
+
648
+ if (conclusions.length === 0) return null;
649
+ return conclusions.length > 0 ? conclusions.join(';') : null;
650
+ }
651
+
463
652
  function buildCostInsight(stats) {
464
653
  if (!stats.estimatedCost || stats.estimatedCost <= 0) return null;
465
654
  if (stats.activeDays > 0) {
@@ -470,6 +659,88 @@ function buildCostInsight(stats) {
470
659
  return null;
471
660
  }
472
661
 
662
+ /**
663
+ * 成本结构分析——按模型/按项目/按天的成本拆解
664
+ */
665
+ function buildCostStructureAnalysis(usageData) {
666
+ if (!usageData.estimatedCost || usageData.estimatedCost <= 0) return null;
667
+
668
+ const lines = [];
669
+
670
+ // ── 按模型拆解 ──
671
+ if (usageData.models && Object.keys(usageData.models).length > 0) {
672
+ const modelCosts = Object.entries(usageData.models)
673
+ .filter(([, m]) => (m.cost || 0) > 0)
674
+ .sort((a, b) => (b[1].cost || 0) - (a[1].cost || 0));
675
+
676
+ if (modelCosts.length > 0) {
677
+ lines.push('**费用构成(按模型)**:');
678
+ const totalCost = usageData.estimatedCost;
679
+ for (const [model, data] of modelCosts.slice(0, 5)) {
680
+ const cost = data.cost || 0;
681
+ const pct = Math.round((cost / totalCost) * 100);
682
+ const outputTokens = data.outputTokens || 0;
683
+ const outputK = outputTokens >= 1000 ? (outputTokens / 1000).toFixed(1) + 'K' : String(outputTokens);
684
+ lines.push(`- **${model}**:$${cost.toFixed(2)}(${pct}%,输出 ${outputK} Token)`);
685
+ }
686
+ // 集中度判断
687
+ if (modelCosts.length >= 2) {
688
+ const topPct = Math.round(((modelCosts[0][1].cost || 0) / totalCost) * 100);
689
+ if (topPct >= 80) {
690
+ lines.push(`> 费用高度集中在 ${modelCosts[0][0]}(${topPct}%),可评估是否有更经济的模型替代部分场景。`);
691
+ } else if (topPct >= 60) {
692
+ lines.push(`> ${modelCosts[0][0]} 占 ${topPct}% 费用,其余 ${modelCosts.length - 1} 个模型分担 ${100 - topPct}%。`);
693
+ }
694
+ }
695
+ lines.push('');
696
+ }
697
+ }
698
+
699
+ // ── 按项目拆解 ──
700
+ const projects = Object.entries(usageData.projects || {})
701
+ .filter(([, d]) => (d.estimatedCost || 0) > 0)
702
+ .sort((a, b) => (b[1].estimatedCost || 0) - (a[1].estimatedCost || 0));
703
+ if (projects.length > 1) {
704
+ lines.push('**费用分配(按项目)**:');
705
+ const totalCost = usageData.estimatedCost;
706
+ for (const [proj, data] of projects) {
707
+ const cost = data.estimatedCost || 0;
708
+ const pct = Math.round((cost / totalCost) * 100);
709
+ lines.push(`- **${simplifyPath(proj)}**:$${cost.toFixed(2)}(${pct}%)`);
710
+ }
711
+ lines.push('');
712
+ }
713
+
714
+ // ── 按天波动 ──
715
+ if (usageData.dailyStats && Object.keys(usageData.dailyStats).length >= 3) {
716
+ const dailyEntries = Object.entries(usageData.dailyStats)
717
+ .map(([date, ds]) => ({ date, cost: ds.estimatedCost || 0, requests: ds.requests || 0 }))
718
+ .filter(d => d.cost > 0)
719
+ .sort((a, b) => a.date.localeCompare(b.date));
720
+
721
+ if (dailyEntries.length >= 3) {
722
+ const avgCost = dailyEntries.reduce((s, d) => s + d.cost, 0) / dailyEntries.length;
723
+ const maxDay = dailyEntries.reduce((a, b) => a.cost > b.cost ? a : b);
724
+ const minDay = dailyEntries.reduce((a, b) => a.cost < b.cost ? a : b);
725
+ const volatility = avgCost > 0 ? ((maxDay.cost - minDay.cost) / avgCost * 100).toFixed(0) : 0;
726
+
727
+ lines.push('**费用波动**:');
728
+ lines.push(`- 日均 $${avgCost.toFixed(2)},最高 ${maxDay.date}($${maxDay.cost.toFixed(2)}),最低 ${minDay.date}($${minDay.cost.toFixed(2)}),波动幅度 ${volatility}%`);
729
+
730
+ if (parseFloat(volatility) > 200) {
731
+ lines.push('> 日费用波动极大,可能存在偶发性大批量任务。建议排查高峰日的具体工作内容,评估是否可以分散执行。');
732
+ } else if (parseFloat(volatility) > 100) {
733
+ lines.push('> 日费用波动较大,说明工作节奏不均匀。如果与工作日/周末模式相关,属于正常现象。');
734
+ } else {
735
+ lines.push('> 日费用波动适中,消费节奏较为平稳。');
736
+ }
737
+ lines.push('');
738
+ }
739
+ }
740
+
741
+ return lines.length > 0 ? lines : null;
742
+ }
743
+
473
744
  function buildDailyTrendInsight(dailyStats, period) {
474
745
  const dates = Object.keys(dailyStats).sort();
475
746
  if (dates.length < 2) return null;
@@ -791,21 +1062,25 @@ export function generateReport(usageData, gitData, period, startDate, endDate) {
791
1062
  lines.push('└─────────────────────────────────────┘');
792
1063
  lines.push('');
793
1064
 
794
- const totalToolCalls = Object.values(usageData.tools).reduce((s, v) => s + v, 0);
1065
+ const toolValues = Object.values(usageData.tools);
1066
+ const totalToolCalls = toolValues.reduce((s, v) => s + toolCalls(v), 0);
795
1067
  const toolTable = new Table({
796
1068
  columns: [
797
1069
  { title: '工具', width: 28 },
798
1070
  { title: '调用次数', width: 10 },
1071
+ { title: '使用次数', width: 10 },
799
1072
  { title: '占比', width: 8 },
800
1073
  ],
801
1074
  });
802
1075
 
803
1076
  const sortedTools = Object.entries(usageData.tools)
804
- .sort((a, b) => b[1] - a[1])
1077
+ .sort((a, b) => toolCalls(b[1]) - toolCalls(a[1]))
805
1078
  .slice(0, 10);
806
1079
 
807
- for (const [name, count] of sortedTools) {
808
- toolTable.addRow([name, formatInt(count), formatPercent(count, totalToolCalls)]);
1080
+ for (const [name, val] of sortedTools) {
1081
+ const calls = toolCalls(val);
1082
+ const uses = toolUses(val);
1083
+ toolTable.addRow([name, formatInt(calls), formatInt(uses), formatPercent(calls, totalToolCalls)]);
809
1084
  }
810
1085
  lines.push(toolTable.render());
811
1086
  lines.push('');
@@ -817,6 +1092,10 @@ export function generateReport(usageData, gitData, period, startDate, endDate) {
817
1092
  return lines.join('\n');
818
1093
  }
819
1094
 
1095
+ // ── 工具值类型兼容辅助 ──
1096
+ function toolCalls(v) { return typeof v === 'number' ? v : (v.calls || 0); }
1097
+ function toolUses(v) { return typeof v === 'number' ? v : (v.uses || 0); }
1098
+
820
1099
  function formatPeriodTitle(period, start, end) {
821
1100
  switch (period) {
822
1101
  case 'daily': return `日报 ${start}`;
@@ -965,6 +1244,13 @@ export function generateBriefReport(usageData, gitData, period, startDate, endDa
965
1244
 
966
1245
  const lines = [];
967
1246
 
1247
+ // ── 一句话结论(简报开头) ──
1248
+ const conclusionLine = buildBriefConclusion(usageData, gitData);
1249
+ if (conclusionLine) {
1250
+ lines.push(conclusionLine);
1251
+ lines.push('');
1252
+ }
1253
+
968
1254
  // 标题
969
1255
  if (isDingtalk) {
970
1256
  lines.push(`${titlePrefix} 工作${periodLabel} - ${dateLabel}`);
@@ -1088,7 +1374,7 @@ export function generateBriefReport(usageData, gitData, period, startDate, endDa
1088
1374
  lines.push('');
1089
1375
  }
1090
1376
 
1091
- // 5. 环比变化
1377
+ // 5. 环比变化(含方向性结论)
1092
1378
  if (prevData) {
1093
1379
  const changes = [];
1094
1380
  const reqPct = pctChange(usageData.requestCount, prevData.requestCount);
@@ -1103,6 +1389,12 @@ export function generateBriefReport(usageData, gitData, period, startDate, endDa
1103
1389
  lines.push(h2('环比变化'));
1104
1390
  lines.push('');
1105
1391
  lines.push(bullet(`相比${prevName},${changes.join('、')}`));
1392
+
1393
+ // 方向性结论
1394
+ const direction = buildBriefDirection(usageData, prevData, gitData);
1395
+ if (direction) {
1396
+ lines.push(bullet(direction));
1397
+ }
1106
1398
  lines.push('');
1107
1399
  }
1108
1400
  }
@@ -1170,6 +1462,371 @@ function toolTitle(tool) {
1170
1462
  return TOOL_LABELS[tool] || 'AI 编码助手';
1171
1463
  }
1172
1464
 
1465
+ /**
1466
+ * Boss 报告——给领导看的工作汇报
1467
+ *
1468
+ * 设计哲学:
1469
+ * 1. 凸显工作成果——交付了多少、做了什么
1470
+ * 2. 说管理者语言——不说技术黑话
1471
+ * 3. 不给自己挖坑——不暴露"AI干了97%"这种数据
1472
+ * 4. 费用包装为"技术工具投入"——工作日计算
1473
+ */
1474
+ export function generateBossReport(usageData, gitData, period, startDate, endDate, prevData, platform = 'default') {
1475
+ const periodName = period === 'daily' ? '今日' : period === 'weekly' ? '本周' : '本月';
1476
+ const dateLabel = period === 'monthly' ? startDate.slice(0, 7) : period === 'weekly' ? `${startDate} ~ ${endDate}` : startDate;
1477
+ const lines = [];
1478
+
1479
+ lines.push(`# 工作${periodName === '今日' ? '日报' : periodName === '本周' ? '周报' : '月报'} - ${dateLabel}`);
1480
+ lines.push('');
1481
+
1482
+ // ── 一、工作成果概述(领导第一眼看到的内容) ──
1483
+ const summary = buildBossSummary(usageData, gitData, periodName);
1484
+ lines.push(summary);
1485
+ lines.push('');
1486
+
1487
+ // ── 二、核心产出(具体做了什么) ──
1488
+ const output = buildBossOutput(usageData, gitData, periodName);
1489
+ if (output) {
1490
+ lines.push('## 本期工作内容');
1491
+ lines.push('');
1492
+ for (const l of output) lines.push(l);
1493
+ lines.push('');
1494
+ }
1495
+
1496
+ // ── 三、工作强度(凸工作态度) ──
1497
+ const intensity = buildBossIntensity(usageData, periodName);
1498
+ if (intensity) {
1499
+ lines.push('## 工作投入');
1500
+ lines.push('');
1501
+ for (const l of intensity) lines.push(l);
1502
+ lines.push('');
1503
+ }
1504
+
1505
+ // ── 四、环比亮点(挑好的说) ──
1506
+ if (prevData) {
1507
+ const comparison = buildBossComparison(usageData, prevData, gitData, periodName);
1508
+ if (comparison) {
1509
+ lines.push('## 工作对比');
1510
+ lines.push('');
1511
+ for (const l of comparison) lines.push(l);
1512
+ lines.push('');
1513
+ }
1514
+ }
1515
+
1516
+ // ── 五、技术工具投入(费用用工作日算,包装为投入) ──
1517
+ const cost = buildBossCost(usageData, periodName);
1518
+ if (cost) {
1519
+ lines.push('## 技术工具投入');
1520
+ lines.push('');
1521
+ for (const l of cost) lines.push(l);
1522
+ lines.push('');
1523
+ }
1524
+
1525
+ const result = lines.join('\n').trim();
1526
+
1527
+ // 平台适配
1528
+ if (platform === 'dingtalk') return adaptDingtalkBoss(result);
1529
+ if (platform === 'feishu') return adaptFeishuBoss(result);
1530
+ return result;
1531
+ }
1532
+
1533
+ /**
1534
+ * 钉钉 Boss 报告适配——纯文本 + emoji 分隔,适合群消息
1535
+ */
1536
+ function adaptDingtalkBoss(text) {
1537
+ return text
1538
+ .replace(/^# (.+)$/gm, '$1')
1539
+ .replace(/^## (.+)$/gm, '\n─── $1 ───')
1540
+ .replace(/\*\*(.+?)\*\*/g, '$1')
1541
+ .replace(/`([^`]+)`/g, '"$1"')
1542
+ .replace(/^> (.+)$/gm, '$1')
1543
+ .trim();
1544
+ }
1545
+
1546
+ /**
1547
+ * 飞书 Boss 报告适配——保留 Markdown(飞书支持良好)
1548
+ */
1549
+ function adaptFeishuBoss(text) {
1550
+ // 飞书支持标准 Markdown,只需确保格式兼容
1551
+ return text.trim();
1552
+ }
1553
+
1554
+ function buildBossSummary(usageData, gitData, periodName) {
1555
+ const parts = [];
1556
+
1557
+ // 产出维度
1558
+ if (gitData && gitData.commits > 0) {
1559
+ const featCount = (gitData.commitList || []).filter(c => {
1560
+ const s = (c.subject || c.message || '').toLowerCase();
1561
+ return s.startsWith('feat') || s.startsWith('feature');
1562
+ }).length;
1563
+ const fixCount = (gitData.commitList || []).filter(c => {
1564
+ const s = (c.subject || c.message || '').toLowerCase();
1565
+ return s.startsWith('fix') || s.startsWith('bug');
1566
+ }).length;
1567
+ const refactorCount = (gitData.commitList || []).filter(c => {
1568
+ const s = (c.subject || c.message || '').toLowerCase();
1569
+ return s.startsWith('refactor');
1570
+ }).length;
1571
+ const perfCount = (gitData.commitList || []).filter(c => {
1572
+ const s = (c.subject || c.message || '').toLowerCase();
1573
+ return s.startsWith('perf');
1574
+ }).length;
1575
+
1576
+ const items = [];
1577
+ if (featCount > 0) items.push(`完成 ${featCount} 项功能开发`);
1578
+ if (fixCount > 0) items.push(`解决 ${fixCount} 个问题`);
1579
+ if (refactorCount > 0) items.push(`优化 ${refactorCount} 处代码结构`);
1580
+ if (perfCount > 0) items.push(`提升 ${perfCount} 处性能`);
1581
+ if (items.length > 0) parts.push(items.join(','));
1582
+
1583
+ // AI 协同效率——包装为"善用 AI 工具"
1584
+ if (gitData.aiContribution) {
1585
+ const aiRatio = gitData.aiContribution.aiLineRatio ?? gitData.aiContribution.aiRatio;
1586
+ if (aiRatio > 0) {
1587
+ const aiPct = Math.round(aiRatio * 100);
1588
+ if (aiPct >= 80) {
1589
+ parts.push('AI 辅助编码深度协同,开发效率显著提升');
1590
+ } else if (aiPct >= 50) {
1591
+ parts.push('充分运用 AI 辅助工具,人机协作高效');
1592
+ } else {
1593
+ parts.push('善用 AI 工具辅助编码');
1594
+ }
1595
+ }
1596
+ }
1597
+ } else {
1598
+ // 无 Git 数据时用交互量代替
1599
+ const reqCount = usageData.requestCount || 0;
1600
+ if (reqCount > 0) {
1601
+ parts.push(`累计进行 ${fmtN(reqCount)} 次 AI 辅助操作`);
1602
+ }
1603
+ }
1604
+
1605
+ // 项目维度
1606
+ const projects = Object.entries(usageData.projects || {}).filter(([, d]) => d.requests > 0);
1607
+ if (projects.length > 1) {
1608
+ parts.push(`覆盖 ${projects.length} 个项目`);
1609
+ }
1610
+
1611
+ // 态度维度
1612
+ if (usageData.activeDays > 0) {
1613
+ const totalDays = periodName === '今日' ? 1 : periodName === '本周' ? 7 : 30;
1614
+ const coverage = Math.round((usageData.activeDays / totalDays) * 100);
1615
+ if (coverage >= 70) {
1616
+ parts.push('保持高效持续产出');
1617
+ } else if (usageData.activeDays >= 5) {
1618
+ parts.push('工作节奏紧凑');
1619
+ }
1620
+ }
1621
+
1622
+ if (parts.length === 0) return `${periodName}使用 AI 辅助工具进行日常工作。`;
1623
+ return `${periodName}${parts.join(',')}。`;
1624
+ }
1625
+
1626
+ /**
1627
+ * Boss 报告的工作内容——只展示摘要数字和分类,不暴露 commit message
1628
+ */
1629
+ function buildBossOutput(usageData, gitData, periodName) {
1630
+ if (!gitData || gitData.commits === 0) return null;
1631
+ const lines = [];
1632
+
1633
+ // 数字概要——说管理者听得懂的
1634
+ const linesAdded = gitData.linesAdded || 0;
1635
+ const linesDeleted = gitData.linesDeleted || 0;
1636
+ const filesChanged = gitData.filesChanged || 0;
1637
+ const commits = gitData.commits || 0;
1638
+
1639
+ lines.push(`本期共提交 ${commits} 次代码变更,涉及 ${filesChanged} 个文件,新增 ${fmtN(linesAdded)} 行代码。`);
1640
+ lines.push('');
1641
+
1642
+ // 分类摘要——只说做了几类事,不说具体 commit
1643
+ const commitList = gitData.commitList || [];
1644
+ const categories = {
1645
+ '功能开发': c => { const s = (c.subject || c.message || '').toLowerCase(); return s.startsWith('feat') || s.startsWith('feature'); },
1646
+ '问题修复': c => { const s = (c.subject || c.message || '').toLowerCase(); return s.startsWith('fix') || s.startsWith('bug'); },
1647
+ '代码优化': c => { const s = (c.subject || c.message || '').toLowerCase(); return s.startsWith('refactor') || s.startsWith('perf'); },
1648
+ '文档更新': c => { const s = (c.subject || c.message || '').toLowerCase(); return s.startsWith('doc'); },
1649
+ '测试完善': c => { const s = (c.subject || c.message || '').toLowerCase(); return s.startsWith('test'); },
1650
+ '工程维护': c => { const s = (c.subject || c.message || '').toLowerCase(); return s.startsWith('chore') || s.startsWith('ci') || s.startsWith('build') || s.startsWith('release'); },
1651
+ };
1652
+
1653
+ const catResults = {};
1654
+ for (const [cat, fn] of Object.entries(categories)) {
1655
+ const items = commitList.filter(fn);
1656
+ if (items.length > 0) catResults[cat] = items.length;
1657
+ }
1658
+ // 未分类的
1659
+ const classified = Object.values(categories).flatMap(fn => commitList.filter(fn));
1660
+ const unclassified = commitList.length - new Set(classified).size;
1661
+ if (unclassified > 0) catResults['其他改进'] = unclassified;
1662
+
1663
+ if (Object.keys(catResults).length > 0) {
1664
+ lines.push('**工作内容分类**:');
1665
+ for (const [cat, count] of Object.entries(catResults)) {
1666
+ lines.push(`- ${cat}:${count} 项`);
1667
+ }
1668
+ lines.push('');
1669
+ }
1670
+
1671
+ // 净代码增长——突出正面的
1672
+ const netLines = linesAdded - linesDeleted;
1673
+ if (netLines > 0) {
1674
+ lines.push(`> 净增代码 ${fmtN(netLines)} 行,项目功能持续扩展。`);
1675
+ } else if (linesAdded > 0) {
1676
+ lines.push(`> 代码变更以优化重构为主,代码库更加精简。`);
1677
+ }
1678
+
1679
+ // AI 协同效率——在产出板块结尾展示
1680
+ if (gitData.aiContribution) {
1681
+ const ai = gitData.aiContribution;
1682
+ const aiLineRatio = ai.aiLineRatio ?? ai.aiRatio;
1683
+ if (aiLineRatio > 0) {
1684
+ const aiPct = Math.round(aiLineRatio * 100);
1685
+ const aiCommits = ai.aiCommits || 0;
1686
+ const totalCommits = gitData.commits || 1;
1687
+ const commitPct = Math.round((aiCommits / totalCommits) * 100);
1688
+ lines.push('');
1689
+ lines.push(`**AI 辅助编码效率**:${aiPct}% 的代码由 AI 辅助生成(涉及 ${aiCommits}/${totalCommits} 次提交),显著提升开发迭代速度。`);
1690
+ }
1691
+ }
1692
+
1693
+ return lines;
1694
+ }
1695
+
1696
+ /**
1697
+ * Boss 报告的工作投入——凸工作态度,不凸技术细节
1698
+ */
1699
+ function buildBossIntensity(usageData, periodName) {
1700
+ const lines = [];
1701
+ const totalDays = periodName === '今日' ? 1 : periodName === '本周' ? 7 : 30;
1702
+ const activeDays = usageData.activeDays || 0;
1703
+
1704
+ if (activeDays > 0) {
1705
+ const coverage = Math.round((activeDays / totalDays) * 100);
1706
+
1707
+ // 用项目维度说事
1708
+ const projects = Object.entries(usageData.projects || {}).filter(([, d]) => d.requests > 0);
1709
+ const projNames = projects.map(([p]) => simplifyPath(p));
1710
+
1711
+ if (periodName === '今日') {
1712
+ // 日报用简洁措辞
1713
+ if (projNames.length === 1) {
1714
+ lines.push(`全天专注于 **${projNames[0]}** 项目开发。`);
1715
+ } else if (projNames.length > 1) {
1716
+ lines.push(`全天推进 ${projNames.join('、')} 等项目。`);
1717
+ }
1718
+ } else if (projNames.length === 1) {
1719
+ lines.push(`持续 ${activeDays} 天在 **${projNames[0]}** 项目上集中投入。`);
1720
+ } else if (projNames.length > 1) {
1721
+ lines.push(`持续 ${activeDays} 天保持活跃,同时推进 ${projNames.join('、')} 等项目。`);
1722
+ } else {
1723
+ lines.push(`${periodName}共 ${activeDays} 天有工作产出。`);
1724
+ }
1725
+
1726
+ // 工作节奏描述(仅周报/月报)
1727
+ if (periodName !== '今日' && usageData.dailyStats) {
1728
+ const dailyEntries = Object.entries(usageData.dailyStats).filter(([, ds]) => (ds.requests || 0) > 0);
1729
+ if (dailyEntries.length >= 3) {
1730
+ // 连续活跃天数
1731
+ const dates = dailyEntries.map(([d]) => d).sort();
1732
+ let maxStreak = 1, streak = 1;
1733
+ for (let i = 1; i < dates.length; i++) {
1734
+ const prev = new Date(dates[i - 1]);
1735
+ const curr = new Date(dates[i]);
1736
+ const diff = (curr - prev) / (1000 * 60 * 60 * 24);
1737
+ if (diff <= 1.5) { streak++; maxStreak = Math.max(maxStreak, streak); }
1738
+ else { streak = 1; }
1739
+ }
1740
+ if (maxStreak >= 5) {
1741
+ lines.push(`最长连续工作 ${maxStreak} 天,工作节奏稳定。`);
1742
+ }
1743
+ }
1744
+ }
1745
+
1746
+ // 覆盖率用工作态度的语言说(仅周报/月报)
1747
+ if (periodName !== '今日') {
1748
+ if (coverage >= 80) {
1749
+ lines.push(`> 工作覆盖率 ${coverage}%,全程高投入。`);
1750
+ } else if (coverage >= 50) {
1751
+ lines.push(`> 工作覆盖率 ${coverage}%,保持稳定产出节奏。`);
1752
+ }
1753
+ }
1754
+
1755
+ lines.push('');
1756
+ }
1757
+
1758
+ return lines.length > 0 ? lines : null;
1759
+ }
1760
+
1761
+ /**
1762
+ * Boss 报告的环比对比——挑好的说,不好的用正面语言
1763
+ */
1764
+ function buildBossComparison(usageData, prevData, gitData, periodName) {
1765
+ if (!prevData) return null;
1766
+ const lines = [];
1767
+ const prevName = periodName === '今日' ? '昨日' : periodName === '本周' ? '上周' : '上月';
1768
+
1769
+ const reqPct = pctChange(usageData.requestCount, prevData.requestCount);
1770
+ const costPct = pctChange(usageData.estimatedCost, prevData.estimatedCost);
1771
+
1772
+ const parts = [];
1773
+
1774
+ // 交互量——下降不一定是坏事,可以用"效率"包装
1775
+ if (reqPct !== null) {
1776
+ if (reqPct > 0) {
1777
+ parts.push(`工作强度提升 ${reqPct}%`);
1778
+ } else if (reqPct > -15) {
1779
+ parts.push('工作节奏保持稳定');
1780
+ }
1781
+ // 下降超过15%就不提交互量了,跳过
1782
+ }
1783
+
1784
+ // 费用——如果上升了但交互量也上升,说明是工作量增加
1785
+ if (costPct !== null && costPct > 0 && reqPct !== null && reqPct > 0) {
1786
+ // 费用上升但交互量也上升——说明工作量增加带动
1787
+ parts.push('投入随工作量同步增长');
1788
+ }
1789
+ // 费用下降或持平——不提
1790
+
1791
+ if (parts.length > 0) {
1792
+ lines.push(`相比${prevName},${parts.join(',')}。`);
1793
+ } else {
1794
+ lines.push(`与${prevName}相比,工作保持稳定推进。`);
1795
+ }
1796
+
1797
+ // 效率洞察——如果产出不变但交互减少
1798
+ if (gitData && gitData.commits > 0 && reqPct !== null && reqPct < 0) {
1799
+ lines.push('> 在工作量调整的同时,持续保持代码产出,工作效率稳定。');
1800
+ }
1801
+
1802
+ return lines.length > 0 ? lines : null;
1803
+ }
1804
+
1805
+ /**
1806
+ * Boss 报告的费用——工作日计算,包装为"技术工具投入"
1807
+ */
1808
+ function buildBossCost(usageData, periodName) {
1809
+ if (!usageData.estimatedCost || usageData.estimatedCost <= 0) return null;
1810
+ const lines = [];
1811
+
1812
+ const activeDays = usageData.activeDays || 1;
1813
+ const dailyCost = usageData.estimatedCost / activeDays;
1814
+
1815
+ lines.push(`本期技术工具投入约 **$${usageData.estimatedCost.toFixed(0)}**(日均 $${dailyCost.toFixed(0)}),用于 AI 辅助编码、代码分析等研发提效工具。`);
1816
+ lines.push('');
1817
+
1818
+ // 工作日计算月度预估——不把周末算进去
1819
+ if (periodName !== '今日') {
1820
+ const workdays = Math.min(activeDays, periodName === '本周' ? 5 : 22);
1821
+ const workdayDaily = usageData.estimatedCost / workdays;
1822
+ const monthlyEst = workdayDaily * 22; // 按每月22个工作日算
1823
+ lines.push(`按工作日折算,月度工具预算约 $${monthlyEst.toFixed(0)}。`);
1824
+ lines.push('');
1825
+ }
1826
+
1827
+ return lines;
1828
+ }
1829
+
1173
1830
  export function generateWorkReport(usageData, gitData, period, startDate, endDate, prevData, options) {
1174
1831
  // 向后兼容:options 可以是字符串(旧 platform 参数)
1175
1832
  const opts = typeof options === 'string'
@@ -1353,6 +2010,14 @@ export function generateWorkReport(usageData, gitData, period, startDate, endDat
1353
2010
  sections.push({ title: '代码产出', lines: sectionLines });
1354
2011
  }
1355
2012
 
2013
+ // 板块:效率指标
2014
+ {
2015
+ const effLines = buildEfficiencyMetrics(usageData, gitData);
2016
+ if (effLines) {
2017
+ sections.push({ title: '效率指标', lines: effLines });
2018
+ }
2019
+ }
2020
+
1356
2021
  // 板块:成本与效率
1357
2022
  if (usageData.estimatedCost) {
1358
2023
  const sectionLines = [];
@@ -1386,6 +2051,16 @@ export function generateWorkReport(usageData, gitData, period, startDate, endDat
1386
2051
  sectionLines.push(`- ${s}`);
1387
2052
  }
1388
2053
  }
2054
+
2055
+ // 成本结构分析
2056
+ const costStructure = buildCostStructureAnalysis(usageData);
2057
+ if (costStructure) {
2058
+ sectionLines.push('');
2059
+ for (const l of costStructure) {
2060
+ sectionLines.push(l);
2061
+ }
2062
+ }
2063
+
1389
2064
  sectionLines.push('');
1390
2065
  const costInsight = buildCostInsight(usageData);
1391
2066
  if (costInsight) {
@@ -1395,54 +2070,10 @@ export function generateWorkReport(usageData, gitData, period, startDate, endDat
1395
2070
  sections.push({ title: '成本与效率', lines: sectionLines });
1396
2071
  }
1397
2072
 
1398
- // 板块:工具使用模式
1399
- if (usageData.tools && Object.keys(usageData.tools).length > 0) {
1400
- const sectionLines = [];
1401
- const sortedTools = Object.entries(usageData.tools).sort((a, b) => b[1] - a[1]);
1402
- const totalCalls = sortedTools.reduce((s, [, v]) => s + v, 0) || 1;
1403
-
1404
- // 分类统计
1405
- const categories = { '代码编辑': 0, '代码阅读': 0, '执行/运行': 0, '规划管理': 0, '搜索研究': 0, '其他': 0 };
1406
- const TOOL_CATEGORIES = {
1407
- Write: '代码编辑', Edit: '代码编辑', NotebookEdit: '代码编辑', replace_symbol_body: '代码编辑',
1408
- replace_content: '代码编辑', insert_before_symbol: '代码编辑', insert_after_symbol: '代码编辑',
1409
- write: '代码编辑', edit: '代码编辑',
1410
- Read: '代码阅读', Glob: '代码阅读', Grep: '代码阅读', find_symbol: '代码阅读',
1411
- find_declaration: '代码阅读', find_referencing_symbols: '代码阅读', find_implementations: '代码阅读',
1412
- get_symbols_overview: '代码阅读', initial_instructions: '代码阅读', get_diagnostics_for_file: '代码阅读',
1413
- glob: '代码阅读', read: '代码阅读',
1414
- Bash: '执行/运行', shell_command: '执行/运行', bash: '执行/运行',
1415
- TaskCreate: '规划管理', TaskUpdate: '规划管理', TaskList: '规划管理', Agent: '规划管理',
1416
- EnterPlanMode: '规划管理', ExitPlanMode: '规划管理', update_plan: '规划管理', todowrite: '规划管理',
1417
- WebSearch: '搜索研究', WebFetch: '搜索研究', view_image: '代码阅读',
1418
- };
1419
-
1420
- for (const [name, count] of sortedTools) {
1421
- let cat = TOOL_CATEGORIES[name];
1422
- if (!cat) {
1423
- // MCP 工具按前缀分类
1424
- if (name.startsWith('mcp__Playwright') || name.startsWith('browser_')) cat = '执行/运行';
1425
- else if (name.startsWith('mcp__context7') || name.startsWith('mcp__open-websearch') || name.startsWith('mcp__mcp-deepwiki') || name.startsWith('mcp__web_reader')) cat = '搜索研究';
1426
- else if (name.startsWith('mcp__serena')) cat = '代码编辑';
1427
- else cat = '其他';
1428
- }
1429
- categories[cat] += count;
1430
- }
1431
-
1432
- const activeCats = Object.entries(categories).filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]);
1433
- for (const [cat, count] of activeCats) {
1434
- const pct = Math.round((count / totalCalls) * 100);
1435
- sectionLines.push(`- **${cat}**:${pct}%(${formatInt(count)} 次)`);
1436
- }
1437
-
1438
- // Top 3 工具
1439
- const top3 = sortedTools.slice(0, 3);
1440
- if (top3.length > 0) {
1441
- sectionLines.push('');
1442
- sectionLines.push(`> 使用最频繁的工具:${top3.map(([n, c]) => `${n}(${formatInt(c)} 次)`).join('、')}。`);
1443
- }
1444
- sectionLines.push('');
1445
- sections.push({ title: '工具使用模式', lines: sectionLines });
2073
+ // 板块:工具使用与分析(合并模式展示 + 深度分析)
2074
+ const toolSectionLines = buildToolAnalysis(usageData);
2075
+ if (toolSectionLines) {
2076
+ sections.push({ title: '工具使用分析', lines: toolSectionLines });
1446
2077
  }
1447
2078
 
1448
2079
  // 统一渲染板块,动态编号
@@ -1459,6 +2090,291 @@ export function generateWorkReport(usageData, gitData, period, startDate, endDat
1459
2090
  return adaptPlatformOutput(lines.join('\n'), fmt);
1460
2091
  }
1461
2092
 
2093
+ /**
2094
+ * 工具使用数据分析——从数据中提炼现象、规律和洞察
2095
+ */
2096
+ function buildToolAnalysis(usageData) {
2097
+ if (!usageData.tools || Object.keys(usageData.tools).length === 0) return null;
2098
+
2099
+ const lines = [];
2100
+
2101
+ // ── 基础数据准备 ──
2102
+ const sortedTools = Object.entries(usageData.tools).sort((a, b) => toolCalls(b[1]) - toolCalls(a[1]));
2103
+ const totalCalls = sortedTools.reduce((s, [, v]) => s + toolCalls(v), 0) || 1;
2104
+ const uniqueToolCount = sortedTools.length;
2105
+
2106
+ // ── 样本量校验 ──
2107
+ const lowSample = totalCalls < 20;
2108
+ const veryLowSample = totalCalls < 5;
2109
+ if (veryLowSample) {
2110
+ lines.push(`> 样本量过少(仅 ${totalCalls} 次工具调用),以下分析仅供参考,统计结论可靠性有限。`);
2111
+ lines.push('');
2112
+ } else if (lowSample) {
2113
+ lines.push(`> 样本量较少(${totalCalls} 次工具调用),分析结论的统计意义有限。`);
2114
+ lines.push('');
2115
+ }
2116
+
2117
+ // ── 分类统计 ──
2118
+ const TOOL_CATEGORIES = {
2119
+ Write: '代码编辑', Edit: '代码编辑', NotebookEdit: '代码编辑', replace_symbol_body: '代码编辑',
2120
+ replace_content: '代码编辑', insert_before_symbol: '代码编辑', insert_after_symbol: '代码编辑',
2121
+ write: '代码编辑', edit: '代码编辑',
2122
+ Read: '代码阅读', Glob: '代码阅读', Grep: '代码阅读', find_symbol: '代码阅读',
2123
+ find_declaration: '代码阅读', find_referencing_symbols: '代码阅读', find_implementations: '代码阅读',
2124
+ get_symbols_overview: '代码阅读', initial_instructions: '代码阅读', get_diagnostics_for_file: '代码阅读',
2125
+ glob: '代码阅读', read: '代码阅读',
2126
+ Bash: '执行/运行', shell_command: '执行/运行', bash: '执行/运行',
2127
+ TaskCreate: '规划管理', TaskUpdate: '规划管理', TaskList: '规划管理', Agent: '规划管理',
2128
+ EnterPlanMode: '规划管理', ExitPlanMode: '规划管理', update_plan: '规划管理', todowrite: '规划管理',
2129
+ WebSearch: '搜索研究', WebFetch: '搜索研究', view_image: '代码阅读',
2130
+ };
2131
+ const catCalls = { '代码编辑': 0, '代码阅读': 0, '执行/运行': 0, '规划管理': 0, '搜索研究': 0, '其他': 0 };
2132
+
2133
+ for (const [name, val] of sortedTools) {
2134
+ const calls = toolCalls(val);
2135
+ let cat = TOOL_CATEGORIES[name];
2136
+ if (!cat) {
2137
+ if (name.startsWith('mcp__Playwright') || name.startsWith('browser_')) cat = '执行/运行';
2138
+ else if (name.startsWith('mcp__context7') || name.startsWith('mcp__open-websearch') || name.startsWith('mcp__mcp-deepwiki') || name.startsWith('mcp__web_reader')) cat = '搜索研究';
2139
+ else if (name.startsWith('mcp__serena')) cat = '代码编辑';
2140
+ else cat = '其他';
2141
+ }
2142
+ catCalls[cat] += calls;
2143
+ }
2144
+
2145
+ const activeCats = Object.entries(catCalls).filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]);
2146
+ if (activeCats.length === 0) return null;
2147
+
2148
+ // ══════════════════════════════════════
2149
+ // 零、分类概览(原"工具使用模式"的数据展示)
2150
+ // ══════════════════════════════════════
2151
+ for (const [cat, count] of activeCats) {
2152
+ const rawPct = (count / totalCalls) * 100;
2153
+ const pct = rawPct < 1 ? '<1' : Math.round(rawPct);
2154
+ lines.push(`- **${cat}**:${pct}%(${formatInt(count)} 次)`);
2155
+ }
2156
+
2157
+ // Top 3 工具
2158
+ const top3 = sortedTools.slice(0, 3);
2159
+ if (top3.length > 0) {
2160
+ lines.push('');
2161
+ lines.push(`> 使用最频繁的工具:${top3.map(([n, val]) => {
2162
+ const c = toolCalls(val);
2163
+ return `${n}(${formatInt(c)} 次)`;
2164
+ }).join('、')}。`);
2165
+ }
2166
+ lines.push('');
2167
+
2168
+ // ══════════════════════════════════════
2169
+ // 一、工作模式判断
2170
+ // ══════════════════════════════════════
2171
+ const patterns = [];
2172
+ const catPcts = {};
2173
+ for (const [cat, count] of activeCats) {
2174
+ catPcts[cat] = Math.round((count / totalCalls) * 100);
2175
+ }
2176
+
2177
+ // 判断主导工作模式
2178
+ const dominant = activeCats[0];
2179
+ const dominantPct = catPcts[dominant[0]];
2180
+
2181
+ if (catPcts['代码编辑'] >= 40) {
2182
+ patterns.push('以**深度开发**为主——大量使用代码编辑类工具,处于密集编码阶段');
2183
+ } else if (catPcts['代码阅读'] >= 35) {
2184
+ patterns.push('以**代码审查与理解**为主——大量阅读代码,可能在熟悉代码库或进行 code review');
2185
+ } else if (catPcts['搜索研究'] >= 30) {
2186
+ patterns.push('以**调研探索**为主——频繁使用搜索和文档工具,处于技术调研或方案选型阶段');
2187
+ } else if (catPcts['执行/运行'] >= 25) {
2188
+ patterns.push('以**测试验证**为主——频繁执行命令,可能在调试、运行测试或部署验证');
2189
+ } else if (catPcts['规划管理'] >= 25) {
2190
+ patterns.push('以**任务规划与协调**为主——大量使用任务管理和子代理工具,处于项目管理或任务拆解阶段');
2191
+ }
2192
+
2193
+ // 多模式叠加判断(作为对主模式的补充,避免与主模式矛盾)
2194
+ const mainMode = patterns[0] || '';
2195
+ if (catPcts['代码编辑'] >= 25 && catPcts['代码阅读'] >= 20) {
2196
+ patterns.push('编辑与阅读比例均衡(编辑 ' + catPcts['代码编辑'] + '% : 阅读 ' + catPcts['代码阅读'] + '%),体现出**边读边改**的渐进式开发风格');
2197
+ }
2198
+ if (catPcts['执行/运行'] >= 30 && catPcts['代码阅读'] >= 20 && !mainMode.includes('代码审查')) {
2199
+ patterns.push('执行与阅读联动(执行 ' + catPcts['执行/运行'] + '% : 阅读 ' + catPcts['代码阅读'] + '%),说明在**边查边试**——先阅读理解代码,再执行命令验证');
2200
+ }
2201
+ if (catPcts['代码编辑'] >= 15 && catPcts['执行/运行'] >= 30) {
2202
+ patterns.push('编辑占比 ' + catPcts['代码编辑'] + '% 配合 ' + catPcts['执行/运行'] + '% 的执行量,体现出**快速迭代、频繁验证**的开发节奏');
2203
+ }
2204
+ if (catPcts['搜索研究'] >= 15 && catPcts['代码编辑'] >= 15) {
2205
+ patterns.push('搜索与编辑联动(搜索 ' + catPcts['搜索研究'] + '% : 编辑 ' + catPcts['代码编辑'] + '%),说明在**边查文档边编码**');
2206
+ }
2207
+
2208
+ if (patterns.length === 0 && dominantPct < 40) {
2209
+ patterns.push('工具使用分布较为**均匀多元**,属于**全栈协作模式**——各类工具交替使用,工作节奏多样');
2210
+ }
2211
+
2212
+ lines.push('**工作模式**:' + patterns[0]);
2213
+ for (let i = 1; i < patterns.length; i++) {
2214
+ lines.push('');
2215
+ lines.push('> ' + patterns[i]);
2216
+ }
2217
+
2218
+ // ══════════════════════════════════════
2219
+ // 二、迭代度分析(Calls / Uses 比率)
2220
+ // ══════════════════════════════════════
2221
+ lines.push('');
2222
+ lines.push('**迭代度分析**:');
2223
+
2224
+ const iterativeTools = [];
2225
+ for (const [name, val] of sortedTools) {
2226
+ const calls = toolCalls(val);
2227
+ const uses = toolUses(val);
2228
+ if (uses > 0 && calls > uses * 1.5 && calls >= 5) {
2229
+ iterativeTools.push({ name, calls, uses, ratio: (calls / uses).toFixed(1) });
2230
+ }
2231
+ }
2232
+ iterativeTools.sort((a, b) => b.ratio - a.ratio);
2233
+
2234
+ if (iterativeTools.length === 0) {
2235
+ lines.push('各工具的调用/使用比接近 1:1,说明工具调用**目标明确**,较少在同一轮对话中重复调用同一工具。');
2236
+ } else {
2237
+ const topIterative = iterativeTools.slice(0, 3);
2238
+ const insights = topIterative.map(t => {
2239
+ const meaning = getIterativeInsight(t.name, t.ratio);
2240
+ return `**${t.name}**(调用 ${formatInt(t.calls)} 次 / 使用 ${formatInt(t.uses)} 次,比率 ${t.ratio}×)——${meaning}`;
2241
+ });
2242
+ for (const ins of insights) {
2243
+ lines.push('- ' + ins);
2244
+ }
2245
+
2246
+ // 综合判断
2247
+ const hasHighIteration = iterativeTools.some(t => parseFloat(t.ratio) >= 3);
2248
+ if (hasHighIteration) {
2249
+ lines.push('');
2250
+ lines.push('> 存在高迭代工具(比率 ≥ 3×),整体工作风格偏向**渐进式打磨**——在单次交互中反复调整,直到达到预期效果。');
2251
+ } else {
2252
+ lines.push('');
2253
+ lines.push('> 迭代倍率适中(均 < 3×),说明虽然存在重复调用,但**收敛速度较快**,每次调整的方向性较强。');
2254
+ }
2255
+ }
2256
+
2257
+ // ══════════════════════════════════════
2258
+ // 三、集中度与多样性
2259
+ // ══════════════════════════════════════
2260
+ lines.push('');
2261
+ lines.push('**工具分布特征**:');
2262
+
2263
+ const top1Pct = Math.round((toolCalls(sortedTools[0][1]) / totalCalls) * 100);
2264
+ const top3Calls = sortedTools.slice(0, Math.min(3, sortedTools.length)).reduce((s, [, v]) => s + toolCalls(v), 0);
2265
+ const top3Pct = Math.round((top3Calls / totalCalls) * 100);
2266
+
2267
+ if (top1Pct >= 50) {
2268
+ lines.push(`最常用工具 **${sortedTools[0][0]}** 占比达 ${top1Pct}%,存在明显的**单工具依赖**现象。`);
2269
+ lines.push(`> 高度集中在单一工具上,可能意味着工作内容比较单一,或该工具承载了过多的操作类型。建议关注是否有更细粒度的工具可以替代部分操作。`);
2270
+ } else if (top3Pct >= 70) {
2271
+ lines.push(`前 3 个工具合计占比 ${top3Pct}%(其中 ${sortedTools[0][0]} ${top1Pct}%),呈现**适度集中**的分布。`);
2272
+ lines.push(`> 核心工具集明确,同时保留了足够的工具多样性。这是一个健康的工作模式——核心流程稳定,辅助工具按需调用。`);
2273
+ } else if (top3Pct >= 50) {
2274
+ lines.push(`前 3 个工具合计占比 ${top3Pct}%,分布**相对分散**,使用了 ${uniqueToolCount} 种不同的工具。`);
2275
+ lines.push(`> 工具使用面较广,说明工作内容**多样化**——不局限于单一操作类型,覆盖了开发链路的多个环节。`);
2276
+ } else {
2277
+ lines.push(`工具分布**高度分散**,前 3 个工具仅占 ${top3Pct}%,共使用了 ${uniqueToolCount} 种工具。`);
2278
+ lines.push(`> 极其多元的工具使用,说明工作内容复杂度高,涉及多个维度的操作。这也可能意味着任务类型跨度较大。`);
2279
+ }
2280
+
2281
+ // ══════════════════════════════════════
2282
+ // 四、关键发现
2283
+ // ══════════════════════════════════════
2284
+ const findings = [];
2285
+
2286
+ // 发现1:编辑/阅读比率
2287
+ const editCalls = catCalls['代码编辑'];
2288
+ const readCalls = catCalls['代码阅读'];
2289
+ if (editCalls > 0 && readCalls > 0) {
2290
+ const editReadRatio = editCalls / readCalls;
2291
+ if (editReadRatio >= 3) {
2292
+ findings.push('编辑/阅读比达 **' + editReadRatio.toFixed(1) + ':1**,远高于均衡值。说明对代码库已有较高熟悉度,以"写"为主——可能处于功能快速落地阶段。');
2293
+ } else if (editReadRatio >= 1.5) {
2294
+ findings.push('编辑/阅读比约 **' + editReadRatio.toFixed(1) + ':1**,略偏编辑。说明在"先理解再修改"的节奏中,修改动作更加频繁,属于**高效迭代**的工作状态。');
2295
+ } else if (editReadRatio >= 0.7) {
2296
+ findings.push('编辑/阅读比约 **' + editReadRatio.toFixed(1) + ':1**,接近均衡。体现出**审慎的工程习惯**——先充分理解再动手修改,降低出错风险。');
2297
+ } else {
2298
+ findings.push('编辑/阅读比仅 **' + editReadRatio.toFixed(1) + ':1**,以阅读为主。可能正在**熟悉新代码库、做技术评审或排查问题**。');
2299
+ }
2300
+ }
2301
+
2302
+ // 发现2:Agent/子代理使用
2303
+ const agentTool = sortedTools.find(([n]) => n === 'Agent');
2304
+ if (agentTool) {
2305
+ const agentCalls = toolCalls(agentTool[1]);
2306
+ const agentPct = Math.round((agentCalls / totalCalls) * 100);
2307
+ if (agentPct >= 15) {
2308
+ findings.push('Agent(子代理)调用占比达 **' + agentPct + '%**,大量使用并行/委托式工作模式。说明善于利用**任务拆解和并行执行**来提升效率。');
2309
+ } else if (agentPct >= 5) {
2310
+ findings.push('Agent(子代理)调用占 **' + agentPct + '%**,适度使用了子代理进行任务委派。');
2311
+ }
2312
+ }
2313
+
2314
+ // 发现3:MCP工具使用
2315
+ const mcpTools = sortedTools.filter(([n]) => n.startsWith('mcp__'));
2316
+ if (mcpTools.length >= 3) {
2317
+ const mcpCalls = mcpTools.reduce((s, [, v]) => s + toolCalls(v), 0);
2318
+ const mcpPct = Math.round((mcpCalls / totalCalls) * 100);
2319
+ const mcpServers = new Set(mcpTools.map(([n]) => n.split('__')[1]?.split('_')[0]).filter(Boolean));
2320
+ findings.push('使用了 **' + mcpTools.length + ' 种** MCP 工具(来自 ' + mcpServers.size + ' 个 MCP 服务),占总调用的 **' + mcpPct + '%**。说明充分利用了外部工具生态,扩展了 AI 的能力边界。');
2321
+ }
2322
+
2323
+ // 发现4:搜索工具依赖
2324
+ const searchCalls = catCalls['搜索研究'];
2325
+ if (searchCalls > 0) {
2326
+ const searchPct = Math.round((searchCalls / totalCalls) * 100);
2327
+ if (searchPct >= 25) {
2328
+ findings.push('搜索研究类工具占比 **' + searchPct + '%**,对外部信息有较高依赖。说明当前工作中有大量**需要参考文档、查证资料**的场景。');
2329
+ }
2330
+ }
2331
+
2332
+ if (findings.length > 0) {
2333
+ lines.push('');
2334
+ lines.push('**关键发现**:');
2335
+ for (const f of findings) {
2336
+ lines.push('- ' + f);
2337
+ }
2338
+ }
2339
+
2340
+ return lines;
2341
+ }
2342
+
2343
+ /**
2344
+ * 根据工具名称和迭代比率,返回有意义的分析文案
2345
+ */
2346
+ function getIterativeInsight(toolName, ratio) {
2347
+ const r = parseFloat(ratio);
2348
+ const level = r >= 5 ? '极高频' : r >= 3 ? '高频' : '中频';
2349
+
2350
+ const insights = {
2351
+ 'Bash': level + '迭代执行——可能在**反复调试命令、逐步修正参数**,或执行多步部署/测试流程',
2352
+ 'Edit': level + '迭代编辑——在同一段代码上**反复打磨**,体现出精细化的代码调整过程',
2353
+ 'Write': level + '迭代写入——可能在**分步骤创建多个文件**,或反复重写同一文件',
2354
+ 'Read': level + '迭代阅读——在同一次对话中**反复查阅**同一文件或多个文件,可能在深入理解代码逻辑',
2355
+ 'Grep': level + '迭代搜索——**多轮搜索不同关键词**,说明在进行系统性的代码定位或模式分析',
2356
+ 'Glob': level + '迭代搜索文件——**多轮文件匹配**,可能在探索项目结构或定位特定类型的文件',
2357
+ 'Agent': level + '迭代派发子任务——在单次对话中**多次委派不同子任务**,说明任务复杂度较高,需要多步骤协调',
2358
+ 'WebSearch': level + '迭代搜索——在单次对话中**多次搜索不同话题**,说明调研范围较广',
2359
+ };
2360
+
2361
+ if (insights[toolName]) return insights[toolName];
2362
+
2363
+ // MCP 工具的特殊处理
2364
+ if (toolName.startsWith('mcp__Playwright') || toolName.startsWith('browser_')) {
2365
+ return level + '迭代浏览器操作——可能在**反复调试页面交互**,或多步骤自动化测试';
2366
+ }
2367
+ if (toolName.startsWith('mcp__serena')) {
2368
+ return level + '迭代代码操作——使用 Serena 进行**多步骤代码重构或分析**';
2369
+ }
2370
+ if (toolName.startsWith('mcp__context7') || toolName.startsWith('mcp__open-websearch') || toolName.startsWith('mcp__web_reader')) {
2371
+ return level + '迭代信息检索——**多轮查证不同文档**,在进行系统性的技术调研';
2372
+ }
2373
+
2374
+ return level + '迭代调用——在单次对话中重复使用该工具,说明对该工具的**操作具有多步骤特性**';
2375
+ }
2376
+
2377
+
1462
2378
  function buildSuggestions(stats) {
1463
2379
  const tips = [];
1464
2380
  // 缓存命中率