Serial Studio Spec 0078 解读:让内置 AI 助手直接读取随构建发布的源码与构建固定的帮助文档 📅 发布时间:2026/9/18 19:07:55 👁 浏览次数: Serial Studio Spec 0078 解读让内置 AI 助手直接读取随构建发布的源码与构建固定的帮助文档【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本文基于仓库 doc/claude/specs/0078-assistant-source-access/spec.md 展开并结合仓库源码、构建脚本与单元测试印证实现细节。Serial Studio 是一款开源遥测仪表盘支持 UART、BLE、MQTT、Modbus、CAN Bus 等数据源本文讲解的 Spec 0078 解决的是其内置 AI 助手读不到自己所在产品的源码这一核心短板。读完本文你将掌握构建提交标识如何从 CI 一路写入 About 对话框与助手上下文、源码如何按构建打包进二进制、沙箱如何以只读方式向助手开放source/前缀、以及帮助页抓取如何被钉在构建所用提交上。一、规范背景助手回答行为问题时的证据链缺口Serial Studio 内置的 AI 助手此前只能从文档、内置技能skills、示例项目和实时命令注册表中获取答案无法读取应用自身的源码。当一个用户问为什么我的帧解析器拒绝了这批数据、某个 transform 的宿主到底暴露了哪些接口、某个驱动是怎么重连的时助手只能基于关于行为的文字描述来推理而不是基于定义这些行为的代码本身spec.md 的 Problem / Motivation 一节。与之形成对照的是维护者的日常体验把终端 Agent 指向仓库用 grep 和 read 一两次搜索就能解决同样的问题。规范将其概括为The shipped assistant is the weaker tool for the same product it lives in——内置助手是同一个产品里更弱的那把工具。除源码不可见外还有两个相互叠加的漂移问题帮助页抓取指向开发分支meta.fetchHelp工具抓取帮助页面时使用master分支的 URL老版本发布版的用户可能拿到自己构建中并不存在的新功能文档或已经改变的行为描述。About 对话框只显示语义版本连续构建continuous builds的多个二进制可能携带完全相同的版本字符串支持对话无法确定某个二进制究竟由哪个 commit 构建。规范快照2026-09-09对应d64a69475v4.1.0-23中的关键现状如下表所有行都标注为规划前必须重新核对观察项位置助手有文件沙箱fs.list/read/search/write/append/delete读取按字节偏移分页、单片上限 32 KB且已暴露多个读取根而非单一根FileSandbox.h、ToolFilesystemTools.cpp、ToolSchemas.cpp语义文档检索是 BM25基于 sanitize 时构建的约 1.5 MB 内置索引因此已经钉在构建上DocSearch.h、build_search_index.py、sanitize-commit.py帮助页抓取裸页面名解析到仓库master分支接受 github.com / raw.githubusercontent.com / serial-studio.com 上的任意完整 https URL页面截断在 32 KB404 时重定向到帮助索引HelpFetcher.h、HelpFetcher.cpp构建只打上语义版本不记录 commitQML 侧见Cpp_AppVersionAbout 打印Version %1CMakeLists.txt、ModuleManager.cpp、About.qml发布版与连续构建由 GitHub Actions 产出工作流已知道 commit 并传给 release 步骤ci.yml除第三方代码外的手写源码约 1100 个文件、10.5 MB 未压缩帮助文档树是独立的app/src、core、doc/help二、目标与显式边界Goals / Non-Goals规范的目标可以浓缩为四条用户向助手提出行为类问题时答案以用户正在运行的构建的源码为根据——通过搜索和阅读源码获得而不是回忆文字描述助手参考的一切文档、技能、示例、帮助页、源码都与正在运行的构建匹配老版本用户除非显式给出完整 URL否则不会拿到开发分支的材料About 对话框标识出精确构建版本 构建所用的 commit整个特性在构建机与运行时都不需要 git、不需要网络源码本身离线可用维护者工作流保持不变sanitize → commit → push → CI 发布。同时规范用 Non-Goals 划定了不做的事这些边界对理解设计取舍非常重要不做远程代码搜索GitHub 搜索 API 需要 token、只索引默认分支且有速率限制无法钉住 commit也无法替代 grep不包含第三方库源码vendored 依赖与 submodule 不在范围内——助手解释的是 Serial Studio 本身而不是 Qt 或其依赖库不授予对发布源码的写权限源码根只读既有工作区写根不变不引入新的检索引擎对文件做子串/正则搜索就是终端 Agent 的做法代码感知的语义索引是可能的后续项不属于本规范不改变助手对源码的用途解释行为、定位责任代码在范围内提出补丁修改应用不是本规范的产品特性不做 pre-commit 脚本打 commit 戳脚本无法预知它正在准备的提交的哈希且在 git 中存放按提交归档的源码会撑爆历史——两个方案都考虑过并被否决。三、构建期提交标识从-DSS_BUILD_COMMIT到 About 对话框R1 要求发布二进制携带其构建来源提交的标识由 CI 构建提供本地开发者构建携带可辨识的占位符而不是哈希。3.1 根 CMakeLists 的注入与校验根 CMakeLists.txt 定义了一个可配置变量set(SS_BUILD_COMMIT CACHE STRING Full commit hash of a CI build; empty for local builds) string(LENGTH ${SS_BUILD_COMMIT} SS_BUILD_COMMIT_LENGTH) if(NOT SS_BUILD_COMMIT STREQUAL AND (NOT SS_BUILD_COMMIT MATCHES ^[0-9a-f]$ OR SS_BUILD_COMMIT_LENGTH LESS 7 OR SS_BUILD_COMMIT_LENGTH GREATER 40)) message(FATAL_ERROR SS_BUILD_COMMIT must be empty or a lowercase hexadecimal commit hash (7-40 chars); got ${SS_BUILD_COMMIT}. It is spliced into help-page URLs and shown in About, so a branch name or tag here would produce broken references.) endif()要点默认空串本地构建即无戳一旦传入必须是7–40 位小写十六进制哈希否则 configure 阶段直接FATAL_ERROR校验理由很关键该值会被拼进帮助页 URL作为 raw.githubusercontent.com 路径段并显示在 About 中若传入分支名或标签会产生损坏的引用。随后通过add_definitions(-DPROJECT_COMMIT${SS_BUILD_COMMIT})暴露为预处理器宏CMakeLists.txt并在 AppInfo.h 中#define APP_COMMIT PROJECT_COMMIT统一引用。注意构建系统任何地方都不调用 gitCMakeLists.txt 注释明确nothing here ever invokes git。3.2 CI 传递github.shaci.yml 在 Linux、macOS、Windows 各平台、各构建腿的 configure 步骤中都传入-DSS_BUILD_COMMIT${{ github.sha }}例如cmake -B build -G Ninja \ -DSS_BUILD_COMMIT${{ github.sha }} \ -DPRODUCTION_OPTIMIZATIONON \ -DENABLE_HARDENINGON \ -DBUILD_COMMERCIALON \ ...这使发布二进制天然携带精确 commit本地开发者构建不传该变量则保持占位符状态。3.3 QML 侧暴露与 About 展示C 侧在 ModuleManager.cpp 注册到 QML 环境registry.add(Cpp_AppCommit, QVariant(QStringLiteral(APP_COMMIT)));About.qml 据此计算显示身份readonly property string buildTag: Cpp_AppCommit ! ? Cpp_AppCommit.substring(0, 7) : qsTr(local build) readonly property string buildIdentity: Cpp_AppName Cpp_AppVersion ( (Cpp_AppCommit ! ? Cpp_AppCommit : buildTag) )并在对话框正文中显示Version %1 (%2)About.qml。对应验收标准AC1CI 构建的二进制 About 显示Version X.Y.Z (abcdef1)且短哈希与工作流的 commit 一致本地构建显示占位符。四、源码随构建打包SS_BUNDLE_SOURCE与:/source资源树R6 要求源码随构建捆绑、由正在编译的同一棵树产出因此构造上就与二进制匹配、离线可用、并且就地读取——不展开到磁盘因此没有需要失效的缓存。4.1 打包选项与范围app/CMakeLists.txt 实现了这一机制option(SS_BUNDLE_SOURCE Bundle the application source for the AI assistant (Pro builds) ON) set(SRC_RCC ) if(BUILD_COMMERCIAL AND SS_BUNDLE_SOURCE) set(SS_SOURCE_BUNDLE_ROOTS app/src app/qml core doc/help examples cmake ) set(SS_SOURCE_BUNDLE_EXTENSIONS h cpp c hpp qml js lua md json txt cmake py ssproj csv dbc yml ) ...关键设计选项默认 ON但仅在商业构建BUILD_COMMERCIAL下生效SS_BUNDLE_SOURCEOFF可跳过整步以加速本地迭代构建此时沙箱对source/的回答是source_unavailable根目录白名单app/src、app/qml、core、doc/help、examples、cmake外加三个根CMakeLists.txt扩展名白名单覆盖手写源码与文档的全部文本类型h cpp c hpp qml js lua md json txt cmake py ssproj csv dbc yml排除规则/ThirdParty/第三方代码、/core/tests/测试、/examples/[^/]/doc/示例截图、/__pycache__/文件清单在configure 时通过对白名单根 扩展名做file(GLOB_RECURSE ... CONFIGURE_DEPENDS)得到——清单就是这棵树本身没有检查入库的清单文件会落后于树新增文件会触发 re-glob.qrc只在其内容变化时重新生成普通 reconfigure 不会重跑 rcc。4.2 资源树与就地读取生成的source_bundle.qrc使用qresource prefix/source每个文件以相对仓库根的路径作为 alias 注入最终通过qt_add_resources(SRC_RCC ...)编入二进制。QML/C 侧可通过:/source/...资源 URL 就地读取app/CMakeLists.txt 注释明确the sandbox then answerssource_unavailable、读取走 model-facingsource/prefix。R9 要求二进制体积增长有界并在 plan 中声明预期量级与既有搜索索引相当而非几十 MB——这正是把源码作为文本资源打包、而非解压缓存的设计动机。R10 则显式记录了一个决策商业授权文件的源码本就在仓库公开打包不会披露新内容但这一决策被记录而非假定。五、沙箱只读源码访问source/前缀与既有边界全量生效R5 要求助手可以列出、搜索、读取当前构建的应用自身源码范围限定为手写应用与核心代码、帮助文档、示例项目既有分页读取与搜索工具原样复用不引入新工具名。R7 要求源码根对助手只读。5.1 第二个读取根与虚拟前缀FileSandbox.h 是核心实现其类注释直接点名 spec 0078class FileSandbox { public: static constexpr qint64 kMaxReadSlice 32 * 1024; static constexpr qint64 kMaxWriteBytes 4 * 1024 * 1024; static constexpr qint64 kMaxSearchFileBytes 4 * 1024 * 1024; static constexpr qint64 kMaxSearchScanBytes 64 * 1024 * 1024; static constexpr int kMaxListEntries 2000; static constexpr int kMaxSearchFiles 5000; static constexpr int kMaxSearchHits 200; static constexpr int kMaxRecurseDepth 16; static constexpr int kBinarySniffBytes 8192; static constexpr const char* kSourcePrefix source; static constexpr const char* kSourceResourceRoot :/source; ...沙箱维护三类根FileSandbox.cpp 的readRoots()工作区根默认读取根也是AI/写根的祖先source/虚拟前缀对应的源码根构造时初始化为:/source资源根FileSandbox.cpp测试中可用setSourceRoot()指向临时目录用户拖入的路径registerDroppedPath会话级。splitSourcePath()FileSandbox.cpp负责把模型侧的source/...路径剥离出前缀resolveSource()FileSandbox.cpp把尾部路径与根拼接后做 canonical 化与包含性检查若该构建未携带源码包root为空或不存在返回source_unavailable提示 This build carries no bundled application source.若 canonical 化后越出根返回outside_sandbox提示 Source paths must stay under source/.。一个有趣的边界真实工作区里恰好有个叫source的文件夹会被前缀遮蔽但通过显式./source拼写可在路径规范化前退出前缀解析从而仍可访问测试sourcePrefixShadowing覆盖见 tst_file_sandbox.cpp。5.2 工具 Schemafs.*的可选作用域按 R5 的修订2026-09-11搜索工具增加一个可选的作用域参数工具名与既有参数全部不变。ToolSchemas.cpp 中fs.search的 schemaprops[QStringLiteral(path)] makeProperty( QStringLiteral(string), QStringLiteral(Directory to search, e.g. Projects, a dragged-in folder, or source/core/Pipeline for the application source. Default: the whole workspace and dragged-in paths (the source is searched only when named).));fs.list与fs.read的 path 参数也明确接受source/...ToolSchemas.cpp。工具描述同样写明the exact source of this build、Read-only、pass path:source/... to grep the applications own source for this buildToolSchemas.cpp。调度层 ToolFilesystemTools.cpp 把六个fs.*工具名一一路由到沙箱原语注释特别强调fs.read与fs.search在 worker 线程运行spec 0075J3因此新增逻辑不得触碰 GUI 持有的对象——沙箱自持状态、每个原语返回值而非修改会话状态。5.3 只读强制与默认搜索隔离R7 的强制实现在resolveWrite()FileSandbox.cpp任何写入路径若解析出source/前缀直接返回read_only_root错误提示 The application source under source/ is read-only; write under AI/ instead.。fs.write/fs.append/fs.delete的 schema 也只允许工作区AI/子目录ToolSchemas.cpp。对应验收标准AC4与AC5由 tst_file_sandbox.cpp 覆盖关键测试用例sourcePrefixListsTheBundlefs.list(source)返回source/core、source/core/Example.cpp等条目且回显前缀sourcePrefixReadsAFilefs.read(source/core/Example.cpp)读到捆绑文件内容source/../notes.txt越界被拒sourceScopedSearchFindsOnlySourceHits作用域为source的搜索只命中源码包工作区里的同名 needle 不出现defaultSearchDoesNotReachTheSource默认搜索仍然只覆盖工作区与拖入路径——源码只在被点名时才会被搜索writeUnderSourcePrefixIsRefused对source/core/Example.cpp的 write / append / remove 全部返回read_only_root且工作区不产生AI/source/目录、源文件内容未被篡改tst_file_sandbox.cppresourceSchemePathIsRefusedForWrite:/source/...资源路径在拼接到写根之前就被拒绝。这些测试印证了规范中的不变量既有沙箱保证canonical 路径包含性、拒绝符号链接、递归与列表上限、32 KB 读取分片对源码根无例外地生效。六、帮助页抓取钉在构建提交上buildRef()与 404 兜底R4 要求帮助页抓取把裸页面名解析到二进制构建所用的 commit包括 404 兜底到帮助索引无 commit 的开发者构建回退到开发分支。6.1 Ref 解析与 URL 组装HelpFetcher.cpp 的核心逻辑QString AI::HelpFetcher::buildRef(const QString stamped) { return stamped.isEmpty() ? QStringLiteral(master) : stamped; } QString AI::HelpFetcher::helpBase(const QString ref) { return QStringLiteral( https://raw.githubusercontent.com/Serial-Studio/Serial-Studio/%1/doc/help/) .arg(ref); } QUrl AI::HelpFetcher::pageUrl(const QString path, const QString ref) { if (path.startsWith(QStringLiteral(http), Qt::CaseInsensitive)) return QUrl(path); QString page path; if (page.startsWith(/)) page.remove(0, 1); if (page.isEmpty()) page QStringLiteral(Home); if (!page.endsWith(QStringLiteral(.md), Qt::CaseInsensitive)) page QStringLiteral(.md); return QUrl(helpBase(ref) page); }行为要点buildRef()无参版本读取APP_COMMIT即构建期戳有戳即该 commit无戳即master——buildRef()永远不返回空 ref裸页面名去掉前导/、补.md、拼到doc/help/下空名落到Home.md完整 URL 原样透传https 开头ref 不改写用户或模型显式拼写的 URL对应测试fullUrlPassesThroughUnchangedindexUrl(ref)是同一 ref 下的doc/help/help.json——404 兜底落在同一个 ref绝不会跳到master测试indexUrlUsesTheSameRef断言 URL 中不含/master/。6.2 传输加固与 404 自纠抓取流程HelpFetcher.cpp延续既有安全策略主机白名单github.com、raw.githubusercontent.com、serial-studio.com及其子域精确锚定匹配host allowed || host.endsWith(. allowed)拒绝evilgithub.com之类仿冒域、拒绝非 https、拒绝带 userinfo 的 URLurlAllowed测试allowlistIsUnchanged覆盖注释明确这是模型可控 URL场景下的外泄闸门重定向复核UserVerifiedRedirectPolicy 每个跳转目标重新过白名单不过即abort()传输上限kFetchTimeoutMs 15s、kMaxFetchBytes 32 KB页面、kMaxIndexBytes 64 KBhelp.json、kMaxTransportBytes 1 MB缓冲硬上限超限即中止分页取消abortPending()通过递增 epoch 使已发出的请求在完成时被丢弃被取消的回合不会收到过期结果404 自纠当 404 发生在raw.githubusercontent.com的/doc/help/路径且不是 help.json 本身时转而去抓同一 ref的help.json返回时附带一段教学性 note教模型传文件名的.md前体并保留连字符如Painter-Widget而非PainterHelpFetcher.cpp页面正文按 32 KB 截断并追加\n... [truncated]标记。对应验收标准AC3老 tag 构建下请求帮助页返回该 tag 的页面而非当前开发文本与AC6单元测试见 tst_help_fetcher.cpp裸名解析、索引同 ref、空 ref 回退 master。规范同时强调全 URL 透传保持既有主机白名单不变——本规范只钉默认值不扩大可抓取范围。七、助手行为准则构建身份注入与何时读源码阶梯R3 要求提交身份暴露给助手上下文R8 要求助手指南明确何时查源码、优先搜索而非整读、把裸页面名交给 help fetch。7.1 构建身份块ContextBuilder.cpp 在系统提示中注入构建身份有戳构建This is %1 %2, built from commit %3 (%4). The application source under source/ and every bare help page name are pinned to that commit; quote the short hash when you cite either.——同时要求助手引用源码或帮助页时引用短哈希本地构建a local developer build with no stamped commit. Bare help page names resolve against the development branch.7.2 何时读源码阶梯同文件ContextBuilder.cpp内置了源码读取决策规则Answer from a skill or help page first, meta.searchDocs second. Read the source (fs.search with path:source/..., then fs.read one file) only when one of these fires: the docs contradict what the user observes; the user quotes an error message (grep the exact text, it lands on the line that raised it); the question is a limit, default or threshold the docs do not state; or it is about the order in which things happen. Never for how-to questions. Cite source/path:line, name the user-facing feature rather than the internal identifier, and summarize code instead of pasting it.可归纳为四类触发条件与两条禁止项触发条件示例文档与用户观察矛盾文档说 X实际行为是 Y用户引用错误消息grep 精确文本直接落到抛出该错误的那一行文档未声明的限制/默认值/阈值缓冲大小、超时、上限等关于事件发生顺序的问题解析、变换、重绘的先后禁止项说明how-to 类问题怎么做一律走技能/文档不读源码大段粘贴代码要求概括代码而非复制粘贴并命名用户可见功能而非内部标识符助手回答行为问题时给出的引用格式为source/path:line可追溯、可核对。此外系统提示把meta.fetchHelp列为权威 GitHub 文档源ContextBuilder.cpp并要求模型对裸页面名走 fetch 以利用构建固定基址而不是自造分支 URL。八、需求清单 R1–R10 与验收标准总览规范以 10 条需求R1–R10收口上文已分别展开汇总如下编号需求实现落点R1二进制携带 CI 提供的构建提交标识本地构建用占位符CMakeLists.txt、ci.ymlR2About 同时显示版本与提交身份可复制进支持消息About.qmlR3提交身份暴露给助手上下文并可随引用输出ContextBuilder.cppR4帮助页裸名解析钉在构建提交404 兜底同 ref无戳回退开发分支HelpFetcher.cppR5助手可列出/搜索/读取当前构建源码手写应用核心帮助示例复用既有工具搜索增加可选 scopeFileSandbox.h、ToolSchemas.cppR6源码随构建捆绑、由被编译的同一棵树产出、离线可用、就地读取不落盘app/CMakeLists.txtR7源码根只读写/追加/删除以明确错误拒绝FileSandbox.cppR8指南规定何时读源码、优先搜索、裸页面名交 fetchContextBuilder.cppR9体积增长有界并在 plan 声明量级同搜索索引规格要求由打包设计满足R10商业构建决策显式化记录规格要求验收标准在规范中全部标记为已通过[x]AC1 提交显示、AC2 离线源码问答哪个文件拒绝校验失败的帧、记录什么日志、AC3 老 tag 帮助页、AC4 只读拒绝、AC5 沙箱双根单测、AC6 帮助抓取单测、AC7 体积增量记录、AC8 sanitize 与语料 lint 通过。其中 AC2 正是规格导语中终端 Agent 一两次搜索就能解决的同一个问题断网状态下助手通过搜索并阅读捆绑源码回答哪个文件拒绝了校验失败的帧并记录了什么并说出文件名与函数。九、约束、不变量与遗留决策9.1 约束与不变量不在数据热路径上助手路径不参与帧摄取与仪表盘更新任何解包若存在都发生在首次使用助手时、离开热路径绝不在应用启动时——规范最终选择了不解包、直接读资源该子句随之删除构建与运行时均不调用 gitcommit 来自 CI 构建定义源码归档来自被编译的树无新运行时依赖归档创建用 CMake 既有能力解包如需要用 Qt 既有能力既有沙箱保证全量适用于源码根包含性、符号链接拒绝、递归/列表上限、32 KB 分片必须兼容所有 provider包括本地模型源码访问是模型可以使用的工具不是回答的硬性前提Spec 0077 正在移动库间代码plan 必须针对规划时的树编写而非针对上表记录的路径——这也是规范开头每个观察都是快照而非契约的提醒所在。9.2 开放问题截至规范写作时问题倾向与理由捆绑 vs 下载规范选捆绑离线、构造上精确、无需 pin 逻辑若 R9 体积约束无法满足备选是下载同一 commit 的归档到缓存除归档来源外复用一切完整 40 位哈希 vs 仅短形式完整哈希是更安全的默认帮助抓取的 ref 需要它显示时派生短形式源码是否并入 BM25 运行时索引默认只做子串/正则搜索若助手按名找不到入口点再重访打包目录范围默认包含 CMake 文件与 SDK 生成脚本排除测试与 CI这些默认值 重新评估条件的模式体现了规范对可逆决策的刻意管理——先以最小实现落地再以证据驱动调整。十、总结对使用者与开发者的实际意义Spec 0078 最终把 Serial Studio 内置 AI 助手从只能回忆文字升级为能读自己所在产品的代码构建身份SS_BUILD_COMMIT→Cpp_AppCommit让 About 对话框与助手上下文都能精确定位二进制来源:/source资源树让手写源码、帮助文档与示例随构建离线打包source/前缀让沙箱以既有边界只读开放源码buildRef()让帮助页抓取与 404 兜底都钉在构建提交上。对于使用者这意味着三类可直接体验的能力更可靠的排障问答问为什么校验失败、这个限制是多少、这个顺序为什么是这样助手可以 grep 并读取你正在运行的这个构建的源码并给出source/path:line级引用版本一致性老版本用户不会被灌输开发分支的新文档About 里的Version X.Y.Z (abcdef1)可以精确复现支持会话安全边界不变源码根只读、默认搜索不触及源码、帮助抓取主机白名单与传输上限原样保留本地模型同样可用。对于想深入源码的读者建议按以下路径继续阅读规格文档 spec.md → 沙箱实现 FileSandbox.h 与 FileSandbox.cpp → 工具 Schema ToolSchemas.cpp → 帮助抓取 HelpFetcher.cpp → 打包逻辑 app/CMakeLists.txt → 单测 tst_file_sandbox.cpp 与 tst_help_fetcher.cpp。如需本地复现可在 configure 时传入-DSS_BUILD_COMMIT40 位十六进制哈希观察 About 与助手行为或设-DSS_BUNDLE_SOURCEOFF体验source_unavailable回退路径注意该特性在商业构建下默认开启GPL 构建与本地构建的默认行为不同。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考