Forge
csharpdeeb25a2
1using System.Security.Cryptography;
2using System.Text;
3using CascadeIDE.Services;
4
5namespace CascadeIDE.Features.Agent.Environment;
6
7public static class VerifySnapshot
8{
9 public static string Create(string solutionPath) =>
10 Create(solutionPath, git: null, workspaceRoot: null);
11
12 public static string Create(
13 string solutionPath,
14 IGitCommandRunner? git,
15 string? workspaceRoot)
16 {
17 var normalized = Path.GetFullPath(solutionPath.Trim());
18 var parts = new List<string> { normalized };
19
20 if (git is not null
21 && !string.IsNullOrWhiteSpace(workspaceRoot)
22 && Directory.Exists(workspaceRoot))
23 {
24 try
25 {
26 parts.AddRange(CollectGitParts(git, workspaceRoot));
27 }
28 catch
29 {
30 parts.Add("tick:" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
31 }
32 }
33 else
34 {
35 parts.Add("tick:" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
36 }
37
38 var payload = string.Join("|", parts);
39 var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(payload)))[..16];
40 return hash;
41 }
42
43 private static List<string> CollectGitParts(IGitCommandRunner git, string workspaceRoot) =>
44 CollectGitPartsCore(git, workspaceRoot);
45
46 private static List<string> CollectGitPartsCore(IGitCommandRunner git, string workspaceRoot)
47 {
48 var parts = new List<string>();
49 var head = TryGitHead(git, workspaceRoot);
50 if (head is not null)
51 parts.Add("head:" + head);
52
53 var dirty = TryGitDirtyFingerprint(git, workspaceRoot);
54 if (dirty is not null)
55 parts.Add("dirty:" + dirty);
56
57 return parts;
58 }
59
60 private static string? TryGitHead(IGitCommandRunner git, string workspaceRoot)
61 {
62 try
63 {
64 var wd = Path.GetFullPath(workspaceRoot);
65 var (ok, _, output) = git.RunAsync(["-c", "core.quotepath=false", "rev-parse", "HEAD"], wd)
66 .GetAwaiter().GetResult();
67 if (!ok)
68 return null;
69
70 var head = output.Trim();
71 return head.Length >= 7 ? head[..7] : head;
72 }
73 catch
74 {
75 return null;
76 }
77 }
78
79 private static string? TryGitDirtyFingerprint(IGitCommandRunner git, string workspaceRoot)
80 {
81 try
82 {
83 var wd = Path.GetFullPath(workspaceRoot);
84 var unstaged = git.RunAsync(["-c", "core.quotepath=false", "diff", "--name-only"], wd)
85 .GetAwaiter().GetResult();
86 var staged = git.RunAsync(["-c", "core.quotepath=false", "diff", "--name-only", "--cached"], wd)
87 .GetAwaiter().GetResult();
88 if (!unstaged.Success && !staged.Success)
89 return null;
90
91 var merged = AgentDiagnoseFilesCsScopeParser.MergeGitNameOnlyOutputs(unstaged.Output, staged.Output);
92 if (merged.Count == 0)
93 return "clean";
94
95 var payload = string.Join("\n", merged.OrderBy(s => s, StringComparer.OrdinalIgnoreCase));
96 return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(payload)))[..12];
97 }
98 catch
99 {
100 return null;
101 }
102 }
103}
104
View only · write via MCP/CIDE