Forge
csharpdeeb25a2
1#nullable enable
2
3using System.Text.Json;
4using System.Text.Json.Serialization;
5using CascadeIDE.Features.Workspace;
6using CascadeIDE.Features.Workspace.DataAcquisition;
7using CascadeIDE.Services;
8
9namespace CascadeIDE.Features.WorkspaceNavigation.Application;
10
11/// <summary>JSON для MCP <c>get_correspondence_context</c> (ADR 0156 §3).</summary>
12public static class WorkspaceCorrespondenceContextBuilder
13{
14 private static readonly JsonSerializerOptions JsonOptions = new()
15 {
16 PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
17 DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
18 WriteIndented = false,
19 };
20
21 public static string BuildJson(string? workspaceRoot, string? absoluteFilePath, bool hasCodeGraph = false)
22 {
23 if (string.IsNullOrWhiteSpace(workspaceRoot) || string.IsNullOrWhiteSpace(absoluteFilePath))
24 return JsonSerializer.Serialize(
25 new CorrespondenceContextPayload(null, [], "", "", null, [], []),
26 JsonOptions);
27
28 var root = workspaceRoot.Trim();
29 var file = absoluteFilePath.Trim();
30 var workspaceToml = RepositoryWorkspaceTomlLoader.TryLoad(root);
31 var correspondence = WorkspaceCorrespondenceResolver.Resolve(root, file);
32 var explicitAnchors = WorkspaceCorrespondenceCodeAnchorsLoader.LoadFromWorkspaceToml(workspaceToml, root);
33 var reverse = DocReverseAnchorResolver.Resolve(
34 root,
35 file,
36 correspondence.AdrDocPaths,
37 explicitAnchors);
38
39 var rel = WorkspaceAdrMapResolver.TryComputeRepoRelativePath(root, file);
40 var signals = CorrespondenceLayersProjection.FromCorrespondence(
41 hasHciOrientation: false,
42 hasFeature: !string.IsNullOrWhiteSpace(correspondence.FeatureLine),
43 hasAdrDocs: correspondence.AdrDocPaths.Length > 0,
44 hasCodeGraph: hasCodeGraph);
45
46 var payload = new CorrespondenceContextPayload(
47 File: rel,
48 ActiveLayers: ToLayerIds(signals),
49 LayersBadge: CorrespondenceLayersProjection.BuildLayersBadge(signals),
50 LayersTooltip: CorrespondenceLayersProjection.BuildLayersTooltip(signals),
51 Feature: string.IsNullOrWhiteSpace(correspondence.FeatureLine)
52 ? null
53 : new FeaturePayload(correspondence.FeatureLine, correspondence.FeatureDocPaths),
54 ForwardDocs: correspondence.AdrDocPaths
55 .Select(p => new ForwardDocPayload(p, WorkspaceAdrMapResolver.GuessAdrPreviewTitle(p)))
56 .ToArray(),
57 ReverseAnchors: reverse
58 .Select(m => new ReverseAnchorPayload(
59 m.DocPath,
60 m.DocTitle,
61 m.Provenance,
62 m.Kind,
63 new CodeAnchorPayload(
64 m.CodeAnchor.File,
65 m.CodeAnchor.LineStart,
66 m.CodeAnchor.LineEnd,
67 m.CodeAnchor.MemberKey,
68 m.CodeAnchor.SyntaxScope),
69 m.Excerpt,
70 m.DocLineHint))
71 .ToArray());
72
73 return JsonSerializer.Serialize(payload, JsonOptions);
74 }
75
76 private static string[] ToLayerIds(CorrespondenceLayerSignals signals)
77 {
78 var ids = new List<string>(6);
79 if (signals.SemanticCanonL0) ids.Add("L0");
80 if (signals.FeatureRegistryL1p) ids.Add("L1p");
81 if (signals.AdrPathMapL1) ids.Add("L1");
82 if (signals.CodeIntentGraphL2) ids.Add("L2");
83 if (signals.ChangeIntentL3) ids.Add("L3");
84 if (signals.DiscourseCodeL4) ids.Add("L4");
85 return ids.ToArray();
86 }
87
88 private sealed record CorrespondenceContextPayload(
89 string? File,
90 string[] ActiveLayers,
91 string LayersBadge,
92 string LayersTooltip,
93 FeaturePayload? Feature,
94 ForwardDocPayload[] ForwardDocs,
95 ReverseAnchorPayload[] ReverseAnchors);
96
97 private sealed record FeaturePayload(string Line, string[] Docs);
98
99 private sealed record ForwardDocPayload(string Path, string Title);
100
101 private sealed record ReverseAnchorPayload(
102 string DocPath,
103 string DocTitle,
104 string Provenance,
105 string Kind,
106 CodeAnchorPayload CodeAnchor,
107 string Excerpt,
108 int? DocLineHint);
109
110 private sealed record CodeAnchorPayload(
111 string File,
112 int? LineStart,
113 int? LineEnd,
114 string? MemberKey,
115 string? SyntaxScope);
116}
117
View only · write via MCP/CIDE