From 6b8311a55e0b8c9bf87aac5af3289359b03ad621 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 23:50:22 +0000 Subject: [PATCH 01/23] OX-02 P14 probe: can a hosted Windows runner drive Visual Studio? Kill-first infrastructure probe (no product code): find VS with vswhere, open a solution in the Exp hive, edit a buffer through DTE, read the Error List. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .github/workflows/owen-vs-probe.yml | 24 +++++++++ scripts/vs/probe.ps1 | 80 +++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 .github/workflows/owen-vs-probe.yml create mode 100644 scripts/vs/probe.ps1 diff --git a/.github/workflows/owen-vs-probe.yml b/.github/workflows/owen-vs-probe.yml new file mode 100644 index 00000000..0310d2ef --- /dev/null +++ b/.github/workflows/owen-vs-probe.yml @@ -0,0 +1,24 @@ +name: owen-vs-probe + +permissions: + contents: read + +on: + push: + branches: ["ccr-*"] + paths: [".github/workflows/owen-vs-probe.yml", "scripts/vs/**"] + +jobs: + probe: + runs-on: windows-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: probe Visual Studio + shell: powershell + run: scripts/vs/probe.ps1 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: vs-probe + path: vs-probe/ diff --git a/scripts/vs/probe.ps1 b/scripts/vs/probe.ps1 new file mode 100644 index 00000000..f7d8ab17 --- /dev/null +++ b/scripts/vs/probe.ps1 @@ -0,0 +1,80 @@ +# OX-02 P14 kill-first probe: can a hosted Windows runner start Visual Studio, open a +# solution in an experimental hive, edit a buffer through DTE and read the Error List? +$ErrorActionPreference = 'Continue' +$out = Join-Path $PWD 'vs-probe' +New-Item -ItemType Directory -Force $out | Out-Null +function Log($m) { $line = "[$(Get-Date -Format HH:mm:ss.fff)] $m"; Write-Host $line; Add-Content (Join-Path $out 'probe.log') $line } + +$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +$all = & $vswhere -all -prerelease -products * -format json | ConvertFrom-Json +foreach ($i in $all) { Log "VS: $($i.displayName) $($i.installationVersion) at $($i.installationPath)" } +$vs = & $vswhere -latest -products * -property installationPath +$ver = & $vswhere -latest -products * -property installationVersion +Log "latest: $vs ($ver)" +$ws = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Workload.VisualStudioExtension -property installationPath +Log "VSSDK workload: '$ws'" +$devenv = Join-Path $vs 'Common7\IDE\devenv.exe' +$vsix = Join-Path $vs 'Common7\IDE\VSIXInstaller.exe' +Log "devenv exists: $(Test-Path $devenv); VSIXInstaller exists: $(Test-Path $vsix)" +Log "session: $([Environment]::UserInteractive) user=$env:USERNAME" +& dotnet --list-sdks | ForEach-Object { Log "sdk $_" } + +$work = Join-Path $env:RUNNER_TEMP 'probe' +New-Item -ItemType Directory -Force $work | Out-Null +Push-Location $work +& dotnet new console -n Probe --framework net8.0 -o Probe 2>&1 | Out-Null +& dotnet new sln -n Probe 2>&1 | Out-Null +& dotnet sln Probe.sln add Probe\Probe.csproj 2>&1 | Out-Null +& dotnet restore Probe.sln 2>&1 | Out-Null +Pop-Location +$sln = Join-Path $work 'Probe.sln' +$cs = Join-Path $work 'Probe\Program.cs' + +$major = $ver.Split('.')[0] +$t0 = Get-Date +$p = Start-Process $devenv -ArgumentList "/rootsuffix", "Exp", "`"$sln`"" -PassThru +Log "devenv pid $($p.Id)" +$dte = $null +for ($k = 0; $k -lt 120 -and -not $dte; $k++) { + Start-Sleep -Seconds 2 + try { $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.$major.0") } catch { } +} +if (-not $dte) { Log "NO DTE after 240 s"; } +else { + Log "DTE after $([int]((Get-Date) - $t0).TotalSeconds) s: $($dte.Version) $($dte.Edition)" + function Retry([scriptblock]$b) { for ($r = 0; $r -lt 60; $r++) { try { return & $b } catch { Start-Sleep -Milliseconds 1000 } } ; throw "gave up" } + for ($k = 0; $k -lt 60; $k++) { try { if ($dte.Solution.IsOpen -and $dte.Solution.Projects.Count -gt 0) { break } } catch { } ; Start-Sleep 2 } + Log "solution: $(Retry { $dte.Solution.FullName }) projects=$(Retry { $dte.Solution.Projects.Count })" + $win = Retry { $dte.ItemOperations.OpenFile($cs) } + $doc = Retry { $dte.ActiveDocument } + Log "active: $(Retry { $doc.FullName })" + $td = Retry { $doc.Object('TextDocument') } + $ep = Retry { $td.EndPoint.CreateEditPoint() } + Retry { $ep.Insert("`r`nclass Broken { void M() { int x = `"s`"; } }`r`n") } | Out-Null + Log "inserted; saved=$(Retry { $doc.Saved })" + Retry { $dte.ExecuteCommand('View.ErrorList') } | Out-Null + for ($k = 0; $k -lt 30; $k++) { + Start-Sleep 2 + $items = Retry { $dte.ToolWindows.ErrorList.ErrorItems } + $n = Retry { $items.Count } + Log "error items: $n" + if ($n -gt 0) { + for ($j = 1; $j -le $n; $j++) { $e = $items.Item($j); Log " [$($e.ErrorLevel)] $($e.FileName)($($e.Line),$($e.Column)) $($e.Description) project=$($e.Project)" } + $e = $items.Item(1) + Retry { $e.Navigate() } | Out-Null + Start-Sleep 1 + $sel = Retry { $dte.ActiveDocument.Selection } + Log "after Navigate: line=$($sel.CurrentLine) col=$($sel.CurrentColumn)" + break + } + } + try { + Add-Type -AssemblyName System.Windows.Forms, System.Drawing + $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds + $bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height + $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size) + $bmp.Save((Join-Path $out 'screen.png')); Log "screenshot $($b.Width)x$($b.Height)" + } catch { Log "screenshot failed: $_" } +} +Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue +Log "end" From adc6ad464f9e2eb06006790c9542970908f9415b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 23:57:24 +0000 Subject: [PATCH 02/23] OX-02 P14 probe: screenshots and window inventory during VS startup Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/probe.ps1 | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/scripts/vs/probe.ps1 b/scripts/vs/probe.ps1 index f7d8ab17..154bb46f 100644 --- a/scripts/vs/probe.ps1 +++ b/scripts/vs/probe.ps1 @@ -34,16 +34,39 @@ $major = $ver.Split('.')[0] $t0 = Get-Date $p = Start-Process $devenv -ArgumentList "/rootsuffix", "Exp", "`"$sln`"" -PassThru Log "devenv pid $($p.Id)" +Add-Type -AssemblyName System.Windows.Forms, System.Drawing, UIAutomationClient, UIAutomationTypes +$shot = 0 +function Shot($name) { + try { + $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds + $bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height + $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size) + $bmp.Save((Join-Path $out "$name.png")); $g.Dispose(); $bmp.Dispose() + } catch { Log "screenshot failed: $_" } +} +function Windows($procId) { + $root = [System.Windows.Automation.AutomationElement]::RootElement + $cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ProcessIdProperty, $procId) + foreach ($w in $root.FindAll([System.Windows.Automation.TreeScope]::Children, $cond)) { + Log " window: '$($w.Current.Name)' class=$($w.Current.ClassName)" + foreach ($c in $w.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition)) { + $n = $c.Current.Name + if ($n -and $c.Current.ControlType.ProgrammaticName -match 'Button|Hyperlink|Text|Window') { Log " $($c.Current.ControlType.ProgrammaticName) '$n'" } + } + } +} $dte = $null for ($k = 0; $k -lt 120 -and -not $dte; $k++) { Start-Sleep -Seconds 2 + if ($k % 15 -eq 0) { Shot ("start-{0:D3}" -f $k); Log "t=$($k*2)s"; Windows $p.Id } try { $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.$major.0") } catch { } } +Shot "dte"; Windows $p.Id if (-not $dte) { Log "NO DTE after 240 s"; } else { Log "DTE after $([int]((Get-Date) - $t0).TotalSeconds) s: $($dte.Version) $($dte.Edition)" function Retry([scriptblock]$b) { for ($r = 0; $r -lt 60; $r++) { try { return & $b } catch { Start-Sleep -Milliseconds 1000 } } ; throw "gave up" } - for ($k = 0; $k -lt 60; $k++) { try { if ($dte.Solution.IsOpen -and $dte.Solution.Projects.Count -gt 0) { break } } catch { } ; Start-Sleep 2 } + for ($k = 0; $k -lt 60; $k++) { try { if ($dte.Solution.IsOpen -and $dte.Solution.Projects.Count -gt 0) { break } } catch { Log "dte call: $($_.Exception.Message)" } ; Start-Sleep 2; if ($k % 10 -eq 0) { Shot ("wait-{0:D3}" -f $k); Windows $p.Id } } Log "solution: $(Retry { $dte.Solution.FullName }) projects=$(Retry { $dte.Solution.Projects.Count })" $win = Retry { $dte.ItemOperations.OpenFile($cs) } $doc = Retry { $dte.ActiveDocument } From 7f0732edb80af75fc397d3eff6760d1c4a3126f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:02:25 +0000 Subject: [PATCH 03/23] OX-02 P14 probe: create a .sln (the .NET 10 SDK defaults to .slnx) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/probe.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vs/probe.ps1 b/scripts/vs/probe.ps1 index 154bb46f..34f09381 100644 --- a/scripts/vs/probe.ps1 +++ b/scripts/vs/probe.ps1 @@ -23,7 +23,7 @@ $work = Join-Path $env:RUNNER_TEMP 'probe' New-Item -ItemType Directory -Force $work | Out-Null Push-Location $work & dotnet new console -n Probe --framework net8.0 -o Probe 2>&1 | Out-Null -& dotnet new sln -n Probe 2>&1 | Out-Null +& dotnet new sln -n Probe --format sln 2>&1 | Out-Null & dotnet sln Probe.sln add Probe\Probe.csproj 2>&1 | Out-Null & dotnet restore Probe.sln 2>&1 | Out-Null Pop-Location From 332b1d96df1bfaccd099215366f91a1508babcb1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:02:36 +0000 Subject: [PATCH 04/23] OX-02 preregistration: one generic Owen.VisualStudio host Fixed before product code: base e889f8b, architecture (one extractor run in-process over an overlay, the build host's contract, the Rust core's Finding via SARIF), live request from design-time builds, owen-live/1 framing/handshake/versioning, diagnostic contract and the two coordinate gaps (no protocol columns; #393), acceptance K1-K8/P9/P10, latency thresholds, Snipper reuse, the real-VS run, mutation controls M1-M6, out of scope, verdict rule. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .../owen-visual-studio-preregistration.md | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/notes/owen-visual-studio-preregistration.md diff --git a/docs/notes/owen-visual-studio-preregistration.md b/docs/notes/owen-visual-studio-preregistration.md new file mode 100644 index 00000000..535ba4bd --- /dev/null +++ b/docs/notes/owen-visual-studio-preregistration.md @@ -0,0 +1,137 @@ +# OX-02 preregistration: one generic `Owen.VisualStudio` host + +Fixed **before** any product code. A change to anything below is an **Amendment** appended at +the end, dated, with its reason; nothing above the amendments is edited after the commit. + +## 0. Base + +- Base: `main` = `e889f8b37f04f94446855f0c7924521d28d8bb94` (merge of #397, OX-01). Verified present: `frontend/roslyn/Owen.Build`, `frontend/roslyn/Owen.TypedBuilder`, `spec/OwenExtension.md`, `scripts/owen_extension_gate.py`, `OwnSharp.Cli/BuildCheckCommand.cs`, `frontend/roslyn/OwenRustCore.props`, `docs/notes/owen-extension-alpha-report.md` (verdict `GO_OWEN_EXTENSION_ALPHA`). +- Nothing in the areas OX-02 touches (extractor input, build host, Rust core renderers) moved since OX-01: the plan in `owen-extension-ide-feasibility.md` (E1) stands, with its two blockers (unsaved buffers, resident process) as the first work. +- Baseline gates on `e889f8b`, run in a clean worktree (results in §11). + +## 1. Read before planning + +- OX-01 contract: `spec/OwenExtension.md` (descriptors, capabilities, OWENB codes, manifest), `owen-extension-alpha-preregistration.md`, `owen-diagnostics-model.md` (the `Finding` model, the missing column on the msbuild line), `owen-extension-ide-feasibility.md`. +- `docs/howto-visual-studio.md`: today's VS path is the build host; Error List rows come from canonical MSBuild lines on build. +- Microsoft VSSDK samples (pattern only, nothing copied): `ErrorList` (an `ITableDataSource` registered on `StandardTables.ErrorsTable`, entries as `TableEntriesSnapshotBase` with `StandardTableKeyNames`), the `IErrorTag` tagger pattern (`ITaggerProvider` + `TagsChanged`). +- Snipper (`PhysShell/snipper` 43b395a, MIT): `extensions/snipper-vs` (SDK-style VSIX csproj with `Microsoft.VSSDK.BuildTools`, `scripts/vs-smoke.ps1` real-IDE smoke, mocked-VS tests). Reuse inventory in §9. + +## 2. What is built (architecture) + + Visual Studio (devenv, net472) + Owen.VisualStudio (VSIX, MEF; ONE project, knows no extension) + | reads /obj/owen/live.txt (written by Owen.Build: §3) + | snapshot of the Roslyn workspace: unsaved text of open documents + source-generated documents + v framed JSON over the child's stdin/stdout (owen-live/1, §4) + `dotnet exec /tools/net8.0/any/ownsharp.dll serve` (long-lived) + = the build host's own code path (descriptor validation, input set, refusals) + + InProcessExtractor: the ONE extractor program, run in-process over an overlay + -> facts (OwnIR v2 + extension facts) -> packaged Rust core `own-cli ownir --format sarif` + <- findings: the core's Finding, unchanged + +- **One frontend.** The extractor is not copied, ported or wrapped in a second implementation. `OwnSharp.Extractor` gains `InProcessExtractor.Run(args, cwd, overlay)`: the same program with the same arguments, whose one source read consults an in-memory overlay first. The CLI, `owen build-check` and `owen serve` all run that program. + - *Why not `Analyze(Compilation)`:* the VS host runs on .NET Framework inside devenv; the extractor is .NET 8 and builds its own compilation (TPA + project `bin/` references). Handing it a VS `Compilation` object across a process boundary is impossible, and building the facts from a different compilation would be a second frontend semantics. The snapshot crossing the boundary is therefore **text** (path -> contents), and the compilation is built by the one extractor exactly as on the CLI. +- **P1 kill-first result (measured before this commit, the only code written before it).** The extractor *is* separable without a semantic rewrite. Its process-global state is four statics: two were already reset at entry (`BodyThrowEdges`, `WeaverOwnedFiles`), two were not (`GuardedFactsViolations`, `OrphanedAwaitables.Sites`) and now are; its one source read (`File.ReadAllText` in the parse loop) consults the overlay. `tests/check_extractor_in_process.py`: 98 extractor jobs (every sample, every protocol case, 6 flag combinations, the OrderBackend project) run in ONE process, forward and reversed, are byte-identical to the command line's facts and exit codes; an overlay equal to the disk changes nothing; an overlay that differs is read instead of the disk and equals the command line over the same contents on disk: **198/198**. No STOP. +- **One host contract.** `owen serve` reuses `BuildCheckCommand`'s descriptor validation, input-set rule (project + the declared generators' output), refusal canonicalisation and capability list. Nothing about any extension is in the service or the VSIX. +- **One engine.** The Rust core decides; the VSIX and the service only carry its `Finding` (via the existing SARIF renderer, pinned by BR-V9) to the editor. +- **No new packaging.** The service is the `ownsharp.dll` already inside `Owen.Build` (`tools/net8.0/any/`, Rust core included). The VSIX ships no core, no extractor, no Python; it needs `dotnet` (already required by Owen.Build) and a project that references `Owen.Build` through any extension. +- Extensions never ship VS code. There is no `Owen.TypedBuilder.VisualStudio` / `Owen.Memory.VisualStudio` / `Owen.Types.VisualStudio`; a future extension gets the IDE transport by declaring a descriptor, exactly as it gets the build host. + +## 3. How the VSIX learns the active extensions (P6) without a build + +- `Owen.Build.targets` gains target `OwenLiveRequest` that writes `obj/owen/live.txt` in the **same key/value format** as the build host's `request.txt`, from the **same items**: `project`, every `descriptor` (`@(OwenExtensionDescriptor)`), `generated-root`, `severity`, plus `host` (the package's `ownsharp.dll`) and `dotnet` (`$(DOTNET_HOST_PATH)` when MSBuild knows it). +- It runs in **design-time builds** (which Visual Studio performs on project load without the user building) and in normal builds. It does nothing else: no analysis in a design-time build. +- No `live.txt` (Owen.Build not referenced, `OwenEnabled=false`): the VSIX does nothing for that project. `live.txt` present but the service rejects a descriptor (OWENB002/003/004, e.g. an unknown required capability): that OWENB diagnostic is shown live, as an Error List row on the descriptor (K7); never a silent skip. + +## 4. Process model and protocol `owen-live/1` + +- **Process.** One service process per `host` path (projects on the same Owen.Build version share it), started lazily by the VSIX on the first analysis, `DOTNET_ROLL_FORWARD=Major`, no window, stdin/stdout piped for the protocol, stderr captured into the VSIX's output pane (never discarded). Killed with devenv (Windows job object, `KILL_ON_JOB_CLOSE`) so it is never orphaned. +- **Framing.** Every message both ways is `Content-Length: \r\n\r\n` + `n` bytes of UTF-8 JSON. Nothing else is ever written to the service's stdout: the service replaces `Console.Out` with stderr at start-up and writes frames to the raw stdout stream only. +- **Handshake.** First client frame `{"type":"hello","protocol":"owen-live/1"}`; the service answers `{"type":"hello","protocol":"owen-live/1","host":"","capabilities":[...]}`. Any other protocol string, a malformed header, a non-JSON body, an unknown `type`: the service writes one `{"type":"fatal","message":...}` frame when it still can, prints the reason to stderr and exits **3**. The client treats a `fatal` frame or EOF as service death (K6). No guessing, no resynchronisation. +- **Requests.** `{"type":"analyze","id":n,"key":"","version":v,"request":"","documents":[{"path","text"}],"generated":[{"path","text"}]}` — `documents`: the open documents of the project, with their current (unsaved) text; `generated`: the project's source-generated documents with the path Roslyn gives them. `{"type":"cancel","id":n}`; `{"type":"shutdown"}` (exit 0). +- **Responses.** `{"type":"result","id":n,"key","version","status":"ok"|"superseded"|"cancelled"|"error","diagnostics":[...],"timing":{...}}`. +- **Determinism.** Same request -> same diagnostics in the same order (the core's order; host diagnostics first, in the build host's order). + +## 5. Cancellation and versioning contract (P7, K4) + +- **Client.** Each edit of a project's open document (re)starts a **250 ms** debounce for that project. When it fires, the client takes ONE workspace snapshot, assigns `version = ++counter[project]` and sends `analyze`. A response is **published only if** its `version` equals the latest version issued for that project; anything older is dropped on arrival, whatever its order of arrival. Diagnostics are always mapped onto the text snapshot the request was taken from and translated forward with tracking spans; a buffer edited after the request keeps the last published result until the newer one arrives. +- **Service.** Requests run one at a time. A queued `analyze` for a key that has a newer queued `analyze` is answered `superseded` without running; `cancel` of a queued request answers `cancelled`. A running analysis is not interrupted mid-extraction (the extractor is not cancellable) — its answer is dropped by the client rule above. +- **Stale never reappears:** a response with version N arriving after N+1 was issued is never shown (K4; mutation M1 removes the version check and K4 must go red). + +## 6. Diagnostic contract (P4) and the coordinate gap + +- **Carried per diagnostic:** `code`, `severity` (error|warning, the host's `OwenSeverity` applied by the core exactly as for the build), `message` (the core's text, byte-identical to the msbuild line's), `file` (absolute), `line`, `column` (when known), `origin` (`core` | `host`), `source` = `Owen`, `extension` (the active extension ids of the project; a finding is not attributed to one extension, because the core does not attribute it), `related` (`file`, `line`, `column?`, `message`: the core's witness/flow steps). +- **Source:** the core's `Finding` through `own-cli ownir --format sarif` (existing, pinned renderer): `ruleId`, `level`, `message.text`, `region.startLine/startColumn`, `codeFlows`/`relatedLocations`. Host conditions (OWENB) come from the build host's own records, not parsed from text. +- **Gap 1 — columns.** `Finding.column` exists, but the state-protocol lowering emits lines only (`ProtocolLowering.Line`), so OWN002 has **no column** in the facts or the finding. Adding columns to the protocol ops would change facts bytes and the core's pinned output: forbidden here (P2, P13). **Coordinate contract (narrowest):** when the core gives a column, the span starts there; when it gives none, the span is the line's text without leading/trailing whitespace, and the column shown is that span's first character (1-based). The VSIX computes it from the snapshot the request was taken from; it is a coordinate, never a verdict. The build's msbuild line has no column at all: **declared renderer difference** for the parity test. +- **Gap 2 — where OWN002 points (issue #393).** For a stale state token the core reports the finding at the **region entry** (`OrderProtocol.WithDraft(order, draft =>`), and its witness step "used here after it was released/returned" at the stale use (the second `draft.Submit(now);`). Moving the primary location is a core change (forbidden, #393 stays open). The VS host therefore shows the finding **once in the Error List at the core's location** (identical to the build's row), and draws a squiggle at the primary span **and at each witness step** of the same finding, with the step's message. The squiggle on the second `draft.Submit(now);` is the core's witness step, not a location the VSIX derived. This is the acceptance's "squiggle on the second Submit". + +## 7. Acceptance scenarios + +Fixture `tests/owen-live/LiveFixture/`: a net8.0 project with `PackageReference Owen.TypedBuilder` (local feed, as in the OX-01 gate), an `Order` declared for the generator, and `Use.cs` whose `Run` has ONE `draft.Submit(now);` in a `WithDraft` region (clean). Synthetic second extension `tests/owen-extensions/Owen.TestProtocol/` (never shipped): a descriptor + its own incremental generator that emits a different protocol (`Turnstile`) as generated source. + +| id | scenario | pass | where | +|---|---|---|---| +| K1 | unsaved edit that stays clean | no OWN row, no OWN tag; Owen ran (a result for that version arrived) | service tests + VS run | +| K2 | unsaved edit inserting a second `draft.Submit(now);` | OWN002 Error List row (file, line = region entry, column per §6), squiggle tags at the primary span and on the inserted line, no save, no build | service tests + **VS run** | +| K3 | delete the inserted line (unsaved) | the OWN002 row and every tag disappear | service tests + **VS run** | +| K4 | response N delivered after N+1 | N is never published; final state = N+1's | client unit test (scheduler/gate) | +| K5 | syntax-broken edit | no crash, no stale-looking verdict: a result arrives (findings or an OWENB010 refusal row, as the build would give for the same text); the next good edit recovers | service tests | +| K6 | the service dies | one operational Error List row `OWENV001` (Owen live analysis stopped: exit code, stderr tail); restart on the next edit, at most 3 restarts in 5 minutes, then the row stays and says live analysis is off until the solution is reopened. Never silent. | client unit test + service kill test | +| K7 | an active extension requires an unknown capability | OWENB004 row live, on the descriptor; no analysis claimed | service tests | +| K8 | two extensions (Typed Builder + Owen.TestProtocol) | both protocols' findings live, no change to the service or the VSIX | service tests (+ VS run if the second fixture is opened) | +| P9 | `CS1061` and OWN002 at once | both rows in the Error List; Owen never converts a CS diagnostic | **VS run** | +| P10 | build/live parity | for the same saved source: `dotnet build` OWN rows == live diagnostics on (code, severity, message, file, line); a live-only or build-only OWN verdict fails | `scripts/owen_live_gate.py` | + +**"Live"** means: the diagnostics reflect the text in the editor, unsaved, within the latency budget, without a save, a build or any user command. + +## 8. Latency and resources (P15) — thresholds fixed now + +Measured on the CI Windows runner in the real VS run (and on Linux for the service alone), on the small fixture: +- cold start (service spawn + hello + first analysis): report only; +- warm analysis inside the service (extractor + core): **p95 < 750 ms**; +- edit -> squiggle visible (end of debounce included): **p95 < 1 s** (over >= 10 edits); NO_GO if the warm small-project diagnostic takes several seconds; +- clean edit -> disappearance: **p95 < 1 s**; +- burst of 20 keystrokes 30 ms apart -> exactly 1-2 analyses run, the last version published; +- UI thread: no Owen handler longer than **50 ms** (stopwatch around every UI-thread callback, max reported); +- service working set after 100 analyses: **< 2x** the working set after 10 (no unbounded growth). + +## 9. Snipper reuse (P11) — ADAPT only, MIT, origin recorded in each file + +| chunk | origin | how | +|---|---|---| +| SDK-style VSIX project skeleton (`Microsoft.VSSDK.BuildTools`, pkgdef/vsix properties) | `extensions/snipper-vs/Snipper.VisualStudio/Snipper.VisualStudio.csproj` | ADAPT | +| real-IDE smoke: vswhere instance pick, deploy with `DeployVsixExtensionFiles` into a dedicated root suffix (never the shared `Exp`), restart-after-registration relaunch, activity-log evidence | `extensions/snipper-vs/scripts/vs-smoke.ps1` | ADAPT | +| test project compiling the VSIX's VS-independent sources by `` | `Snipper.VisualStudio.IntegrationTests.csproj` | ADAPT (pattern) | +| NOT reused | LSP client spine, PATH fallback binary locator, Snipper commands, its process lifecycle (stderr lost, no orphan protection: rejected in the OX-01 audit) | — | + +## 10. Real Visual Studio (P14) + +- Probe on `windows-latest` (this branch, run 37163037704): **Visual Studio Enterprise 2026, 18.10**, with the extension-development workload, `devenv` and `VSIXInstaller` present, interactive session. A DTE object is obtained, but calls were rejected during first launch: the first-run experience must be handled (UI Automation dismissal or settings preseed) — this is infrastructure work, not product. +- Supported target: VS 2022 17.10+ and VS 2026 (`InstallationTarget [17.10,19.0)`); proven on whatever the runner has (18.x). VS 2022 unproven unless a run happens. +- The VS run (`scripts/vs/owen-vs-acceptance.ps1`, workflow `owen-vs.yml`): build + deploy the VSIX into a dedicated root suffix; pack Owen.Build / Owen.TypedBuilder into a local feed; restore the fixture; launch devenv; drive it with DTE: open `Use.cs`, insert the second `draft.Submit(now);` through `EditPoint` (unsaved), poll the Error List (`ErrorItems`: description, file, line, column, project), `Navigate()` the OWN002 row and read the caret line/column, delete the line, poll until gone. The tagger's tags are observed through an opt-in trace (`OWEN_LIVE_TRACE=`: one JSON line per tag set the tagger publishes — file, snapshot version, line, column, length, code) since DTE cannot read tags. A screenshot of the squiggle is kept. +- **GO without a passing real VS run is forbidden.** If the runner cannot be driven, the verdict is NO_GO with that measured blocker. + +## 11. Gates kept green + +`tests/run_tests.py`, ruff, mypy, `cargo fmt/clippy/test`, `protocol_gate --rust`, `heap_effects_gate`, `typed_builder_gate --rust`, `owen_extension_gate` (55/55), Owen.Cli packaging, the P-022 merge gate. Plus new: `tests/check_extractor_in_process.py` (E1–E4), `scripts/owen_live_gate.py` (service + parity + K-scenarios on Linux and Windows), the VS run. + +Baseline on `e889f8b` (clean worktree): filled in by Amendment 1 when the runs finish; a pre-existing red is recorded, not fixed. + +## 12. Mutation controls + +| id | mutation | must turn red | +|---|---|---| +| M1 | the client ignores `version` (publishes every response) | K4 | +| M2 | the service cannot start (bad host path) | the OWENV001 row appears (K6 test) | +| M3 | wrong line/column mapping (off by one) | navigation/coordinate test (VS run and the client coordinate unit test) | +| M4 | the service routes generated files only for `Owen.TypedBuilder.Generator` | K8 (Owen.TestProtocol's finding missing) | +| M5 | live renders with a different verdict path (e.g. severity forced, or msbuild text parsed with a different message) | P10 parity | +| M6 | the unsaved document is read from disk | K2 (service test) and E4 | + +## 13. Out of scope + +VS Code, an LSP server, Rider/ReSharper, code fixes/lightbulbs, hover/explain UI, the Memory/TypeDisciplines extensions, a second aggregate in Typed Builder, BCL summaries, typed write targets, whole-solution incremental analysis (the unit is one project), Marketplace publishing, analysis of a brand-new file that was never saved (the input set is the project's files on disk; their contents come from the editor), columns for state-protocol findings (#393 / the facts), live analysis before the first design-time build of a project. + +## 14. Verdict + +`GO_OWEN_VISUAL_STUDIO_ALPHA` only if all hold: an unsaved edit goes through the one extractor and the authoritative core to OWN002, a squiggle, an Error List row with navigation to the reported location, and disappears when the line is deleted — in a real Visual Studio; the parity gate passes; K1–K8 pass where registered; thresholds of §8 hold; mutations M1–M6 go red. Otherwise `NO_GO_OWEN_VISUAL_STUDIO_ALPHA` with the exact measured blocker. After GO: STOP. From 421736ffd23ddbe3fa60666514fa39732d0563dc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:02:51 +0000 Subject: [PATCH 05/23] OX-02 P1/P2: the extractor runs in-process over an unsaved-buffer overlay InProcessExtractor.Run(args, cwd, overlay) runs the ONE extractor program inside a long-lived host: same arguments, same facts; its one source read consults an in-memory overlay first, and stdout/stderr are captured. The two run collectors that were not reset at entry (GuardedFactsViolations, OrphanedAwaitables.Sites) now are, a no-op for the CLI. tests/check_extractor_in_process.py: 98 jobs in one process, forward and reversed, byte-identical to the command line (E1/E2); overlay == disk changes nothing (E3); a differing overlay is read instead of the disk (E4, M6): 198/198. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .../roslyn/OwnSharp.Extractor/InProcess.cs | 90 +++++++++ frontend/roslyn/OwnSharp.Extractor/Program.cs | 11 +- tests/check_extractor_in_process.py | 184 ++++++++++++++++++ .../InProcessDriver/InProcessDriver.csproj | 15 ++ tests/owen-live/InProcessDriver/Program.cs | 19 ++ 5 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 frontend/roslyn/OwnSharp.Extractor/InProcess.cs create mode 100644 tests/check_extractor_in_process.py create mode 100644 tests/owen-live/InProcessDriver/InProcessDriver.csproj create mode 100644 tests/owen-live/InProcessDriver/Program.cs diff --git a/frontend/roslyn/OwnSharp.Extractor/InProcess.cs b/frontend/roslyn/OwnSharp.Extractor/InProcess.cs new file mode 100644 index 00000000..8bd28684 --- /dev/null +++ b/frontend/roslyn/OwnSharp.Extractor/InProcess.cs @@ -0,0 +1,90 @@ +using System.Reflection; +using System.Runtime.ExceptionServices; + +// OX-02 (docs/notes/owen-visual-studio-preregistration.md): the extractor as a library. +// +// The extractor is ONE program; this file adds no second frontend. It lets a long-lived host +// (the `owen serve` IDE service) run that same program in its own process, with two +// differences from a command line and none in what is extracted: +// * the source of a path may come from an in-memory overlay (an IDE's unsaved buffer) +// instead of the disk — read at the one place the program reads a source file; +// * stdout/stderr are captured instead of inherited, so nothing the program prints can +// reach the host's own protocol stream. +// The arguments are the command line's arguments, so a run here and `ownsharp-extract` with +// the same arguments over the same file contents write the same facts, byte for byte +// (tests/check_extractor_in_process.py pins that over the extractor goldens). + +partial class Program +{ + /// Full path -> contents of an unsaved buffer, for the run in progress. + internal static IReadOnlyDictionary? SourceOverlay; + + internal static StringComparer PathComparer => + OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + + static bool SourceExists(string path) => + SourceOverlay?.ContainsKey(Path.GetFullPath(path)) == true || File.Exists(path); + + static string ReadSource(string path) => + SourceOverlay is not null && SourceOverlay.TryGetValue(Path.GetFullPath(path), out var text) + ? text + : File.ReadAllText(path); +} + +namespace OwnSharp.Extractor +{ + /// Runs the extractor program inside the calling process. + public static class InProcessExtractor + { + // The program keeps run state in statics (it resets them at entry) and reads the + // process's current directory for the paths it reports: one run at a time. + private static readonly object Gate = new(); + + private static readonly MethodInfo Entry = + typeof(Program).Assembly.EntryPoint + ?? throw new InvalidOperationException("ownsharp-extract has no entry point"); + + /// + /// Run ownsharp-extract with (the command line's own + /// arguments) in . maps a + /// full source path to the text to read for it instead of the file. Returns the exit + /// code and everything the program printed. + /// + public static (int ExitCode, string Output) Run( + IReadOnlyList args, + string workingDirectory, + IReadOnlyDictionary? overlay) + { + lock (Gate) + { + var output = new StringWriter(); + var (stdout, stderr, cwd) = (Console.Out, Console.Error, Environment.CurrentDirectory); + Program.SourceOverlay = overlay is null + ? null + : new Dictionary( + overlay.Select(kv => KeyValuePair.Create(Path.GetFullPath(kv.Key), kv.Value)), + Program.PathComparer); + try + { + Console.SetOut(output); + Console.SetError(output); + Environment.CurrentDirectory = workingDirectory; + var rc = Entry.Invoke(null, [args.ToArray()]); + return ((int)rc!, output.ToString()); + } + catch (TargetInvocationException ex) when (ex.InnerException is not null) + { + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + throw; + } + finally + { + Program.SourceOverlay = null; + Environment.CurrentDirectory = cwd; + Console.SetOut(stdout); + Console.SetError(stderr); + } + } + } + } +} diff --git a/frontend/roslyn/OwnSharp.Extractor/Program.cs b/frontend/roslyn/OwnSharp.Extractor/Program.cs index 621f4bc6..6bda2a27 100644 --- a/frontend/roslyn/OwnSharp.Extractor/Program.cs +++ b/frontend/roslyn/OwnSharp.Extractor/Program.cs @@ -111,6 +111,11 @@ // as belonging to a Fody-enabled project must not stay "weaver-owned" for an unrelated later // invocation in the same process (a sticky false positive). Same reset discipline as above. WeaverOwnedFiles.Clear(); +// OX-02: the two collectors below are filled while a run walks the compilation, so a run in a +// long-lived process (InProcessExtractor, the IDE service) must start them empty too. A fresh +// process starts them empty anyway: for the CLI this is a no-op. +GuardedFactsViolations.Clear(); +OrphanedAwaitables.Sites.Clear(); // `extract` verb: the tool's one job is extraction, so an optional leading `extract` // makes the advertised `ownsharp-extract extract --project App.csproj --out facts.json` // UX real while the bare form (no verb) stays the default and back-compatible. (The @@ -5698,7 +5703,7 @@ static bool IsPublicCtor(SyntaxTokenList modifiers) // Defensive: an explicit input that is not a readable file (a directory // passed by mistake, a deleted path) is skipped with a note, never an // unhandled exception that aborts the whole scan. - if (!File.Exists(path)) + if (!SourceExists(path)) { Console.Error.WriteLine($"ownsharp-extract: skipping (not a file): {path}"); continue; @@ -5706,7 +5711,9 @@ static bool IsPublicCtor(SyntaxTokenList modifiers) string text; try { - text = File.ReadAllText(path); + // OX-02: an IDE's unsaved buffer for this path wins over the file on disk + // (InProcessExtractor); with no overlay — the CLI — this is File.ReadAllText. + text = ReadSource(path); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { diff --git a/tests/check_extractor_in_process.py b/tests/check_extractor_in_process.py new file mode 100644 index 00000000..65992c89 --- /dev/null +++ b/tests/check_extractor_in_process.py @@ -0,0 +1,184 @@ +"""OX-02 P2: the extractor run as a library writes what the command line writes. + +`InProcessExtractor.Run` (frontend/roslyn/OwnSharp.Extractor/InProcess.cs) is how the IDE +service runs the ONE extractor program inside a long-lived process. This check holds it to +the command line: + + E1 every job, run in-process, writes facts byte-identical to `ownsharp-extract` run as a + process with the same arguments in the same directory, and exits with the same code; + E2 the same, with the jobs run in the reverse order inside one process: no run leaks + state into the next; + E3 an overlay equal to the file on disk changes nothing; + E4 an overlay that differs from the disk is read INSTEAD of the disk: the facts equal a + command-line run over a copy of the tree with that file changed on disk, and differ + from the facts of the unchanged tree (an implementation that kept reading the disk + would fail here: mutation M6). + +Run: python tests/check_extractor_in_process.py (needs the .NET 8 SDK) +""" + +from __future__ import annotations + +import json +import os +import shutil +import subprocess +import sys +import tempfile + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +EXTRACTOR = os.path.join(ROOT, "frontend", "roslyn", "OwnSharp.Extractor") +DRIVER = os.path.join(ROOT, "tests", "owen-live", "InProcessDriver") +SAMPLES = "frontend/roslyn/samples" +PROTO = "frontend/roslyn/protocol-samples" + +FLAG_SETS: list[list[str]] = [ + [], + ["--flow-locals"], + ["--flow-locals", "--stats"], + ["--fix-candidates"], + ["--flow-locals", "--body-throw-edges"], + ["--no-event-leaks", "--flow-locals"], +] + + +def _run(cmd: list[str], cwd: str = ROOT) -> subprocess.CompletedProcess[str]: + return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, encoding="utf-8", + errors="replace", check=False) + + +def _build(project: str) -> None: + done = _run(["dotnet", "build", project, "-c", "Release", "-nologo", "-v", "q"]) + if done.returncode != 0: + print(done.stdout[-3000:]) + raise SystemExit(f"FAIL: {project} does not build") + + +def _jobs() -> list[tuple[str, list[str]]]: + """(label, arguments without -o); every job runs with the repository root as cwd.""" + jobs: list[tuple[str, list[str]]] = [] + for flags in FLAG_SETS: + jobs.append((f"samples {' '.join(flags)}", [SAMPLES, *flags])) + for name in sorted(os.listdir(os.path.join(ROOT, SAMPLES))): + if name.endswith(".cs"): + jobs.append((f"samples/{name}", [f"{SAMPLES}/{name}", "--flow-locals"])) + for name in sorted(os.listdir(os.path.join(ROOT, PROTO, "cases"))): + if name.endswith(".cs"): + jobs.append((f"cases/{name}", [f"{PROTO}/Api", f"{PROTO}/cases/{name}", + "--flow-locals"])) + for sub in ("same-assembly", "unrelated"): + jobs.append((sub, [f"{PROTO}/{sub}", "--flow-locals"])) + jobs.append(("heap-effects-samples", ["frontend/roslyn/heap-effects-samples", + "--flow-locals"])) + jobs.append(("project-input-sample", + ["frontend/roslyn/project-input-sample/ProjectInputSample.csproj", + "--flow-locals"])) + jobs.append(("OrderBackend", ["samples/OrderBackend/OrderBackend/OrderBackend.csproj", + "--flow-locals"])) + return jobs + + +def _cli(dll: str, args: list[str], out: str, cwd: str = ROOT) -> int: + return _run(["dotnet", "exec", dll, *args, "-o", out], cwd=cwd).returncode + + +def _in_process(driver: str, jobs: list[dict[str, object]], tmp: str) -> list[int]: + path = os.path.join(tmp, "jobs.jsonl") + with open(path, "w", encoding="utf-8") as f: + for job in jobs: + f.write(json.dumps(job) + "\n") + done = _run(["dotnet", "exec", driver, path]) + if done.returncode != 0: + raise SystemExit(f"FAIL: the in-process driver exited {done.returncode}: " + f"{done.stderr[-2000:]}") + lines = [ln for ln in done.stdout.splitlines() if ln.strip()] + return [int(json.loads(ln)["rc"]) for ln in lines] + + +def _read(path: str) -> bytes | None: + try: + with open(path, "rb") as f: + return f.read() + except FileNotFoundError: + return None + + +def main() -> int: + _build(EXTRACTOR) + _build(DRIVER) + dll = os.path.join(EXTRACTOR, "bin", "Release", "net8.0", "ownsharp-extract.dll") + driver = os.path.join(DRIVER, "bin", "Release", "net8.0", "InProcessDriver.dll") + fails: list[str] = [] + checks = 0 + jobs = _jobs() + with tempfile.TemporaryDirectory() as tmp: + cli_out = [os.path.join(tmp, f"cli-{i}.json") for i in range(len(jobs))] + cli_rc = [_cli(dll, args, out) for (_, args), out in zip(jobs, cli_out, strict=True)] + + for order, label in ((list(range(len(jobs))), "E1"), + (list(reversed(range(len(jobs)))), "E2")): + outs = {i: os.path.join(tmp, f"{label}-{i}.json") for i in order} + rcs = _in_process(driver, [{"cwd": ROOT, "args": [*jobs[i][1], "-o", outs[i]]} + for i in order], tmp) + for i, rc in zip(order, rcs, strict=True): + checks += 1 + name = jobs[i][0] + if rc != cli_rc[i]: + fails.append(f"{label} {name}: exit {rc}, the command line exited {cli_rc[i]}") + elif _read(outs[i]) != _read(cli_out[i]): + fails.append(f"{label} {name}: facts differ from the command line's") + + # E3/E4: OrderBackend, the endpoint file overlaid + project = "samples/OrderBackend/OrderBackend" + target = os.path.join(ROOT, project, "OrderEndpoints.cs") + with open(target, encoding="utf-8") as f: + original = f.read() + same = os.path.join(tmp, "same.cs") + with open(same, "w", encoding="utf-8", newline="") as f: + f.write(original) + # a non-escaping disposable local: the flow pass emits a function for it + changed_text = original + ( + "\nnamespace OrderBackend { static class OverlayProbe { static void M() " + "{ var s = new System.IO.MemoryStream(); s.WriteByte(1); } } }\n") + changed = os.path.join(tmp, "changed.cs") + with open(changed, "w", encoding="utf-8", newline="") as f: + f.write(changed_text) + args = [f"{project}/OrderBackend.csproj", "--flow-locals"] + base_out = os.path.join(tmp, "e3-base.json") + _cli(dll, args, base_out) + e3 = os.path.join(tmp, "e3.json") + e4 = os.path.join(tmp, "e4.json") + _in_process(driver, [ + {"cwd": ROOT, "args": [*args, "-o", e3], "overlay": {target: same}}, + {"cwd": ROOT, "args": [*args, "-o", e4], "overlay": {target: changed}}, + ], tmp) + checks += 1 + if _read(e3) != _read(base_out): + fails.append("E3: an overlay equal to the disk changed the facts") + # the same change made on disk, in a copy of the tree, run by the command line + copy = os.path.join(tmp, "tree") + # bin/ included: a .csproj input auto-references the project's built output + shutil.copytree(os.path.join(ROOT, project), os.path.join(copy, project)) + with open(os.path.join(copy, project, "OrderEndpoints.cs"), "w", encoding="utf-8", + newline="") as f: + f.write(changed_text) + disk_out = os.path.join(tmp, "e4-disk.json") + _cli(dll, args, disk_out, cwd=copy) + checks += 1 + e4_bytes = _read(e4) + if e4_bytes is None or e4_bytes == _read(base_out): + fails.append("E4: an overlay that differs from the disk did not change the facts " + "(the disk was read instead of the buffer)") + elif e4_bytes != _read(disk_out): + fails.append("E4: overlay facts differ from the command line's over the same " + "contents on disk") + + for f in fails: + print(f"FAIL: {f}") + print(f"extractor in-process: {checks - len(fails)}/{checks} passed, {len(fails)} failed " + f"({len(jobs)} jobs)") + return 1 if fails else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/owen-live/InProcessDriver/InProcessDriver.csproj b/tests/owen-live/InProcessDriver/InProcessDriver.csproj new file mode 100644 index 00000000..58c596e1 --- /dev/null +++ b/tests/owen-live/InProcessDriver/InProcessDriver.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + false + + + + + diff --git a/tests/owen-live/InProcessDriver/Program.cs b/tests/owen-live/InProcessDriver/Program.cs new file mode 100644 index 00000000..ebb676bb --- /dev/null +++ b/tests/owen-live/InProcessDriver/Program.cs @@ -0,0 +1,19 @@ +// jobs file: one JSON object per line +// {"cwd": "...", "args": ["..."], "overlay": {"": ""}} +// prints one JSON line per job: {"rc": n, "output": "..."} +using System.Text.Json; +using OwnSharp.Extractor; + +foreach (var line in File.ReadLines(args[0])) +{ + if (line.Length == 0) + continue; + using var job = JsonDocument.Parse(line); + var root = job.RootElement; + var jobArgs = root.GetProperty("args").EnumerateArray().Select(a => a.GetString()!).ToList(); + Dictionary? overlay = null; + if (root.TryGetProperty("overlay", out var o)) + overlay = o.EnumerateObject().ToDictionary(p => p.Name, p => File.ReadAllText(p.Value.GetString()!)); + var (rc, output) = InProcessExtractor.Run(jobArgs, root.GetProperty("cwd").GetString()!, overlay); + Console.WriteLine(JsonSerializer.Serialize(new { rc, output })); +} From 44a25a203728c9f8c8ca74b1cc3908a28b12dd41 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:20:40 +0000 Subject: [PATCH 06/23] OX-02: owen serve, the live request, and the generic Owen.VisualStudio - OwenHost: the build host's contract (descriptors, input set, refusals) shared by build-check (byte-identical output) and the new live service. - owen serve: owen-live/1 (Content-Length framing, hello, fatal exit 3), one analysis at a time, newest-per-project wins (superseded/cancelled); each analysis is the build host's over the editor's text, through the in-process extractor and the packaged Rust core's SARIF (columns + witness steps). - Owen.Build writes obj/owen/live.txt (same request lines + the service path) in design-time builds too: Visual Studio learns the active extensions with no build. - Owen.VisualStudio (VSIX, knows no extension): workspace snapshots (unsaved open documents + source-generated documents) -> supervised service (job object, restart budget, OWENV001) -> version gate -> IErrorTag squiggles (finding and witness steps) + ITableDataSource Error List rows; opt-in trace for the real-IDE run. - Owen.TestProtocol: a synthetic second extension with its own generator (K8, M4). - scripts/owen_live_gate.py (27 checks on Linux): L0, protocol S*, K1-K8, P10 build/live parity x5, latency p95 208 ms, memory flat, client tests incl. K4/K6/M1/M2. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .../Owen.Build/build/Owen.Build.targets | 37 +- .../Live/ChildProcessGuard.cs | 92 +++ .../Owen.VisualStudio/Live/LiveEngine.cs | 285 ++++++++ .../Owen.VisualStudio/Live/LiveProtocol.cs | 252 ++++++++ .../Owen.VisualStudio/Live/LiveScheduler.cs | 103 +++ .../Owen.VisualStudio/Live/LiveService.cs | 328 ++++++++++ .../Owen.VisualStudio.csproj | 56 ++ .../Owen.VisualStudio/OwenErrorListSource.cs | 144 +++++ .../Owen.VisualStudio/OwenErrorTagger.cs | 116 ++++ .../Owen.VisualStudio/OwenLiveMonitor.cs | 220 +++++++ frontend/roslyn/Owen.VisualStudio/OwenLog.cs | 75 +++ .../source.extension.vsixmanifest | 28 + .../roslyn/OwnSharp.Cli/BuildCheckCommand.cs | 294 +-------- frontend/roslyn/OwnSharp.Cli/LiveAnalysis.cs | 241 +++++++ frontend/roslyn/OwnSharp.Cli/OwenHost.cs | 306 +++++++++ frontend/roslyn/OwnSharp.Cli/Program.cs | 16 + frontend/roslyn/OwnSharp.Cli/ServeCommand.cs | 310 +++++++++ scripts/owen_live_gate.py | 608 ++++++++++++++++++ tests/check_extractor_in_process.py | 92 ++- .../Owen.TestProtocol.csproj | 31 + .../Owen.TestProtocol/TurnstileGenerator.cs | 76 +++ .../buildTransitive/Owen.TestProtocol.props | 5 + .../buildTransitive/owen-extension.json | 13 + .../LiveClientTests/LiveClientTests.csproj | 19 + tests/owen-live/LiveClientTests/Program.cs | 159 +++++ tests/owen-live/LiveFixture/Gate.cs.txt | 15 + tests/owen-live/LiveFixture/Order.cs.txt | 51 ++ tests/owen-live/LiveFixture/README.md | 12 + tests/owen-live/LiveFixture/Use.cs.txt | 16 + 29 files changed, 3696 insertions(+), 304 deletions(-) create mode 100644 frontend/roslyn/Owen.VisualStudio/Live/ChildProcessGuard.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/Live/LiveEngine.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/Live/LiveProtocol.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/Live/LiveScheduler.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/Live/LiveService.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj create mode 100644 frontend/roslyn/Owen.VisualStudio/OwenErrorListSource.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/OwenLog.cs create mode 100644 frontend/roslyn/Owen.VisualStudio/source.extension.vsixmanifest create mode 100644 frontend/roslyn/OwnSharp.Cli/LiveAnalysis.cs create mode 100644 frontend/roslyn/OwnSharp.Cli/OwenHost.cs create mode 100644 frontend/roslyn/OwnSharp.Cli/ServeCommand.cs create mode 100644 scripts/owen_live_gate.py create mode 100644 tests/owen-extensions/Owen.TestProtocol/Owen.TestProtocol.csproj create mode 100644 tests/owen-extensions/Owen.TestProtocol/TurnstileGenerator.cs create mode 100644 tests/owen-extensions/Owen.TestProtocol/buildTransitive/Owen.TestProtocol.props create mode 100644 tests/owen-extensions/Owen.TestProtocol/buildTransitive/owen-extension.json create mode 100644 tests/owen-live/LiveClientTests/LiveClientTests.csproj create mode 100644 tests/owen-live/LiveClientTests/Program.cs create mode 100644 tests/owen-live/LiveFixture/Gate.cs.txt create mode 100644 tests/owen-live/LiveFixture/Order.cs.txt create mode 100644 tests/owen-live/LiveFixture/README.md create mode 100644 tests/owen-live/LiveFixture/Use.cs.txt diff --git a/frontend/roslyn/Owen.Build/build/Owen.Build.targets b/frontend/roslyn/Owen.Build/build/Owen.Build.targets index d7b3638c..2304892d 100644 --- a/frontend/roslyn/Owen.Build/build/Owen.Build.targets +++ b/frontend/roslyn/Owen.Build/build/Owen.Build.targets @@ -18,22 +18,51 @@ <_OwenObjDir>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)', 'owen')) - + + <_OwenDotnet>$(DOTNET_HOST_PATH) <_OwenDotnet Condition="'$(_OwenDotnet)' == ''">dotnet <_OwenGeneratedRoot>$(CompilerGeneratedFilesOutputPath) <_OwenGeneratedRoot Condition="'$(_OwenGeneratedRoot)' != '' and !$([System.IO.Path]::IsPathRooted('$(_OwenGeneratedRoot)'))">$(MSBuildProjectDirectory)/$(_OwenGeneratedRoot) - <_OwenRequest>$(_OwenObjDir)request.txt + <_OwenRequestLine Remove="@(_OwenRequestLine)" /> <_OwenRequestLine Include="project $(MSBuildProjectFullPath)" /> <_OwenRequestLine Include="@(OwenExtensionDescriptor->'descriptor %(FullPath)')" /> <_OwenRequestLine Include="generated-root $(_OwenGeneratedRoot)" Condition="'$(_OwenGeneratedRoot)' != ''" /> <_OwenRequestLine Include="severity $(OwenSeverity)" /> + + + + + + + <_OwenLiveLine Include="@(_OwenRequestLine)" /> + <_OwenLiveLine Include="host $(_OwenHostDir)ownsharp.dll" /> + <_OwenLiveLine Include="dotnet $(DOTNET_HOST_PATH)" Condition="'$(DOTNET_HOST_PATH)' != ''" /> + + + + + + + + <_OwenRequest>$(_OwenObjDir)request.txt + + <_OwenRequestLine Include="manifest $(_OwenObjDir)extensions.json" /> <_OwenRequestLine Include="emit-facts $(OwenEmitFacts)" Condition="'$(OwenEmitFacts)' != ''" /> diff --git a/frontend/roslyn/Owen.VisualStudio/Live/ChildProcessGuard.cs b/frontend/roslyn/Owen.VisualStudio/Live/ChildProcessGuard.cs new file mode 100644 index 00000000..e9c1e16a --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/Live/ChildProcessGuard.cs @@ -0,0 +1,92 @@ +// OX-02 (preregistration §4): the service never outlives Visual Studio. On Windows the child is +// put in a job object that kills it when the job's last handle — held by this process — closes, +// so a crashed or killed devenv cannot leave an orphan. Elsewhere (the tests) the parent kills +// it on dispose. +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Owen.VisualStudio.Live +{ + internal static class ChildProcessGuard + { + private static IntPtr _job = IntPtr.Zero; + private static readonly object Gate = new object(); + + public static void Attach(Process process) + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + return; + lock (Gate) + { + if (_job == IntPtr.Zero) + { + var job = CreateJobObject(IntPtr.Zero, null); + if (job == IntPtr.Zero) + return; + var info = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); + info.BasicLimitInformation.LimitFlags = 0x2000; // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + var size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + var ptr = Marshal.AllocHGlobal(size); + try + { + Marshal.StructureToPtr(info, ptr, false); + if (!SetInformationJobObject(job, 9 /* JobObjectExtendedLimitInformation */, ptr, (uint)size)) + return; + } + finally + { + Marshal.FreeHGlobal(ptr); + } + _job = job; + } + AssignProcessToJobObject(_job, process.Handle); + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct JOBOBJECT_BASIC_LIMIT_INFORMATION + { + public long PerProcessUserTimeLimit; + public long PerJobUserTimeLimit; + public uint LimitFlags; + public UIntPtr MinimumWorkingSetSize; + public UIntPtr MaximumWorkingSetSize; + public uint ActiveProcessLimit; + public UIntPtr Affinity; + public uint PriorityClass; + public uint SchedulingClass; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IO_COUNTERS + { + public ulong ReadOperationCount; + public ulong WriteOperationCount; + public ulong OtherOperationCount; + public ulong ReadTransferCount; + public ulong WriteTransferCount; + public ulong OtherTransferCount; + } + + [StructLayout(LayoutKind.Sequential)] + private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION + { + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + public IO_COUNTERS IoInfo; + public UIntPtr ProcessMemoryLimit; + public UIntPtr JobMemoryLimit; + public UIntPtr PeakProcessMemoryUsed; + public UIntPtr PeakJobMemoryUsed; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr CreateJobObject(IntPtr attributes, string? name); + + [DllImport("kernel32.dll")] + private static extern bool SetInformationJobObject(IntPtr job, int infoClass, IntPtr info, uint length); + + [DllImport("kernel32.dll")] + private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/Live/LiveEngine.cs b/frontend/roslyn/Owen.VisualStudio/Live/LiveEngine.cs new file mode 100644 index 00000000..d95072ed --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/Live/LiveEngine.cs @@ -0,0 +1,285 @@ +// OX-02: the editor-independent heart of Owen.VisualStudio. An edit of a project schedules one +// analysis of a snapshot of it; the answer becomes entries (Error List rows and squiggle spans) +// only if it is still the newest. Knows no Owen extension: what is analysed is whatever the +// project's live request (obj/owen/live.txt, written by Owen.Build) names. +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Owen.VisualStudio.Live +{ + /// What an analysis is taken over: one project, as the editor has it right now. + public sealed class LiveSnapshot + { + public LiveSnapshot(string key, string request, IReadOnlyList documents, + IReadOnlyList generated, object? state = null) + { + Key = key; + Request = request; + Documents = documents; + Generated = generated; + State = state; + } + + /// The project file: one analysis unit. + public string Key { get; } + + /// The project's obj/owen/live.txt. + public string Request { get; } + + public IReadOnlyList Documents { get; } + + public IReadOnlyList Generated { get; } + + /// The editor's own handle on this snapshot (VS: text snapshots, for mapping). + public object? State { get; } + + /// The text of as analysed: the editor's, else the disk's. + public string? TextOf(string file) + { + foreach (var d in Documents) + if (string.Equals(Path.GetFullPath(d.Path), Path.GetFullPath(file), StringComparison.OrdinalIgnoreCase)) + return d.Text; + try + { + return File.Exists(file) ? File.ReadAllText(file) : null; + } + catch (IOException) + { + return null; + } + } + } + + /// One thing to show: an Error List row () or a squiggle only. + public sealed class LiveEntry + { + public string Code { get; set; } = ""; + + public string Severity { get; set; } = ""; + + public string Message { get; set; } = ""; + + public string File { get; set; } = ""; + + /// 1-based; null for a file-level condition. + public int? Line { get; set; } + + /// 1-based column shown (the span's first character, preregistration §6). + public int? Column { get; set; } + + /// 0-based [start, end) within the line, for the squiggle. + public int Start { get; set; } + + public int End { get; set; } + + /// True: the finding's own location (an Error List row and a squiggle). False: one + /// of its witness steps (a squiggle carrying the step's message). + public bool Primary { get; set; } + + public string Origin { get; set; } = ""; + + public string Project { get; set; } = ""; + } + + public sealed class LivePublication + { + public LivePublication(string key, long version, IReadOnlyList entries, LiveSnapshot? snapshot, + IReadOnlyDictionary timing) + { + Key = key; + Version = version; + Entries = entries; + Snapshot = snapshot; + Timing = timing; + } + + public string Key { get; } + + public long Version { get; } + + public IReadOnlyList Entries { get; } + + public LiveSnapshot? Snapshot { get; } + + public IReadOnlyDictionary Timing { get; } + } + + /// What answers an analysis: the supervised service, or a test double. + public interface ILiveAnalyzer + { + Task AnalyzeAsync(string key, long version, string request, + IReadOnlyList documents, IReadOnlyList generated); + + /// The operational condition to show, or null while healthy. + string? Condition { get; } + } + + public sealed class SupervisedAnalyzer : ILiveAnalyzer + { + private readonly LiveSupervisor _supervisor; + + public SupervisedAnalyzer(LiveSupervisor supervisor) + { + _supervisor = supervisor; + } + + public string? Condition => _supervisor.Condition; + + public Task AnalyzeAsync(string key, long version, string request, + IReadOnlyList documents, IReadOnlyList generated) => + _supervisor.AnalyzeAsync(key, version, request, documents, generated); + } + + public sealed class LiveEngine + { + /// The code of Owen.VisualStudio's own operational row (the service stopped). + public const string ServiceCode = "OWENV001"; + + private readonly Func> _snapshot; + private readonly Func _analyzer; + private readonly Action _log; + private readonly LiveDebouncer _debouncer; + private readonly LiveVersionGate _gate = new LiveVersionGate(); + + /// Mutation M1 (preregistration §12): publish every answer, current or not. + public bool IgnoreVersions { get; set; } + + public LiveEngine(Func> snapshot, Func analyzer, + Action log, TimeSpan debounce) + { + _snapshot = snapshot; + _analyzer = analyzer; + _log = log; + _debouncer = new LiveDebouncer(debounce); + } + + public event Action? Published; + + /// An edit (or an open) of something in project . + public void OnEdit(string key) => _debouncer.Schedule(key, () => AnalyzeNowAsync(key)); + + /// Take the snapshot and analyse it now (the debounce's trailing edge). + public async Task AnalyzeNowAsync(string key) + { + LiveSnapshot? snapshot; + try + { + snapshot = await _snapshot(key).ConfigureAwait(false); + } + catch (Exception ex) + { + _log("owen: snapshot of " + key + " failed: " + ex); + return; + } + if (snapshot == null) + { + // no live request: Owen is not active in this project; show nothing for it + Publish(new LivePublication(key, _gate.Issue(key), Array.Empty(), null, new Dictionary())); + return; + } + var version = _gate.Issue(key); + await RunAsync(snapshot, version).ConfigureAwait(false); + } + + /// Analyse a snapshot under an already-issued version (tests drive this directly). + public long Issue(string key) => _gate.Issue(key); + + public async Task RunAsync(LiveSnapshot snapshot, long version) + { + var analyzer = _analyzer(snapshot); + if (analyzer == null) + return; + LiveResponse response; + try + { + response = await analyzer.AnalyzeAsync(snapshot.Key, version, snapshot.Request, + snapshot.Documents, snapshot.Generated).ConfigureAwait(false); + } + catch (LiveServiceDiedException ex) + { + _log("owen: " + ex.Message); + if (IgnoreVersions || _gate.IsCurrent(snapshot.Key, version)) + Publish(new LivePublication(snapshot.Key, version, new[] { Operational(snapshot.Key, ex.Message) }, + snapshot, new Dictionary())); + return; + } + if (response.Status != "ok" && response.Status != "error") + return; // superseded/cancelled: a newer request is on its way + if (!IgnoreVersions && !_gate.IsCurrent(snapshot.Key, response.Version)) + { + _log("owen: dropped the answer for version " + response.Version + " of " + snapshot.Key + " (stale)"); + return; + } + var entries = Entries(response, snapshot); + if (analyzer.Condition is string condition) + entries.Insert(0, Operational(snapshot.Key, condition)); + Publish(new LivePublication(snapshot.Key, response.Version, entries, snapshot, response.Timing)); + } + + private void Publish(LivePublication publication) => Published?.Invoke(publication); + + public static LiveEntry Operational(string key, string message) => new LiveEntry + { + Code = ServiceCode, + Severity = "warning", + Message = "Owen live analysis: " + message, + File = key, + Primary = true, + Origin = "host", + Project = Path.GetFileNameWithoutExtension(key), + }; + + /// The service's diagnostics as entries, with spans per the coordinate contract. + public static List Entries(LiveResponse response, LiveSnapshot snapshot) + { + var project = Path.GetFileNameWithoutExtension(snapshot.Key); + var texts = new Dictionary(StringComparer.OrdinalIgnoreCase); + string[]? Lines(string file) + { + if (!texts.TryGetValue(file, out var lines)) + texts[file] = lines = snapshot.TextOf(file)?.Replace("\r\n", "\n").Split('\n'); + return lines; + } + LiveEntry Entry(LiveDiagnostic d, string file, int? line, int? column, string message, bool primary) + { + var entry = new LiveEntry + { + Code = d.Code, + Severity = d.Severity, + Message = message, + File = file, + Line = line is int l && l >= 1 ? l : (int?)null, + Column = column, + Primary = primary, + Origin = d.Origin, + Project = project, + }; + if (entry.Line is int n && Lines(file) is string[] all && n <= all.Length) + { + var (start, end, shown) = LiveCoordinates.Span(all[n - 1], column); + entry.Start = start; + entry.End = end; + entry.Column = shown; + } + return entry; + } + + var entries = new List(); + foreach (var d in response.Diagnostics) + { + entries.Add(Entry(d, d.File, d.Line, d.Column, d.Message, primary: true)); + foreach (var r in d.Related) + { + if (string.Equals(r.File, d.File, StringComparison.OrdinalIgnoreCase) && r.Line == d.Line) + continue; // the finding's own line already carries the squiggle + var step = r.Message is string m && m.Length > 0 ? m + " — " + d.Message : d.Message; + entries.Add(Entry(d, r.File, r.Line, r.Column, step, primary: false)); + } + } + return entries; + } + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/Live/LiveProtocol.cs b/frontend/roslyn/Owen.VisualStudio/Live/LiveProtocol.cs new file mode 100644 index 00000000..73c6794a --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/Live/LiveProtocol.cs @@ -0,0 +1,252 @@ +// OX-02 (docs/notes/owen-visual-studio-preregistration.md §4): the client half of owen-live/1. +// Independent of Visual Studio, so tests/owen-live/LiveClientTests runs it on any OS. +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Owen.VisualStudio.Live +{ + /// One source the editor holds: full path and current text. + public sealed class LiveSource + { + public LiveSource(string path, string text) + { + Path = path; + Text = text; + } + + public string Path { get; } + + public string Text { get; } + } + + /// A location the core names: the finding's own, or one of its witness steps. + public sealed class LiveLocation + { + public string File { get; set; } = ""; + + public int? Line { get; set; } + + public int? Column { get; set; } + + public string? Message { get; set; } + } + + /// One diagnostic, as the service sends it (preregistration §6). + public sealed class LiveDiagnostic + { + public string Code { get; set; } = ""; + + public string Severity { get; set; } = ""; + + public string Message { get; set; } = ""; + + public string File { get; set; } = ""; + + public int? Line { get; set; } + + public int? Column { get; set; } + + /// core (an OWN finding) or host (an OWENB condition). + public string Origin { get; set; } = ""; + + public List Related { get; set; } = new List(); + } + + public sealed class LiveResponse + { + public long Id { get; set; } + + public string Key { get; set; } = ""; + + public long Version { get; set; } + + /// ok, superseded, cancelled or error. + public string Status { get; set; } = ""; + + public List Diagnostics { get; set; } = new List(); + + public List Extensions { get; set; } = new List(); + + public Dictionary Timing { get; set; } = new Dictionary(); + } + + public sealed class LiveProtocolException : Exception + { + public LiveProtocolException(string message) + : base(message) + { + } + } + + /// Content-Length: n\r\n\r\n + n bytes of UTF-8 JSON, both ways. + public static class LiveFrames + { + public const string Protocol = "owen-live/1"; + + public static void Write(Stream output, JObject message) + { + var body = Encoding.UTF8.GetBytes(message.ToString(Formatting.None)); + var header = Encoding.ASCII.GetBytes("Content-Length: " + body.Length + "\r\n\r\n"); + output.Write(header, 0, header.Length); + output.Write(body, 0, body.Length); + output.Flush(); + } + + /// One frame, or null at a clean end of stream between frames. + public static JObject? Read(Stream input) + { + int? length = null; + var first = true; + while (true) + { + var line = ReadLine(input, first); + first = false; + if (line == null) + return null; + if (line.Length == 0) + break; + const string prefix = "Content-Length: "; + if (!line.StartsWith(prefix, StringComparison.Ordinal) + || !int.TryParse(line.Substring(prefix.Length), System.Globalization.NumberStyles.None, + System.Globalization.CultureInfo.InvariantCulture, out var n)) + throw new LiveProtocolException("malformed frame header '" + line + "'"); + length = n; + } + if (length == null) + throw new LiveProtocolException("a frame without Content-Length"); + var body = new byte[length.Value]; + var read = 0; + while (read < body.Length) + { + var got = input.Read(body, read, body.Length - read); + if (got == 0) + throw new LiveProtocolException("the stream closed inside a frame body"); + read += got; + } + try + { + return JObject.Parse(Encoding.UTF8.GetString(body)); + } + catch (JsonException ex) + { + throw new LiveProtocolException("a frame body that is not a JSON object: " + ex.Message); + } + } + + private static string? ReadLine(Stream input, bool atFrameStart) + { + var bytes = new List(); + while (true) + { + var b = input.ReadByte(); + if (b < 0) + { + if (bytes.Count == 0 && atFrameStart) + return null; + throw new LiveProtocolException("the stream closed inside a frame header"); + } + if (b == '\n') + { + if (bytes.Count == 0 || bytes[bytes.Count - 1] != '\r') + throw new LiveProtocolException("a frame header line not ended by CRLF"); + bytes.RemoveAt(bytes.Count - 1); + return Encoding.ASCII.GetString(bytes.ToArray()); + } + if (bytes.Count > 1024) + throw new LiveProtocolException("a frame header line longer than 1024 bytes"); + bytes.Add((byte)b); + } + } + + public static JObject Hello() => new JObject { ["type"] = "hello", ["protocol"] = Protocol }; + + public static JObject Analyze(long id, string key, long version, string request, + IEnumerable documents, IEnumerable generated) + { + JArray Sources(IEnumerable sources) + { + var array = new JArray(); + foreach (var s in sources) + array.Add(new JObject { ["path"] = s.Path, ["text"] = s.Text }); + return array; + } + return new JObject + { + ["type"] = "analyze", + ["id"] = id, + ["key"] = key, + ["version"] = version, + ["request"] = request, + ["documents"] = Sources(documents), + ["generated"] = Sources(generated), + }; + } + + public static LiveResponse Response(JObject frame) + { + var response = frame.ToObject(JsonSerializer.Create(new JsonSerializerSettings + { + ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver + { + NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy(), + }, + MissingMemberHandling = MissingMemberHandling.Ignore, + })); + return response ?? throw new LiveProtocolException("an empty result"); + } + } + + /// The project's obj/owen/live.txt, written by Owen.Build (preregistration §3). + public sealed class LiveRequestFile + { + private LiveRequestFile(string path, string host, string? dotnet, string? project) + { + Path = path; + Host = host; + Dotnet = dotnet; + Project = project; + } + + public string Path { get; } + + /// The Owen.Build package's ownsharp.dll: the service. + public string Host { get; } + + /// The muxer MSBuild ran with, when it said. + public string? Dotnet { get; } + + public string? Project { get; } + + /// Where a project's live request is: obj/owen/live.txt beside it. + public static string For(string projectFile) => + System.IO.Path.Combine(System.IO.Path.GetDirectoryName(projectFile) ?? "", "obj", "owen", "live.txt"); + + public static LiveRequestFile? TryRead(string path) + { + if (!File.Exists(path)) + return null; + string? host = null, dotnet = null, project = null; + foreach (var raw in File.ReadAllLines(path)) + { + var tab = raw.IndexOf('\t'); + if (tab <= 0) + continue; + var key = raw.Substring(0, tab); + var value = raw.Substring(tab + 1).Trim(); + if (value.Length == 0) + continue; + if (key == "host") + host = value; + else if (key == "dotnet") + dotnet = value; + else if (key == "project") + project = value; + } + return host == null ? null : new LiveRequestFile(path, host, dotnet, project); + } + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/Live/LiveScheduler.cs b/frontend/roslyn/Owen.VisualStudio/Live/LiveScheduler.cs new file mode 100644 index 00000000..f91eeeab --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/Live/LiveScheduler.cs @@ -0,0 +1,103 @@ +// OX-02 (preregistration §5, K4): edit -> debounce -> one snapshot -> analyze -> publish only if +// that version is still the newest one issued. Independent of Visual Studio. +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Owen.VisualStudio.Live +{ + /// + /// Per-project request versions. A result is published only when its version is the latest + /// one issued for its project, so an answer that arrives late — however late, in whatever + /// order — can never replace a newer one (K4; mutation M1 drops this check). + /// + public sealed class LiveVersionGate + { + private readonly Dictionary _issued = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public long Issue(string key) + { + lock (_issued) + { + _issued.TryGetValue(key, out var last); + _issued[key] = last + 1; + return last + 1; + } + } + + public bool IsCurrent(string key, long version) + { + lock (_issued) + return _issued.TryGetValue(key, out var last) && last == version; + } + } + + /// + /// Trailing-edge debounce per project: every edit restarts the delay; when it expires the + /// work runs once, for the text as it is then. Bursts coalesce into one analysis. + /// + public sealed class LiveDebouncer + { + private readonly TimeSpan _delay; + private readonly Dictionary _timers = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + public LiveDebouncer(TimeSpan delay) + { + _delay = delay; + } + + public void Schedule(string key, Func work) + { + CancellationTokenSource cts; + lock (_timers) + { + if (_timers.TryGetValue(key, out var previous)) + previous.Cancel(); + _timers[key] = cts = new CancellationTokenSource(); + } + _ = RunAsync(key, cts, work); + } + + private async Task RunAsync(string key, CancellationTokenSource cts, Func work) + { + try + { + await Task.Delay(_delay, cts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return; + } + lock (_timers) + { + if (_timers.TryGetValue(key, out var current) && ReferenceEquals(current, cts)) + _timers.Remove(key); + } + await work().ConfigureAwait(false); + } + } + + /// + /// The coordinate contract (preregistration §6, gap 1): with a column, the span starts there; + /// without one, it is the line without its leading and trailing whitespace. Returns 0-based + /// [start, end) within the line, and the 1-based column shown. A coordinate, never a verdict. + /// + public static class LiveCoordinates + { + public static (int Start, int End, int Column) Span(string lineText, int? column) + { + var first = 0; + while (first < lineText.Length && char.IsWhiteSpace(lineText[first])) + first++; + var end = lineText.Length; + while (end > first && char.IsWhiteSpace(lineText[end - 1])) + end--; + var start = column is int c && c >= 1 && c - 1 < end ? c - 1 : first; + if (end <= start) + end = Math.Min(lineText.Length, start + 1); + return (start, end, start + 1); + } + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/Live/LiveService.cs b/frontend/roslyn/Owen.VisualStudio/Live/LiveService.cs new file mode 100644 index 00000000..c68f9ffd --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/Live/LiveService.cs @@ -0,0 +1,328 @@ +// OX-02 (preregistration §4, K6): the long-lived `owen serve` child, and the policy that keeps +// it honest. Independent of Visual Studio. +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace Owen.VisualStudio.Live +{ + /// The service process is gone (or never came up): its exit code and last stderr. + public sealed class LiveServiceDiedException : Exception + { + public LiveServiceDiedException(string message) + : base(message) + { + } + } + + /// + /// One dotnet exec <Owen.Build>/ownsharp.dll serve process. Requests are framed on its + /// stdin, answers read from its stdout and matched by id; its stderr is kept (the last lines + /// go into the failure message, every line to ), never discarded. When the + /// process ends, every pending request fails with . + /// + public sealed class LiveService : IDisposable + { + private readonly Process _process; + private readonly Stream _stdin; + private readonly object _writeGate = new object(); + private readonly ConcurrentDictionary> _pending = + new ConcurrentDictionary>(); + private readonly Queue _stderrTail = new Queue(); + private readonly TaskCompletionSource _hello = new TaskCompletionSource(); + private long _nextId; + private string? _death; + + private LiveService(Process process, Action log) + { + _process = process; + Log = log; + _stdin = process.StandardInput.BaseStream; + } + + public Action Log { get; } + + public int ProcessId => _process.Id; + + public bool IsAlive => _death == null && !_process.HasExited; + + /// Why the service is gone, once it is. + public string? Death => _death; + + public event Action? Died; + + /// Start the service and complete the owen-live/1 handshake. + public static async Task StartAsync(string dotnet, string hostDll, Action log, + TimeSpan handshakeTimeout) + { + if (!File.Exists(hostDll)) + throw new LiveServiceDiedException("the Owen service '" + hostDll + "' does not exist (restore the project, or update Owen.Build)"); + var psi = new ProcessStartInfo(dotnet, "exec \"" + hostDll + "\" serve") + { + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + }; + psi.EnvironmentVariables["DOTNET_ROLL_FORWARD"] = "Major"; + Process process; + try + { + process = Process.Start(psi) ?? throw new LiveServiceDiedException("the Owen service did not start"); + } + catch (System.ComponentModel.Win32Exception ex) + { + throw new LiveServiceDiedException("the Owen service could not be started with '" + dotnet + "': " + ex.Message); + } + ChildProcessGuard.Attach(process); + var service = new LiveService(process, log); + service.Run(); + LiveFrames.Write(service._stdin, LiveFrames.Hello()); + var done = await Task.WhenAny(service._hello.Task, Task.Delay(handshakeTimeout)).ConfigureAwait(false); + if (done != service._hello.Task) + { + service.Kill(); + throw new LiveServiceDiedException("the Owen service did not answer hello within " + handshakeTimeout.TotalSeconds + " s"); + } + var hello = await service._hello.Task.ConfigureAwait(false); + if ((string?)hello["protocol"] != LiveFrames.Protocol) + { + service.Kill(); + throw new LiveServiceDiedException("the Owen service speaks '" + hello["protocol"] + "', not " + LiveFrames.Protocol); + } + return service; + } + + public Task AnalyzeAsync(string key, long version, string request, + IEnumerable documents, IEnumerable generated) + { + var id = Interlocked.Increment(ref _nextId); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _pending[id] = tcs; + if (_death != null) + Fail(_death); + try + { + lock (_writeGate) + LiveFrames.Write(_stdin, LiveFrames.Analyze(id, key, version, request, documents, generated)); + } + catch (IOException ex) + { + Died1("writing to the Owen service failed: " + ex.Message); + } + return tcs.Task; + } + + private void Run() + { + var reader = new Thread(() => + { + try + { + var stdout = _process.StandardOutput.BaseStream; + while (true) + { + var frame = LiveFrames.Read(stdout); + if (frame == null) + break; + var type = (string?)frame["type"]; + if (type == "hello") + _hello.TrySetResult(frame); + else if (type == "result") + { + var response = LiveFrames.Response(frame); + if (_pending.TryRemove(response.Id, out var tcs)) + tcs.TrySetResult(response); + } + else if (type == "fatal") + Log("owen serve: fatal: " + frame["message"]); + else + throw new LiveProtocolException("unknown message type '" + type + "'"); + } + } + catch (Exception ex) when (ex is LiveProtocolException || ex is IOException) + { + Log("owen serve: " + ex.Message); + Kill(); + } + _process.WaitForExit(5000); + Died1(null); + }) + { IsBackground = true, Name = "Owen live service reader" }; + reader.Start(); + + var errors = new Thread(() => + { + string? line; + while ((line = _process.StandardError.ReadLine()) != null) + { + Log("owen serve: " + line); + lock (_stderrTail) + { + _stderrTail.Enqueue(line); + while (_stderrTail.Count > 20) + _stderrTail.Dequeue(); + } + } + }) + { IsBackground = true, Name = "Owen live service stderr" }; + errors.Start(); + } + + private void Died1(string? why) + { + if (_death != null) + return; + string tail; + lock (_stderrTail) + tail = string.Join(" | ", _stderrTail.Where(l => l.Trim().Length > 0)); + var code = _process.HasExited ? _process.ExitCode.ToString(System.Globalization.CultureInfo.InvariantCulture) : "?"; + _death = (why ?? "the Owen live analysis service exited (code " + code + ")") + + (tail.Length > 0 ? ": " + tail : ""); + _hello.TrySetException(new LiveServiceDiedException(_death)); + Fail(_death); + Died?.Invoke(this); + } + + private void Fail(string why) + { + foreach (var id in _pending.Keys.ToList()) + if (_pending.TryRemove(id, out var tcs)) + tcs.TrySetException(new LiveServiceDiedException(why)); + } + + public void Kill() + { + try + { + if (!_process.HasExited) + _process.Kill(); + } + catch (InvalidOperationException) + { + // already gone + } + } + + public void Dispose() + { + try + { + lock (_writeGate) + LiveFrames.Write(_stdin, new JObject { ["type"] = "shutdown" }); + } + catch (IOException) + { + // already gone + } + if (!_process.WaitForExit(2000)) + Kill(); + } + } + + /// + /// K6: the service may die; that is shown, and live analysis restarts on the next request, at + /// most times in . After that it stays off, and + /// says so, until the solution is reopened. Never silent. + /// + public sealed class LiveSupervisor : IDisposable + { + public const int MaxRestarts = 3; + public static readonly TimeSpan Window = TimeSpan.FromMinutes(5); + + private readonly string _dotnet; + private readonly string _host; + private readonly Action _log; + private readonly Func _now; + private readonly List _starts = new List(); + private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1); + private LiveService? _service; + + public LiveSupervisor(string dotnet, string host, Action log, Func? now = null) + { + _dotnet = dotnet; + _host = host; + _log = log; + _now = now ?? (() => DateTime.UtcNow); + } + + /// The operational condition to show (OWENV001), or null while healthy. + public string? Condition { get; private set; } + + /// True once the restart budget is spent. + public bool GaveUp { get; private set; } + + public event Action? ConditionChanged; + + public int? ProcessId => _service?.ProcessId; + + public async Task AnalyzeAsync(string key, long version, string request, + IReadOnlyList documents, IReadOnlyList generated) + { + var service = await EnsureAsync().ConfigureAwait(false); + return await service.AnalyzeAsync(key, version, request, documents, generated).ConfigureAwait(false); + } + + private async Task EnsureAsync() + { + await _gate.WaitAsync().ConfigureAwait(false); + try + { + if (_service != null && _service.IsAlive) + return _service; + if (GaveUp) + throw new LiveServiceDiedException(Condition ?? "Owen live analysis is off"); + var now = _now(); + _starts.RemoveAll(t => now - t > Window); + if (_service != null && _starts.Count > MaxRestarts) + { + GaveUp = true; + SetCondition((Condition ?? "the Owen live analysis service stopped") + + ". It was restarted " + MaxRestarts + " times in " + Window.TotalMinutes + + " minutes; live analysis is off until the solution is reopened (builds still run Owen)."); + throw new LiveServiceDiedException(Condition!); + } + _starts.Add(now); + try + { + _service = await LiveService.StartAsync(_dotnet, _host, _log, TimeSpan.FromSeconds(60)).ConfigureAwait(false); + } + catch (LiveServiceDiedException ex) + { + SetCondition(ex.Message); + throw; + } + _service.Died += s => + { + if (ReferenceEquals(s, _service)) + SetCondition(s.Death); + }; + if (Condition != null && !GaveUp) + SetCondition(null); + return _service; + } + finally + { + _gate.Release(); + } + } + + private void SetCondition(string? condition) + { + Condition = condition; + ConditionChanged?.Invoke(this); + } + + /// Test hook and K6 drill: kill the running service as a crash would. + public void KillService() => _service?.Kill(); + + public void Dispose() => _service?.Dispose(); + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj b/frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj new file mode 100644 index 00000000..a5fef60b --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj @@ -0,0 +1,56 @@ + + + + net472 + Library + latest + enable + Owen.VisualStudio + Owen.VisualStudio + 0.1.0 + false + false + true + true + true + true + true + Owen.VisualStudio.vsix + true + + $(NoWarn);VSTHRD003;VSTHRD010;VSTHRD100;VSTHRD110;VSTHRD200;VSSDK007 + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/roslyn/Owen.VisualStudio/OwenErrorListSource.cs b/frontend/roslyn/Owen.VisualStudio/OwenErrorListSource.cs new file mode 100644 index 00000000..79e8d0ab --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/OwenErrorListSource.cs @@ -0,0 +1,144 @@ +// OX-02: Owen's Error List rows — one ITableDataSource on the standard errors table, one table +// snapshot per project, replaced whole on every publication. The pattern is the VSSDK ErrorList +// sample's (nothing copied). Rows carry DocumentName/Line/Column, which is what the Error List +// navigates by on a double-click. +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.TableControl; +using Microsoft.VisualStudio.Shell.TableManager; +using Owen.VisualStudio.Live; + +namespace Owen.VisualStudio +{ + internal sealed class OwenErrorListSource : ITableDataSource + { + private readonly List _sinks = new List(); + private readonly Dictionary _snapshots = + new Dictionary(StringComparer.OrdinalIgnoreCase); + private int _version; + + public OwenErrorListSource(ITableManager manager) + { + manager.AddSource(this, + StandardTableColumnDefinitions.ErrorSeverity, + StandardTableColumnDefinitions.ErrorCode, + StandardTableColumnDefinitions.Text, + StandardTableColumnDefinitions.ProjectName, + StandardTableColumnDefinitions.DocumentName, + StandardTableColumnDefinitions.Line, + StandardTableColumnDefinitions.Column, + StandardTableColumnDefinitions.BuildTool, + StandardTableColumnDefinitions.ErrorSource); + } + + public string SourceTypeIdentifier => StandardTableDataSources.ErrorTableDataSource; + + public string Identifier => "Owen"; + + public string DisplayName => "Owen"; + + public IDisposable Subscribe(ITableDataSink sink) + { + lock (_sinks) + { + _sinks.Add(sink); + foreach (var s in _snapshots.Values) + sink.AddSnapshot(s); + } + return new Unsubscriber(this, sink); + } + + public void Publish(string key, IReadOnlyList rows) + { + lock (_sinks) + { + _snapshots.TryGetValue(key, out var old); + var next = new OwenEntriesSnapshot(rows, ++_version); + _snapshots[key] = next; + foreach (var sink in _sinks) + { + if (old != null) + sink.ReplaceSnapshot(old, next); + else + sink.AddSnapshot(next); + } + } + } + + private sealed class Unsubscriber : IDisposable + { + private readonly OwenErrorListSource _source; + private readonly ITableDataSink _sink; + + public Unsubscriber(OwenErrorListSource source, ITableDataSink sink) + { + _source = source; + _sink = sink; + } + + public void Dispose() + { + lock (_source._sinks) + _source._sinks.Remove(_sink); + } + } + } + + internal sealed class OwenEntriesSnapshot : TableEntriesSnapshotBase + { + private readonly IReadOnlyList _rows; + private readonly int _version; + + public OwenEntriesSnapshot(IReadOnlyList rows, int version) + { + _rows = rows; + _version = version; + } + + public override int Count => _rows.Count; + + public override int VersionNumber => _version; + + public override bool TryGetValue(int index, string keyName, out object? content) + { + content = null; + if (index < 0 || index >= _rows.Count) + return false; + var row = _rows[index]; + switch (keyName) + { + case StandardTableKeyNames.ErrorSeverity: + content = row.Severity == "error" ? __VSERRORCATEGORY.EC_ERROR : __VSERRORCATEGORY.EC_WARNING; + return true; + case StandardTableKeyNames.ErrorCode: + content = row.Code; + return true; + case StandardTableKeyNames.Text: + content = row.Message; + return true; + case StandardTableKeyNames.ProjectName: + content = row.Project; + return true; + case StandardTableKeyNames.DocumentName: + content = row.File; + return true; + case StandardTableKeyNames.Line: + // 0-based in the table; a file-level row has no line + content = row.Line is int line ? line - 1 : -1; + return row.Line != null; + case StandardTableKeyNames.Column: + content = row.Column is int column ? column - 1 : 0; + return row.Line != null; + case StandardTableKeyNames.BuildTool: + content = "Owen"; + return true; + case StandardTableKeyNames.ErrorSource: + content = ErrorSource.Other; + return true; + default: + return false; + } + } + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs b/frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs new file mode 100644 index 00000000..f9d59319 --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs @@ -0,0 +1,116 @@ +// OX-02: Owen's squiggles. One tagger per C# buffer; it shows what OwenLiveMonitor published for +// the buffer's file — the finding's own span and its witness steps — mapped onto the current text. +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Linq; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; +using Owen.VisualStudio.Live; + +namespace Owen.VisualStudio +{ + [Export(typeof(ITaggerProvider))] + [ContentType("CSharp")] + [TagType(typeof(IErrorTag))] + internal sealed class OwenErrorTaggerProvider : ITaggerProvider + { + [Import] + internal OwenLiveMonitor Monitor { get; set; } = null!; + + public ITagger? CreateTagger(ITextBuffer buffer) + where T : ITag + { + if (!buffer.Properties.TryGetProperty(typeof(ITextDocument), out ITextDocument document)) + return null; + return buffer.Properties.GetOrCreateSingletonProperty( + typeof(OwenErrorTagger), () => new OwenErrorTagger(buffer, document, Monitor)) as ITagger; + } + } + + internal sealed class OwenErrorTagger : ITagger + { + private readonly ITextBuffer _buffer; + private readonly ITextDocument _document; + private readonly OwenLiveMonitor _monitor; + private string _lastTrace = ""; + + public OwenErrorTagger(ITextBuffer buffer, ITextDocument document, OwenLiveMonitor monitor) + { + _buffer = buffer; + _document = document; + _monitor = monitor; + _monitor.FileChanged += OnFileChanged; + _monitor.Opened(document.FilePath); + } + + public event EventHandler? TagsChanged; + + private void OnFileChanged(string file) + { + if (!string.Equals(System.IO.Path.GetFullPath(file), System.IO.Path.GetFullPath(_document.FilePath), + StringComparison.OrdinalIgnoreCase)) + return; + var snapshot = _buffer.CurrentSnapshot; + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(snapshot, 0, snapshot.Length))); + } + + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + { + if (spans.Count == 0) + return Array.Empty>(); + var clock = Stopwatch.StartNew(); + var snapshot = spans[0].Snapshot; + var tags = new List>(); + var traced = new List(); + foreach (var placed in _monitor.EntriesFor(_document.FilePath)) + { + var span = Locate(placed, snapshot); + if (span is not SnapshotSpan s) + continue; + var entry = placed.Entry; + var type = entry.Severity == "error" ? PredefinedErrorTypeNames.SyntaxError : PredefinedErrorTypeNames.Warning; + var tip = entry.Code + ": " + entry.Message; + traced.Add(new + { + code = entry.Code, + primary = entry.Primary, + line = s.Start.GetContainingLine().LineNumber + 1, + column = s.Start.Position - s.Start.GetContainingLine().Start.Position + 1, + length = s.Length, + text = s.GetText(), + }); + if (spans.IntersectsWith(new NormalizedSnapshotSpanCollection(s))) + tags.Add(new TagSpan(s, new ErrorTag(type, tip))); + } + var key = snapshot.Version.VersionNumber + "|" + string.Join(";", traced.Select(t => t.ToString())); + if (key != _lastTrace) + { + _lastTrace = key; + OwenTrace.Event("tags", new { file = _document.FilePath, snapshot = snapshot.Version.VersionNumber, tags = traced }); + } + OwenTrace.Ui("GetTags", clock.Elapsed.TotalMilliseconds); + return tags; + } + + private static SnapshotSpan? Locate(PlacedEntry placed, ITextSnapshot snapshot) + { + if (placed.Span != null) + { + if (placed.Span.TextBuffer != snapshot.TextBuffer) + return null; + var s = placed.Span.GetSpan(snapshot); + return s.Length > 0 ? s : (SnapshotSpan?)null; // the text it named was deleted + } + if (placed.Entry.Line is not int line || line > snapshot.LineCount) + return null; + var l = snapshot.GetLineFromLineNumber(line - 1); + var start = Math.Min(l.Start.Position + placed.Entry.Start, l.End.Position); + var end = Math.Max(start, Math.Min(l.Start.Position + placed.Entry.End, l.End.Position)); + return end > start ? new SnapshotSpan(snapshot, Span.FromBounds(start, end)) : (SnapshotSpan?)null; + } + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs b/frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs new file mode 100644 index 00000000..9a072079 --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs @@ -0,0 +1,220 @@ +// OX-02: the one MEF part that connects Visual Studio's Roslyn workspace to LiveEngine. It knows +// no Owen extension. For a project it sends the service the unsaved text of the project's open +// documents and its source-generated documents, exactly as the workspace holds them now. +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServices; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.TableManager; +using Microsoft.VisualStudio.Text; +using Owen.VisualStudio.Live; + +namespace Owen.VisualStudio +{ + /// What a published entry needs in the editor: where it is now. + internal sealed class PlacedEntry + { + public PlacedEntry(LiveEntry entry, ITrackingSpan? span) + { + Entry = entry; + Span = span; + } + + public LiveEntry Entry { get; } + + /// The squiggle's span on the text the analysis read, tracked forward; null when + /// that text was not an editor buffer (then the line is located on the current text). + public ITrackingSpan? Span { get; } + } + + [Export(typeof(OwenLiveMonitor))] + [PartCreationPolicy(CreationPolicy.Shared)] + internal sealed class OwenLiveMonitor + { + private readonly VisualStudioWorkspace _workspace; + private readonly OwenErrorListSource _errorList; + private readonly LiveEngine _engine; + private readonly ConcurrentDictionary _supervisors = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + // project -> what its newest publication placed + private readonly ConcurrentDictionary> _placed = + new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + + [ImportingConstructor] + public OwenLiveMonitor(VisualStudioWorkspace workspace, ITableManagerProvider tables) + { + _workspace = workspace; + _errorList = new OwenErrorListSource(tables.GetTableManager(StandardTables.ErrorsTable)); + _engine = new LiveEngine(SnapshotAsync, AnalyzerFor, OwenLog.Write, TimeSpan.FromMilliseconds(250)) + { + IgnoreVersions = Environment.GetEnvironmentVariable("OWEN_LIVE_MUTATION") == "M1", + }; + _engine.Published += OnPublished; + _workspace.WorkspaceChanged += OnWorkspaceChanged; + OwenLog.Write("Owen live analysis loaded"); + } + + /// A file's entries changed (its tagger re-reads them). + public event Action? FileChanged; + + public IReadOnlyList EntriesFor(string file) + { + var full = Path.GetFullPath(file); + return _placed.Values.SelectMany(p => p) + .Where(p => string.Equals(Path.GetFullPath(p.Entry.File), full, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + + /// An editor opened : analyse its project. + public void Opened(string file) + { + var key = ProjectOf(file); + if (key != null) + _engine.OnEdit(key); + } + + private string? ProjectOf(string file) + { + var solution = _workspace.CurrentSolution; + foreach (var id in solution.GetDocumentIdsWithFilePath(file)) + if (solution.GetProject(id.ProjectId)?.FilePath is string path) + return path; + return null; + } + + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + { + switch (e.Kind) + { + case WorkspaceChangeKind.DocumentChanged: + case WorkspaceChangeKind.DocumentAdded: + case WorkspaceChangeKind.DocumentRemoved: + case WorkspaceChangeKind.DocumentReloaded: + case WorkspaceChangeKind.ProjectChanged: + case WorkspaceChangeKind.ProjectAdded: + case WorkspaceChangeKind.ProjectReloaded: + if (e.ProjectId != null && e.NewSolution.GetProject(e.ProjectId)?.FilePath is string key + && HasOpenDocument(e.NewSolution.GetProject(e.ProjectId)!)) + { + OwenTrace.Event("edit", new { key, kind = e.Kind.ToString() }); + _engine.OnEdit(key); + } + break; + } + } + + private bool HasOpenDocument(Project project) => project.DocumentIds.Any(_workspace.IsDocumentOpen); + + private async Task SnapshotAsync(string key) + { + var request = LiveRequestFile.For(key); + if (LiveRequestFile.TryRead(request) == null) + return null; + var project = _workspace.CurrentSolution.Projects.FirstOrDefault( + p => string.Equals(p.FilePath, key, StringComparison.OrdinalIgnoreCase)); + if (project == null) + return null; + var documents = new List(); + var texts = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var document in project.Documents) + { + if (document.FilePath == null || !_workspace.IsDocumentOpen(document.Id)) + continue; + var text = await document.GetTextAsync().ConfigureAwait(false); + texts[Path.GetFullPath(document.FilePath)] = text; + documents.Add(new LiveSource(document.FilePath, text.ToString())); + } + var generated = new List(); + foreach (var document in await project.GetSourceGeneratedDocumentsAsync().ConfigureAwait(false)) + { + if (document.FilePath == null) + continue; + var text = await document.GetTextAsync().ConfigureAwait(false); + generated.Add(new LiveSource(document.FilePath, text.ToString())); + } + OwenTrace.Event("snapshot", new + { + key, + documents = documents.Select(d => d.Path), + generated = generated.Select(g => g.Path), + }); + return new LiveSnapshot(key, request, documents, generated, texts); + } + + private ILiveAnalyzer? AnalyzerFor(LiveSnapshot snapshot) + { + var request = LiveRequestFile.TryRead(snapshot.Request); + if (request == null) + return null; + var supervisor = _supervisors.GetOrAdd(request.Host, host => + { + var s = new LiveSupervisor(Dotnet(request), host, OwenLog.Write); + s.ConditionChanged += _ => OwenTrace.Event("service", new { host, condition = s.Condition, gave_up = s.GaveUp }); + return s; + }); + return new SupervisedAnalyzer(supervisor); + } + + /// The muxer: the one MSBuild named, else the machine's, else PATH's. + private static string Dotnet(LiveRequestFile request) + { + if (request.Dotnet != null && File.Exists(request.Dotnet)) + return request.Dotnet; + var programFiles = Environment.GetEnvironmentVariable("ProgramW6432") + ?? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + var machine = Path.Combine(programFiles, "dotnet", "dotnet.exe"); + return File.Exists(machine) ? machine : "dotnet"; + } + + private void OnPublished(LivePublication publication) + { + var texts = publication.Snapshot?.State as Dictionary; + var placed = new List(); + foreach (var entry in publication.Entries) + { + ITrackingSpan? span = null; + if (entry.Line is int line && texts != null + && texts.TryGetValue(Path.GetFullPath(entry.File), out var text) + && text.FindCorrespondingEditorTextSnapshot() is ITextSnapshot snapshot + && line <= snapshot.LineCount) + { + var l = snapshot.GetLineFromLineNumber(line - 1); + var start = Math.Min(l.Start.Position + entry.Start, l.End.Position); + var end = Math.Max(start, Math.Min(l.Start.Position + entry.End, l.End.Position)); + span = snapshot.CreateTrackingSpan(Span.FromBounds(start, end), SpanTrackingMode.EdgeExclusive); + } + placed.Add(new PlacedEntry(entry, span)); + } + + var files = new HashSet(StringComparer.OrdinalIgnoreCase); + if (_placed.TryGetValue(publication.Key, out var old)) + foreach (var p in old) + files.Add(Path.GetFullPath(p.Entry.File)); + foreach (var p in placed) + files.Add(Path.GetFullPath(p.Entry.File)); + _placed[publication.Key] = placed; + + _errorList.Publish(publication.Key, publication.Entries.Where(e => e.Primary).ToList()); + OwenTrace.Event("publish", new + { + key = publication.Key, + version = publication.Version, + timing = publication.Timing, + entries = publication.Entries.Select(e => new + { + code = e.Code, severity = e.Severity, file = e.File, line = e.Line, column = e.Column, + start = e.Start, end = e.End, primary = e.Primary, message = e.Message, + }), + }); + foreach (var file in files) + FileChanged?.Invoke(file); + } + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/OwenLog.cs b/frontend/roslyn/Owen.VisualStudio/OwenLog.cs new file mode 100644 index 00000000..2f291b1e --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/OwenLog.cs @@ -0,0 +1,75 @@ +// OX-02: where Owen.VisualStudio's own words go. The "Owen" output pane (the service's stderr +// included: never discarded), and an opt-in machine-readable trace for the real-IDE acceptance +// run (OWEN_LIVE_TRACE=): what was published, which tags the tagger produced on which text +// version, and how long each UI-thread handler took. The trace never changes behaviour. +using System; +using System.Diagnostics; +using System.IO; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Newtonsoft.Json; + +namespace Owen.VisualStudio +{ + internal static class OwenLog + { + private static readonly Guid PaneId = new Guid("a6b1a0f4-2c55-4e43-9a8b-9d0f2b8e5f31"); + private static IVsOutputWindowPane? _pane; + private static bool _creating; + + public static void Write(string line) + { + OwenTrace.Event("log", new { line }); + var pane = _pane; + if (pane != null) + { + pane.OutputStringThreadSafe(line + Environment.NewLine); + return; + } + if (_creating) + return; + _creating = true; + _ = ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (Package.GetGlobalService(typeof(SVsOutputWindow)) is IVsOutputWindow window) + { + var id = PaneId; + window.CreatePane(ref id, "Owen", 1, 0); + window.GetPane(ref id, out _pane); + } + _pane?.OutputStringThreadSafe(line + Environment.NewLine); + }); + } + } + + internal static class OwenTrace + { + private static readonly string? Path = Environment.GetEnvironmentVariable("OWEN_LIVE_TRACE"); + private static readonly object Gate = new object(); + private static double _uiMax; + + public static void Event(string kind, object data) + { + if (Path == null) + return; + var line = JsonConvert.SerializeObject(new + { + t = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + kind, + data, + }); + lock (Gate) + File.AppendAllText(Path, line + "\n"); + } + + /// A UI-thread handler's duration; traced when it sets a new maximum. + public static void Ui(string handler, double ms) + { + if (Path == null || ms <= _uiMax) + return; + _uiMax = ms; + Event("ui", new { handler, ms, pid = Process.GetCurrentProcess().Id }); + } + } +} diff --git a/frontend/roslyn/Owen.VisualStudio/source.extension.vsixmanifest b/frontend/roslyn/Owen.VisualStudio/source.extension.vsixmanifest new file mode 100644 index 00000000..8373fa85 --- /dev/null +++ b/frontend/roslyn/Owen.VisualStudio/source.extension.vsixmanifest @@ -0,0 +1,28 @@ + + + + + + Owen + Live Owen diagnostics while you type, for every Owen extension a project uses (squiggles and Error List). Analysis is done by the Owen build host the project already references (Owen.Build). + owen, ownership, static analysis, csharp + + + + amd64 + + + amd64 + + + amd64 + + + + + + + + + + diff --git a/frontend/roslyn/OwnSharp.Cli/BuildCheckCommand.cs b/frontend/roslyn/OwnSharp.Cli/BuildCheckCommand.cs index 34e168f2..bfeb33f5 100644 --- a/frontend/roslyn/OwnSharp.Cli/BuildCheckCommand.cs +++ b/frontend/roslyn/OwnSharp.Cli/BuildCheckCommand.cs @@ -1,6 +1,4 @@ using System.Text; -using System.Text.Json; -using System.Text.RegularExpressions; namespace OwnSharp.Cli; @@ -25,25 +23,14 @@ namespace OwnSharp.Cli; /// (--format msbuild) and passed through byte for byte. This host renders nothing of /// its own and knows no extension by name. /// +/// The host contract itself (descriptors, the input set, refusals) is +/// , shared with the live IDE service (owen serve). +/// /// Exit: 0 analysed (findings shown as warnings, or none); 1 analysed, findings shown as /// errors; 2 a host / contract / refusal error, already printed as an OWENB line. /// internal static class BuildCheckCommand { - /// The descriptor schema this host reads (`owen_extension`). - public const int DescriptorSchema = 1; - - /// The OwnIR version the packed core speaks (spec/OwnIR.md §2). - public const int OwnIr = 2; - - /// What an extension may require of this host. A capability is a promise about - /// the analysis the packed extractor + core perform; it is added here only when they - /// do. - public static readonly string[] Capabilities = ["heap-effects", "ownership", "proven-call", "state-protocol"]; - - private sealed record Descriptor(string Path, string Id, string Version, IReadOnlyList Capabilities, - IReadOnlyList Generators); - public static async Task RunAsync(string[] args) { if (args.Length != 2 || args[0] != "--request") @@ -55,7 +42,7 @@ public static async Task RunAsync(string[] args) Dictionary> request; try { - request = ReadRequest(args[1]); + request = OwenHost.ReadRequest(args[1]); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { @@ -63,11 +50,11 @@ public static async Task RunAsync(string[] args) return 2; } - var project = One(request, "project"); - var severity = One(request, "severity") ?? "warning"; - var manifest = One(request, "manifest"); - var generatedRoot = One(request, "generated-root"); - var emitFacts = One(request, "emit-facts"); + var project = OwenHost.One(request, "project"); + var severity = OwenHost.One(request, "severity") ?? "warning"; + var manifest = OwenHost.One(request, "manifest"); + var generatedRoot = OwenHost.One(request, "generated-root"); + var emitFacts = OwenHost.One(request, "emit-facts"); if (project is null || manifest is null) { Console.WriteLine("owen: error OWENB012: the build request names no project or no manifest path"); @@ -84,43 +71,20 @@ public static async Task RunAsync(string[] args) if (paths.Count == 0) { Console.WriteLine($"{project}: warning OWENB001: Owen.Build is referenced but no Owen extension is active; nothing was analysed"); - WriteManifest(manifest, []); + OwenHost.WriteManifest(manifest, []); return 0; } - var descriptors = new List(); - var failed = false; - foreach (var path in paths.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal)) - { - var d = ReadDescriptor(path, out var errors); - foreach (var e in errors) - Console.WriteLine(e); - failed |= errors.Count > 0; - if (d is not null) - descriptors.Add(d); - } - foreach (var dup in descriptors.GroupBy(d => d.Id, StringComparer.Ordinal).Where(g => g.Count() > 1)) - { - Console.WriteLine($"{dup.Last().Path}: error OWENB002: extension '{dup.Key}' is declared {dup.Count()} times"); - failed = true; - } - if (failed) + var errors = new List(); + var descriptors = OwenHost.Validate(paths, errors); + foreach (var e in errors) + Console.WriteLine(e.ToMsBuild()); + if (descriptors is null) return 2; - descriptors.Sort((a, b) => StringComparer.Ordinal.Compare(a.Id, b.Id)); - WriteManifest(manifest, descriptors); + OwenHost.WriteManifest(manifest, descriptors); Console.WriteLine("Owen: active extensions: " + string.Join(", ", descriptors.Select(d => $"{d.Id} {d.Version}"))); // ---- 2. the inputs ------------------------------------------------------------------ - var inputs = new List { project }; - if (generatedRoot is not null) - { - foreach (var generator in descriptors.SelectMany(d => d.Generators).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal)) - { - var dir = Path.Combine(generatedRoot, generator); - if (Directory.Exists(dir)) - inputs.AddRange(Directory.EnumerateFiles(dir, "*.cs", SearchOption.AllDirectories) - .Order(StringComparer.Ordinal)); - } - } + var inputs = OwenHost.Inputs(project, generatedRoot, descriptors); // ---- 3. the engine ------------------------------------------------------------------ RustCore core; @@ -130,7 +94,7 @@ public static async Task RunAsync(string[] args) } catch (RustCoreNotResolvedException ex) { - Console.WriteLine($"{project}: error OWENB005: {OneLine(ex.Message)}"); + Console.WriteLine($"{project}: error OWENB005: {OwenHost.OneLine(ex.Message)}"); return 2; } @@ -142,13 +106,13 @@ public static async Task RunAsync(string[] args) .ConfigureAwait(false); if (extractRc == 2) { - foreach (var line in Canonical(extractOutput, project, "OWENB010", ExtractorRefusal)) - Console.WriteLine(Absolute(line, project)); + foreach (var d in OwenHost.Refusals(extractOutput, project, "OWENB010", OwenHost.ExtractorRefusal)) + Console.WriteLine(d.ToMsBuild()); return 2; } if (extractRc != 0) { - Console.WriteLine($"{project}: error OWENB012: the extractor exited {extractRc}: {OneLine(extractOutput)}"); + Console.WriteLine($"{project}: error OWENB012: the extractor exited {extractRc}: {OwenHost.OneLine(extractOutput)}"); return 2; } if (emitFacts is not null) @@ -162,7 +126,7 @@ public static async Task RunAsync(string[] args) } catch (EngineRunner.RustCoreNotStartedException ex) { - Console.WriteLine($"{project}: error OWENB005: {OneLine(ex.Message)}"); + Console.WriteLine($"{project}: error OWENB005: {OwenHost.OneLine(ex.Message)}"); return 2; } var stdout = Encoding.UTF8.GetString(outcome.Stdout); @@ -173,15 +137,15 @@ public static async Task RunAsync(string[] args) // the core's own canonical lines, unchanged but for the origin path: the // core names files relative to the project directory (the extractor ran // there), and the Error List must not have to guess the base - Console.Write(Absolute(stdout, project)); - Console.Write(Absolute(stderr, project)); + Console.Write(OwenHost.Absolute(stdout, project)); + Console.Write(OwenHost.Absolute(stderr, project)); return outcome.Rc == 1 && severity == "error" ? 1 : 0; case 2: - foreach (var line in Canonical(stderr + stdout, project, "OWENB011", CoreRefusal)) - Console.WriteLine(Absolute(line, project)); + foreach (var d in OwenHost.Refusals(stderr + stdout, project, "OWENB011", OwenHost.CoreRefusal)) + Console.WriteLine(d.ToMsBuild()); return 2; default: - Console.WriteLine($"{project}: error OWENB012: the Rust core exited {outcome.Rc}, which is not a verdict: {OneLine(stderr)}"); + Console.WriteLine($"{project}: error OWENB012: the Rust core exited {outcome.Rc}, which is not a verdict: {OwenHost.OneLine(stderr)}"); return 2; } } @@ -190,208 +154,4 @@ public static async Task RunAsync(string[] args) try { File.Delete(factsPath); } catch (IOException) { /* best-effort */ } } } - - // ---- descriptors -------------------------------------------------------------------------- - - private static Descriptor? ReadDescriptor(string path, out List errors) - { - var found = new List(); - errors = found; - void Bad(string code, string text) => found.Add($"{path}: error {code}: {text}"); - - JsonDocument doc; - try - { - doc = JsonDocument.Parse(File.ReadAllBytes(path)); - } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException) - { - Bad("OWENB002", $"the extension descriptor cannot be read: {OneLine(ex.Message)}"); - return null; - } - using (doc) - { - var root = doc.RootElement; - if (root.ValueKind != JsonValueKind.Object) - { - Bad("OWENB002", "the extension descriptor is not a JSON object"); - return null; - } - if (!root.TryGetProperty("owen_extension", out var schema) || schema.ValueKind != JsonValueKind.Number - || !schema.TryGetInt32(out var schemaVersion)) - { - Bad("OWENB002", "the extension descriptor has no integer 'owen_extension' schema version"); - return null; - } - if (schemaVersion != DescriptorSchema) - { - Bad("OWENB002", $"descriptor schema owen_extension={schemaVersion} is not one this host reads (it reads {DescriptorSchema}); update Owen.Build"); - return null; - } - var id = Text(root, "id"); - var version = Text(root, "version"); - if (id is null || version is null) - { - Bad("OWENB002", "the extension descriptor needs non-empty string 'id' and 'version'"); - return null; - } - if (!root.TryGetProperty("requires", out var requires) || requires.ValueKind != JsonValueKind.Object) - { - Bad("OWENB002", $"extension '{id}' has no 'requires' object"); - return null; - } - var host = Text(requires, "host"); - if (host is null || !Version.TryParse(host, out var needHost)) - { - Bad("OWENB002", $"extension '{id}': 'requires.host' must be a version such as \"0.1.0\""); - return null; - } - if (!requires.TryGetProperty("ownir", out var ownir) || ownir.ValueKind != JsonValueKind.Number - || !ownir.TryGetInt32(out var needOwnIr)) - { - Bad("OWENB002", $"extension '{id}': 'requires.ownir' must be an integer"); - return null; - } - var capabilities = Strings(requires, "capabilities"); - if (capabilities is null) - { - Bad("OWENB002", $"extension '{id}': 'requires.capabilities' must be an array of strings"); - return null; - } - IReadOnlyList generators = []; - if (root.TryGetProperty("frontend", out var frontend)) - { - if (frontend.ValueKind != JsonValueKind.Object - || (frontend.TryGetProperty("generators", out _) && Strings(frontend, "generators") is null)) - { - Bad("OWENB002", $"extension '{id}': 'frontend.generators' must be an array of strings"); - return null; - } - generators = Strings(frontend, "generators") ?? []; - } - - var hostVersion = Version.Parse(ToolVersion.Current); - if (needHost > hostVersion) - Bad("OWENB003", $"extension '{id}' {version} requires Owen host {needHost}, this host is {hostVersion}; update Owen.Build"); - if (needOwnIr != OwnIr) - Bad("OWENB003", $"extension '{id}' {version} requires OwnIR {needOwnIr}, this host's core speaks OwnIR {OwnIr}"); - foreach (var unknown in capabilities.Where(c => !Capabilities.Contains(c, StringComparer.Ordinal))) - Bad("OWENB004", $"extension '{id}' {version} requires capability '{unknown}', which this host does not provide (it provides: {string.Join(", ", Capabilities)})"); - return found.Count > 0 ? null : new Descriptor(path, id, version, capabilities, generators); - } - } - - private static string? Text(JsonElement e, string name) => - e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String && v.GetString() is { Length: > 0 } s - ? s - : null; - - private static List? Strings(JsonElement e, string name) - { - if (!e.TryGetProperty(name, out var v) || v.ValueKind != JsonValueKind.Array) - return null; - var list = new List(); - foreach (var item in v.EnumerateArray()) - { - if (item.ValueKind != JsonValueKind.String || item.GetString() is not { Length: > 0 } s) - return null; - list.Add(s); - } - return list; - } - - /// obj/owen/extensions.json: what is active in this project, sorted, byte-stable. - private static void WriteManifest(string path, IReadOnlyList descriptors) - { - var o = new StringBuilder(); - o.Append("{\n"); - o.Append($" \"owen_host\": \"{ToolVersion.Current}\",\n"); - o.Append($" \"ownir\": {OwnIr},\n"); - o.Append(" \"extensions\": ["); - for (var i = 0; i < descriptors.Count; i++) - { - var d = descriptors[i]; - o.Append(i == 0 ? "\n" : ",\n"); - o.Append(" {\n"); - o.Append($" \"id\": {JsonSerializer.Serialize(d.Id)},\n"); - o.Append($" \"version\": {JsonSerializer.Serialize(d.Version)},\n"); - o.Append($" \"capabilities\": [{string.Join(", ", d.Capabilities.Order(StringComparer.Ordinal).Select(c => JsonSerializer.Serialize(c)))}]\n"); - o.Append(" }"); - } - o.Append(descriptors.Count == 0 ? "]\n" : "\n ]\n"); - o.Append("}\n"); - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))!); - File.WriteAllText(path, o.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); - } - - // ---- refusals, as canonical MSBuild lines ---------------------------------------------------- - - // extractor: protocol lowering refused: OrderEndpoints.cs:17: - private static readonly Regex ExtractorRefusal = new(@"refused:\s+(?[^\r\n]+?):(?\d+):\s+(?.+)$"); - - // facts.json: error: - private static readonly Regex CoreRefusal = new(@"^.*?:\s+error:\s+(?.*\((?[^()\r\n]+):(?\d+)\).*)$"); - - /// One canonical error per refusal line that names a location; one project-level - /// error carrying the whole output when none does. Never zero lines. - private static List Canonical(string output, string project, string code, Regex located) - { - var lines = new List(); - foreach (var raw in output.Split('\n')) - { - var line = raw.TrimEnd('\r'); - var m = located.Match(line); - if (m.Success) - lines.Add($"{m.Groups["file"].Value}({m.Groups["line"].Value}): error {code}: {m.Groups["text"].Value}"); - } - if (lines.Count == 0) - lines.Add($"{project}: error {code}: {OneLine(output)}"); - return lines; - } - - // `File.cs(18): warning OWN002: …` / `File.cs(18,3): error …` - private static readonly Regex CanonicalOrigin = new(@"^(?[^\r\n(]+?)\((?\d+(?:,\d+)*)\): (?(?:warning|error) .*)$"); - - /// Make the origin of every canonical line absolute against the project - /// directory; every other byte, the message text included, is left as it is. - private static string Absolute(string text, string project) - { - var dir = Path.GetDirectoryName(Path.GetFullPath(project))!; - // split and re-join on '\n': a trailing newline survives as the empty last piece - return string.Join('\n', text.Split('\n').Select(piece => - { - var line = piece.TrimEnd('\r'); - var m = CanonicalOrigin.Match(line); - return m.Success && !Path.IsPathRooted(m.Groups["file"].Value) - ? $"{Path.GetFullPath(Path.Combine(dir, m.Groups["file"].Value))}({m.Groups["pos"].Value}): {m.Groups["rest"].Value}" - : piece; - })); - } - - private static string OneLine(string text) => - string.Join(" ", text.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())); - - // ---- the request file --------------------------------------------------------------------- - - private static Dictionary> ReadRequest(string path) - { - var map = new Dictionary>(StringComparer.Ordinal); - foreach (var raw in File.ReadAllLines(path)) - { - var tab = raw.IndexOf('\t'); - if (tab <= 0) - continue; - var key = raw[..tab]; - var value = raw[(tab + 1)..].Trim(); - if (value.Length == 0) - continue; - if (!map.TryGetValue(key, out var list)) - map[key] = list = []; - list.Add(value); - } - return map; - } - - private static string? One(Dictionary> map, string key) => - map.TryGetValue(key, out var list) && list.Count > 0 ? list[^1] : null; } diff --git a/frontend/roslyn/OwnSharp.Cli/LiveAnalysis.cs b/frontend/roslyn/OwnSharp.Cli/LiveAnalysis.cs new file mode 100644 index 00000000..daeb4f74 --- /dev/null +++ b/frontend/roslyn/OwnSharp.Cli/LiveAnalysis.cs @@ -0,0 +1,241 @@ +using System.Diagnostics; +using System.Text; +using System.Text.Json; +using OwnSharp.Extractor; + +namespace OwnSharp.Cli; + +/// One source an IDE holds in memory: its full path and its current text. +internal sealed record LiveDocument(string Path, string Text); + +/// A location the core names: the finding's own, or one of its witness steps. +internal sealed record LiveLocation(string File, int Line, int? Column, string? Message); + +/// One diagnostic as the IDE shows it (docs/notes/owen-visual-studio-preregistration.md §6). +/// An OWN finding is the core's Finding, carried; an OWENB one is the host's own. +internal sealed record LiveDiagnostic(string Code, string Severity, string Message, string File, int? Line, + int? Column, string Origin, IReadOnlyList Related); + +internal sealed record LiveResult(IReadOnlyList Diagnostics, IReadOnlyList Extensions, + IReadOnlyDictionary TimingMs); + +/// +/// One live analysis (OX-02): the build host's contract () over the text an +/// IDE holds, through the ONE extractor run in-process () and +/// the packaged Rust core. It is owen build-check with three differences, none of them +/// analysis: the sources come from memory where the IDE has them, the extractor runs in this +/// process, and the core's findings come back through its SARIF renderer so that columns and +/// witness steps survive. A verdict is never formed here. +/// +internal static class LiveAnalysis +{ + private static RustCore? _core; + + public static async Task RunAsync(string requestPath, IReadOnlyList documents, + IReadOnlyList generated) + { + var timing = new Dictionary(StringComparer.Ordinal); + var clock = Stopwatch.StartNew(); + var diagnostics = new List(); + LiveResult Done(IReadOnlyList extensions) + { + timing["total"] = clock.ElapsedMilliseconds; + // the service's own footprint, so a host can watch it grow (preregistration §8) + timing["working_set_kb"] = Environment.WorkingSet / 1024; + return new LiveResult(diagnostics, extensions, timing); + } + void Host(HostDiagnostic d) => + diagnostics.Add(new(d.Code, d.Severity, d.Text, d.File, d.Line, null, "host", [])); + + Dictionary> request; + try + { + request = OwenHost.ReadRequest(requestPath); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + Host(new(requestPath, null, "error", "OWENB012", $"cannot read the live request: {ex.Message}")); + return Done([]); + } + var project = OwenHost.One(request, "project"); + var severity = OwenHost.One(request, "severity") ?? "warning"; + var generatedRoot = OwenHost.One(request, "generated-root"); + if (project is null) + { + Host(new(requestPath, null, "error", "OWENB012", "the live request names no project")); + return Done([]); + } + if (severity is not ("warning" or "error")) + { + Host(new(project, null, "error", "OWENB002", $"OwenSeverity must be 'warning' or 'error', not '{severity}'")); + return Done([]); + } + + // ---- 1. the extensions: the build host's own validation --------------------------- + var paths = request.TryGetValue("descriptor", out var given) ? given : []; + if (paths.Count == 0) + { + Host(new(project, null, "warning", "OWENB001", "Owen.Build is referenced but no Owen extension is active; nothing was analysed")); + return Done([]); + } + var errors = new List(); + var descriptors = OwenHost.Validate(paths, errors); + errors.ForEach(Host); + if (descriptors is null) + return Done([]); + var extensions = descriptors.Select(d => d.Id).ToList(); + + // ---- 2. the inputs: the build's input set, the editor's text --------------------- + var overlay = new Dictionary(StringComparer.Ordinal); + foreach (var d in documents) + overlay[Path.GetFullPath(d.Path)] = d.Text; + Dictionary>? live = null; + if (generatedRoot is not null) + { + live = []; + foreach (var generator in OwenHost.Generators(descriptors)) + foreach (var g in generated) + if (Relocate(g.Path, generator, generatedRoot) is { } path) + { + overlay[path] = g.Text; + (live.TryGetValue(generator, out var list) ? list : live[generator] = []).Add(path); + } + } + var inputs = OwenHost.Inputs(project, generatedRoot, descriptors, live); + var projectDir = Path.GetDirectoryName(Path.GetFullPath(project))!; + + // ---- 3. the extractor, in this process --------------------------------------------- + var factsPath = Path.GetTempFileName(); + try + { + var step = Stopwatch.StartNew(); + var (extractRc, extractOutput) = InProcessExtractor.Run( + [.. inputs, "-o", factsPath, "--flow-locals"], projectDir, overlay); + timing["extract"] = step.ElapsedMilliseconds; + if (extractRc == 2) + { + OwenHost.Refusals(extractOutput, project, "OWENB010", OwenHost.ExtractorRefusal).ForEach(Host); + return Done(extensions); + } + if (extractRc != 0) + { + Host(new(project, null, "error", "OWENB012", $"the extractor exited {extractRc}: {OwenHost.OneLine(extractOutput)}")); + return Done(extensions); + } + + // ---- 4. the core --------------------------------------------------------------- + step.Restart(); + EngineOutcome outcome; + try + { + _core ??= RustCoreLocator.Resolve(); + outcome = await EngineRunner.RunRustAsync(_core, factsPath, "sarif", severity, capture: true) + .ConfigureAwait(false); + } + catch (RustCoreNotResolvedException ex) + { + Host(new(project, null, "error", "OWENB005", OwenHost.OneLine(ex.Message))); + return Done(extensions); + } + catch (EngineRunner.RustCoreNotStartedException ex) + { + _core = null; + Host(new(project, null, "error", "OWENB005", OwenHost.OneLine(ex.Message))); + return Done(extensions); + } + timing["core"] = step.ElapsedMilliseconds; + var stdout = Encoding.UTF8.GetString(outcome.Stdout); + var stderr = Encoding.UTF8.GetString(outcome.Stderr); + switch (outcome.Rc) + { + case 0 or 1: + diagnostics.AddRange(FromSarif(stdout, projectDir)); + break; + case 2: + OwenHost.Refusals(stderr + stdout, project, "OWENB011", OwenHost.CoreRefusal).ForEach(Host); + break; + default: + Host(new(project, null, "error", "OWENB012", $"the Rust core exited {outcome.Rc}, which is not a verdict: {OwenHost.OneLine(stderr)}")); + break; + } + return Done(extensions); + } + finally + { + try { File.Delete(factsPath); } catch (IOException) { /* best-effort */ } + } + } + + /// + /// Where the build would have written an in-memory generated document: the compiler lays a + /// generator's output out as <generated-root>/<generator assembly>/<generator type>/<hint>, + /// and an IDE names its generated documents with the same three trailing parts under a base of + /// its own. The generator is matched by name, never by which extension it belongs to. + /// + internal static string? Relocate(string path, string generator, string generatedRoot) + { + var parts = path.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); + for (var i = parts.Length - 3; i >= 0; i--) + if (string.Equals(parts[i], generator, StringComparison.Ordinal)) + return Path.GetFullPath(Path.Combine([generatedRoot, .. parts[i..]])); + return null; + } + + /// + /// The core's findings as the SARIF renderer wrote them, carried field for field: rule id, + /// message text, location, witness steps. Exactly the findings the msbuild renderer prints: + /// a suppressed one ([OwnIgnore]) is not shown there and is not shown here, and an + /// advisory (SARIF note) is a warning there and a warning here. + /// + internal static IEnumerable FromSarif(string sarif, string projectDir) + { + using var doc = JsonDocument.Parse(sarif); + foreach (var run in doc.RootElement.GetProperty("runs").EnumerateArray()) + { + if (!run.TryGetProperty("results", out var results)) + continue; + foreach (var r in results.EnumerateArray()) + { + if (r.TryGetProperty("suppressions", out var sup) && sup.GetArrayLength() > 0) + continue; + var level = r.GetProperty("level").GetString(); + var primary = Location(r.GetProperty("locations")[0], projectDir, null); + var related = new List(); + if (r.TryGetProperty("codeFlows", out var flows)) + foreach (var flow in flows.EnumerateArray()) + foreach (var thread in flow.GetProperty("threadFlows").EnumerateArray()) + foreach (var step in thread.GetProperty("locations").EnumerateArray()) + if (step.TryGetProperty("location", out var at)) + related.Add(Location(at, projectDir, Text(at))); + if (r.TryGetProperty("relatedLocations", out var rel)) + foreach (var at in rel.EnumerateArray()) + related.Add(Location(at, projectDir, Text(at))); + yield return new LiveDiagnostic( + r.GetProperty("ruleId").GetString()!, + level == "error" ? "error" : "warning", + r.GetProperty("message").GetProperty("text").GetString()!, + primary.File, primary.Line, primary.Column, "core", related); + } + } + + static string? Text(JsonElement at) => + at.TryGetProperty("message", out var m) && m.TryGetProperty("text", out var t) ? t.GetString() : null; + } + + private static LiveLocation Location(JsonElement at, string projectDir, string? message) + { + var physical = at.GetProperty("physicalLocation"); + var uri = physical.GetProperty("artifactLocation").GetProperty("uri").GetString()!; + var file = Path.IsPathRooted(uri) ? uri : Path.GetFullPath(Path.Combine(projectDir, uri)); + int line = 0; + int? column = null; + if (physical.TryGetProperty("region", out var region)) + { + if (region.TryGetProperty("startLine", out var l)) + line = l.GetInt32(); + if (region.TryGetProperty("startColumn", out var c)) + column = c.GetInt32(); + } + return new LiveLocation(file, line, column, message); + } +} diff --git a/frontend/roslyn/OwnSharp.Cli/OwenHost.cs b/frontend/roslyn/OwnSharp.Cli/OwenHost.cs new file mode 100644 index 00000000..2b5cdd03 --- /dev/null +++ b/frontend/roslyn/OwnSharp.Cli/OwenHost.cs @@ -0,0 +1,306 @@ +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace OwnSharp.Cli; + +/// One condition of the host itself (an OWENB code): a descriptor rejected, no +/// engine, a refusal. Never an analysis finding; never an OWN code. +internal sealed record HostDiagnostic(string File, int? Line, string Severity, string Code, string Text) +{ + /// The canonical MSBuild line: file(line): severity CODE: text. + public string ToMsBuild() => + Line is { } line ? $"{File}({line}): {Severity} {Code}: {Text}" : $"{File}: {Severity} {Code}: {Text}"; +} + +/// An active Owen extension, as its descriptor declares it. +internal sealed record OwenDescriptor(string Path, string Id, string Version, IReadOnlyList Capabilities, + IReadOnlyList Generators); + +/// +/// The generic Owen host contract (OX-01, spec/OwenExtension.md), shared by its two front +/// doors: owen build-check (after a build) and owen serve (live, for an IDE; +/// OX-02). Everything here decides WHAT is analysed and how the host's own conditions read; +/// nothing here analyses, and nothing here names an extension. +/// +internal static class OwenHost +{ + /// The descriptor schema this host reads (`owen_extension`). + public const int DescriptorSchema = 1; + + /// The OwnIR version the packed core speaks (spec/OwnIR.md §2). + public const int OwnIr = 2; + + /// What an extension may require of this host. A capability is a promise about + /// the analysis the packed extractor + core perform; it is added here only when they + /// do. + public static readonly string[] Capabilities = ["heap-effects", "ownership", "proven-call", "state-protocol"]; + + // ---- the request file --------------------------------------------------------------------- + + /// The `key\tvalue` request the Owen.Build targets write (request.txt for a build, + /// live.txt for an IDE). A key may repeat (`descriptor`). + public static Dictionary> ReadRequest(string path) + { + var map = new Dictionary>(StringComparer.Ordinal); + foreach (var raw in File.ReadAllLines(path)) + { + var tab = raw.IndexOf('\t'); + if (tab <= 0) + continue; + var key = raw[..tab]; + var value = raw[(tab + 1)..].Trim(); + if (value.Length == 0) + continue; + if (!map.TryGetValue(key, out var list)) + map[key] = list = []; + list.Add(value); + } + return map; + } + + public static string? One(Dictionary> map, string key) => + map.TryGetValue(key, out var list) && list.Count > 0 ? list[^1] : null; + + // ---- descriptors -------------------------------------------------------------------------- + + /// Validate every descriptor (sorted, deduplicated), then the set (one id once). + /// Returns the active extensions sorted by id, or null with the reasons. + public static List? Validate(IEnumerable paths, List errors) + { + var descriptors = new List(); + var failed = false; + foreach (var path in paths.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal)) + { + var d = ReadDescriptor(path, errors); + failed |= d is null; + if (d is not null) + descriptors.Add(d); + } + foreach (var dup in descriptors.GroupBy(d => d.Id, StringComparer.Ordinal).Where(g => g.Count() > 1)) + { + errors.Add(new(dup.Last().Path, null, "error", "OWENB002", $"extension '{dup.Key}' is declared {dup.Count()} times")); + failed = true; + } + if (failed) + return null; + descriptors.Sort((a, b) => StringComparer.Ordinal.Compare(a.Id, b.Id)); + return descriptors; + } + + private static OwenDescriptor? ReadDescriptor(string path, List errors) + { + var before = errors.Count; + void Bad(string code, string text) => errors.Add(new(path, null, "error", code, text)); + + JsonDocument doc; + try + { + doc = JsonDocument.Parse(File.ReadAllBytes(path)); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException) + { + Bad("OWENB002", $"the extension descriptor cannot be read: {OneLine(ex.Message)}"); + return null; + } + using (doc) + { + var root = doc.RootElement; + if (root.ValueKind != JsonValueKind.Object) + { + Bad("OWENB002", "the extension descriptor is not a JSON object"); + return null; + } + if (!root.TryGetProperty("owen_extension", out var schema) || schema.ValueKind != JsonValueKind.Number + || !schema.TryGetInt32(out var schemaVersion)) + { + Bad("OWENB002", "the extension descriptor has no integer 'owen_extension' schema version"); + return null; + } + if (schemaVersion != DescriptorSchema) + { + Bad("OWENB002", $"descriptor schema owen_extension={schemaVersion} is not one this host reads (it reads {DescriptorSchema}); update Owen.Build"); + return null; + } + var id = Text(root, "id"); + var version = Text(root, "version"); + if (id is null || version is null) + { + Bad("OWENB002", "the extension descriptor needs non-empty string 'id' and 'version'"); + return null; + } + if (!root.TryGetProperty("requires", out var requires) || requires.ValueKind != JsonValueKind.Object) + { + Bad("OWENB002", $"extension '{id}' has no 'requires' object"); + return null; + } + var host = Text(requires, "host"); + if (host is null || !System.Version.TryParse(host, out var needHost)) + { + Bad("OWENB002", $"extension '{id}': 'requires.host' must be a version such as \"0.1.0\""); + return null; + } + if (!requires.TryGetProperty("ownir", out var ownir) || ownir.ValueKind != JsonValueKind.Number + || !ownir.TryGetInt32(out var needOwnIr)) + { + Bad("OWENB002", $"extension '{id}': 'requires.ownir' must be an integer"); + return null; + } + var capabilities = Strings(requires, "capabilities"); + if (capabilities is null) + { + Bad("OWENB002", $"extension '{id}': 'requires.capabilities' must be an array of strings"); + return null; + } + IReadOnlyList generators = []; + if (root.TryGetProperty("frontend", out var frontend)) + { + if (frontend.ValueKind != JsonValueKind.Object + || (frontend.TryGetProperty("generators", out _) && Strings(frontend, "generators") is null)) + { + Bad("OWENB002", $"extension '{id}': 'frontend.generators' must be an array of strings"); + return null; + } + generators = Strings(frontend, "generators") ?? []; + } + + var hostVersion = System.Version.Parse(ToolVersion.Current); + if (needHost > hostVersion) + Bad("OWENB003", $"extension '{id}' {version} requires Owen host {needHost}, this host is {hostVersion}; update Owen.Build"); + if (needOwnIr != OwnIr) + Bad("OWENB003", $"extension '{id}' {version} requires OwnIR {needOwnIr}, this host's core speaks OwnIR {OwnIr}"); + foreach (var unknown in capabilities.Where(c => !Capabilities.Contains(c, StringComparer.Ordinal))) + Bad("OWENB004", $"extension '{id}' {version} requires capability '{unknown}', which this host does not provide (it provides: {string.Join(", ", Capabilities)})"); + return errors.Count > before ? null : new OwenDescriptor(path, id, version, capabilities, generators); + } + } + + private static string? Text(JsonElement e, string name) => + e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String && v.GetString() is { Length: > 0 } s + ? s + : null; + + private static List? Strings(JsonElement e, string name) + { + if (!e.TryGetProperty(name, out var v) || v.ValueKind != JsonValueKind.Array) + return null; + var list = new List(); + foreach (var item in v.EnumerateArray()) + { + if (item.ValueKind != JsonValueKind.String || item.GetString() is not { Length: > 0 } s) + return null; + list.Add(s); + } + return list; + } + + /// obj/owen/extensions.json: what is active in this project, sorted, byte-stable. + public static void WriteManifest(string path, IReadOnlyList descriptors) + { + var o = new StringBuilder(); + o.Append("{\n"); + o.Append($" \"owen_host\": \"{ToolVersion.Current}\",\n"); + o.Append($" \"ownir\": {OwnIr},\n"); + o.Append(" \"extensions\": ["); + for (var i = 0; i < descriptors.Count; i++) + { + var d = descriptors[i]; + o.Append(i == 0 ? "\n" : ",\n"); + o.Append(" {\n"); + o.Append($" \"id\": {JsonSerializer.Serialize(d.Id)},\n"); + o.Append($" \"version\": {JsonSerializer.Serialize(d.Version)},\n"); + o.Append($" \"capabilities\": [{string.Join(", ", d.Capabilities.Order(StringComparer.Ordinal).Select(c => JsonSerializer.Serialize(c)))}]\n"); + o.Append(" }"); + } + o.Append(descriptors.Count == 0 ? "]\n" : "\n ]\n"); + o.Append("}\n"); + Directory.CreateDirectory(System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(path))!); + File.WriteAllText(path, o.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + + // ---- the input set ------------------------------------------------------------------------ + + /// The generator output directories the active extensions declare, in order. + public static IEnumerable Generators(IEnumerable descriptors) => + descriptors.SelectMany(d => d.Generators).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal); + + /// + /// What the extractor reads: the project, then the C# the declared generators wrote under + /// (the extractor's own expansion skips generated files, + /// and an extension's protocol lives in generated code). maps a + /// generator to the files an IDE holds for it in memory; a generator it names is read from + /// there instead of from the last build's output on disk. + /// + public static List Inputs(string project, string? generatedRoot, IReadOnlyList descriptors, + IReadOnlyDictionary>? live = null) + { + var inputs = new List { project }; + if (generatedRoot is null) + return inputs; + foreach (var generator in Generators(descriptors)) + { + if (live is not null && live.TryGetValue(generator, out var held)) + { + inputs.AddRange(held.Order(StringComparer.Ordinal)); + continue; + } + var dir = System.IO.Path.Combine(generatedRoot, generator); + if (Directory.Exists(dir)) + inputs.AddRange(Directory.EnumerateFiles(dir, "*.cs", SearchOption.AllDirectories) + .Order(StringComparer.Ordinal)); + } + return inputs; + } + + // ---- refusals ----------------------------------------------------------------------------- + + // extractor: protocol lowering refused: OrderEndpoints.cs:17: + public static readonly Regex ExtractorRefusal = new(@"refused:\s+(?[^\r\n]+?):(?\d+):\s+(?.+)$"); + + // facts.json: error: + public static readonly Regex CoreRefusal = new(@"^.*?:\s+error:\s+(?.*\((?[^()\r\n]+):(?\d+)\).*)$"); + + /// One error per refusal line that names a location (its file made absolute + /// against the project directory); one project-level error carrying the whole output + /// when none does. Never zero. + public static List Refusals(string output, string project, string code, Regex located) + { + var dir = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(project))!; + var found = new List(); + foreach (var raw in output.Split('\n')) + { + var m = located.Match(raw.TrimEnd('\r')); + if (!m.Success) + continue; + var file = m.Groups["file"].Value; + found.Add(new(System.IO.Path.IsPathRooted(file) ? file : System.IO.Path.GetFullPath(System.IO.Path.Combine(dir, file)), + int.Parse(m.Groups["line"].Value, System.Globalization.CultureInfo.InvariantCulture), + "error", code, m.Groups["text"].Value)); + } + if (found.Count == 0) + found.Add(new(project, null, "error", code, OneLine(output))); + return found; + } + + // `File.cs(18): warning OWN002: …` / `File.cs(18,3): error …` + private static readonly Regex CanonicalOrigin = new(@"^(?[^\r\n(]+?)\((?\d+(?:,\d+)*)\): (?(?:warning|error) .*)$"); + + /// Make the origin of every canonical line absolute against the project + /// directory; every other byte, the message text included, is left as it is. + public static string Absolute(string text, string project) + { + var dir = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(project))!; + // split and re-join on '\n': a trailing newline survives as the empty last piece + return string.Join('\n', text.Split('\n').Select(piece => + { + var line = piece.TrimEnd('\r'); + var m = CanonicalOrigin.Match(line); + return m.Success && !System.IO.Path.IsPathRooted(m.Groups["file"].Value) + ? $"{System.IO.Path.GetFullPath(System.IO.Path.Combine(dir, m.Groups["file"].Value))}({m.Groups["pos"].Value}): {m.Groups["rest"].Value}" + : piece; + })); + } + + public static string OneLine(string text) => + string.Join(" ", text.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())); +} diff --git a/frontend/roslyn/OwnSharp.Cli/Program.cs b/frontend/roslyn/OwnSharp.Cli/Program.cs index 386b8537..2c0f73fe 100644 --- a/frontend/roslyn/OwnSharp.Cli/Program.cs +++ b/frontend/roslyn/OwnSharp.Cli/Program.cs @@ -38,6 +38,22 @@ } } +// The live Owen service for an IDE (OX-02, docs/notes/owen-visual-studio-preregistration.md): +// Owen.VisualStudio starts it from the Owen.Build package and speaks owen-live/1 to it. Not a +// user verb either. +if (args[0] == "serve") +{ + try + { + return await ServeCommand.RunAsync(args[1..]).ConfigureAwait(false); + } + catch (Exception ex) + { + Console.Error.WriteLine($"owen serve: internal failure: {ex}"); + return 5; + } +} + if (args[0] != "check") { Console.Error.WriteLine($"owen: unknown command '{args[0]}'"); diff --git a/frontend/roslyn/OwnSharp.Cli/ServeCommand.cs b/frontend/roslyn/OwnSharp.Cli/ServeCommand.cs new file mode 100644 index 00000000..d686481a --- /dev/null +++ b/frontend/roslyn/OwnSharp.Cli/ServeCommand.cs @@ -0,0 +1,310 @@ +using System.Diagnostics; +using System.Text; +using System.Text.Json; + +namespace OwnSharp.Cli; + +/// +/// `owen serve` — the live Owen service for an IDE (OX-02, +/// docs/notes/owen-visual-studio-preregistration.md §4–§5). Long-lived: started once by +/// Owen.VisualStudio, it answers analysis requests over its stdin/stdout until told to +/// shut down or until its stdin closes. Not a user verb. +/// +/// Protocol owen-live/1. Every message, both ways, is +/// Content-Length: n\r\n\r\n followed by n bytes of UTF-8 JSON. Nothing else is ever +/// written to stdout (the console is redirected to stderr before anything can print). The +/// first client message is hello naming this protocol. A malformed frame, a body that is +/// not a JSON object, an unknown type or another protocol is fatal: one fatal +/// frame, the reason on stderr, exit 3 — never a guess, never a resynchronisation. +/// +/// Order. Analyses run one at a time, in arrival order. A queued request whose key +/// has a newer queued request is answered superseded without running; cancel of a +/// queued request answers cancelled. A running analysis completes (the extractor is not +/// cancellable); the client publishes a result only for the latest version it issued. +/// +internal static class ServeCommand +{ + public const string Protocol = "owen-live/1"; + public const int ProtocolError = 3; + private const int MaxFrame = 256 * 1024 * 1024; + + private sealed record Pending(long Id, string Key, long Version, string Request, + IReadOnlyList Documents, IReadOnlyList Generated); + + private sealed class ProtocolException(string message) : Exception(message); + + public static async Task RunAsync(string[] args) + { + if (args.Length != 0) + { + Console.Error.WriteLine("usage: owen serve (started by an IDE; speaks owen-live/1 on stdin/stdout)"); + return 2; + } + var input = new BufferedStream(Console.OpenStandardInput()); + var output = Console.OpenStandardOutput(); + // stdout carries frames and nothing else: anything printed from here on goes to stderr + Console.SetOut(Console.Error); + var writeGate = new object(); + void Send(object message) + { + var body = JsonSerializer.SerializeToUtf8Bytes(message); + var header = Encoding.ASCII.GetBytes($"Content-Length: {body.Length}\r\n\r\n"); + lock (writeGate) + { + output.Write(header); + output.Write(body); + output.Flush(); + } + } + int Fatal(string reason) + { + Console.Error.WriteLine($"owen serve: fatal: {reason}"); + try { Send(new { type = "fatal", message = reason }); } catch (IOException) { /* the client is gone */ } + return ProtocolError; + } + + var pending = new List(); + var signal = new SemaphoreSlim(0); + var stop = new CancellationTokenSource(); + var worker = Task.Run(async () => + { + while (!stop.IsCancellationRequested) + { + try { await signal.WaitAsync(stop.Token).ConfigureAwait(false); } + catch (OperationCanceledException) { return; } + Pending? next; + lock (pending) + { + if (pending.Count == 0) + continue; + next = pending[0]; + pending.RemoveAt(0); + } + Send(await AnswerAsync(next).ConfigureAwait(false)); + // Each analysis builds a whole compilation (metadata for every reference); collect + // it now, between answers, rather than in the middle of the next one. + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + }); + + try + { + var hello = ReadFrame(input) ?? throw new ProtocolException("stdin closed before hello"); + if (Type(hello) != "hello" || Str(hello, "protocol") != Protocol) + throw new ProtocolException($"the first message must be {{\"type\":\"hello\",\"protocol\":\"{Protocol}\"}}"); + Send(new + { + type = "hello", + protocol = Protocol, + host = ToolVersion.Current, + capabilities = OwenHost.Capabilities, + }); + + while (true) + { + if (ReadFrame(input) is not { } message) + return 0; // the IDE closed our stdin: it is gone, so are we + switch (Type(message)) + { + case "analyze": + var request = new Pending( + Long(message, "id"), Str(message, "key"), Long(message, "version"), Str(message, "request"), + Documents(message, "documents"), Documents(message, "generated")); + lock (pending) + { + foreach (var older in pending.Where(p => p.Key == request.Key).ToList()) + { + pending.Remove(older); + Send(Answer(older, "superseded")); + } + pending.Add(request); + } + signal.Release(); + break; + case "cancel": + var id = Long(message, "id"); + lock (pending) + { + var queued = pending.FirstOrDefault(p => p.Id == id); + if (queued is not null) + { + pending.Remove(queued); + Send(Answer(queued, "cancelled")); + } + } + break; + case "shutdown": + return 0; + case var other: + throw new ProtocolException($"unknown message type '{other}'"); + } + } + } + catch (ProtocolException ex) + { + return Fatal(ex.Message); + } + finally + { + stop.Cancel(); + } + } + + private static object Answer(Pending p, string status) => new + { + type = "result", + id = p.Id, + key = p.Key, + version = p.Version, + status, + diagnostics = Array.Empty(), + extensions = Array.Empty(), + timing = new Dictionary(), + }; + + private static async Task AnswerAsync(Pending p) + { + var clock = Stopwatch.StartNew(); + try + { + var result = await LiveAnalysis.RunAsync(p.Request, p.Documents, p.Generated).ConfigureAwait(false); + return new + { + type = "result", + id = p.Id, + key = p.Key, + version = p.Version, + status = "ok", + diagnostics = result.Diagnostics.Select(d => new + { + code = d.Code, + severity = d.Severity, + message = d.Message, + file = d.File, + line = d.Line, + column = d.Column, + origin = d.Origin, + related = d.Related.Select(r => new { file = r.File, line = r.Line, column = r.Column, message = r.Message }), + }), + extensions = result.Extensions, + timing = result.TimingMs, + }; + } + catch (Exception ex) + { + // an analysis that blew up is answered, never dropped: the IDE shows it + Console.Error.WriteLine($"owen serve: analysis {p.Id} failed: {ex}"); + return new + { + type = "result", + id = p.Id, + key = p.Key, + version = p.Version, + status = "error", + diagnostics = new[] + { + new + { + code = "OWENB012", severity = "error", + message = $"the live analysis failed internally: {ex.GetType().Name}: {OwenHost.OneLine(ex.Message)}", + file = p.Key, line = (int?)null, column = (int?)null, origin = "host", + related = Array.Empty(), + }, + }, + extensions = Array.Empty(), + timing = new Dictionary { ["total"] = clock.ElapsedMilliseconds }, + }; + } + } + + // ---- frames --------------------------------------------------------------------------------- + + /// One frame, or null at a clean end of stream (between frames). + private static JsonElement? ReadFrame(Stream input) + { + int? length = null; + var sawAny = false; + while (true) + { + var line = ReadHeaderLine(input, ref sawAny); + if (line is null) + return sawAny ? throw new ProtocolException("stdin closed inside a frame header") : null; + if (line.Length == 0) + break; + const string prefix = "Content-Length: "; + if (!line.StartsWith(prefix, StringComparison.Ordinal) + || !int.TryParse(line.AsSpan(prefix.Length), System.Globalization.NumberStyles.None, + System.Globalization.CultureInfo.InvariantCulture, out var n) + || n < 0 || n > MaxFrame || length is not null) + throw new ProtocolException($"malformed frame header '{line}'"); + length = n; + } + if (length is null) + throw new ProtocolException("a frame without Content-Length"); + var body = new byte[length.Value]; + var read = 0; + while (read < body.Length) + { + var got = input.Read(body, read, body.Length - read); + if (got == 0) + throw new ProtocolException($"stdin closed inside a {length} byte frame body"); + read += got; + } + try + { + using var doc = JsonDocument.Parse(body); + if (doc.RootElement.ValueKind != JsonValueKind.Object) + throw new ProtocolException("a frame body that is not a JSON object"); + return doc.RootElement.Clone(); + } + catch (JsonException ex) + { + throw new ProtocolException($"a frame body that is not JSON: {ex.Message}"); + } + } + + private static string? ReadHeaderLine(Stream input, ref bool sawAny) + { + var bytes = new List(); + while (true) + { + var b = input.ReadByte(); + if (b < 0) + return bytes.Count == 0 && !sawAny ? null : throw new ProtocolException("stdin closed inside a frame header"); + sawAny = true; + if (b == '\n') + { + if (bytes.Count == 0 || bytes[^1] != '\r') + throw new ProtocolException("a frame header line not ended by CRLF"); + bytes.RemoveAt(bytes.Count - 1); + return Encoding.ASCII.GetString(bytes.ToArray()); + } + if (bytes.Count > 1024) + throw new ProtocolException("a frame header line longer than 1024 bytes"); + bytes.Add((byte)b); + } + } + + private static string Type(JsonElement m) => Str(m, "type"); + + private static string Str(JsonElement m, string name) => + m.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String + ? v.GetString()! + : throw new ProtocolException($"'{name}' must be a string"); + + private static long Long(JsonElement m, string name) => + m.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt64(out var n) + ? n + : throw new ProtocolException($"'{name}' must be an integer"); + + private static List Documents(JsonElement m, string name) + { + if (!m.TryGetProperty(name, out var v)) + return []; + if (v.ValueKind != JsonValueKind.Array) + throw new ProtocolException($"'{name}' must be an array"); + return v.EnumerateArray().Select(d => new LiveDocument(Str(d, "path"), Str(d, "text"))).ToList(); + } +} diff --git a/scripts/owen_live_gate.py b/scripts/owen_live_gate.py new file mode 100644 index 00000000..b8be0adc --- /dev/null +++ b/scripts/owen_live_gate.py @@ -0,0 +1,608 @@ +"""OX-02 live gate: the Owen IDE service, as Owen.VisualStudio uses it, outside any IDE. + +docs/notes/owen-visual-studio-preregistration.md. Packs Owen.Build (with this platform's Rust +core), Owen.TypedBuilder and two synthetic extensions, creates a project OUTSIDE the checkout +that references Owen.TypedBuilder, and then — with no build of that project — proves: + + L0 a design-time build (what Visual Studio runs on load) writes obj/owen/live.txt naming + the package's service, and analyses nothing; + S* `owen serve` speaks owen-live/1 and nothing else on stdout; malformed input is fatal (3); + K1 an unsaved clean edit -> no OWN finding; K2 an unsaved second Submit -> OWN002 + K3 the line deleted -> gone; K5 broken syntax -> an answer, then recovery + K4s queued requests of one project: only the newest runs (superseded), cancel answers cancelled + K7 an extension needing an unknown capability -> OWENB004, live + K8 a second extension with its own generator -> its finding too, no host change + P10 build/live parity on saved sources (code, severity, message, file, line) + T latency (warm p50/p95, cold start) and the service's working set after 10 and 100 runs + C tests/owen-live/LiveClientTests against the real service (K4, K6, M1/M2 controls) + G neither the service nor Owen.VisualStudio names an extension + +Run: python scripts/owen_live_gate.py [--rust-core ] [--keep] +""" + +from __future__ import annotations + +import json +import os +import re +import shutil +import statistics +import subprocess +import sys +import tempfile +import time +from typing import Any + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import owen_extension_gate as ox + +ROOT = ox.ROOT +LIVE = os.path.join(ROOT, "tests", "owen-live") +FIXTURE = os.path.join(LIVE, "LiveFixture") +TEST_PROTOCOL = os.path.join(ROOT, "tests", "owen-extensions", "Owen.TestProtocol") +GENERIC_SOURCES = ( + os.path.join(ox.ROSLYN, "OwnSharp.Cli", "ServeCommand.cs"), + os.path.join(ox.ROSLYN, "OwnSharp.Cli", "LiveAnalysis.cs"), + os.path.join(ox.ROSLYN, "OwnSharp.Cli", "OwenHost.cs"), + os.path.join(ox.ROSLYN, "Owen.VisualStudio"), +) +CANONICAL = re.compile( + r"^(?P.+?)\((?P\d+)(?:,\d+)?\): (?Pwarning|error) " + r"(?POW(?:EN)?[NB]?\d+): " + r"(?P.*?)(?: \[[^\]]*\.csproj\])?$" +) + +check = ox.check + +USE_CLEAN = open(os.path.join(FIXTURE, "Use.cs.txt"), encoding="utf-8").read() +ANCHOR = " var submitted = draft.Submit(now);\n" +STALE_LINE = " draft.Submit(now);\n" +USE_STALE = USE_CLEAN.replace(ANCHOR, ANCHOR + STALE_LINE) +USE_BROKEN = USE_CLEAN.replace(ANCHOR, ANCHOR + " draft.Submit(now\n") +USE_COPY = USE_CLEAN.replace( + ANCHOR, " var copy = draft;\n" + ANCHOR + " copy.Submit(now);\n" +) +USE_RAW = USE_CLEAN.replace(ANCHOR, ANCHOR + " var customer = order.Customer;\n") +USE_REFUSED = USE_CLEAN.replace(ANCHOR, ANCHOR + ' System.Console.WriteLine("x");\n') +GATE_CLEAN = open(os.path.join(FIXTURE, "Gate.cs.txt"), encoding="utf-8").read() +GATE_STALE = GATE_CLEAN.replace( + " var unlocked = locked.Coin();\n", + " var unlocked = locked.Coin();\n locked.Coin();\n", +) + + +# ---- the service, as a client sees it --------------------------------------------------------- + + +class Service: + def __init__(self, host: str, env: dict[str, str], cwd: str) -> None: + env = dict(env, DOTNET_ROLL_FORWARD="Major") + self.proc = subprocess.Popen( + ["dotnet", "exec", host, "serve"], + cwd=cwd, + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + self.next_id = 0 + + def send(self, message: dict[str, Any]) -> None: + body = json.dumps(message).encode("utf-8") + assert self.proc.stdin is not None + self.proc.stdin.write(b"Content-Length: %d\r\n\r\n" % len(body) + body) + self.proc.stdin.flush() + + def read(self) -> dict[str, Any] | None: + assert self.proc.stdout is not None + length = None + while True: + line = self.proc.stdout.readline() + if not line: + return None + if line == b"\r\n": + break + m = re.fullmatch(rb"Content-Length: (\d+)\r\n", line) + if m is None: + raise AssertionError(f"not a frame header on the service's stdout: {line!r}") + length = int(m.group(1)) + assert length is not None + body = self.proc.stdout.read(length) + result: dict[str, Any] = json.loads(body) + return result + + def hello(self) -> dict[str, Any] | None: + self.send({"type": "hello", "protocol": "owen-live/1"}) + return self.read() + + def submit( + self, + key: str, + version: int, + request: str, + documents: list[tuple[str, str]], + generated: list[tuple[str, str]], + ) -> int: + self.next_id += 1 + self.send( + { + "type": "analyze", + "id": self.next_id, + "key": key, + "version": version, + "request": request, + "documents": [{"path": p, "text": t} for p, t in documents], + "generated": [{"path": p, "text": t} for p, t in generated], + } + ) + return self.next_id + + def analyze( + self, + key: str, + version: int, + request: str, + documents: list[tuple[str, str]], + generated: list[tuple[str, str]], + ) -> dict[str, Any]: + self.submit(key, version, request, documents, generated) + answer = self.read() + assert answer is not None, "the service closed its stdout" + return answer + + def close(self) -> tuple[int, str]: + try: + self.send({"type": "shutdown"}) + except OSError: + pass + try: + _, err = self.proc.communicate(timeout=30) + except subprocess.TimeoutExpired: + self.proc.kill() + _, err = self.proc.communicate() + return self.proc.returncode, err.decode("utf-8", "replace") + + +def raw_exchange( + host: str, env: dict[str, str], cwd: str, payload: bytes +) -> tuple[int, bytes, str]: + done = subprocess.run( + ["dotnet", "exec", host, "serve"], + cwd=cwd, + env=dict(env, DOTNET_ROLL_FORWARD="Major"), + input=payload, + capture_output=True, + timeout=120, + check=False, + ) + return done.returncode, done.stdout, done.stderr.decode("utf-8", "replace") + + +def frame(message: dict[str, Any]) -> bytes: + body = json.dumps(message).encode("utf-8") + return b"Content-Length: %d\r\n\r\n" % len(body) + body + + +def frames(data: bytes) -> list[dict[str, Any]] | None: + """Every byte of stdout as frames, or None if anything else is there.""" + out = [] + while data: + m = re.match(rb"Content-Length: (\d+)\r\n\r\n", data) + if m is None: + return None + start = m.end() + end = start + int(m.group(1)) + out.append(json.loads(data[start:end])) + data = data[end:] + return out + + +def codes(answer: dict[str, Any]) -> list[str]: + return sorted(d["code"] for d in answer["diagnostics"]) + + +# ---- the fixture ------------------------------------------------------------------------------ + + +def scaffold(c: ox.Consumer, name: str) -> None: + c.dir = os.path.join(c.ws, name) + os.makedirs(c.dir) + done = c.dotnet("new", "classlib", "-n", name, "-o", ".", "--framework", "net8.0") + if done.returncode != 0: + raise SystemExit(f"FAIL: dotnet new classlib: {done.stdout[-800:]}{done.stderr[-800:]}") + os.remove(c.path("Class1.cs")) + done = c.dotnet("add", "package", "Owen.TypedBuilder", "--version", ox.VERSION) + if done.returncode != 0: + raise SystemExit(f"FAIL: dotnet add package: {done.stdout[-1500:]}") + write(c.path("Order.cs"), open(os.path.join(FIXTURE, "Order.cs.txt"), encoding="utf-8").read()) + write(c.path("Use.cs"), USE_CLEAN) + + +def write(path: str, text: str) -> None: + with open(path, "w", encoding="utf-8", newline="\n") as f: + f.write(text) + + +def design_time(c: ox.Consumer) -> subprocess.CompletedProcess[str]: + done = c.dotnet("restore", "-nologo") + if done.returncode != 0: + raise SystemExit(f"FAIL: restore: {done.stdout[-1500:]}") + return c.dotnet( + "msbuild", + "-nologo", + "-t:CoreCompile", + "-p:DesignTimeBuild=true", + "-p:SkipCompilerExecution=true", + "-p:ProvideCommandLineArgs=true", + "-p:BuildingProject=false", + ) + + +def read_request(path: str) -> dict[str, list[str]]: + out: dict[str, list[str]] = {} + with open(path, encoding="utf-8") as f: + for line in f: + key, _, value = line.rstrip("\r\n").partition("\t") + if value: + out.setdefault(key, []).append(value) + return out + + +def generated_like_an_ide( + c: ox.Consumer, packages: list[str], extra: dict[str, str] +) -> list[tuple[str, str]]: + """What Visual Studio's workspace holds as source-generated documents, WITHOUT building the + project: build a throwaway copy, and name each output /// + — a base of the IDE's own, not obj/generated, so the service has to relocate it.""" + copy = ox.Consumer.__new__(ox.Consumer) + copy.__dict__.update(c.__dict__) + copy.dir = os.path.join(c.ws, "GenCopy-" + str(len(packages))) + shutil.copytree(c.dir, copy.dir, ignore=shutil.ignore_patterns("bin", "obj")) + for rel, text in extra.items(): + write(os.path.join(copy.dir, rel), text) + done = copy.build("-p:OwenEnabled=false", "-p:EmitCompilerGeneratedFiles=true") + if done.returncode != 0: + raise SystemExit(f"FAIL: building the generator copy: {done.stdout[-1500:]}") + root = os.path.join(copy.dir, "obj", "Debug", "net8.0", "generated") + if not os.path.isdir(root): + root = os.path.join(copy.dir, "obj", "generated") + out = [] + for dirpath, _, files in os.walk(root): + for name in sorted(files): + full = os.path.join(dirpath, name) + rel = os.path.relpath(full, root) + with open(full, encoding="utf-8") as f: + out.append((os.path.join(c.dir, rel), f.read())) + return sorted(out) + + +def build_lines(c: ox.Consumer) -> tuple[int, set[tuple[str, int, str, str, str]]]: + done = c.build() + found = set() + for raw in (done.stdout + done.stderr).splitlines(): + m = CANONICAL.match(raw.strip()) + if m and m.group("code").startswith(("OWN", "OWENB")): + found.add( + ( + os.path.normcase(os.path.realpath(m.group("file"))), + int(m.group("line")), + m.group("sev"), + m.group("code"), + m.group("msg"), + ) + ) + return done.returncode, found + + +def live_set(answer: dict[str, Any]) -> set[tuple[str, int, str, str, str]]: + return { + ( + os.path.normcase(os.path.realpath(d["file"])), + int(d["line"] or 0), + d["severity"], + d["code"], + d["message"], + ) + for d in answer["diagnostics"] + } + + +# ---- the gate --------------------------------------------------------------------------------- + + +def generic() -> None: + hits = [] + for src in GENERIC_SOURCES: + paths = ( + [src] + if os.path.isfile(src) + else [ + os.path.join(d, n) + for d, _, fs in os.walk(src) + for n in fs + if n.endswith((".cs", ".csproj", ".vsixmanifest")) + and os.sep + "obj" + os.sep not in d + and os.sep + "bin" + os.sep not in d + ] + ) + for path in paths: + with open(path, encoding="utf-8", errors="replace") as f: + text = f.read() + if "TypedBuilder" in text or "TestProtocol" in text or "Turnstile" in text: + hits.append(os.path.relpath(path, ROOT)) + check("G-host-is-generic", hits == [], f"service/VSIX sources naming an extension: {hits}") + + +def protocol_errors(host: str, env: dict[str, str], cwd: str) -> None: + for name, payload in ( + ("S-bad-protocol", b'Content-Length: 41\r\n\r\n{"type":"hello","protocol":"owen-live/9"}'), + ("S-bad-header", b"Content-Lenght: 2\r\n\r\n{}"), + ("S-not-json", b"Content-Length: 5\r\n\r\nhello"), + ( + "S-unknown-type", + frame({"type": "hello", "protocol": "owen-live/1"}) + frame({"type": "poke"}), + ), + ): + rc, out, err = raw_exchange(host, env, cwd, payload) + parsed = frames(out) + fatal = parsed is not None and parsed and parsed[-1].get("type") == "fatal" + check( + name, + rc == 3 and bool(fatal) and "fatal" in err, + f"exit {rc}, stdout frames {[p.get('type') for p in parsed or []]}, " + f"stderr {err.strip()[:120]!r}", + ) + + +def main() -> int: + argv = sys.argv[1:] + given = argv[argv.index("--rust-core") + 1] if "--rust-core" in argv else None + keep = "--keep" in argv + work = tempfile.mkdtemp(prefix="owen-live-gate-") + try: + run_gate(work, given) + finally: + if keep: + print(f"kept: {work}") + else: + shutil.rmtree(work, ignore_errors=True) + print(f"owen live gate ({ox.KEY}): {len(ox.PASSED)} checks passed, {len(ox.FAILS)} failed") + for f in ox.FAILS: + print(f"FAIL: {f}") + return 1 if ox.FAILS else 0 + + +def run_gate(work: str, given: str | None) -> None: + generic() + stage = ox.stage_core(work, given) + feed = ox.pack(work, stage) + done = ox.run( + ["dotnet", "pack", TEST_PROTOCOL, "-c", "Release", "-o", feed, "-nologo"], cwd=ROOT + ) + if done.returncode != 0: + raise SystemExit(f"FAIL: pack Owen.TestProtocol: {done.stdout[-1500:]}") + c = ox.Consumer(work, feed) + ox.isolation(c) + scaffold(c, "LiveFixture") + project = c.path("LiveFixture.csproj") + use = c.path("Use.cs") + + # ---- L0: the live request, from a design-time build only -------------------------------- + dt = design_time(c) + live_txt = c.path("obj", "owen", "live.txt") + request = read_request(live_txt) if os.path.exists(live_txt) else {} + host = (request.get("host") or [""])[-1] + check( + "L0-live-request", + dt.returncode == 0 + and bool(host) + and os.path.isfile(host) + and os.path.realpath(host).startswith(os.path.realpath(c.nuget)) + and len(request.get("descriptor", [])) == 1, + f"design-time build exit {dt.returncode}; live.txt keys {sorted(request)}; host {host}", + ) + built = [n for _, _, fs in os.walk(c.path("bin")) for n in fs] + check( + "L0-no-build", + built == [] + and not os.path.exists(c.path("obj", "owen", "request.txt")) + and not os.path.exists(c.path("obj", "owen", "extensions.json")), + f"no bin/ output ({built}), no build-check request, no manifest: nothing was analysed", + ) + + generated = generated_like_an_ide(c, ["Owen.TypedBuilder"], {}) + check( + "L0-generated-in-memory", + len(generated) >= 2 and not os.path.exists(c.path("obj", "Debug", "net8.0", "generated")), + f"{len(generated)} generated documents held in memory, none on disk: " + f"{[os.path.relpath(p, c.dir) for p, _ in generated]}", + ) + + # ---- the service ------------------------------------------------------------------------ + protocol_errors(host, c.env, c.dir) + t0 = time.perf_counter() + svc = Service(host, c.env, c.dir) + hello = svc.hello() + check( + "S-hello", hello is not None and hello.get("protocol") == "owen-live/1", f"hello: {hello}" + ) + first = svc.analyze(project, 1, live_txt, [(use, USE_CLEAN)], generated) + cold = (time.perf_counter() - t0) * 1000 + check( + "K1-clean", + first["status"] == "ok" and not [x for x in codes(first) if x.startswith("OWN")], + f"unsaved clean text: status {first['status']}, codes {codes(first)}, " + f"extensions {first['extensions']}", + ) + + k2 = svc.analyze(project, 2, live_txt, [(use, USE_STALE)], generated) + own002 = [d for d in k2["diagnostics"] if d["code"] == "OWN002"] + stale_line = USE_STALE.splitlines().index(STALE_LINE.rstrip("\n")) + 1 + region_line = ( + USE_STALE.splitlines().index(" OrderProtocol.WithDraft(order, draft =>") + 1 + ) + with open(use, encoding="utf-8") as f: + on_disk = f.read() + check( + "K2-unsaved-own002", + len(own002) == 1 + and os.path.realpath(own002[0]["file"]) == os.path.realpath(use) + and own002[0]["line"] == region_line + and any(r["line"] == stale_line for r in own002[0]["related"]) + and on_disk == USE_CLEAN, + f"disk still clean={on_disk == USE_CLEAN}; OWN002 at line {[d['line'] for d in own002]} " + f"(region entry {region_line}), witness lines " + f"{[r['line'] for d in own002 for r in d['related']]} (inserted line {stale_line}); " + f"message {[d['message'] for d in own002]}", + ) + + k3 = svc.analyze(project, 3, live_txt, [(use, USE_CLEAN)], generated) + check("K3-deleted-gone", "OWN002" not in codes(k3), f"line deleted again: codes {codes(k3)}") + + k5 = svc.analyze(project, 4, live_txt, [(use, USE_BROKEN)], generated) + k5b = svc.analyze(project, 5, live_txt, [(use, USE_STALE)], generated) + check( + "K5-broken-syntax", + k5["status"] == "ok" and "OWN002" in codes(k5b), + f"broken edit answered {k5['status']} with {codes(k5)}; " + f"the next edit recovered: {codes(k5b)}", + ) + + # K4 on the service: three queued requests of one project, the newest wins + ids = [svc.submit(project, v, live_txt, [(use, USE_STALE)], generated) for v in (6, 7, 8)] + cancel_id = svc.submit(project + ".other", 1, live_txt, [(use, USE_CLEAN)], generated) + svc.send({"type": "cancel", "id": cancel_id}) + answers: dict[int, dict[str, Any]] = {} + while len(answers) < 4: + got = svc.read() + assert got is not None + answers[got["id"]] = got + statuses = [answers[i]["status"] for i in ids] + check( + "K4s-newest-runs", + statuses[-1] == "ok" + and set(statuses[:-1]) <= {"ok", "superseded"} + and "superseded" in statuses + and answers[cancel_id]["status"] in ("cancelled", "ok"), + f"versions 6,7,8 -> {statuses}; cancelled request -> {answers[cancel_id]['status']}", + ) + + # latency and footprint + times = [] + sets = [] + phases = [] + for i in range(100): + start = time.perf_counter() + a = svc.analyze( + project, 100 + i, live_txt, [(use, USE_STALE if i % 2 else USE_CLEAN)], generated + ) + times.append((time.perf_counter() - start) * 1000) + phases.append((a["timing"].get("extract", 0), a["timing"].get("core", 0))) + if i in (9, 99): + sets.append(a["timing"].get("working_set_kb", 0)) + warm = sorted(times[5:]) + p50 = statistics.median(warm) + p95 = warm[int(len(warm) * 0.95) - 1] + check( + "T-warm-latency", + p95 < 750, + f"warm analysis round trip p50 {p50:.0f} ms, p95 {p95:.0f} ms " + f"(threshold 750; in the service: " + f"extract median {statistics.median(x for x, _ in phases[5:]):.0f} ms, core median " + f"{statistics.median(y for _, y in phases[5:]):.0f} ms); cold start " + f"(spawn + hello + first analysis) {cold:.0f} ms", + ) + check( + "T-memory", + sets[1] < 2 * sets[0], + f"working set after 10 analyses {sets[0] // 1024} MB, after 100 {sets[1] // 1024} MB", + ) + + # ---- K7: an extension that needs what this host lacks ----------------------------------- + descriptor = request["descriptor"][0] + with open(descriptor, "rb") as f: + original = f.read() + try: + doc = json.loads(original) + doc["requires"]["capabilities"].append("telepathy") + with open(descriptor, "w", encoding="utf-8") as f: + json.dump(doc, f) + k7 = svc.analyze(project, 300, live_txt, [(use, USE_STALE)], generated) + check( + "K7-unknown-capability", + codes(k7) == ["OWENB004"] and "telepathy" in k7["diagnostics"][0]["message"], + f"live answer: {[(d['code'], d['message'][:90]) for d in k7['diagnostics']]}", + ) + finally: + with open(descriptor, "wb") as f: + f.write(original) + + # ---- K8: a second extension with its own generator ---------------------------------------- + done = c.dotnet("add", "package", "Owen.TestProtocol", "--version", ox.VERSION) + if done.returncode != 0: + raise SystemExit(f"FAIL: add Owen.TestProtocol: {done.stdout[-1500:]}") + write(c.path("Gate.cs"), GATE_CLEAN) + dt = design_time(c) + request = read_request(live_txt) + generated2 = generated_like_an_ide(c, ["Owen.TypedBuilder", "Owen.TestProtocol"], {}) + gate = c.path("Gate.cs") + k8 = svc.analyze(project, 400, live_txt, [(use, USE_STALE), (gate, GATE_STALE)], generated2) + files = sorted(os.path.basename(d["file"]) for d in k8["diagnostics"] if d["code"] == "OWN002") + check( + "K8-two-extensions", + len(request.get("descriptor", [])) == 2 + and sorted(k8["extensions"]) == ["Owen.TestProtocol", "Owen.TypedBuilder"] + and files == ["Gate.cs", "Use.cs"], + f"descriptors {len(request.get('descriptor', []))}; active {k8['extensions']}; " + f"OWN002 in {files}", + ) + + rc, err = svc.close() + check("S-shutdown", rc == 0, f"shutdown exit {rc}; stderr {err.strip()[-200:]!r}") + + # ---- P10: build/live parity on saved sources --------------------------------------------- + write(gate, GATE_CLEAN) + svc = Service(host, c.env, c.dir) + svc.hello() + for name, text in ( + ("clean", USE_CLEAN), + ("stale", USE_STALE), + ("copy", USE_COPY), + ("raw", USE_RAW), + ("refused", USE_REFUSED), + ): + write(use, text) + rc, built_set = build_lines(c) + live = svc.analyze(project, 500, live_txt, [], []) + live_found = live_set(live) + check( + f"P10-parity-{name}", + built_set == live_found and (name == "clean" or bool(built_set)), + f"build {sorted(x[3:] for x in built_set)} vs live {sorted(x[3:] for x in live_found)}" + + ( + "" + if built_set == live_found + else f"; build-only {built_set - live_found}; live-only {live_found - built_set}" + ), + ) + svc.close() + write(use, USE_CLEAN) + + # ---- C: the client library against the real service -------------------------------------- + tests = os.path.join(LIVE, "LiveClientTests") + done = ox.run(["dotnet", "build", tests, "-c", "Release", "-nologo", "-v", "q"], cwd=ROOT) + if done.returncode != 0: + raise SystemExit(f"FAIL: LiveClientTests build: {done.stdout[-1500:]}") + dll = os.path.join(tests, "bin", "Release", "net8.0", "LiveClientTests.dll") + done = ox.run(["dotnet", dll, host, live_txt], cwd=c.dir, env=c.env) + print(done.stdout, end="") + last = done.stdout.strip().splitlines()[-1] if done.stdout.strip() else done.stderr[-300:] + check( + "C-client-tests", + done.returncode == 0 and "0 failed" in done.stdout, + f"LiveClientTests exit {done.returncode}: {last}", + ) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/check_extractor_in_process.py b/tests/check_extractor_in_process.py index 65992c89..8abc5c12 100644 --- a/tests/check_extractor_in_process.py +++ b/tests/check_extractor_in_process.py @@ -43,8 +43,15 @@ def _run(cmd: list[str], cwd: str = ROOT) -> subprocess.CompletedProcess[str]: - return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, encoding="utf-8", - errors="replace", check=False) + return subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) def _build(project: str) -> None: @@ -64,17 +71,21 @@ def _jobs() -> list[tuple[str, list[str]]]: jobs.append((f"samples/{name}", [f"{SAMPLES}/{name}", "--flow-locals"])) for name in sorted(os.listdir(os.path.join(ROOT, PROTO, "cases"))): if name.endswith(".cs"): - jobs.append((f"cases/{name}", [f"{PROTO}/Api", f"{PROTO}/cases/{name}", - "--flow-locals"])) + jobs.append( + (f"cases/{name}", [f"{PROTO}/Api", f"{PROTO}/cases/{name}", "--flow-locals"]) + ) for sub in ("same-assembly", "unrelated"): jobs.append((sub, [f"{PROTO}/{sub}", "--flow-locals"])) - jobs.append(("heap-effects-samples", ["frontend/roslyn/heap-effects-samples", - "--flow-locals"])) - jobs.append(("project-input-sample", - ["frontend/roslyn/project-input-sample/ProjectInputSample.csproj", - "--flow-locals"])) - jobs.append(("OrderBackend", ["samples/OrderBackend/OrderBackend/OrderBackend.csproj", - "--flow-locals"])) + jobs.append(("heap-effects-samples", ["frontend/roslyn/heap-effects-samples", "--flow-locals"])) + jobs.append( + ( + "project-input-sample", + ["frontend/roslyn/project-input-sample/ProjectInputSample.csproj", "--flow-locals"], + ) + ) + jobs.append( + ("OrderBackend", ["samples/OrderBackend/OrderBackend/OrderBackend.csproj", "--flow-locals"]) + ) return jobs @@ -89,8 +100,9 @@ def _in_process(driver: str, jobs: list[dict[str, object]], tmp: str) -> list[in f.write(json.dumps(job) + "\n") done = _run(["dotnet", "exec", driver, path]) if done.returncode != 0: - raise SystemExit(f"FAIL: the in-process driver exited {done.returncode}: " - f"{done.stderr[-2000:]}") + raise SystemExit( + f"FAIL: the in-process driver exited {done.returncode}: {done.stderr[-2000:]}" + ) lines = [ln for ln in done.stdout.splitlines() if ln.strip()] return [int(json.loads(ln)["rc"]) for ln in lines] @@ -115,11 +127,14 @@ def main() -> int: cli_out = [os.path.join(tmp, f"cli-{i}.json") for i in range(len(jobs))] cli_rc = [_cli(dll, args, out) for (_, args), out in zip(jobs, cli_out, strict=True)] - for order, label in ((list(range(len(jobs))), "E1"), - (list(reversed(range(len(jobs)))), "E2")): + for order, label in ( + (list(range(len(jobs))), "E1"), + (list(reversed(range(len(jobs)))), "E2"), + ): outs = {i: os.path.join(tmp, f"{label}-{i}.json") for i in order} - rcs = _in_process(driver, [{"cwd": ROOT, "args": [*jobs[i][1], "-o", outs[i]]} - for i in order], tmp) + rcs = _in_process( + driver, [{"cwd": ROOT, "args": [*jobs[i][1], "-o", outs[i]]} for i in order], tmp + ) for i, rc in zip(order, rcs, strict=True): checks += 1 name = jobs[i][0] @@ -139,7 +154,8 @@ def main() -> int: # a non-escaping disposable local: the flow pass emits a function for it changed_text = original + ( "\nnamespace OrderBackend { static class OverlayProbe { static void M() " - "{ var s = new System.IO.MemoryStream(); s.WriteByte(1); } } }\n") + "{ var s = new System.IO.MemoryStream(); s.WriteByte(1); } } }\n" + ) changed = os.path.join(tmp, "changed.cs") with open(changed, "w", encoding="utf-8", newline="") as f: f.write(changed_text) @@ -148,10 +164,14 @@ def main() -> int: _cli(dll, args, base_out) e3 = os.path.join(tmp, "e3.json") e4 = os.path.join(tmp, "e4.json") - _in_process(driver, [ - {"cwd": ROOT, "args": [*args, "-o", e3], "overlay": {target: same}}, - {"cwd": ROOT, "args": [*args, "-o", e4], "overlay": {target: changed}}, - ], tmp) + _in_process( + driver, + [ + {"cwd": ROOT, "args": [*args, "-o", e3], "overlay": {target: same}}, + {"cwd": ROOT, "args": [*args, "-o", e4], "overlay": {target: changed}}, + ], + tmp, + ) checks += 1 if _read(e3) != _read(base_out): fails.append("E3: an overlay equal to the disk changed the facts") @@ -159,24 +179,30 @@ def main() -> int: copy = os.path.join(tmp, "tree") # bin/ included: a .csproj input auto-references the project's built output shutil.copytree(os.path.join(ROOT, project), os.path.join(copy, project)) - with open(os.path.join(copy, project, "OrderEndpoints.cs"), "w", encoding="utf-8", - newline="") as f: + with open( + os.path.join(copy, project, "OrderEndpoints.cs"), "w", encoding="utf-8", newline="" + ) as f: f.write(changed_text) disk_out = os.path.join(tmp, "e4-disk.json") _cli(dll, args, disk_out, cwd=copy) checks += 1 e4_bytes = _read(e4) if e4_bytes is None or e4_bytes == _read(base_out): - fails.append("E4: an overlay that differs from the disk did not change the facts " - "(the disk was read instead of the buffer)") + fails.append( + "E4: an overlay that differs from the disk did not change the facts " + "(the disk was read instead of the buffer)" + ) elif e4_bytes != _read(disk_out): - fails.append("E4: overlay facts differ from the command line's over the same " - "contents on disk") - - for f in fails: - print(f"FAIL: {f}") - print(f"extractor in-process: {checks - len(fails)}/{checks} passed, {len(fails)} failed " - f"({len(jobs)} jobs)") + fails.append( + "E4: overlay facts differ from the command line's over the same contents on disk" + ) + + for failure in fails: + print(f"FAIL: {failure}") + print( + f"extractor in-process: {checks - len(fails)}/{checks} passed, {len(fails)} failed " + f"({len(jobs)} jobs)" + ) return 1 if fails else 0 diff --git a/tests/owen-extensions/Owen.TestProtocol/Owen.TestProtocol.csproj b/tests/owen-extensions/Owen.TestProtocol/Owen.TestProtocol.csproj new file mode 100644 index 00000000..e94fba60 --- /dev/null +++ b/tests/owen-extensions/Owen.TestProtocol/Owen.TestProtocol.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + 12 + enable + Owen.TestProtocol.Generator + true + true + true + Owen.TestProtocol + 0.1.0 + Test fixture: a synthetic Owen extension with a generator. Not published. + PhysShell + false + false + $(NoWarn);NU5128 + + + + + + + + + + + diff --git a/tests/owen-extensions/Owen.TestProtocol/TurnstileGenerator.cs b/tests/owen-extensions/Owen.TestProtocol/TurnstileGenerator.cs new file mode 100644 index 00000000..675ef4db --- /dev/null +++ b/tests/owen-extensions/Owen.TestProtocol/TurnstileGenerator.cs @@ -0,0 +1,76 @@ +// The synthetic second extension's generator (OX-02 live gate). It adds ONE fixed source: a +// turnstile state protocol in the shape Own.NET's state-protocol lowering reads (a +// [ProtocolRegion] entry handing a [ProtocolToken] ref struct to a callback; every token +// method a transition). The attributes are matched by name, so they are declared here, in +// this extension's own namespace. + +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Owen.TestProtocol +{ + [Generator(LanguageNames.CSharp)] + public sealed class TurnstileGenerator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) => + context.RegisterPostInitializationOutput(static post => + post.AddSource("Turnstile.Protocol.g.cs", SourceText.From(Source, Encoding.UTF8))); + + private const string Source = @"// Generated by Owen.TestProtocol (a synthetic test extension). +#nullable enable +using System; + +namespace Owen.TestProtocol +{ + [AttributeUsage(AttributeTargets.Struct)] + internal sealed class ProtocolTokenAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class ProtocolRegionAttribute : Attribute { } + + public enum TurnstileState { Locked, Unlocked } + + public sealed class Turnstile + { + public TurnstileState State { get; internal set; } + } + + [ProtocolToken] + public readonly ref struct LockedTurnstile + { + private readonly Turnstile _turnstile; + internal LockedTurnstile(Turnstile turnstile) => _turnstile = turnstile; + + public UnlockedTurnstile Coin() + { + _turnstile.State = TurnstileState.Unlocked; + return new UnlockedTurnstile(_turnstile); + } + } + + [ProtocolToken] + public readonly ref struct UnlockedTurnstile + { + private readonly Turnstile _turnstile; + internal UnlockedTurnstile(Turnstile turnstile) => _turnstile = turnstile; + + public void Push() => _turnstile.State = TurnstileState.Locked; + } + + public delegate void LockedRegion(LockedTurnstile locked); + + public static class TurnstileProtocol + { + [ProtocolRegion] + public static void WithLocked(Turnstile turnstile, LockedRegion body) + { + if (turnstile.State != TurnstileState.Locked) + throw new InvalidOperationException(""the turnstile is not locked""); + body(new LockedTurnstile(turnstile)); + } + } +} +"; + } +} diff --git a/tests/owen-extensions/Owen.TestProtocol/buildTransitive/Owen.TestProtocol.props b/tests/owen-extensions/Owen.TestProtocol/buildTransitive/Owen.TestProtocol.props new file mode 100644 index 00000000..56a6fb18 --- /dev/null +++ b/tests/owen-extensions/Owen.TestProtocol/buildTransitive/Owen.TestProtocol.props @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/owen-extensions/Owen.TestProtocol/buildTransitive/owen-extension.json b/tests/owen-extensions/Owen.TestProtocol/buildTransitive/owen-extension.json new file mode 100644 index 00000000..68572374 --- /dev/null +++ b/tests/owen-extensions/Owen.TestProtocol/buildTransitive/owen-extension.json @@ -0,0 +1,13 @@ +{ + "owen_extension": 1, + "id": "Owen.TestProtocol", + "version": "0.1.0", + "requires": { + "host": "0.1.0", + "ownir": 2, + "capabilities": ["state-protocol"] + }, + "frontend": { + "generators": ["Owen.TestProtocol.Generator"] + } +} diff --git a/tests/owen-live/LiveClientTests/LiveClientTests.csproj b/tests/owen-live/LiveClientTests/LiveClientTests.csproj new file mode 100644 index 00000000..9d98a15a --- /dev/null +++ b/tests/owen-live/LiveClientTests/LiveClientTests.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + latest + false + enable + + + + + + diff --git a/tests/owen-live/LiveClientTests/Program.cs b/tests/owen-live/LiveClientTests/Program.cs new file mode 100644 index 00000000..ec93d883 --- /dev/null +++ b/tests/owen-live/LiveClientTests/Program.cs @@ -0,0 +1,159 @@ +// usage: LiveClientTests [ ] (the service tests need both) +using System.Diagnostics; +using Owen.VisualStudio.Live; + +var failures = new List(); +var passed = 0; +void Check(string name, bool holds, string detail) +{ + Console.WriteLine($"{(holds ? "ok" : "FAIL")}[{name}]: {detail}"); + if (holds) passed++; else failures.Add(name); +} + +// ---- coordinates (preregistration §6; mutation M3 moves them) ------------------------------ +{ + var (s, e, c) = LiveCoordinates.Span(" draft.Submit(now); ", null); + Check("coord-no-column", (s, e, c) == (12, 30, 13), $"trimmed line -> [{s},{e}) column {c}"); + (s, e, c) = LiveCoordinates.Span(" x = y;", 9); + Check("coord-column", (s, e, c) == (8, 10, 9), $"column 9 -> [{s},{e}) column {c}"); + (s, e, c) = LiveCoordinates.Span("", null); + Check("coord-empty", (s, e, c) == (0, 0, 1), $"empty line -> [{s},{e}) column {c}"); +} + +// ---- the entries a response becomes --------------------------------------------------------- +{ + var file = Path.Combine(Path.GetTempPath(), "Use.cs"); + var text = "a\nb\n region(x, d =>\n {\n d.Go();\n d.Go();\n });\n"; + var snapshot = new LiveSnapshot("/p/P.csproj", "/p/obj/owen/live.txt", [new LiveSource(file, text)], []); + var response = new LiveResponse + { + Status = "ok", + Diagnostics = + [ + new LiveDiagnostic + { + Code = "OWN002", Severity = "warning", Message = "used after", File = file, Line = 3, Origin = "core", + Related = [new LiveLocation { File = file, Line = 3, Message = "acquired here" }, + new LiveLocation { File = file, Line = 6, Message = "used here" }], + }, + ], + }; + var entries = LiveEngine.Entries(response, snapshot); + var primary = entries.Where(x => x.Primary).ToList(); + var witness = entries.Where(x => !x.Primary).ToList(); + Check("entries-one-row", primary.Count == 1 && primary[0].Line == 3 && primary[0].Column == 3, + $"one Error List row at the core's location: {string.Join(", ", primary.Select(p => $"{p.Line}:{p.Column}"))}"); + Check("entries-witness-squiggle", witness.Count == 1 && witness[0].Line == 6 && witness[0].Start == 4 && witness[0].End == 11 + && witness[0].Message.StartsWith("used here", StringComparison.Ordinal), + $"the witness step on line 6 is a squiggle only: {string.Join(", ", witness.Select(w => $"{w.Line}[{w.Start},{w.End}) '{w.Message}'"))}"); +} + +// ---- K4: a late answer never replaces a newer one ------------------------------------------- +async Task> RaceAsync(bool ignoreVersions) +{ + var published = new List(); + var analyzer = new FakeAnalyzer(); + var engine = new LiveEngine(_ => Task.FromResult(null), _ => analyzer, _ => { }, TimeSpan.FromMilliseconds(10)) + { + IgnoreVersions = ignoreVersions, + }; + engine.Published += p => { lock (published) published.Add(p.Version); }; + var snap = new LiveSnapshot("K", "R", [], []); + var v1 = engine.Issue("K"); + var v2 = engine.Issue("K"); + analyzer.Delays[v1] = 400; // N answers after N+1 + analyzer.Delays[v2] = 10; + await Task.WhenAll(engine.RunAsync(snap, v1), engine.RunAsync(snap, v2)); + return published; +} +{ + var published = await RaceAsync(ignoreVersions: false); + Check("K4-stale-dropped", published.SequenceEqual([2L]), $"published versions: [{string.Join(", ", published)}] (version 1 answered last)"); + var mutated = await RaceAsync(ignoreVersions: true); + Check("K4-M1-control", mutated.LastOrDefault() == 1, + $"with the version check removed (M1) the stale answer is published last: [{string.Join(", ", mutated)}]"); +} + +// ---- bursts coalesce ------------------------------------------------------------------------- +{ + var snapshots = 0; + var engine = new LiveEngine(_ => { Interlocked.Increment(ref snapshots); return Task.FromResult(null); }, + _ => null, _ => { }, TimeSpan.FromMilliseconds(250)); + for (var i = 0; i < 20; i++) + { + engine.OnEdit("K"); + await Task.Delay(30); + } + await Task.Delay(600); + Check("burst-coalesced", snapshots is >= 1 and <= 2, $"20 edits 30 ms apart -> {snapshots} analysis snapshot(s)"); +} + +// ---- the real service: death, restart, budget (K6), a service that cannot start (M2) -------- +if (args.Length >= 2) +{ + var host = Path.GetFullPath(args[0]); + var request = Path.GetFullPath(args[1]); + var dotnet = Process.GetCurrentProcess().MainModule!.FileName; + var log = new List(); + var clock = DateTime.UtcNow; + using var supervisor = new LiveSupervisor(dotnet, host, l => { lock (log) log.Add(l); }, () => clock); + var key = File.ReadAllLines(request).First(l => l.StartsWith("project\t", StringComparison.Ordinal))[8..]; + var first = await supervisor.AnalyzeAsync(key, 1, request, [], []); + var pid1 = supervisor.ProcessId; + Check("service-answers", first.Status == "ok", $"status {first.Status}, {first.Diagnostics.Count} diagnostic(s), pid {pid1}"); + + supervisor.KillService(); + await Task.Delay(1000); + Check("K6-death-shown", supervisor.Condition is string c1 && c1.Contains("exited", StringComparison.Ordinal), + $"condition after a crash: {supervisor.Condition}"); + var engine = new LiveEngine(_ => Task.FromResult(null), _ => new SupervisedAnalyzer(supervisor), _ => { }, TimeSpan.Zero); + var rows = new List(); + engine.Published += p => rows.AddRange(p.Entries); + var snap = new LiveSnapshot(key, request, [], []); + await engine.RunAsync(snap, engine.Issue(key)); + var pid2 = supervisor.ProcessId; + Check("K6-restarted", pid2 != pid1 && supervisor.Condition is null, $"the next request restarted it (pid {pid1} -> {pid2}); condition {supervisor.Condition ?? "cleared"}"); + + for (var i = 0; i < 3; i++) + { + supervisor.KillService(); + await Task.Delay(800); + try { await supervisor.AnalyzeAsync(key, 10 + i, request, [], []); } catch (LiveServiceDiedException) { } + } + supervisor.KillService(); + await Task.Delay(800); + rows.Clear(); + await engine.RunAsync(snap, engine.Issue(key)); + Check("K6-budget", supervisor.GaveUp && rows.Count == 1 && rows[0].Code == LiveEngine.ServiceCode + && rows[0].Message.Contains("off until the solution is reopened", StringComparison.Ordinal), + $"after {LiveSupervisor.MaxRestarts} restarts in the window: gave up={supervisor.GaveUp}, row: {rows.FirstOrDefault()?.Code} '{rows.FirstOrDefault()?.Message}'"); + + using var broken = new LiveSupervisor(dotnet, host + ".missing", _ => { }); + var brokenEngine = new LiveEngine(_ => Task.FromResult(null), _ => new SupervisedAnalyzer(broken), _ => { }, TimeSpan.Zero); + var brokenRows = new List(); + brokenEngine.Published += p => brokenRows.AddRange(p.Entries); + await brokenEngine.RunAsync(snap, brokenEngine.Issue(key)); + Check("M2-cannot-start", brokenRows.Count == 1 && brokenRows[0].Code == LiveEngine.ServiceCode, + $"a service that cannot start is one visible row: {brokenRows.FirstOrDefault()?.Code} '{brokenRows.FirstOrDefault()?.Message}'"); +} +else +{ + Console.WriteLine("skip[service]: no ownsharp.dll / live.txt given"); +} + +Console.WriteLine($"live client tests: {passed} passed, {failures.Count} failed"); +return failures.Count == 0 ? 0 : 1; + +sealed class FakeAnalyzer : ILiveAnalyzer +{ + public Dictionary Delays { get; } = []; + + public string? Condition => null; + + public async Task AnalyzeAsync(string key, long version, string request, + IReadOnlyList documents, IReadOnlyList generated) + { + await Task.Delay(Delays.TryGetValue(version, out var d) ? d : 0); + return new LiveResponse { Key = key, Version = version, Status = "ok" }; + } +} diff --git a/tests/owen-live/LiveFixture/Gate.cs.txt b/tests/owen-live/LiveFixture/Gate.cs.txt new file mode 100644 index 00000000..7e44dd0b --- /dev/null +++ b/tests/owen-live/LiveFixture/Gate.cs.txt @@ -0,0 +1,15 @@ +using Owen.TestProtocol; + +namespace LiveFixture; + +public static class Gate +{ + public static void Pass(Turnstile turnstile) + { + TurnstileProtocol.WithLocked(turnstile, locked => + { + var unlocked = locked.Coin(); + unlocked.Push(); + }); + } +} diff --git a/tests/owen-live/LiveFixture/Order.cs.txt b/tests/owen-live/LiveFixture/Order.cs.txt new file mode 100644 index 00000000..9056d76e --- /dev/null +++ b/tests/owen-live/LiveFixture/Order.cs.txt @@ -0,0 +1,51 @@ +using System; + +namespace LiveFixture; + +public enum OrderStatus +{ + Draft, + Submitted, + Approved, + Shipped, +} + +/// An ordinary EF Core entity — no base class, no interface — and the declaration its +/// protocol is generated from (Order.Protocol.cs). EF materializes it through the private +/// constructor and tracks THIS instance; every state token wraps the same reference. +[TypedProtocol] +public sealed partial class Order +{ + private Order() + { + } + + public int Id { get; private set; } + + [BuilderRequired] + public string Customer { get; private set; } = ""; + + [ProtocolState] + public OrderStatus Status { get; private set; } + + public DateTime? SubmittedAt { get; private set; } + + public DateTime? ApprovedAt { get; private set; } + + public DateTime? ShippedAt { get; private set; } + + public int? TrackingNumber { get; private set; } + + [Transition("Submit", OrderStatus.Draft, OrderStatus.Submitted)] + private void OnSubmit(DateTime at) => SubmittedAt = at; + + [Transition("Approve", OrderStatus.Submitted, OrderStatus.Approved)] + private void OnApprove(DateTime at) => ApprovedAt = at; + + [Transition("Ship", OrderStatus.Approved, OrderStatus.Shipped)] + private void OnShip(DateTime at, int trackingNumber) + { + ShippedAt = at; + TrackingNumber = trackingNumber; + } +} diff --git a/tests/owen-live/LiveFixture/README.md b/tests/owen-live/LiveFixture/README.md new file mode 100644 index 00000000..b81e6f86 --- /dev/null +++ b/tests/owen-live/LiveFixture/README.md @@ -0,0 +1,12 @@ +# OX-02 live fixture + +The sources of the small project the live gate (`scripts/owen_live_gate.py`) and the real +Visual Studio run (`scripts/vs/owen-vs-acceptance.ps1`) analyse. Stored as `.cs.txt` so no +scan of this repository picks them up; both copy them, as `.cs`, into a project created +outside the checkout that references `Owen.TypedBuilder` (and, for K8, `Owen.TestProtocol`). + +- `Order.cs` — the TB-MVP `Order`, declared for the Typed Builder generator. +- `Use.cs` — one clean `WithDraft` region. The acceptance inserts a second + `draft.Submit(now);` after `var submitted = draft.Submit(now);` (OWN002) and deletes it. +- `Gate.cs` — the synthetic second extension's turnstile, clean (K8 inserts a second + `locked.Coin();`). diff --git a/tests/owen-live/LiveFixture/Use.cs.txt b/tests/owen-live/LiveFixture/Use.cs.txt new file mode 100644 index 00000000..f8768d46 --- /dev/null +++ b/tests/owen-live/LiveFixture/Use.cs.txt @@ -0,0 +1,16 @@ +using System; + +namespace LiveFixture; + +public static class Use +{ + public static void Run(Order order) + { + var now = DateTime.UnixEpoch; + OrderProtocol.WithDraft(order, draft => + { + var submitted = draft.Submit(now); + submitted.Approve(now); + }); + } +} From 1c2a98e7f35545db25174ac9baf5bbc8cd1b73cb Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:22:58 +0000 Subject: [PATCH 07/23] OX-02 P14: the real Visual Studio acceptance, and the live jobs in CI - scripts/vs/prepare_vs_fixture.py: packs Owen.Build/Owen.TypedBuilder and creates a restored, never-built solution outside the checkout. - scripts/vs/owen-vs-acceptance.ps1: installs the VSIX into a dedicated root suffix with VSIXInstaller, opens the solution, and drives the IDE: unsaved edits via DTE, the Error List read with UI Automation, navigation by double-click, Owen's squiggles from the opt-in tagger trace; K1/K2/P9/K3, repeated-edit latency, UI-thread max, no save, no build. (Shape ADAPTED from Snipper's vs-smoke.ps1, MIT.) - ci.yml: owen-live (ubuntu+windows) and owen-visual-studio (windows, real VS). - The probe workflow is retired (runs 37163037704/37163398893/37163671022 recorded). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .github/workflows/ci.yml | 73 ++++ .github/workflows/owen-vs-probe.yml | 24 -- .../Owen.VisualStudio/OwenLiveMonitor.cs | 8 +- scripts/vs/owen-vs-acceptance.ps1 | 320 ++++++++++++++++++ scripts/vs/prepare_vs_fixture.py | 94 +++++ scripts/vs/probe.ps1 | 103 ------ 6 files changed, 491 insertions(+), 131 deletions(-) delete mode 100644 .github/workflows/owen-vs-probe.yml create mode 100644 scripts/vs/owen-vs-acceptance.ps1 create mode 100644 scripts/vs/prepare_vs_fixture.py delete mode 100644 scripts/vs/probe.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 105c738a..0a748cde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3487,6 +3487,79 @@ jobs: - name: The extension gate (pack, isolated consumer A-H, mutations M1-M7) run: python scripts/owen_extension_gate.py + # OX-02 (docs/notes/owen-visual-studio-preregistration.md): live Owen diagnostics in the IDE. + # The extractor run in-process over an overlay is byte-identical to the command line; the + # `owen serve` service, as Owen.VisualStudio uses it, answers unsaved edits (K1-K8), agrees + # with `dotnet build` on saved sources (P10), stays inside its latency/memory budget, and its + # client half survives the service dying (K6). Both platforms: the VSIX host is Windows-only, + # the service is not. + owen-live: + name: Owen live service (in-process extractor, owen serve, build/live parity) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 + with: + dotnet-version: "8.0.x" + - uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10 + with: + toolchain: stable + - name: The extractor as a library (E1-E4) + run: python tests/check_extractor_in_process.py + - name: The live gate (L0, S, K1-K8, P10, T, C, G) + run: python scripts/owen_live_gate.py + + # OX-02 P14: the real Visual Studio run. The hosted image carries Visual Studio with the + # extension workload; the VSIX is built with its MSBuild, installed into a dedicated root + # suffix, and the IDE is driven (DTE for edits, UI Automation for the Error List, Owen's + # opt-in trace for the squiggles) on a solution that is restored and never built. + owen-visual-studio: + name: Owen.VisualStudio in a real Visual Studio (unsaved edit -> squiggle + Error List) + runs-on: windows-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" + - uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10 + with: + toolchain: stable + - name: Build the Rust core + shell: bash + run: cargo build -p own-cli --release --manifest-path rust/Cargo.toml + - name: Prepare the solution (packed Owen.Build + Owen.TypedBuilder; restored, not built) + shell: bash + run: python scripts/vs/prepare_vs_fixture.py --out "$RUNNER_TEMP/vsfx" --rust-core "$PWD/rust/target/release/own-cli.exe" + - name: Build the VSIX + shell: powershell + run: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $msbuild = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1 + & $msbuild frontend\roslyn\Owen.VisualStudio\Owen.VisualStudio.csproj /restore /p:Configuration=Release /p:DeployExtension=false /v:m + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + Get-ChildItem -Recurse frontend\roslyn\Owen.VisualStudio -Filter *.vsix | ForEach-Object { $_.FullName } + - name: The acceptance in Visual Studio + shell: powershell + run: | + $vsix = (Get-ChildItem -Recurse frontend\roslyn\Owen.VisualStudio -Filter Owen.VisualStudio.vsix | Select-Object -First 1).FullName + scripts\vs\owen-vs-acceptance.ps1 -Vsix $vsix -Fixture "$env:RUNNER_TEMP\vsfx\fixture.json" -Out "$env:RUNNER_TEMP\vs-evidence" + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: owen-visual-studio-evidence + path: ${{ runner.temp }}/vs-evidence/ + # Alpha gate A (issue #202): the single delightful command, proven end-to-end # on a clean runner — install -> check -> findings. Packaging only, no # analysis-behaviour change: the underlying project stays OwnSharp.Cli diff --git a/.github/workflows/owen-vs-probe.yml b/.github/workflows/owen-vs-probe.yml deleted file mode 100644 index 0310d2ef..00000000 --- a/.github/workflows/owen-vs-probe.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: owen-vs-probe - -permissions: - contents: read - -on: - push: - branches: ["ccr-*"] - paths: [".github/workflows/owen-vs-probe.yml", "scripts/vs/**"] - -jobs: - probe: - runs-on: windows-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: probe Visual Studio - shell: powershell - run: scripts/vs/probe.ps1 - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - if: always() - with: - name: vs-probe - path: vs-probe/ diff --git a/frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs b/frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs index 9a072079..a351c326 100644 --- a/frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs +++ b/frontend/roslyn/Owen.VisualStudio/OwenLiveMonitor.cs @@ -52,10 +52,7 @@ public OwenLiveMonitor(VisualStudioWorkspace workspace, ITableManagerProvider ta { _workspace = workspace; _errorList = new OwenErrorListSource(tables.GetTableManager(StandardTables.ErrorsTable)); - _engine = new LiveEngine(SnapshotAsync, AnalyzerFor, OwenLog.Write, TimeSpan.FromMilliseconds(250)) - { - IgnoreVersions = Environment.GetEnvironmentVariable("OWEN_LIVE_MUTATION") == "M1", - }; + _engine = new LiveEngine(SnapshotAsync, AnalyzerFor, OwenLog.Write, TimeSpan.FromMilliseconds(250)); _engine.Published += OnPublished; _workspace.WorkspaceChanged += OnWorkspaceChanged; OwenLog.Write("Owen live analysis loaded"); @@ -116,7 +113,10 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) { var request = LiveRequestFile.For(key); if (LiveRequestFile.TryRead(request) == null) + { + OwenTrace.Event("no-request", new { key, request }); return null; + } var project = _workspace.CurrentSolution.Projects.FirstOrDefault( p => string.Equals(p.FilePath, key, StringComparison.OrdinalIgnoreCase)); if (project == null) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 new file mode 100644 index 00000000..483a0474 --- /dev/null +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -0,0 +1,320 @@ +# OX-02 P14: the real Visual Studio acceptance of Owen.VisualStudio +# (docs/notes/owen-visual-studio-preregistration.md §7, §8, §10). +# +# Installs the built VSIX into a dedicated root suffix, opens the prepared solution (restored, +# never built), and drives the IDE the way a user would: open Use.cs, type a second +# `draft.Submit(now);` WITHOUT saving, and require +# * Owen's squiggle (the tagger's own tags, from the opt-in trace) on the inserted line and at +# the finding's location, +# * an OWN002 row in the Error List (read from the Error List control itself, UI Automation), +# * double-clicking that row moves the caret to the row's file/line/column, +# * a CS error and OWN002 side by side, +# * deleting the line makes the row and the squiggles disappear, +# and measures edit -> squiggle and edit -> disappearance over repeated edits, and the longest +# UI-thread handler. Nothing is saved and nothing is built. +# +# ADAPTED from Snipper (PhysShell/snipper 43b395a, MIT, extensions/snipper-vs/scripts/vs-smoke.ps1): +# vswhere instance selection, a dedicated root suffix (never the shared Exp hive), the activity +# log, and the relaunch when VS restarts itself after registering an extension. +param( + [Parameter(Mandatory = $true)][string]$Vsix, + [Parameter(Mandatory = $true)][string]$Fixture, + [Parameter(Mandatory = $true)][string]$Out, + [string]$RootSuffix = "OwenLive", + [int]$Repeats = 10 +) + +$ErrorActionPreference = 'Stop' +New-Item -ItemType Directory -Force $Out | Out-Null +$results = [ordered]@{ checks = @(); measurements = [ordered]@{} } +$failed = $false +function Log($m) { $line = "[$(Get-Date -Format HH:mm:ss.fff)] $m"; Write-Host $line; Add-Content (Join-Path $Out 'acceptance.log') $line } +function Check($name, $holds, $detail) { + $script:results.checks += [ordered]@{ name = $name; ok = [bool]$holds; detail = "$detail" } + if ($holds) { Log "ok[$name]: $detail" } else { Log "FAIL[$name]: $detail"; $script:failed = $true } +} +function Now() { [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() } + +Add-Type -AssemblyName System.Windows.Forms, System.Drawing, UIAutomationClient, UIAutomationTypes +Add-Type @" +using System; +using System.Runtime.InteropServices; +public static class Mouse { + [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); + [DllImport("user32.dll")] public static extern void mouse_event(uint f, uint x, uint y, uint d, UIntPtr e); + [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); + public static void DoubleClick(int x, int y) { + SetCursorPos(x, y); + for (int i = 0; i < 2; i++) { mouse_event(2, 0, 0, 0, UIntPtr.Zero); mouse_event(4, 0, 0, 0, UIntPtr.Zero); } + } +} +"@ + +function Shot($name) { + try { + $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds + $bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size) + $bmp.Save((Join-Path $Out "$name.png")); $g.Dispose(); $bmp.Dispose() + } catch { Log "screenshot failed: $_" } +} + +function Retry([scriptblock]$Block, [int]$Seconds = 60) { + $deadline = (Get-Date).AddSeconds($Seconds) + while ($true) { + try { return & $Block } + catch { + if ((Get-Date) -gt $deadline) { throw } + Start-Sleep -Milliseconds 500 + } + } +} + +# ---- Visual Studio ---------------------------------------------------------------------------- +$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +$vs = (& $vswhere -latest -products * -format json | ConvertFrom-Json)[0] +Log "Visual Studio: $($vs.displayName) $($vs.installationVersion) [$($vs.instanceId)]" +$results.measurements.visual_studio = "$($vs.displayName) $($vs.installationVersion)" +$devenv = $vs.productPath +$installer = Join-Path $vs.installationPath 'Common7\IDE\VSIXInstaller.exe' + +$fx = Get-Content $Fixture -Raw | ConvertFrom-Json +$env:NUGET_PACKAGES = $fx.nuget_packages +$trace = Join-Path $Out 'trace.jsonl' +if (Test-Path $trace) { Remove-Item $trace } +$env:OWEN_LIVE_TRACE = $trace + +# install the VSIX like a user would, into the dedicated hive +$p = Start-Process $installer -ArgumentList "/quiet", "/rootSuffix:$RootSuffix", "/instanceIds:$($vs.instanceId)", "/logFile:$(Join-Path $Out 'vsixinstaller.log')", "`"$Vsix`"" -Wait -PassThru +Check "install-vsix" ($p.ExitCode -eq 0) "VSIXInstaller exit $($p.ExitCode) into root suffix $RootSuffix" +if ($p.ExitCode -ne 0) { $results | ConvertTo-Json -Depth 8 | Set-Content (Join-Path $Out 'results.json'); exit 1 } + +$major = $vs.installationVersion.Split('.')[0] +$activity = Join-Path $Out 'ActivityLog.xml' +$t0 = Now +$proc = Start-Process $devenv -ArgumentList "/rootsuffix", $RootSuffix, "/log", "`"$activity`"", "`"$($fx.sln)`"" -PassThru +Log "devenv pid $($proc.Id)" +$dte = $null +for ($k = 0; $k -lt 180 -and -not $dte; $k++) { + Start-Sleep -Seconds 2 + if ($proc.HasExited) { + Log "devenv exited ($($proc.ExitCode)); relaunching once (extension registration restart)" + $proc = Start-Process $devenv -ArgumentList "/rootsuffix", $RootSuffix, "/log", "`"$activity`"", "`"$($fx.sln)`"" -PassThru + } + try { $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.$major.0") } catch { } +} +if (-not $dte) { Check "dte" $false "no DTE after 360 s"; Shot "no-dte"; exit 1 } +Retry { if (-not ($dte.Solution.IsOpen -and $dte.Solution.Projects.Count -gt 0)) { throw "loading" } } 300 | Out-Null +Log "solution open after $([int]((Now) - $t0) / 1000) s: $($dte.Solution.FullName)" + +$liveTxt = Join-Path (Split-Path $fx.project) 'obj\owen\live.txt' +$window = Retry { $dte.ItemOperations.OpenFile($fx.use) } +$doc = Retry { $dte.ActiveDocument } +$td = Retry { $doc.Object('TextDocument') } + +# ---- trace ------------------------------------------------------------------------------------ +$script:traceSeen = 0 +$script:events = New-Object System.Collections.ArrayList +function Poll-Trace { + if (-not (Test-Path $trace)) { return } + $lines = @(Get-Content $trace -Encoding UTF8) + for ($i = $script:traceSeen; $i -lt $lines.Count; $i++) { + if ($lines[$i].Trim().Length -gt 0) { [void]$script:events.Add(($lines[$i] | ConvertFrom-Json)) } + } + $script:traceSeen = $lines.Count +} +function Tags-Since($since) { + Poll-Trace + @($script:events | Where-Object { $_.kind -eq 'tags' -and $_.t -ge $since -and $_.data.file -like '*Use.cs' }) +} +function Own002($tagsEvent) { @($tagsEvent.data.tags | Where-Object { $_.code -eq 'OWN002' }) } +function Wait-Tags($since, [scriptblock]$Want, [int]$Seconds = 60) { + $deadline = (Get-Date).AddSeconds($Seconds) + while ((Get-Date) -lt $deadline) { + foreach ($e in (Tags-Since $since)) { if (& $Want $e) { return $e } } + Start-Sleep -Milliseconds 50 + } + return $null +} + +# ---- the Error List, as the user sees it ------------------------------------------------------ +function Error-Rows { + $root = [System.Windows.Automation.AutomationElement]::RootElement + $byPid = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ProcessIdProperty, $proc.Id) + $rows = @() + foreach ($w in $root.FindAll([System.Windows.Automation.TreeScope]::Children, $byPid)) { + $items = $w.FindAll([System.Windows.Automation.TreeScope]::Descendants, + (New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty, [System.Windows.Automation.ControlType]::DataItem))) + foreach ($item in $items) { + $cells = @() + foreach ($c in $item.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition)) { + $n = $c.Current.Name + if ($n -and $cells -notcontains $n) { $cells += $n } + } + $text = ($cells -join ' | ') + if ($text -match '\b(OWN\d{3}|OWEN[BV]\d{3}|CS\d{4})\b') { + $rows += [pscustomobject]@{ Element = $item; Text = $text; Code = $Matches[1] } + } + } + } + return $rows +} +function Wait-Row($code, [bool]$present, [int]$Seconds = 60) { + $deadline = (Get-Date).AddSeconds($Seconds) + while ((Get-Date) -lt $deadline) { + $rows = @(Error-Rows | Where-Object { $_.Code -eq $code }) + if ($present -and $rows.Count -gt 0) { return $rows[0] } + if (-not $present -and $rows.Count -eq 0) { return $true } + Start-Sleep -Milliseconds 250 + } + return $null +} + +# ---- text edits through the editor (never saved) --------------------------------------------- +function All-Text { $ep = $td.StartPoint.CreateEditPoint(); return $ep.GetText($td.EndPoint) } +function Line-Of($needle) { + $lines = (All-Text) -split "`r?`n" + for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i].Trim() -eq $needle) { return $i + 1 } } + return -1 +} +function Insert-Line($before, $text) { + $ep = $td.StartPoint.CreateEditPoint(); $ep.MoveToLineAndOffset($before, 1); $ep.Insert($text + "`r`n") +} +function Delete-Line($line) { + $a = $td.StartPoint.CreateEditPoint(); $a.MoveToLineAndOffset($line, 1) + $b = $td.StartPoint.CreateEditPoint(); $b.MoveToLineAndOffset($line + 1, 1) + $a.Delete($b) +} +$stale = ' draft.Submit(now);' +$anchor = 'var submitted = draft.Submit(now);' +$region = 'OrderProtocol.WithDraft(order, draft =>' + +# ---- K1: the project's first analysis, and an unsaved clean edit ------------------------------ +Retry { if (-not (Test-Path $liveTxt)) { throw "no live.txt" } } 240 | Out-Null +Check "L0-live-request" (Test-Path $liveTxt) "the design-time build wrote $liveTxt (no build was run)" +$first = $null +$deadline = (Get-Date).AddSeconds(240) +while (-not $first -and (Get-Date) -lt $deadline) { + Poll-Trace + $first = @($script:events | Where-Object { $_.kind -eq 'publish' })[0] + Start-Sleep -Milliseconds 250 +} +if (-not $first) { + Poll-Trace + Log "trace tail: $(@($script:events | Select-Object -Last 15 | ForEach-Object { $_ | ConvertTo-Json -Compress -Depth 6 }) -join "`n")" + Check "first-publication" $false "no Owen publication within 240 s of opening Use.cs" + Shot "no-publication" +} +else { + $own = @($first.data.entries | Where-Object { $_.code -like 'OWN*' }) + Check "K1-open-clean" ($own.Count -eq 0) "first publication (version $($first.data.version), $($first.data.timing.total) ms in the service): $(@($first.data.entries).Count) entries, OWN: $($own.Count)" + $results.measurements.cold_open_to_first_publication_ms = $first.t - $t0 +} +$snap = @($script:events | Where-Object { $_.kind -eq 'snapshot' })[0] +if ($snap) { Log "snapshot: documents $($snap.data.documents -join ', '); generated $($snap.data.generated -join ', ')" } + +$since = Now +Insert-Line ((Line-Of $anchor) + 1) ' // an unsaved, harmless edit' +$cleanPub = $null +$deadline = (Get-Date).AddSeconds(60) +while (-not $cleanPub -and (Get-Date) -lt $deadline) { + Poll-Trace + $cleanPub = @($script:events | Where-Object { $_.kind -eq 'publish' -and $_.t -ge $since })[0] + Start-Sleep -Milliseconds 100 +} +Check "K1-unsaved-clean" ($cleanPub -and @($cleanPub.data.entries | Where-Object { $_.code -like 'OWN*' }).Count -eq 0 -and -not $doc.Saved) "a clean unsaved edit was analysed (version $($cleanPub.data.version)) with no OWN entry; document saved=$($doc.Saved)" +Delete-Line (Line-Of '// an unsaved, harmless edit') + +# ---- K2: the second Submit, unsaved ----------------------------------------------------------- +$since = Now +$insertAt = (Line-Of $anchor) + 1 +Insert-Line $insertAt $stale +$regionLine = Line-Of $region +$hit = Wait-Tags $since { param($e) $o = Own002 $e; ($o | Where-Object { -not $_.primary -and $_.line -eq $insertAt }) -and ($o | Where-Object { $_.primary -and $_.line -eq $regionLine }) } +Check "K2-squiggle" ($null -ne $hit) ("tags on Use.cs after the edit: " + $(if ($hit) { (Own002 $hit | ForEach-Object { "$($_.line):$($_.column)+$($_.length) primary=$($_.primary) '$($_.text)'" }) -join '; ' } else { 'none with OWN002 at the region entry and on the inserted line' })) +if ($hit) { $results.measurements.k2_edit_to_squiggle_ms = $hit.t - $since } +Check "K2-unsaved" (-not $doc.Saved) "Use.cs is unsaved (Saved=$($doc.Saved)); the disk still has one Submit: $(-not ((Get-Content $fx.use -Raw) -match 'draft\.Submit\(now\);\s*\r?\n\s*draft\.Submit'))" +Start-Sleep -Milliseconds 700 +Shot "k2-squiggle" +$row = Wait-Row 'OWN002' $true 60 +Check "K2-error-list" ($null -ne $row) "Error List row: $(if ($row) { $row.Text } else { 'none' })" + +# navigation: double-click the row, then read the caret +if ($row) { + try { $row.Element.GetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern).Select() } catch { } + $r = $row.Element.Current.BoundingRectangle + [Mouse]::SetForegroundWindow([IntPtr]$dte.MainWindow.HWnd) | Out-Null + [Mouse]::DoubleClick([int]($r.X + [Math]::Min(200, $r.Width / 2)), [int]($r.Y + $r.Height / 2)) + Start-Sleep -Milliseconds 1500 + $active = Retry { $dte.ActiveDocument } + $point = Retry { $active.Selection.ActivePoint } + $expectColumn = ((Retry { (All-Text) -split "`r?`n" })[$regionLine - 1]).IndexOf('OrderProtocol') + 1 + Check "K2-navigation" ($active.FullName -eq $fx.use -and $point.Line -eq $regionLine -and $point.LineCharOffset -eq $expectColumn) "double-click -> $($active.Name) line $($point.Line) column $($point.LineCharOffset); expected Use.cs line $regionLine column $expectColumn (the core's location, §6)" + Shot "k2-navigated" +} + +# ---- P9: a CS error beside OWN002 ------------------------------------------------------------- +$csAt = (Line-Of $region) +Insert-Line $csAt ' int notAString = "s";' +$cs = Wait-Row 'CS0029' $true 90 +$own = @(Error-Rows | Where-Object { $_.Code -eq 'OWN002' }) +Check "P9-cs-and-own" (($null -ne $cs) -and $own.Count -ge 1) "CS0029 row: $(if ($cs) { $cs.Text } else { 'none' }); OWN002 rows: $($own.Count)" +Shot "p9-cs-and-own" +Delete-Line (Line-Of 'int notAString = "s";') +[void](Wait-Row 'CS0029' $false 90) + +# ---- K3: delete the line ------------------------------------------------------------------------ +Start-Sleep -Milliseconds 1500 +$since = Now +Delete-Line (Line-Of 'draft.Submit(now);') +$gone = Wait-Tags $since { param($e) (Own002 $e).Count -eq 0 } +$rowGone = Wait-Row 'OWN002' $false 60 +Check "K3-disappears" (($null -ne $gone) -and ($rowGone -eq $true)) "after deleting the line: tags without OWN002 at +$(if ($gone) { $gone.t - $since } else { '?' }) ms; Error List row gone: $($rowGone -eq $true)" +Shot "k3-gone" + +# ---- latency over repeated edits --------------------------------------------------------------- +$appear = @(); $vanish = @() +for ($i = 0; $i -lt $Repeats; $i++) { + Start-Sleep -Milliseconds 800 + $since = Now + Insert-Line ((Line-Of $anchor) + 1) $stale + $e = Wait-Tags $since { param($x) (Own002 $x | Where-Object { -not $_.primary }).Count -gt 0 } 30 + if ($e) { $appear += ($e.t - $since) } + Start-Sleep -Milliseconds 800 + $since = Now + Delete-Line (Line-Of 'draft.Submit(now);') + $e = Wait-Tags $since { param($x) (Own002 $x).Count -eq 0 } 30 + if ($e) { $vanish += ($e.t - $since) } +} +function P95($xs) { $s = @($xs | Sort-Object); if ($s.Count -eq 0) { return $null }; return $s[[Math]::Max(0, [int][Math]::Ceiling($s.Count * 0.95) - 1)] } +$results.measurements.edit_to_squiggle_ms = $appear +$results.measurements.edit_to_disappearance_ms = $vanish +Check "T-edit-to-squiggle" ($appear.Count -eq $Repeats -and (P95 $appear) -lt 1000) "edit -> squiggle over $($appear.Count)/$Repeats edits: p95 $(P95 $appear) ms (threshold 1000), all: $($appear -join ', ')" +Check "T-edit-to-disappearance" ($vanish.Count -eq $Repeats -and (P95 $vanish) -lt 1000) "edit -> disappearance over $($vanish.Count)/$Repeats edits: p95 $(P95 $vanish) ms (threshold 1000), all: $($vanish -join ', ')" + +Poll-Trace +$ui = @($script:events | Where-Object { $_.kind -eq 'ui' } | ForEach-Object { $_.data.ms } | Measure-Object -Maximum).Maximum +$results.measurements.ui_thread_max_ms = $ui +Check "T-ui-thread" ($ui -lt 50) "longest Owen UI-thread handler: $ui ms (threshold 50)" +$pubs = @($script:events | Where-Object { $_.kind -eq 'publish' }) +$service = @($pubs | ForEach-Object { $_.data.timing.total } | Where-Object { $_ }) +$results.measurements.service_total_ms = $service +$results.measurements.publications = $pubs.Count +$ws = @($pubs | ForEach-Object { $_.data.timing.working_set_kb } | Where-Object { $_ }) +if ($ws.Count -gt 0) { $results.measurements.service_working_set_kb_first_last = @($ws[0], $ws[-1]) } + +# ---- nothing was saved or built ------------------------------------------------------------------ +$projDir = Split-Path $fx.project +$dll = @(Get-ChildItem -Recurse -ErrorAction SilentlyContinue (Join-Path $projDir 'bin') -Filter 'LiveFixture.dll') +Check "no-build" ($dll.Count -eq 0 -and -not (Test-Path (Join-Path $projDir 'obj\owen\request.txt'))) "no LiveFixture.dll under bin/ ($($dll.Count)), no build-check request: the build host never ran" +Check "never-saved" ((Get-Content $fx.use -Raw) -eq (Get-Content (Join-Path $PSScriptRoot '..\..\tests\owen-live\LiveFixture\Use.cs.txt') -Raw)) "Use.cs on disk is byte-identical to the fixture" + +$results.failed = $failed +$results | ConvertTo-Json -Depth 8 | Set-Content (Join-Path $Out 'results.json') -Encoding UTF8 +try { $dte.Documents.CloseAll(2) } catch { } +Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue +Get-Process -Name dotnet -ErrorAction SilentlyContinue | ForEach-Object { Log "dotnet still running after devenv: pid $($_.Id)" } +if ($failed) { Log "VS acceptance: FAILED"; exit 1 } +Log "VS acceptance: all checks passed" +exit 0 diff --git a/scripts/vs/prepare_vs_fixture.py b/scripts/vs/prepare_vs_fixture.py new file mode 100644 index 00000000..f2b0ac02 --- /dev/null +++ b/scripts/vs/prepare_vs_fixture.py @@ -0,0 +1,94 @@ +"""OX-02 P14: prepare the solution the real Visual Studio acceptance opens. + +Packs Owen.Build (with this machine's natively built Rust core) and Owen.TypedBuilder into a +local feed, then creates OUTSIDE the checkout a solution whose one project references +Owen.TypedBuilder and holds tests/owen-live/LiveFixture's Order.cs and Use.cs. It is restored +and NOT built: the acceptance must reach a live finding without any build. + +Writes /fixture.json: {"sln", "project", "use", "nuget_packages", "feed"}. + +Run: python scripts/vs/prepare_vs_fixture.py --out [--rust-core ] +""" + +from __future__ import annotations + +import json +import os +import shutil +import subprocess +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import owen_extension_gate as ox # noqa: E402 + +FIXTURE = os.path.join(ox.ROOT, "tests", "owen-live", "LiveFixture") + + +def main() -> int: + argv = sys.argv[1:] + out = os.path.abspath(argv[argv.index("--out") + 1]) + given = argv[argv.index("--rust-core") + 1] if "--rust-core" in argv else None + if os.path.exists(out): + shutil.rmtree(out) + os.makedirs(out) + stage = ox.stage_core(out, given) + feed = ox.pack(out, stage) + packages = os.path.join(out, "nuget") + ws = os.path.join(out, "ws") + project_dir = os.path.join(ws, "LiveFixture") + os.makedirs(project_dir) + env = dict( + os.environ, NUGET_PACKAGES=packages, DOTNET_CLI_TELEMETRY_OPTOUT="1", DOTNET_NOLOGO="1" + ) + with open(os.path.join(ws, "nuget.config"), "w", encoding="utf-8", newline="\n") as f: + f.write(f""" + + + + + + + + + + + +""") + + def dotnet(*args: str, cwd: str = project_dir) -> None: + done = subprocess.run( + ["dotnet", *args], cwd=cwd, env=env, capture_output=True, text=True, check=False + ) + if done.returncode != 0: + raise SystemExit( + f"FAIL: dotnet {' '.join(args)}: {done.stdout[-1500:]}{done.stderr[-800:]}" + ) + + dotnet("new", "classlib", "-n", "LiveFixture", "-o", ".", "--framework", "net8.0") + os.remove(os.path.join(project_dir, "Class1.cs")) + dotnet("add", "package", "Owen.TypedBuilder", "--version", ox.VERSION) + for name in ("Order.cs", "Use.cs"): + shutil.copyfile(os.path.join(FIXTURE, name + ".txt"), os.path.join(project_dir, name)) + dotnet("new", "sln", "-n", "LiveFixture", "--format", "sln", cwd=ws) + dotnet( + "sln", "LiveFixture.sln", "add", os.path.join("LiveFixture", "LiveFixture.csproj"), cwd=ws + ) + dotnet("restore", "LiveFixture.sln", cwd=ws) + for leftover in ("bin",): + if os.path.exists(os.path.join(project_dir, leftover)): + raise SystemExit(f"FAIL: the fixture has a {leftover}/ before Visual Studio opened it") + info = { + "sln": os.path.join(ws, "LiveFixture.sln"), + "project": os.path.join(project_dir, "LiveFixture.csproj"), + "use": os.path.join(project_dir, "Use.cs"), + "nuget_packages": packages, + "feed": feed, + } + with open(os.path.join(out, "fixture.json"), "w", encoding="utf-8") as f: + json.dump(info, f, indent=2) + print(json.dumps(info, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/vs/probe.ps1 b/scripts/vs/probe.ps1 deleted file mode 100644 index 34f09381..00000000 --- a/scripts/vs/probe.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -# OX-02 P14 kill-first probe: can a hosted Windows runner start Visual Studio, open a -# solution in an experimental hive, edit a buffer through DTE and read the Error List? -$ErrorActionPreference = 'Continue' -$out = Join-Path $PWD 'vs-probe' -New-Item -ItemType Directory -Force $out | Out-Null -function Log($m) { $line = "[$(Get-Date -Format HH:mm:ss.fff)] $m"; Write-Host $line; Add-Content (Join-Path $out 'probe.log') $line } - -$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -$all = & $vswhere -all -prerelease -products * -format json | ConvertFrom-Json -foreach ($i in $all) { Log "VS: $($i.displayName) $($i.installationVersion) at $($i.installationPath)" } -$vs = & $vswhere -latest -products * -property installationPath -$ver = & $vswhere -latest -products * -property installationVersion -Log "latest: $vs ($ver)" -$ws = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Workload.VisualStudioExtension -property installationPath -Log "VSSDK workload: '$ws'" -$devenv = Join-Path $vs 'Common7\IDE\devenv.exe' -$vsix = Join-Path $vs 'Common7\IDE\VSIXInstaller.exe' -Log "devenv exists: $(Test-Path $devenv); VSIXInstaller exists: $(Test-Path $vsix)" -Log "session: $([Environment]::UserInteractive) user=$env:USERNAME" -& dotnet --list-sdks | ForEach-Object { Log "sdk $_" } - -$work = Join-Path $env:RUNNER_TEMP 'probe' -New-Item -ItemType Directory -Force $work | Out-Null -Push-Location $work -& dotnet new console -n Probe --framework net8.0 -o Probe 2>&1 | Out-Null -& dotnet new sln -n Probe --format sln 2>&1 | Out-Null -& dotnet sln Probe.sln add Probe\Probe.csproj 2>&1 | Out-Null -& dotnet restore Probe.sln 2>&1 | Out-Null -Pop-Location -$sln = Join-Path $work 'Probe.sln' -$cs = Join-Path $work 'Probe\Program.cs' - -$major = $ver.Split('.')[0] -$t0 = Get-Date -$p = Start-Process $devenv -ArgumentList "/rootsuffix", "Exp", "`"$sln`"" -PassThru -Log "devenv pid $($p.Id)" -Add-Type -AssemblyName System.Windows.Forms, System.Drawing, UIAutomationClient, UIAutomationTypes -$shot = 0 -function Shot($name) { - try { - $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds - $bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height - $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size) - $bmp.Save((Join-Path $out "$name.png")); $g.Dispose(); $bmp.Dispose() - } catch { Log "screenshot failed: $_" } -} -function Windows($procId) { - $root = [System.Windows.Automation.AutomationElement]::RootElement - $cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ProcessIdProperty, $procId) - foreach ($w in $root.FindAll([System.Windows.Automation.TreeScope]::Children, $cond)) { - Log " window: '$($w.Current.Name)' class=$($w.Current.ClassName)" - foreach ($c in $w.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition)) { - $n = $c.Current.Name - if ($n -and $c.Current.ControlType.ProgrammaticName -match 'Button|Hyperlink|Text|Window') { Log " $($c.Current.ControlType.ProgrammaticName) '$n'" } - } - } -} -$dte = $null -for ($k = 0; $k -lt 120 -and -not $dte; $k++) { - Start-Sleep -Seconds 2 - if ($k % 15 -eq 0) { Shot ("start-{0:D3}" -f $k); Log "t=$($k*2)s"; Windows $p.Id } - try { $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.$major.0") } catch { } -} -Shot "dte"; Windows $p.Id -if (-not $dte) { Log "NO DTE after 240 s"; } -else { - Log "DTE after $([int]((Get-Date) - $t0).TotalSeconds) s: $($dte.Version) $($dte.Edition)" - function Retry([scriptblock]$b) { for ($r = 0; $r -lt 60; $r++) { try { return & $b } catch { Start-Sleep -Milliseconds 1000 } } ; throw "gave up" } - for ($k = 0; $k -lt 60; $k++) { try { if ($dte.Solution.IsOpen -and $dte.Solution.Projects.Count -gt 0) { break } } catch { Log "dte call: $($_.Exception.Message)" } ; Start-Sleep 2; if ($k % 10 -eq 0) { Shot ("wait-{0:D3}" -f $k); Windows $p.Id } } - Log "solution: $(Retry { $dte.Solution.FullName }) projects=$(Retry { $dte.Solution.Projects.Count })" - $win = Retry { $dte.ItemOperations.OpenFile($cs) } - $doc = Retry { $dte.ActiveDocument } - Log "active: $(Retry { $doc.FullName })" - $td = Retry { $doc.Object('TextDocument') } - $ep = Retry { $td.EndPoint.CreateEditPoint() } - Retry { $ep.Insert("`r`nclass Broken { void M() { int x = `"s`"; } }`r`n") } | Out-Null - Log "inserted; saved=$(Retry { $doc.Saved })" - Retry { $dte.ExecuteCommand('View.ErrorList') } | Out-Null - for ($k = 0; $k -lt 30; $k++) { - Start-Sleep 2 - $items = Retry { $dte.ToolWindows.ErrorList.ErrorItems } - $n = Retry { $items.Count } - Log "error items: $n" - if ($n -gt 0) { - for ($j = 1; $j -le $n; $j++) { $e = $items.Item($j); Log " [$($e.ErrorLevel)] $($e.FileName)($($e.Line),$($e.Column)) $($e.Description) project=$($e.Project)" } - $e = $items.Item(1) - Retry { $e.Navigate() } | Out-Null - Start-Sleep 1 - $sel = Retry { $dte.ActiveDocument.Selection } - Log "after Navigate: line=$($sel.CurrentLine) col=$($sel.CurrentColumn)" - break - } - } - try { - Add-Type -AssemblyName System.Windows.Forms, System.Drawing - $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds - $bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height - $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size) - $bmp.Save((Join-Path $out 'screen.png')); Log "screenshot $($b.Width)x$($b.Height)" - } catch { Log "screenshot failed: $_" } -} -Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue -Log "end" From 5b89a93299655c95fe6e6c26e985e2c0fff77e4b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:25:39 +0000 Subject: [PATCH 08/23] OX-02: live-host docs, Snipper reuse inventory, mutation controls M1-M6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - spec/OwenExtension.md §4a: the live request, owen serve, generated-source routing, parity, OWENV001. - docs/howto-visual-studio.md: installing and using Owen.VisualStudio, known limits. - docs/notes/owen-visual-studio-snipper-reuse.md: the four ADAPTed chunks, origin, licence. - scripts/owen_live_mutations.py: each mutation edits one product source, its registered check must go red, the source is restored. - prepare_vs_fixture.py: drop an unused noqa (ruff RUF100). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- docs/howto-visual-studio.md | 27 +++ .../notes/owen-visual-studio-snipper-reuse.md | 19 ++ scripts/owen_live_mutations.py | 164 ++++++++++++++++++ scripts/vs/prepare_vs_fixture.py | 2 +- spec/OwenExtension.md | 14 +- 5 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 docs/notes/owen-visual-studio-snipper-reuse.md create mode 100644 scripts/owen_live_mutations.py diff --git a/docs/howto-visual-studio.md b/docs/howto-visual-studio.md index 823de48b..a3fe83bb 100644 --- a/docs/howto-visual-studio.md +++ b/docs/howto-visual-studio.md @@ -58,6 +58,33 @@ so it never breaks a wrapping build by accident. ## 2. In Visual Studio +### Live: Owen.VisualStudio (OX-02) — for projects that use an Owen extension + +If the project references an Owen extension package (for example `Owen.TypedBuilder`, which +brings `Owen.Build`), install the one generic **Owen** VSIX +(`frontend/roslyn/Owen.VisualStudio`, built on Windows with Visual Studio's MSBuild: +`msbuild frontend\roslyn\Owen.VisualStudio\Owen.VisualStudio.csproj /restore /p:Configuration=Release`, +then double-click `Owen.VisualStudio.vsix`). Requirements: Visual Studio 2022 17.14+ or Visual +Studio 2026, and the .NET 8+ runtime that `Owen.Build` already needs. No Python, Rust, checkout +or global `owen` tool. + +What you get, while typing and without saving or building: +- Owen's findings as **squiggles**: at the finding's location, and on each witness step the core reports (for a stale state token, the line that uses it again); +- one **Error List** row per finding (Source/Tool `Owen`), double-click to navigate; CS diagnostics stay the compiler's; +- the **Owen** output pane: the live service's own log, including its stderr. + +How it works and what it does not do: `docs/notes/owen-visual-studio-preregistration.md`. In +short, the VSIX sends the editor's text to `owen serve` from the project's own `Owen.Build` +package, and the Rust core decides; the VSIX decides nothing. `dotnet build` stays the +authoritative gate, with the same findings (the build/live parity gate holds them equal). + +Known limits: a state-protocol finding has no column (the squiggle covers the reported line); +a stale token is reported at the region entry with the stale use as its witness step (#393); a +file that was never saved is not part of the project's input set; live analysis starts after the +project's first design-time build (Visual Studio runs it on load). + +### Without an extension + Two approaches, cheapest first. ### Option A — External Tool (on-demand, no build coupling) — recommended diff --git a/docs/notes/owen-visual-studio-snipper-reuse.md b/docs/notes/owen-visual-studio-snipper-reuse.md new file mode 100644 index 00000000..2307b19f --- /dev/null +++ b/docs/notes/owen-visual-studio-snipper-reuse.md @@ -0,0 +1,19 @@ +# OX-02 P11: what Owen.VisualStudio reuses from Snipper + +Source: `PhysShell/snipper` at `43b395a`, MIT licence (its `LICENSE`). Only audited +infrastructure was taken, and only by **adaptation** (nothing is vendored or copied verbatim). +The OX-01 audit (`owen-extension-snipper-salvage.md`) rejected Snipper's long-lived process +lifecycle (stderr lost, crashes unwatched, orphans possible); none of it is used here. + +| # | chunk in Owen.NET | origin in Snipper | verdict | what changed | +|---|---|---|---|---| +| 1 | `frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj` (SDK-style VSIX project: `net472`, `Microsoft.VisualStudio.SDK` with `ExcludeAssets="runtime"`, `Microsoft.VSSDK.BuildTools`, `CreateVsixContainer`) | `extensions/snipper-vs/Snipper.VisualStudio/Snipper.VisualStudio.csproj` | ADAPT | no cargo build or bundled binary (the service comes from the project's `Owen.Build`); no pkgdef/VsPackage (MEF only); the build tools only on Windows so the project compiles on Linux | +| 2 | `frontend/roslyn/Owen.VisualStudio/source.extension.vsixmanifest` | `extensions/snipper-vs/Snipper.VisualStudio/source.extension.vsixmanifest` | ADAPT | MEF component only; targets 17.14–18.x; adds the Roslyn language-services prerequisite | +| 3 | `scripts/vs/owen-vs-acceptance.ps1`: vswhere instance selection, a dedicated root suffix (never the shared `Exp`), the `/log` activity log, relaunch after the registration restart | `extensions/snipper-vs/scripts/vs-smoke.ps1` | ADAPT | installs with `VSIXInstaller` instead of `DeployVsixExtensionFiles`; drives the IDE (DTE edits, UI Automation Error List, Owen's trace) instead of only checking that a process started | +| 4 | `tests/owen-live/LiveClientTests/LiveClientTests.csproj`: the VS-independent sources compiled into a test project by `` | `extensions/snipper-vs/Snipper.VisualStudio.IntegrationTests/*.csproj` | ADAPT (pattern) | `net8.0` console runner on any OS instead of a `net472` mocked-VS xunit project; tests the real `owen serve` | + +**Not reused**, by decision: the LSP client spine (`SnipperLanguageServerProvider`, StreamJsonRpc), +the PATH-fallback binary locator, Snipper's commands/options page, its process lifecycle. +`owen-live/1` is a small purpose-built framing (`Content-Length` + JSON, fatal on anything +malformed); the service lifecycle is new (`LiveService`/`LiveSupervisor`: stderr kept, death +detected and shown, restart budget, a Windows job object so the child never outlives devenv). diff --git a/scripts/owen_live_mutations.py b/scripts/owen_live_mutations.py new file mode 100644 index 00000000..fa3a3d55 --- /dev/null +++ b/scripts/owen_live_mutations.py @@ -0,0 +1,164 @@ +"""OX-02 mutation controls M1-M6 (docs/notes/owen-visual-studio-preregistration.md §12). + +Each mutation edits ONE product source the way the bug it stands for would, runs the check +that is registered to catch it, and requires that check to go red; the source is restored +whatever happens. A mutation that leaves its check green is a FAIL of this script. + + M1 the client publishes every answer, current or not -> LiveClientTests K4-stale-dropped + M2 a service that cannot start is swallowed silently -> LiveClientTests M2-cannot-start + M3 the coordinate contract is off by one -> LiveClientTests coord-* + M4 the host routes only Typed Builder's generated sources -> owen_live_gate K8-two-extensions + M5 live forms its own severity instead of the core's -> owen_live_gate P10-parity-* + M6 the unsaved document is read from disk -> owen_live_gate K2 + extractor E4 + +Run: python scripts/owen_live_mutations.py [--rust-core ] [--only M1,M3] +""" + +from __future__ import annotations + +import os +import subprocess +import sys +import tempfile + +ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) +VS = os.path.join(ROOT, "frontend", "roslyn", "Owen.VisualStudio", "Live") +CLI = os.path.join(ROOT, "frontend", "roslyn", "OwnSharp.Cli") +EXTRACTOR = os.path.join(ROOT, "frontend", "roslyn", "OwnSharp.Extractor") +TESTS = os.path.join(ROOT, "tests", "owen-live", "LiveClientTests") + +MUTATIONS: list[tuple[str, str, str, str, str, list[str]]] = [ + ( + "M1", + os.path.join(VS, "LiveEngine.cs"), + "if (!IgnoreVersions && !_gate.IsCurrent(snapshot.Key, response.Version))", + "if (false && !_gate.IsCurrent(snapshot.Key, response.Version))", + "client", + ["K4-stale-dropped"], + ), + ( + "M2", + os.path.join(VS, "LiveEngine.cs"), + " if (IgnoreVersions || _gate.IsCurrent(snapshot.Key, version))\n" + " Publish(", + " if (false)\n Publish(", + "client", + ["M2-cannot-start"], + ), + ( + "M3", + os.path.join(VS, "LiveScheduler.cs"), + "return (start, end, start + 1);", + "return (start + 1, end, start + 2);", + "client", + ["coord-no-column", "coord-column"], + ), + ( + "M4", + os.path.join(CLI, "OwenHost.cs"), + "descriptors.SelectMany(d => d.Generators)", + 'descriptors.Where(d => d.Id == "Owen.TypedBuilder").SelectMany(d => d.Generators)', + "gate", + ["K8-two-extensions"], + ), + ( + "M5", + os.path.join(CLI, "LiveAnalysis.cs"), + 'level == "error" ? "error" : "warning",', + '"error",', + "gate", + ["P10-parity-stale", "P10-parity-copy", "P10-parity-raw"], + ), + ( + "M6", + os.path.join(EXTRACTOR, "InProcess.cs"), + "SourceOverlay is not null && " + "SourceOverlay.TryGetValue(Path.GetFullPath(path), out var text)", + "false && SourceOverlay!.TryGetValue(Path.GetFullPath(path), out var text)", + "gate", + ["K2-unsaved-own002"], + ), +] + + +def run(cmd: list[str]) -> subprocess.CompletedProcess[str]: + return subprocess.run( + cmd, + cwd=ROOT, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + check=False, + ) + + +def red(output: str, names: list[str]) -> list[str]: + return [n for n in names if f"FAIL[{n}]" in output] + + +def main() -> int: + argv = sys.argv[1:] + core = argv[argv.index("--rust-core") + 1] if "--rust-core" in argv else None + only = set(argv[argv.index("--only") + 1].split(",")) if "--only" in argv else None + failures = [] + # the client tests' service half needs a real `owen serve` and a live request; a request + # with no extension is enough (the service answers OWENB001), so build the host locally + built = run(["dotnet", "build", CLI, "-nologo", "-v", "q"]) + if built.returncode != 0: + raise SystemExit(f"FAIL: OwnSharp.Cli does not build: {built.stdout[-800:]}") + host = os.path.join(CLI, "bin", "Debug", "net8.0", "ownsharp.dll") + scratch = tempfile.mkdtemp(prefix="owen-live-mutations-") + request = os.path.join(scratch, "live.txt") + with open(request, "w", encoding="utf-8") as f: + f.write(f"project\t{os.path.join(scratch, 'Empty.csproj')}\nhost\t{host}\n") + for name, path, before, after, where, expect in MUTATIONS: + if only is not None and name not in only: + continue + with open(path, encoding="utf-8") as f: + original = f.read() + if original.count(before) != 1: + failures.append( + f"{name}: its anchor is not exactly once in {os.path.relpath(path, ROOT)}" + ) + continue + try: + with open(path, "w", encoding="utf-8", newline="") as f: + f.write(original.replace(before, after)) + if where == "client": + built = run(["dotnet", "build", TESTS, "-c", "Release", "-nologo", "-v", "q"]) + if built.returncode != 0: + failures.append( + f"{name}: LiveClientTests no longer builds: {built.stdout[-500:]}" + ) + continue + done = run( + [ + "dotnet", + os.path.join(TESTS, "bin", "Release", "net8.0", "LiveClientTests.dll"), + host, + request, + ] + ) + else: + cmd = [sys.executable, os.path.join(ROOT, "scripts", "owen_live_gate.py")] + if core: + cmd += ["--rust-core", core] + done = run(cmd) + caught = red(done.stdout, expect) + if caught: + print(f"ok[{name}]: caught by {caught} (exit {done.returncode})") + else: + failures.append(f"{name}: none of {expect} went red (exit {done.returncode})") + print(done.stdout[-3000:]) + finally: + with open(path, "w", encoding="utf-8", newline="") as f: + f.write(original) + for failure in failures: + print(f"FAIL[{failure.split(':')[0]}]: {failure}") + print(f"owen live mutations: {len(failures)} not caught") + return 1 if failures else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/vs/prepare_vs_fixture.py b/scripts/vs/prepare_vs_fixture.py index f2b0ac02..d5d7da01 100644 --- a/scripts/vs/prepare_vs_fixture.py +++ b/scripts/vs/prepare_vs_fixture.py @@ -19,7 +19,7 @@ import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -import owen_extension_gate as ox # noqa: E402 +import owen_extension_gate as ox FIXTURE = os.path.join(ox.ROOT, "tests", "owen-live", "LiveFixture") diff --git a/spec/OwenExtension.md b/spec/OwenExtension.md index d8d66bff..e7532759 100644 --- a/spec/OwenExtension.md +++ b/spec/OwenExtension.md @@ -105,8 +105,20 @@ developer declared, and "could not be checked" must never read as clean. **Generator diagnostic:** `OWENTB001` (Typed Builder) means a refused declaration, one error per defect. +## 4a. The live host (OX-02): the same contract in the IDE + +An extension gets live diagnostics in Visual Studio without shipping any IDE code. The one +generic `Owen.VisualStudio` extension (a VSIX, installed once per machine) serves every active +extension through the same descriptors: + +- **The live request.** `Owen.Build.targets` writes `obj/owen/live.txt` (target `OwenLiveRequest`, after `CoreCompile`, design-time builds included). It carries the same `project` / `descriptor` / `generated-root` / `severity` lines as the build request, plus `host` (this package's `ownsharp.dll`) and `dotnet`. Visual Studio's own design-time build writes it on load: no build is needed. +- **The service.** `owen serve` (the same packed program) speaks `owen-live/1`: `Content-Length` framing of UTF-8 JSON on stdin/stdout, `hello` first, anything malformed fatal (exit 3). An `analyze` request names the live request and carries the editor's unsaved documents and source-generated documents. The service validates the descriptors exactly as `build-check` does (OWENB002/003/004 live), takes the same input set, reads the editor's text instead of the disk, runs the ONE extractor in-process, and returns the Rust core's findings (its SARIF renderer: code, level, message, location, witness steps). +- **Generated sources.** An in-memory generated document whose path contains a declared generator's name (`…///`) is used in place of that generator's last build output on disk. Matched by generator name, never by extension. +- **Parity.** For the same saved sources a build and the live service report the same findings (code, severity, message, file, line). The live host additionally carries the column (when the core has one) and the witness steps; the msbuild line has neither. +- **Its own condition.** `OWENV001` (Owen.VisualStudio): the live service could not start or died. Shown in the Error List, restarted on the next edit at most three times in five minutes, then live analysis stays off (and says so) until the solution is reopened. Builds are unaffected. + ## 5. What the contract deliberately does not have - **Dynamic loading of analysis code.** Extensions add frontends and vocabulary. The analysis is the one Rust core, and its semantics change only through OwnIR (spec/OwnIR.md). -- **Per-extension IDE integration.** A future `Owen.VisualStudio` or language server serves every active extension from the same manifest. +- **Per-extension IDE integration.** `Owen.VisualStudio` (§4a) serves every active extension from the same descriptors; an extension never ships IDE code. - **Version ranges.** `requires.host` is a lower bound. The host refuses what it does not understand; it does not guess. From bdeb874df336e0ec10f0610c1e7554655421060a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:33:50 +0000 Subject: [PATCH 09/23] OX-02: create the VSIX container (import Microsoft.VsSDK.targets, as Snipper's SDK-style project does) The first Windows CI run built Owen.VisualStudio.dll but no .vsix: an SDK-style project gets no VSIX packaging from the build tools package alone. The build step now fails loudly when no .vsix is produced. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .github/workflows/ci.yml | 4 +++- .../Owen.VisualStudio.csproj | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a748cde..5c294beb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3548,7 +3548,9 @@ jobs: $msbuild = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1 & $msbuild frontend\roslyn\Owen.VisualStudio\Owen.VisualStudio.csproj /restore /p:Configuration=Release /p:DeployExtension=false /v:m if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - Get-ChildItem -Recurse frontend\roslyn\Owen.VisualStudio -Filter *.vsix | ForEach-Object { $_.FullName } + $built = @(Get-ChildItem -Recurse frontend\roslyn\Owen.VisualStudio -Filter Owen.VisualStudio.vsix) + if ($built.Count -eq 0) { Write-Error "no Owen.VisualStudio.vsix was produced"; exit 1 } + $built | ForEach-Object { "$($_.FullName) ($($_.Length) bytes)" } - name: The acceptance in Visual Studio shell: powershell run: | diff --git a/frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj b/frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj index a5fef60b..333001c9 100644 --- a/frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj +++ b/frontend/roslyn/Owen.VisualStudio/Owen.VisualStudio.csproj @@ -30,6 +30,18 @@ true Owen.VisualStudio.vsix true + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(TargetFramework)\$(TargetVsixContainerName) + $(MSBuildProjectDirectory)\obj\$(Configuration)\$(TargetFramework)\$(AssemblyName).dll + + $(BaseIntermediateOutputPath)$(Configuration)\$(TargetFramework)\vsix\ + $(VsixIntermediateOutputPath)extension.vsixmanifest + $(VsixIntermediateOutputPath)files.json + $(VsixIntermediateOutputPath)templateFiles.json + $(VsixIntermediateOutputPath)resources.json + $(VsixIntermediateOutputPath)ctoFiles.json + $(VsixIntermediateOutputPath)mergeCto.cache + $(VsixIntermediateOutputPath)createTemplateManifests.cache + $(VsixIntermediateOutputPath) $(NoWarn);VSTHRD003;VSTHRD010;VSTHRD100;VSTHRD110;VSTHRD200;VSSDK007 @@ -53,4 +65,14 @@ + + + + + + + From 333262cbd6fd99cd21cafd65e4c14e6f6b69cae5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:40:31 +0000 Subject: [PATCH 10/23] OX-02: keep the extractor's disk read sites where P-037 pins them; E3/E4 on a self-contained input - Program.cs keeps 'if (!File.Exists(path))' and 'text = File.ReadAllText(path);' (tests/test_p037_evidence.py anchors and counts them); the overlay is consulted first through TryOverlay, which touches no filesystem. CLI behaviour unchanged. - check_extractor_in_process E3/E4 overlay frontend/roslyn/samples instead of OrderBackend, whose extraction refuses on a clean checkout (no bin/, EF types unresolved) and so compared no facts with no facts in CI. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .../roslyn/OwnSharp.Extractor/InProcess.cs | 17 +++++---- frontend/roslyn/OwnSharp.Extractor/Program.cs | 35 ++++++++++--------- scripts/owen_live_mutations.py | 5 ++- tests/check_extractor_in_process.py | 21 ++++++----- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/frontend/roslyn/OwnSharp.Extractor/InProcess.cs b/frontend/roslyn/OwnSharp.Extractor/InProcess.cs index 8bd28684..0f49c8ae 100644 --- a/frontend/roslyn/OwnSharp.Extractor/InProcess.cs +++ b/frontend/roslyn/OwnSharp.Extractor/InProcess.cs @@ -22,13 +22,16 @@ partial class Program internal static StringComparer PathComparer => OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - static bool SourceExists(string path) => - SourceOverlay?.ContainsKey(Path.GetFullPath(path)) == true || File.Exists(path); - - static string ReadSource(string path) => - SourceOverlay is not null && SourceOverlay.TryGetValue(Path.GetFullPath(path), out var text) - ? text - : File.ReadAllText(path); + /// The overlaid text of , when an overlay holds it. No + /// filesystem access: the disk read stays at its one site in the parse loop. + static bool TryOverlay(string path, out string text) + { + text = ""; + if (SourceOverlay is null || !SourceOverlay.TryGetValue(Path.GetFullPath(path), out var held)) + return false; + text = held; + return true; + } } namespace OwnSharp.Extractor diff --git a/frontend/roslyn/OwnSharp.Extractor/Program.cs b/frontend/roslyn/OwnSharp.Extractor/Program.cs index 6bda2a27..f3b6cab6 100644 --- a/frontend/roslyn/OwnSharp.Extractor/Program.cs +++ b/frontend/roslyn/OwnSharp.Extractor/Program.cs @@ -5703,23 +5703,26 @@ static bool IsPublicCtor(SyntaxTokenList modifiers) // Defensive: an explicit input that is not a readable file (a directory // passed by mistake, a deleted path) is skipped with a note, never an // unhandled exception that aborts the whole scan. - if (!SourceExists(path)) + // OX-02: an IDE's unsaved buffer for this path is read INSTEAD of the file + // (InProcessExtractor). The CLI never has an overlay, so for it this is the disk path + // below, unchanged. + if (!TryOverlay(path, out var text)) { - Console.Error.WriteLine($"ownsharp-extract: skipping (not a file): {path}"); - continue; - } - string text; - try - { - // OX-02: an IDE's unsaved buffer for this path wins over the file on disk - // (InProcessExtractor); with no overlay — the CLI — this is File.ReadAllText. - text = ReadSource(path); - } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) - { - // A locked/unreadable file is skipped with a note, not an abort. - Console.Error.WriteLine($"ownsharp-extract: skipping unreadable file: {path} ({ex.Message})"); - continue; + if (!File.Exists(path)) + { + Console.Error.WriteLine($"ownsharp-extract: skipping (not a file): {path}"); + continue; + } + try + { + text = File.ReadAllText(path); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + // A locked/unreadable file is skipped with a note, not an abort. + Console.Error.WriteLine($"ownsharp-extract: skipping unreadable file: {path} ({ex.Message})"); + continue; + } } parsed.Add((Rel(path), CSharpSyntaxTree.ParseText(text, path: path))); if (path.EndsWith(".xaml.cs", StringComparison.OrdinalIgnoreCase)) diff --git a/scripts/owen_live_mutations.py b/scripts/owen_live_mutations.py index fa3a3d55..9783e690 100644 --- a/scripts/owen_live_mutations.py +++ b/scripts/owen_live_mutations.py @@ -72,9 +72,8 @@ ( "M6", os.path.join(EXTRACTOR, "InProcess.cs"), - "SourceOverlay is not null && " - "SourceOverlay.TryGetValue(Path.GetFullPath(path), out var text)", - "false && SourceOverlay!.TryGetValue(Path.GetFullPath(path), out var text)", + "if (SourceOverlay is null || !SourceOverlay.TryGetValue(", + "if (true || !SourceOverlay!.TryGetValue(", "gate", ["K2-unsaved-own002"], ), diff --git a/tests/check_extractor_in_process.py b/tests/check_extractor_in_process.py index 8abc5c12..6e02ed01 100644 --- a/tests/check_extractor_in_process.py +++ b/tests/check_extractor_in_process.py @@ -143,9 +143,11 @@ def main() -> int: elif _read(outs[i]) != _read(cli_out[i]): fails.append(f"{label} {name}: facts differ from the command line's") - # E3/E4: OrderBackend, the endpoint file overlaid - project = "samples/OrderBackend/OrderBackend" - target = os.path.join(ROOT, project, "OrderEndpoints.cs") + # E3/E4: the samples directory, one file overlaid. Self-contained on purpose: a run + # whose facts depend on a built bin/ (OrderBackend's EF types) would refuse on a + # clean checkout and compare no facts with no facts. + project = SAMPLES + target = os.path.join(ROOT, project, "FlowLocalsSample.cs") with open(target, encoding="utf-8") as f: original = f.read() same = os.path.join(tmp, "same.cs") @@ -153,13 +155,13 @@ def main() -> int: f.write(original) # a non-escaping disposable local: the flow pass emits a function for it changed_text = original + ( - "\nnamespace OrderBackend { static class OverlayProbe { static void M() " - "{ var s = new System.IO.MemoryStream(); s.WriteByte(1); } } }\n" + "\nstatic class OverlayProbe { static void M() " + "{ var s = new System.IO.MemoryStream(); s.WriteByte(1); } }\n" ) changed = os.path.join(tmp, "changed.cs") with open(changed, "w", encoding="utf-8", newline="") as f: f.write(changed_text) - args = [f"{project}/OrderBackend.csproj", "--flow-locals"] + args = [project, "--flow-locals"] base_out = os.path.join(tmp, "e3-base.json") _cli(dll, args, base_out) e3 = os.path.join(tmp, "e3.json") @@ -177,17 +179,18 @@ def main() -> int: fails.append("E3: an overlay equal to the disk changed the facts") # the same change made on disk, in a copy of the tree, run by the command line copy = os.path.join(tmp, "tree") - # bin/ included: a .csproj input auto-references the project's built output shutil.copytree(os.path.join(ROOT, project), os.path.join(copy, project)) with open( - os.path.join(copy, project, "OrderEndpoints.cs"), "w", encoding="utf-8", newline="" + os.path.join(copy, project, "FlowLocalsSample.cs"), "w", encoding="utf-8", newline="" ) as f: f.write(changed_text) disk_out = os.path.join(tmp, "e4-disk.json") _cli(dll, args, disk_out, cwd=copy) checks += 1 e4_bytes = _read(e4) - if e4_bytes is None or e4_bytes == _read(base_out): + if _read(base_out) is None: + fails.append("E4: the unchanged samples produced no facts; nothing was compared") + elif e4_bytes is None or e4_bytes == _read(base_out): fails.append( "E4: an overlay that differs from the disk did not change the facts " "(the disk was read instead of the buffer)" From 4790674c894308d47a18501aa1c76f9c94a11b29 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 00:56:27 +0000 Subject: [PATCH 11/23] OX-02 P14: dismiss Visual Studio's first-run dialogs; run in the image's Exp hive The run on bdeb874 installed the VSIX (VSIXInstaller exit 0) but a fresh root suffix opened on the modal 'Sign in to Visual Studio' dialog, which rejects every DTE call (screenshot in that run's evidence). The driver now dismisses first-run dialogs through UI Automation as a user would, and CI uses the Exp hive the image already initialised (an ephemeral runner: nothing shared to protect). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .github/workflows/ci.yml | 2 +- scripts/vs/owen-vs-acceptance.ps1 | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c294beb..c72efb73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3555,7 +3555,7 @@ jobs: shell: powershell run: | $vsix = (Get-ChildItem -Recurse frontend\roslyn\Owen.VisualStudio -Filter Owen.VisualStudio.vsix | Select-Object -First 1).FullName - scripts\vs\owen-vs-acceptance.ps1 -Vsix $vsix -Fixture "$env:RUNNER_TEMP\vsfx\fixture.json" -Out "$env:RUNNER_TEMP\vs-evidence" + scripts\vs\owen-vs-acceptance.ps1 -Vsix $vsix -Fixture "$env:RUNNER_TEMP\vsfx\fixture.json" -Out "$env:RUNNER_TEMP\vs-evidence" -RootSuffix Exp - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index 483a0474..ba555105 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -95,9 +95,35 @@ $activity = Join-Path $Out 'ActivityLog.xml' $t0 = Now $proc = Start-Process $devenv -ArgumentList "/rootsuffix", $RootSuffix, "/log", "`"$activity`"", "`"$($fx.sln)`"" -PassThru Log "devenv pid $($proc.Id)" +# A hive's first launch shows modal first-run dialogs (sign-in, then the environment +# settings); a modal dialog rejects every DTE call. Dismiss them the way a user would. +$firstRun = @('Skip and add accounts later.', 'Skip and add accounts later', 'Not now, maybe later.', 'Start Visual Studio') +function Dismiss-FirstRun { + $root = [System.Windows.Automation.AutomationElement]::RootElement + $byPid = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ProcessIdProperty, $proc.Id) + foreach ($w in $root.FindAll([System.Windows.Automation.TreeScope]::Children, $byPid)) { + foreach ($name in $firstRun) { + $c = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::NameProperty, $name) + $el = $w.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $c) + if ($el) { + Log "first-run dialog: '$name'" + try { $el.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke() } + catch { + $r = $el.Current.BoundingRectangle + [Mouse]::SetCursorPos([int]($r.X + $r.Width / 2), [int]($r.Y + $r.Height / 2)) | Out-Null + [Mouse]::mouse_event(2, 0, 0, 0, [UIntPtr]::Zero); [Mouse]::mouse_event(4, 0, 0, 0, [UIntPtr]::Zero) + } + return $true + } + } + } + return $false +} $dte = $null for ($k = 0; $k -lt 180 -and -not $dte; $k++) { Start-Sleep -Seconds 2 + try { [void](Dismiss-FirstRun) } catch { Log "dismiss: $_" } + if ($k % 30 -eq 0) { Shot ("startup-{0:D3}" -f $k) } if ($proc.HasExited) { Log "devenv exited ($($proc.ExitCode)); relaunching once (extension registration restart)" $proc = Start-Process $devenv -ArgumentList "/rootsuffix", $RootSuffix, "/log", "`"$activity`"", "`"$($fx.sln)`"" -PassThru @@ -105,7 +131,7 @@ for ($k = 0; $k -lt 180 -and -not $dte; $k++) { try { $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.$major.0") } catch { } } if (-not $dte) { Check "dte" $false "no DTE after 360 s"; Shot "no-dte"; exit 1 } -Retry { if (-not ($dte.Solution.IsOpen -and $dte.Solution.Projects.Count -gt 0)) { throw "loading" } } 300 | Out-Null +Retry { try { [void](Dismiss-FirstRun) } catch { }; if (-not ($dte.Solution.IsOpen -and $dte.Solution.Projects.Count -gt 0)) { throw "loading" } } 300 | Out-Null Log "solution open after $([int]((Now) - $t0) / 1000) s: $($dte.Solution.FullName)" $liveTxt = Join-Path (Split-Path $fx.project) 'obj\owen\live.txt' From bd0ef669f55765f7e0085112096b3a24fe05618e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 01:25:15 +0000 Subject: [PATCH 12/23] OX-02 P14: realize the editor before measuring it The run on 4790674 opened the solution in 23 s and the design-time build wrote obj/owen/live.txt (L0 ok in Visual Studio), but Use.cs opened as a background tab behind 'What's new': no text view, so no tagger and no Owen monitor, and the Error List window was never opened, so not even CS0029 could be read. The driver now closes the start page, opens the Error List, activates Use.cs, and records whether Owen.VisualStudio.dll is loaded into devenv. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/owen-vs-acceptance.ps1 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index ba555105..229a6be6 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -135,8 +135,24 @@ Retry { try { [void](Dismiss-FirstRun) } catch { }; if (-not ($dte.Solution.IsOp Log "solution open after $([int]((Now) - $t0) / 1000) s: $($dte.Solution.FullName)" $liveTxt = Join-Path (Split-Path $fx.project) 'obj\owen\live.txt' +# The editor must be REALIZED for its taggers to exist: a background tab is not. Close the +# start page VS opens after an update, open the Error List, and bring Use.cs to the front. +foreach ($w in @(Retry { $dte.Windows })) { + try { if ($w.Caption -like "What's new*" -or $w.Caption -like "Start Page*") { Log "closing '$($w.Caption)'"; $w.Close() } } catch { } +} +Retry { $dte.ExecuteCommand('View.ErrorList') } | Out-Null $window = Retry { $dte.ItemOperations.OpenFile($fx.use) } +Retry { $window.Activate() } | Out-Null +Retry { $window.Visible = $true } | Out-Null $doc = Retry { $dte.ActiveDocument } +Log "active document: $($doc.FullName)" +$loaded = $null +for ($k = 0; $k -lt 60 -and -not $loaded; $k++) { + try { $loaded = @((Get-Process -Id $proc.Id).Modules | Where-Object { $_.FileName -like '*Owen.VisualStudio.dll' })[0] } catch { } + if (-not $loaded) { Start-Sleep -Seconds 1 } +} +Check "vsix-loaded" ($null -ne $loaded) "Owen.VisualStudio.dll in devenv: $(if ($loaded) { $loaded.FileName } else { 'not loaded' })" +Shot "opened" $td = Retry { $doc.Object('TextDocument') } # ---- trace ------------------------------------------------------------------------------------ From d330abedd361d8809050526091aa90ea0c8a861e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 02:05:34 +0000 Subject: [PATCH 13/23] OX-02 P14: diagnose why Owen.VisualStudio.dll is not in devenv The run on bd0ef66 realized Use.cs, read the Error List through UI Automation (CS0029 | LiveFixture | Use.cs | 10), but Owen.VisualStudio.dll never loaded into devenv and the ActivityLog does not mention it. Collect the hive's installed extensions, the MEF composition's .err files and the VSIXInstaller logs, and stop early when the extension is not in the process. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/owen-vs-acceptance.ps1 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index 229a6be6..691396dc 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -71,6 +71,23 @@ function Retry([scriptblock]$Block, [int]$Seconds = 60) { } } +# Where an installed extension and the MEF composition's own errors live, for the evidence. +function Collect-Diagnostics { + $hive = Get-ChildItem (Join-Path $env:LOCALAPPDATA 'Microsoft\VisualStudio') -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like "*$RootSuffix" } + foreach ($h in $hive) { + Log "hive: $($h.FullName)" + Get-ChildItem (Join-Path $h.FullName 'Extensions') -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like '*Owen*' -or $_.Name -like '*.vsixmanifest' -or $_.Name -like 'extensions.*' } | + ForEach-Object { Log " ext: $($_.FullName)" } + Get-ChildItem (Join-Path $h.FullName 'ComponentModelCache') -ErrorAction SilentlyContinue | ForEach-Object { + Log " cache: $($_.Name) $($_.Length)" + if ($_.Name -like '*.err') { Copy-Item $_.FullName (Join-Path $Out $_.Name) -ErrorAction SilentlyContinue } + } + } + Get-ChildItem $env:TEMP -Filter 'dd_VSIXInstaller*' -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item $_.FullName $Out -ErrorAction SilentlyContinue } +} + # ---- Visual Studio ---------------------------------------------------------------------------- $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" $vs = (& $vswhere -latest -products * -format json | ConvertFrom-Json)[0] @@ -151,8 +168,16 @@ for ($k = 0; $k -lt 60 -and -not $loaded; $k++) { try { $loaded = @((Get-Process -Id $proc.Id).Modules | Where-Object { $_.FileName -like '*Owen.VisualStudio.dll' })[0] } catch { } if (-not $loaded) { Start-Sleep -Seconds 1 } } +if (-not $loaded) { Collect-Diagnostics } Check "vsix-loaded" ($null -ne $loaded) "Owen.VisualStudio.dll in devenv: $(if ($loaded) { $loaded.FileName } else { 'not loaded' })" Shot "opened" +if (-not $loaded) { + # nothing below can pass without the extension in the process: stop with the evidence + $results.failed = $true + $results | ConvertTo-Json -Depth 8 | Set-Content (Join-Path $Out 'results.json') -Encoding UTF8 + Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue + exit 1 +} $td = Retry { $doc.Object('TextDocument') } # ---- trace ------------------------------------------------------------------------------------ From 4289f6a84475f8b2b90731724ad1093f83f2a192 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 02:24:37 +0000 Subject: [PATCH 14/23] OX-02 P14: print the built VSIX's entries and manifest; keep the .vsix as evidence The run on d330abe shows the extension installed and enabled in the Exp hive and the MEF cache rebuilt, yet no composition error or catalog trace mentions Owen: check what the packaged manifest declares. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c72efb73..479fc2d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3551,6 +3551,12 @@ jobs: $built = @(Get-ChildItem -Recurse frontend\roslyn\Owen.VisualStudio -Filter Owen.VisualStudio.vsix) if ($built.Count -eq 0) { Write-Error "no Owen.VisualStudio.vsix was produced"; exit 1 } $built | ForEach-Object { "$($_.FullName) ($($_.Length) bytes)" } + Add-Type -AssemblyName System.IO.Compression.FileSystem + $zip = [System.IO.Compression.ZipFile]::OpenRead($built[0].FullName) + $zip.Entries | ForEach-Object { " entry: $($_.FullName) ($($_.Length))" } + $m = $zip.GetEntry('extension.vsixmanifest'); $r = New-Object System.IO.StreamReader($m.Open()); $r.ReadToEnd(); $r.Dispose(); $zip.Dispose() + New-Item -ItemType Directory -Force "$env:RUNNER_TEMP\vs-evidence" | Out-Null + Copy-Item $built[0].FullName "$env:RUNNER_TEMP\vs-evidence\" - name: The acceptance in Visual Studio shell: powershell run: | From a28c958be5669e46c8cebf7455afa2a18716d8ec Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 02:35:06 +0000 Subject: [PATCH 15/23] OX-02 P14: rebuild the hive's MEF cache after installing the VSIX The run on 4289f6a printed a correct manifest (MefComponent Owen.VisualStudio.dll) and an installed, enabled extension, yet the hive's ComponentModelCache files were byte-for-byte those of the previous run: the image ships the MEF composition prebuilt and the install rebuilt only the pkgdef cache, so Owen's parts were never composed. Drop the cache and run devenv /updateconfiguration before launching. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/owen-vs-acceptance.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index 691396dc..ae2052ec 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -107,6 +107,18 @@ $p = Start-Process $installer -ArgumentList "/quiet", "/rootSuffix:$RootSuffix", Check "install-vsix" ($p.ExitCode -eq 0) "VSIXInstaller exit $($p.ExitCode) into root suffix $RootSuffix" if ($p.ExitCode -ne 0) { $results | ConvertTo-Json -Depth 8 | Set-Content (Join-Path $Out 'results.json'); exit 1 } +# The MEF composition is cached per hive (ComponentModelCache). A hosted image ships that +# cache prebuilt, and installing an extension rebuilt the pkgdef cache but not this one (the +# run on 4289f6a: the cache files were byte-for-byte the image's, no part of Owen in them). +# Drop it so Visual Studio composes again with the installed extension, as /updateconfiguration +# would on a developer machine. +foreach ($h in (Get-ChildItem (Join-Path $env:LOCALAPPDATA 'Microsoft\VisualStudio') -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*$RootSuffix" })) { + $cache = Join-Path $h.FullName 'ComponentModelCache' + if (Test-Path $cache) { Remove-Item -Recurse -Force $cache; Log "dropped the MEF cache $cache" } +} +$u = Start-Process $devenv -ArgumentList "/rootsuffix", $RootSuffix, "/updateconfiguration" -Wait -PassThru +Log "devenv /updateconfiguration exit $($u.ExitCode)" + $major = $vs.installationVersion.Split('.')[0] $activity = Join-Path $Out 'ActivityLog.xml' $t0 = Now From e71027fa60d084e16dbb38614df338aa79ec1d9e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 02:49:53 +0000 Subject: [PATCH 16/23] OX-02 P14: prove the extension loaded from its own trace, not the module list The run on a28c958 (MEF cache rebuilt) shows Owen.VisualStudio composed in the IDE and working: its trace has 'Owen live analysis loaded', a workspace snapshot with the unsaved Use.cs and Roslyn's in-memory generated documents, and publication 1 of the project with no entries (K1). Only the driver's check was wrong: a .NET Framework assembly is not reliably among Process.Modules. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/owen-vs-acceptance.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index ae2052ec..fae47ab2 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -175,13 +175,15 @@ Retry { $window.Activate() } | Out-Null Retry { $window.Visible = $true } | Out-Null $doc = Retry { $dte.ActiveDocument } Log "active document: $($doc.FullName)" +# The extension's own word that its MEF parts were composed and its monitor runs (a .NET +# Framework assembly does not reliably show among the process's native modules). $loaded = $null -for ($k = 0; $k -lt 60 -and -not $loaded; $k++) { - try { $loaded = @((Get-Process -Id $proc.Id).Modules | Where-Object { $_.FileName -like '*Owen.VisualStudio.dll' })[0] } catch { } +for ($k = 0; $k -lt 120 -and -not $loaded; $k++) { + if (Test-Path $trace) { $loaded = Select-String -Path $trace -Pattern '"Owen live analysis loaded"' -SimpleMatch -List } if (-not $loaded) { Start-Sleep -Seconds 1 } } if (-not $loaded) { Collect-Diagnostics } -Check "vsix-loaded" ($null -ne $loaded) "Owen.VisualStudio.dll in devenv: $(if ($loaded) { $loaded.FileName } else { 'not loaded' })" +Check "vsix-loaded" ($null -ne $loaded) "Owen.VisualStudio composed in devenv: $(if ($loaded) { $loaded.Line } else { 'no load line in the trace after 120 s' })" Shot "opened" if (-not $loaded) { # nothing below can pass without the extension in the process: stop with the evidence From a0c038d304883cb01514ba1478e590105ba794f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 03:06:09 +0000 Subject: [PATCH 17/23] OX-02: TagsChanged on the UI thread; keep the measured editor in front The run on e71027f reached the core of the acceptance in Visual Studio 2026: the unsaved second Submit went workspace snapshot -> owen serve (extract 278 ms, core 11 ms) -> OWN002, and the Error List showed 'OWN002 | IDisposable local draft is used after it is disposed | LiveFixture | Use.cs | 10' with Use.cs still unsaved. No squiggle was traced because VS reopened its start page over the editor (an invisible view is never asked for tags). - The tagger raises TagsChanged on the UI thread and reports any exception to the trace and the Owen pane instead of letting the editor switch it off. - The driver brings Use.cs to the front before every measured edit, and reports a navigation failure as a check instead of dying silently. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .../Owen.VisualStudio/OwenErrorTagger.cs | 33 +++++++++++++++++-- scripts/vs/owen-vs-acceptance.ps1 | 16 +++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs b/frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs index f9d59319..14499a18 100644 --- a/frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs +++ b/frontend/roslyn/Owen.VisualStudio/OwenErrorTagger.cs @@ -5,6 +5,7 @@ using System.ComponentModel.Composition; using System.Diagnostics; using System.Linq; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Adornments; using Microsoft.VisualStudio.Text.Tagging; @@ -54,11 +55,39 @@ private void OnFileChanged(string file) if (!string.Equals(System.IO.Path.GetFullPath(file), System.IO.Path.GetFullPath(_document.FilePath), StringComparison.OrdinalIgnoreCase)) return; - var snapshot = _buffer.CurrentSnapshot; - TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(snapshot, 0, snapshot.Length))); + // Publications arrive on a background thread; the editor's tag aggregators expect + // TagsChanged on the UI thread. + _ = ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try + { + var snapshot = _buffer.CurrentSnapshot; + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(snapshot, 0, snapshot.Length))); + } + catch (Exception ex) + { + OwenTrace.Event("error", new { where = "TagsChanged", error = ex.ToString() }); + } + }); } public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + { + try + { + return Tags(spans); + } + catch (Exception ex) + { + // a tagger that throws is switched off by the editor: report, show nothing this time + OwenTrace.Event("error", new { where = "GetTags", error = ex.ToString() }); + OwenLog.Write("Owen: squiggles failed: " + ex.Message); + return Array.Empty>(); + } + } + + private IEnumerable> Tags(NormalizedSnapshotSpanCollection spans) { if (spans.Count == 0) return Array.Empty>(); diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index fae47ab2..6c65e615 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -267,6 +267,14 @@ function Delete-Line($line) { $b = $td.StartPoint.CreateEditPoint(); $b.MoveToLineAndOffset($line + 1, 1) $a.Delete($b) } +# VS may reopen its start page over the editor after the solution loads; an editor that is not +# visible is never asked for tags, so every measured edit first brings Use.cs to the front. +function Front-Use { + foreach ($w in @($dte.Windows)) { + try { if ($w.Caption -like "What's new*" -or $w.Caption -like "Start Page*") { $w.Close() } } catch { } + } + try { $script:window.Activate() } catch { Log "activate: $_" } +} $stale = ' draft.Submit(now);' $anchor = 'var submitted = draft.Submit(now);' $region = 'OrderProtocol.WithDraft(order, draft =>' @@ -295,6 +303,7 @@ else { $snap = @($script:events | Where-Object { $_.kind -eq 'snapshot' })[0] if ($snap) { Log "snapshot: documents $($snap.data.documents -join ', '); generated $($snap.data.generated -join ', ')" } +Front-Use $since = Now Insert-Line ((Line-Of $anchor) + 1) ' // an unsaved, harmless edit' $cleanPub = $null @@ -308,6 +317,7 @@ Check "K1-unsaved-clean" ($cleanPub -and @($cleanPub.data.entries | Where-Object Delete-Line (Line-Of '// an unsaved, harmless edit') # ---- K2: the second Submit, unsaved ----------------------------------------------------------- +Front-Use $since = Now $insertAt = (Line-Of $anchor) + 1 Insert-Line $insertAt $stale @@ -322,7 +332,7 @@ $row = Wait-Row 'OWN002' $true 60 Check "K2-error-list" ($null -ne $row) "Error List row: $(if ($row) { $row.Text } else { 'none' })" # navigation: double-click the row, then read the caret -if ($row) { +if ($row) { try { try { $row.Element.GetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern).Select() } catch { } $r = $row.Element.Current.BoundingRectangle [Mouse]::SetForegroundWindow([IntPtr]$dte.MainWindow.HWnd) | Out-Null @@ -333,7 +343,7 @@ if ($row) { $expectColumn = ((Retry { (All-Text) -split "`r?`n" })[$regionLine - 1]).IndexOf('OrderProtocol') + 1 Check "K2-navigation" ($active.FullName -eq $fx.use -and $point.Line -eq $regionLine -and $point.LineCharOffset -eq $expectColumn) "double-click -> $($active.Name) line $($point.Line) column $($point.LineCharOffset); expected Use.cs line $regionLine column $expectColumn (the core's location, §6)" Shot "k2-navigated" -} +} catch { Check "K2-navigation" $false "navigation could not be driven: $($_.Exception.Message) at $($_.InvocationInfo.PositionMessage)" } } # ---- P9: a CS error beside OWN002 ------------------------------------------------------------- $csAt = (Line-Of $region) @@ -347,6 +357,7 @@ Delete-Line (Line-Of 'int notAString = "s";') # ---- K3: delete the line ------------------------------------------------------------------------ Start-Sleep -Milliseconds 1500 +Front-Use $since = Now Delete-Line (Line-Of 'draft.Submit(now);') $gone = Wait-Tags $since { param($e) (Own002 $e).Count -eq 0 } @@ -357,6 +368,7 @@ Shot "k3-gone" # ---- latency over repeated edits --------------------------------------------------------------- $appear = @(); $vanish = @() for ($i = 0; $i -lt $Repeats; $i++) { + Front-Use Start-Sleep -Milliseconds 800 $since = Now Insert-Line ((Line-Of $anchor) + 1) $stale From 23d4aeb1fa8e5d77ea37f020bdd453cdd5bb9ab2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 03:25:02 +0000 Subject: [PATCH 18/23] OX-02 P14: three driver fixes after the first near-complete Visual Studio run The run on a0c038d passed in Visual Studio 2026: vsix-loaded, L0, K1 (open and unsaved clean edit), K2 squiggle (10:9 'OrderProtocol.WithDraft(order, draft =>' and the witness 13:13 'draft.Submit(now);'), K2 unsaved, K2 Error List row, P9 (CS0029 beside OWN002), K3 (gone after 372 ms), disappearance p95 420 ms, UI thread max 6.5 ms, no build, never saved. Three failures were the driver's: - navigation: DTE MainWindow.HWnd is null in VS 2026; use the process's window; - edit->squiggle 0/10 although the trace has every squiggle: Windows PowerShell gives a lone PSCustomObject no .Count; always count arrays; - the orphan probe counted every dotnet.exe; check owen serve processes only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/owen-vs-acceptance.ps1 | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index 6c65e615..b333c3ac 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -209,7 +209,8 @@ function Tags-Since($since) { Poll-Trace @($script:events | Where-Object { $_.kind -eq 'tags' -and $_.t -ge $since -and $_.data.file -like '*Use.cs' }) } -function Own002($tagsEvent) { @($tagsEvent.data.tags | Where-Object { $_.code -eq 'OWN002' }) } +# always an array: Windows PowerShell gives a lone PSCustomObject no .Count +function Own002($tagsEvent) { return ,@($tagsEvent.data.tags | Where-Object { $_.code -eq 'OWN002' }) } function Wait-Tags($since, [scriptblock]$Want, [int]$Seconds = 60) { $deadline = (Get-Date).AddSeconds($Seconds) while ((Get-Date) -lt $deadline) { @@ -335,7 +336,7 @@ Check "K2-error-list" ($null -ne $row) "Error List row: $(if ($row) { $row.Text if ($row) { try { try { $row.Element.GetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern).Select() } catch { } $r = $row.Element.Current.BoundingRectangle - [Mouse]::SetForegroundWindow([IntPtr]$dte.MainWindow.HWnd) | Out-Null + [Mouse]::SetForegroundWindow((Get-Process -Id $proc.Id).MainWindowHandle) | Out-Null [Mouse]::DoubleClick([int]($r.X + [Math]::Min(200, $r.Width / 2)), [int]($r.Y + $r.Height / 2)) Start-Sleep -Milliseconds 1500 $active = Retry { $dte.ActiveDocument } @@ -360,7 +361,7 @@ Start-Sleep -Milliseconds 1500 Front-Use $since = Now Delete-Line (Line-Of 'draft.Submit(now);') -$gone = Wait-Tags $since { param($e) (Own002 $e).Count -eq 0 } +$gone = Wait-Tags $since { param($e) @(Own002 $e).Count -eq 0 } $rowGone = Wait-Row 'OWN002' $false 60 Check "K3-disappears" (($null -ne $gone) -and ($rowGone -eq $true)) "after deleting the line: tags without OWN002 at +$(if ($gone) { $gone.t - $since } else { '?' }) ms; Error List row gone: $($rowGone -eq $true)" Shot "k3-gone" @@ -372,12 +373,12 @@ for ($i = 0; $i -lt $Repeats; $i++) { Start-Sleep -Milliseconds 800 $since = Now Insert-Line ((Line-Of $anchor) + 1) $stale - $e = Wait-Tags $since { param($x) (Own002 $x | Where-Object { -not $_.primary }).Count -gt 0 } 30 + $e = Wait-Tags $since { param($x) @(Own002 $x | Where-Object { -not $_.primary }).Count -gt 0 } 30 if ($e) { $appear += ($e.t - $since) } Start-Sleep -Milliseconds 800 $since = Now Delete-Line (Line-Of 'draft.Submit(now);') - $e = Wait-Tags $since { param($x) (Own002 $x).Count -eq 0 } 30 + $e = Wait-Tags $since { param($x) @(Own002 $x).Count -eq 0 } 30 if ($e) { $vanish += ($e.t - $since) } } function P95($xs) { $s = @($xs | Sort-Object); if ($s.Count -eq 0) { return $null }; return $s[[Math]::Max(0, [int][Math]::Ceiling($s.Count * 0.95) - 1)] } @@ -406,8 +407,14 @@ Check "never-saved" ((Get-Content $fx.use -Raw) -eq (Get-Content (Join-Path $PSS $results.failed = $failed $results | ConvertTo-Json -Depth 8 | Set-Content (Join-Path $Out 'results.json') -Encoding UTF8 try { $dte.Documents.CloseAll(2) } catch { } +$services = @(Get-CimInstance Win32_Process -Filter "Name = 'dotnet.exe'" | Where-Object { $_.CommandLine -like '*ownsharp.dll*serve*' }) +Log "owen serve processes before devenv ends: $(@($services | ForEach-Object { $_.ProcessId }) -join ', ')" Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue -Get-Process -Name dotnet -ErrorAction SilentlyContinue | ForEach-Object { Log "dotnet still running after devenv: pid $($_.Id)" } +Start-Sleep -Seconds 3 +$orphans = @(Get-CimInstance Win32_Process -Filter "Name = 'dotnet.exe'" | Where-Object { $_.CommandLine -like '*ownsharp.dll*serve*' }) +Check "no-orphan" ($services.Count -ge 1 -and $orphans.Count -eq 0) "owen serve ran ($($services.Count) process(es)) and did not outlive a killed devenv ($($orphans.Count) left)" +$results.failed = $failed +$results | ConvertTo-Json -Depth 8 | Set-Content (Join-Path $Out 'results.json') -Encoding UTF8 if ($failed) { Log "VS acceptance: FAILED"; exit 1 } Log "VS acceptance: all checks passed" exit 0 From 2a9944de7d0e150ec0f43d7afadd30160a709c73 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 03:25:22 +0000 Subject: [PATCH 19/23] OX-02 preregistration Amendment 1: baseline results and what the real IDE required Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- docs/notes/owen-visual-studio-preregistration.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/notes/owen-visual-studio-preregistration.md b/docs/notes/owen-visual-studio-preregistration.md index 535ba4bd..f1794e0f 100644 --- a/docs/notes/owen-visual-studio-preregistration.md +++ b/docs/notes/owen-visual-studio-preregistration.md @@ -135,3 +135,16 @@ VS Code, an LSP server, Rider/ReSharper, code fixes/lightbulbs, hover/explain UI ## 14. Verdict `GO_OWEN_VISUAL_STUDIO_ALPHA` only if all hold: an unsaved edit goes through the one extractor and the authoritative core to OWN002, a squiggle, an Error List row with navigation to the reported location, and disappears when the line is deleted — in a real Visual Studio; the parity gate passes; K1–K8 pass where registered; thresholds of §8 hold; mutations M1–M6 go red. Otherwise `NO_GO_OWEN_VISUAL_STUDIO_ALPHA` with the exact measured blocker. After GO: STOP. + +## Amendment 1 (2026-10-04): baseline results, and what the real IDE required + +**Baseline on `e889f8b`** (clean worktree, Linux): `cargo test` all ok (51 result lines); `protocol_gate --rust` 0 failures; `heap_effects_gate` PASS; `typed_builder_gate --rust` 0 failures (transcript `ba447304…`); `owen_extension_gate` 55/55; `tests/run_tests.py` exit 1 with exactly the two pre-existing local-only Stage-1 controls (`divergence-is-5`, `exec-failure-is-5`; red only when the launcher is built locally, recorded in OX-01) — CI on the same base is green. P-022 merge-gate controls 15/15 + wiring 12/12. + +**Changes to the plan, none to a threshold or a verdict rule:** +1. **The Error List is read with UI Automation, not `DTE.ToolWindows.ErrorList.ErrorItems`.** In Visual Studio 2026 18.10 `ErrorItems` returned 0 while the window showed CS0029 (probe run 37163671022, screenshot). UI Automation reads the rows the user sees (code, description, project, file, line); the column is checked through navigation. +2. **The hive.** CI runs in the image's `Exp` hive (an ephemeral runner; nothing shared to protect) instead of a dedicated root suffix: a fresh suffix opens on the modal sign-in dialog, which rejects every DTE call. The driver also dismisses first-run dialogs through UI Automation. +3. **The MEF cache.** The image ships the hive's `ComponentModelCache` prebuilt, and installing a VSIX rebuilt only the pkgdef cache, so Owen's parts were never composed (identical cache bytes across runs, Owen absent from the composition log). The driver drops that cache and runs `devenv /updateconfiguration` after installing, as a developer machine would on a first start after an install. +4. **"Loaded" is the extension's own trace line** (`Owen live analysis loaded`), not `Process.Modules`, which does not reliably list a .NET Framework assembly. +5. **The editor must be in front.** VS reopens its start page over the editor after load; a view that is not visible is never asked for tags, so the driver brings `Use.cs` to the front before each measured edit. (Product change made while finding this: the tagger raises `TagsChanged` on the UI thread and reports any exception instead of being switched off silently.) +6. **E3/E4** overlay a self-contained input (`frontend/roslyn/samples`): OrderBackend's extraction refuses on a clean checkout (no `bin/`, EF types unresolved), so it compared no facts with no facts in CI. +7. **P-037 pins** the extractor's disk read sites in `Program.cs`; the overlay is consulted before them (`TryOverlay`, no filesystem access) instead of replacing them. From 7cdf5291b2b6eb6a04253d5089b0dadf39a3cd8f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 03:41:59 +0000 Subject: [PATCH 20/23] OX-02 P14: count Owen's tags correctly in Windows PowerShell The run on 23d4aeb passed K2 navigation (double-click -> Use.cs 10:9, the core's location) and no-orphan (owen serve did not outlive a killed devenv: the job object holds), but the previous fix returned ',@(...)', so @(Own002 $e) became a one-element array holding the array: 'no OWN002 tag' never held (K3, disappearance 0/10) and 'a witness tag' held at once (the 12-16 ms 'latencies' are void). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/owen-vs-acceptance.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index b333c3ac..b3d0e401 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -209,8 +209,9 @@ function Tags-Since($since) { Poll-Trace @($script:events | Where-Object { $_.kind -eq 'tags' -and $_.t -ge $since -and $_.data.file -like '*Use.cs' }) } -# always an array: Windows PowerShell gives a lone PSCustomObject no .Count -function Own002($tagsEvent) { return ,@($tagsEvent.data.tags | Where-Object { $_.code -eq 'OWN002' }) } +# Windows PowerShell gives a lone PSCustomObject no .Count: callers count @(Own002 $e). (A +# function returning ",@(...)" would make that a one-element array holding the array.) +function Own002($tagsEvent) { $tagsEvent.data.tags | Where-Object { $_.code -eq 'OWN002' } } function Wait-Tags($since, [scriptblock]$Want, [int]$Seconds = 60) { $deadline = (Get-Date).AddSeconds($Seconds) while ((Get-Date) -lt $deadline) { @@ -323,8 +324,8 @@ $since = Now $insertAt = (Line-Of $anchor) + 1 Insert-Line $insertAt $stale $regionLine = Line-Of $region -$hit = Wait-Tags $since { param($e) $o = Own002 $e; ($o | Where-Object { -not $_.primary -and $_.line -eq $insertAt }) -and ($o | Where-Object { $_.primary -and $_.line -eq $regionLine }) } -Check "K2-squiggle" ($null -ne $hit) ("tags on Use.cs after the edit: " + $(if ($hit) { (Own002 $hit | ForEach-Object { "$($_.line):$($_.column)+$($_.length) primary=$($_.primary) '$($_.text)'" }) -join '; ' } else { 'none with OWN002 at the region entry and on the inserted line' })) +$hit = Wait-Tags $since { param($e) $o = @(Own002 $e); ($o | Where-Object { -not $_.primary -and $_.line -eq $insertAt }) -and ($o | Where-Object { $_.primary -and $_.line -eq $regionLine }) } +Check "K2-squiggle" ($null -ne $hit) ("tags on Use.cs after the edit: " + $(if ($hit) { (@(Own002 $hit) | ForEach-Object { "$($_.line):$($_.column)+$($_.length) primary=$($_.primary) '$($_.text)'" }) -join '; ' } else { 'none with OWN002 at the region entry and on the inserted line' })) if ($hit) { $results.measurements.k2_edit_to_squiggle_ms = $hit.t - $since } Check "K2-unsaved" (-not $doc.Saved) "Use.cs is unsaved (Saved=$($doc.Saved)); the disk still has one Submit: $(-not ((Get-Content $fx.use -Raw) -match 'draft\.Submit\(now\);\s*\r?\n\s*draft\.Submit'))" Start-Sleep -Milliseconds 700 From 90bd419b3863501c461bedc2e96d390ca5a3c37d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 03:42:33 +0000 Subject: [PATCH 21/23] OX-02 report: draft (Visual Studio results and verdict pending the real-IDE run) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- docs/notes/owen-visual-studio-report.md | 85 +++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs/notes/owen-visual-studio-report.md diff --git a/docs/notes/owen-visual-studio-report.md b/docs/notes/owen-visual-studio-report.md new file mode 100644 index 00000000..64f996ba --- /dev/null +++ b/docs/notes/owen-visual-studio-report.md @@ -0,0 +1,85 @@ +# OX-02 report: one generic `Owen.VisualStudio` host + +Preregistration: `owen-visual-studio-preregistration.md` (committed before product code, plus +Amendment 1). Base `main` = `e889f8b` (OX-01). Verdict at the end. + +## 1. What was built + +| piece | where | what it is | +|---|---|---| +| extractor as a library | `OwnSharp.Extractor/InProcess.cs` (+ 2 resets and the overlay look-up in `Program.cs`) | `InProcessExtractor.Run(args, cwd, overlay)`: the ONE extractor program run inside a long-lived process; an unsaved buffer is read instead of its file | +| shared host contract | `OwnSharp.Cli/OwenHost.cs` | descriptor validation, input set, refusals, manifest: one implementation for `build-check` (byte-identical output) and `serve` | +| the live service | `OwnSharp.Cli/ServeCommand.cs`, `LiveAnalysis.cs` | `owen serve`, protocol `owen-live/1` (framed, versioned, fatal on malformed input, newest-per-project wins); findings are the Rust core's, via its SARIF renderer | +| the live request | `Owen.Build/build/Owen.Build.targets` (`OwenLiveRequest`) | `obj/owen/live.txt` from the SAME request lines as the build, written by design-time builds too | +| the VS host | `frontend/roslyn/Owen.VisualStudio` (VSIX, one project, knows no extension) | workspace snapshots (unsaved open documents + Roslyn's in-memory generated documents) → supervised service → version gate → `IErrorTag` squiggles + `ITableDataSource` Error List rows | +| synthetic extension #2 | `tests/owen-extensions/Owen.TestProtocol` | its own generator and protocol (a turnstile): K8 and M4 | +| gates | `tests/check_extractor_in_process.py`, `scripts/owen_live_gate.py`, `tests/owen-live/LiveClientTests`, `scripts/owen_live_mutations.py`, `scripts/vs/*` | see §3 | + +No change to OwnIR, H0/H1, the state protocol, T0/P-022, diagnostic meanings or OX-01 +capability semantics. The Rust core is untouched. + +## 2. Two coordinate facts (preregistration §6) + +- **No column for state-protocol findings.** The protocol lowering emits lines only, so OWN002 carries none. The VS host applies the registered contract: the span is the reported line without surrounding whitespace, and the column shown is its first character. In VS the Error List row navigates to `Use.cs` 10:9, exactly that. +- **Where OWN002 points (#393, unchanged).** The core reports a stale state token at the region entry (`OrderProtocol.WithDraft(order, draft =>`, line 10). Its witness step "used here after it was released/returned" is the stale use (the inserted `draft.Submit(now);`, line 13). The Error List shows the finding once, at the core's location, as the build does. The squiggle is drawn at both: the one on the inserted line is the core's witness step, not a location the VSIX derived. + +## 3. Evidence + +### 3.1 Extractor as a library (P1/P2) — `tests/check_extractor_in_process.py`, 198/198 +- 98 extractor jobs: every sample, every protocol case, 6 flag sets, OrderBackend. +- Run in ONE process, forward and reversed: facts and exit codes are byte-identical to the command line's (E1/E2). +- An overlay equal to the disk changes nothing (E3). +- An overlay that differs is read instead of the disk and equals the command line over the same contents on disk (E4). +- CI `owen-live`: green on Ubuntu and Windows. + +### 3.2 The service (Linux and Windows CI) — `scripts/owen_live_gate.py`, 27/27 +- **L0:** a design-time build writes `live.txt` naming the package's service. No `bin/`, no build request, no manifest: nothing analysed. Generated sources stay in memory only. +- **S:** + - stdout carries frames only; + - a bad protocol, a bad header, a non-JSON body or an unknown type each gives a `fatal` frame and exit 3; + - clean shutdown exits 0. +- **Scenarios:** + - K1 clean, unsaved; + - K2 OWN002 at the region entry with a witness on the inserted line, disk still clean; + - K3 gone; + - K5 broken syntax answered, next edit recovers; + - K4s queued 6/7/8 → `ok, superseded, ok`, cancel → `cancelled`; + - K7 an unknown capability gives OWENB004 live; + - K8 Typed Builder + Owen.TestProtocol, both protocols' OWN002, no host change. +- **P10 parity:** build lines == live diagnostics on (file, line, severity, code, message) for clean / stale (OWN002) / copy (OWN005) / raw entity (OWN013) / refused (OWENB010). +- **T (Linux):** + - warm round trip p50 105 ms, p95 208 ms (threshold 750); in the service extract ≈ 88 ms and core ≈ 2 ms; + - cold start 1.7 s; + - working set 432 MB after 10 analyses, 455 MB after 100 (threshold 2×). +- **C (client half, against a real `owen serve`), 13/13:** + - coordinates; + - one Error List row plus a witness squiggle; + - K4 (a late answer is never published; M1 control); + - bursts coalesce (20 edits 30 ms apart → 1 analysis); + - K6 (a crash is shown, the next request restarts, after 3 restarts in the window it stays off and says so); + - M2 control. +- **G:** neither the service nor the VSIX names an extension. + +### 3.3 Mutations (M1–M6) — `scripts/owen_live_mutations.py`, 6/6 caught +| | mutation | caught by | +|---|---|---| +| M1 | publish every answer | K4-stale-dropped | +| M2 | swallow a service that cannot start | M2-cannot-start | +| M3 | coordinates off by one | coord-no-column, coord-column | +| M4 | route only Typed Builder's generator | K8-two-extensions | +| M5 | live forms its own severity | P10-parity-stale/copy/raw | +| M6 | read the unsaved document from disk | K2-unsaved-own002 (and E4) | + +### 3.4 Real Visual Studio (P14) — CI job `owen-visual-studio`, Visual Studio Enterprise 2026 18.10 on `windows-2025-vs2026` +VS_RESULTS_PLACEHOLDER + +## 4. Gates kept green +GATES_PLACEHOLDER + +## 5. Snipper reuse +`owen-visual-studio-snipper-reuse.md`: four ADAPTed chunks (VSIX project shape, manifest, the +real-IDE smoke's instance/hive/relaunch shape, link-compiled tests). Nothing copied verbatim; +the LSP spine, PATH fallback, commands and lifecycle are not reused. + +## 6. Verdict +VERDICT_PLACEHOLDER From fbdc978c23ed43a44a884b762fa41c83c5bbf416 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 03:59:49 +0000 Subject: [PATCH 22/23] OX-02 P14: compare the navigated file's path case-insensitively The run on 90bd419 passed every check in Visual Studio 2026 (19/19). The run on 7cdf529 (same product code) failed only K2-navigation with the caret at exactly 10:9 = expected 10:9: the driver compared DTE's FullName to the fixture path with a case-sensitive -eq. Compare full paths ignoring case, and print both. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- scripts/vs/owen-vs-acceptance.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/vs/owen-vs-acceptance.ps1 b/scripts/vs/owen-vs-acceptance.ps1 index b3d0e401..bbbcd13a 100644 --- a/scripts/vs/owen-vs-acceptance.ps1 +++ b/scripts/vs/owen-vs-acceptance.ps1 @@ -343,7 +343,10 @@ if ($row) { try { $active = Retry { $dte.ActiveDocument } $point = Retry { $active.Selection.ActivePoint } $expectColumn = ((Retry { (All-Text) -split "`r?`n" })[$regionLine - 1]).IndexOf('OrderProtocol') + 1 - Check "K2-navigation" ($active.FullName -eq $fx.use -and $point.Line -eq $regionLine -and $point.LineCharOffset -eq $expectColumn) "double-click -> $($active.Name) line $($point.Line) column $($point.LineCharOffset); expected Use.cs line $regionLine column $expectColumn (the core's location, §6)" + # the same file, whatever casing or form DTE reports its path in (the run on 7cdf529 failed + # this check with the caret exactly at 10:9 on a FullName that compared unequal by case) + $sameFile = [System.IO.Path]::GetFullPath($active.FullName).Equals([System.IO.Path]::GetFullPath($fx.use), [System.StringComparison]::OrdinalIgnoreCase) + Check "K2-navigation" ($sameFile -and $point.Line -eq $regionLine -and $point.LineCharOffset -eq $expectColumn) "double-click -> '$($active.FullName)' line $($point.Line) column $($point.LineCharOffset); expected '$($fx.use)' line $regionLine column $expectColumn (the core's location, §6)" Shot "k2-navigated" } catch { Check "K2-navigation" $false "navigation could not be driven: $($_.Exception.Message) at $($_.InvocationInfo.PositionMessage)" } } From eb9567de82c4e816cd68726d568681492f6442dc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 04:18:06 +0000 Subject: [PATCH 23/23] OX-02: real Visual Studio evidence, report and verdict GO_OWEN_VISUAL_STUDIO_ALPHA - docs/notes/evidence/ox02-visual-studio/: the run on fbdc978 (Visual Studio Enterprise 2026 18.10), 19/19: acceptance log, results, Owen's trace, and the screenshots of the live squiggle/Error List row, navigation, CS+OWN, and disappearance. Kept byte-for-byte (-text). - docs/notes/owen-visual-studio-report.md: what was built, the two coordinate facts (no protocol column; #393), the evidence (198/198 in-process, 27/27 live gate on two OSes, 6/6 mutations, 19/19 in Visual Studio: edit->squiggle p95 349 ms, disappearance p95 347 ms, UI thread max 3.3 ms), gates, verdict. - alpha-readiness and the OX-01 IDE feasibility note point to it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL --- .gitattributes | 1 + docs/notes/alpha-readiness.md | 14 ++ .../evidence/ox02-visual-studio/README.md | 13 ++ .../ox02-visual-studio/acceptance.log | 26 +++ .../ox02-visual-studio/k2-navigated.png | Bin 0 -> 89128 bytes .../ox02-visual-studio/k2-squiggle.png | Bin 0 -> 88367 bytes .../evidence/ox02-visual-studio/k3-gone.png | Bin 0 -> 82775 bytes .../ox02-visual-studio/p9-cs-and-own.png | Bin 0 -> 93323 bytes .../evidence/ox02-visual-studio/results.json | 153 ++++++++++++++++++ .../evidence/ox02-visual-studio/trace.jsonl | 132 +++++++++++++++ docs/notes/owen-extension-ide-feasibility.md | 6 +- docs/notes/owen-visual-studio-report.md | 72 ++++++++- 12 files changed, 413 insertions(+), 4 deletions(-) create mode 100644 docs/notes/evidence/ox02-visual-studio/README.md create mode 100644 docs/notes/evidence/ox02-visual-studio/acceptance.log create mode 100644 docs/notes/evidence/ox02-visual-studio/k2-navigated.png create mode 100644 docs/notes/evidence/ox02-visual-studio/k2-squiggle.png create mode 100644 docs/notes/evidence/ox02-visual-studio/k3-gone.png create mode 100644 docs/notes/evidence/ox02-visual-studio/p9-cs-and-own.png create mode 100644 docs/notes/evidence/ox02-visual-studio/results.json create mode 100644 docs/notes/evidence/ox02-visual-studio/trace.jsonl diff --git a/.gitattributes b/.gitattributes index 51ef9854..aeb43790 100644 --- a/.gitattributes +++ b/.gitattributes @@ -30,3 +30,4 @@ docs/evidence/*.json text eol=lf # `core.autocrlf=true` would make a correct generator look stale. samples/OrderBackend/OrderBackend/Domain/Order.Protocol.cs text eol=lf samples/OrderBackend/evidence/* text eol=lf +docs/notes/evidence/ox02-visual-studio/** -text diff --git a/docs/notes/alpha-readiness.md b/docs/notes/alpha-readiness.md index e4bed9f7..8fb7b70a 100644 --- a/docs/notes/alpha-readiness.md +++ b/docs/notes/alpha-readiness.md @@ -135,3 +135,17 @@ until the .NET alpha above is delicious. Do not let the spike exceed 20%. The standing priority is unchanged: **prove value, don't reshape form** ([consolidation-and-positioning.md](consolidation-and-positioning.md)). This note is the concrete "what value, packaged how" gate for that. + +## Update 2026-10-04: live diagnostics in Visual Studio (OX-02) + +`GO_OWEN_VISUAL_STUDIO_ALPHA` (docs/notes/owen-visual-studio-report.md). One generic +`Owen.VisualStudio` VSIX shows every active Owen extension's findings live, unsaved, as +squiggles and Error List rows. It reads the project's `obj/owen/live.txt`, written by +Owen.Build in design-time builds. It talks to `owen serve` from the project's own Owen.Build +package. The extractor runs in-process over the editor's text, and the verdict is the Rust +core's. + +- Proven in a real Visual Studio 2026 on CI: 19/19. +- Build/live parity is a gate. +- Extensions ship no IDE code. +- `dotnet build` stays the authoritative gate. diff --git a/docs/notes/evidence/ox02-visual-studio/README.md b/docs/notes/evidence/ox02-visual-studio/README.md new file mode 100644 index 00000000..06d4cda4 --- /dev/null +++ b/docs/notes/evidence/ox02-visual-studio/README.md @@ -0,0 +1,13 @@ +# OX-02 real Visual Studio evidence + +From CI run 37175674769 (job `owen-visual-studio`, head `fbdc978`), Visual Studio Enterprise +2026 18.10.12217.157 on the hosted `windows-2025-vs2026` image, driven by +`scripts/vs/owen-vs-acceptance.ps1`. Kept byte-for-byte as uploaded. + +- `acceptance.log`: every check, 19/19 passed. +- `results.json`: the checks and the measurements (edit -> squiggle / disappearance per edit, UI-thread maximum, service timings, working set). +- `trace.jsonl`: Owen.VisualStudio's own trace (`OWEN_LIVE_TRACE`): load, workspace snapshots (unsaved `Use.cs` + Roslyn's in-memory generated documents), publications with the service's timings, the tags the tagger produced per text version, UI-thread maxima. +- `k2-squiggle.png`: `Use.cs*` unsaved; squiggles on `OrderProtocol.WithDraft(order, draft =>` (the finding) and on the inserted `draft.Submit(now);` (its witness step); the Error List row `OWN002 … LiveFixture Use.cs 10`. +- `k2-navigated.png`: after double-clicking the row: caret at 10:9. +- `p9-cs-and-own.png`: CS0029 and OWN002 side by side. +- `k3-gone.png`: after deleting the line: no squiggle, no row. diff --git a/docs/notes/evidence/ox02-visual-studio/acceptance.log b/docs/notes/evidence/ox02-visual-studio/acceptance.log new file mode 100644 index 00000000..f0244cdb --- /dev/null +++ b/docs/notes/evidence/ox02-visual-studio/acceptance.log @@ -0,0 +1,26 @@ +[04:03:20.333] Visual Studio: Visual Studio Enterprise 2026 18.10.12217.157 [b61ad8f0] +[04:06:52.917] ok[install-vsix]: VSIXInstaller exit 0 into root suffix Exp +[04:06:52.954] dropped the MEF cache C:\Users\runneradmin\AppData\Local\Microsoft\VisualStudio\18.0_b61ad8f0Exp\ComponentModelCache +[04:07:48.331] devenv /updateconfiguration exit 0 +[04:07:48.396] devenv pid 1812 +[04:07:58.313] solution open after 9.893 s: D:\a\_temp\vsfx\ws\LiveFixture.sln +[04:08:09.801] active document: D:\a\_temp\vsfx\ws\LiveFixture\Use.cs +[04:08:10.954] ok[vsix-loaded]: Owen.VisualStudio composed in devenv: {"t":1791086890317,"kind":"log","data":{"line":"Owen live analysis loaded"}} +[04:08:11.024] ok[L0-live-request]: the design-time build wrote D:\a\_temp\vsfx\ws\LiveFixture\obj\owen\live.txt (no build was run) +[04:08:30.432] ok[K1-open-clean]: first publication (version 2, 4921 ms in the service): 0 entries, OWN: 0 +[04:08:30.437] snapshot: documents D:\a\_temp\vsfx\ws\LiveFixture\Use.cs; generated D:\a\_temp\vsfx\ws\LiveFixture\obj\Debug\net8.0\generated\Owen.TypedBuilder.Generator\Own.TypedBuilder.TypedBuilderGenerator\Order.Protocol.g.cs, D:\a\_temp\vsfx\ws\LiveFixture\obj\Debug\net8.0\generated\Owen.TypedBuilder.Generator\Own.TypedBuilder.TypedBuilderGenerator\Owen.TypedBuilder.Attributes.g.cs +[04:08:31.460] ok[K1-unsaved-clean]: a clean unsaved edit was analysed (version 3) with no OWN entry; document saved=False +[04:08:31.963] ok[K2-squiggle]: tags on Use.cs after the edit: 10:9+39 primary=True 'OrderProtocol.WithDraft(order, draft =>'; 13:13+18 primary=False 'draft.Submit(now);' +[04:08:31.967] ok[K2-unsaved]: Use.cs is unsaved (Saved=False); the disk still has one Submit: True +[04:08:33.309] ok[K2-error-list]: Error List row: OWN002 | IDisposable local 'draft' is used after it is disposed [resource: disposable] | LiveFixture | Use.cs | 10 +[04:08:34.933] ok[K2-navigation]: double-click -> 'D:\a\_temp\vsfx\ws\LiveFixture\Use.cs' line 10 column 9; expected 'D:\a\_temp\vsfx\ws\LiveFixture\Use.cs' line 10 column 9 (the core's location, §6) +[04:08:36.475] ok[P9-cs-and-own]: CS0029 row: CS0029 | Cannot implicitly convert type 'string' to 'int' | LiveFixture | Use.cs | 10; OWN002 rows: 1 +[04:08:40.340] ok[K3-disappears]: after deleting the line: tags without OWN002 at +344 ms; Error List row gone: True +[04:09:04.176] ok[T-edit-to-squiggle]: edit -> squiggle over 10/10 edits: p95 349 ms (threshold 1000), all: 334, 328, 349, 330, 336, 330, 347, 343, 328, 335 +[04:09:04.179] ok[T-edit-to-disappearance]: edit -> disappearance over 10/10 edits: p95 347 ms (threshold 1000), all: 328, 347, 330, 328, 336, 329, 322, 342, 343, 326 +[04:09:04.194] ok[T-ui-thread]: longest Owen UI-thread handler: 3.3251 ms (threshold 50) +[04:09:04.202] ok[no-build]: no LiveFixture.dll under bin/ (0), no build-check request: the build host never ran +[04:09:04.204] ok[never-saved]: Use.cs on disk is byte-identical to the fixture +[04:09:04.556] owen serve processes before devenv ends: 5372 +[04:09:07.578] ok[no-orphan]: owen serve ran (1 process(es)) and did not outlive a killed devenv (0 left) +[04:09:07.580] VS acceptance: all checks passed diff --git a/docs/notes/evidence/ox02-visual-studio/k2-navigated.png b/docs/notes/evidence/ox02-visual-studio/k2-navigated.png new file mode 100644 index 0000000000000000000000000000000000000000..5b6d86c850929b9cf36f2270f4a1016cc1d333c8 GIT binary patch literal 89128 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 zⅆ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 literal 0 HcmV?d00001 diff --git a/docs/notes/evidence/ox02-visual-studio/k2-squiggle.png b/docs/notes/evidence/ox02-visual-studio/k2-squiggle.png new file mode 100644 index 0000000000000000000000000000000000000000..df90c2d7673c7c30dea400cd7b3c1b6f109dc5cb GIT binary patch literal 88367 zcmagG2{@GB|33a0laeG9Ew;*15vA2SiWYli&-xA#4Z>K)G7~LIS!0qlk=-C>vJPe6 z2@_+@Zj5!tGGk``kKUi}^7}8>|G6%ghig2~Ij?ih>)iMKx{o*a4DRsmKfWIT0AB68 zS`PpK3f^)8`?$bw6%|K#!EYR15ANIq3OhyS!5^SbH}r1+Kyf6`&Odv=pSeBmT6h7# z!JoTd98K=o4gkQ;)YiIT>}Rvcd>wGuEJcBZVuw^txP3~t6uPnR(7q_>sXKzu5;;#U z&Z~8jrxib*xp!3Wf}JPViK2@ec9*U<+ZFMCtb?8o%@pOE985^A4p<)Xuc`EP`nlD# z$qZ2Mw;stdG(ni*D@^vgt96m1>3uo5M*TNv+kHw|>{RwDjI}rH-z)nW14V_L`uB1P z{0K<==N9sx+p!&*E&B>z*>H#~0smugQ5H_wfZKR-0o`nQe7y&cv|JLDS*FC85m#tqq2L-=e&*qQs< zx`224!uyWAnudrN_RE)-#%_(J#6Cs*{i7G}5KegZZwDKIIeb6xVJZB1$l7Of<8Oyt zSG5(Fj~0f@zrjyvy?-+cMSD}kuT>pB;u`#fYr|Sa z_*J}ix!6t~4!)Cm^s4h`c;aq+nAawlP4LgZ8C&<)=AjG0q&gwY^t0AW$XLz z8d%wRmlHH(naR$o;JRwIBTSxk)KFdCU_Mxo-`Q|$SZ#Yc_`vovm##=wpqjP^Ea$0x z71cgQJd2ptNe+-2D)Cb)GeK1=Yd<~_b8FPq0A>>*V-hp+)i~8Rix`R_x+(T&ZI|L6U3kBjy~9>ACp(B9%<+ zGIeX|>}=d#pM_7q3Ht;%_o)7?T2~;L5vVWEXJd+453tU^bx4Xb^o=j8Yla=V$wO-u ze0ZeW^qUg-ef^5en~#WJJG%7nI7cs3Y$i>wXYB3 z$2soyJ}S+2X~zlIaY<&|tKyeJ9ih+qGJ|<!3ud=MzdnOC-h5jiTRZ8 zZ=4vbeeeVAn=fOMKG?A*nc(cSdmyo*^ssXMe(L{pxOc>MX^T946A5khtR8-o@H^*c zhL?3xqH(T1B<)s3Evk4Ovh`ymzriK`S&Q0LXZhG*tLKnB@vC9JsLB6qr^Co)ecfXpJ#Oh zV(+csn0|`E*qwSsM-*Y0F!tGc&2m0MY*Id380k?kdaIYT;H};_3W=b__B0;lumu;1 zkKREnCKATV9I%!hew97jI8o-PCO$zUkAF;8n?2Z+$A%Ah`BN4c5ty1(jf9O)i0hco zw72~}dzJzhNTq59J;ml!W`bix)1mE$Th2Y{83ZA@;vq>G|45s4{H>bXRH7%P>$AN| zD1%##&>%j^-W+ip_P%b`Iji)&gE_H;nIOJp9;IHbR}~h+=ZU*k^#QOQeCZ7}1Iow# z%@R$O4$5tR*mu!@`euo4~m zg2LU|A$a<2l5jNtn?DEc9_QRFVhBIED!9B*A)(S9n6(AT$x(E|IQgq z_NLa{sbex`MLMlk0)Pu2;C13NeLy|p6G4*{8(txyHaqwkJq|H}_ zaMLCQ7XgVQb%d1y|Vh9z>f~WySMq$HiBB-*(E+{yWaj<419EO958#Imo26Ol#8)Fqw zEzeSL#_pjt&7(rg4eGrLMF~HhUec2=;5-aCpY&m6EwW`7&waHWkdu zf`nvU}P{V)xoN$9H+_SX-%3g35LJe6s-w$h!40f?o!#oqhIbiUQ{r& zZ&77UvxSl*$=ER$kNRvMAta|#dg)Kda9g?e)=K}T5txb>Z-lqG)>4+wV6B73Ry~JI z>yr)eUh_pMzq~##pN?yK)PtW#9;oxAL9A`BSx_m;)k2!!5GgXPl!tq=d8?E8+d>4- zC|O@mh#cwsW~0z}@i(SPUAU7hwK=>`cEyy_Lw4Mhb2$uG?~>KVIJrmmG8T11oCy0ji%xKD7 zH2{u)8uuFb%PS&j-)E%OpS_kBZRchOGs(6p5AklvaLe2vHvAV?gA$vS4@<+4 z2{m#LMPU}cEZPLn@<)k7;s(J)-M{^yrH4WW2K}o7fdPuxUis&VK7N7rl?ApZKBLch zN6&O~Vb=M3`Y(Gw-@N#?Y%+&8%ic#t#sJ}ZdT4{{XfWG+_NYhhEUxBPu#4`PO`pnw z{Oo{_syz0@-{{99Q>s!fbv1qbVP8B)@>byr@1JbQp|qsC6La)erGb1$7Y2>l*!G0+ z*)c@`*jRs>H(SJqpRIk8=r(&3mG5o_H%iYZx);A=n=QAas>amHPh=+djPmtFj-;}A zkmW`v;E1P9;;j7c&0~CIOF;5Beg+aH#u0NkYKeX8)-4>O>cTSpmhREK)JT^SW0(P+ zMnlTa4!-{c%_fiAO3HFbL>5%e&y3K=tr4`&psA}&UEq3|jF2?&K(mH!s9@AG8zRX; z^|YP~DnzA}2Z;p)j!Bmse0hs!iR=Z@pn%y(Mnloh0C;!8#wA{x57=HHAas|0Lw3vt z*h0z5(Bs((y8e*b9hE)4&gr<*fj$2$581OV$9&-C!s&&+73oQQ>zy$J4eOJUlo5W{ z(;n(Sgk?8ygX{r(beJ>g7iPZepQ)E0M{hIA-;GNBD(8LEjTd&#>c=&Qk7-~oFv`o= zl%1jeO;H6)OPbdrQvP%sMYy3S;^LL2N6%EGnMaKWMl6Fy9D4A1se2i&N<)ePjK0dQ zvI!hhqr9|^4tEbH`TG<)jlKTmb*QVnTbxWXX%ld)#`P2P`)@0xrgpX^jq%YH)zb86 z>^;>Z2GcevUeyu7B4sCq86aS(VCZ%$5T=Wv>>g=uW9D~1!rJf)_MJJ(K_Z`_w9xio zA*^eZH8vMn9fh{7*a@#Vz`CUf?$Slft!HU6XcKAJ=cdyUf3m3pD^b9`%@c@y8W*(f zD5o#@qP-xs1!^uQk}hyW9akKM4VeYDW}qM3QHW%z`?A{0P-0ENV?%i10MY&7Lc@75 zx*|;J{kGvPfm$QT`1Gx7?W=6h*MvtM3LV_+irM!?d$t}?{-h>yL#kE#xdE?)?JlEv zR&wb{AbMourm&a}UahG`Ere1cy`_CSKyKmX&FuEc?QUYfxJ)=QcuK~5^nqV672fpl zYszWHt?$7RBAuBV^dj}SSd`vE5f|lX-_jRx)Z&(Msc2-ItJ#=u)uXch{7Db_f{m6J zW&@GPJ6)8cG{+WIq440PcEv1vk1YecoWyowh?Hp}b$tmZ_vgx=Ue-{zE#KPuNR9Am z$g%bGGkr|eH7lhQCuW(*L$&bijkEfX%^Av22_FK7=Z4o{>R^26{oRcr_P#g+WMWj7yS!2{hfxSPtxU6 zplG^1*CY}makP)@`@7$5^K;uu~qd}@T98DUdM5I&9NwnvGP7RsyKlhqlI3N-&&`-1Vem=VqzAB$O()aZkv z^_FC;+0%t6(m?r)Dj?9@6}*&D9yqjBjgzqb9!Dn0`7PDmS$#ZKcI*D9UGjIT z=4hXw63bPRKo~dLIJ%MzawNsj9Tt=Ex^?;lM}}U!wgM1Tt+hhWSZbj6kQtPn?e&N{ z!mSXtgMRn%D;(W5*1jNtgpV?9L7*FESL5EL?L3%Xu-%VBrY+wdQ@C@JSxzBVQ zRhnME-RHcW02~t7X|v)$DoMTK2HZaacSS+UVg;lIu4y~}fpPCl=K)`M`ND*;hS}!@1%f=n4Be@C)1;3|K=La+>Q zru-wY@`@Y%>NQcuIMrVV*f-C6!FA!8*Ijfww}cm9-j-p-w}tqU`kpQ8y~}j7pV4Ds z+k)%zd15OMB&C6{GYP6PfZX{{PQP0n&S(s_5sLOO*9w4NGK+jcmjIB{@<(s2!`NOK zSy?2A&HlFL=Gs)&eCklADF6NRKL)_}`JwXe16xunmcPG4NaAge@LY`RP+S#=Qc-O(dlG8rR(P-w#*77~fz&BZH}@*A4JuJ69b& z)xnLdk`0^r{p^%ZdIs3c-RsXP2DOcOoF;2Oa#o}B_3*aSilJ6ZBlK@8LGmqF`~P}- z=n}^Y5yd3V5KEhV%N8bM|32)vVEIg(tL=6?gjr$UM4p<;j+|9ZxypY<5(NLCEW!3& zf2j}~)x25S`Yz*dE8oXitzUH?9ZpKRSCgqhP@h3~uJt#0EtTnO@xFJ3Xq33FNq?Jj z-@mJfq&MmnzLo-}5S}ABk+Z`E;76ieA+-x@f0`2Cg`J6;Qi)eVEWGGUR{5ffk)MiA zQc!YFNJzkWwR%~q*mR!f@2?g*|L~)23)r#>Bk`_s%fzI+BUj-=Y1WlK&SNz})za_} zI2w_%&wg{m2Moxcb6-><>d2RtFV<}>C|BGX|@K>#j&wQ|2{2e>L3nkhqQ~= z%ef1fXHXQEZ{$X16>j`8o}VRhgGx@H!(CxlwcqLWccaev=KH(F8p7~eQwmwNk;Il9 z6R@*JiN>C@SNeU6j1qvNp_GNmoIK`b6Z$sd2UH`*n>^5A`n9dKi#YbZ5}?B1R~WvSGkf*~I0j|h&CD(VeAuah*qI$N&KOB)ura+vPqWWpxCUro-cQ*KaHPBpuD4T$lYL7sJM32+R99iFnpRu zDt%W$_2yuu52d7FS(uyURKv7f_qDLFsCKp06e3^IEXzu@_qlH+7p(cp&iFoBEa(<2$qt9-K%Oybwe%M%At+E&$XZ$9`Yj6pahSJ}bEgS1JDj8nQ zx|-u6n1r?X)>zdTKSozHfp1K_Jbt~g68rY;{`I5Y4+@C|9l^&#q%lAgk1%&O*rK-) zqAIUiGf#Rda`7HFR*-)hGty};UK@+kf47hB#hAIR8tle!_?A+2KSE6H?ddxazj614 zm3@{YTlTvDGhw0?C5e{XtPHAPbrsZxV^|FXHYPnolQ`O+BXF2$h?cEPygBw*oaF1i zrXV`CE>jn?;)~k!o$*>5uyjvwq$3gfqcCuqHZ(_{zUZ@Pe&Cp5y_^yG@R0^op+IJ; zJy%Xk15{(7ex2P!XDe>Di+C?#TrCY)D`fv@V&o1i>L1B9OE>Rks3o%6xEtm*V$S|a z)wr=a=#<(Y&W0O2x2+V>RX_=uHB-fUyH!8VC!b(_H?C@EZs8G#89u>)SOEnmh*5=u^40(n~p!s1d9hBObtW{2)1wPOnEW1{A-d@U|A&<0)`L`rtK*QXA4yhr#w?xkL?zF5qXy* z7B=2TBSl-Q_dUI|7DV0hxL<%9>~8itSNZAiej_*V=$BSLaNb**CN@K!@W@}ktZkAUpHO=Ks+CE#)UNB%l; zjnUa-v#NthLm8g?9g;&H$-Mu(7j2ZbY$~=KJXC7&Qr0kKyd_rFy#MR_2kMYJ;&)is z=v3LfKIP{KnkSx8Sg{5*EX?<`c8kYWJd!mk2k8#Z92OZFQ5*Ipa&-#qj41!_6oggp zK|F#A&s&h=kYGQZD)cW+dsrY_SLR(u#AH6q9Xdwl{9SS37kfKTjbo~~N;^LzRQv`` zOpELVY>lWVi@n2>`v!L`gY(*45~26-Qgt=RP21KAwM&DGC)2RP`uW6ntvY~t9a~qS z+97We6sm?P7(Uf%s|X`~$mMrXwAj(cgQhgvrw}bcKuOTx4`UfSF|^qNvz@`Es@9d! zbR=LCk_Lr%0bnA?;QDrTyoj?1Hz1p%8kmF6$gl+ED}j;6>E!-918l@eHhDHV`2=zO z&r7SUyR{0{$EJ&S&T9iFH32F;=PPQa;(qKh^#F$bm2!SH({S;U~M2a28kB}!%5(}$at_YmCFx5hcweV;wMD6RL=&Qd;U zBU;afbNyDh(1b8y>*b)P2`gSP%i&H568OlmIVt;nZR?HD@Lq`H6^Zx?m#BQMZc{t= z=vtCoWHitnx-=vWEWQczsKvUEJ(TfQ1p#{T&#T6WcpwUOnGQQKSpH7s7kU4-lOywI zuC4*4^sZ%Qhf4x+cWG@5an>Gp!uI0hbWAm5lSLO0evf5Grv;qLAh03IhCGq(9j7unS4ep1~i-vdQM zM|<;r&lmyjKeSL zu7{RrNj%%)=h>lrqRNLsSts$@#UQ%3UV@_3(*F_tRXTn+u^XCsE|Kh40+{AYC9gw$Tt{ThPKEks zK{e7MJYTD~aHChQI*La(Ip_l4MZV>B3INCzIaJ$-ms%R`u7pGz%~MA4&3n>TnO~(U znPRL|Ht4Ei9|Y(MYtYj5kdW-i0wuuPhP~R6tU?U@LG!#Au<_I~LD*)4k{NMskU|ys zDD%K<*3mjIf|vpn+gv)Q)YTJ$Iw_1=RTXaumY1ycC8ZR_03bzoS!WTuiZK%XXXk!# z>XPNXBwR)A9(XZeDQ@$451xJ%9vMdoLak-M**PT+EdrN3y4zklP-%+Rr?}Czd&@-?sU!ZC!Z#ux!5%89&O0DZbgN>Spw6&kOwK^-@tUON`lX+!u8m zt0vGwej$W`a1IZys&GIFJxme+gt%E!+=nRsZ*D;X^C}5DI99x)XMufO+X}EPLOZ@A zvwn%FuiCQU$HHd5H5wOQMwG+>-=9ss8Vr2^p=^!@bTyva{JRQof|aQ{LZDcSdVNc* z%pj-4esIrDf(-fz0-l=$95))QE>sQ#tTqV49nWE(mqY2M5RD<1xVF_+5v~V?*x2S^ zEeYTtbnJQM*oK4+->d%0sR@AYthFLK6fe}SA`O_9y^%A#1*ugQvP@4GlK~vKg2?WJ zIo{uvK^!u7u;LbPVLu|>f2n--!joP}5=@d?+9AOPcDyRq<8wK@q(el`EM;dd&?^I_x7tEKamZ8inTSH6_goV=N7c;LHB z|G9Np2P$Gjl2IPj!ls66Caf4jf=#Mofq@w&c6TewHR{gdN~3#nYjNJl{Q%?kJ$o(m z{_J?KnBS{V%Wsy*kQ+`_g|>tUL-p-?q3oK~#uK{@kpB!t3sCb~K=O?^82a;kyTq7I zQS(^qg#!ti8iZe1nZw@n^{Cd9K#MBAc#dr}Y_8Vfjf|ogyK&87%wEJ7d>6U9Z;q^^ z=9N#d5L0@5A1l6P8RAXG|J+Dd#clBGoSH*~!h0gkFpFI`bxWaW$Kl3{gik z^S=pM>Z47iPr)&dwh^Cla^V=w5Wxoo9Su3M)8?+t)$D-=w{NP~aq_dvAV9e}=9J&m zbNz2(ZcGG;bWC6xR7%F*{KC+=nq#rH>q=y{AoB7*1&6y>#G&w z{BtbS;nVGv+jW&2riYdV4&*(@9oNXaQPrQQ!8bD^0Pt(SdB;$>#f%dJk-q!kM4 z056dHuP<9(ehWp5gasbWWk*ekM}KGmCEm+O71~DTISz^Z7IgjuFikjfMZ9tk`v)1O zUcPT@ArE^>{?XK*Tdh@(nTY?&45cBIe{_8YAztAinv*{D&;zUBl{;DA=GvI|l1I2v zjoYpymB+uUnW31_B(<9xNPnd6BKFgt`WBSc-i8=u(efosY=xo$k4l~tjqYO#+f|jm z%QjkR=QwhA8&xA84MmSq#t!@_K*NUo71^dp%j;13C=$qK`~5`Ff-2+uJN@Q=~`92g1DvgQ#=S zv*3tr1W^r5aN1_d|u6vTFK5?;z*MLrXmQXx*;fkIWI_`G)G$MFZ0MR zITR)Q9OE7B`tg60ftCQ!vy`Gj(Uien^Jn!*qM_^u(rr{aSXUG^EUEt{&fQE)^*}Gh z03M4R7jDkqIAkC>S65{T(GW}2=G4C3r^pd?mk*#dJqa>NmI5*ot5^sm(ia)`p|XQ- zT7~>0`nQe@9V1fnpr$2J+fC<&7~{ zyCun?tDsVgNRnLp^z6|dw&$H&wnDm!sXQl_3yftr{`j-Wlof#ts~T~|7yauV2ZNQ|RWN#@PR`4}uv%xJ{QWIH zYI5^;FMQz)Qi5V?GyDeJ}_ zF0rj=qx2;?4#7pUg04riPn!zVx~?cZ+A~An3XYTeMXr2PCLTV-gc*N5!0Dk2Kp*$x z4bx{2J2b@t`{24fy@)rGzz=wY`d+j!2!o22(()X8LS~&~SIeV0W0st5vIZ~#6MY8Z zzjgo{(majRe0nFK27$9wPAFgK9(j3Fi{HDiCtLV!u)bP!*goVEnKXUqi8QFI&4jz~`qH>U4p zoICbNy6$UDgbrWENNWecSaq}t7CBhLiTE+HGBSWT$G0Qux=&+jhjZp06c0t~T2|?7 zIDIyPXrzS4rJmoUR@FbUwpDsRh=HQp`6VXVf@<*&finw|Td9F7rdmI2Sn-l*(lweS zGC5igMSE;oxA2B_%rYk1#0QK;DV! z^Uiz&#gAE$iw~C1bELW1x?|3bf{EsKllB3!Jw&7CoZ(VR028T(wQayW%>f62&n_8* zar`~@pD9(0v;kk=-BUbZ401oI`sA$hXhAi%60yM3xJvi zQQVRgcH{#oCH2hY&glUj>C;pi6^Zza;|8`z{g2re5)qH~bS@bBi4fF|5-a=3*2~qr z9AQb&d&nc^vHM$hAOk+_h>&V1#1Ug10DLdI1sknMmj)cNQ}wolyVeE5k4&}c@AvP> zUI)wtArE6Y7RX=UvJ&#V7%-9Z{!5#PS04L!{*)WU&uS4E@{>z6iDB)03Dp55j9snn z%5tgdK#frjZdUckzr4~ zkL73-aD@q7IqG{3WI+p&VjsyI-f4TEGv2W-3IcUO3yk{>I)GN`W@~P(x@ZM+Z$vQ& zYhHLii9BM}rkEqx)u3OJnVwv%j!yR5&YH%!J9PG8^d|PvFvz^n|i%6;j9!{$lY8 zwqpENnE3dizrq7JRY7w_x6p8#{b1p{ZHyaRSl(*i`EaML8|V5rlCnZTxvK|a@sAWR zkZ)nn)h2M6jCk~>EOO}Wk&l*ed0Xc!JgI$;;Y77WJP>_2cI+RTDGmqw4(R&Z^+oHnuPuH%2T)7k?XD zN4Pt6#Xe^l6u3tXS3BvcdUU12Jc+WaRJS>rg|!So-4;0h%})RrAhyfbd_Oa>I`ttC zv08Ok7*!q-odSUKF^^2ZOoX?9ymKL6!nTH^md<_>B#184s}@Ag_hx1jPa+!5u7uAj zTf71mN2^H(atms0rN?3c5>HS-F1UU-zU*c9p4M279+&)I^XBc5=2q8-jV*0uUZrnX z>AQhg6Zoi`rm(C}3tH%tl{l`zc+&jI3Zij$#^Rur&9GujLaCT zJiA6Z+KU6)-JIuhMN>$9J+=g`DZc+hdhHD`M+%ec%oQ`0IV2Vq{1#kb`f)6}@vdu6 z|J6?DMDoH^)cP6WQ>yrutW?WWYZ#+(P^Rw71%>V${oMHTmS1<=e9j31Yp5!dJkztF zT?v$@GgED43c1m+?O0fy?M{R3)`m1#3U~=h(pyy zdzuSQLs7q<7ak!03ev5Vodra>tzXSEJX3C`*^S z;C~(^Tb{}d@bq2VbpiS(TfJVGRPuF=jipk(FYSx%T7RW6bk6tPNJt~7@>)}}a^50> zO$xaI|IqCO$J4tq{@SmaRM z7Tmo;aRUVfW4t|@Q zh%!_+Hwxqwyo33IAmebeufR%>daP^1Zjpf)L8oObK^bMcq-B2LW^d=~F>DzqCbd*0JmCj`YriG$Y9 zI~aNE?i?c-(k!;&3P)`vTmSX&vv?IYJ^b}f^sk(~I%a016dAB5uhPJ&h$HKSd*6?% zG^VrFPo2_90rmAEb<}yVTg;zH#}$sDI^Ec`a8()lI{3D}dvnA%p<`LCZfq^B9I184 zTq*&q=Qkg?N)zVY4&NOwmu4#XM5_-A?Dep)-BMgyHfX9q?T%miD?LJC=>>4fd_d{} z_l!ZWAxsZA#D71iD42=d*}xO7z(*?jal`L}t)Kh@L5H28#iRfqgYx$`J;v`>bL)4x z8etOjuD4ESaM3R zai^%3zl*B_^Gsd13N*UO{^hWzvM)nI%RIS*U&BSSm(>9o*b6Mih% zRo3jUA(wW;(NRiAN9Tg?l9eiw+Wc?ZRzahq+U)h*+}w}|dxLl6_X7p9<9;Smt!rP1 zzsfolG#w%2a_-WfF21#^N~`WFJ(DNXD`YM$iC^~sUh|1JqB%I9woTGz65StdtxVuN z!xtxiyyU@on)HIzdk=A>>i5IqW+oGpy>*{U>N3R>BL%gAIWj2aJmi()C|X++%!V1t zV@vzB>&v}nJ*JxZ7TpoAW_m@v%KPoICIm^-vgD|bQ96Xw5UD;X@kIxUtj(m!}q|} zRny*x1Vx+NAzT_m*7K{Hw#C7<7B%xWx0?4g!MPS%)HxZ1s@|Fczq{S2$wuA}adEdk z=%VFEwrKR#2eH5^lpV6WR@QzljZ}8HN|uuqBJV_qyy;SBwC@nk2q!;?kBfUrCSoV4 z^S^Na)lZ4v?`ulr#%kIZMeUYtuw3oP6_Mb41jtaEY_1J#wEfT&6d}zX`VQKZB1>95 zm>wag&3z60Qa>v{!;!aETf6VQX|AYKe2qks-W-oTYxH%uZi@4~`0S6CrNt>vFRxW_ zRPR99Li3GSVkG7hG)!hoSlbZTC+lpw+}Wirq__W!CiEspP*Fs=+)=+gk`br7+P=u_ zEM8dm%MB-r?OjXypp`k9#x!5yC6T6c3tj!{;- z%8HG6lWt?lez1Ooay^K)WvyWzTReG^v(3I_q6rey%cM~2>0-s;F-u5oTZuD?Pm;IY zaYkg%hK90n*F^wBC>U$K8d@#7$XFkhC)U>`?aFsz_w9ZE)N`+U=@~*kU@(cGuCO{s z#ESbY4L^~-O{*PS+ZC4vbYH~%jmcTiyD7bG6yhXnZW{X7vbz9wmD8K7*{cbu{io)6 z*ff2kzy48IA&2_IVU93rsbVO)w1m~j2{0rULIL4lnGFU~Nf zrqH&h9A2doV~z<;6f*f%DKT@{N+{{~JwgC>05N21Md?NE|0se~K6J%L@nY)mOS4!& zU`ICrj$^lJ4ZXg*IbX`L(M;m~K&I~HD#un7weJ~2|_kFV8 zJ2fz}y*0xdB^er+U4D=wteDV@`0&qx&$aBMk6pb)FPn190DX#5Dwl z>tvGmarLM(LGM_vcu%xfvOG6!v59BONQ4@K`M-Igc_cVt3k%klXY9)-C4E`b?1Xo0 zOi7`2ADiwD)?Q-plQL-oi5FKBkeb;U>$k!VC(Fm;m;j|Pg6YIBV6 zJnMsqhqM2tA|dWZnj~nQO}%LUs=zJ1&y8*11<{KA1l4ess}XyuYjM3f;&@cs z2+GwNeJm*eTp3wQ+w=;41`p2AR;KiP0hwKF|NJBbInpTo5TY@6qvFU%a$S&CN$tWA zR`2j{`T>_Fg2H%ICyeb+mqeHl)AbhR&k_HxYChH=5Rg&Z1F3MR~^ofiq2(nrn zP+&3p;Q3gBjA6=C`B*UswuYVy4+;l2NO0_()w0@!4(E;hK3)0aPQ)`l==Ow$a*-N$ zm6{}=>Sk-JkWRVZmi!ucQL$XPsKD|WtfZ4^VW0MMZ)LU-?wYPBvV#v$tcU<@$6yynWO&F!vVr8b?NP1k%&Z_o364*Vv?+HPL z6D5IN%snN1jCf9Sdg_O1&B1%pOu+~8!EDA@Vl^~F&z6{OYxYo83JAH8KlDh0b%%TEWuHrb`Oux$1yGmHY`&Qf+}-D3oRfqA z!S;M1QMV5)=!=B@_harE>!0W7KD3K^z_Tmf>-gYD`a4;@AY{`6UhHCwoF7o&7Ff_{ z{@iuyTyX$5wU^xJ3t-MWWAgupp4O74ycZk;3PHqu9Q5q;f{)_A51us8C@dM8{UhTl zPO`Z?g+=JPeYUizp=|FW>3twP`^$K8nxH^A@}IRE?Fl|VHquX`^u^W%4|n-@Z3Kdl z{0j7MJAhN^efALa)4ykoPQ;IBHXXEns&IVGjD({Uc}`?~WdBHz0Codqo>zwibt=xZ3Ult8?h{tKJ|81E$+bX;B4oaasz|tok`I7 zqc&9LdPhrsO6}AT4xA20OIDa#Q@mrK5o&rPB_;zcXL$LIG}#eE*^X`TpW%vN)TglE zTh|P4sQ;#+PH9aUj9yxJ8HJ2)*c7}U2);~5o~9FlkfU{mWR)vxo)8#j8GGO3SD`x}1Ng;m+tK&udzI=DST7 z-)&1~P1aPgSUvI8(?o6{w`)cXbzD}+su5iy*mE-P8{FSII%sqGQs;q^Of}kB4TAxt z+^(kIhb5DM@sT6x_02D%#EQUveK=Z^8Wz)rG%QCjY^(e(=H}(CCXVei_JE0^9Xzfv z;FAEP(eEy~=t|^b81RYw^^cdo{xDs7ZJhjt?)^^x)(?=dND$di4&8rhz`+AH?9Bw@ z&6)E2%@1N#mXfs~Moj~qhi#HK(lFr0n_Uy5(&K4nsjz8jgUKomo;SmJ!ZH>_`m^kx zo&$$3*M8vEAx@$6A>9VXS-g?h1T~#a`QtN#*Eu}ZqH+*=S*yinq zSa5};-H8;#DfYMHQQPWTm(%N zMM#74tHt0{mM!e7Mzxu{_ z;MP4wh!8g&74StwfpGaap}M@>R2%)38tkcTnx&Y_j#&*x7J3|ukgt5dHaL5MGC18l z-`8Mzr!_+c-8cAc&gff;&7EoL5TQg_30rW#Dfp#~ndxgWr@|V6VsZb-dGhzwbwp}& zjhU^#0CDyH&c6dZysO2=Bm+f|%hzl;PbhH2eS$_23M5*GFwnekq4OmLciw>Kn?PI| zp$%|TNlw1Sal4y;8-nh&bQs;bt{lAM_dikL^B>U}S6Jp%x{6;XYq`HRBUG0)n10pu zCVlv*tP9+|2eYdMEXxIRWLhBAR+#1nO}W1og6#QX`4AVuqJ6Sk_>f?2$X!FAI>ju< zk5{ImLyNfH`h;QNXv~(HWK!u1ul*{TF5x_{sjbly)F4#PI2JNSg1KviNw%mYViHst z{TJWucatfyc7!E-@mMq)4RxqaMx2cf+8d!zgE*r;5TL_18hdgOEVqpNrei1TT{Rp* z@6s|T8YOTTySWu^n}ZGx?rh2CULyS5l+wFR|96i!mqs4fp1YA7_d>gIg@>u+yIPP|tE(QKZwvVgaB<6)`=n!D?Xx}t(4E8seM$9#lBqq+Vs6_x_wy+af0Lj?Dz*HZGtHY>TvGmm4v{x zl(36z#RGN|-_45Pqg^7zXX%6#YSWvTO17zo`yK|eD@23ugnP>?hca3IH%lZv1l%OeKh(DB9JnmCw!l-S%jg&e2SeDu4Q=@Lv)RfIhdHd>rZP zR-Zq_t}i0md=q76PmPHF?5N9fyI=2K7JS)Fm~X<9(GcxFai}Ul;QQVE-aVyD?S3PV z=OFFFqu86Ii+F@%8C+$-p1dq;7JOtBs|l7IGwC-fwu7iWcW|4vpe3Ng5gBB?yI2-% zZI1f!>UglRd_DkS*#7kNzzVvKvx0>C0-lxISZiDl?!{+eES|^F>_xruDGzoQeloOq zXQr2GylE(SPu6E`VA_qHbpOPuS6ubUz`Ze9y5ei%da?b48#9`NP?DmXh8+l}v9|h) z?W5t({NAg;Pd`GnDz6D53D@&6?}EtH{|PNb;@E>YAgXX{%`)WZ+1Rm#HS;PiW6rPG zl0BG#8slCu!t!w*?!_r3V%wMZ1%@Av>{v#|#zBixeukk<$lPg>jL5hQesE0*p%<_x zwQo0jB`&TDzaPC?O<9csYxf2gv&MeM7Siyi`C~vlqNRw_AMN*tXoRqJlKz(NpO#p-iQ}ITG-7%9|GRQulkMdO`ZkPV-t)Z;Re7In>{?#N z{jC@OI}0C;t`f=z^Ⓢ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

yaS_cs_z| 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\X<%McQ~}D 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

i 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 Q6byFbn$^{c%Wi-E4?*8fUH||9 literal 0 HcmV?d00001 diff --git a/docs/notes/evidence/ox02-visual-studio/p9-cs-and-own.png b/docs/notes/evidence/ox02-visual-studio/p9-cs-and-own.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce7f474709669d28815171ec27eb1af94396a00 GIT binary patch literal 93323 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"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086914995,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":5,"tags":[{"code":"OWN002","primary":true,"line":11,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":14,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086915008,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086915264,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086915346,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":5,"timing":{"extract":68,"core":6,"total":75,"working_set_kb":198164},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":11,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":14,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086916535,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":6,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086916538,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086916792,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086916861,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":6,"timing":{"extract":57,"core":5,"total":63,"working_set_kb":201784},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086919268,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":7,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086919270,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086919539,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086919604,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":7,"timing":{"extract":54,"core":5,"total":60,"working_set_kb":203612},"entries":[]}} +{"t":1791086919604,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":7,"tags":[]}} +{"t":1791086921256,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":8,"tags":[]}} +{"t":1791086921258,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086921514,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086921582,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":8,"timing":{"extract":53,"core":6,"total":60,"working_set_kb":204312},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086921582,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":8,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086922417,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":9,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086922419,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086922674,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086922736,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":9,"timing":{"extract":51,"core":6,"total":57,"working_set_kb":203332},"entries":[]}} +{"t":1791086922736,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":9,"tags":[]}} +{"t":1791086923622,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":10,"tags":[]}} +{"t":1791086923624,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086923881,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086923942,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":10,"timing":{"extract":49,"core":5,"total":55,"working_set_kb":203648},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086923943,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":10,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086924784,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":11,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086924786,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086925041,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086925122,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":11,"timing":{"extract":70,"core":5,"total":76,"working_set_kb":205408},"entries":[]}} +{"t":1791086925122,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":11,"tags":[]}} +{"t":1791086926005,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":12,"tags":[]}} +{"t":1791086926007,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086926265,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086926346,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":12,"timing":{"extract":68,"core":7,"total":76,"working_set_kb":208088},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086926348,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":12,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086927200,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":13,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086927202,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086927459,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086927522,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":13,"timing":{"extract":51,"core":5,"total":58,"working_set_kb":208936},"entries":[]}} +{"t":1791086927522,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":13,"tags":[]}} +{"t":1791086928377,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":14,"tags":[]}} +{"t":1791086928379,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086928629,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086928696,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":14,"timing":{"extract":52,"core":8,"total":61,"working_set_kb":205092},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086928701,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":14,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086929571,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":15,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086929573,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086929830,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086929891,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":15,"timing":{"extract":51,"core":5,"total":57,"working_set_kb":205204},"entries":[]}} +{"t":1791086929891,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":15,"tags":[]}} +{"t":1791086930734,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":16,"tags":[]}} +{"t":1791086930734,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086930993,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086931060,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":16,"timing":{"extract":52,"core":6,"total":59,"working_set_kb":205324},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086931062,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":16,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086931943,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":17,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086931945,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086932203,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086932265,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":17,"timing":{"extract":51,"core":6,"total":58,"working_set_kb":204592},"entries":[]}} +{"t":1791086932271,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":17,"tags":[]}} +{"t":1791086933166,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":18,"tags":[]}} +{"t":1791086933168,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086933426,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086933488,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":18,"timing":{"extract":49,"core":5,"total":56,"working_set_kb":201948},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086933489,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":18,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086934359,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":19,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086934361,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086934618,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086934680,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":19,"timing":{"extract":49,"core":5,"total":56,"working_set_kb":208228},"entries":[]}} +{"t":1791086934680,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":19,"tags":[]}} +{"t":1791086935531,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":20,"tags":[]}} +{"t":1791086935533,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086935807,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086935872,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":20,"timing":{"extract":52,"core":6,"total":59,"working_set_kb":208444},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086935872,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":20,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086936729,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":21,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086936731,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086936983,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086937042,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":21,"timing":{"extract":49,"core":5,"total":55,"working_set_kb":208520},"entries":[]}} +{"t":1791086937042,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":21,"tags":[]}} +{"t":1791086937886,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":22,"tags":[]}} +{"t":1791086937888,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086938144,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086938223,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":22,"timing":{"extract":62,"core":8,"total":71,"working_set_kb":206956},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086938223,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":22,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086939074,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":23,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086939076,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086939333,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086939408,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":23,"timing":{"extract":47,"core":6,"total":54,"working_set_kb":208952},"entries":[]}} +{"t":1791086939408,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":23,"tags":[]}} +{"t":1791086940291,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":24,"tags":[]}} +{"t":1791086940293,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086940552,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086940613,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":24,"timing":{"extract":48,"core":5,"total":55,"working_set_kb":208916},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086940613,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":24,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086941497,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":25,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086941501,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086941761,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086941819,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":25,"timing":{"extract":49,"core":5,"total":55,"working_set_kb":208860},"entries":[]}} +{"t":1791086941821,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":25,"tags":[]}} +{"t":1791086942649,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":26,"tags":[]}} +{"t":1791086942649,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086942908,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086942976,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":26,"timing":{"extract":51,"core":7,"total":58,"working_set_kb":207796},"entries":[{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":10,"column":9,"start":8,"end":47,"primary":true,"message":"IDisposable local 'draft' is used after it is disposed [resource: disposable]"},{"code":"OWN002","severity":"warning","file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","line":13,"column":13,"start":12,"end":30,"primary":false,"message":"used here after it was released/returned — IDisposable local 'draft' is used after it is disposed [resource: disposable]"}]}} +{"t":1791086942976,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":26,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"},{"code":"OWN002","primary":false,"line":13,"column":13,"length":18,"text":"draft.Submit(now);"}]}} +{"t":1791086943840,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":27,"tags":[{"code":"OWN002","primary":true,"line":10,"column":9,"length":39,"text":"OrderProtocol.WithDraft(order, draft =>"}]}} +{"t":1791086943842,"kind":"edit","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","kind":"DocumentChanged"}} +{"t":1791086944099,"kind":"snapshot","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","documents":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs"],"generated":["D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Order.Protocol.g.cs","D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\obj\\Debug\\net8.0\\generated\\Owen.TypedBuilder.Generator\\Own.TypedBuilder.TypedBuilderGenerator\\Owen.TypedBuilder.Attributes.g.cs"]}} +{"t":1791086944158,"kind":"publish","data":{"key":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\LiveFixture.csproj","version":27,"timing":{"extract":49,"core":5,"total":55,"working_set_kb":208184},"entries":[]}} +{"t":1791086944158,"kind":"tags","data":{"file":"D:\\a\\_temp\\vsfx\\ws\\LiveFixture\\Use.cs","snapshot":27,"tags":[]}} diff --git a/docs/notes/owen-extension-ide-feasibility.md b/docs/notes/owen-extension-ide-feasibility.md index 97c904c3..fb095255 100644 --- a/docs/notes/owen-extension-ide-feasibility.md +++ b/docs/notes/owen-extension-ide-feasibility.md @@ -20,7 +20,11 @@ clang-tidy's integration code is not copied; only the shape is borrowed. ## E1: a generic VSIX host (`IErrorTag` + `ITableDataSource` + a long-lived Owen process) -**Verdict: NOT EXECUTED in this environment.** The session runs on Linux with no Windows +**Update (OX-02): executed and shipped as `Owen.VisualStudio`; see owen-visual-studio-report.md +(GO). The two blockers below were solved by the in-process extractor with an overlay and by +`owen serve`.** + +**Verdict at OX-01: NOT EXECUTED in this environment.** The session runs on Linux with no Windows and no Visual Studio, so a VSIX cannot be built, deployed to an experimental instance or observed. Nothing below is measured. It is the specification of the spike, plus the two blockers already visible from the code. diff --git a/docs/notes/owen-visual-studio-report.md b/docs/notes/owen-visual-studio-report.md index 64f996ba..2cdeae26 100644 --- a/docs/notes/owen-visual-studio-report.md +++ b/docs/notes/owen-visual-studio-report.md @@ -71,10 +71,55 @@ capability semantics. The Rust core is untouched. | M6 | read the unsaved document from disk | K2-unsaved-own002 (and E4) | ### 3.4 Real Visual Studio (P14) — CI job `owen-visual-studio`, Visual Studio Enterprise 2026 18.10 on `windows-2025-vs2026` -VS_RESULTS_PLACEHOLDER +**19/19 on the final product head `fbdc978`** (run 37175674769; evidence kept in +`evidence/ox02-visual-studio/`). The solution is restored and never built. The VSIX is +installed with `VSIXInstaller` into the image's `Exp` hive. The IDE is driven as a user would +drive it: +- edits go through DTE `EditPoint`, never saved; +- the Error List is read through UI Automation; +- navigation is a double-click on the row; +- squiggles come from Owen's own tagger trace and are visible in the screenshots. + +| check | result | +|---|---| +| vsix-loaded | Owen.VisualStudio composed in devenv (its trace: `Owen live analysis loaded`) | +| L0 | Visual Studio's own design-time build wrote `obj/owen/live.txt`; no build ran | +| K1 | first publication: 0 entries; an unsaved clean edit: analysed, 0 OWN entries | +| **K2 squiggle** | `10:9+39 'OrderProtocol.WithDraft(order, draft =>'` (finding) and `13:13+18 'draft.Submit(now);'` (witness, the inserted line), `Use.cs` unsaved | +| **K2 Error List** | `OWN002 \| IDisposable local 'draft' is used after it is disposed [resource: disposable] \| LiveFixture \| Use.cs \| 10` | +| **K2 navigation** | double-click → `Use.cs` line 10 column 9 = the registered coordinate | +| P9 | `CS0029 … Use.cs 10` beside the OWN002 row | +| **K3** | line deleted → no OWN002 tag after 344 ms; the row is gone | +| edit → squiggle | 10/10, p95 **349 ms** (threshold 1000); 328–349 ms with the 250 ms debounce included | +| edit → disappearance | 10/10, p95 **347 ms** (threshold 1000) | +| UI thread | longest Owen handler **3.3 ms** (threshold 50) | +| service in VS | 26 analyses; cold (first) 4.9 s; warm median **58 ms**, p95 76 ms, max 150 ms; working set 169 → 203 MB | +| no-build / never-saved | no `LiveFixture.dll`, no build request; `Use.cs` on disk byte-identical to the fixture | +| no-orphan | `owen serve` did not outlive a killed devenv (job object) | + +The same product code also passed 19/19 on `90bd419` (run 37174835723: p95 399/413 ms, UI +4.8 ms). Run 37174809967 on `7cdf529` (same product code) failed one check, +K2-navigation, with the caret at exactly 10:9 = expected. The driver's path comparison is +now normalised and prints both paths; it passed on `fbdc978`. Not counted as a pass. + +**What the IDE run took** (Amendment 1): the Error List read through UI Automation (DTE +`ErrorItems` returned 0 in VS 2026), the `Exp` hive plus first-run dialog dismissal, a MEF +cache rebuilt after install, and the editor kept in front. One product fix came out of it: +`TagsChanged` now raises on the UI thread and the tagger reports its exceptions. ## 4. Gates kept green -GATES_PLACEHOLDER +Final head, CI run 37175674769: **every job green**, Ubuntu and Windows. That includes: +- lint (ruff + mypy --strict); +- tests on Python 3.11/3.12/3.13 (`run_tests.py`, P-037 pins included); +- rust fmt/clippy/tests; +- the state-protocol gate, the heap-effects gate and the typed-builder gate; +- `owen_extension_gate` (55/55 on both platforms); +- Owen.Cli gate A on both platforms, and the Stage-1 engine controls; +- Rust-default dogfood; +- the new `owen-live` job (Ubuntu + Windows) and `owen-visual-studio`. + +Locally `run_tests.py` shows the same two pre-existing local-only Stage-1 controls as on the +base (Amendment 1). ## 5. Snipper reuse `owen-visual-studio-snipper-reuse.md`: four ADAPTed chunks (VSIX project shape, manifest, the @@ -82,4 +127,25 @@ real-IDE smoke's instance/hive/relaunch shape, link-compiled tests). Nothing cop the LSP spine, PATH fallback, commands and lifecycle are not reused. ## 6. Verdict -VERDICT_PLACEHOLDER +**GO_OWEN_VISUAL_STUDIO_ALPHA.** + +In a real Visual Studio 2026, an unsaved edit goes through the shared Roslyn frontend (the ONE +extractor, in-process, over the editor's text and Roslyn's in-memory generated sources) to the +authoritative Rust core. The result is OWN002: a live squiggle on the inserted +`draft.Submit(now);` (the core's witness step) and at the finding's location, and an Error List +row with exact navigation to the reported location. Deleting the line removes them. No save, +no build. + +For the same saved source, build and live agree on code, severity, message, file and line +(P10, five sources). K1–K8, M1–M6 and the §8 thresholds hold. The VS host and the service +name no extension; Owen.TestProtocol, a second extension with its own generator, works with +no host change. + +Known and registered: +- OWN002 points at the region entry, with the stale use as its witness step (#393, unchanged); +- state-protocol findings have no column (the narrowest coordinate contract applies); +- a never-saved new file is not in the input set; +- live analysis starts after the project's first design-time build; +- proven on VS 2026 18.10 only; VS 2022 17.14 is declared by the manifest but was not run. + +STOP here, per the brief.