Forge
csharpdeeb25a2
1using CascadeIDE.Features.Chat;
2using CascadeIDE.Models.AgentChat;
3using Xunit;
4
5namespace CascadeIDE.Tests;
6
7public sealed class SedmEventProjectorTests
8{
9 [Fact]
10 public void Project_RoundTripsContextIntentAndDecision()
11 {
12 var workline = Guid.Parse("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
13 var decisionId = Guid.Parse("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
14 var sid = Guid.NewGuid();
15
16 var events = new List<ChatHistoryEvent>
17 {
18 NewEvent(sid, ChatHistoryEventKind.ContextCardMaterialized, new SedmContextCardMaterializedPayload(
19 1,
20 workline.ToString("N"),
21 new SedmContextCardAnchorPayload("Features/Chat/Foo.cs", "Bar"),
22 TriggerReason: "attach")),
23 NewEvent(sid, ChatHistoryEventKind.IntentCardRecorded, new SedmIntentCardRecordedPayload(
24 1,
25 "operator",
26 workline.ToString("N"),
27 new SedmIntentCardBodyPayload(
28 "Strip shows open worklines",
29 Trigger: "switch loses tail",
30 ChosenApproach: "meta projection",
31 SelectionRationale: "preserves head"),
32 Considered: [new SedmIntentConsideredOptionPayload("New Chat", "flat")])),
33 NewEvent(sid, decisionId, ChatHistoryEventKind.DecisionRecorded, new SedmDecisionRecordedPayload(
34 1,
35 "agent",
36 workline.ToString("N"),
37 new SedmIntentCardBodyPayload("S1 events in log", ChosenApproach: "append-only"),
38 Basis: new SedmDecisionBasisPayload("git:abc", ["Features/Chat/Foo.cs"]),
39 Findings: [new SedmDecisionFindingPayload("adr", "0172", "G2 scope strip")])),
40 };
41
42 var projection = SedmEventProjector.Project(events, workline, openWorklineCount: 2);
43 var wl = SedmEventProjector.ResolveWorkline(projection, workline);
44
45 Assert.NotNull(wl.ContextCard);
46 Assert.Equal("Features/Chat/Foo.cs", wl.ContextCard!.Anchor.Path);
47 Assert.NotNull(wl.IntentCard);
48 Assert.NotNull(wl.ActiveDecision);
49 Assert.Equal("active", wl.ActiveDecision!.Status);
50 Assert.Single(wl.DecisionHistory);
51 }
52
53 [Fact]
54 public void Project_MarksDecisionStale()
55 {
56 var workline = Guid.Parse("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC");
57 var decisionId = Guid.Parse("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD");
58 var sid = Guid.NewGuid();
59
60 var events = new List<ChatHistoryEvent>
61 {
62 NewEvent(sid, decisionId, ChatHistoryEventKind.DecisionRecorded, new SedmDecisionRecordedPayload(
63 1,
64 "agent",
65 workline.ToString("N"),
66 new SedmIntentCardBodyPayload("old basis"))),
67 NewEvent(sid, ChatHistoryEventKind.DecisionMarkedStale, new SedmDecisionLifecyclePayload(
68 1,
69 workline.ToString("N"),
70 decisionId.ToString("N"),
71 "path_touch")),
72 };
73
74 var wl = SedmEventProjector.ResolveWorkline(SedmEventProjector.Project(events, workline), workline);
75 Assert.Null(wl.ActiveDecision);
76 Assert.Equal("stale", wl.DecisionHistory[0].Status);
77 }
78
79 [Fact]
80 public void IsSameContextCard_CoalescesDuplicateAnchor()
81 {
82 var workline = Guid.Parse("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE");
83 var left = new SedmContextCardMaterializedPayload(
84 1,
85 workline.ToString("N"),
86 new SedmContextCardAnchorPayload("a.cs"),
87 TriggerReason: "attach");
88 var right = left with { TriggerReason = "workline_switch" };
89 Assert.True(SedmEventProjector.IsSameContextCard(left, right));
90 }
91
92 private static ChatHistoryEvent NewEvent(Guid sessionId, string kind, object payload) =>
93 NewEvent(sessionId, Guid.NewGuid(), kind, payload);
94
95 private static ChatHistoryEvent NewEvent(Guid sessionId, Guid eventId, string kind, object payload) =>
96 new(
97 eventId,
98 sessionId,
99 DateTimeOffset.UtcNow,
100 kind,
101 ChatHistoryJson.Serialize(payload));
102}
103
View only · write via MCP/CIDE