docs: the references and code the deletion commit left behind

68b031ae5 was pushed with only its deletions and renames staged. Its
`git add` named CHANGELOG.md, which was already removed, and the failed
pathspec skipped every modification after it. That commit alone does
not build:
- productdocs still imports reasonix/release-notes;
- repolint still calls checkSpecParity;
- the extension golden test reads the old fixture path.

This commit carries the changes 68b031ae5 describes: productdocs and
docs-manifest without the release catalog, repolint without spec-parity,
the golden test on testdata, the /docs tests, the link fixes, ci.yml,
CODEOWNERS, the standard, the theme generator and the lowered baseline.
Verified locally on this exact tree before 68b031ae5 was pushed:
go test ./... passes and make lint is clean.
This commit is contained in:
esengine committed 2026-09-16 21:50:24 -07:00
1 parent 68b031ae5d
commit 99c5360fd0
24 files changed
+130 -585

No files matched your search

-2
View File
@@ -14,13 +14,11 @@
/README.md @SivanCola @esengine
/README.zh-CN.md @SivanCola @esengine
/SECURITY.md @SivanCola @esengine
/CHANGELOG.md @SivanCola @esengine
# Contracts and processes the Studio line runs on.
/docs/DOCS_STANDARD.md @esengine @SivanCola
/docs/STUDIO_*.md @esengine @SivanCola
/docs/SESSION_MEMORY_RETRIEVAL.md @esengine @SivanCola
/docs/research/ @esengine @SivanCola
/release-notes/ @esengine @SivanCola
# Release signing: provider policy, its contract and the workflow that uses it.
-3
View File
@@ -461,11 +461,8 @@ jobs:
run: |
go run github.com/rhysd/actionlint/cmd/[email protected] \
-ignore 'label "windows-11-arm" is unknown' \
.github/workflows/prepare-release-notes.yml \
.github/workflows/release-studio.yml \
.github/workflows/release-verify-issues.yml \
.github/workflows/ci.yml
node scripts/check-single-release-public-contract.mjs
# The site/ auth client has security-sensitive redirect-validation logic
# (safeNext) covered by node:test unit tests. Those tests use only Node
+19 -19
View File
@@ -9,11 +9,11 @@
 · 
<a href="./docs/GUIDE.zh-CN.md">指南</a>
&nbsp;·&nbsp;
<a href="./docs/ACP.zh-CN.md">ACP</a>
<a href="./docs/ACP.md">ACP</a>
&nbsp;·&nbsp;
<a href="./docs/EXTENSIONS.zh-CN.md">扩展开发</a>
<a href="./docs/EXTENSIONS.md">扩展开发</a>
&nbsp;·&nbsp;
<a href="./docs/SPEC.zh-CN.md">规格</a>
<a href="./docs/SPEC.md">规格</a>
&nbsp;·&nbsp;
<a href="https://esengine.github.io/DeepSeek-Reasonix/">官方网站</a>
&nbsp;·&nbsp;
@@ -135,26 +135,26 @@ reasonix run "把 main.go 里的 TODO 实现掉"
CLI 进阶用法和详细配置见 **[CLI 命令参考](./docs/CLI.zh-CN.md)**、
**[指南](./docs/GUIDE.zh-CN.md)** 和
**[配置路径](./docs/CONFIG_PATHS.zh-CN.md)**。
**[配置路径](./docs/CONFIG_PATHS.md)**。
## 文档
- **开始使用:** [指南](./docs/GUIDE.zh-CN.md) ·
[CLI 命令参考](./docs/CLI.zh-CN.md) · [配置路径](./docs/CONFIG_PATHS.zh-CN.md) ·
[ACP 编辑器接入](./docs/ACP.zh-CN.md)
- **功能与排障:** [子智能体 Profile](./docs/SUBAGENT_PROFILES.zh-CN.md) ·
[Context Engine v2](./docs/SESSION_MEMORY_RETRIEVAL.zh-CN.md) ·
[能力诊断](./docs/CAPABILITY_DIAGNOSTICS.zh-CN.md) ·
[恢复与安全模式](./docs/RECOVERY.zh-CN.md) ·
[机器人使用指南](./docs/BOT_GUIDE.zh-CN.md) ·
[Checkpoints 与 rewind](./docs/CHECKPOINTS.zh-CN.md)
- **工程与迁移:** [规格](./docs/SPEC.zh-CN.md) ·
[任务合约与暂停策略](./docs/TASK_CONTRACT.zh-CN.md) ·
[工具合约](./docs/TOOL_CONTRACT.zh-CN.md) ·
[从 0.x 迁移](./docs/MIGRATING.zh-CN.md)
- **扩展开发:** [扩展概览](./docs/EXTENSIONS.zh-CN.md) ·
[插件包与 Manifest v1](./docs/PLUGIN_PACKAGES.zh-CN.md) ·
[Extension Protocol](./docs/EXTENSION_PROTOCOL.zh-CN.md) ·
[CLI 命令参考](./docs/CLI.zh-CN.md) · [配置路径](./docs/CONFIG_PATHS.md) ·
[ACP 编辑器接入](./docs/ACP.md)
- **功能与排障:** [子智能体 Profile](./docs/SUBAGENT_PROFILES.md) ·
[Context Engine v2](./docs/SESSION_MEMORY_RETRIEVAL.md) ·
[能力诊断](./docs/CAPABILITY_DIAGNOSTICS.md) ·
[恢复与安全模式](./docs/RECOVERY.md) ·
[机器人使用指南](./docs/BOT_GUIDE.md) ·
[Checkpoints 与 rewind](./docs/CHECKPOINTS.md)
- **工程与迁移:** [规格](./docs/SPEC.md) ·
[任务合约与暂停策略](./docs/TASK_CONTRACT.md) ·
[工具合约](./docs/TOOL_CONTRACT.md) ·
[从 0.x 迁移](./docs/MIGRATING.md)
- **扩展开发:** [扩展概览](./docs/EXTENSIONS.md) ·
[插件包与 Manifest v1](./docs/PLUGIN_PACKAGES.md) ·
[Extension Protocol](./docs/EXTENSION_PROTOCOL.md) ·
[Go SDK 与 starter](./sdk/go/README.md)
## Star 趋势
-2
View File
@@ -2,8 +2,6 @@
<a href="../README.md">README</a>
&nbsp;·&nbsp;
<a href="./ACP.zh-CN.md">简体中文</a>
&nbsp;·&nbsp;
<a href="./GUIDE.md">Guide</a>
&nbsp;·&nbsp;
<a href="https://agentclientprotocol.com/">ACP specification</a>
-2
View File
@@ -2,8 +2,6 @@
<a href="../README.md">README</a>
&nbsp;·&nbsp;
<a href="./BOT_GUIDE.zh-CN.md">简体中文</a>
&nbsp;·&nbsp;
<a href="./GUIDE.md">General guide</a>
> For desktop and CLI users. This guide explains how to connect Feishu, Lark,
-2
View File
@@ -1,7 +1,5 @@
# Capability diagnostics
<a href="./CAPABILITY_DIAGNOSTICS.zh-CN.md">简体中文</a>
&nbsp;·&nbsp;
<a href="./GUIDE.md">Guide</a>
&nbsp;·&nbsp;
<a href="./PLUGIN_PACKAGES.md">Plugin packages</a>
+4 -4
View File
@@ -412,10 +412,10 @@ reasonix doctor catalogs [--json]
reasonix catalogs reindex history [--dir PATH ...] [--json]
```
详见 [历史搜索 Catalog](./HISTORY_SEARCH_CATALOG.zh-CN.md)。
详见 [历史搜索 Catalog](./HISTORY_SEARCH_CATALOG.md)。
用量统计使用独立的可丢弃 rollup 投影:
reasonix catalogs reindex usage [--json]
详见 [用量 Catalog](./USAGE_CATALOG.zh-CN.md)。
详见 [用量 Catalog](./USAGE_CATALOG.md)。
### 记忆诊断与恢复
@@ -435,7 +435,7 @@ reasonix catalogs reindex usage [--json]
这些命令始终作用于当前 session controller。当会话位于远端主机上(`reasonix remote
connect` 或桌面的远程网页窗口)时,它们使用远程 memory catalog,绝不回退读取桌面本机
记忆。权限、自动召回、写入确认和迁移行为见
[Context Engine v2](./SESSION_MEMORY_RETRIEVAL.zh-CN.md)。
[Context Engine v2](./SESSION_MEMORY_RETRIEVAL.md)。
用量统计使用独立的可丢弃 rollup 投影:
@@ -447,7 +447,7 @@ reasonix catalogs reindex usage [--json]
```
详见 [用量 Catalog](./USAGE_CATALOG.zh-CN.md)。
详见 [用量 Catalog](./USAGE_CATALOG.md)。
### 记忆诊断与恢复
@@ -1,7 +1,5 @@
# Windows and Linux Desktop crash diagnostics runbook
<a href="./DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK.zh-CN.md">简体中文</a>
This is the release, privacy, performance, and root-cause checklist for the
cross-platform Desktop diagnostics pipeline. Windows build `17763` remains a
priority experiment, not a code whitelist. A diagnostic release does not by
+2 -2
View File
@@ -24,8 +24,8 @@ Current areas:
| Area | Paths | Primary | Backup |
| --- | --- | --- | --- |
| Kernel, Studio shell, tooling, standing instructions | `*` | @esengine | @SivanCola |
| User guides and references | `docs/`, `sdk/`, `README*`, `SECURITY.md`, `CHANGELOG.md` | @SivanCola | @esengine |
| Studio contracts and processes | `docs/STUDIO_*.md`, `docs/DOCS_STANDARD.md`, `docs/research/` | @esengine | @SivanCola |
| User guides and references | `docs/`, `sdk/`, `README*`, `SECURITY.md` | @SivanCola | @esengine |
| Studio contracts and processes | `docs/STUDIO_*.md`, `docs/DOCS_STANDARD.md` | @esengine | @SivanCola |
| Release notes and release signing | `release-notes/`, `.signpath/`, `release-studio.yml` | @esengine | @SivanCola |
## 2. Document classes
+1 -1
View File
@@ -1,6 +1,6 @@
# Extension Runtime v2 (Spatiotemporal Composability)
English overview of the Reasonix plugin/runtime v2 model. Chinese: [EXTENSION_RUNTIME_V2.zh-CN.md](./EXTENSION_RUNTIME_V2.zh-CN.md).
English overview of the Reasonix plugin/runtime v2 model.
## Protocol and manifest
+12 -19
View File
@@ -584,8 +584,7 @@ events.
The injected hook context is dynamic current-turn context. It does not change
the stable system prompt, memory prefix, or tool schema, though dynamic content
can still reduce cache reuse for that turn. The detailed desktop hook schema and
loading model are documented in [the Chinese desktop hooks guide](./DESKTOP_HOOKS.zh-CN.md).
can still reduce cache reuse for that turn.
## Keyboard shortcuts
@@ -1190,32 +1189,26 @@ MCP prompts also appear here as `/mcp__<server>__<prompt>`.
## Embedded documentation retrieval
Reasonix bundles the Markdown files from `docs/` and the reviewed
`release-notes/releases.json` catalog into each CLI and Desktop build. The
read-only `docs` tool searches that exact offline corpus with local BM25
retrieval and can read a complete matching section with source provenance. It
renders every release in both languages under paths such as
`changelog/v1.19.5.md` and `changelog/v1.19.5.zh-CN.md`, so questions about a
specific version, upgrades, fixes, or known risks work offline. The agent should
use the tool before web search or assumptions when a question concerns Reasonix
configuration, CLI/Desktop behavior, release history, permissions, MCP, memory,
Reasonix bundles the Markdown files from `docs/` into each CLI and Desktop
build. The read-only `docs` tool searches that exact offline corpus with local
BM25 retrieval and can read a complete matching section with source provenance.
The agent should use the tool before web search or assumptions when a question
concerns Reasonix configuration, CLI/Desktop behavior, permissions, MCP, memory,
recovery, providers, or maintainer workflows.
No setup, network connection, vector database, or embedding service is needed.
Search results prefer the query language while retaining explicit `en`,
`zh-CN`, audience, and catalog filters. Balanced and Delivery profiles expose the
tool directly; Economy connects the `docs` source on demand. Every result reports
the product version, immutable source revision, and corpus SHA-256 digest. Release
CI compiles the CLI and rejects publication unless that embedded manifest matches
the candidate's `docs/*.md`, `release-notes/releases.json`, and build identity. A
newer online `main-v2` page therefore cannot silently replace version-matched
local guidance or release history.
the product version, immutable source revision, and corpus SHA-256 digest. The
embedded manifest is bound to the candidate's `docs/*.md` and build identity, so
an online page cannot silently replace version-matched local guidance.
Use `/docs` to inspect the bundled corpus identity and usage examples without
calling a model. Use `/docs <question>` (for example,
`/docs 1.19.5 changelog`) to make Reasonix search the corpus locally first and
then pass the version-matched evidence to the currently configured AI for a
sourced answer. This command path does not depend on the model deciding to call
`/docs how to configure an MCP server`) to make Reasonix search the corpus
locally first and then pass the matched sections to the currently configured AI
for a sourced answer. This command path does not depend on the model deciding to call
the `docs` tool, while ordinary natural-language questions may still use the
tool automatically. Existing custom commands and compatible plugin or skill
aliases keep ownership of `/docs`; when that happens, CLI and Desktop normally
+24 -33
View File
@@ -12,15 +12,14 @@
## 目录
- [配置](#配置)
- [计费与展示币种](./BILLING.zh-CN.md)
- [计费与展示币种](./BILLING.md)
- [CLI 命令参考](./CLI.zh-CN.md)
- [环境变量](#环境变量)
- [Web 前端](#web-前端)
- [配置路径](./CONFIG_PATHS.zh-CN.md)
- [思考语言](./REASONING_LANGUAGE.zh-CN.md)
- [任务合约与暂停策略](./TASK_CONTRACT.zh-CN.md)
- [配置路径](./CONFIG_PATHS.md)
- [思考语言](./REASONING_LANGUAGE.md)
- [任务合约与暂停策略](./TASK_CONTRACT.md)
- [自定义 OpenAI-compatible provider](#自定义-openai-compatible-provider)
- [桌面端 Hooks](./DESKTOP_HOOKS.zh-CN.md)
- [快捷键](#快捷键)
- [权限与沙盒](#权限与沙盒)
- [能力诊断](#能力诊断)
@@ -35,13 +34,12 @@
优先级:**flag > `./reasonix.toml` > 用户配置文件 > 内置默认值**。从
**Reasonix v1.8.1** 开始,用户配置位于 macOS/Linux 的
`~/.reasonix/config.toml`,Windows 为 `%AppData%\reasonix\config.toml`;迁移和相关数据路径见
[配置路径](./CONFIG_PATHS.zh-CN.md)。标注为“仅用户/全局”的字段(包括 agent 轮数上限)不会被 `./reasonix.toml` 覆盖。
[配置路径](./CONFIG_PATHS.md)。标注为“仅用户/全局”的字段(包括 agent 轮数上限)不会被 `./reasonix.toml` 覆盖。
Provider 通过 `api_key_env` 命名密钥,真实密钥值保存在 CLI 与桌面端共用的
Reasonix 全局 `<Reasonix home>/.env`。项目 `.env`、home `.env`、继承的 shell 环境变量、旧 credentials 和系统 keyring 都不再作为 provider key 的运行时 fallback;旧凭据只作为迁移来源读取。项目 `.env` 仍会作为当前 workspace 范围内的 MCP/plugin 非 provider `${VAR}` 展开来源,但不会导入 provider key 或 Reasonix 控制变量。全局 `config.toml` 和 `.env` 的完整结构见
[配置路径](./CONFIG_PATHS.zh-CN.md)。
[配置路径](./CONFIG_PATHS.md)。
桌面端和 CLI 端的可见思考语言设置,见 [思考语言](./REASONING_LANGUAGE.zh-CN.md)。
桌面端 Hooks 的 JSON 配置、事件 key 和 payload 字段,见 [桌面端 Hooks](./DESKTOP_HOOKS.zh-CN.md)。
桌面端和 CLI 端的可见思考语言设置,见 [思考语言](./REASONING_LANGUAGE.md)。
`SessionStart` hook 可通过 stdout 或 `hookSpecificOutput.additionalContext` 把插件/工作流 bootstrap 内容一次性注入下一轮真实用户输入上下文,而不是写入稳定 system prompt。
插件包可通过 `hooks/session-start-codex` 或插件根目录 `CLAUDE.md` 提供该启动上下文;Claude 风格 `.claude/settings.json` command hooks 也会按同名事件映射到 Reasonix hooks。
@@ -255,7 +253,7 @@ Goal、由 `todo_write` 工具驱动的实时 Todo 面板、扩展发布的 stat
## 通过 ACP 接入编辑器
`reasonix acp` 把 Reasonix 作为 ACP v1 stdio agent 提供给编辑器和其他 host 客户端。
独立的 **[ACP 编辑器接入](./ACP.zh-CN.md)** 文档集中说明启动方式、能力协商、会话生命周期、
独立的 **[ACP 编辑器接入](./ACP.md)** 文档集中说明启动方式、能力协商、会话生命周期、
彼此独立的模型/工作/协作/审批控制轴、客户端文件与 terminal 能力、MCP server、权限请求,
以及 Reasonix 的回合中引导扩展。
@@ -573,7 +571,7 @@ CLI/TUI 文本输入可通过 `[ui].cursor_shape` 设置光标形状,支持 `u
| `Ctrl+O` | 切换详细 reasoning 显示 | 也可通过 `/verbose` 使用。 |
| `Ctrl+B` | 展开或收起较长 shell 输出 | 较长 shell 输出的提示行也可点击;全屏 TUI 开启鼠标接管时,文本选区由应用内处理。 |
| `/goal <目标>`、`/goal status`、`/goal pause`、`/goal resume`、`/goal clear` | 启动、查看、暂停、恢复或清除 Goal | Goal 默认持续执行;只有用户显式预算会按数字暂停。 |
| `/migrate`、`/migrate --from <旧目录>` | 重试旧数据迁移,或从指定 v0.x 来源导入 sessions | Windows v0.52 自定义安装/数据目录用 `--from`;该形式只导入 sessions。详见[配置路径](./CONFIG_PATHS.zh-CN.md)。 |
| `/migrate`、`/migrate --from <旧目录>` | 重试旧数据迁移,或从指定 v0.x 来源导入 sessions | Windows v0.52 自定义安装/数据目录用 `--from`;该形式只导入 sessions。详见[配置路径](./CONFIG_PATHS.md)。 |
选择器与审批:
@@ -670,7 +668,7 @@ compaction 摘要数,以及可用时的桌面端 token/cache telemetry。结
## 能力诊断
当 skill、斜杠命令、Hook、插件包、MCP 或 `AGENTS.md` 缺失、被覆盖或启动失败时,用统一只读诊断。完整参数、JSON schema 与 issue code 见
**[能力诊断](./CAPABILITY_DIAGNOSTICS.zh-CN.md)**。
**[能力诊断](./CAPABILITY_DIAGNOSTICS.md)**。
```bash
# 静态(默认):无网络、不启动 MCP 子进程
@@ -778,7 +776,7 @@ headers = { Authorization = "Bearer ${STRIPE_KEY}" }
启用的 MCP 服务器会在会话开始后于后台自动连接,因此工具上线期间聊天仍可正常使用。
用 `/mcp` 或桌面端 MCP 面板可刷新状态、重连服务器、查看失败原因,或在当前会话内禁用某个服务器。
若要跨 skills / hooks / 插件包 / MCP 做只读健康检查(不改配置),见
[能力诊断](./CAPABILITY_DIAGNOSTICS.zh-CN.md)
[能力诊断](./CAPABILITY_DIAGNOSTICS.md)
(`reasonix doctor capabilities` 或 **设置 → 诊断**)。
交互调用方只会为冷启动短暂等待;即使等待结束,共享启动仍会在后台继续,不会被杀掉后反复重启,
@@ -809,7 +807,7 @@ RPC 调用。两者都可按服务器覆盖。
内置 **Skill**(如 `/init`、`/explore`、`/test`、`/reasonix-guide`)也会出现在斜杠菜单,
并可通过 `run_skill` 调用(正文按需加载;只有索引行进入缓存稳定前缀)。配置或能力排障时
用 `/reasonix-guide`,它会引导运行 `reasonix doctor capabilities`(见
[能力诊断](./CAPABILITY_DIAGNOSTICS.zh-CN.md))。
[能力诊断](./CAPABILITY_DIAGNOSTICS.md))。
`/new` 会开启新会话,同时保存之前的 transcript 供历史记录和恢复使用;`/clear` 会二次确认,确认后丢弃当前上下文且不保存。
`/tree` 查看已保存的对话分支,`/branch [name]` 从当前对话末端分支,`/switch <id|name>`
切换到另一个分支。**自定义命令**
@@ -843,7 +841,7 @@ custom path 或包含更多手写结构的 Skill,避免丢失 frontmatter、re
`--model` 和 `--effort`,并写入与桌面设置页相同的按名称覆盖配置。
完整 CLI 参数、Skill 文件格式、模型优先级、安全行为和排障说明见
[子智能体 Profile](./SUBAGENT_PROFILES.zh-CN.md)。
[子智能体 Profile](./SUBAGENT_PROFILES.md)。
Context Engine v2 把上下文分成两个用途不同的层:
@@ -920,7 +918,7 @@ feedback、更新、重复项、敏感/超长内容,以及所有 `forget` 仍
旧事实会原地获得确定性 ID 和 revision 1;缺失 scope 时根据所在目录推导。Migration 幂等,
旧客户端仍能安全路由,旧 Memory v5 transcript 也继续可读。完整行为、隐私与 cache 契约见
[`Context Engine v2`](SESSION_MEMORY_RETRIEVAL.zh-CN.md)。
[`Context Engine v2`](SESSION_MEMORY_RETRIEVAL.md)。
```markdown
---
@@ -935,24 +933,20 @@ Review the staged diff. Focus on $ARGUMENTS, list bugs with file:line.
## 内置文档检索
Reasonix 会把 `docs/` 中的 Markdown 文档和已审查的 `release-notes/releases.json` 更新日志
目录随 CLI 和桌面端一起编译发布。只读 `docs` 工具通过本地 BM25 检索这份与当前安装版本
完全一致的离线语料,并可按命中的 `section_id` 读取完整章节及来源。每个版本都会生成
`changelog/v1.19.5.md`、`changelog/v1.19.5.zh-CN.md` 这类中英文虚拟文档,因此可以离线
查询指定版本的新增功能、升级说明、修复和已知风险。涉及 Reasonix 配置、CLI/桌面端行为、
版本历史、权限、MCP、记忆、恢复、Provider 或维护流程的问题,Agent 应先查询这里,再考虑
联网搜索或凭经验回答。
Reasonix 会把 `docs/` 中的 Markdown 文档随 CLI 和桌面端一起编译发布。只读 `docs` 工具
通过本地 BM25 检索这份与当前安装版本完全一致的离线语料,并可按命中的 `section_id` 读取
完整章节及来源。涉及 Reasonix 配置、CLI/桌面端行为、权限、MCP、记忆、恢复、Provider 或
维护流程的问题,Agent 应先查询这里,再考虑联网搜索或凭经验回答。
普通路径不需要设置、联网、向量数据库或 embedding 服务。搜索会优先匹配提问语言,同时支持
显式 `en`、`zh-CN`、受众和目录筛选。Balanced 与 Delivery 默认暴露该工具;Economy 会在需要时
按需连接 `docs` 来源。每次返回都会给出产品版本、不可变源码 revision 与语料 SHA-256 digest。
发布 CI 会实际编译 CLI;只有编译后的清单与候选提交的 `docs/*.md`、
`release-notes/releases.json` 和构建身份完全一致时才允许发布。因此,更新较快的在线
`main-v2` 页面不会静默覆盖与本地版本匹配的说明或更新历史。
编译后的清单与候选提交的 `docs/*.md` 和构建身份一一对应,因此在线页面不会静默覆盖与本地
版本匹配的说明。
直接输入 `/docs` 会在本地显示内置语料的版本、revision、digest 和使用示例,不调用模型。
输入 `/docs <问题>`(例如 `/docs 1.19.5 更新日志`)时,Reasonix 会先在本地完成检索,再把
与当前版本匹配的证据交给当前配置的 AI 生成带来源的回答。这个命令路径不依赖模型是否主动
输入 `/docs <问题>`(例如 `/docs 如何配置 MCP 服务器`)时,Reasonix 会先在本地完成检索,再把
命中的文档片段交给当前配置的 AI 生成带来源的回答。这个命令路径不依赖模型是否主动
选择 `docs` 工具;普通自然语言问题仍可由模型自动调用该工具。已有自定义命令以及兼容插件或
Skill 别名会继续拥有 `/docs`;发生冲突时,CLI 与桌面端通常会改为通过 `/reasonix:docs` 暴露
内置语料。如果这个限定名也已被占用,Reasonix 会选择下一个空闲的 `reasonix:` 限定后备名,
@@ -988,7 +982,7 @@ token、可选的显式 token 阈值和工作时间。每个目标 turn 结束
continue/complete/blocked;没有报告时由独立的有界 evaluator 判定一次,任何 evaluator
故障都会安全暂停目标而不是静默继续。
复杂任务建议把目标写成[任务合约](./TASK_CONTRACT.zh-CN.md):Context、Request、
复杂任务建议把目标写成[任务合约](./TASK_CONTRACT.md):Context、Request、
Output format、Constraints 和 Pause policy。Goal 模式会把这些部分当作自主执行的边界;
除非下一步需要不可逆或对外可见操作、任务范围变化,或必须由用户提供信息,否则会继续采用合理默认值推进,并在最后汇报假设与结果。
@@ -1167,11 +1161,8 @@ provider 可见核心工具面(直接读/bash/编辑/写入、后台 shell 生
`reasonix config reasoning-language auto|zh|en`。只有明确想为
reasoning-language 写项目级覆盖时,才给 shell 命令加 `--local`。
桌面端“协作方式”菜单里的计划模式、目标模式和“轻量 / 均衡 / 交付优先”三档运行模式的使用方法与注意事项,
见 [`COLLABORATION_MODES.zh-CN.md`](./COLLABORATION_MODES.zh-CN.md)。
桌面端“工具权限”里的询问、自动和 Yolo 模式的区别与使用场景,
见 [`TOOL_APPROVAL_MODES.zh-CN.md`](./TOOL_APPROVAL_MODES.zh-CN.md)。
见 [`TOOL_APPROVAL_MODES.md`](./TOOL_APPROVAL_MODES.md)。
分离 session(让各模型前缀缓存稳定)背后的取舍见
[`SPEC.md` §3.5](./SPEC.md#35-two-model-collaboration-coordinator)。
-2
View File
@@ -1,8 +1,6 @@
# Reasoning Language
<a href="./GUIDE.md">Guide</a>
&nbsp;·&nbsp;
<a href="./REASONING_LANGUAGE.zh-CN.md">简体中文</a>
`agent.reasoning_language` controls the preferred language of visible
reasoning or thinking text when a provider exposes it.
-2
View File
@@ -1,8 +1,6 @@
# Task Contracts and Pause Policy
<a href="./GUIDE.md">Guide</a>
&nbsp;·&nbsp;
<a href="./TASK_CONTRACT.zh-CN.md">Simplified Chinese</a>
Reasonix works best when nontrivial work is described as a **task contract**:
what the work is for, what action is requested, how the result should be
-2
View File
@@ -6,8 +6,6 @@ the home and task/workspace scenes. They **cannot** run CSS, JavaScript, fonts,
remote URLs, or SVG scripts. V1 packs remain valid and use the home image in
both scenes.
> Chinese: [THEME_PACK.zh-CN.md](./THEME_PACK.zh-CN.md)
## Goals (first release)
- Built-in styles, user themes, backgrounds, live preview, import/export, local library
-2
View File
@@ -1,7 +1,5 @@
# Tool Contract
<a href="./TOOL_CONTRACT.zh-CN.md">简体中文</a>
This document records the provider-visible contract for Reasonix compile-time built-in tools. It is generated from the same canonical schema path used by the runtime registry.
| Tool | Read-only | Description |
+5 -8
View File
@@ -46,18 +46,15 @@ func docsManifestCommand(args []string, cliVersion string) int {
return 1
}
if sourceDir := strings.TrimSpace(*verifySource); sourceDir != "" {
source, err := productdocs.SourceManifest(
os.DirFS(filepath.Join(sourceDir, "docs")),
os.DirFS(filepath.Join(sourceDir, "release-notes")),
)
source, err := productdocs.SourceManifest(os.DirFS(filepath.Join(sourceDir, "docs")))
if err != nil {
fmt.Fprintln(os.Stderr, "error: load source documentation:", err)
return 1
}
if manifest.Digest != source.Digest || manifest.Documents != source.Documents || manifest.Sections != source.Sections || manifest.ReleaseNotes != source.ReleaseNotes {
fmt.Fprintf(os.Stderr, "error: embedded docs do not match %s: embedded=%s/%d/%d/%d source=%s/%d/%d/%d\n",
sourceDir, manifest.Digest, manifest.Documents, manifest.Sections, manifest.ReleaseNotes,
source.Digest, source.Documents, source.Sections, source.ReleaseNotes)
if manifest.Digest != source.Digest || manifest.Documents != source.Documents || manifest.Sections != source.Sections {
fmt.Fprintf(os.Stderr, "error: embedded docs do not match %s: embedded=%s/%d/%d source=%s/%d/%d\n",
sourceDir, manifest.Digest, manifest.Documents, manifest.Sections,
source.Digest, source.Documents, source.Sections)
return 1
}
}
+4 -4
View File
@@ -1163,12 +1163,12 @@ func TestSubmitDocsShowsLocalOverviewAndGroundsModelTurn(t *testing.T) {
t.Fatalf("bare /docs should not start a model turn, inputs=%q", runner.inputs)
}
c.Submit("/docs 1.19.5 更新日志")
c.Submit("/docs REASONIX_HOME configuration paths")
waitForTurnDone(t, events)
if len(runner.inputs) != 1 {
t.Fatalf("/docs query model turns = %d, inputs=%q", len(runner.inputs), runner.inputs)
}
for _, want := range []string{"1.19.5 更新日志", "changelog/v1.19.5.zh-CN.md", "embedded_docs_search_results"} {
for _, want := range []string{"REASONIX_HOME configuration paths", "docs/CONFIG_PATHS.md", "embedded_docs_search_results"} {
if !strings.Contains(runner.inputs[0], want) {
t.Fatalf("grounded /docs prompt missing %q:\n%s", want, runner.inputs[0])
}
@@ -1220,12 +1220,12 @@ func TestSubmitQualifiedReasonixDocsPreservesExistingCommandAndUsesNextFallback(
}
waitIdle(t, c)
c.Submit("/reasonix:builtin:docs 1.19.5 update notes")
c.Submit("/reasonix:builtin:docs REASONIX_HOME configuration paths")
waitForTurnDone(t, events)
if len(runner.inputs) != 2 {
t.Fatalf("generated docs fallback model turns = %d, inputs=%q", len(runner.inputs), runner.inputs)
}
for _, want := range []string{"1.19.5 update notes", "changelog/v1.19.5.md", "embedded_docs_search_results"} {
for _, want := range []string{"REASONIX_HOME configuration paths", "docs/CONFIG_PATHS.md", "embedded_docs_search_results"} {
if !strings.Contains(runner.inputs[1], want) {
t.Fatalf("qualified docs prompt missing %q:\n%s", want, runner.inputs[1])
}
+1 -8
View File
@@ -4,7 +4,6 @@ import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"testing"
"reasonix/internal/extension"
@@ -12,13 +11,7 @@ import (
func fixturesDir(t *testing.T) string {
t.Helper()
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("runtime.Caller failed")
}
// internal/extension -> repo root
root := filepath.Clean(filepath.Join(filepath.Dir(file), "..", ".."))
return filepath.Join(root, "docs", "superpowers", "specs", "fixtures", "spatiotemporal-v2")
return filepath.Join("testdata", "spatiotemporal-v2")
}
func TestGoldenLifecycleStates(t *testing.T) {
+37 -98
View File
@@ -13,7 +13,6 @@ import (
"hash"
"io/fs"
"path"
"regexp"
"runtime/debug"
"sort"
"strings"
@@ -29,7 +28,6 @@ import (
productcontent "reasonix/docs"
"reasonix/internal/retrieval"
"reasonix/internal/tool"
releasenotes "reasonix/release-notes"
)
const (
@@ -41,14 +39,12 @@ const (
)
type document struct {
path string
source string
title string
locale string
audience string
releaseNote bool
releaseVersion string
sections []*section
path string
source string
title string
locale string
audience string
sections []*section
}
type section struct {
@@ -65,40 +61,32 @@ type section struct {
}
func (d *document) displayPath() string {
if d.releaseNote {
return d.path
}
return "docs/" + d.path
}
func (d *document) sourceRange(startLine, endLine int) string {
if d.releaseNote {
return fmt.Sprintf("%s rendered-lines=%d-%d", d.source, startLine, endLine)
}
return fmt.Sprintf("%s:%d-%d", d.source, startLine, endLine)
}
type catalog struct {
digest string
docs []*document
byPath map[string]*document
byID map[string]*section
sections []*section
df map[string]int
avgLen float64
releaseNotes int
digest string
docs []*document
byPath map[string]*document
byID map[string]*section
sections []*section
df map[string]int
avgLen float64
}
// Manifest identifies the exact documentation corpus bundled into one build.
// Version and Revision describe the product binary; Digest binds the sorted
// Markdown sources plus the structured release catalog consumed by retrieval.
// Markdown sources consumed by retrieval.
type Manifest struct {
Version string `json:"version"`
Revision string `json:"revision"`
Digest string `json:"digest"`
Documents int `json:"documents"`
Sections int `json:"sections"`
ReleaseNotes int `json:"release_notes"`
Version string `json:"version"`
Revision string `json:"revision"`
Digest string `json:"digest"`
Documents int `json:"documents"`
Sections int `json:"sections"`
}
type searchHit struct {
@@ -112,8 +100,6 @@ type docsTool struct {
}
var (
queryVersionRe = regexp.MustCompile(`(?i)\bv?([0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9a-z.-]+)?)\b`)
defaultOnce sync.Once
defaultCatalog *catalog
defaultLoadErr error
@@ -132,7 +118,7 @@ func NewTool() tool.Tool {
func loadDefaultCatalog() (*catalog, error) {
defaultOnce.Do(func() {
defaultCatalog, defaultLoadErr = loadCatalogWithReleaseNotes(productcontent.Content, releasenotes.Content)
defaultCatalog, defaultLoadErr = loadCatalog(productcontent.Content)
})
return defaultCatalog, defaultLoadErr
}
@@ -160,14 +146,14 @@ func CommandOverviewFor(language, commandName string) (string, error) {
}
m := c.manifest()
identity := fmt.Sprintf("version=%s revision=%s digest=%s", m.Version, m.Revision, m.Digest)
stats := fmt.Sprintf("documents=%d sections=%d releases=%d", m.Documents, m.Sections, m.ReleaseNotes)
stats := fmt.Sprintf("documents=%d sections=%d", m.Documents, m.Sections)
switch strings.ToLower(strings.TrimSpace(language)) {
case "zh", "zh-cn":
return fmt.Sprintf("内置 Reasonix 文档\n%s\n%s\n\n用法:%s <问题>\n示例:%s 1.19.5 更新日志\n\n搜索在本地完成,命中的版本匹配资料会交给当前配置的 AI 组织答案。", identity, stats, commandName, commandName), nil
return fmt.Sprintf("内置 Reasonix 文档\n%s\n%s\n\n用法:%s <问题>\n示例:%s 如何配置 MCP 服务器\n\n搜索在本地完成,命中的文档片段会交给当前配置的 AI 组织答案。", identity, stats, commandName, commandName), nil
case "zh-tw":
return fmt.Sprintf("內建 Reasonix 文件\n%s\n%s\n\n用法:%s <問題>\n範例:%s 1.19.5 更新日誌\n\n搜尋在本機完成,命中的版本匹配資料會交給目前設定的 AI 組織答案。", identity, stats, commandName, commandName), nil
return fmt.Sprintf("內建 Reasonix 文件\n%s\n%s\n\n用法:%s <問題>\n範例:%s 如何設定 MCP 伺服器\n\n搜尋在本機完成,命中的文件片段會交給目前設定的 AI 組織答案。", identity, stats, commandName, commandName), nil
default:
return fmt.Sprintf("Embedded Reasonix documentation\n%s\n%s\n\nUsage: %s <question>\nExample: %s 1.19.5 changelog\n\nSearch runs locally, then the version-matched evidence is passed to the configured AI to compose the answer.", identity, stats, commandName, commandName), nil
return fmt.Sprintf("Embedded Reasonix documentation\n%s\n%s\n\nUsage: %s <question>\nExample: %s how to configure an MCP server\n\nSearch runs locally, then the matched sections are passed to the configured AI to compose the answer.", identity, stats, commandName, commandName), nil
}
}
@@ -182,10 +168,10 @@ func SearchEmbedded(ctx context.Context, query string) (string, error) {
return (&docsTool{catalog: c}).search(ctx, query, "auto", "all", defaultLimit)
}
// SourceManifest computes the corpus identity from the source Markdown and
// structured release catalog used by a build.
func SourceManifest(docsFS, releaseNotesFS fs.FS) (Manifest, error) {
c, err := loadCatalogWithReleaseNotes(docsFS, releaseNotesFS)
// SourceManifest computes the corpus identity from the source Markdown used by
// a build.
func SourceManifest(docsFS fs.FS) (Manifest, error) {
c, err := loadCatalog(docsFS)
if err != nil {
return Manifest{}, err
}
@@ -195,12 +181,11 @@ func SourceManifest(docsFS, releaseNotesFS fs.FS) (Manifest, error) {
func (c *catalog) manifest() Manifest {
version, revision := buildIdentity()
return Manifest{
Version: version,
Revision: revision,
Digest: "sha256:" + c.digest,
Documents: len(c.docs),
Sections: len(c.sections),
ReleaseNotes: c.releaseNotes,
Version: version,
Revision: revision,
Digest: "sha256:" + c.digest,
Documents: len(c.docs),
Sections: len(c.sections),
}
}
@@ -337,7 +322,6 @@ func (t *docsTool) search(ctx context.Context, query, language, audience string,
preferredLanguage = detectQueryLanguage(query)
}
queryLower := strings.ToLower(query)
queryVersions := queryVersionRe.FindAllStringSubmatch(queryLower, -1)
hits := make([]searchHit, 0, len(t.catalog.sections))
for _, section := range t.catalog.sections {
if err := ctx.Err(); err != nil {
@@ -350,21 +334,9 @@ func (t *docsTool) search(ctx context.Context, query, language, audience string,
continue
}
score := retrieval.BM25Score(section.counts, section.length, queryTerms, t.catalog.df, len(t.catalog.sections), t.catalog.avgLen)
exactReleaseVersion := false
for _, match := range queryVersions {
if len(match) > 1 && strings.EqualFold(section.document.releaseVersion, match[1]) {
exactReleaseVersion = true
break
}
}
if score <= 0 && !exactReleaseVersion {
if score <= 0 {
continue
}
if exactReleaseVersion {
// Release-note virtual paths carry the exact requested version. Keep
// that stronger than generic terms such as "changelog" or "更新日志".
score += 100
}
for _, term := range queryTerms {
if section.headingHits[term] > 0 {
score += 0.35
@@ -444,11 +416,7 @@ func (t *docsTool) list(language, audience string) string {
return b.String()
}
func loadCatalog(fsys fs.FS) (*catalog, error) {
return loadCatalogWithReleaseNotes(fsys, nil)
}
func loadCatalogWithReleaseNotes(docsFS, releaseNotesFS fs.FS) (*catalog, error) {
func loadCatalog(docsFS fs.FS) (*catalog, error) {
entries, err := fs.ReadDir(docsFS, ".")
if err != nil {
return nil, err
@@ -479,35 +447,6 @@ func loadCatalogWithReleaseNotes(docsFS, releaseNotesFS fs.FS) (*catalog, error)
return nil, err
}
}
if releaseNotesFS != nil {
data, err := fs.ReadFile(releaseNotesFS, "releases.json")
if err != nil {
return nil, fmt.Errorf("read release-notes/releases.json: %w", err)
}
if !utf8.Valid(data) {
return nil, fmt.Errorf("read release-notes/releases.json: JSON is not valid UTF-8")
}
writeDigestRecord(hash, "release-notes/releases.json", data)
rendered, releaseCount, err := renderReleaseDocuments(data)
if err != nil {
return nil, err
}
for _, virtual := range rendered {
doc := parseDocumentWithParser(virtual.path, virtual.content, markdownParser)
doc.source = virtual.source
doc.locale = virtual.locale
doc.audience = "user"
doc.releaseNote = true
doc.releaseVersion = virtual.version
if len(doc.sections) == 0 {
return nil, fmt.Errorf("rendered release note %s contains no sections", virtual.path)
}
if err := c.addDocument(doc); err != nil {
return nil, err
}
}
c.releaseNotes = releaseCount
}
if len(c.docs) == 0 || len(c.sections) == 0 {
return nil, fmt.Errorf("embedded documentation corpus is empty")
}
@@ -762,9 +701,9 @@ func detectDocumentLanguage(name, content string) string {
func documentAudience(name string) string {
stem := strings.TrimSuffix(strings.TrimSuffix(name, ".md"), ".zh-CN")
switch strings.ToUpper(stem) {
case "RELEASING", "SIGNPATH_WINDOWS_ADMIN_SOP", "PRODUCTION_CHECKLIST", "THEME_ASSETS", "STUDIO_RELEASE", "DOCS_STANDARD":
case "THEME_ASSETS", "STUDIO_RELEASE", "DOCS_STANDARD":
return "maintainer"
case "CHECKPOINTS", "GOAL_ENFORCEMENT", "SESSION_REFERENCE_ARCHITECTURE", "SPEC", "TASK_CONTRACT", "TOOL_CONTRACT":
case "CHECKPOINTS", "SPEC", "TASK_CONTRACT", "TOOL_CONTRACT":
return "developer"
default:
return "user"
+13 -131
View File
@@ -12,7 +12,6 @@ import (
productcontent "reasonix/docs"
"reasonix/internal/provider"
releasenotes "reasonix/release-notes"
)
func TestEmbeddedCatalogLoadsDeterministically(t *testing.T) {
@@ -24,8 +23,8 @@ func TestEmbeddedCatalogLoadsDeterministically(t *testing.T) {
if err != nil {
t.Fatalf("loadCatalog second time: %v", err)
}
if len(first.docs) < 40 {
t.Fatalf("embedded docs = %d, want at least 40", len(first.docs))
if len(first.docs) < 30 {
t.Fatalf("embedded docs = %d, want at least 30", len(first.docs))
}
if len(first.sections) < len(first.docs) {
t.Fatalf("embedded sections = %d, docs = %d", len(first.sections), len(first.docs))
@@ -46,7 +45,7 @@ func TestEmbeddedAndSourceManifestsMatch(t *testing.T) {
if err != nil {
t.Fatalf("EmbeddedManifest: %v", err)
}
source, err := SourceManifest(productcontent.Content, releasenotes.Content)
source, err := SourceManifest(productcontent.Content)
if err != nil {
t.Fatalf("SourceManifest: %v", err)
}
@@ -56,9 +55,6 @@ func TestEmbeddedAndSourceManifestsMatch(t *testing.T) {
if !strings.HasPrefix(embedded.Digest, "sha256:") || embedded.Version == "" || embedded.Revision == "" {
t.Fatalf("manifest is missing build identity: %#v", embedded)
}
if embedded.ReleaseNotes < 10 {
t.Fatalf("embedded release notes = %d, want release history", embedded.ReleaseNotes)
}
}
func TestDocsCommandOverviewAndSearchUseEmbeddedCorpus(t *testing.T) {
@@ -66,7 +62,7 @@ func TestDocsCommandOverviewAndSearchUseEmbeddedCorpus(t *testing.T) {
if err != nil {
t.Fatal(err)
}
for _, want := range []string{"内置 Reasonix 文档", "version=", "revision=", "digest=sha256:", "/docs 1.19.5 更新日志"} {
for _, want := range []string{"内置 Reasonix 文档", "version=", "revision=", "digest=sha256:", "/docs 如何配置 MCP 服务器"} {
if !strings.Contains(overview, want) {
t.Fatalf("command overview missing %q:\n%s", want, overview)
}
@@ -79,14 +75,12 @@ func TestDocsCommandOverviewAndSearchUseEmbeddedCorpus(t *testing.T) {
t.Fatalf("qualified command overview used the wrong invocation:\n%s", qualified)
}
results, err := SearchEmbedded(context.Background(), "1.19.5 更新日志")
results, err := SearchEmbedded(context.Background(), "REASONIX_HOME configuration paths")
if err != nil {
t.Fatal(err)
}
firstStart := strings.Index(results, "\n1. ")
firstEnd := strings.Index(results, "\n2. ")
if firstStart < 0 || firstEnd <= firstStart || !strings.Contains(results[firstStart:firstEnd], "path=changelog/v1.19.5.zh-CN.md") || !strings.Contains(results, "digest=sha256:") {
t.Fatalf("command search did not use the embedded release catalog:\n%s", results)
if !strings.Contains(results, "path=docs/CONFIG_PATHS.md") || !strings.Contains(results, "digest=sha256:") {
t.Fatalf("command search did not use the embedded corpus:\n%s", results)
}
}
@@ -97,11 +91,8 @@ func TestExtensionDeveloperGuidesAreEmbeddedAndSearchable(t *testing.T) {
}
for _, path := range []string{
"EXTENSIONS.md",
"EXTENSIONS.zh-CN.md",
"EXTENSION_PROTOCOL.md",
"EXTENSION_PROTOCOL.zh-CN.md",
"PLUGIN_PACKAGES.md",
"PLUGIN_PACKAGES.zh-CN.md",
} {
if _, ok := c.byPath[path]; !ok {
t.Fatalf("extension developer guide %q is missing from the embedded catalog", path)
@@ -116,22 +107,14 @@ func TestExtensionDeveloperGuidesAreEmbeddedAndSearchable(t *testing.T) {
if !strings.Contains(english, "path=docs/EXTENSIONS.md") || !strings.Contains(english, "path=docs/PLUGIN_PACKAGES.md") {
t.Fatalf("English extension search did not expose the overview and manifest reference:\n%s", english)
}
chinese, err := tool.search(context.Background(), "Sidecar 插件 Manifest v2 扩展开发", "zh-CN", "all", 10)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(chinese, "path=docs/EXTENSIONS.zh-CN.md") || !strings.Contains(chinese, "path=docs/PLUGIN_PACKAGES.zh-CN.md") {
t.Fatalf("Chinese extension search did not expose the overview and manifest reference:\n%s", chinese)
}
}
func TestSourceManifestChangesWithMarkdownBytes(t *testing.T) {
first, err := SourceManifest(fstest.MapFS{"GUIDE.md": {Data: []byte("# Guide\n\nFirst.\n")}}, nil)
first, err := SourceManifest(fstest.MapFS{"GUIDE.md": {Data: []byte("# Guide\n\nFirst.\n")}})
if err != nil {
t.Fatal(err)
}
second, err := SourceManifest(fstest.MapFS{"GUIDE.md": {Data: []byte("# Guide\n\nSecond.\n")}}, nil)
second, err := SourceManifest(fstest.MapFS{"GUIDE.md": {Data: []byte("# Guide\n\nSecond.\n")}})
if err != nil {
t.Fatal(err)
}
@@ -167,14 +150,14 @@ func TestSourceManifestDigestUsesUnambiguousFileFraming(t *testing.T) {
first, err := SourceManifest(fstest.MapFS{
"A.md": {Data: firstA},
"B.md": {Data: firstB},
}, nil)
})
if err != nil {
t.Fatal(err)
}
second, err := SourceManifest(fstest.MapFS{
"A.md": {Data: secondA},
"B.md": {Data: secondB},
}, nil)
})
if err != nil {
t.Fatal(err)
}
@@ -221,107 +204,6 @@ func TestSearchRejectsOversizedQueriesBeforeRetrieval(t *testing.T) {
}
}
func TestReleaseNotesAreSearchableInBothLanguages(t *testing.T) {
c, err := loadCatalogWithReleaseNotes(productcontent.Content, releasenotes.Content)
if err != nil {
t.Fatal(err)
}
tl := &docsTool{catalog: c}
english, err := tl.search(context.Background(), "v1.19.5 usage statistics dashboard", "en", "user", 5)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(english, "path=changelog/v1.19.5.md") || !strings.Contains(english, "source=release-notes/releases.json#v1.19.5") {
t.Fatalf("English release search missing versioned changelog:\n%s", english)
}
chinese, err := tl.search(context.Background(), "v1.19.5 用量统计面板", "zh-CN", "user", 5)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(chinese, "path=changelog/v1.19.5.zh-CN.md") {
t.Fatalf("Chinese release search missing versioned changelog:\n%s", chinese)
}
sections, err := tl.read("", "changelog/v1.19.5.zh-CN.md")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(sections, "source: release-notes/releases.json#v1.19.5") {
t.Fatalf("release section listing missing JSON provenance:\n%s", sections)
}
section, err := tl.read("changelog/v1.19.5.zh-CN.md::s002", "")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(section, "source: release-notes/releases.json#v1.19.5 rendered-lines=") {
t.Fatalf("release section missing rendered provenance:\n%s", section)
}
}
func TestReleaseSearchDoesNotTreatPreviewAsExactStableVersion(t *testing.T) {
docsFS := fstest.MapFS{
"GUIDE.md": {Data: []byte("# Guide\n\nEmbedded documentation.\n")},
}
releaseNotesFS := fstest.MapFS{
"releases.json": {Data: []byte(`{
"releases": [
{"version":"1.19.0","title":{"en":"Stable","zh":"稳定版"},"summary":{"en":"Stable changelog","zh":"稳定版更新日志"}},
{"version":"1.19.0-preview.1","title":{"en":"Preview","zh":"预览版"},"summary":{"en":"Preview-only changelog","zh":"仅预览版更新日志"}}
]
}`)},
}
c, err := loadCatalogWithReleaseNotes(docsFS, releaseNotesFS)
if err != nil {
t.Fatal(err)
}
tl := &docsTool{catalog: c}
stable, err := tl.search(context.Background(), "1.19.0 changelog", "en", "user", 10)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(stable, "path=changelog/v1.19.0.md") {
t.Fatalf("stable release search missing exact changelog:\n%s", stable)
}
if strings.Contains(stable, "path=changelog/v1.19.0-preview.1.md") {
t.Fatalf("stable release search treated preview as an exact version match:\n%s", stable)
}
preview, err := tl.search(context.Background(), "1.19.0-preview.1 changelog", "en", "user", 10)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(preview, "path=changelog/v1.19.0-preview.1.md") {
t.Fatalf("preview release search missing exact changelog:\n%s", preview)
}
}
func TestSourceManifestChangesWithReleaseCatalogBytes(t *testing.T) {
docsFS := fstest.MapFS{"GUIDE.md": {Data: []byte("# Guide\n\nBody.\n")}}
firstNotes := fstest.MapFS{"releases.json": {Data: []byte(`{"releases":[{"version":"1.0.0","title":{"en":"First","zh":"第一"},"summary":{"en":"Body","zh":"正文"}}]}`)}}
secondNotes := fstest.MapFS{"releases.json": {Data: []byte(`{"releases":[{"version":"1.0.0","title":{"en":"Second","zh":"第二"},"summary":{"en":"Body","zh":"正文"}}]}`)}}
first, err := SourceManifest(docsFS, firstNotes)
if err != nil {
t.Fatal(err)
}
second, err := SourceManifest(docsFS, secondNotes)
if err != nil {
t.Fatal(err)
}
if first.Digest == second.Digest || first.ReleaseNotes != 1 || second.ReleaseNotes != 1 {
t.Fatalf("release catalog was not bound into manifests: first=%#v second=%#v", first, second)
}
}
func TestReleaseCatalogRejectsUnsafeVirtualPathVersion(t *testing.T) {
_, _, err := renderReleaseDocuments([]byte(`{"releases":[{"version":"../secret","title":{"en":"Bad","zh":"错误"},"summary":{"en":"Body","zh":"正文"}}]}`))
if err == nil || !strings.Contains(err.Error(), "invalid version") {
t.Fatalf("unsafe release version error = %v", err)
}
}
func TestSearchPrefersRelevantChineseAndEnglishSections(t *testing.T) {
c, err := loadCatalog(productcontent.Content)
if err != nil {
@@ -333,8 +215,8 @@ func TestSearchPrefersRelevantChineseAndEnglishSections(t *testing.T) {
if err != nil {
t.Fatalf("Chinese search: %v", err)
}
if !strings.Contains(zh, "docs/TOOL_APPROVAL_MODES.zh-CN.md") {
t.Fatalf("Chinese search did not find the permission guide:\n%s", zh)
if !strings.Contains(zh, "path=docs/CLI.zh-CN.md") || !strings.Contains(zh, "权限") {
t.Fatalf("Chinese search did not find the permission reference:\n%s", zh)
}
en, err := tl.search(context.Background(), "REASONIX_HOME configuration paths", "auto", "all", 5)
+2 -23
View File
@@ -1,4 +1,4 @@
"""Emit docs/THEME_ASSETS.md + .zh-CN.md — provenance & licence ledger for the
"""Emit docs/THEME_ASSETS.md — provenance & licence ledger for the
official theme assets. Reads hashes from out/report.json (produced by build.py).
"""
from __future__ import annotations
@@ -68,30 +68,9 @@ def main():
en.append(f"| {name} (`{tid}`) | `{prompt}` | `{bg}` | `{pv}` |")
en.append("")
zh = []
zh.append("# 官方主题素材来源与许可记录\n")
zh.append("八款 Reasonix 官方主题的全部图片均为**原创**,由 `scripts/official-theme-art/` 中的脚本")
zh.append("从零程序化生成(numpy + Pillow,固定随机种子,可完全复现)。视觉*方向*参考了 MIT 许可的")
zh.append("[Codex-Dream-Skin](https://github.com/Fei-Away/Codex-Dream-Skin) 概念图库,但是:\n")
zh.append("- **未复制参考项目或任何第三方素材的任何像素**、布局、界面元素、文字、标志或水印;")
zh.append(" 所有背景均为代码重新生成的独立素材。")
zh.append("- 图中人物均为生成器绘制的**原创虚构成年人**:插画女性(玫瑰晨光)、吉祥程序员(鸿运工坊)、")
zh.append(" 读者(鼠尾草清风)、动漫人物(灵感手账)、剪影女性(紫曜星夜)、数字表演者(青岚舞台)、")
zh.append(" 绅士(黑金序曲)。赤曜新城不含人物。")
zh.append("- 背景中不含窗口、侧栏、卡片、按钮、输入框或可读文字,并已去除 EXIF 等元数据。\n")
zh.append("素材随 Reasonix 仓库以 MIT 许可发布,© Reasonix Contributors。人工审核:Reasonix Contributors(发布 PR 审核)。\n")
zh.append(f"生成日期:{today}\n")
zh.append("| 主题 | 生成器(最终提示词等价物) | background.webp SHA-256 | preview.webp SHA-256 |")
zh.append("| --- | --- | --- | --- |")
for tid, name, prompt, bg, pv in rows:
zh.append(f"| {name}(`{tid}`) | `{prompt}` | `{bg}` | `{pv}` |")
zh.append("")
with open(os.path.join(DOCS, "THEME_ASSETS.md"), "w") as f:
f.write("\n".join(en))
with open(os.path.join(DOCS, "THEME_ASSETS.zh-CN.md"), "w") as f:
f.write("\n".join(zh))
print("wrote docs/THEME_ASSETS.md + .zh-CN.md")
print("wrote docs/THEME_ASSETS.md")
if __name__ == "__main__":
+5 -211
View File
@@ -6,9 +6,9 @@
"codeowners": 0,
"commented-code": 0,
"complexity": 1495,
"doc-language": 33,
"doc-owner": 91,
"doc-prose": 2755,
"doc-language": 0,
"doc-owner": 44,
"doc-prose": 2375,
"error-text": 9,
"essay": 3207,
"file-size": 31285,
@@ -21,7 +21,6 @@
"orphan": 80,
"refusal-path": 0,
"release-note": 661,
"spec-parity": 0,
"struct-state": 32,
"wire-parity": 0
},
@@ -29,10 +28,6 @@
".github/pull_request_template.md": {
"doc-prose": 1
},
"CHANGELOG.md": {
"doc-owner": 1,
"doc-prose": 94
},
"CONTRIBUTING.md": {
"doc-owner": 1,
"doc-prose": 1
@@ -40,9 +35,6 @@
"README.md": {
"doc-prose": 1
},
"README.zh-CN.md": {
"doc-prose": 1
},
"REASONIX.md": {
"doc-prose": 92
},
@@ -99,46 +91,22 @@
"doc-owner": 1,
"doc-prose": 13
},
"docs/ACP.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 3
},
"docs/BILLING.md": {
"doc-owner": 1,
"doc-prose": 2
},
"docs/BILLING.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/BOT_GUIDE.md": {
"doc-owner": 1,
"doc-prose": 27
},
"docs/BOT_GUIDE.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 11
},
"docs/CAPABILITY_DIAGNOSTICS.md": {
"doc-owner": 1,
"doc-prose": 5
},
"docs/CAPABILITY_DIAGNOSTICS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 3
},
"docs/CHECKPOINTS.md": {
"doc-owner": 1,
"doc-prose": 4
},
"docs/CHECKPOINTS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 1
},
"docs/CLI.md": {
"doc-owner": 1,
"doc-prose": 50
@@ -147,42 +115,18 @@
"doc-owner": 1,
"doc-prose": 17
},
"docs/COLLABORATION_MODES.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/CONFIG_PATHS.md": {
"doc-owner": 1,
"doc-prose": 30
},
"docs/CONFIG_PATHS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 12
},
"docs/DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK.md": {
"doc-owner": 1,
"doc-prose": 11
},
"docs/DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 2
},
"docs/DESKTOP_HOOKS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 10
},
"docs/EXTENSIONS.md": {
"doc-owner": 1,
"doc-prose": 16
},
"docs/EXTENSIONS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 4
},
"docs/EXTENSION_PROTOCOL.generated.md": {
"doc-owner": 1
},
@@ -190,121 +134,54 @@
"doc-owner": 1,
"doc-prose": 20
},
"docs/EXTENSION_PROTOCOL.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 6
},
"docs/EXTENSION_RUNTIME_V2.md": {
"doc-owner": 1,
"doc-prose": 13
},
"docs/EXTENSION_RUNTIME_V2.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 6
},
"docs/EXTENSION_RUNTIME_V2_PERF.md": {
"doc-owner": 1,
"doc-prose": 9
},
"docs/EXTENSION_RUNTIME_V2_PERF.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 3
},
"docs/GOAL_ENFORCEMENT.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 8
},
"docs/GUIDE.md": {
"doc-owner": 1,
"doc-prose": 372
"doc-prose": 366
},
"docs/GUIDE.zh-CN.md": {
"doc-owner": 1,
"doc-prose": 178
"doc-prose": 173
},
"docs/HISTORY_SEARCH_CATALOG.md": {
"doc-owner": 1,
"doc-prose": 8
},
"docs/HISTORY_SEARCH_CATALOG.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/MIGRATING.md": {
"doc-owner": 1,
"doc-prose": 44
},
"docs/MIGRATING.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 3
},
"docs/PLUGIN_PACKAGES.md": {
"doc-owner": 1,
"doc-prose": 103
},
"docs/PLUGIN_PACKAGES.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 69
},
"docs/REASONING_LANGUAGE.md": {
"doc-owner": 1,
"doc-prose": 3
},
"docs/REASONING_LANGUAGE.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/REASONING_PROVIDERS.md": {
"doc-owner": 1,
"doc-prose": 23
},
"docs/REASONING_PROVIDERS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 11
},
"docs/RECOVERY.md": {
"doc-owner": 1,
"doc-prose": 2
},
"docs/RECOVERY.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/RELEASING.md": {
"doc-owner": 1,
"doc-prose": 7
},
"docs/SESSION_MEMORY_RETRIEVAL.md": {
"doc-owner": 1,
"doc-prose": 15
},
"docs/SESSION_MEMORY_RETRIEVAL.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 3
},
"docs/SESSION_REFERENCE_ARCHITECTURE.md": {
"doc-owner": 1
},
"docs/SIGNPATH_WINDOWS_ADMIN_SOP.md": {
"doc-owner": 1
},
"docs/SPEC.md": {
"doc-owner": 1,
"doc-prose": 341
},
"docs/SPEC.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 78
},
"docs/STUDIO_SHELL_BOUNDARIES.md": {
"doc-owner": 1,
"doc-prose": 2
@@ -313,113 +190,34 @@
"doc-owner": 1,
"doc-prose": 4
},
"docs/SUBAGENT_PROFILES.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/SUBAGENT_PROGRESS.md": {
"doc-owner": 1,
"doc-prose": 5
},
"docs/SUBAGENT_PROGRESS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 1
},
"docs/TASK_CONTRACT.md": {
"doc-owner": 1,
"doc-prose": 1
},
"docs/TASK_CONTRACT.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/THEME_ASSETS.md": {
"doc-owner": 1,
"doc-prose": 2
},
"docs/THEME_ASSETS.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/THEME_PACK.md": {
"doc-owner": 1,
"doc-prose": 5
},
"docs/THEME_PACK.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 1
},
"docs/TOOL_APPROVAL_MODES.md": {
"doc-owner": 1,
"doc-prose": 10
},
"docs/TOOL_APPROVAL_MODES.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 1
},
"docs/TOOL_CONTRACT.md": {
"doc-owner": 1,
"doc-prose": 41
},
"docs/TOOL_CONTRACT.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1,
"doc-prose": 20
},
"docs/USAGE_CATALOG.md": {
"doc-owner": 1,
"doc-prose": 2
},
"docs/USAGE_CATALOG.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/WINDOWS_LTSC_2019_CRASH_RUNBOOK.md": {
"doc-owner": 1
},
"docs/WINDOWS_LTSC_2019_CRASH_RUNBOOK.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/production_checklist.md": {
"doc-owner": 1
},
"docs/production_checklist.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"docs/research/cache-aware-compaction-design.md": {
"doc-owner": 1
},
"docs/research/goal-verification-authority.md": {
"doc-owner": 1
},
"docs/research/subagent-handoff-baseline.md": {
"doc-owner": 1,
"doc-prose": 3
},
"docs/superpowers/audits/2026-06-30-autoresearch-runtime-verification.md": {
"doc-owner": 1,
"doc-prose": 2
},
"docs/superpowers/plans/2026-06-29-autoresearch-runtime-implementation.md": {
"doc-owner": 1,
"doc-prose": 1
},
"docs/superpowers/plans/billing-currency-acceptance.md": {
"doc-owner": 1
},
"docs/superpowers/specs/2026-06-29-autoresearch-runtime-design.md": {
"doc-owner": 1,
"doc-prose": 2
},
"docs/superpowers/specs/2026-08-07-spatiotemporal-composability-v2.md": {
"doc-owner": 1,
"doc-prose": 3
},
"internal/acp/clientio.go": {
"essay": 4
},
@@ -2043,10 +1841,6 @@
"sdk/go/examples/starterextension/README.md": {
"doc-owner": 1
},
"sdk/go/examples/starterextension/README.zh-CN.md": {
"doc-language": 1,
"doc-owner": 1
},
"sdk/go/sdk.go": {
"essay": 27,
"file-size": 367
+1 -3
View File
@@ -37,7 +37,6 @@ const (
ruleRefusalPath = "refusal-path"
ruleErrorText = "error-text"
ruleClaudeDialect = "claude-dialect"
ruleSpecParity = "spec-parity"
ruleWireParity = "wire-parity"
ruleFrontendParity = "frontend-parity"
ruleFlatView = "flat-view"
@@ -53,7 +52,7 @@ var allRules = []string{
ruleEssay, ruleBanner, ruleMarker, ruleDeadCode,
ruleNarrative, ruleFileSize, ruleLayering,
ruleFuncSize, ruleComplexity, ruleStructState, ruleRefusalPath, ruleErrorText,
ruleClaudeDialect, ruleSpecParity, ruleWireParity, ruleOrphan,
ruleClaudeDialect, ruleWireParity, ruleOrphan,
ruleFrontendParity, ruleFlatView, ruleBuildArtifact,
ruleCodeOwners, ruleDocOwner, ruleDocProse, ruleDocLanguage, ruleReleaseNote,
}
@@ -197,7 +196,6 @@ func run(root string) ([]Finding, error) {
}
findings = append(findings, orphans.findings()...)
findings = append(findings, checkLayering(imports)...)
findings = append(findings, checkSpecParity(root)...)
findings = append(findings, checkBuildArtifacts(root)...)
findings = append(findings, checkCodeOwners(root)...)
findings = append(findings, checkDocs(root)...)