Forge
csharpdeeb25a2
1#nullable enable
2using System.Text;
3using CascadeIDE.Services;
4
5namespace CascadeIDE.Features.Chat;
6
7/// <summary>Локальные текстовые отчёты Intercom (<c>kind=report</c>, <c>report_handler</c>).</summary>
8public static class ChatSlashSessionReports
9{
10 public static string? TryFormat(string slashPath, ChatSurfaceSnapshot snapshot)
11 {
12 if (!IntentSlashCatalog.TryGetRoute(slashPath, out var route)
13 || route.ExecutionKind != ChatSlashCommandExecutionKind.LocalReport)
14 {
15 return null;
16 }
17
18 if (string.IsNullOrWhiteSpace(route.ReportHandlerId)
19 || !ChatSlashReportHandlers.TryFormat(route.ReportHandlerId, snapshot, out var text))
20 {
21 return $"Неизвестный отчёт: {slashPath}";
22 }
23
24 return text;
25 }
26
27 public static string FormatTopicList(ChatSurfaceSnapshot snapshot) =>
28 ChatThreadPresentation.FormatTopicList(snapshot);
29
30 public static string FormatTopicTree(ChatSurfaceSnapshot snapshot) =>
31 ChatThreadPresentation.FormatTopicTree(snapshot);
32
33 public static string FormatSpineList(ChatProductSpine spine)
34 {
35 if (!spine.HasContent)
36 return "Spine пуст. Задай фокус в classic Intercom или /spine set <фокус>.";
37
38 var title = ChatProductSpinePresentation.ResolveLineTitle(spine);
39 var lines = new List<string> { $"Spine · {title}" };
40 if (!string.IsNullOrWhiteSpace(spine.CurrentFocus))
41 lines.Add(" Фокус: " + spine.CurrentFocus.Trim());
42
43 if (spine.Milestones.Count > 0)
44 {
45 lines.Add($" Вехи ({spine.Milestones.Count}):");
46 foreach (var milestone in spine.Milestones)
47 {
48 if (!string.IsNullOrWhiteSpace(milestone))
49 lines.Add(" • " + milestone.Trim());
50 }
51 }
52 else
53 {
54 lines.Add(" Вехи: —");
55 }
56
57 lines.Add(" " + ChatProductSpinePresentation.FormatAgentContextFooter(spine.IncludeInAgentContext));
58 return string.Join(Environment.NewLine, lines);
59 }
60
61 public static string FormatSpineTree(ChatProductSpine spine)
62 {
63 if (!spine.HasContent)
64 return "Spine пуст. Задай фокус в classic Intercom или /spine set <фокус>.";
65
66 var title = ChatProductSpinePresentation.ResolveLineTitle(spine);
67 var sb = new StringBuilder();
68 sb.AppendLine(title);
69 var focus = string.IsNullOrWhiteSpace(spine.CurrentFocus)
70 ? "—"
71 : spine.CurrentFocus.Trim();
72 sb.AppendLine("└─ Фокус: " + focus);
73
74 var milestones = spine.Milestones
75 .Where(m => !string.IsNullOrWhiteSpace(m))
76 .Select(m => m.Trim())
77 .ToList();
78 if (milestones.Count == 0)
79 return sb.ToString().TrimEnd();
80
81 for (var i = 0; i < milestones.Count; i++)
82 {
83 var branch = i == milestones.Count - 1 ? " └─ " : " ├─ ";
84 sb.AppendLine(branch + milestones[i]);
85 }
86
87 var ctx = ChatProductSpinePresentation.FormatAgentContextFooter(spine.IncludeInAgentContext);
88 sb.Append(" (" + ctx + ")");
89 return sb.ToString().TrimEnd();
90 }
91
92 public static string FormatSedmScope(ChatSedmScopeStrip strip)
93 {
94 var text = strip.FormatStripText();
95 return string.IsNullOrWhiteSpace(text)
96 ? "SEDM scope strip пуст. Прикрепи файл или зафиксируй intent/decision."
97 : "SEDM scope · " + text;
98 }
99
100 public static string FormatSedmScopeDetail(ChatSurfaceSnapshot snapshot)
101 {
102 var strip = snapshot.SedmScopeStrip;
103 var lines = new List<string> { "SEDM · активная workline" };
104 if (strip.OpenWorklineCount > 1)
105 lines.Add($"Open worklines: {strip.OpenWorklineCount}");
106 if (!string.IsNullOrWhiteSpace(strip.ContextOneLiner))
107 lines.Add("Context: " + strip.ContextOneLiner);
108 if (!string.IsNullOrWhiteSpace(strip.IntentOneLiner))
109 lines.Add("Intent: " + strip.IntentOneLiner + (strip.IntentIncomplete ? " (incomplete)" : ""));
110 if (!string.IsNullOrWhiteSpace(strip.DecisionOneLiner))
111 lines.Add("Decision: " + strip.DecisionOneLiner + (string.IsNullOrWhiteSpace(strip.DecisionStatus) ? "" : $" [{strip.DecisionStatus}]"));
112
113 var timeline = snapshot.Layout.Lanes
114 .SelectMany(l => l.Entries)
115 .Where(e => e.Kind == ChatSurfaceEntryKind.SedmCard)
116 .ToList();
117 if (timeline.Count > 0)
118 {
119 lines.Add($"Timeline cards: {timeline.Count}");
120 foreach (var card in timeline.Take(6))
121 lines.Add($" · {card.Title}: {Truncate(card.Body, 72)}");
122 }
123
124 return string.Join(Environment.NewLine, lines);
125 }
126
127 private static string Truncate(string text, int max) =>
128 text.Length <= max ? text : text[..max] + "…";
129}
130
View only · write via MCP/CIDE