Release v1.28.0 - #620
Merged
Merged
Release v1.28.0#620
Conversation
SignPath's Pipeline Connector (action v3) reads .signpath/policies files only when the signing policy references them as a Pipeline Policy. This SignPath organization is on the OSS subscription, and its dashboard has no Pipeline Policy setting (checked 2026-09-25), so nothing references the file and SignPath does not check the runner rule. The old comment told readers to add the reference in the dashboard, which is not possible. Comments only. Part of #569. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ujjGZ64tzhVqkEBfaxe4o
…t-enforced Say the SignPath policy file is not enforced
Move all 34 package versions into Directory.Packages.props, preserving existing versions and project-specific metadata. Scope the framework reference entry to SSMS to avoid conflicting with the installer's implicit SDK reference. Update CI cache inputs and deployment triggers for the central file.
A StmtCond keeps its condition's own QueryPlan (and any UDF sub-plans) under <Condition>. The parser fed each child of <Condition> back in as a statement, so the condition became an empty STATEMENT placeholder and its operator tree, hashes, missing indexes and warnings were lost. Parse the StmtCond itself as the statement and read its plan and sub-plans from <Condition> (new optional planContainerEl argument on ParseStatement). ParseStatement read the statement attributes only after the no-QueryPlan return, so a MULTIPLE PLAN statement lost the QueryHash and QueryPlanHash it carries. Read them before that return. Port of erikdarlingdata/PerformanceMonitor#4470. The two golden baselines change because eager_table_spool_plan.sqlplan has a WHILE (SELECT ...) condition that is now a real statement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
…-plan-580 Parse IF-condition query plans and MULTIPLE PLAN hashes (#580)
#577: rule 5 reported a row-estimate mismatch on operators that never executed. Skip ActualExecutions = 0, as rules 11, 12 and 29 already do. #576: rule 6 dropped its scalar-UDF warning whenever the statement's NonParallelPlanReason was one rule 3 explains, even when rule 3 was disabled or gated out. Suppress it only when rule 3's Serial Plan finding is actually on the statement. #579: rules matched hints and keywords in the raw statement text, so string literals and comments counted as code. Add MaskCommentsAndLiterals and use it for rule 27 (OPTIMIZE FOR UNKNOWN) and for the same pattern in rule 3 (MAXDOP 1), rule 20 (RECOMPILE), rule 28 (NOT IN), rule 37 (cursor declaration) and the rule 26 row-goal cause. #578: rule 30 grouped missing-index suggestions by schema and table, so same-named tables in two databases looked like duplicates. Add the database to the key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Review of #582 found rule 38 still matched MAXDOP 2 against the raw text, so a MAXDOP 2 mentioned in a comment suppressed the Standard Edition DOP warning. The app's parameters panel had the same raw OPTIMIZE FOR UNKNOWN check for its annotation. Both now use MaskCommentsAndLiterals, which is internal so the app can call it. Add direct tests for the helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
The rule now runs only when ActualExecutions > 0, so the fallback to 1 can never apply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Each analyzer rule now stamps its number on the findings it adds (PlanWarning.RuleNumber), and TryOverrideSeverity reads that number instead of matching WarningType against a rule-to-name table. The table had no entry for rules 34-37 and 39, for rule 30's Low Impact Index and Duplicate Index Suggestions, or for rule 10's RID Lookup, so overrides for them were silently ignored. The table, its unused reverse map and the static constructor that built it are gone. Engine warnings are still never overridden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Key severity overrides on the rule that emitted the finding (#575)
STRING_SPLIT, OPENJSON, GENERATE_SERIES and every DMV and DMF run as a Table-valued function operator whose Object has no Database and no Schema. A user function always has both. Rule 23's advice (rewrite as an inline function, or stage the rows in a #temp table) is for code the user wrote, so the rule now skips an operator with neither part. The parser records the schema on PlanNode.SchemaName for the check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Skip the engine's own functions in rule 23
CleanTempTableName found an internal temp table name's hex suffix by skipping trailing hex digits. For a name that is all hex after the #, such as #deadbeef1 or a table variable's internal name like #A1B2C3D4, the skip ran to the start and the name came back as a bare #. Ported from PerformanceMonitor b31e5d18: a name with no underscore padding before its hex run is returned unchanged, and the function never returns a bare #. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Keep an all-hex temp table name whole
- HtmlExporter: map warning severity to a fixed CSS class name instead of writing the raw value into class attributes. A null severity no longer throws. - ReproScriptBuilder: split "*/" and "/*" and fold line breaks in every value written into the header comment, so a plan's database name cannot end it. - CliConnectionResolver: --trust-cert keeps encryption Mandatory, matching the direct-login path in ConnectionHelper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
The header line still named SQL Server Performance Monitor, the product this code was ported from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
- Header cases for overlapping delimiters and for VT, FF, NEL, U+2028 and U+2029. - HTML export cases for an attribute payload with no markup and for a warning on an operator. - Comments say why the USE line keeps line breaks in the name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
…ning Harden HTML export, repro script header, and CLI encryption
A .env file in the working directory can pick the server and turn off certificate validation for analyze and query-store. The CLI applied those settings without a word. Now it prints one line on stderr that names the file and the settings it supplied, never their values. A setting that a command-line option overrode is not listed. PasswordResolver asks for the .env password only when neither --password-stdin nor --password gave one, so the list is exact. Its doc comment and --password warning no longer mention a PLANVIEW_PASSWORD environment variable, which the CLI never read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
…characters Review round 1 on #588: - A PLANVIEW_ value with a control character now stops the command with an error that names the key and the file, never the value. The CLI prints the server and database later, and an escape sequence there could erase the notice. The file path and keys in the notice and the error show control characters as '?'. - Both commands merge the file through one EnvFile.Fill method. With no server, analyze runs offline and takes nothing from the file. The file's password is used only with a login, so the notice lists only settings that had an effect. - PasswordResolver.TryResolve takes an optional writer for its messages, so the tests no longer swap Console.Error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Say on stderr which settings came from a .env file
A plan about 450 operators deep overflowed the 1 MB stack of the UI thread and the CLI's main thread before MaxParseDepth (1,000) could refuse it, and the process ended. Parse and ParseAsync now walk the tree on a 32 MB thread and join it, so the depth limit stops a deep plan. Same fix as PerformanceMonitor#4551. The web viewer (WebAssembly) still parses inline. ScopedDescendants and ResultMapper.MapNode use loops with their own stacks, and HtmlExporter keeps its per-node text out of the recursive method, so every step after the parse fits a 1 MB thread at the depth limit. JSON output, limited to about 500 operator levels, now says the plan is too deeply nested instead of "a possible object cycle" (CLI, Robot Advice, MCP, web share). The CLI stops with the parse error when a plan does not parse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Simple and forced parameterization name their parameters @0, @1, ..., and IsValidParameterName required a letter after the @. Those parameters were dropped, so the script ran the statement without declaring them and failed with "Must declare the scalar variable". - IsValidParameterName allows a digit after the @. - The name, type and literal checks use \A...\z instead of ^...$, because $ also matches before a final line break. - IsValidDataType checks the shape of the type (1-3 dot-separated names, then an optional (n), (max), (p,s) or (n,name) suffix) instead of a character list. Same idea as PerformanceMonitor#4567, but it also keeps vector(3,float16), which SQL Server 2025 writes into plans, and it allows only plain spaces, not line breaks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
- get_query_store_top reports a plan the parser refuses in load_error. It returned the plan as loaded, with no warnings. - The web viewer's share button maps only the serializer's JsonException to TooDeepMessage. A reply from the server that isn't JSON gets the usual "Share failed" message again. - HtmlExporter.WriteOperatorNode drops a parameter it never used. - New tests: the HTML exporter at 1,000 levels on a 384 KB thread (the old exporter overflows there), the search's document order and RelOp skipping, the CLI's parse-failure message, and the too-deep messages from the CLI and the MCP tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Parse deep plans on a large-stack thread (#589)
- A batch's plan lists each statement's parameters, so a name can appear more than once. Keeping @0/@1 made that common: each auto-parameterized statement numbers its own, and the script declared @1 twice and failed with Msg 134. A parameter that several statements use (for example a shared @id) had the same problem before #590. - Each name is now declared once. - A name that the statements give different types is left out with a warning. Those statements are usually literal text that doesn't use it. - The type check's numbers and the compiled-value check use [0-9], not \d, which also matches other scripts' digits. (max) takes no second part. - Tests for both multi-statement cases, the digit checks, and the omitted warning for a hostile name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
Review round 2 for #590. A parameter listed by several statements now takes the first compiled value that can go into the script, not the first entry, so a statement compiled without sniffing no longer sets it to ?. When the statements disagree, the script uses the first usable value and says so. When every parameter is left out, the script says to see the warnings instead of claiming the plan cache had none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G625JBNh45iTR1hpT4CxNR
An exchange's elapsed time is mostly spent waiting on the operators that feed it and drain it, so Rule 35 could name a Parallelism operator when the operator beside it was the real cost. Three committed plans already showed it (serially-parallel gave the Sort 17,111 ms and the Repartition Streams below it, which feeds the Sort, the same 17,111 ms), and a live plan from SQL Server 2025 did too: an exchange feeding a spilling Sort was named at 47% of the statement on about 2.4 s of CPU per thread. Rule 35 now skips exchanges, as the text report's "Expensive operators" list already does. The three Parallelism rows come out of the warning baseline; nothing else in it changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
…n on a change The box opened on Local whatever the setting or another grid had chosen, so it could say Local beside times shown in Server mode. It now opens on TimeDisplayHelper.Current. Changing the mode redrew the rows and the slicer but not the wait ribbon, which kept the old mode's labels and tips until a resize. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
…bels Fix three analyzer findings: Rule 9 unused grants, Rule 33 CE guess labels, Rule 35 exchanges
…nnection Server time display reads each connection's own offset (E5)
…verwrites the first window's session (F9) --new-instance skipped the single-instance slot entirely, so a second window restored the running window's saved open-tab list. It opened a copy of every tab, shared the running window's scratch buffer ids (so both windows wrote, dropped and swept the same files), and whichever window closed last overwrote the other's saved list. --new-instance now claims the slot first. If it is free, the launch is an ordinary one. If another instance holds it, this process is a secondary: it restores nothing (a file argument still opens, otherwise the usual new tab), never writes the saved list or a scratch buffer, never sweeps the buffer folder, and keeps the list already on disk when it saves settings. It loses crash recovery for its own tabs only; the unsaved-changes prompts are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
…RestoreOpenPlans is skipped The secondary tests now keep the settings file and buffer folder as the owner left them and compare them at start-up as well as at the end, so the sweep and the clear-and-save that an ordinary start does are caught on their own. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
…nViewer.App/dev/patch-and-minor-0d3a8b6e00 deps: Bump Avalonia and 6 others
The comment said crash recovery only. Its file tabs are not reopened at the next start after a clean close either, as the PR body already says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
Every `uses:` line in .github/workflows now names a 40-character commit SHA, with the exact version tag in a trailing comment. Each pin is the commit that the major tag pointed at, so no action changes version: checkout v7.0.1, setup-dotnet v6.0.0, upload-artifact v7.0.1, deploy-pages v5.0.1, upload-pages-artifact v5.0.0, setup-msbuild v3.0.0, signpath submit-signing-request v3.0, paths-filter v4.0.3 and claude-code-action v1.0.236. .github/dependabot.yml already has a github-actions entry (directory /, target-branch dev, weekly on Monday), so Dependabot keeps proposing updates to the pins and rewrites the version comments. No change there. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
release.yml built PlanViewer.Ssms.vsix and InstallSsmsExtension.exe but uploaded them unsigned. They now go through SignPath, using the "Vsix" artifact configuration in the PerformanceStudio project. New steps, after the App is signed and before the release is created: - Stage the two files at the root of ssms-unsigned/ and upload that folder as the Ssms-unsigned artifact (if-no-files-found: error). - Submit it to SignPath: same organization, project, policy and 1800 s timeout as the App, artifact-configuration-slug Vsix, output in signed/ssms. - If signing succeeded, copy both signed files over the ones in releases/. The release upload, the SSMS Gallery upload and the checksums all read from releases/, so they use the signed files. - If it did not, write a ::warning:: annotation and ship the unsigned files, which is what the release did before. SSMS problems must never block the cross-platform release, so the staging, upload and signing steps are all continue-on-error. The copy step is gated on the signing step's outcome, not its conclusion, and copies both files or neither. SHA256SUMS.txt now also lists PlanViewer.Ssms.vsix and InstallSsmsExtension.exe when they were built. The line format is unchanged. Each SignPath request needs a manual approval, so a release run now waits for two approvals: the App first, then the SSMS files. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
The listener retries until it gets the pipe, so a secondary took it once the owner exited, and later launches handed their files to a window whose tabs are never saved. Without it, such a launch finds no pipe, claims the slot, and runs as the new owner. Also notes that --new-instance acts as an owner where named mutexes fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
…ner save The comment said the write hands the list back unchanged. An owner save between the read and the write is still lost, as the PR body already says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
A second window from --new-instance no longer duplicates or overwrites the first window's session (F9)
- Replace step: if copying a signed file fails, put the unsigned pair back from ssms-unsigned/ and warn, instead of stopping the release. The job stops only if that restore fails, before anything is published. - Upload SSMS extension to release: continue-on-error, so a failed SSMS upload no longer stops the App files from reaching a release that already exists. - Installer: Release builds look for the VSIX only in the argument or next to the exe. The build-folder search is now Debug-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
Pin workflow actions to commit SHAs and sign the SSMS extension
A signed InstallSsmsExtension.exe now installs only a PlanViewer.Ssms.vsix that has the same certificate. The check needs exactly one signature that verifies, the same thumbprint as the installer's Authenticode certificate, and coverage of every part and relationship except the signature's own. An unsigned installer skips the check and prints one line, so dev builds and unsigned releases keep working. The installer copies the VSIX into a new temp folder, checks the copy, installs the copy, then deletes the folder. --verify-only <vsix> runs the same checks, installs nothing and never waits for a key. The release workflow runs it on the signed pair. If it fails, the step puts the unsigned pair back and writes a warning, and the release continues. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
The VSIX check now starts from the raw ZIP entries instead of the parts that OPC lists. It passes only when every entry is one of these, matched by exact name (names that differ only in case are different names): - a part that the signature signs - [Content_Types].xml - a relationship part that the signature covers, or that belongs to the origin part or the signature part - the origin part, when it is signed or empty - the signature part - a certificate part that has the certificate content type, is linked from the signature part, and holds exactly the installer certificate Relationships of the origin part and the signature part may point only to those entries. A second entry with the same name, in any case, fails. No entry is classified by parsing what is in it. The signer is compared with the installer certificate by its full raw data, not by its thumbprint. Only the origin relationship of the package is exempt from relationship cover. Before, every relationship with a digital-signature type was exempt. The project now references System.IO.Compression. The InternalsVisibleTo entry is removed, because the test project it named does not exist. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
The VSIX held the license as an entry named LICENSE, with no extension. Its content type came only from an Override entry in [Content_Types].xml. OpenVsixSignTool rewrites that file without the override. The entry then stopped being a part, stayed unsigned, and the installer check rejected the signed VSIX. An entry named LICENSE.txt gets its content type from the Default entry for the txt extension, and the tool keeps that entry. The Link metadata sets only the folder of a file in the VSIX, not its name, so it cannot rename LICENSE. A VSSDK build with Link set to LICENSE.txt fails with VSSDK1310. The StageVsixLicense target instead copies the LICENSE file of the repo to obj as LICENSE.txt, and the VSIX takes that copy. The LICENSE file of the repo does not change. The manifest License element now names LICENSE.txt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
CheckVsix compared the raw certificate of the signer with the certificate of the installer after VerifySignatures. It now compares first, so a VSIX from another signer is rejected before the expensive verification. The verification uses the same Signer, so an accepted file is still verified with the certificate that was compared. A signature that holds no certificate returns the same reason as before: the signature is not valid (CertificateRequired). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
…ture Check the VSIX signature before a signed SSMS installer installs it
The early abort reason is part of the optimization result, so the App's Runtime Summary now shows it on the row under Optimization with its label indented. CE model moves up above Optimization, so Optimization and its reason end the list, as in the issue's mockup. The web Runtime card and the HTML export follow the same #215 E11 order, so CE Model moves above Optimization there too. They have no Early abort row, so there is nothing to nest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tS6P95Dtzs4aeMpb1xqzX
The label printed both row counts N0 but took the percentage from the unrounded values, so a Key Lookup that ran 117 times for 1 row read "1 of 1 (89%)". PlanRowAccuracy, ported from PerformanceMonitor's PlanRowAccuracy (#4684), adds the fewest decimals (up to 4, fixed-point) at which the printed numbers give the printed percentage, and never prints a non-zero value as 0. That label now reads "1 of 1.128 (89%)". The App and Web node labels and the HTML export all use it, so all three print the same numbers, and PerformanceMonitor prints the same string for the same plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tS6P95Dtzs4aeMpb1xqzX
Resolves the conflicts with dev's 2026-09-28 package bumps. Directory.Packages.props takes dev's versions: Avalonia, Avalonia.Desktop, Avalonia.Fonts.Inter, Avalonia.Headless and Avalonia.Themes.Fluent 12.1.3, Microsoft.SqlServer.TransactSql.ScriptDom 180.117.0 and coverlet.collector 10.1.0. It also adds Microsoft.AspNetCore.Mvc.Testing 10.0.12, which dev added to the test project. Every version pinned on dev now has the same central version, and no PackageReference keeps a Version attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tS6P95Dtzs4aeMpb1xqzX
build(deps): centralize NuGet package versions
Nest Early abort under Optimization and list CE model above it
Make the node row label agree with its percentage
Bumps all four version carriers together: Directory.Build.props, the SSMS vsixmanifest, the SSMS AssemblyInfo (both attributes), and CITATION.cff (version and date-released). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tS6P95Dtzs4aeMpb1xqzX
Bump version to 1.28.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR releases v1.28.0. It brings 31 PRs from
devtomain: 30 merged since v1.27.0, plus the version bump in #619.Before you merge, read these points:
devcommits never reached the history ofmain.deploy-planshare.ymlships the PlanShare server changes from Harden PlanShare: storage limit, daily upload budget, one client key #603 to stats.erikdarling.com.deploy-web.ymlpublishes the web viewer to plans.erikdarling.com.Changes
Plan analysis and display:
Hardening:
App behavior:
CLI and web viewer:
Build and release:
Test Plan
release.ymlruns. The VSSDK targets came from theMicrosoft.VSSDK.BuildToolspackage, as they do on the runner.LICENSE.txt(b22fea5). Its manifest namesLICENSE.txttoo.build-and-testandcheck-versionpass on this PR.deploy-planshareanddeploy-webpass, and https://stats.erikdarling.com/api/stats answers.Generated with Claude Code
https://claude.ai/code/session_019tS6P95Dtzs4aeMpb1xqzX