Skip to content

feat: 支持按 BootstrapBlazor 版本钉住文档与多版本并存查询 - #4

Open
momijijin wants to merge 24 commits into
BootstrapBlazor:masterfrom
momijijin:feat/multi-version-pin
Open

feat: 支持按 BootstrapBlazor 版本钉住文档与多版本并存查询#4
momijijin wants to merge 24 commits into
BootstrapBlazor:masterfrom
momijijin:feat/multi-version-pin

Conversation

@momijijin

@momijijin momijijin commented Aug 17, 2026

Copy link
Copy Markdown

Fixes #3

为 MCPServer 增加「版本钉住 + 多版本文档」能力:

  • GitSync.PinRef 钉住任意 BootstrapBlazor 版本(tag/分支),提取产物按版本槽并存于 OutputDir/{slot}/,夜间任务围绕钉住版本工作(tag 幂等跳过 / 分支跟随上游)
  • 4 个既有 MCP 工具新增可选 version 参数(缺省=钉住版本,10.8.1/10.8.1.0/v10.8.1 等价归一);新增 GetAvailableVersions / PinVersion / ExtractVersion 三个工具与 /api/versions REST 端点(变更端点带 Cookie 鉴权)
  • 组件参数表改经 MetadataLoadContext 从被提取仓库的构建产物加载,与服务自身 UI 包版本解耦
  • 每槽独立 git worktree 提取(并行、即用即删),提取失败写入 manifest(含原因)而非静默吞掉
  • 未配置 PinRef 时行为与现有版本一致,旧客户端零迁移
  • 修复提取链路两个潜在缺陷:样例 md 嵌套子目录未创建导致提取中断(原先被整体 catch 吞掉)、反射上下文未释放导致的产物文件锁
  • 新增 xUnit 测试项目(92 个用例,全程离线),README 双语补充用法

Summary by Sourcery

Add version-pinned, multi-slot BootstrapBlazor documentation extraction and querying with supporting admin UI, REST APIs, and tests.

New Features:

  • Introduce a version-slot extraction pipeline that pins documentation to specific BootstrapBlazor refs and supports multiple coexisting versions.
  • Expose new MCP tools and REST endpoints to list, pin, extract, and query documentation by version, including optional sectioned component docs.
  • Add Blazor admin UI for viewing, re-extracting, deleting, and creating documentation version slots, with localized status messaging.

Bug Fixes:

  • Fix sample extraction failures caused by missing nested sample directories and ensure reflection contexts are disposed to avoid DLL file locks.
  • Ensure Git branch tip resolution and worktree lifecycle behave correctly across repeated extractions, avoiding stale commits and reference errors.
  • Resolve MCP tool binding issues so optional arguments (like version) remain backward compatible with existing clients.

Enhancements:

  • Decouple component metadata loading from the server’s own BootstrapBlazor package by using MetadataLoadContext over extracted build artifacts.
  • Improve component-not-found and legacy-mode diagnostics with version-aware notices and cross-slot existence hints.
  • Harden configuration handling, slot-name validation, and error surfacing across extraction, pinning, and querying flows.

Build:

  • Introduce IGitClient and IProcessRunner abstractions and register them for dependency injection to support the new extraction pipeline.

Documentation:

  • Extend English and Chinese READMEs with detailed documentation on version pinning, version slots, new tools/endpoints, and the GetComponentDocs section parameter.

Tests:

  • Add a dedicated xUnit test project covering the extraction pipeline, git client behavior, MCP tool binding, version resolution, MetadataLoadContext loader, and end-to-end version pinning.

- VersionSlot.Normalize:refs 前缀剥离、v 前缀与四段截三段归一、分支名小写
- SlotManifest record + JSON 读写;10.8.1/10.8.1.0/v10.8.1 等价命中 v10.8.1
- 新增 xunit 测试项目(用户授权 dotnet new xunit);slnx 注册测试项目
- 主项目 csproj 加 Compile Remove 排除嵌套测试目录(否则测试源被 glob 进主项目致编译失败)
- brief 两处 SDK 层最小修正(断言与行为语义不变):TrimStart(string) 无该重载,改用 StripPrefix 前缀剥离;LoadManifest 先 ReadAllText 再反序列化(不能把路径传给 Deserialize(string))
- 收编既往中止遗留的脚手架(测试 csproj/VersionSlotTests/slnx 注册,与 brief 逐字一致)
- 验证:dotnet test BootstrapBlazor.McpServer.Test/...(全量及 --filter VersionSlot)14/14 全绿

Co-Authored-By: ZCode (GLM-5.3)
- AppSettingsModel 新增 PinRef 属性(缺省空串 = 遗产模式,行为不变)
- LoadSettings 的 JsonNode 解析抽为 internal static ParseJson(string),供测试直测模型绑定逻辑(settings 路径绑定 exe 目录、测试无法换路径)
- LoadSettings/SaveSettings 补 GitSync.PinRef 读写,data/config.json 持久化
- appsettings.json 示例 GitSync 节同步加 "PinRef": ""
- 主 csproj 新增独立 ItemGroup:InternalsVisibleTo BootstrapBlazor.McpServer.Test(internal 成员测试依赖,零包改动)
- 验证:dotnet test --filter AppSettingsPinRef 2/2 绿(RED 前态 CS0117 编译失败已确认);全量 16/16 绿;dotnet build BootstrapBlazor.McpServer.csproj 0 错误

Co-Authored-By: ZCode (GLM-5.3)
- Services/GitClient.cs:IGitClient 六成员(IsValid/Clone/Fetch/RefExists/Checkout/HeadCommit)+ LibGit2Sharp 真实现;Fetch 用 FetchOptions{TagFetchMode=All, Prune=true} + 空 refspecs 实现 git fetch --tags --prune 等价(brief 的 "--tags" 是 CLI 旗标非合法 refspec);Checkout 用 CheckoutModifiers 枚举(brief 的 Force=true 为笔误,0.31 无该布尔属性)
- Services/ProcessRunner.cs:IProcessRunner.Run 同步执行外部进程,stdout/stderr 逐行回调 onOutput,返回退出码;Process 用法沿用 GitSyncInvocable(RedirectOutput+CreateNoWindow)
- 测试 GitClientTests:LibGit2Sharp 0.31 bare 仓库 Commit 抛 BareRepositoryException,按 brief 弹性点改为非 bare 源仓库构造,断言项不变(IsValid 假/真、RefExists 存在/不存在 tag、Checkout 成功、HeadCommit 长度 7),并附 Fetch 拉新 tag 验证;全程本地临时仓库离线运行
- 测试脚手架:git 会将 loose object 写为只读文件,清理临时目录前先去只读属性再删(5 连跑 0 残留),best-effort 不影响断言
- 验证:dotnet test 17/17 通过(16 存量 + 1 新增);dotnet build 主项目 Build succeeded(仅存量 NU1903 传递依赖告警,零包改动)
- 接口为普通实例方法,未挡 T4 追加 TipOf 的道(YAGNI,T3 不预埋)

Co-Authored-By: ZCode (GLM-5.3)
- 二审 Important:克隆后 HEAD 本就指向 v10.8.1 的 commit,仅检 v10.8.1 时 Checkout 为 no-op/检错 ref/force 映射反转均无法暴露;现于 Fetch 段(源仓库已有 v10.8.2)追加 git.Checkout(work, "v10.8.2", force: true),并断言 HeadCommit 等于源仓库 v10.8.2 tag Target 的短 SHA——覆盖「tag 检出 detached + HEAD 真实移动 + force 路径」
- 变异自证:临时注释该 Checkout 行后测试即红(Expected cb7bc79 / Actual 0b2537a),断言确能抓 no-op
- brief 六项断言原样保留;其余 4 条 Minor 按裁定暂缓未动
- 验证:--filter GitClient 1/1 通过;全量 17/17 通过;主项目 Build succeeded;%TEMP% 零残留

Co-Authored-By: ZCode (GLM-5.3)
…斥)+ GitSync 遗产分支分派 (T4)

- 新增 Services/ExtractionService.cs:RunAsync 互斥内跑完整流水线
  (clone-or-fetch → RefExists 二次检查 → Checkout → dotnet build →
  Extract 到 .staging-{slot} → 同卷原子 Move 为槽目录 → manifest ready);
  build 失败仅记 failed manifest,槽内旧产物不动
- 跳过语义(nightly 幂等):tag 槽 ready 即跳过(零 git 调用);分支槽
  fetch 后经 IGitClient.TipOf(workDir, ref) 取分支/tag tip 与 manifest
  commit 比对,未变则跳过(注记①:checkout 前 HEAD 不代表目标 ref)
- StartExtraction fire-and-forget(对外 MCP/REST 唯一入口,不挂调用方);
  PinAndExtract 保存 PinRef 后启动提取(T6/T7 共享);DeleteSlot 钉住槽
  拒删(T8 用);GetAvailableVersions 遍历含 manifest 的槽目录出摘要
- IGitClient/GitClient 追加 TipOf:Branches/Tags(附注 tag 经 PeeledTarget
  剥壳)取 7 位短 SHA,缺失返回 null,与 HeadCommit/manifest.Commit 同格式
- GitSyncInvocable:Invoke 开头加 PinRef 非空分派(早返回等价 if/else),
  遗产 clone-or-pull→build→Extract 分支体逐字未动(spec §6.2 零回归);
  构造追加注入 ExtractionService;Coravel 注册与 ForceSync 调用点不动
- 台账折入项:manifest 损坏按缺失处理(TryLoadManifest try/catch);槽名
  path-safety 守卫(纯点段/含分隔符直接返回错误不抛异常);状态字面量
  常量化(internal SlotStatus),新增生产代码无裸状态字面量
- 测试(ExtractionServiceTests,全 fake + 真实临时目录,离线)六剧本:
  A tag-ready 跳过零 git 调用;B 分支 fetch 后 TipOf 同 commit 跳过;
  C force 全流程 + staging 原子替换 + manifest ready;D build 失败保旧
  产物记 failed;E 互斥第二次立即 in progress;F ref 不存在先 Fetch 后
  报 not found;另含守卫/损坏 manifest/列表/删槽/PinAndExtract/GitSync
  分派共 13 个用例
- 验证:dotnet test 30/30 passed;dotnet build 0 error(NU1903 为预存
  传递依赖告警,与本次无关)

Co-Authored-By: ZCode (GLM-5.3)
二审 Important 两项修复:
- Imp-1 分支槽「跟随上游」在真实 git 语义下结构性失效:Commands.Fetch 空
  refspec 只推进 refs/remotes/origin/* 不动本地分支,Branches 短名解析本地
  优先且远端回退缺 origin/ 前缀,Checkout 按名只检本地 tip 不 fast-forward
  ——默认分支槽恒「up to date」谎报静默过期,非默认分支槽恒「not found」。
  GitClient 改 origin 优先:RefExists 补认 origin/{name};TipOf 分支先取
  Branches["origin/"+name]?.Tip 再回退本地(tag 路径不动,PeeledTarget 剥壳
  保留);Checkout 分支 detached 检出 origin tip 的 commit SHA(tag 按名不变)
- Imp-2 StartExtraction fire-and-forget 完全吞故障:RunPipeline 未捕获
  Clone/Fetch 网络失败、extract 抛出、目录替换/manifest IO 失败等,成为
  unobserved task exception 零日志零痕迹。Task.Run lambda 内包 try/catch →
  LogError + 尽力补写 failed manifest(内层再套 catch 防二次抛;崩溃点未知
  故 Commit 记 null,LastError 记异常消息)
- 顺带:Directory.Move 处「同卷原子替换」注释改为如实描述(删旧目录+改名
  两步,窗口内槽缺失由下轮重提自愈)

新增测试(真实 git 离线,仿 T3 本地源仓库手法):
- GitClientTests.BranchSemantics_OriginFirst_FollowsUpstreamAfterFetch:源仓库
  建 dev 分支+默认分支前进 → clone 后本地 dev 不存在但 RefExists/TipOf 经
  origin/dev 可见;Fetch 后 TipOf 返回默认分支新 commit、Checkout 后
  HeadCommit 为新 commit
- ExtractionServiceTests.BranchSlot_StaleReadyManifest_ReextractsAfterUpstreamAdvance:
  旧 ready manifest(克隆时 commit)+ 上游前进 → RunAsync 非 force 不谎报
  up to date、重提取落新 commit
- ExtractionServiceTests.StartExtraction_PipelineCrash_WritesFailedManifest_DoesNotThrow:
  extract 回调抛异常 → failed manifest 落盘(LastError 含异常消息)

验证:RED 三测分别失败于 RefExists=false / Skipped=true / manifest 停
extracting(预期原因);修复后 filter(GitClient|ExtractionService)17/17、
全量 33/33 passed;主项目 build 0 error(NU1903 预存)

Co-Authored-By: ZCode (GLM-5.3)
- 新增 Services/MlcAssemblyLoader.cs:MetadataLoadContext 元数据专用 ALC 加载目标目录
  BootstrapBlazor.dll,与进程内主程序包版本彻底隔离(多版本钉住坑①「主程序包版本污染
  查询口径」的根治)
- 三部分 resolver:① 目标目录(含其 NuGet 依赖)② AspNetCore.App 共享框架最新版本目录
  (IComponent/ParameterAttribute 所在;按 Version.TryParse 语义排序取最大——字典序会把
  10.0.10 排在 10.0.9 前)③ NETCore 运行时目录(RuntimeEnvironment.GetRuntimeDirectory)
- brief 笔误修正:路径字典改用 TryAdd 落实「先目标目录后框架、后加入者不覆盖」——原代码块
  索引器赋值会令框架同名 DLL 覆盖目标目录依赖,重新引入版本污染
- DocsExtractorService.Extract 反射块:MLC 优先,回退链保留(MLC → 进程内已加载 →
  LoadFrom),目标 DLL 缺失时旧场景零回归;Extract 签名与输出结构不变(T9 夹具测试消费)
- IComponent/ParameterAttribute 两处改为经 assembly 所在上下文按全名解析:IComponent 由
  组件类型接口链反查(框架类型不在 BootstrapBlazor.dll 自身);ParameterAttribute 统一走
  CustomAttributeData 按全名比对(MLC 不支持实例化 Attribute);in-proc 分支解析结果即
  typeof(...)(运行时 Type 实例规范化唯一),原语义不变
- 主项目加包 System.Reflection.MetadataLoadContext 10.0.11(用户已授权的唯一包变更)
- 测试:新增 MlcAssemblyLoaderTests 2 例(目标 bin 目录真实 BootstrapBlazor.dll 的
  组件解析 + 缺目录返回 null);全量 dotnet test 35/35 通过,dotnet build 0 错误

Co-Authored-By: ZCode (GLM-5.3)
…on/ExtractVersion (T6)

- Services/McpService.cs:新增 internal static 纯函数 ResolveDocsRoot(settings, version, out notice),规则按优先级:version 非空→归一化→命中槽→槽;PinRef 空→根目录+legacy notice;未命中→null+可用槽清单;version 空→钉住槽存在→槽,否则根目录(兼容未提取,notice=null)
- 4 个既有查询工具(GetComponentList/SearchComponentKeyword/GetComponentDocs/AskComponentExpert)args 增可选 string? Version 并改走 ResolveDocsRoot;不传 Version 且遗产模式下解析结果与返回文案与旧版一致;notice 非空时在返回文案前附一行前缀;LoadComponentDocs 首参由 settings 改为 root 路径;XML doc 补「建议传入当前项目实际引用的 BootstrapBlazor 版本(如 10.8.1)」提示
- GetComponentList 换签名为 GetComponentList(GetComponentListArgs{Version}),Program.cs 对应 REST 调用点同步传空 args(行为不变,编译必需)
- 新增 3 工具:GetAvailableVersions()(槽台账列表+legacy 附注)、PinVersion(PinVersionArgs{Ref})(PinAndExtract:保存 PinRef+fire-and-forget 提取,立即返回)、ExtractVersion(ExtractVersionArgs{Ref})(StartExtraction force:true,立即返回);PinVersion/ExtractVersion XML doc 首句标【状态变更操作】;绝不 await RunAsync,分钟级提取不挂 MCP 调用
- 构造签名追加 ExtractionService 参数(DI 自动满足,注册由 T7 落地);Services/ExtractionService.cs 仅把 IsUnsafeSlotName private→internal 供 ResolveDocsRoot 复用同一路径穿越守卫(台账折入项单一来源)
- brief 笔误级修正:PinVersion 中 SlotName 结果未使用的局部变量收敛为纯校验调用(空 ref 仍抛 ArgumentException,语义不变);测试断言 notice 前补 Assert.NotNull(编译期 nullable 清洁,行为不变)
- 验证:dotnet test 全量 40/40 通过(新增 McpServiceVersionTests 5 用例:槽命中/裸版本归一化/未命中清单/遗产忽略 version+notice/钉住槽缺失回落根);dotnet build 主项目 0 error(仅存量 NU1903 警告);零包改动,未启动服务

Co-Authored-By: ZCode (GLM-5.3)
- Services/ExtractionService.cs:IsUnsafeSlotName 增 `slot.Contains(Path.VolumeSeparatorChar)` 一项。原守卫只查空白/纯点段/"/"/"\",漏掉盘符形——"c:" 经 Normalize 原样放行后,Path.Combine(outputDir, "c:") 因驱动器相对根被 IsPathRooted 判 rooted 而**直接返回 "c:" 不拼接**,Directory.Exists("c:") 在 CWD 位于 C 盘时恒 true,root 逃出 OutputDir 不变量;"c:xxx" 同类
- 守卫为 RunAsync/ResolveDocsRoot/DeleteSlot 共用,一处加固三个面同时收口;既有测试槽名均不含 ":",零破坏
- BootstrapBlazor.McpServer.Test/McpServiceVersionTests.cs:新增 Theory 守卫测试 UnsafeVersion_ReturnsNullWithInvalidNotice——version 传 "c:"/".."/"a/b" 时 ResolveDocsRoot 均返回 null 且 notice 为 Invalid 提示(含 available slots 清单);此前安全分支零覆盖
- 验证:TDD 红(仅 "c:" 用例失败,复现审核者实证链)→ 修复后 filter 8/8、全量 43/43 通过;dotnet build 主项目 0 error;仅动守卫与测试两文件,4 条 Minor 台账项未触碰

Co-Authored-By: ZCode (GLM-5.3)
- Program.cs 注册 IGitClient/IProcessRunner,并以工厂注册解 ExtractionService 委托式五参构造(Action/Func 非服务类型,DI 无法直接构造;工厂内延迟解析 DocsExtractorService/AppSettingsManager)——补上 GitSyncInvocable/McpService 此前运行时解析必失败的缺口
- 新增端点:GET /api/versions(公开,列槽摘要);POST /api/versions/pin?ref=(鉴权,PinAndExtract 保存 PinRef+启动提取,与 McpService.PinVersion 共享实现);POST /api/versions/extract?ref=&force=(鉴权,force 可选默认 false;ref 省略回退 PinRef,两者皆空→400 绝不 500;StartExtraction 立即返回不 await)
- N2:C# 关键字 ref 不能作参数名且 minimal API 不做下划线匹配 → [FromQuery(Name="ref")];认证失败保持上游 Cookie 默认 302→/login,未改全局 401 策略;既有 /api/components* 端点与无鉴权现状零改动;主项目零包改动
- Program.cs 末尾补 public partial class Program {}(WebApplicationFactory<Program> 可见性,上游无害)
- 测试 VersionsApiTests:WebApplicationFactory 内存宿主三用例(GET 200 公开 / 未认证 302→/login / 登录后 ?ref= 绑定 200 哨兵);测试项目加 Microsoft.AspNetCore.Mvc.Testing 10.0.9(仅测试依赖);与 ExtractionServiceTests 同 Collection 串行(共享 data/config.json 落盘,防并行写互踩)
- brief 笔误级最小修正(断言与行为语义不变):补 using System.Net;CreateDefaultClient(HttpClientHandler) 编译不过改等价 CreateClient(ClientOptions){AllowAutoRedirect=false};Windows 路径嵌入 JSON 转义反斜杠;$$→$$$ 原始串(JSON 尾部连续 }});extract 端点可选参数 force 移到必选参数后(CS1737)
- 验证:dotnet test 46/46 通过(43 旧+3 新,两轮稳定);dotnet build BootstrapBlazor.McpServer.csproj 0 错误(2 warning 为改动前既有)

Co-Authored-By: ZCode (GLM-5.3)
- Config.razor:GitSync 配置区新增 PinRef 输入框(BootstrapInput 同款模式,标签经 AppSettingsModel 资源段自动渲染),PlaceHolder 提示留空 = 跟随最新;保存链路复用现 SaveSettings(T1 起 PinRef 已持久化)
- Index.razor:新增 @Inject ExtractionService Ext / SwalService Swal;ForceSync 的 OnSyncClick 按 PinRef 分派——空 → 原路径 await GitSync.Invoke()(遗产模式零回归,提示串逐字保留);非空 → _syncResult = Ext.StartExtraction(VersionSlot.SlotName(pin), force: true)(B1 红线:UI 进程内直调,禁 HttpClient 出站打自身 REST)
- Index.razor:ForceSync 下方新增「版本槽」卡片——刷新按钮 + OnInitialized 预载 GetAvailableVersions() 渲染槽卡片(槽名/BB 版本/commit/提取时间/状态徽标 ready 绿 extracting 黄 failed 红/PINNED 标记);每槽「重新提取」(force: true)与「删除」(Swal 确认弹窗后调 DeleteSlot,钉住槽置灰禁用且服务端二次拒绝);「提取新版本」输入框 + 提交(SlotName 归一化,空值/非法槽名前置校验——fire-and-forget 不回传 RunAsync 拒绝结果)
- Locales:zh-CN/en-US 键集完全一致(Index 段新增 16 键 + AppSettingsModel 段 PinRef),PowerShell ConvertFrom-Json 校验通过
- 验证:dotnet build 0 error(3 告警均为存量 NU1903×2/CS8604,与本任务无关);dotnet test 全量 46/46 通过不回归;T7 REST 端点零改动
- 影响面:仅 UI 层与本地化资源,UI 无单测(按计划验证 = 编译 + 测试不回归,运行时表现留 T9 走查/用户实测)

Co-Authored-By: ZCode (GLM-5.3)
- 新增 Fixtures/BBFake.V1、BBFake.V2 两夹具假 BootstrapBlazor 程序集(AssemblyName=BootstrapBlazor,Version 1.0.0/2.0.0,net10.0 + FrameworkReference Microsoft.AspNetCore.App 免 NuGet 离线可编;不入 sln,仅测试 setup 现场 dotnet build)
- V1 仅 ComponentA;V2 同名重编一份 + ComponentB(ComponentBase 派生 + [Parameter] string? Title,命名空间 BootstrapBlazor.Fakes)
- 测试 csproj:Compile Remove Fixtures/**/*.cs(防 SDK 通编进测试程序集)+ None 整目录复制到输出;brief 笔误修正——.csproj 受 SDK DefaultItemExcludes(**/*.*proj)排除须显式包含,否则 setup 找不到夹具工程
- VersionPinningIntegrationTests 两测试端到端压真实链路(现场 build 夹具 → LayOutBbRepo 摆 repo 结构 → T5 MLC 化的 DocsExtractorService.Extract → McpService.ResolveDocsRoot);两测经 Lazy 复用同一批真实提取产物(xunit 同类串行)
- 断言锁定属性:v1 槽含 ComponentA 不含 ComponentB(不串版本)、两槽均无 Table/Divider(不串宿主 10.3.2,坑①哨兵)、v2 双组件齐(不漏)+ ComponentA.md 参数表来自本夹具反射;ResolveDocsRoot 对真实槽四路解析(缺省→钉住槽/2.0.0/v2.0.0→v2 槽/7.0.0→null+清单)
- docs.json 夹具内容为 {"src":{}}(非 {}):Extract 无 try/catch 地 GetProperty("src"),{} 必抛 KeyNotFoundException(第三轮 plan 审核实证);BuildFixture 重定向 stderr 附失败诊断
- 验证:RED(夹具缺失时 BuildFixture exit 1,两测均红)→ GREEN(本类 2/2);全量 dotnet test 48/48 passed(46 既有 + 2 新增);主项目 dotnet build 0 error
- 影响面:纯测试新增,零包改动(FrameworkReference 非 PackageReference),既有 46 用例零改动

Co-Authored-By: ZCode (GLM-5.3)
- 二审 Important 修复:原哨兵(DoesNotContain Table/Divider)触发依赖环境状态——本测试链路(Extract 只触框架类型、Program.Main 不执行)从不加载宿主 BB,隔离跑本类时若 T5 被回退为旧 in-proc 代码(GetAssemblies.FirstOrDefault ?? LoadFrom(夹具 DLL)),GetAssemblies 落空 → 加载夹具 DLL → 产物仍正确 → 假阴性;全量跑亦依赖 WAF 时序竞态,检测概率化
- 修法(审核者建议):PrepareSlots 在 Extract 前 Assembly.LoadFrom(AppContext.BaseDirectory/BootstrapBlazor.dll)(测试宿主 bin 的宿主包 10.3.2 副本,已核实存在),显式注入生产恒真前提(Program.cs:45 AddBootstrapBlazor() 使真实 MCP 进程启动即加载宿主 BB)——哨兵随即确定性武装
- 对现有绿路径零影响(MLC 分支不查询进程内状态);绿测含金量提升:隔离跑本类即活体证明 MLC 压过进程内宿主副本
- 验证:本类 filter 2/2 passed(注入进程内宿主副本后 MLC 仍胜出,产物仅夹具组件);全量 dotnet test 48/48 passed;仅动测试文件一行 + using + 注释,5 条 Minor 台账项未动

Co-Authored-By: ZCode (GLM-5.3)
- README.md / README.zh-CN.md 各新增「Version Pinning / 多版本钉住」一节:
  PinRef 配置示例(data/config.json 的 GitSync 节 + Config 页热改)、
  三个新 MCP 工具(GetAvailableVersions/PinVersion/ExtractVersion)与
  三个新 REST 端点(GET /api/versions、POST pin、POST extract)速查表、
  既有四工具可选 version 参数说明(归一化:10.8.1/10.8.1.0/v10.8.1 等价)、
  「建议 MCP 客户端传入当前项目实际引用的 BootstrapBlazor 版本」提示、
  遗产模式说明(PinRef 空 = 上游原行为,零迁移)。
- 验收剧本静态走查(spec §6.7 四条 + 遗产两处 + 互斥)逐条链路确认,结论:
  ① 日常查询:GetAvailableVersions(McpService.cs L162)→ GetComponentDocs(L100)
    → ResolveDocsRoot(L206):version 经 VersionSlot.Normalize(VersionSlot.cs L16-25,
    v 前缀可选/四段截三段)→ 槽命中 L220 / 钉住缺省 L233 / 根目录回退 L238,链路完整;
  ② 切钉住:PinVersion(McpService.cs L180)→ PinAndExtract(ExtractionService.cs L169,
    持久化 PinRef 后 StartExtraction)→ 立即返回 "Extraction started … Poll
    GetAvailableVersions"(L147);进度经 manifest 状态机(extracting L107 →
    failed L114 / ready L123)由 GetAvailableVersions(L192)可查;
  ③ 对照临时查:ExtractVersion(McpService.cs L196)→ StartExtraction(force:true),
    调用链上无任何 PinRef 写操作,建槽不动钉住;
  ④ 管理页兜底:Index.razor 槽列表 L64-96、重提取 L157-161、删除 L163-174
    (ExtractionService.DeleteSlot L184 拒删钉住槽)、提取新版本 L176-192;
    Config.razor PinRef 输入 L31-33 经 SaveSettings 持久化(AppSettingsManager.cs L75);
  ⑤ 遗产模式两处:GitSyncInvocable.cs L31-39 按 PinRef 分派(空 → L41 起上游
    逐字保留的 clone-or-pull 遗产分支);ResolveDocsRoot L223-227 忽略 version
    并附 "(legacy mode…)" notice;
  ⑥ 互斥:SemaphoreSlim(1,1)(ExtractionService.cs L34),RunAsync L54-55
    占用时立即返回 "Extraction already in progress",不排队。
- 验证结论:dotnet build BootstrapBlazor.McpServer.csproj -c Release 通过(0 Error,
  3 条既有警告:NU1903 上游传递依赖 ×2 + MlcAssemblyLoader CS8604 ×1,均为 T5 前后
  既有状态、非本次引入);dotnet test 全绿 48/48(Failed 0 / Skipped 0)。
- 影响面:纯文档改动,零代码、零包变更;走查未发现任何缺口,无需返工。

Co-Authored-By: ZCode (GLM-5.3)
- GetComponentList 参数改为 GetComponentListArgs? args = null(Services/McpService.cs:38):可选参数不进 AIFunctionFactory 生成的 schema required,旧客户端空参(arguments:{})绑定为 null 而非抛 ArgumentException;方法体改用 args?.Version(终审 Imp-1,wire-schema 兼容)
- ListSlots 加 manifest.json 存在性过滤(Services/McpService.cs:246):目录含 manifest 才算槽,与 GetAvailableVersions 口径统一;迁移期遗产根产物 API/Samples 不再列入 notice 的可用槽清单误导 LLM(终审 Imp-3)
- GetAvailableVersions 空输出返回 "(no versions extracted yet)"(legacy 附注保留);[PINNED] 改条件拼接,未钉住行不再有尾部 " | " 悬挂分隔符(终审顺手项3)
- PinVersion 移除入口 SlotName 纯校验调用、收敛为直调 PinAndExtract:为 Imp-2b 的 MCP 侧入口,校验前置随下一提交(pin 校验链)落地
- 测试适配:McpServiceVersionTests.MakeSlot 补写 manifest 对齐新槽口径,新增 NoticeListing_ExcludesLegacyDirs_WithoutManifest;VersionPinningIntegrationTests 夹具(直调 Extract 绕过流水线)补 SaveManifest 还原真实槽不变量
- 验证:dotnet test 全量 65 用例通过 / 0 失败;dotnet build BootstrapBlazor.McpServer.csproj 0 error(既有 3 警告为上游 NU1903/CS8604,非本次引入)
- 影响面:REST /api/components 显式传 new GetComponentListArgs(),行为不变;空参兼容仅放宽不收紧,嵌套 {args:{version}} 形态保留(绑定层断言随下一提交)

Co-Authored-By: ZCode (GLM-5.3)
- Program.cs pin 端点:ref 参数改 string? 并入口校验,缺失/空白返回 400 "ref is required."(与 extract 端点对称),不再让 SlotName 抛 ArgumentException 变 500(Imp-2a)
- ExtractionService.PinAndExtract(Services/ExtractionService.cs:175):入口先 Normalize + IsUnsafeSlotName 校验,空/路径形 ref 返回 "Invalid ref '…'. PinRef not saved." 且不写 PinRef、不分发后台任务——此前路径形 ref 先把垃圾 PinRef 写进 config 再被拒,假 200 后垃圾状态永久驻留(Imp-2b);McpService.PinVersion 空 ref 由此返回错误文案而非抛异常
- StartExtraction 分发前 _lock.CurrentCount==0 直接同步返回 "Extraction already in progress for slot '…'."(spec §6.2:占用时新请求立即返回提取已在进行中,第二个请求不再谎报 Extraction started)(Imp-2d)
- StartExtraction 的 Task.Run 内对非 Started 结果记 LogWarning("Extraction not started for slot '{Slot}': {Message}")——not-found/invalid/busy 拒绝结果此前被直接丢弃(零日志零 manifest),三渠道只拿启动文案而轮询等不到解释(Imp-2c)
- 新增 McpToolBindingTests(Imp-1 绑定层契约,此前 48 测全停在方法层才漏掉该缺陷):经 McpServerTool.Create 断言 GetComponentList wire-schema(args 保留在 properties、不进 required);空参/嵌套 {args:{version}} 绑定行为经与 SDK 0.9.0-preview.2 内部装配完全同构的 AIFunctionFactory + McpJsonUtilities.DefaultOptions 驱动(该版本 McpServerTool.InvokeAsync 需 RequestContext/真实服务器,不经此暴露)
- VersionsApiTests:登录态哨兵 PostExtract 改 AllowAutoRedirect=false 客户端并先断言 /api/login 200(终审顺手项2:防登录 302 被跟随成登录页 200 的哨兵空转恒绿);新增 pin 空白 400(Theory×3:缺 ref/ref=/全空白)与 ref=c:evil 返回 Invalid 文案且 config PinRef 保持原值用例
- ExtractionServiceTests 新增:PinAndExtract 非法 ref 不落盘(Theory×4:""/空白/../evil/c:evil,断言 PinRef 未写、零 git 调用、输出目录无槽目录)、拒绝结果记 Warning(注入 CapturingLogger 行为断言)、busy 同步文案(先等 runner 进入即互斥确已持有,消除调度竞态)
- 验证:dotnet test 全量 65 用例通过 / 0 失败(含本簇新增 14 例);dotnet build 0 error
- 影响面:pin/extract 合法路径零变化;既有用例除夹具口径适配外零删改

Co-Authored-By: ZCode (GLM-5.3)
- Index.razor 新增 _slotIsError:_slotResult 按结果语义分 success/danger 两色——错误文案配 alert-danger 与告警图标(Imp-4);赋值点:OnExtractNew 的必填/非法槽名错误路径置 true,OnDeleteSlot 按服务串语义分类(Invalid 前缀/cannot be deleted 视为错误),启动成功路径显式置 false
- 启动类操作不再直显服务层英文串(Imp-2e):OnSyncClick 钉住分支、OnReExtract、OnExtractNew 经 LocalizeStartMessage 映射本地化键;busy 语义(服务串含 already in progress)单独走 ExtractionInProgress 键,不与「已启动」混淆
- Locales/zh-CN.json、Locales/en-US.json 新增 ExtractStarted / ExtractionInProgress 双语键(Imp-2e 配套)
- VersionSlot.cs 补 Apache copyright header,与其余 5 个 Services 文件对齐(终审顺手项1)
- 验证:dotnet test 全量 65 用例通过 / 0 失败;dotnet build 0 error;UI 逻辑为纯服务端渲染、无独立单测,人工核验路径:登录后仪表盘提交空版本号/非法槽名观察红色告警、正常提取观察绿色提示
- 影响面:OnSyncClick 遗产分支(PinRef 空)两条文案维持原样;刷新/删除流程行为不变

Co-Authored-By: ZCode (GLM-5.3)
- docs.json 条目含分隔符(如 Charts\Bar)时 componentKey 为嵌套相对路径,
  Samples/Charts/ 子目录从未创建,StreamWriter 直接抛 DirectoryNotFoundException
- 上游旧版该异常被 GitSyncInvocable 整体 catch 静默吞掉(提取部分产物缺失无人知晓);
  本分支槽流水线如实记 failed 后暴露
- 修复:写文件前 Directory.GetDirectoryName + CreateDirectory(最小改动,其余行为不变)
- 验证:dotnet test 65/65 通过;Release 重建后实跑提取 v10.8.1(见部署验证)

Co-Authored-By: ZCode (GLM-5.3)
- 症状:第二次起切换版本提取时 dotnet build 以 MSB3027 失败(目标 DLL 被
  .NET Host 锁住);v10.0.0 连续两次失败、v10.9.2 偶发成功的差别仅为 GC
  回收旧映射的时机——此前 MLC 从不 Dispose,其文件映射滞留至进程级回收
- MlcAssemblyLoader.LoadBootstrapBlazor 返回 LoadedTarget(Assembly, Context):IDisposable;
  DocsExtractorService 反射段以 using 约束生命周期(所有 Type 访问均在 API 段内,无逃逸引用)
- 回归测试 DisposedContext_ReleasesFileMapping:Dispose 后以写权限打开目标 DLL;
  用临时副本避开 T9 哨兵在同进程对原件的合法 LoadFrom 锁(单测通过/全量失败的干扰源)
- T5 二审「MLC 不 Dispose 可忽略」的定性在多版本生产场景下失效,本修复推翻该结论
- 验证:dotnet test 66/66 两遍稳定通过(原 65 + 新增 1)

Co-Authored-By: ZCode (GLM-5.3)
- 改了什么:Services/McpService.cs —— GetComponentDocsArgs 新增可空 Section(api/samples,大小写不敏感,不传或空白=全量);返回组装抽为 internal static 纯函数 ComposeDocument(分段时节标题与全量逐字一致,非法值返回用法提示不抛错);LoadComponentDocs 增 outputDir 参数,组件未命中且 OutputDir 存在槽(含 manifest.json 的一级目录,与 ListSlots 同口径)时经 EnrichNotFound(参数序 componentName, outputDir, currentSlotOrRoot)附加跨槽存在性提示(槽名按 Ordinal 字母序,API/Samples 文件名 OrdinalIgnoreCase 与主路径 fuzzy 同规则,子目录缺失的槽静默跳过),无槽(遗产根产物)由外层显式守卫返回原样文案;AskComponentExpert 共享 LoadComponentDocs,not-found 提示同步受益(spec §3 明示接受的带效应)。README 双语各补一行 section 用法。
- 为什么:全量文档常 >50KB 被 MCP 客户端 resultBudget 截断,调用方拿不全也控不住量(目标 A);原 not-found 文案 "not found in documentation." 让 LLM 无法区分组件名错还是版本选错(目标 B)。
- 兼容声明:不传 Section = 现行为逐字不变,含 wire 层——新增 QueryEnhancementTests 18 用例锁定(纯函数直击 10 + 管线 6 + 绑定层 2:args 只含 componentName 调用成功返回全量、section 经 camelCase 绑定生效);遗产模式两处不变:not-found 原样文案以 Assert.Equal 逐字断言、Section 生效为纯新增面。
- 验证结论:dotnet test BootstrapBlazor.McpServer.Test 全量 84/84 绿(基线 66 + 新增 18),TDD 先红(CS0117 缺失 Section/ComposeDocument/EnrichNotFound)后绿;dotnet build BootstrapBlazor.McpServer.csproj 0 错(2 个 warning 为既有 NU1903/CS8604,非本次引入)。

Co-Authored-By: ZCode (GLM-5.3)
- IGitClient 追加 CreateWorktree(workDir,name,path,commitSha)/RemoveWorktree(workDir,name)(并行提取 T1,Task 2/3 消费);Services/GitClient.cs 同步真实现
- 0.31 API 实证(反射+行为探针):Worktrees.Add(committishOrBranchSpec,name,path,isLocked) 无 Options 类、无 Worktrees.Remove;Worktree 无 Path 属性,路径经 WorktreeRepository.Info.WorkingDirectory 反查(先 Dispose 释放 .git 句柄再删目录);Prune(wt,ifLocked:false) 注销并连带删工作目录,之后再 RobustDelete 兜底
- Add 直接收 TipOf 7 位短 SHA(libgit2 可解析并 detached 检出,探针证实);libgit2 不建中间目录,父目录缺失抛错 → CreateWorktree 先 Directory.CreateDirectory 父目录
- 健壮删除链(spec §4.4 折入版):只读清理统一可移植 BCL File.SetAttributes(Normal)(Unix 映射权限位);重试 ≤3 次 ×500ms 仅 OperatingSystem.IsWindows() 启用(MSBuild 节点攥句柄仅 Windows 存在),非 Windows 常规删除;重试穷尽后异常上抛由调用方(ExtractionService)catch 记 Warning;禁 P/Invoke/Windows 特有 API
- RemoveWorktree 注册不在(半注销残留)时无路径可寻,no-op;残留目录由 CreateWorktree 同名重建前 RobustDelete 清除(spec §4.1)
- 接口演进编译必需的 fake 机械补齐:McpToolBindingTests/QueryEnhancementTests 的 NoopGit 空实现;ExtractionServiceTests.FakeGit 沿用「记录全部调用名」契约记 Calls(参数捕获留给流水线任务,spec §4.5)
- GitClientTests 新增两离线用例:Worktree_CreateAtSha_And_Remove_CleansRegistration(检出于 TipOf SHA/注册/双净)、CreateWorktree_OverwritesStaleDirectory(只读残留先清后建);抽取 MakeSourceRepo/CleanupTree 辅助并收敛既有两用例的内联清理块
- 验证:dotnet test 86/86 通过(HEAD 前 84 + 新 2,零回归);dotnet build BootstrapBlazor.McpServer.csproj 0 错 0 新警

Co-Authored-By: ZCode (GLM-5.3)
- 锁层级重构(spec §4.3):全局 SemaphoreSlim(1,1) 拆为三层——按槽锁 ConcurrentDictionary<string, SemaphoreSlim>(异槽并行、同槽 WaitAsync(0) 占用即 busy)→ 并发上限 SemaphoreSlim(2)(await 排队等待、不拒绝)→ fetch/clone 全局锁 SemaphoreSlim(1,1)(LibGit2Sharp Repository 非线程安全,Clone/Fetch 必在持锁内;锁序恒定无环)
- 流水线 worktree 化(spec §4.1/§4.3):主仓库不再 Checkout(接口成员保留),提取主体在 {LocalPath 同级}/Worktrees/{slot} 的即用即删 worktree 上跑——build 工作目录、_extract basePath、ReadBbVersion 均改读 worktree;finally 注销删除(成败都删,失败仅记 Warning 不判提取失败),注册名约定 wt-{slot}
- SHA 唯一解析点:sha = TipOf(LocalPath, ToGitRef(slot))(分支 origin 优先/tag 剥壳),worktree committish 与 manifest(failed/ready 两态)共用同一次解析,build 后不得二次 TipOf——防并发 fetch 在 build 窗口推进 ref 致 manifest 与内容错标;clone 双检锁(fetch 锁内重验 IsValid 再 Clone);分支跟随判定的 fetch+TipOf 移入 fetch 锁内
- busy 文案演进:RunAsync 侧全局文案改为 "Extraction already in progress for slot '{slot}'."(StartExtraction 侧 HEAD 已带槽名不动;其同步 busy 检查由全局 CurrentCount 改查该槽信号量);对外五签名零变化
- 测试(TDD,先红后绿):FakeGit 并发化(ConcurrentQueue 调用记录、CreateWorktreeCalls committish 捕获、Fetch/Clone Timeline 交叠检测 OVERLAP、Clone 后 IsValid 翻转)、FakeRunner 进入/离开信号(HoldCount 门 + EnteredCount/MaxInFlight 峰值);剧本E 语义级改造——异槽组合改同槽断言新文案,另证异槽并行;六用例:并行成立/同槽 busy 带槽名/worktree committish=TipOf SHA 且 manifest 共用/成败都删 worktree/tag 跳过零 worktree/fetch 锁窗口无交叠+clone 双检恰一次+上限 2 第三排队后完成
- 其余:IGitClient.RemoveWorktree 接口 XML doc 补「name 未注册时安全 no-op」契约(T1 二审 Minor 折入);README 双语「并行提取」节更新(每槽并行、上限 2、即用即删);遗产模式(PinRef 空)路径零改动;零包改动
- 验证:dotnet test 全量 91/91 通过(86 既有零回归 + 5 净新增;先确认 5 个行为用例在新语义未实现时红);dotnet build BootstrapBlazor.McpServer.csproj 0 错误

Co-Authored-By: ZCode (GLM-5.3)
- 根因(第二批异会话验收 blocker):LibGit2Sharp Worktrees.Add 传 committish 时以 name
  创建同名分支(refs/heads/{name})作检出载体,而 RemoveWorktree 的 Prune 只注销注册不删
  该分支——首次创建成功、第二次(重提取/夜间刷新)即撞 "reference already exists" 立败
- 修复(按用户意见定职责归属):删除分支归 RemoveWorktree(每次提取的 finally 当场删净
  注册+目录+分支,正常路径零残留、不依赖下次请求);CreateWorktree 侧的同名残留处理降级
  为崩溃恢复兜底(进程被杀等异常路径自愈)
- 为什么双审未拦:T1 真实 git 测试每用例独立新建仓库、T2 用 fake——"同仓库同名重复创建"
  这条生产序列恰处两者盲区;新增回归测试 Worktree_RecreateSameName_InSameRepo_Succeeds
  (create→remove→create 再成 + 两轮后注册/目录/分支三净)补死该盲区
- 验证:dotnet test 92/92 通过(91+新增 1);服务重启后双版本 force 重提取实测见部署记录

Co-Authored-By: ZCode (GLM-5.3)
- 复验建议 + 首轮终审留档 Minor(MCP 版清单缺时间)双落点:批次时间戳此前只能靠
  「同一轮询窗口内同时 ready」间接佐证并行,现在直接可判
- 格式:槽名 | BB 版本 | commit | 状态 | yyyy-MM-dd HH:mm:ss(本地时间)| [PINNED]
- REST /api/versions 本就含 extractedAt(SlotSummary),零改动
- 验证:dotnet test 92/92 通过

Co-Authored-By: ZCode (GLM-5.3)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @momijijin, your pull request is larger than the review limit of 150000 diff characters

@sourcery-ai

sourcery-ai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Add version-pinned, multi-slot documentation extraction and querying to the MCP server, including new extraction pipeline and git worktree handling, optional versioned queries for existing tools, admin UI for managing slots, REST APIs for versions, and a comprehensive offline test suite.

Sequence diagram for version-pinned documentation extraction via REST and MCP tools

sequenceDiagram
    actor Admin
    participant BlazorIndex as BlazorIndexPage
    participant ExtractionService
    participant GitClient
    participant ProcessRunner
    participant DocsExtractorService
    participant AppSettingsManager
    participant McpService

    Admin->>BlazorIndex: Click ExtractNewVersion(ref)
    BlazorIndex->>ExtractionService: StartExtraction(slot, force=true)
    note over ExtractionService: fire-and-forget
    ExtractionService->>AppSettingsManager: LoadSettings()
    ExtractionService->>GitClient: IsValid(LocalPath)
    alt repo_invalid
        ExtractionService->>GitClient: Clone(RepositoryUrl, LocalPath)
    end
    ExtractionService->>GitClient: RefExists(LocalPath, gitRef)
    alt ref_missing
        ExtractionService-->>BlazorIndex: "Ref not found" message
    else ref_exists
        ExtractionService->>GitClient: TipOf(LocalPath, gitRef)
        ExtractionService->>GitClient: CreateWorktree(LocalPath, wt-slot, path, commitSha)
        ExtractionService->>ProcessRunner: Run("dotnet", BuildArguments, worktreePath)
        alt build_success
            ExtractionService->>DocsExtractorService: Extract(worktreePath, stagingDir)
            ExtractionService->>VersionSlot: SaveManifest(OutputDir, slot, status=ready)
        else build_failed
            ExtractionService->>VersionSlot: SaveManifest(OutputDir, slot, status=failed)
        end
        ExtractionService->>GitClient: RemoveWorktree(LocalPath, wt-slot)
    end

    Admin->>McpService: GetComponentDocs({componentName, version}) via MCP
    McpService->>AppSettingsManager: LoadSettings()
    McpService->>McpService: ResolveDocsRoot(settings, version, notice)
    alt slot_found
        McpService->>McpService: LoadComponentDocs(root, componentName, OutputDir)
        McpService-->>Admin: ComposeDocument(componentName, api, samples, section)
    else slot_not_found
        McpService-->>Admin: notice with available slots
    end
Loading

File-Level Changes

Change Details Files
Introduce a version-slot extraction pipeline with git worktree support, manifest tracking, and concurrency controls, and wire it into MCP tools, GitSync, DI, and REST APIs.
  • Add ExtractionService to manage per-slot extraction: clone/fetch, worktree creation/removal, build, staging replacement, and manifest writes with extracting/ready/failed states.
  • Implement SlotManifest and VersionSlot utilities for slot naming normalization, git ref resolution, manifest load/save, and path safety checks.
  • Create GitClient and ProcessRunner abstractions for git and process operations, including robust worktree lifecycle and fetch/clone behavior with origin-preferring branch semantics.
  • Wire ExtractionService into DI in Program.cs and pass it to McpService and GitSyncInvocable; adjust GitSyncInvocable to dispatch to the slot pipeline when PinRef is set, otherwise fall back to legacy extraction.
  • Expose new REST endpoints /api/versions (GET), /api/versions/pin (POST), and /api/versions/extract (POST) with proper query binding, auth requirements, and error handling for invalid or missing refs.
Services/ExtractionService.cs
Services/VersionSlot.cs
Services/GitClient.cs
Services/ProcessRunner.cs
Program.cs
Services/GitSyncInvocable.cs
Extend MCP tools and query behavior to support optional per-version documentation queries, slot discovery, and pin/extract operations, while preserving legacy behavior when PinRef is not configured.
  • Modify existing MCP tools (GetComponentList, SearchComponentKeyword, GetComponentDocs, AskComponentExpert) to accept optional version arguments, resolve a docs root via ResolveDocsRoot, and prepend legacy/slot notices via WithNotice.
  • Enhance LoadComponentDocs to be slot-aware, enrich not-found errors with cross-slot existence information when slots are present, and keep legacy wording when only root artifacts exist.
  • Add new MCP tools GetAvailableVersions, PinVersion, and ExtractVersion that surface slot manifests, allow pinning a ref with validation, and trigger background extraction without blocking.
  • Introduce helper methods ComposeDocument, EnrichNotFound, ListSlots, HasAnySlot, SlotContainsComponent, and CurrentSlotOrRoot to support sectioned docs output and richer error messages.
  • Ensure AIFunction binding compatibility for optional args via new DTOs and tests so legacy clients can still invoke tools with empty or minimal arguments without exceptions.
Services/McpService.cs
BootstrapBlazor.McpServer.Test/McpToolBindingTests.cs
BootstrapBlazor.McpServer.Test/QueryEnhancementTests.cs
BootstrapBlazor.McpServer.Test/McpServiceVersionTests.cs
Update admin UI to surface and manage version slots, including refresh, re-extract, delete, and new-ref extraction flows with localized messaging and slot status display.
  • Inject ExtractionService and SwalService into Index.razor and add a new VersionSlots card showing slot name, status badge, pinned flag, commit, BootstrapBlazor version, and extracted timestamp.
  • Implement UI actions to refresh slots, re-extract a slot, delete an unpinned slot with confirmation, and extract a new ref with client-side slot-name validation and localized result messages.
  • Adjust ForceSync behavior in the admin UI to dispatch to the slot extraction pipeline when PinRef is configured, preserving legacy GitSync behavior when PinRef is empty.
  • Add helper methods LocalizeStartMessage and GetStatusBadgeClass to map service messages and slot status to localized UI strings and Bootstrap badge classes.
  • Extend Config.razor to edit PinRef alongside OutputDir and adjust appsettings.json to include an empty PinRef default.
Components/Pages/Index.razor
Components/Pages/Config.razor
appsettings.json
Locales/en-US.json
Locales/zh-CN.json
Refactor DocsExtractorService to load component metadata from version-specific build outputs via MetadataLoadContext and to fix extraction issues with nested sample paths and attribute reflection.
  • Use MlcAssemblyLoader and MetadataLoadContext to load BootstrapBlazor.dll from the target build output directory, ensuring component discovery matches the extracted version rather than the server's own package version.
  • Resolve IComponent in the context of the loaded assembly via full-name matching and interface traversal, and detect [Parameter] attributes using CustomAttributeData and full-name checks instead of instantiating attributes.
  • Ensure sample markdown file generation creates necessary nested directories before writing, preventing failures when docs.json contains component keys with subpaths.
  • Add ResolveContextType helper to centralize type resolution within the loaded assembly context and maintain behavior parity in in-process reflection scenarios.
  • Introduce MlcAssemblyLoader and its tests to provide a robust, disposable MetadataLoadContext with proper resolver setup (target dir, AspNetCore.App, runtime) and to verify disposal releases file locks on the target DLL.
Services/DocsExtractorService.cs
Services/MlcAssemblyLoader.cs
BootstrapBlazor.McpServer.Test/MlcAssemblyLoaderTests.cs
Enhance documentation with version pinning concepts and usage, and add an extensive offline test suite to cover extraction pipeline behavior, version resolution, MCP bindings, git semantics, and integration flows.
  • Update README.md and README.zh-CN.md with a new Version Pinning section describing PinRef, version slots, new MCP tools, REST endpoints, optional version parameters, and legacy mode semantics.
  • Add a new xUnit test project BootstrapBlazor.McpServer.Test with multiple test classes covering ExtractionService, Query enhancement behavior, MCP tool binding, GitClient worktree and fetch semantics, VersionSlot normalization and manifest operations, AppSettings PinRef parsing, and integration tests for version pinning and REST APIs.
  • Create fixture projects BBFake.V1 and BBFake.V2 to build fake BootstrapBlazor assemblies with controlled component sets, used to validate that extraction follows target assembly versions and does not mix in host components.
  • Adjust Program.cs (partial class) to support WebApplicationFactory in tests and configure environment-based behavior for tests to avoid stdio transport issues.
  • Ensure tests exercise concurrency limits, slot locking, error logging, manifest states, origin-preferred branch semantics, and robust directory cleanup behavior across Windows and non-Windows environments.
README.md
README.zh-CN.md
BootstrapBlazor.McpServer.Test/BootstrapBlazor.McpServer.Test.csproj
BootstrapBlazor.McpServer.Test/*.cs
BootstrapBlazor.McpServer.Test/Fixtures/*
BootstrapBlazor.McpServer.slnx

Assessment against linked issues

Issue Objective Addressed Explanation
#3 Allow pinning documentation to a specific BootstrapBlazor version (tag/branch), support multiple versions co-existing, and expose a version parameter in tools/queries with sensible defaults and tolerant version string formats.
#3 Ensure component API/parameter tables are generated from the BootstrapBlazor assembly built from the target repository version, not from the MCP server’s own UI package version.
#3 Improve the extraction pipeline to support multiple version work copies concurrently, allow on-demand extraction outside the nightly cron, and expose extraction status and failures instead of silently swallowing errors.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

文档无法按 BootstrapBlazor 版本钉住与多版本查询:组件参数表还取自服务自身包版本,与被提取仓库不一致

1 participant