diff --git a/README.md b/README.md
index a245781..0a1cea0 100644
--- a/README.md
+++ b/README.md
@@ -7,13 +7,13 @@ A Windows-local integration that lets DeepSeek Harness users type `@`, choose a
- `protocol/`: versioned JSON snapshot contract shared by all components.
- `dsh-plugin/`: dual-face Cordis package. The Host reads snapshots; the Client registers the `@` source.
- `vscode-extension/`: VS Code companion using `window.tabGroups.all`.
-- `visualstudio-extension/`: Visual Studio 2022 VSIX using `IVsRunningDocumentTable`.
+- `visualstudio-extension/`: Visual Studio 2022/2026 VSIX using `IVsRunningDocumentTable`.
The companions publish paths and dirty flags only. They never copy source text. DSH and the model continue to use the normal filesystem tools, so an unsaved IDE buffer is explicitly marked but is not silently substituted for the disk file.
## Build
-Prerequisites: Node.js 20+, pnpm 10+, .NET SDK/MSBuild with the Visual Studio 2022 extension workload.
+Prerequisites: Node.js 20+, pnpm 10+, .NET SDK/MSBuild with the Visual Studio extension workload.
```powershell
pnpm install
@@ -29,7 +29,7 @@ Build the DSH package, then install its local profile bundle into the Web profil
```powershell
pnpm --filter ./dsh-plugin build
-dsh plugin --profile web add F:\dsh-plugin-coop-with-vs\dsh-plugin
+dsh plugin --profile web add -w F:\dsh-plugin-coop-with-vs\dsh-plugin
```
The package's `cordis.patch.yml` inserts its own shared Host/Client row. Restart `dsh web` after the first install. During development, run the DSH package watcher and the Harness `pnpm run dev:web` watcher when Client HMR is required.
diff --git a/visualstudio-extension/Prophet.CoopWithDsh.Tests/Prophet.CoopWithDsh.Tests.csproj b/visualstudio-extension/Prophet.CoopWithDsh.Tests/Prophet.CoopWithDsh.Tests.csproj
index 63f3a96..453d8d5 100644
--- a/visualstudio-extension/Prophet.CoopWithDsh.Tests/Prophet.CoopWithDsh.Tests.csproj
+++ b/visualstudio-extension/Prophet.CoopWithDsh.Tests/Prophet.CoopWithDsh.Tests.csproj
@@ -7,11 +7,11 @@
-
+
diff --git a/visualstudio-extension/Prophet.CoopWithDsh.Tests/SnapshotModelTests.cs b/visualstudio-extension/Prophet.CoopWithDsh.Tests/SnapshotModelTests.cs
index 8342f8d..c99f410 100644
--- a/visualstudio-extension/Prophet.CoopWithDsh.Tests/SnapshotModelTests.cs
+++ b/visualstudio-extension/Prophet.CoopWithDsh.Tests/SnapshotModelTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using Xunit;
namespace Prophet.CoopWithDsh.Tests
@@ -19,5 +20,32 @@ namespace Prophet.CoopWithDsh.Tests
Assert.True(snapshot.Documents[0].Dirty);
Assert.Equal("2026-01-01T00:00:00.0000000Z", snapshot.UpdatedAt);
}
+
+ [Fact]
+ public void PublisherWritesProtocolV1WithoutExternalJsonRuntime()
+ {
+ var root = Path.Combine(Path.GetTempPath(), "coop-with-dsh-test-" + Guid.NewGuid().ToString("N"));
+ try
+ {
+ using (var publisher = new SnapshotPublisher(root))
+ {
+ var snapshot = SnapshotModel.Create(publisher.InstanceId, 42, null, new[] {
+ new OpenDocument { Path = "F:\\Project\\main.cs", Label = "main.cs", Dirty = true },
+ }, new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
+ publisher.Publish(snapshot);
+ var json = File.ReadAllText(publisher.FilePath);
+
+ Assert.Contains("\"version\":1", json);
+ Assert.Contains("\"ide\":\"visualstudio\"", json);
+ Assert.Contains("\"documents\":[{", json);
+ Assert.Contains("\"dirty\":true", json);
+ Assert.DoesNotContain("\"workspace\"", json);
+ }
+ }
+ finally
+ {
+ if (Directory.Exists(root)) Directory.Delete(root, true);
+ }
+ }
}
}
diff --git a/visualstudio-extension/Prophet.CoopWithDsh/Prophet.CoopWithDsh.csproj b/visualstudio-extension/Prophet.CoopWithDsh/Prophet.CoopWithDsh.csproj
index 3fc680f..7024a27 100644
--- a/visualstudio-extension/Prophet.CoopWithDsh/Prophet.CoopWithDsh.csproj
+++ b/visualstudio-extension/Prophet.CoopWithDsh/Prophet.CoopWithDsh.csproj
@@ -8,6 +8,7 @@
Prophet.CoopWithDsh
true
true
+ true
true
false
true
@@ -17,7 +18,6 @@
-
diff --git a/visualstudio-extension/Prophet.CoopWithDsh/SnapshotModels.cs b/visualstudio-extension/Prophet.CoopWithDsh/SnapshotModels.cs
index 97958c5..a4ddb6f 100644
--- a/visualstudio-extension/Prophet.CoopWithDsh/SnapshotModels.cs
+++ b/visualstudio-extension/Prophet.CoopWithDsh/SnapshotModels.cs
@@ -2,26 +2,28 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using Newtonsoft.Json;
+using System.Runtime.Serialization;
namespace Prophet.CoopWithDsh
{
+ [DataContract]
internal sealed class OpenDocument
{
- [JsonProperty("path")] public string Path { get; set; } = string.Empty;
- [JsonProperty("label")] public string Label { get; set; } = string.Empty;
- [JsonProperty("dirty")] public bool Dirty { get; set; }
+ [DataMember(Name = "path", Order = 1)] public string Path { get; set; } = string.Empty;
+ [DataMember(Name = "label", Order = 2)] public string Label { get; set; } = string.Empty;
+ [DataMember(Name = "dirty", Order = 3)] public bool Dirty { get; set; }
}
+ [DataContract]
internal sealed class OpenFileSnapshot
{
- [JsonProperty("version")] public int Version { get; set; } = 1;
- [JsonProperty("instanceId")] public string InstanceId { get; set; } = string.Empty;
- [JsonProperty("ide")] public string Ide { get; set; } = "visualstudio";
- [JsonProperty("processId")] public int ProcessId { get; set; }
- [JsonProperty("workspace", NullValueHandling = NullValueHandling.Ignore)] public string? Workspace { get; set; }
- [JsonProperty("updatedAt")] public string UpdatedAt { get; set; } = string.Empty;
- [JsonProperty("documents")] public IReadOnlyList Documents { get; set; } = Array.Empty();
+ [DataMember(Name = "version", Order = 1)] public int Version { get; set; } = 1;
+ [DataMember(Name = "instanceId", Order = 2)] public string InstanceId { get; set; } = string.Empty;
+ [DataMember(Name = "ide", Order = 3)] public string Ide { get; set; } = "visualstudio";
+ [DataMember(Name = "processId", Order = 4)] public int ProcessId { get; set; }
+ [DataMember(Name = "workspace", Order = 5, EmitDefaultValue = false)] public string? Workspace { get; set; }
+ [DataMember(Name = "updatedAt", Order = 6)] public string UpdatedAt { get; set; } = string.Empty;
+ [DataMember(Name = "documents", Order = 7)] public OpenDocument[] Documents { get; set; } = Array.Empty();
}
internal static class SnapshotModel
diff --git a/visualstudio-extension/Prophet.CoopWithDsh/SnapshotPublisher.cs b/visualstudio-extension/Prophet.CoopWithDsh/SnapshotPublisher.cs
index 13a0fcb..da50a76 100644
--- a/visualstudio-extension/Prophet.CoopWithDsh/SnapshotPublisher.cs
+++ b/visualstudio-extension/Prophet.CoopWithDsh/SnapshotPublisher.cs
@@ -1,12 +1,13 @@
using System;
using System.IO;
+using System.Runtime.Serialization.Json;
using System.Text;
-using Newtonsoft.Json;
namespace Prophet.CoopWithDsh
{
internal sealed class SnapshotPublisher : IDisposable
{
+ private static readonly DataContractJsonSerializer Serializer = new DataContractJsonSerializer(typeof(OpenFileSnapshot));
private readonly object gate = new object();
internal string InstanceId { get; } = Guid.NewGuid().ToString("N");
internal string FilePath { get; }
@@ -26,7 +27,11 @@ namespace Prophet.CoopWithDsh
var directory = Path.GetDirectoryName(FilePath)!;
Directory.CreateDirectory(directory);
var temporary = FilePath + "." + Guid.NewGuid().ToString("N") + ".tmp";
- File.WriteAllText(temporary, JsonConvert.SerializeObject(snapshot) + Environment.NewLine, new UTF8Encoding(false));
+ using (var stream = new MemoryStream())
+ {
+ Serializer.WriteObject(stream, snapshot);
+ File.WriteAllText(temporary, Encoding.UTF8.GetString(stream.ToArray()) + Environment.NewLine, new UTF8Encoding(false));
+ }
try
{
if (File.Exists(FilePath)) File.Replace(temporary, FilePath, null);
diff --git a/visualstudio-extension/Prophet.CoopWithDsh/source.extension.vsixmanifest b/visualstudio-extension/Prophet.CoopWithDsh/source.extension.vsixmanifest
index 234e09b..6dd1d14 100644
--- a/visualstudio-extension/Prophet.CoopWithDsh/source.extension.vsixmanifest
+++ b/visualstudio-extension/Prophet.CoopWithDsh/source.extension.vsixmanifest
@@ -1,19 +1,19 @@
-
+
DSH Open File Bridge
Publishes Visual Studio open file metadata for DeepSeek Harness @ references.
LICENSE.txt
-
- amd64
- amd64
+
+ amd64
+ amd64
-
+
diff --git a/visualstudio-extension/README.md b/visualstudio-extension/README.md
index eaae898..fbec477 100644
--- a/visualstudio-extension/README.md
+++ b/visualstudio-extension/README.md
@@ -1,12 +1,12 @@
-# DSH Open File Bridge for Visual Studio 2022
+# DSH Open File Bridge for Visual Studio
-This VSIX is a background-loaded `AsyncPackage`. Each Visual Studio process enumerates its own `IVsRunningDocumentTable`, listens for RDT changes, and publishes one protocol-v1 snapshot. This avoids the ambiguity and integrity-level failures of attaching externally through EnvDTE/ROT.
+This VSIX supports Visual Studio 2022 (17.x) and Visual Studio 2026 (18.x). It is a background-loaded `AsyncPackage`. Each Visual Studio process enumerates its own `IVsRunningDocumentTable`, listens for RDT changes, and publishes one protocol-v1 snapshot. This avoids the ambiguity and integrity-level failures of attaching externally through EnvDTE/ROT.
Only absolute, existing local files are published. Dirty state is read through `IVsPersistDocData`; source buffers are never copied.
## Build and test
-Use a Visual Studio Developer PowerShell or a machine with the VS 2022 extension workload:
+Use a Visual Studio Developer PowerShell or a machine with the Visual Studio extension workload:
```powershell
dotnet restore CoopWithDsh.sln