Refactor/project layout - #66
Open
lsoft wants to merge 19 commits into
Open
Conversation
FreeAIr.csproj listed all 279 source files by hand, which silently drops a file that nobody remembers to add. It is SDK-style now and globs its sources: 660 lines down to ~120. Two assemblies come out of it: - FreeAIr.Shared gains CallAwaiter, NonDisposableSemaphoreSlim, TempFile and ActivityLogHelper, none of which had any reason to sit in the VSIX; - FreeAIr.Voice takes the whole Record/ folder and the recording options page, and with them six packages (NAudio, Whisper.net and its two runtimes, System.Speech, Windows.SDK.Contracts) plus the whisper runtime zipping, which is now a proper AfterTargets="Build" target instead of a PostBuildEvent that never saw $(TargetDir). CLAUDE.md records the two traps this hit: MSBuild-only builds, and F5 being driven by AdditionalArguments now that dropping the project flavor handed the Debug page to Visual Studio's own extensibility project system. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
InfoBarService.ShowInfoBar returned silently when the main window had no info bar host, which is exactly the state devenv is in while the start window is up - so on a machine that opens no solution the "new version installed" and first-run wizard bars were simply dropped. Waiting for ShellInitializedContext does not help either: the shell reports itself initialized long before the main window exists. It now retries once a second for a minute and gives up with a log line rather than in silence. The two callers log what they decided, which is what made this findable in the activity log at all. Dismissing the first-run bar also records FreeAIrLastVersion now. That bar links to the release notes itself, so without it a brand new install got the plain "new version installed" bar on its very next start. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…onEdit The parsed-prompt model (IParsedPart, Parsed, the answer parts, SelectedIdentifier) has no WPF in it and is used by Chat, Helper, Git, Commands and NLOutline, yet it lived under UI/Embedillo/. The files move to Chat/Context/Parser/; the namespace deliberately stays as it was, so this commit changes no using directive anywhere. IParser was the real coupling: it published its mentions as MentionVisualLineGenerator, an AvalonEdit VisualLineElementGenerator, so the whole ANTLR pipeline dragged in the editor. The parsers only ever used two members of it, which are now IMentionRecognizer. The generator implements that interface without a line of its body changing, IParser.Generators is gone, and the chat control passes the generators to the editor itself - the parser has no business handing out editor objects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Most of what looked like a UI dependency was a misfiled type. BackgroundTask sat inside WaitForTaskWindow.xaml.cs and DifferenceShower under UI/Difference, yet neither has a line of WPF in it - the first is a cancellable task with a status event, the second drives IVsDifferenceService. Both move out; the window keeps only the window. GitWindowModifier makes the opposite trip: it is pure WPF and lived in Git/. What genuinely needed a seam now has one in Interaction/, implemented in the UI assembly and resolved through MEF the way ChatContainer already was: - IBackgroundTaskShower puts up the wait dialog, so the git diff collector no longer constructs a Window; - IUserChooser asks for a support action and an agent, hiding the Visual Studio context menus behind the two questions every command starts with; - IGitCommitMessageBox is the Git Changes commit box as far as the commit message builder cares, implemented by GitWindowModifier; - IChatWindowShower shows a chat wherever chats go; - IChatStatusIndicator takes the aggregate chat status, and ChatsStatusEnum moves with it, so ChatContainer no longer imports the status bar control. UIInformer and GitWindowModifier now carry two exports each and are marked Shared explicitly - both must stay singletons, since the package initializes the one instance the rest of the extension reports into. Git/GitNaturalLanguageOutliner still reaches for NaturalLanguageOutlinesViewModel. That is a real dependency on the NLO feature rather than a presentation seam, and belongs to the wave which extracts it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NLO is one feature spread over five folders: the outline tree builder, the
embedding index, the natural language search, four view models and four tool
windows, none of which anything else uses. Nothing but the paths changes here
- the namespaces stay where they are, so no using is touched and the diff is
renames only.
NLOutline/ -> Nlo/NLOutline/
Embedding/ -> Nlo/Embedding/
Find/ -> Nlo/Find/
UI/ViewModels/*NaturalLanguage*, *Rag*, OutlineEmbeddingOutputPanel
-> Nlo/ViewModels/
UI/ToolWindows/ same four -> Nlo/ToolWindows/
Search/ is not part of it and never was: it is the Google scraper behind the
WebSearch MCP tool, and a folder of that name next to the FreeAIr.Search
project - which holds the outline index and the RAG ranking - is a trap. It
becomes WebSearch/, after the tool that uses it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The one edge left over from the previous wave was Git/GitNaturalLanguageOutliner reaching into NaturalLanguageOutlinesViewModel to open the outlines panel over the pending diff. It now goes through INaturalLanguageOutlinesPanel, the single export Nlo/ publishes to the rest of the extension - implemented in Nlo/Interaction/ next to the view model it forwards to. The other direction had three edges left, all of them pickers or a dialog: - DoSearch asked the context menus directly for the scope, the support action and both agents. IUserChooser already answered two of those questions and now answers the other two: ChooseAnyAgentAsync, the picker without the has-a-token filter that the embedding agent needs, and ChooseOneOfAsync<T> for a plain list of labelled values - the search scope, for one. - RagCalibrationViewModel used the same agent picker; same treatment. - BuildNaturalLanguageOutlinesJsonFileToolViewModel constructed WaitForTaskWindow itself and now uses IBackgroundTaskShower like the git commands do. What is left pointing from Nlo/ at UI/ is UI/NestedCheckBox, a reusable control which is equally used by the tools and the control center windows and belongs wherever the shared WPF controls end up; everything else is a namespace which no longer matches its folder (UI.ViewModels and UI.ToolWindows are the feature's own now, UI.Windows is BackgroundTask in BLogic/, UI.Embedillo.Answer.Parser is in Chat/Context/Parser/). A separate FreeAIr.Nlo assembly is still not possible: the view models need Resources, Chat, Options2 and Helper, all of which are in the VSIX assembly, so the reference would be circular. Nlo/ is the shape it will have when they are not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tool scraped google.com through a headless Chromium: it typed the query character by character, walked the result pages and parsed titles out of class names like `LC20lb MBeuO DKV0Md`. That is not something which keeps working, and the two packages behind it were the price - PuppeteerSharp, which also downloads a Chromium build on the first call, and HtmlAgilityPack. Both were referenced by nothing else, so they go too, along with NaturalLanguageHelper whose only caller was the scraper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Almost every feature folder of the VSIX reaches for Resources.Resources, so while the .resx files sat inside FreeAIr.dll nothing could be pulled out of it without referencing the VSIX back. They are now the leaf assembly everything else may depend on, which is what unblocks the next extraction. The manifest name stays FreeAIr.Resources.Resources - RootNamespace plus a .resx at the project root reproduces it exactly - so the generated ResourceManager and the satellites are unchanged. Only the three .resx files and Resources1.Designer.cs moved; the icons stay in FreeAIr\Resources\, where the pack URIs and VSCommandTable.vsct expect them. Two things this uncovered: - XAML reads `clr-namespace:` as the current assembly, so all 17 files naming the strings needed `;assembly=FreeAIr.Resources` appended. The C# compiled fine and the markup compiler failed with MC3050. - A referenced project's satellites reach the VSIX packer twice, once with the culture folder and once without, and the flattened copy shadows every culture but the first in the root of the package. RemoveFlattenedSatellites drops it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Thirty-seven places wrote the same line to resolve an exported service:
(IComponentModel)await FreeAIrPackage.Instance.GetServiceAsync(typeof(SComponentModel))
which tied the natural language outlines, the git commands and the MCP tools
to the package class for no reason beyond that being where somebody first
wrote it. The global async service provider hands out the same
SComponentModel, so MefHelper in Shared does it and belongs to no feature.
Nlo\ no longer names FreeAIrPackage at all, which was the first of the edges
standing between it and an assembly of its own. The two stragglers asking the
package for EnvDTE.DTE now use the same provider.
Shared went SDK-style on the way: MefHelper had to be added to a hand-written
<Compile Include> list otherwise, which is exactly the trap that project was
documented for. CodeLens is now the only legacy-format project left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four moves, each of them a file sitting somewhere it does not belong: - UI\NestedCheckBox and the converter its XAML needs go to WpfHelpers. It is a general tree-of-checkboxes control, used by the outlines panel, the available tools list and the control center alike, and nothing about it is FreeAIr's. - BLogic\BackgroundTask goes to Shared. It has no UI in it - the wait dialog is one possible presenter - and git, MCP and the outlines all derive from it, so keeping it in the VSIX pinned them there too. - Helper\EnumHelper and Helper\TextDescriptorHelper go to Chat\. One formats a ChatStatusEnum for display, the other builds a SelectedTextDescriptor; both are chat code that happened to be filed under Helper. - SelectedSpan leaves the parser file for Shared. It is a start offset and a length; SolutionHelper needs it, and that was the last thing tying Helper to Chat. What remains of the file is SelectedIdentifier, and it is now named so. Helper\ has no dependency on any other folder left. Nlo\ is down from eight folders to five - Chat, Options2, Helper, Git and the Interaction seams. Helper\ResourceHelper.cs is deleted rather than moved: it looked up a resource by name in the configured answer culture and nothing has ever called it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Options2 depended on six other folders. A model of what is configured has no business knowing the runtime that acts on it, and while it did, nothing built on top of it could be moved anywhere. Five of the six are gone: - DataPieceCache moves into Options2\. It caches the parsed options file and FreeAIrOptions was its only caller; it was never general BLogic. - ApplyMcpServerNodeAsync moves to McpServerProxyApplication as ApplyServerNodeAsync. It starts the configured servers, collects the ones that failed and puts up a message box about them - none of which is settings code, and it sat right next to the UpdateExternalServersAsync it wraps. - SupportContext.WithContextItemAsync took a list of SolutionItemChatContextItem to read one file path off each, and WithErrorInformationAsync took a BuildResultInformation to read four fields off it. Both now take what they actually use, which drops Chat and BuildErrors. - InternalPage and MCPToolsExecutionStatus move into Options2\. InternalPage is where the serialized settings are stored - it is FreeAIr's persisted internal state and only incidentally a (Browsable(false)) options page. Options\ is left with the three pages a user can actually see. What Options2 still names outside itself is Helper.UriHelper, and Helper has no dependencies of its own left, so that one points the right way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OpenAI.dll only surfaces the HTTP status; the JSON body is what names the rejected parameter. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
CallToolRequest carried its arguments as a Dictionary<string, object?>, and the channel to the proxy is StreamJsonRpc with its default Newtonsoft formatter, so everything that was not a primitive arrived as a JObject or a JArray. Handing those to System.Text.Json - the serializer the MCP SDK builds tools/call with - writes out the token's children rather than its value, because every JToken implements IEnumerable<JToken>. Nested objects and arrays therefore reached the MCP server as nested empty arrays, which is why only tools taking flat scalar arguments appeared to work. Closes #70. The arguments now cross as raw JSON text, the way the tool schema already travels back in GetToolReply.Parameters, and are revived as JsonElement on the proxy side. IServer and BaseServer take that type through to the SDK, so nothing weakly typed can be passed down that path again. JsonElementDeserializer moves to MCP/Dto next to the conversion it feeds; its namespace is unchanged. The new FreeAIr.Mcp.Tests covers both halves against live components - a real JsonRpc pair and a real MCP server on an in-process duplex stream - including a test that asserts a weakly typed member still loses its shape on the channel, so the reason for the design cannot quietly stop being true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
No description provided.