设计前逐项答复,直接决定画板与实现形态:
| # | 裁定项 | 结论 | 落点 |
|---|---|---|---|
| 1 | tab 名与位置 | Runtime · Notes · Skills · Source · About | 三处登记:shared/site-tabs.ts 插在 skills 与 about 之间 + 迁移种子 + web/lib/tabs.ts |
| 2 | 收录范围 | 含 rounds/,不含 lockfile;design/、.claude/、.agents/、.mcp.json、二进制不收 | 发布脚本里的闭集(tools/source-publish/),改 = 发版 |
| 3 | 单文件上限 | 256 KB | 服务端与脚本各校一遍(同 skills「写入时一次、调用前一次」) |
| 4 | agent 工具 | 三个分开,都要,默认开:source_list / source_read / source_search | 纯函数组,经 agent_ro;tool_config 种子 enabled = TRUE |
| 5 | 版本一致性 | 快照随每次生产版本发布,从源头解决 | 快照按 40 位 SHA 不可变;is_current 唯一;页面与读面回 SHA;不做运行 SHA 比对、不加 env、不画「快照落后」态 |
| 6 | 页面功能 | 不做站内搜索、zip、行号深链 | 画板上没有,代码里也不出现;/source* 无 .zip 分流规则 |
| 7 | 端 | 先桌面;移动端只保证工具可用,tab 与页面不做 | 移动 Tab Bar 过滤掉 source;/source* 不套移动壳,窄视口按桌面版式渲染;移动端记 BACKLOG |
| 8 | 发布方式 | 挂 dev.ps1 ship 自动,不手动 | ship 在远端 docker load 成功后调发布脚本;失败只警告不中止;另留 dev.ps1 source-publish 给首次发版与补发 |
派生取舍(实现者定,可推翻):
| 画板 | 页面 / 组件 | 核对项(2026-09-08 本机逐项核过,快照 57e890b / 319 个文件) |
|---|---|---|
| 2n Source 首页(README 态) | app/(site)/source/page.tsx + components/source/SourceBrowser.tsx | ✅ 面包屑 Source · 22px ClickPM/agent-xray + MIT 微徽标 · 一句话 · meta 快照 57e890b · 发布 2026-09-08 · 319 个文件 · 3.2 MB · 右上 GitHub ↗ 指 tree/<40 位 sha> · 264px 粘性树、六个根目录全收起、根文件带体积、README.md 选中 --bg-selected + 600 · 预览卡头部条 README.md · markdown · 3.8 KB · 60 行 · copy · README 按 2c 排版 · 页脚一行 |
| 2o 代码文件态 | app/(site)/source/[...path]/page.tsx + 同一组件 + SourceCodeView | ✅ 面包屑 Source / apps / api / agent(Source 是链接、目录段是文本)· 标题 tools.ts · GitHub ↗ 指 blob/57e890b…/apps/api/agent/tools.ts · 树只展开 apps › api › agent(about/ migrations/ 收起)· 头部条 apps/api/agent/tools.ts · typescript · 91.5 KB · 1,764 行 · 36px 行号列钉住、代码列一个 overflow-x:auto、行高 20.4px、三色高亮 · body.scrollWidth === innerWidth(页面不横滚)· copy 点击后 copied、1.5s 回落(真实点击实测)· 目录行展开 / 收起(经 React onClick 直接调用实测;Browser pane 隐藏态下坐标点击不落到行上,见「本轮实测」)· 带 (site) [series] 的路径 200 且面包屑段原样 |
| 2p 加载态 | [...path]/loading.tsx → SourceSkeleton + SourceLoadingNote | ✅ 骨架对位 2o(264 树列 / 12 行 / 36px 行号列实体边框 / 20 行代码条);「正在取 apps/api…」从 URL 读(客户端 usePathname)。首页 /source 刻意没有 loading(见「本轮实测」的嵌套 Suspense 坑) |
| 20 块导航五格 | GlobalNav(零改动,数据驱动) | ✅ Runtime · Notes · Skills · Source · About,Source 选中态与 2f 的 Skills 同一画法;移动 Tab Bar 仍四格(desktopOnly 过滤) |
| 2k-B | 既有 not-found | ✅ 目录地址 /source/apps/api、不存在的 /source/nope.ts、site_tab_set source false 后的 /source 都走 2k-B;主出口新增「回 Source 目录」(与 Skills / Notes 同一分支,tab 藏着时退到首页) |
| 1f/1g Tools 面板 | 数据驱动,零改动 | ✅ 纯函数组多出 source_list / source_read / source_search 三张卡,顺序在 notes 三张之后、skill_load 之前 |
不用 JSONB(规则 4 不涉及)。content 可空只在 staging 期间成立,commit 在同一事务里核完再翻 status,读面只查 status = 'current',永远读不到半成品。
| 工具 | 入参(Zod) | 行为 | 返回 |
|---|---|---|---|
| source_snapshot_begin | sha(40 位 hex)· files[]{path, sha256, bytes ≤ 262144, lines}(1–2000 项,path 过 checkSourcePath,kind 派生不出来即拒) | 建 / 重建该 sha 的 staging 快照(同 sha 重 begin = 清掉旧 staging 行);逐条与 current 快照比对 (path, sha256),相同的直接复制 content | { sha, total, reused, missing: string[] } |
| source_files_put | sha · files[]{path, content ≤ 262144}(1–200 项;批总量 ≤ 512 KB 服务端再算一遍) | 只收 manifest 里且 content 为 NULL 的 path;服务端算 sha256 必须等于 manifest 声明值;文本判据(UTF-8 / 无 NUL / 无孤立代理对)复用 skill-pack.ts | { sha, stored, pending } |
| source_snapshot_commit | sha | 一个事务:manifest 每一项 content 非 NULL 且 count 相符 → 现 current 改 staging → 本 sha 改 current → 删除其余快照;任一不符回滚并报缺哪些 path | { sha, fileCount, totalBytes, publishedAt, replaced } |
| source_snapshots_list | 无 | 全部快照(status / 计数 / 时间) | { current, snapshots[] } |
| source_snapshot_delete | sha | 只删 非 current(残留的 staging);删 current 拒,提示先发布另一版 | { sha, deleted } |
审计与幂等口径与 skills_* 相同(mcp_audit 每次调用一行,不记正文)。
dev.ps1 三处改动:
| 端点 | 用途 |
|---|---|
| GET /source | current 快照:{ sha, publishedAt, fileCount, totalBytes, files[]{path, kind, bytes, lines} }(平铺,前端建树;约 300 项)。无 current → not_found |
| GET /source/file?path= | 单文件:{ sha, path, kind, content, bytes, lines }。用 query 而不是通配路径:段里有 ()[],不赌路由器的解码边角。不存在 → not_found |
响应形状里 kind 写显式字面量联合(Encore 静态解析器不认索引访问类型,R-SKILLS 实测)。site_tab_set{source,false} 只藏导航与页面,两条端点照常服务(R-TABS 边界只到呈现层)。
| 工具 | 入参 | 行为 | 输出 |
|---|---|---|---|
| source_list | prefix?(≤ 300) | agent_ro READ ONLY 事务读 current 快照下以 prefix 开头的路径,≤ 400 条(超出提示收窄 prefix);首行 snapshot <sha7> · N files | 每行 path · bytes · lines |
| source_read | file · startLine? · endLine? | 读一个文件;一次 ≤ 400 行且按整行凑在结果正文上限内,超出提示用 startLine 续读;每行带行号前缀(便于回答时引用)。入参叫 file:path 字段名被 catalog.test 的泄露清单点名禁止(沙箱执行组的验收),不给「某个工具接受 path」留先例 | 首行 # <file> @ <sha7> · <kind> · L<a>–L<b> / N + 正文 |
| source_search | q(2–100)· prefix? | SQL 侧逐行:regexp_split_to_table(content, E'\n') WITH ORDINALITY + ILIKE(% _ 转义),LIMIT 41 判「还有更多」;statement_timeout 沿用 ro-db | ≤ 40 行 path:line: <行内容截 160 字> |
| 文档 | 改什么 |
|---|---|
| docs/security.md | 上面四条补记(先于代码) |
| docs/mcp.md | 工具总数 46 → 51;source_* 五个的入参 / 返回 / 安全要点;site_tab_set enum 加 source |
| docs/deploy-environments.md | 部署流加「ship 自动发快照 / 首次发版手动 source-publish」;冒烟清单加「/api/source 200 且 sha 前 7 位 == IMAGE_TAG」;--services 含 source |
| CLAUDE.md | 项目定位「四个 Tab」→ 五个;仓库结构加 apps/api/source/、tools/source-publish/;规则 8 修订段(已加);画板计数与「从 2q 顺延」(拉回设计稿时);dev.ps1 清单加 source-publish |
| ROUNDS.md | 功能边界段画板计数;进度表与拆解段(已加骨架) |
| design/README.md | 文件表 + 画板增删记录 + token 速查(拉回设计稿时) |
| apps/api/source/README.md(新)/ apps/api/agent/README.md / apps/api/mcp/README.md | 端点与工具清单 |
| docs/releases.md | 发版时一行 + 首次 source-publish 留证 |
| rounds/BACKLOG.md | 移动端 Source(已记) |
| # | 检查 | 命令 / 期望 |
|---|---|---|
| 1 | 编译与测试 | dev.ps1 check / dev.ps1 test 全绿;apps/web 另跑 npx tsc --noEmit(dev.ps1 不拦 web 的 TS 错) |
| 2 | 三处登记 | site-tabs.ts / 迁移种子 / web/lib/tabs.ts 一致;site_tab_set enum 下发五值;桌面导航五格与画板一字不差;移动 Tab Bar 仍四格 |
| 3 | 写面拒非法输入 | sha 非 40 位 hex / path 含 .. 或 \ 或超 12 段 / 派生不出 kind / 非 UTF-8 或含 NUL / 单文件 > 256 KB / 批 > 512 KB / manifest 外的 path / sha256 与声明不符 / commit 缺文件 / 删 current —— 逐条拒且原因可行动 |
| 4 | 增量与幂等 | 同 sha 发两遍:第二遍 missing = []、commit 不改 published_at 之外任何行;改一个文件再发:missing 恰为一项 |
| 5 | 原子性 | commit 中途失败(人为让一项缺 content)→ current 不变、staging 保留可补发;成功后 source_snapshots 只剩一行 |
| 6 | 读面 | /source 的 files 条目数 == 快照 file_count;/source/file?path=apps/web/app/(site)/notes/[series]/[chapter]/page.tsx 200 且正文一致;不存在 404;无 current 时 /source 404 |
| 7 | agent 权限 | sandbox.test.ts:agent_ro 能 SELECT 两表、写任一表 permission denied;三张 skills 表等既有 denied 清单不变 |
| 8 | agent 工具 | source_list 上限 400 条与 prefix;source_read 行区间 / 截断提示 / 行号前缀;source_search 大小写不敏感、%_ 转义、41 条判「更多」;工具目录含三项且分组 = 纯函数组 |
| 9 | 轨迹 | 假 provider 驱动真实 pi loop:一轮 source_read 的事件序列与 tool_call / tool_result 形状;/trace/stream 侧脱敏 7 项 0 命中 |
| 10 | 画板对照 | 2n / 2o / 2p 逐项(对照表填满);GitHub ↗ 为 blob/<40 位 sha>/<path>;copy 1.5s 回落;目录树折叠 / 展开 |
| 11 | 呈现开关 | site_tab_set source false:导航不渲染、/source* 不可达;/api/source 照常 200 |
| 12 | 发布脚本只看 git 树 | 工作树放一个 .secrets.local.cue 与一个未提交文件后发布:快照里没有它们;--check 对闭集外扩展名退出码 1 |
| 13 | dev.ps1 挂接 | build 跑 --check;ship 对未配置 host 只警告;对旧 api(无 source_*)报 -32601 后打印补发命令且 ship 退出码 0 |
| 14 | 镜像 | $hostedServices 含 source;生产冒烟 /api/source 200 且 sha 前 7 位 == IMAGE_TAG |
| 15 | 体积 | 快照文件数 / 总字节 / 最大文件 / 最深层级实测回填(基线:294 文件 / 3,153,856 字节 / 最大 109 KB / 8 层) |
| 16 | 文档 | docs/mcp.md 记 51 与 apps/api/mcp/tools.ts 注册数相等;security.md 四条补记在代码之前的提交里;gen client 已重跑 |
同一验收项针对性整改后连续 2 次验证仍不过 → 写 rounds/round-source/BLOCKED.md,停下呼人。禁止放宽验收标准自我通过。 findings 连续两轮落在同一块自建机制上(比如快照三段式写入)→ 停下回所有者重定方案,不堆补丁(「审查循环不是设计」)。