一次聚焦的硬化发布。
没有新功能——只是修复自 0.8.7 发布以来报告的粗糙之处。
此处的每项变更要么是用户发现的回归、CI 工作流错误,要么是构建时的可靠性问题。

🐛 Bug 修复

Docker:卷挂载权限修复

docmd init 和其他写入文件的命令在 /docs 是由容器内置 docmd 用户(uid 1001)以外的其他 uid 拥有的主机挂载卷时,会以"Permission denied"失败。
镜像现在捆绑了 su-exec,入口点会检测挂载目录所属的 uid:gid,并在运行任何命令前以该身份重新执行——这是官方 postgresredis 镜像使用的相同模式。

# 以下在主机卷上无需 -u 标志即可工作
docker run -v $(pwd):/docs ghcr.io/docmd-io/docmd:latest init
docker run -v $(pwd):/docs -p 3000:3000 ghcr.io/docmd-io/docmd:latest

触发"Operation not permitted"所有权保留警告的 cp -a(归档)模式已移除。
-u $(id -u):$(id -g) 变通方法不再需要,已从所有 README 和 Docker 部署指南中移除。

Docker:init 在 CI 中不再阻塞

交互式的"是否要覆盖这些文件?"提示在非 TTY 环境(CI、不带 -itdocker run、管道输入)中会挂起。
CLI 现在会检测非交互模式并自动跳过该提示——默认保留现有文件(安全),或接受 -y/--yes 以选择覆盖。
新的 --force 标志在任何环境中都可用于显式覆盖。

# 在 CI / Docker / 管道输入中强制覆盖
docker run --rm -v $(pwd):/docs ghcr.io/docmd:latest init --force

# 或使用 -y
docker run --rm -v $(pwd):/docs ghcr.io/docmd:latest init -y

docmd add 在 npx 上:Windows 上的误报"npm not found"

npx @docmd/core add <plugin> 在 Windows PowerShell 上打印"系统 PATH 上找不到包管理器 ‘npm’",即使 npm --version 在同一 shell 中正常工作。
根本原因:当通过 npx 启动 @docmd/core 时,派生的子进程在 Windows 上继承了一个被剥离的 PATH。
安装程序现在相对于 process.execPath(Node 自身的路径)解析 npm/pnpm/yarn/bun,无论 @docmd/core 是如何启动的,这都能正常工作。

错误消息现在还会检测特定情况(Node 可达 + 二进制缺失)并建议直接通过主机的包管理器安装插件。

@docmd/template-* 的模板注册表查找

theme.template 设置为官方模板(如 summer@docmd/template-summer)会打印:

⬢ Plugin "template-summer" not found in official registry - manual installation required

根本原因:自动安装注册表查找从包名中去除了 @docmd/plugin- 来推导注册表键,但没有去除 @docmd/template-
因此 @docmd/template-summer 解析为键 template-summer 而不是 summer,后者在注册表中不存在。
现在两个前缀都被正确去除。
自定义/第三方模板不受影响。

语义搜索的对等依赖处理

当设置了 plugins.search.semantic = true 且 ML 对等依赖(@huggingface/transformersonnxruntime-node)不存在时,构建会静默回退到关键词搜索,并在 docmd-search 内部深处抛出令人困惑的错误:

⬢ Semantic indexing failed: Embedding failed: Failed to load model "Xenova/all-MiniLM-L6-v2": Cannot find package '@huggingface/transformers'

两处改进:

  1. 清晰的前置检查 —— 插件现在在自动安装后验证对等依赖是否可解析,并发出包含缺失包名的可操作错误,而不是让失败从 docmd-search 内部浮出水面。
  2. 过时的模块缓存修复 —— 在构建过程中自动安装 docmd-search 后,当前 Node 进程的模块缓存无法看到新包。
    索引现在在子进程中运行,因此以全新的模块解析启动。
    npm install 自动安装还会传递 --foreground-scripts,以便 onnxruntime-node 的 postinstall(本机二进制下载)实际在启用了 npm allow-scripts 安全功能的 CI 环境中运行。

npm-publish:packages/templates/* 现已加入发布循环

npm-publish.yml 工作流在遍历 packages/*packages/legacy/*packages/plugins/*packages/engines/*,但遗漏了 packages/templates/*
@docmd/template-summer@docmd/template-rain 这样的模板没有被发布。
现已加入。

docmd init 默认不再发起网络调用

之前 docmd init 每次运行都会从 raw.githubusercontent.com/docmd-io/docmd-skills 拉取 SKILL.md
如果拉取成功,获取的内容会覆盖捆绑的默认值。
两处变更:

  • 默认现在是仅本地的 —— 捆绑的 SKILL.md 内容随包提供,在不发起任何网络调用的情况下写入。
  • 通过 --with-skill 标志或 DOCMD_FETCH_REMOTE_SKILL=1 环境变量选择启用 从 docmd-skills 仓库获取最新的 SKILL.md(CI 环境和离线工作站可以继续使用捆绑的默认值)。
docmd init --with-skill    # 从 docmd-skills 获取最新的 SKILL.md
DOCMD_FETCH_REMOTE_SKILL=1 docmd init   # 相同,通过环境变量

docker-publish:停止对 main 的每次推送触发

docker-publish.yml 工作流具有 push: branches: [main] 触发器,导致它在每次提交到 main 时运行,而不仅仅是发布事件。
已移除——现在仅在 v* 标签推送、发布的版本和手动调度时触发。

Dockerfile:在 CI 中使 pnpm install 可靠

生产阶段使用 corepack prepare pnpm@10.33.2 --activate(带显式版本),但构建器阶段的 corepack prepare --activate(不带版本)在 GitHub Actions CI 中失败,因为它要求当前工作目录中有 package.json
两个阶段现在直接使用 npm install -g pnpm@10.33.2——更简单,没有 corepack 解析步骤,在 CI 和本地行为一致。
在干净构建上,pnpm 还在非 TTY Docker 环境中拒绝在没有交互式确认的情况下删除现有的 node_modules 目录。
install 命令现在传递 --config.confirmModulesPurge=false,这是 pnpm 针对这种情况的定向标志。
更广泛的 ENV CI=true 路由已被考虑并拒绝,因为它会泄漏到每个后续的 RUN

UI 翻译键在已发布的包中作为字面文本呈现

builtWithcopyContextcopiedToClipboard 和其他模板翻译的字符串在使用已发布的 @docmd/ui 包构建的输出中作为字面键文本呈现。
monorepo 正常工作,因为 packages/ui/translations/dist/ 并存,但已发布包的 files 数组不包含 translations/ 目录,因此 pnpm pack 将其从 tarball 中剥离。
已添加 translationsfiles 数组;loadTranslations 现在还会在目录缺失时发出清晰的警告,以便未来的回归不会作为静默的字面键输出浮出水面。

@docmd/template-* 注册表键不匹配

theme.template 设置为 summer@docmd/template-summer 会打印"Plugin ‘template-summer’ not found in official registry — manual installation required",即使该包已安装。
根本原因:自动安装注册表查找从包名中去除了 @docmd/plugin- 来推导注册表键,但没有去除 @docmd/template-
因此 @docmd/template-summer 解析为键 template-summer 而不是 summer
两个前缀现在都被正确去除。
自定义/第三方模板不受影响——它们完全绕过注册表查找。

--offline 模式:导航链接不导航 (#164)

点击 --offline 构建中导航侧边栏子链接时,仅切换父项的折叠状态,而不打开目标页面。
根本原因:data-spa-enabled 标志在离线构建中保持为 true,因此 SPA 单击处理程序绑定并使用 preventDefault() 消费单击事件。
file:// 没有用于 SPA 路径获取的 fetch,因此导航从未发生。
在两处修复:

  • data-spa-enabled 现在在 isOfflineMode 为 true 时为 false,因此 SPA 处理程序从不绑定,浏览器执行正常的整页导航。
  • 主 SPA 单击处理程序中的安全网检查在 file:// 协议下退出,因此即使标志错误,单击事件也不会被消费。
docmd build --offline   # SPA 现在自动禁用
# 0.8.7 上的变通方法:通过本地服务器打开,或设置 layout.spa: false

--offline 模式:全面的 file:// 鲁棒性修复

在修复 #164 时,审计了客户端 JS 接触 localStoragefetchXMLHttpRequest 或依赖 SPA/网络的每个其他位置。
所有 localStorage 写入(侧边栏折叠、主题切换、语言偏好、Cookie 同意)现在都被 try/catch 包裹,以便 file:// 构建在禁用本地存储的基于 Chromium 的浏览器中不会抛出。
版本切换器的 HEAD fetch 检查在 file:// 下被跳过(浏览器无论如何都会阻止),直接进入直接导航。
无样式 i18n 加载器中的 XHR 用 onerror 处理包裹。
URL 构建器已为离线模式附加 /index.html(未更改)。
结果:通过 file:// 打开已构建的站点现在可以端到端工作,没有 JS 错误,没有断开的导航,也没有静默的功能丢失。

📚 文档更新

更新了以下文档以反映上述修复:

  • 部署 → Docker —— 镜像详情表已更新以反映 root + su-exec 模型,"自定义工作目录和文件所有权"部分不再推荐 -u $(id -u):$(id -g) 变通方法(不再需要)。
  • docker/DOCKER.md —— 权限问题部分已重写。
  • 所有六个 README(en、de、es、fr、ja、zh)—— 移除了 -u $(id -u):$(id -g) 说明。

Docker 文档将在后续扩展以明确涵盖 init --force-y 标志以及非 TTY 行为。

🔒 安全基础(Phase 0 — 内部)

此版本落地了 0.8.8 中其余安全工作所基于的共享安全原语。
这些更改是非破坏性的、纯增量——尚无公共 API 更改。

新的 safePath 归属和 UserPath 品牌

safePath(root, relativePath) —— DEVELOPMENT-BENCHMARK.md S1 所需的路径遍历防护——已从 @docmd/api 移至 @docmd/utils,以便在没有 API 包依赖的情况下随处可用。
@docmd/api 继续重新导出它以保持向后兼容。

与之配套的:一个新的 UserPath TypeScript 品牌类型。
通过 asUserPath() 强制转换的任何字符串都被标记为用户控制输入;下游代码在调用任何 fs.* 之前应通过 safePath() 解析它。
该品牌在运行时是无操作(零成本),是类型系统信号。

集中的转义辅助函数

四个辅助函数现在从 @docmd/utils 提供:

  • escHtml —— HTML 文本和内联插值
  • attrEsc —— HTML 属性值的语义别名
  • jsonInject —— <script> 注入的 JSON.stringify 包装
  • scriptLiteral —— 内联 JS 字符串上下文的 JSON.stringify 包装

每个转义辅助函数都使用 XSS canary 负载进行单元测试(19 个断言)。
一条规范的路径意味着未来的贡献者不必重新发明 .replace(/&/g, '&amp;') 模式。

两条新的内部 ESLint 规则

  • docmd/no-unsafe-fs-read —— 当路径参数未通过 safePath() 解析时,标记 fs.readFilefs.writeFilefs.unlinkSync 等。在 lint 时捕获潜在的 CWE-22 违规。目前 warn 严重性;Phase 1 修复表面后提升为 error
  • docmd/require-verify-client —— 标记每个没有 verifyClient 回调的 new WebSocketServer({...})。立即设置为 error,以便两个现有的开发服务器 WebSocket 实例(开发服务器和工作区开发服务器)在 CI 中失败,直到它们获得 origin 检查。Phase 1 PR 1.D 添加了 verifyClient 回调。

初始 lint 通过揭示了 monorepo 中 141 个路径遍历警告和 2 个 verify-client 错误——这些成为 Phase 1 清理积压。

新的 security.html 配置键(默认翻转为 escape

markdown 解析器现在遵循具有三个值的 security.html 配置键:

行为 用例
'escape'(0.8.8 中的默认值) markdown 中的原始 HTML 被 HTML 转义并显示为文本 默认——对用户生成的内容安全
'allow' 原始 HTML 通过到渲染输出 故意使用原始 HTML(如 <details> 块)的文档站点
'strip' 原始 HTML 从渲染输出中完全删除 锁定内容(公司 wiki、受监管的行业)

行为变更:0.8.8 之前的默认值实际上是 'allow'。如果您的文档依赖原始 HTML(例如 <details><summary>、嵌入的 <iframe>),请将 security: { html: 'allow' } 添加到您的配置以保留旧行为。容器块(::: callout::: tabs::: steps::: cards 等)不受影响——它们由自定义规则解析,而不是原始 HTML。

端到端覆盖位于 scripts/brute-test-security.js(22 个断言跨越所有三个策略加上回退行为)。

路径遍历硬化(私人披露)

三个内部文件访问路径已针对路径遍历进行硬化。无公共 API 更改。请参阅安全公告了解受影响的版本。

修复位于 packages/core/src/commands/mcp.tspackages/api/src/hooks.tspackages/plugins/openapi/src/index.ts
三者都通过 @docmd/utils 中集中的 safePath 辅助函数进行,因此任何未来的代码路径都由同一原语把关。

scripts/brute-test-security.js 场景 S7 到 S10 中的端到端覆盖(15 个断言,包括项目根外的故意 canary 文件和恶意本地路径插件装置)。

开发服务器硬化(私人披露)

开发服务器 WebSocket 处理程序和 init 命令已硬化。
开发服务器现在默认绑定到 127.0.0.1(使用 DOCMD_HOST=0.0.0.0 公开到 LAN),两个 WebSocketServer 实例在接收握手前对循环回址允许列表验证 Origin 头。
init 命令在每次运行时不再发起任何网络调用——捆绑的 SKILL.md 内容是本地写入的;用户通过 npx docmd-skills [dir] 安装完整的智能体技能集。

scripts/brute-test-security.js 场景 S11 和 S12 中的端到端覆盖(16 个断言,包括截获的 fetch 调用的快照,证明零网络属性)。

HTML 转义硬化(私人披露)

插值到元标签和插件 head/body 注入点的用户可控值现在通过 @docmd/utils 中集中的 attrEsc / sanitizeHeadInjection 辅助函数进行转义。
og/twitter 元标签、链接 href 方案和插件生成的 head HTML 都通过同一规范的转义路径。
容器块(::: callout::: tabs 等)已由 Phase 0 默认的 html: 'escape' markdown-it 策略保护,不需要进一步更改。

scripts/brute-test-security.js 场景 S13 到 S15 中的端到端覆盖(13 个断言,包括一个项目内插件,其 generateMetaTags 返回 <script>javascript: 负载)。

插件输入验证(私人披露)

analytics 插件现在在注入 Google Analytics 脚本之前对 measurementId(必须匹配 G-XXXXXXXX)和 trackingId(必须匹配 UA-XXXXXXX-Y)进行格式验证。
无效的 ID 会被跳过并发出控制台警告,而不是被原始插值。
PWA 插件现在根据 CSS 颜色正则(#hexrgb()hsl() 或 CSS 命名颜色)对 themeColor 进行格式验证,并在无效输入时回退到默认的 #1e293b
两个验证器都使用 @docmd/utils 中的 scriptLiteral() / attrEsc() 进行纵深防御。

scripts/brute-test-security.js 场景 S16 和 S17 中的端到端覆盖(15 个断言:无效的 GA4 / UA / themeColor 负载、有效情况、默认回退路径)。

MCP 和捆绑的 SKILL.md 现在是 docmd-skills-aware

docmd-skills 作为独立的 npm 包发布(https://www.npmjs.com/package/docmd-skills)。由 docmd init 写入的捆绑 SKILL.md 和 MCP get_skill 后备现在都指向智能体的一行安装命令:

npx docmd-skills ~/.claude/skills   # 或 ~/.cursor/skills、./.skills 等

完整的智能体技能集(docmd-skillsdocmd-devdocmd-writer)由这一个命令安装。捆绑的 SKILL.md 足以满足 MCP get_skill 消费;npx docmd-skills 是通向丰富的、多文件技能集的路径,用于交互式智能体使用。

🛡️ 安全硬化(Phase 1 — 13 个 CVE 修复)

Battle Test 0.8.6 审计在核心 + 插件中发现了 13 个 CVE 级问题。
全部在 0.8.8 中修复。
在这些修复之前,对 docmd <cmd> 退出代码把关的 CI 流水线在悄悄通过损坏的构建。

亮点

  • 跨站 WebSocket 劫持(CSWSH)— N-S1:开发服务器 WebSocket 没有 verifyClient 回调,允许任何源打开连接并触发 threads:add-thread 文件写入。
    新的 verifyClient 工厂(packages/core/src/utils/ws-origin-guard.ts)默认允许回环以及任何显式的额外主机。
    dev.tsworkspace.ts 中的两个 WebSocket 调用站点现在以退出代码 1 拒绝未列入允许列表的源。

  • MCP read_doc 路径遍历 — T-S1 / S-3packages/core/src/commands/mcp.ts 中的 path.resolve(cwd, route) 接受绝对路径(如 /etc/passwd)和 ../ 转义。
    替换为 safePath(cwd, asUserPath(route)),它在遍历时抛出。safePath 原语位于 @docmd/utils,是所有文件解析调用的单一事实来源。

  • 通过本地子文件夹进行插件 RCE — T-S8packages/api/src/hooks.ts 中的 require.resolve(name, { paths: resolvePaths }) 搜索父目录,从同级文件夹加载任意 npm 包。
    本地路径插件现在通过 safePath(projectRoot, asUserPath(name)) 解析,该函数限制在项目根目录内。

  • 插件 head XSS — T-S7:从 generateMetaTags 返回 <script>alert(1)</script> 的插件将活动脚本注入到每个页面。
    @docmd/utils 中的新 sanitizeHeadInjection() 删除 <script> / <style> 并中和 javascript: / vbscript: URI。
    连接到 generator.ts 以用于 injectHead / injectBody

  • OpenAPI / Analytics / PWA / Threads XSS — T-S3 / S-4 / S-5 / S-7:所有四个插件现在在插入 HTML 之前对不受信任的配置输入(GA4 measurementId、UA trackingId、PWA themeColor)进行格式验证,并通过 @docmd/utils 中的 attrEsc() / scriptLiteral() 进行转义。

  • Init 向 GitHub 发起网络调用 — T-S5:已移除。
    docmd-skills 现在是一个独立的 npm 包——捆绑的 SKILL.md 是默认值;仅通过旧版 --with-skill 标志选择启用网络调用(为向后兼容而保留)。

  • 开发服务器默认绑定到 0.0.0.0 — T-S6:默认翻转为 127.0.0.1DOCMD_HOST 环境变量选择启用 LAN 绑定。
    TUI 在绑定到非回环地址时发出警告。

@docmd/utils 中的新辅助函数

函数 用途
escHtml(s) 转义 HTML 文本和 <script>
attrEsc(s) 转义 HTML 属性值
jsonInject(s) 安全注入到 JSON 上下文
scriptLiteral(s) 安全注入到 JS 字符串字面量
safePath(root, userPath) 路径遍历安全的文件解析
asUserPath(s) 品牌化的 UserPath 类型
normalisePath(s) POSIX 路径规范化
sanitizeHeadInjection(html) 删除 <script><style>,中和 javascript: / vbscript:

测试覆盖

scripts/brute-test-security.js —— 13 个场景中的 88 个断言(S-1、S-3、S-4、S-5、S-6、S-7、S-9、S-10、S-12、S-13、S-15、S-16、S-17)涵盖每个已记录的 CVE 路径。所有 13 个问题都在 CLI 表面进行回归测试——CI 运行自动捕获任何回归。

📦 容器解析器(Phase 2 — 5 个 bug 修复)

0.8.6 审计发现了五个静默容器解析器 bug(F1–F5),它们产生错误输出但没有错误也没有退出代码变化。
解析器现在是确定性的、大括号感知的、缩进感知的。

修复内容

  • F1 — 嵌套网格::: grids + N× ::: grid + 每个卡片一个 ::: 过去会将整个块转储为原始 <p>::: grids<br>::: grid<br>...<br>:::</p> 文本。规范化器添加显式的 ::: 关闭,以便 grids 规则匹配。
  • F2 — 自闭合标签损坏::: tag + 孤立 ::: 会损坏后续每个容器的深度计数器。自闭合名称(buttontagembed)受到尊重;孤立 :::[normaliser] WARNING 一起被删除。
  • F3 — 关闭类型不匹配::: callout ... ::: card ... ::: 默默重新扎根解析器。callout 在 EOF 处自动关闭,并带有 [normaliser] ERROR
  • F4 — 三重关闭擦除内容:裸露的 ::: 行作为 <p>:::</p> 段落泄漏到页面中。孤立项与警告一起被删除。
  • F5 — 5 层嵌套 callout 折叠:嵌套 callout 仅呈现最外层。规范化器为内层插入隐式关闭,以便每个 callout 规则递归匹配。

实现

新的 packages/parser/src/utils/container-normaliser.ts —— 旧版 robust-parser-shim/(146 行)的纯 TypeScript 移植。
线性扫描、缩进感知栈、无模块级可变状态。
在用户插件的 onBeforeParse 之前以及 markdown-it 的渲染之前,连接到 packages/parser/src/markdown-processor.tsprocessContentprocessContentAsync 中。
smartDedent 之前在 createDepthTrackingContainer 内重新运行,以便内部递归渲染看到平衡的输入。

确定性契约

规范化器是其输入的纯函数——没有 Date.now,没有 Math.random,没有模块级可变状态。
三层保护契约:

  1. 声明性 —— 文件头中的 DETERMINISM AUDIT 块记录模块不使用的四个非确定性原语。
  2. 经验性 —— packages/parser/test/container-normaliser.test.js 具有 60 个断言,包括 100 路并发解析和真正的 node:worker_threads 跨工作线程解析。
  3. 防回归 —— packages/core/src/engine/worker-parser.ts 中的 verifyDeterminismAtBoot() 在工作线程初始化时解析已知输入,并断言输出与冻结快照匹配。任何未来的非确定性都会在启动时使工作线程崩溃。

🔧 CLI 契约(Phase 3 — 7 个 bug 修复)

五个已记录的 CLI 失败路径默默退出 0;两个 validate 路径返回错误结果。
所有七个都已修复。

修复内容

命令 失败情况 修复前退出 修复后退出
docmd build 配置中存在未知插件 0 1
docmd migrate --docusaurus/etc. 标志 0 1
docmd migrate --help (它是一个帮助打印) 0 0(未变)
docmd remove <nonexistent> 插件不在注册表中 0 1
docmd validate --json 损坏的链接 0 1
docmd add <plugin> 在 TS/MJS/CJS 上 配置文件未触动 0 1 + 已修复
docmd remove <plugin> 插件条目留在配置中 0 1 + 已修复
docmd build(工作区) 原始堆栈跟踪,退出 0 0 1 + 干净的 TUI
docmd init 示例 教授 F2 陷阱模式 OK 已清理
docmd validate(尾随斜杠) /page/ 的误报 1 0

实现

  • 新的 packages/core/src/utils/exit.ts 导出 exitCodeFor(err)exitWithError(err, opts?)failWith(message, opts?) —— 退出代码契约的单一事实来源。
  • @docmd/api 中的新 getPluginLoadErrors() 单独跟踪插件 LOAD 失败与运行时钩子错误。build.tsloadPlugins() 之后检查它并抛出,由现有的 catch 块转换为退出 1。
  • 新的 packages/plugins/installer/src/config-editor.ts —— 一个大括号平衡扫描器,统一处理所有五种支持的配置格式(docmd.config.{json,js,mjs,cjs,ts})。旧版基于正则的注入器仅匹配 module.exports = {...},并对 export default defineConfig({...}) 默默地无操作。resolveConfigPath() 仅检查 .json/.js,将在项目的 .ts 旁边默默地脚手架一个新文件。

测试覆盖

scripts/brute-test.js —— 三个 Phase 3 部分的 22 个新断言(测试 26:退出代码,测试 27:插件添加/删除,测试 29:验证 + 工作区 + 初始化)。全部在 tests/cli-contracts/ 下。

🤖 OKF 插件(0.8.8 — 新核心插件)

一个新的核心插件为 AI 智能体消费生成一个 Open Knowledge Format bundle。
bundle 位于 site/okf/,包含类型化清单(okf.yaml)、交互式图形查看器,以及每个页面一个 markdown 文件。

开箱即用

site/okf/
├── okf.yaml              ← 类型化清单(bundle 摘要)
├── index.md              ← 按类型分组的 Karpathy 风格目录
├── graph.html            ← 交互式力导向查看器
├── graph.json            ← 图形数据(节点 + 边)
├── graph.js              ← 查看器运行时(原生,无 CDN 依赖)
├── graph.css             ← 查看器样式(支持主题)
├── concepts/<slug>.md    ← 每个页面一个 markdown 文件
└── _meta/
    ├── bundle.json       ← okf.yaml 的 JSON 镜像
    └── lint-report.txt   ← 生成过程中产生的警告

默认行为(0.8.8)

  • 默认启用 —— OKF 是一个核心插件,如 llmsseo
    构建自动加载它,即使用户尚未在其配置中添加 plugins.okf,也会生成 bundle。
    支持三种退出路径(falseenabled: false,或能力过滤器)。
  • 默认单语言 —— bundle 仅包含默认语言的页面。
    默认语言的文件位于 bundle 根目录(无 en/ 子目录)。
  • 类型推断 —— /api//guides/ 等下的页面会自动分类;其他所有内容回退到 concept

多语言选择启用

{
  "plugins": {
    "okf":  { "localeStrategy": "folders" },
    "llms": { "i18n": true }
  }
}

设置这两个标志后,默认语言的文件保留在 bundle 根目录(因此现有消费者不会中断),非默认语言获得 <locale>/ 子目录(OKF)或 .<locale> 后缀(LLMS):

site/okf/okf.yaml         ← 默认语言
site/okf/concepts/...md   ← 默认语言概念
site/okf/ja/okf.yaml     ← 日语
site/okf/ja/concepts/...md

site/llms.txt             ← 默认语言(en)
site/llms.ja.txt         ← 日语

测试覆盖

packages/plugins/okf/tests/index.test.ts(8 个测试)+ packages/plugins/okf/tests/brute.test.ts(15 个测试),包括 12 个真实 fs 场景。23/23 通过
新测试验证默认启用契约(没有 plugins.okf 条目的项目仍会生成 bundle)。

🧪 测试基础设施(分类)

暴力测试过去是 scripts/ 中两个文件中的 1000 多行,测试编号重复(Phase 3 PR 3.A 重用了 26、27、29)。
它们现在在 tests/ 下分类:

tests/
├── shared.js                                ← 装置辅助函数
├── runner.js                                ← 带 TUI 分段的编排器
├── cli-contracts/
│   ├── exit-codes.test.js                   ← F6、M-12
│   ├── plugin-add-remove.test.js            ← F7、M-3
│   └── validate-workspace.test.js           ← M-1、F8、F9
└── (每个包的测试保留在 packages/*/test/ 下)

编排器由 pnpm prep(或直接通过 node tests/runner.js)调用,并在其自己的 TUI 分段中运行每个套件。
--only=exit-codes,container-normaliser 过滤以仅运行已命名的套件。

结果

9 个分类文件中 342 个断言,全部通过(外加每个包的单元测试在 4 个包中贡献了 89 个,合计 13 个文件 / 431 个断言):

套件 结果 覆盖范围
cli-contracts/exit-codes 7 / 7 F6、M-12
cli-contracts/plugin-add-remove 9 / 9 F7、M-3(TS、MJS、JSON、CJS、JS)
cli-contracts/validate-workspace 6 / 6 M-1、F8、F9
packages/parser/test/container-normaliser 60 / 60 F1–F5 + 确定性
packages/utils/test/{path,html-escape} 31 / 31 safePathescHtml
scripts/brute-test-security 88 / 88 Phase 1 CVE 套件(13 个场景)
scripts/brute-test 114 / 114 29 个功能集成场景
packages/plugins/okf 23 / 23 单元 + 12 个真实 fs 场景 + 图形按需启用 / 弃用别名
packages/plugins/llms 6 / 6 默认语言 + i18n 选择启用(新)

更新 — pnpm prep 发布流水线加固

发布流水线增加了三项改进:

  • 每个包的单元测试现已纳入发布门禁。tests/runner.js 之后运行 pnpm -r run test --if-present,因此 @docmd/parser@docmd/utils@docmd/plugin-okf@docmd/plugin-mermaid 中的回归会以与分类运行器回归相同的方式使发布流水线失败。再也不会有任何包级测试套件被静默忽略。
  • --verbose / --full 标志。 默认输出保持折叠(每个测试步骤一行),干净的运行一目了然。传递 --verbose 可内联流式输出原始测试输出 —— 在迭代刚刚失败的测试并希望断言文本处于上下文中时非常有用。
  • 尾部 Summary 块。 干净运行时,每个 section 都会贡献一项统计信息(lint 计数、包总数、测试计数、Docker 版本),并在末尾渲染为一个绿色块。失败时,同一位置变为红色 Issues 块,列出每个失败及其文件 :行详情,并带上限溢出。查看发布结论只需要看一个地方。

🔌 插件作者说明

@docmd/plugin-llms@docmd/plugin-okf 在 0.8.8 中都是 默认启用的核心插件
构建自动加载它们。
要退出,请使用已对所有其他核心插件有效的 enabled: false 形式:

{
  "plugins": {
    "okf":  { "enabled": false },
    "llms": { "enabled": false }
  }
}

展望 —— Node 20+ 在 v0.9.0 中

下一个次要版本将最低要求提升到 Node.js 20+(从 18+),将引擎要求、发布的 Docker 运行时和 CI 环境统一到 Node 22 上。Node 18 于 2025 年 4 月达到生命周期终点;这使 docmd 保持在受支持的运行时上,并留有充足的空间。

此版本(0.8.8)不受影响 —— >=18 最低要求将保持到 0.9.0 发布。

当前(0.8.x) 下一个(0.9.0)
最低 Node 18+ 20+
Docker 基础镜像 20-alpine 20-alpine
Node 类型 ^24 ^24
CI 目标 24 24

受影响的版本

此版本中的所有修复都针对 0.8.7。没有破坏性更改——每个修复都是增量的或纯错误更正。