SQL Prompt-style extension for SQL Server Management Studio 22
  • C# 97%
  • PowerShell 1.9%
  • JavaScript 1.1%
Find a file
2026-10-26 23:30:44 -04:00
dist Docs: release 0.3.4 cut - what shipped, what is still owed 2026-10-26 23:24:15 -04:00
research SqlCents: SQL Prompt-style extension for SSMS 22 2026-10-17 15:24:55 -04:00
src publish-release.ps1: two Windows PowerShell 5.1 bugs that bit the 0.3.4 cut 2026-10-26 23:28:47 -04:00
tests Copy as IN clause: a results-grid right-click item that works in SSMS 22 2026-10-26 23:21:18 -04:00
tools publish-release.ps1: two Windows PowerShell 5.1 bugs that bit the 0.3.4 cut 2026-10-26 23:28:47 -04:00
.gitignore Tab expands the wildcard at the caret, with a cue popup 2026-10-22 21:25:30 -04:00
Directory.Build.targets SqlCents: SQL Prompt-style extension for SSMS 22 2026-10-17 15:24:55 -04:00
HANDOFF.md Docs: 0.3.4 published - Forgejo release 13, and the two script bugs it exposed 2026-10-26 23:30:44 -04:00
INSTALL.md Duplicate-install detector: say which folder to delete before completion goes silent 2026-10-22 23:30:43 -04:00
install.ps1 Self-update: a Forgejo release feed, an update banner, and installer hand-off 2026-10-22 22:03:33 -04:00
NuGet.config SqlCents: SQL Prompt-style extension for SSMS 22 2026-10-17 15:24:55 -04:00
README.md Copy as IN clause: a results-grid right-click item that works in SSMS 22 2026-10-26 23:21:18 -04:00
SqlCents.slnx SqlCents: SQL Prompt-style extension for SSMS 22 2026-10-17 15:24:55 -04:00

SqlCents

Working state / handoff for new sessions: HANDOFF.md (build state, verified facts, the install loop, how to drive SSMS, remaining work).

A SQL Prompt–style add-in for SQL Server Management Studio 22 (VS 18 shell): metadata-aware T-SQL completion, formatting, casing, refactorings, code analysis, snippets and query history — modelled on Red Gate SQL Prompt 10 (see research/SQL-PROMPT-ANALYSIS.md for the reverse-engineered feature inventory that drives this build).

Layout

src/SqlCents.Core/       netstandard2.0 engine — no VS dependencies, fully unit-tested
  Lexing/                T-SQL lexer + keyword/data-type/global-variable tables
  Analysis/              caret context analyser, object names, CodeAnalysis/ (22 rules)
  Completion/            CompletionEngine — candidates for every context
  Formatting/            SqlFormatter + FormattingStyle/StyleStore (SQL Prompt style-file compatible)
  Casing/                AutoCaser
  Refactoring/           16 refactorings + RefactoringContext helpers
  Snippets/              SnippetStore (SQL Prompt snippet JSON compatible) + expander
  History/               SqlHistoryStore (JSON-lines)
  Options/               SqlCentsOptions + SqlCentsPaths (%LOCALAPPDATA%\SqlCents)
  Update/                UpdateFeed (Forgejo/Gitea releases API) + UpdateState cache + version compare
src/SqlCents/            net48 VSIP package for SSMS 22
  SqlCentsPackage.cs     AsyncPackage, background autoload, tool windows
  Editor/                DTE text ops, commands, MEF completion source, editor monitor
  Services/              SqlClientMetadataCache, ConnectionProvider, composition root
  Services/UpdateService.cs  feed fetch, VSIX download, elevated VSIXInstaller hand-off at shell exit
  Dialogs/               Options dialog (WPF)
tests/SqlCents.Core.Tests/  357 xunit tests over the engine
research/                SQL Prompt + SSMS 22 reverse-engineering evidence (REA)

Build

dotnet build SqlCents.sln -c Release          # engine + package + tests
dotnet test  tests/SqlCents.Core.Tests -c Release

Latest run: 357 / 357 tests pass. Package output: src/SqlCents/bin/Release/net48/SqlCents.vsix (plus SqlCents.dll, SqlCents.Core.dll, Newtonsoft.Json.dll, SqlCents.pkgdef, extension.vsixmanifest).

Install

Shipping it: the VSIX (verified 2026-10-19)

dotnet build src/SqlCents -c Release produces src/SqlCents/bin/Release/net48/SqlCents.vsix (~493 KiB: SqlCents.dll, SqlCents.Core.dll, SqlCents.Core.pdb, Newtonsoft.Json.dll, SqlCents.pkgdef, extension.vsixmanifest, plus manifest.json/catalog.json). Ship that one file as dist\SqlCents-<version>.vsix; the recipient-facing guide is INSTALL.md.

Install with SSMS 22's own installer, elevated and with SSMS closed — an unelevated run exits 1012:

& "C:\Program Files\Microsoft SQL Server Management Studio 22\Release\Common7\IDE\VSIXInstaller.exe" /quiet dist\SqlCents-0.2.0.vsix

It lands per-user at %LOCALAPPDATA%\Microsoft\SSMS\<hive>\Extensions\<8.3-name>\ with catalog.json + manifest.json — exactly the layout the MEF catalog needs, so the installer now delivers what install.ps1 used to hand-build. Uninstall with tools\uninstall-sqlcents.ps1; VSIXInstaller /uninstall reports "not found" (2001) for these legacy installs.

Three build settings are load-bearing for this route — do not remove them:

  • source.extension.vsixmanifest targets only Microsoft.VisualStudio.Ssms [22.0,) with <ProductArchitecture>amd64</ProductArchitecture>, declares no Microsoft.VisualStudio.MPF.16.0 dependency, and carries the Microsoft.VisualStudio.Component.CoreEditor prerequisite. With the previous IntegratedShell [18.0,19.0) + MPF shape the installer aborts with InvalidExtensionManifestException ("Install path does not exist" → "vsixmanifest, version Invalid"); the current shape matches a working commercial SSMS 22 extension's manifest.
  • UseCodebase=true — the generated pkgdef must carry "CodeBase"="$PackageFolder$\SqlCents.dll", or the package registers but the assembly is never resolved (ActivityLog: CreateInstance failed for package [SqlCentsPackage] ... Could not load file or assembly 'SqlCents').
  • Payload curation — IncludeOutputGroupsInVSIX=BuiltProjectOutputGroup on the SqlCents.Core project reference plus SuppressFromVsix keep ScriptDom (7 MB + 12 satellite DLLs) and shell-provided SDK assemblies out of the container, and the IncludeNewtonsoftInVsix target puts Newtonsoft.Json.dll back in, which VSSDK's default suppression list drops.

Developer loop: loose-folder install

install.ps1 (elevated) copies the five payload files into a machine-wide loose folder, touches Extensions\extensions.configurationchanged and mirrors the pkgdef into SSMS 22's own product hive (HKLM\SOFTWARE\Microsoft\SSMS\22.0_<instanceId>). It now expands $PackageFolder$ in the generated pkgdef, because that token is only resolved by the VSIX installer.

Do not ship Microsoft.SqlServer.TransactSql.ScriptDom.dll — SSMS 22 already provides it in Common7\IDE\Extensions\Application\.

Two gotchas found the hard way:

  • The VSIX manifest must declare the assets, or the shell copies the files but never loads the package: <Asset Type="Microsoft.VisualStudio.VsPackage" …/> and <Asset Type="Microsoft.VisualStudio.MefComponent" …/> (see src/SqlCents/source.extension.vsixmanifest).
  • SSMS 22 does not read HKLM\SOFTWARE\Microsoft\VisualStudio\18.0; its hive is SOFTWARE\Microsoft\SSMS\22.0_ecebcc7f on this machine (the installer discovers it).

Verified in SSMS 22 (2026-10-16, SSMS 22.9.12112.369, connected to Azure SQL Test eN3 / En.Enrollment)

Screenshots in dist/: screen16-completion.png, screen20-panel-final.png, screen23-formatted2.png.

Check Result
Package loads sqlcents.log: SqlCents 0.2.0.0 starting in SSMS → services initialised; snippets=12 → package initialised → DTE available
MEF parts catalogued ComponentModelCache\…Default.catalogs lists SqlCents.Editor.SqlCentsCompletionSourceProvider and …SqlCentsEditorMonitorProvider
Editor integration editor monitor attached (buffer.Changed + keyboard preview); completion source created for content type 'SQL' (SSMS 22's T-SQL content type is SQL)
Command panel opens with Ctrl+Alt+Q; all 16 actions present
Refactoring command status: Expand wildcards … nothing to change (ran against the open document)
Formatter status: Format document: 1 edit(s) applied — a one-line select … join … where … order by became 6 clause lines
Completion list a live-connection table list appears in the query window; whether it is SqlCents' MEF set or SSMS' own IntelliSense is not yet proven (see gaps)

Known issues / next fixes

  • Startup tool window hangs SSMS 22. Showing a tool window from InitializeAsync (background load) freezes the shell — with it enabled SSMS goes Not Responding and Windows kills it after ~70 s. Editor.ShowPanelOnStartup therefore defaults to false; the panel opens on demand. Fix: show it after the shell finishes loading.
  • Connection not detected in a connected query window: the DTE Document.Object("ScriptEditorControl") reflection in ConnectionProvider.cs returns nothing on SSMS 22 — switch to the brokered ISqlEditorServiceBrokered.GetCurrentConnectionAsync().
  • Undo grouping fails: undo context unavailable: Object reference not set… — edits apply but are not wrapped in one undo unit.
  • The WPF PreviewKeyDown class handler fires once per UIElement in the route (log spam, repeated ShowToolWindow); set args.Handled = true and guard re-entrancy.
  • No .vsct command table yet: no SSMS menu items, only the two editor shortcuts (Ctrl+Alt+Q panel, Ctrl+Alt+H history).
  • SSMS 22 lowercases keywords in new query windows by itself (built-in T-SQL formatting) — reconcile SqlCents' casing rules with it.

Install gotchas (both cost hours)

  1. extension.vsixmanifest must declare the Microsoft.VisualStudio.VsPackage and Microsoft.VisualStudio.MefComponent assets, or the shell copies the files and never loads the package.
  2. For a loose (folder) extension the shell does not probe the folder for the package assembly: the pkgdef needs an absolute "CodeBase"="…\Extensions\SqlCents\SqlCents.dll" ($InstallPath$ is not expanded here). install.ps1 appends it.
  3. SSMS 22's product hive is HKLM\SOFTWARE\Microsoft\SSMS\22.0_<instanceId>, not SOFTWARE\Microsoft\VisualStudio\18.0.
  4. The load-failure dialog points at %APPDATA%\Microsoft\SSMS\22.0_<instanceId>\ActivityLog.xml (Roaming, not Local) — that file shows exactly what the shell did.

Features

Area Status Notes
Metadata-aware completion verified live completion source created for content type 'SQL' then completion: injected 95 items (context=select list) for select ad and injected 197 items (context=procedure name) for exec u, both keyed to the live connection. SSMS 22 does route Ctrl+Space to the MEF broker, so the monitor's extra binding is unnecessary. Known defect: with an empty prefix after exec the engine reports suppressed: inside string/comment (kind=Unknown) and offers nothing
Accepting a suggestion verified live Enter and Tab now commit the highlighted candidate — answered in the per-view IOleCommandTarget (VSStd2KCmdID.RETURN 0x3 / TAB 0x4), because SSMS 22 answers those keys itself and never routes them to the completion controller, which is why the list previously had to be clicked: autocomp: committed 'Addresses' via Tab, autocomp: committed 'Addresses' via Enter, autocomp: committed 'SELECT' via Enter. Typing a character that cannot belong to the word (space, ., ,, ;, () dismisses the stale list and rebuilds it for the new caret context — …(context=table or view name) items=286 after from , then completion: injected 20 items (context=dbo.) after a dot, and autocomp: no session created at caret 3 where no list belongs — and the live list is re-filtered when the shell does not do it (autocomp: re-filtered the live list — 1 set(s): SqlCents 'SqlCents' items=3). Rules live in SqlCents.Core/Completion/CompletionKeyPolicy.cs (4 tests); the Commit on Enter/Tab/space/dot/… checkboxes on the Suggestions page are now actually honoured (Enter/Tab on by default, the rest off)
Table aliases on completion verified live committing a table/view suggestion in a FROM/JOIN position writes its alias too — SQL Prompt's AutoCompletion.AliasGenerator: FROM Ord + Tab → FROM dbo.Orders o, confirmed in SSMS 22 by the user. Naming defaults to camel humps (TableAliasNamingStrategy = "camel"): elig.ContractMembers → cm, audit.AdminCobraStatusAuditLogs → acsal, sales.vwActiveOrders → vao; the Inserted Code page now has a Table alias naming drop-down for camel humps / first letter / object name, next to Alias table/view names, Capitalise generated aliases and Use AS for generated table aliases. INSERT / UPDATE / DELETE / MERGE / TRUNCATE targets and table-valued functions stay unaliased, and an alias already in the statement makes the generated one grow (FROM dbo.Customers c JOIN Cust → dbo.Customers cu; a hump alias grows to <alias>2 rather than to a name prefix). Side fix: contextual (non-reserved) T-SQL keywords used as names — schema audit, table [user].[Order] — were dropped by the table-source scanner, so such references got no alias from either route; FindTableSources now reads them as names (1 test)
Snippet completion done 12 built-ins, SQL Prompt snippet JSON format, expansion on commit
Formatting verified live, single undo verified token re-emitter, clause breaks, alignment, collapse rules, -- sqlcents:ignore; imports Red Gate style files. Confirmed in SSMS 22: Format document: 1 edit(s) applied and the buffer really is re-flowed. Undo is now one unit: undo: history from the MEF-imported registry → apply: undo unit separated; undo 1 leaves the text intact (snapshot v52, still 6 issues), undo 2 removes the typed text (snapshot v101, 0 issues)
Auto-casing done keyword/identifier/data-type casing rules
Refactorings (19) done expand SELECT * at the caret (Tab — see the wildcard row below; the whole-window variant lost its button), qualify names, brackets, commas, quotes, comment/uncomment, semicolons, casing fix, INSERT→UPDATE (multi-row, PK-aware), alias style, AS aliases, Add table aliases (FROM common.Addresses → FROM common.Addresses a; verified live — panel button Table aliases over a highlighted query), unicode strings, surround-with, unformat, Generate INSERT statement, Generate EXEC with DECLAREs/OUTPUT
Code analysis (22 rules) verified live end to end squiggles: 6 issue(s) published (ct=SQL, snapshot v50) followed by squiggles: GetTags asked for 1 span(s), holding 6 issue(s) — the editor's marker service really does query our tagger and receive the issues. Rules pointing one past the end of the buffer (e.g. "missing semicolon") used to be dropped; they now clamp onto the last character
Inserted-code generators verified live INSERT skeleton on select * from dbo.Addresses now reports 1 edit(s) applied with apply: undo unit separated, and the analysis re-publishes on the new content. Root cause of the earlier nothing to change was the metadata loader, not the generator: it filtered sys.columns on the assumed schema (the table lives in another one — SSMS's own sys.tables list shows bare names), and once rows actually came back it threw Specified cast is not valid because max_length is smallint, precision/scale are tinyint and the is_* flags are bit, which SqlDataReader.GetInt32 rejects. The loader now resolves the schema from the server, reads columns by GetOrdinal name and coerces through Convert → pre-loaded columns for dbo.Addresses … -> 16 column(s)
Query history done execution + edit capture, search window, insert-at-caret, retention prune
Options verified live Panel button Options opens a window titled SqlCents options with the seven pages `Suggestions
Command panel done tool window with every action + connection + open-query-window list
Live connection discovery verified live brokered ISqlEditorServiceBrokered (GetCurrentConnectionAsync / GetOpenEditorsAsync); DTE reflection kept as fallback. Metadata cache reuses SSMS's connection string → metadata cache loaded … (databases=28, objects=532)
SSMS menu items verified live SqlCents now appears in SSMS 22's real Tools menu: dte menu: popup added to Tools (21 items), 21 controls read back, 21 click handlers wired, and UIA confirms Tools gained a SqlCents item (15 → 16). Mechanism: DTE.CommandBars cast to Microsoft.VisualStudio.CommandBars — the shipped interop; EnvDTE.CommandBars does not exist in this shell — then a popup added to the Tools command bar with Click handlers calling the same command delegates
Keyboard chords not working yet the .vsct table compiles and embeds (VSPackage.resources → Menus.ctmenu, 1973 bytes) and the Menus registration is byte-identical to SSMS's own packages, yet no table item renders and no chord fires; the DTE popup supplies no key bindings, so chords still depend on the table being merged
MEF editor composition fixed — and automated SSMS 22 only catalogues an extension for MEF when it lives in the per-user %LOCALAPPDATA%\Microsoft\SSMS\<hive>\Extensions\<folder> with catalog.json + manifest.json and an installer-style short folder name (A/B tested: a folder named SqlCents loads as a package but contributes zero MEF parts), plus the private-registry value ExtensionManager\EnabledExtensions["<vsixId>,0.2.0"] = <folder>\. install.ps1 now does all of it in one elevated run, clears the ExtensionMetadata*.mpack caches and touches extensions.configurationchanged; result: monitor: tagger requested for content type 'SQL', editor monitor attached, squiggles: tagger requested, colouring: view attached
Object-definition at caret verified live MEF quick info is never invoked by SSMS 22's SQL editor (no quickinfo: source created line ever, Ctrl+K Ctrl+I does nothing), so parity comes from a route that works: a panel button Definition at caret and a Tools ▸ SqlCents menu item. Verified: definition: caret object dbo.zz_hint_test → definition: shown for dbo.zz_hint_test (89 chars), with the definition read back through UIA (create procedure dbo.zz_hint_test (@OrderId int, @Note nvarchar(50) = null) as select 1) and shown in the panel; a 5053-char definition truncates at 4000 with a note; a caret inside a comment gives definition: none (no-name). Commands now resolve the right editor even after Execute: ActiveEditorRegistry prefers the SQL view that most recently had aggregate focus, verified by Insert semicolons: 1 edit(s) applied + apply: undo unit separated with focus left in the Results pane
Parameter hints verified live Typing ( fires the trigger (via ITextBuffer.Changed, since the key class handler never fires in this editor) and the hint renders for both built-ins and server objects: paramhint: shown for CONVERT ( data_type [ ( length ) ], expression [ , style ] ) (3 params, arg 0) and paramhint: shown for dbo.zz_hint_test (2 params, arg 0). The server-object case was blocked by the same SqlDataReader cast bug as the column loader (GetInt32 on bit/tinyint columns); the loader no longer assumes the caller's schema either
Wildcard expansion at the caret verified live select * from dbo.Addresses with the caret on or beside the * paints a cue box under the star — Press Tab to expand * / → 16 column(s) of dbo.Addresses — and Tab replaces that one star with its column list. Engine: src/SqlCents.Core/Refactoring/WildcardAtCaret.cs (13 tests) resolves the star the caret touches (caret >= star.Start && caret <= star.End), its qualifier, its FROM/JOIN/CTE/#temp/table-variable source and the column list; src/SqlCents/Editor/WildcardSupport.cs loads the missing columns on demand (EnsureColumnsLoaded, deduped per table) and applies one ITextEdit inside its own UndoBoundary; src/SqlCents/Editor/WildcardHintPopup.cs renders the cue in its own adornment layer SqlCentsWildcardHint (debounced 150 ms, hidden while a completion list is up, Escape dismisses). Tab precedence: highlighted completion commit → wildcard expand → normal indent, and Tab never expands while the SqlCents list is up. Not wildcards, so Tab stays an indent: count(*), OrderId * 2, a star with no resolvable table. Live in SSMS 22: wildcard hint: shown for * → 16 column(s) of dbo.Addresses then wildcard: expanded * into 16 column(s) at offset 7 + apply: undo unit separated, one Ctrl+Z restores select * from dbo.Addresses and Ctrl+Y redoes it; a.* gives wildcard hint: shown for a.* → 16 column(s) of dbo.Addresses → select a.Id, a.Type, … from dbo.Addresses a; the star in select count(*) from dbo.Addresses with the caret right after it produced no cue and no expansion. The old whole-window Expand wildcards panel button is gone (it rewrote every star in the document); Tools ▸ SqlCents ▸ Expand Wildcard at Caret and Ctrl+Alt+W now do the caret-scoped thing, and Options ▸ Editor gained Expand the wildcard at the caret on Tab + Show the "press Tab to expand *" cue
Connection colouring off by default, both forms The user rejected the editor-edge stripe ("i dislike this design entirely"), so EditorOptions.ConnectionColoringEnabled defaults to false and the bars are gone — verified live: the log says colouring: disabled by options and a screen scan finds no vertical run of the stripe colour #4E5E7E. A replacement cue on the document tab (Editor/ConnectionTabColoring.cs, EditorOptions.ConnectionTabColoringEnabled) is implemented but also defaults to false: in SSMS 22 the document tab well sits in no visual tree an in-process extension can reach — Application.Current.Windows holds only dialogs, the shell window's WPF root comes from an HwndSource that yields no tab strip, and the editor view's visual ancestry stops at WpfMultiViewHost (the document pane is a separate presentation source). Both checkboxes remain on Options ▸ Editor for anyone who wants to retry it. The shell's own "Colorize document tabs" (environment.tabs.documentTabs.colorization, methods none / tool-windows / documents-and-tool-windows) stays unused: the shell picks the colour, so it can never mean prod-vs-test
Self-update (release feed) unit-tested + verified live in SSMS 22 src/SqlCents.Core/Update/ parses the repository's Forgejo/Gitea releases API (UpdateFeed, 13 tests: highest-version pick, drafts/pre-releases, v0.3.0 / SqlCents-0.3.0 / 0.3.0-beta1 tags, relative asset URLs, .vsix asset choice, non-JSON feeds) and UpdateState caches the result with a 24 h throttle, a 6 h retry after failure and per-version dismissal (8 tests). Services/UpdateService.cs fetches it 15 s after package load, compares against the running assembly version, stages the VSIX and arms SSMS' own VSIXInstaller.exe for shell exit — idempotently: an in-process flag plus an installer.arm marker naming the SSMS process being waited for, so a second click cannot start a second installer and a second SSMS window cannot arm another (InstallerArm, 8 tests). Live on 2026-10-22: update: 0.3.1 available (running 0.3.0.0) → banner → update: staged … → update: installer armed → update: VSIXInstaller exit 0 → SqlCents 0.3.1.0 starting in SSMS
Copy as IN clause (results grid) verified live in SSMS 22 — right-click menu + clipboard Select cells in an SSMS results grid, right-click → Copy as IN clause, and the clipboard holds IN ( 723500, 723503, 723502, 723501 ) — the SQL Prompt feature, clipboard only, nothing inserted at the caret. Engine: src/SqlCents.Core/Grid/InClause.cs (InClauseBuilder.Build, 17 tests) — numbers stay bare, grouped money (1,234,567.80) and hex (0x1A2F) survive, text is quoted with ' doubling ('O''Brien'), optional N'…', empty cell → NULL, optional de-dup, sort (lexical or numeric) and a value cap; layouts IN ( a, b ) or one value per line. Grid reading: src/SqlCents/ResultsGrid/ResultsGridAccess.cs scans every window in the process for a GridControl/GridResultsGrid, ranks grids by selected-cell area, and reads SelectedCells blocks through IGridStorage.GetCellDataAsString — BlockOfCells.Right/Bottom are inclusive — falling back to GetClipboardTextForCells and then to parsing the Ctrl+C TSV (GridSelectionText, 6 tests). Menu: added at runtime to SSMS' own popup command bar "SQL Results Grid Tab Context" (13 controls: Copy, Copy with Headers, Select All, …, Save Results As…, Page Setup…, Print…) by src/SqlCents/Commands/GridContextMenuBarInstaller.cs; the XMLA grid's bar gets it too. The .vsct route compiles and its key bindings fire, but SSMS 22 renders no foreign command-table placement in any menu and its grid popup is a WPF island (no #32768), so the item is written through the CommandBars object model instead — the failed .vsct context-menu placements, the Ctrl+Alt+I chord and the WPF popup-injection hook have since been removed as dead weight. Fallbacks: panel button Copy as IN clause (grid) and Tools ▸ SqlCents ▸ Copy as IN clause (results grid). Options ▸ Results Grid page carries quoting / N'…' / NULL / de-dup / sort / layout / max values. Live in SSMS 22 on 2026-10-26: the item appears in the grid's right-click menu and the clipboard comes back as IN ( … ) with every selected value — the user's verdict: it works! Log lines: in-clause: names the source used; grid menu bar: lists the bars written to and every results-related bar inspected

Staying up to date (self-update)

SqlCents is not on the Visual Studio Marketplace, so the shell's own extension auto-update never lists it — the extension polls its own release feed instead.

  • Feed: the repository's Forgejo/Gitea releases API, default https://forgejo.vtor.io/api/v1/repos/vidane/sqlcents/releases?limit=20 (Options ▸ Updates). UpdateFeed picks the highest non-draft version, tolerates v0.3.0 / SqlCents-0.3.0 / 0.3.0-beta1 tags, resolves relative asset URLs and prefers the SqlCents-*.vsix asset. Verified against a live Forgejo instance (Codeberg) as well as in UpdateFeedTests.
  • Check: throttled (default every 24 h, 6 h retry after a failure), 15 s after package load, never on the load path — SSMS 22 hangs if anything touches the UI during load. Result cached in %LOCALAPPDATA%\SqlCents\update.json. Log lines: update: 0.3.0 available (running 0.2.0), update: up to date (…), update: HTTP 404 — a private repository answers 404 unless its releases are public or an access token is set.
  • Prompt: a banner in the command panel (SqlCents 0.3.0 is available — this SSMS runs 0.2.0) with Download and install… / Not this version (dismissed releases stay hidden until a newer one appears), plus Tools ▸ SqlCents ▸ Check for Updates… and Install SqlCents Update… (the runtime popup now carries 27 items).
  • Install: the VSIX is staged to %LOCALAPPDATA%\SqlCents\update\SqlCents-<version>.vsix, then a detached helper waits for this SSMS process to exit and runs SSMS's own VSIXInstaller.exe /quiet elevated — the installer refuses a live shell and needs elevation (unelevated exits 1012), and the running shell holds SqlCents.dll open. Its exit code is appended to sqlcents.log (update: VSIXInstaller exit 0 for …).
  • Publishing the release that the feed reads: tools\publish-release.ps1 -Version 0.3.0 bumps SqlCents.csproj, SqlCents.Core.csproj and the VSIX manifest Identity in one step, builds, stages dist\SqlCents-0.3.0.vsix, tags v0.3.0 and (with -Token) creates the Forgejo release with the VSIX attached. install.ps1 now reads its version from the built extension.vsixmanifest instead of hardcoded 0.2.0 literals, so the loose-folder dev install no longer pins EnabledExtensions["<vsixId>,0.2.0"] to an old build.
  • Verified live in SSMS 22 (2026-10-22, through 0.2.0 → 0.3.0 → 0.3.1 → 0.3.2): the banner rendered with both buttons, the download staged, the installer armed, update: VSIXInstaller exit 0, and the next start logged the new version with no banner. The repository is public, so the feed needs no token.
  • Duplicate-install check (InstallLayout + Services/InstallHealth.cs, 18 tests): 8 s after load SqlCents scans every %LOCALAPPDATA%\Microsoft\SSMS\<instance>\Extensions folder for copies of its own VSIX id. Two copies means two installs, and the shell loads both: on 2026-10-22 a triple-armed update wrote two folders, and the third installer kept writing while SSMS rebuilt its MEF catalogue — the package, menu and panel kept working while completion, colouring and the hints went dead with no error anywhere. The scan logs install: … and shows a red panel banner naming the folder to keep, the folders to delete and the catalogue to clear, with Copy cleanup command to put the whole repair on the clipboard.

Milestones

  1. Skeleton VSIX loads in SSMS 22, tool window visible — done
  2. Editor spike: read active T-SQL buffer + apply edits at caret (DTE + undo group) — done, and now through the MEF undo history (undo: history from the MEF-imported registry) so each feature is one undo unit
  3. Completion engine + MEF completion source in SSMS — done and verified live: the provider is created for the editor's real content type (completion source created for content type 'SQL') and injects live-connection items (95 in a select list, 197 for a procedure name)
  4. Formatting / casing / refactorings / analysis / history / snippets — done; formatting and analysis are additionally verified live in SSMS 22, the rest are unit-tested but not each exercised in the editor
  5. Options UI + command panel + live metadata cache — done and verified live (7-page dialog, save round-trip to options.json; metadata cache loaded … (databases=28, objects=532) reusing SSMS's connection string)
  6. Menu + tagger-based UX — mostly done: squiggles, parameter hints and definition-at-caret are verified live; the menu is delivered through DTE.CommandBars (22 items) because SSMS 22 never merges the .vsct table, so keyboard chords still do not fire
  7. Live verification pass, feature by feature, with log evidence recorded per row above — done (this replaced the earlier assumption-driven approach; see HANDOFF.md for the root causes it uncovered)
  8. Distribution — done (2026-10-19): a single VSIX installs and verifies through SSMS 22's own VSIXInstaller.exe (package load, Tools menu, MEF taggers and completion all confirmed from the VSIX-installed copy) — recipient steps in INSTALL.md. Remaining: keyboard chords (command-table merge in SSMS 22), the exec empty-prefix completion defect, per-refactoring live checks, and an MSI / per-SSMS-version manifest only if a managed deployment is ever needed