From 3d0ad3db3e4002c565f7d592a152429e306843f7 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Tue, 14 Jul 2026 07:23:53 -0700 Subject: [PATCH] =?UTF-8?q?docs(i18n):=20cookbook=20batch=20=E2=80=94=20si?= =?UTF-8?q?x=20bilingual=20pairs=20via=20the=20committed=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 六篇 cookbook 全部配对(adding-a-package / adding-a-tool / adding-a-vendored-package / adding-an-llm-adapter / extension-cookbook / responding-to-pr-review-on-a-stack):译文由 进仓流水线产出(translation-prompt.md 全占位符渲染 + 5 组金标 few-shot),双侧语言切换行齐备,六篇加入 manifest required(15)。 另补 prompt 的 When translating into English 一节(此前为占位): 标点/术语双向绑定/主语显化/惯用语概念还原/语域,与 translation-rules 的中文先行条款一致。 --- docs/cookbook/adding-a-package.i18n.yaml | 6 + docs/cookbook/adding-a-package.md | 2 + docs/cookbook/adding-a-package.zh.md | 83 ++++++++++++ docs/cookbook/adding-a-tool.i18n.yaml | 6 + docs/cookbook/adding-a-tool.md | 2 + docs/cookbook/adding-a-tool.zh.md | 85 ++++++++++++ .../adding-a-vendored-package.i18n.yaml | 6 + docs/cookbook/adding-a-vendored-package.md | 2 + docs/cookbook/adding-a-vendored-package.zh.md | 60 +++++++++ docs/cookbook/adding-an-llm-adapter.i18n.yaml | 6 + docs/cookbook/adding-an-llm-adapter.md | 2 + docs/cookbook/adding-an-llm-adapter.zh.md | 45 +++++++ docs/cookbook/extension-cookbook.i18n.yaml | 6 + docs/cookbook/extension-cookbook.md | 2 + docs/cookbook/extension-cookbook.zh.md | 123 ++++++++++++++++++ ...sponding-to-pr-review-on-a-stack.i18n.yaml | 6 + .../responding-to-pr-review-on-a-stack.md | 2 + .../responding-to-pr-review-on-a-stack.zh.md | 26 ++++ docs/i18n/translation-prompt.md | 5 + scripts/translation-pairing.manifest.json | 6 + 20 files changed, 481 insertions(+) create mode 100644 docs/cookbook/adding-a-package.i18n.yaml create mode 100644 docs/cookbook/adding-a-package.zh.md create mode 100644 docs/cookbook/adding-a-tool.i18n.yaml create mode 100644 docs/cookbook/adding-a-tool.zh.md create mode 100644 docs/cookbook/adding-a-vendored-package.i18n.yaml create mode 100644 docs/cookbook/adding-a-vendored-package.zh.md create mode 100644 docs/cookbook/adding-an-llm-adapter.i18n.yaml create mode 100644 docs/cookbook/adding-an-llm-adapter.zh.md create mode 100644 docs/cookbook/extension-cookbook.i18n.yaml create mode 100644 docs/cookbook/extension-cookbook.zh.md create mode 100644 docs/cookbook/responding-to-pr-review-on-a-stack.i18n.yaml create mode 100644 docs/cookbook/responding-to-pr-review-on-a-stack.zh.md diff --git a/docs/cookbook/adding-a-package.i18n.yaml b/docs/cookbook/adding-a-package.i18n.yaml new file mode 100644 index 0000000000..e74b42a540 --- /dev/null +++ b/docs/cookbook/adding-a-package.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +adding-a-package.md: ff05f130e4ae5499c3f7b3ef8a73b1beeb00938d +adding-a-package.zh.md: 7b612ee1e7caf06075cb25851b61706ce7ac31db diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index db0684b762..ff05f130e4 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -1,5 +1,7 @@ # Cookbook: adding a workspace package +English | [中文](adding-a-package.zh.md) + The file-by-file checklist for a new `@deepseek-ai/dsh-` package. (Verified by the bash and adapter packages; if it drifts, fix it here.) ## 1. Create the package diff --git a/docs/cookbook/adding-a-package.zh.md b/docs/cookbook/adding-a-package.zh.md new file mode 100644 index 0000000000..7b612ee1e7 --- /dev/null +++ b/docs/cookbook/adding-a-package.zh.md @@ -0,0 +1,83 @@ +# 实操手册:添加 workspace package + +[English](adding-a-package.md) | 中文 + +为新建 `@deepseek-ai/dsh-` package 提供的逐文件清单。(已通过 bash 和 adapter package 验证;如有漂移,请在此修正。) + +## 1. 创建 package + +``` +packages/// + package.json # copy from packages/core/tools, adjust name/description/deps + tsconfig.json # extends ../../../tsconfig.base.json, rootDir src, + # outDir lib/types, references: ../../../vendor/cosmokit, + # ../../../vendor/cordis (+ ../../../vendor/schemastery if + # you use Config, + ../..// for each dsh dep) + src/index.ts # service default export or plugin (name/inject/apply/Config) + tests/.spec.ts + README.md # service API, events, extension points, design notes, + # + gated Model Experience context blocks or short sentence + # + the gated "Known Limitations and Deferred Work" section + # (or a whitelist entry in scripts/verify-package-readme-limitations.ts) +``` + +当已有分组与 package 的角色匹配时,选择该分组(`core`、`llm`、`bash`、`compact`、`subagent`、`todo`、`session-persistence`、`ui`、`util` 或 `support`)。允许新建分组,但分组只是纯容器:没有 `package.json`,没有源文件,package 仍然恰好位于其下一层。 + +package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-constraints.ts` 强制执行):`private: true`,`version` 与根 `package.json` 一致,`type: module`,`main: "lib/index.js"`,`types: "lib/types/index.d.ts"`,`exports["."].types: "./lib/types/index.d.ts"`,`exports["."].default: "./lib/index.js"`,`cordis` 同时出现在 peerDependencies 和 devDependencies 中(相同范围)。每个 dsh 对等依赖(peer dependency)都要在 devDependencies 中镜像。`schemastery` 放在 `dependencies` 中(它是运行时校验器),与 agent-loop 保持一致。`files` 列表要精确:`lib/index.js`、`lib/types/**/*.d.ts`、`lib/types/**/*.d.ts.map` 和 `src`;不要发布 `lib/types` 下的 JS 或 JS-map 中间产物,也不要发布陈旧的根声明文件。带有 package `bin` 的 CLI 应用 package 在 `files` 中将 `lib/bin.js` 紧跟在 `lib/index.js` 之后。 + +package 内的相对导入在源码中使用显式 `.ts` 后缀(例如 `export * from './types.ts'`)。编译器在输出的 JS 中将其重写为 `.js`,在声明文件中保留显式 `.ts` 后缀;标准的 NodeNext/Node16 TypeScript 消费方会将其解析到同目录的 `.d.ts` 文件。 + +## 2. 在根配置中注册 + +| 文件 | 变更 | +|---|---| +| `tsconfig.base.json` | 已有分组无需编辑;新分组需为 `@deepseek-ai/dsh-*` 通配符添加 `./packages//*/src` 候选路径 | +| `tsconfig.json` | 在 `references` 中添加 `{ "path": "./packages//" }` | +| `tsconfig.build.json` | 在 `references` 中添加 `{ "path": "./packages//" }` | +| `knip.json` | 仅当 package 有非 `*.spec.ts` 入口时需要(如 `*.e2e.ts` → 添加 per-workspace override,参照 `packages/llm/llm-deepseek`) | + +以下内容由 glob 或 package-manifest 发现机制自动覆盖,无需手动编辑:根 `package.json` workspaces、`scripts/publint-all.ts`、`tsdown.config.ts`、`vitest.config.ts`、`eslint.config.mjs`、`scripts/check-workspace-constraints.ts`。 + +## 3. 确定 package 拓扑 + +对于可替换的能力,将接口、实现、消费方拆分为独立的 package(见 docs/architecture.md § "Capability seams"——bash 三组件是模板)。单一用途的插件保持为一个 package。 + +## 4. 编写 package README + +将 package 特有的服务 API、配置、事件、扩展点和设计说明放在前面。limitations 部分记录持久的消费方缺口和本 package 拥有的非显而易见的维护者约束;日常清理事项留在源码 TODO 或 RFC 中。间接的 Model Experience 语句可以点名暴露本 package 贡献的消费方,但不重述该消费方的实现。package README 以如下规范序列结尾: + +````markdown +## Model Experience + +### Request surface and condition + +**What the model sees**: An exact data-dependent shape, an anchored generated-catalog link, or an introduction to the verbatim literal below. + +**Token effect**: Fixed, conditional, retained, replaced, capped, or zero-direct token effect. + +#### Verbatim text for this context surface, when needed + +```markdown +Stable system-prompt prose of any length, or another long non-generated literal, copied exactly from source. +``` + +## Known Limitations and Deferred Work + +- **Consumer-visible gap** — exact boundary, consequence, or maintainer constraint. +```` + +根据实现填写 Model Experience。每个直接、条件、上限、生命周期或辅助模型的 surface 使用一个 H3,包含上述两个字段。引用 package 拥有的稳定文本:系统提示词放在带标题的 H4 加 `markdown` 围栏中,其他短文本以命名占位符内联,其他长文本使用相同的嵌套形式。仅概述数据依赖或提供方拥有的文本。tool-schema surface 链接到生成的[工具目录](../tool-catalog.md)中对应的锚定章节,仅说明该处缺失的差异。当作用域可以隐藏 prompt 或 schema 其中之一而不影响另一个时,将二者分开。[行文标准](../../.agents/skills/dsh-prose-standard/SKILL.md)约束完整性与归属;验证器强制执行机械形状。 + +没有上下文效果或仅有消费方拥有路径的 package 使用 [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) 中经过审计的 `None, as ` 或 `Indirectly, through ` 语句;与模型无关的通用 package 可以改为加入 `NO_MODEL_EXPERIENCE_SECTION`。两种情况都不要展开为对另一个 package 工作的描述。limitations [allowlist](../../scripts/verify-package-readme-limitations.ts) 独立管理。[Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) 记录了设计动机。 + +## 5. 验证 + +```sh +pnpm install # registers the workspace +pnpm run doc-sync +pnpm run constraints && pnpm run typecheck && pnpm run lint +pnpm run test:coverage # 100% per-file over src (types.ts exempt) +pnpm run build && pnpm run hygiene +``` + +测试要求:每个注册表/注册操作都需要一个 HMR(热模块替换)安全测试(从子 fiber(插件运行时)注册,dispose(资源释放)它,断言清理完成)。鼓励编写充分的测试——见 [docs/testing.md](../testing.md)。 diff --git a/docs/cookbook/adding-a-tool.i18n.yaml b/docs/cookbook/adding-a-tool.i18n.yaml new file mode 100644 index 0000000000..b738f15e4a --- /dev/null +++ b/docs/cookbook/adding-a-tool.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +adding-a-tool.md: 3c6b3a143fd25c6fe7aefc59f0256d5bbef39b0c +adding-a-tool.zh.md: ce8a59031bccb4b5710b95f1ee2c7ee89f44a823 diff --git a/docs/cookbook/adding-a-tool.md b/docs/cookbook/adding-a-tool.md index 86803fdebb..3c6b3a143f 100644 --- a/docs/cookbook/adding-a-tool.md +++ b/docs/cookbook/adding-a-tool.md @@ -1,5 +1,7 @@ # Cookbook: adding a tool +English | [中文](adding-a-tool.zh.md) + How to give the model a new capability. Reference implementations: `examples/echo-agent/src/echo-tool.ts` (minimal) and `packages/bash/tool-bash` (production-grade, three-package seam). ## The minimal shape diff --git a/docs/cookbook/adding-a-tool.zh.md b/docs/cookbook/adding-a-tool.zh.md new file mode 100644 index 0000000000..ce8a59031b --- /dev/null +++ b/docs/cookbook/adding-a-tool.zh.md @@ -0,0 +1,85 @@ +# 实操手册:添加工具 + +[English](adding-a-tool.md) | 中文 + +如何为模型赋予一项新能力。参考实现:`examples/echo-agent/src/echo-tool.ts`(最小化)和 `packages/bash/tool-bash`(生产级,由三个 package 构成的 seam)。 + +## 最小形态 + +```ts +import { readFile } from 'node:fs/promises' +import type { Context } from 'cordis' +import { defineTool } from '@deepseek-ai/dsh-tools' + +export const name = 'my-tool' +export const inject = ['tools'] + +export function apply(ctx: Context) { + ctx.tools.register(defineTool({ + name: 'read_file', + description: 'Read a file from disk.', // what the model sees + parameters: { + path: { type: 'string', required: true, description: 'Absolute path' }, + limit: { type: 'number' }, // optional by default + }, + async execute(args, exec) { + // args is TYPED from the schema: { path: string; limit?: number } + // exec carries immutable identity + token; signal is the operational field + return [{ type: 'text', text: await readFile(args.path, 'utf8') }] + }, + })) +} +``` + +注册基于副作用:dispose(资源释放)插件 fiber(插件运行时)即注销该工具(请编写 HMR(热模块替换)测试)。schema 会自动流入系统提示词的组装过程。 + +## execute() 契约的规则 + +- **参数已为你校验。** `defineTool` 在 `execute` 运行前,会根据 `SchemaSpec` 校验模型生成的 `arguments`(类型、必填键、枚举成员、嵌套对象/数组——见[运行时参数校验](../rfc/implemented/architecture/2026-06-11-runtime-arg-validation.md)),因此 `execute` 内部的 args 已匹配 `InferArgs`。你仍需手动检查 DSL 无法表达的值约束(非空字符串、正数、跨字段规则),对这些情况抛出描述性 Error。直接注册的原始 JSON-Schema 工具(MCP)不由 harness 校验,它们自行校验输入。 +- **注册借用你的只读定义。** 类型化的同进程贡献不是序列化边界;注册后不要修改其 schema 或替换回调。`schemas()` 只物化显式的模型可见投影。如需热替换工具,请 dispose 其所属副作用并注册替代品;回调闭包内的可变状态仍是普通的插件状态。 +- **执行身份受保护。** 注册表在一次递归遍历中将 `arguments` 物化为分离的无损 JSON,在策略开始前冻结该值,并分配一个不透明的 `exec.token`;`callId`、`name`、`arguments`、`agent`、`token` 以及可选的外层传输 `parent` token 在整个分发过程中保持不可变。`parent` 仅用于身份标识,不暴露活跃的外层执行。请将 `args` 视为只读输入。around-dispatch 包装器只能添加、替换或移除 `exec.signal`,以施加取消或截止时间。 +- **抛出异常或返回非 JSON 数据意味着 `isError`。** 注册表捕获异常,并在观察者运行前物化最终结果。格式错误或非 JSON 的结果变为 `{ isError: true }`,防止出现无法记录的活跃成功。基础设施故障请抛异常;当模型需要解读领域失败时,请在结果文本中报告。 +- **遵守 `exec.signal`。** 信号触发时取消进行中的工作。 +- **使用 `meta` 附加持久化的卡片数据(可选)。** `execute` 可以返回 `{ content, meta }` 而非裸的 `ContentBlock[]`。`meta` 是 JSON 可序列化的载荷,核心将其视为不透明数据,持久化在 `tool/result` 事件上并回传给你的 `presentResult`(这样需要 `args` 之外信息的卡片——如 `write`/`edit` 的已应用 hunk diff——在会话回放中依然存活)。仅在此处放 UI 数据,绝不放入模型可见的 `content`。 +- **使用 `exec.agent` 发送异步通知。** `agent.inject(content, {source: {kind: 'plugin', plugin: ''}})` 追加持久化上下文,下一次模型请求会看到它——这不是唤醒(空闲的 agent(智能体)保持空闲)。请防范已 dispose 的 agent(try/catch)。 + +## 长时间运行的工作 + +遵循 tool-bash 的后台模式:`run_in_background` 标志立即返回一个 task id;配套工具增量轮询和终止;完成通知通过 `agent.inject()` 到达。限定缓冲区大小,将完整输出溢写到磁盘,避免静默丢失。 + +> TODO: 目前每个工具都手动重新实现这套后台模式。未来需要一个通用的长时间运行工具层,统一处理 task id、增量轮询、终止和完成通知。 + +## 执行策略与观测 + +尽量不要把部署策略内建到工具中。使用 `tools/pre-execute` 实现可扩展的允许/拒绝/询问策略(见[权限门禁示例](./extension-cookbook.md#a-hook-plugin-permission-gate));使用 `ctx.tools.guard()` 设置最终的单调拒绝(后续监听器无法撤销);使用 `tools/execute` 为核心分发包装截止时间/重试/指标作用域;使用 `tools/post-execute` 转换或附加模型可见的上下文;使用 `tools/result` 观测不可变的归一化结果而不改变它。沙箱实现也可以位于工具执行器的能力 seam 之后;确切契约见 [`dsh-tools` README](../../packages/core/tools/README.md#extension-points)。 + +## Code Mode 自动触达你的工具 + +在 [Code Mode](../../packages/core/tools/README.md) 中,每个可见的已注册工具都可通过 `await tools.(args)` 调用,无需额外集成。SDK 从同一份 JSON Schema 派生参数,调用重新进入正常的执行流水线。请将描述写成面向模型的 API 文档;非文本结果块在程序中变为占位符。 + +## 工具在编辑器中的渲染方式(ACP 展示) + +工具的 `execute` 返回模型可见的内容;其**编辑器卡片**是一个独立的、可选的关注点,通过 `defineTool` 选项中的两个纯展示方法声明。请与 `execute` 同步设计,而非事后补充——编辑器(如 Zed,通过 ACP(Agent Client Protocol)桥接)会展示该卡片,没有展示方法的工具回退为一个朴素的通用卡片(标题 = 工具名,原始 args 作为输入)。 + +两个方法都返回一个 **`card` 标签的渲染意图**——选择与你的工具行为匹配的卡片类型: + +- `presentCall(args)` → 一个 `ToolCallView`(PENDING 卡片): + - `{ card: 'generic', title, kind?, rawInput?, content?, locations? }`——默认。设置 `kind` 获取图标(`read`/`search`/…);设置 `locations: [{ path, line? }]` 标注工具涉及的文件,使有能力的编辑器跟随/跳转。 + - `{ card: 'terminal', title, description?, cwd? }`——你的调用本身就是 shell 命令。`title` 是命令,`description` 渲染在终端卡片上方。(tool-bash。) + - `{ card: 'diff', title, diffs, locations? }`——你的调用创建或修改文件。`diffs: [{ path, oldText, newText }]`(新文件时 `oldText: null`)渲染为内联 diff 卡片。(tool-fs `write`/`edit`。) +- `presentResult(args, { content, isError, meta? })` 返回完成后的卡片: + - `generic` 提供可选的标题和内容。 + - `terminal` 提供原始输出和可选的退出元数据;桥接层渲染能力特定或围栏回退视图。 + - `diff` 提供已应用的 hunk,通常由持久化的 `result.meta` 携带,使回放能重现它们。变更类工具保留 diff 结果,因为 ACP 更新会替换 pending 卡片的内容。 + +硬性规则(违反会出问题): + +- **纯函数。** 这些方法在实时流式输出和会话日志回放时都会运行,因此必须是 `args`(加 result)的纯函数——不做 I/O、不读会话状态、不用时钟/随机数。diff 从 args 派生(`write` 使用 `oldText: null`,因为调用时的展示器没有文件先前内容);**桥接层**(而非工具)填充会话 cwd 并相对化展示路径标题。如果你发现自己想在 `presentCall` 内获取文件旧内容或工作目录,请停下——那属于桥接层或未来的 result-event 形态,不属于展示器。 +- **UI 格式不进入模型结果。** 围栏 ` ```console ` 块、diff、相对化路径——这些都不得出现在 `execute` 返回给模型的内容中;它们只存在于展示层。(`terminal` 结果视图携带原始 `output`;桥接层添加围栏。) +- **`defineTool` 对展示路径做软校验。** 格式错误或旧版日志中的 arg 形态会使包装器返回 `undefined`(通用回退)而非抛异常——展示绝不能导致回放崩溃。 + +中性词汇定义在 `dsh-tools` 中(绝不在工具中导入 ACP 类型);ACP 桥接层将每个 `card` 映射到协议格式(wire format)。设计与原因见[渲染意图联合体 RFC](../rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md);`dsh-tool-fs`(generic/diff)和 `dsh-tool-bash`(terminal)是参考实现。 + +## 每个工具必须的测试 + +覆盖参数拒绝、每种结果形态和 HMR dispose。对于有副作用的工具,使用脚本化的 `MockAdapter` 驱动真实工具通过 agent loop(智能体循环),并断言其 `tool/call` 和 `tool/result` 会话事件。对于编辑器卡片,断言 `presentCall` 和 `presentResult` 的精确视图,并通过真实桥接层添加一个 [ACP 快照](../rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md);终端卡片的场景设置 `terminalOutput: true` 以覆盖 capable-client 路径。 diff --git a/docs/cookbook/adding-a-vendored-package.i18n.yaml b/docs/cookbook/adding-a-vendored-package.i18n.yaml new file mode 100644 index 0000000000..764f41a251 --- /dev/null +++ b/docs/cookbook/adding-a-vendored-package.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +adding-a-vendored-package.md: d7b5b93b59fb39d8369be6eb42fb0a8b977c68b4 +adding-a-vendored-package.zh.md: e1274b0855539e5f69b278b256c45fdcb57c048a diff --git a/docs/cookbook/adding-a-vendored-package.md b/docs/cookbook/adding-a-vendored-package.md index 59df2f617b..d7b5b93b59 100644 --- a/docs/cookbook/adding-a-vendored-package.md +++ b/docs/cookbook/adding-a-vendored-package.md @@ -1,5 +1,7 @@ # Cookbook: adding a vendored package +English | [中文](adding-a-vendored-package.zh.md) + When the harness needs another upstream Cordis package (e.g. `@cordisjs/plugin-http`), it is **vendored** as pinned source under `vendor/`, not added as an npm dependency — see [the vendoring decision](../rfc/implemented/process/2026-06-11-vendor-cordis-as-source.md) for why. [vendor/README.md](../../vendor/README.md) covers *updating* an already-vendored package; this guide is the file-by-file checklist for adding a **new** one. (Verified against the existing vendored set; if it drifts, fix it here.) ## 1. Copy the source in diff --git a/docs/cookbook/adding-a-vendored-package.zh.md b/docs/cookbook/adding-a-vendored-package.zh.md new file mode 100644 index 0000000000..e1274b0855 --- /dev/null +++ b/docs/cookbook/adding-a-vendored-package.zh.md @@ -0,0 +1,60 @@ +# 实操手册:添加一个 vendored package + +[English](adding-a-vendored-package.md) | 中文 + +当 harness 需要引入另一个上游 Cordis package(如 `@cordisjs/plugin-http`)时,应将其作为固定版本的源码 **vendor** 到 `vendor/` 下,而非作为 npm 依赖添加——原因见[vendoring 决策](../rfc/implemented/process/2026-06-11-vendor-cordis-as-source.md)。[vendor/README.md](../../vendor/README.md) 介绍如何*更新*已有的 vendored package;本指南是添加**新** vendored package 的逐文件清单。(已对照现有 vendored 集合验证;如有偏差,请在此修正。) + +## 1. 复制源码 + +``` +vendor// + package.json # from upstream; set "private": true, keep name/exports/type + tsconfig.json # extends ../../tsconfig.base.json (see shape below) + src/ # the upstream src/ verbatim + README.md LICENSE # if upstream ships them +``` + +`tsconfig.json` 与其他 vendored package 保持一致:`rootDir: src`、`outDir: lib/types`、上游代码所需的严格性放宽项,以及对所导入的每个其他 vendored package 的 `references` 条目: + +```jsonc +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", "outDir": "lib/types", + "noUncheckedIndexedAccess": false, "exactOptionalPropertyTypes": false, + "noImplicitOverride": false, "noUnusedLocals": false, "noUnusedParameters": false + }, + "include": ["src"], + "references": [{ "path": "../cordis" }, { "path": "../cosmokit" }] +} +``` + +`package.json` 的不变式:`"private": true`(vendored package 永不发布);保留上游的 `name`/`version`/`exports`/`type`;声明元数据指向 `lib/types`;发布 `.d.ts` 与 `.d.ts.map` 声明输出;在 `peerDependencies` 中列出其 Cordis 依赖(与上游 manifest(元数据清单)一致)。传递性上游依赖本身也必须被 vendor 或已存在于仓库中——vendor 一个 package 往往意味着 vendor 其整条依赖树(如 `@cordisjs/plugin-http` 会拉入 `@cordisjs/fetch-file`)。 + +vendored TypeScript 源码中的本地相对导入/导出在复制后使用显式 `.ts` 后缀。这是仓库本地的构建形态与上游的差异:`rewriteRelativeImportExtensions` 输出 `.js` 运行时导入,而声明文件保留显式 `.ts` 后缀,使 NodeNext/Node16 的 TypeScript 消费方能够解析。 + +## 2. 在根配置中注册 + +| 文件 | 修改内容 | +|---|---| +| `tsconfig.base.json` | 在 `paths` 中添加 `"": ["./vendor//src"]` | +| `tsconfig.json` | 在 `references` 中添加 `{ "path": "./vendor/" }` | +| `tsconfig.build.json` | 在 `references` 中添加 `{ "path": "./vendor/" }`(置于 `packages/*` 条目之前) | +| `vendor/README.md` | 添加一行 manifest 表格行(dir、npm name、version、upstream repo、commit SHA)并记录所有本地修改 | +| `scripts/publint-all.ts` | 仅当该 vendored package 本身从此仓库发布时才需要(vendored 依赖通常不发布——跳过) | + +以下由 glob 自动覆盖,无需手动编辑:根 `package.json` 的 workspaces(`vendor/*`)、`tsdown.config.ts`、`vitest.config.ts`、`eslint.config.mjs`。只有当构建形态偏离根默认值时(双 ESM/CJS 或多入口——参见 `vendor/schemastery` 和 `vendor/logger-console`),才需要单独的 `vendor//tsdown.config.ts`;其入口应读取 `lib/types` 下输出的 JS。 + +## 3. 注意 manifest 守卫 + +`scripts/check-vendor-manifest.sh`(pre-commit 钩子)会在 `vendor/*/src` 下有暂存改动但 `vendor/README.md` 未一起暂存时失败。请将 manifest 更新与源码一起暂存,以通过提交检查。 + +## 4. 验证 + +```sh +pnpm install # registers the workspace +pnpm run typecheck +pnpm run build && pnpm run test && pnpm run constraints +``` + +源码 `paths` 映射由构建配置和根类型检查配置共享。重要的隔离边界是 project-reference 图:vendored 源码必须通过其自身的 `vendor//tsconfig.json` 被引用,而非被拉入根目录的严格程序中。 diff --git a/docs/cookbook/adding-an-llm-adapter.i18n.yaml b/docs/cookbook/adding-an-llm-adapter.i18n.yaml new file mode 100644 index 0000000000..37f934f3e6 --- /dev/null +++ b/docs/cookbook/adding-an-llm-adapter.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +adding-an-llm-adapter.md: 70306ccf119f523dd812a859eef1e8628383bb48 +adding-an-llm-adapter.zh.md: e6d151adfc793a829a876031d5d7280ba078c8e9 diff --git a/docs/cookbook/adding-an-llm-adapter.md b/docs/cookbook/adding-an-llm-adapter.md index fb59969e52..70306ccf11 100644 --- a/docs/cookbook/adding-an-llm-adapter.md +++ b/docs/cookbook/adding-an-llm-adapter.md @@ -1,5 +1,7 @@ # Cookbook: adding an LLM adapter +English | [中文](adding-an-llm-adapter.zh.md) + How to connect a new model provider. Reference implementations: `packages/llm/llm-deepseek` (hand-rolled HTTP/SSE) and `packages/llm/llm-pi-ai` (wrapping an LLM library). Read the `StreamChunk` doc in `packages/llm/llm/src/types.ts` first — it records the protocol conventions both adapters were verified against. ## The shape diff --git a/docs/cookbook/adding-an-llm-adapter.zh.md b/docs/cookbook/adding-an-llm-adapter.zh.md new file mode 100644 index 0000000000..e6d151adfc --- /dev/null +++ b/docs/cookbook/adding-an-llm-adapter.zh.md @@ -0,0 +1,45 @@ +# 实操手册:添加 LLM 适配器 + +[English](adding-an-llm-adapter.md) | 中文 + +如何接入一个新的模型提供方。参考实现:`packages/llm/llm-deepseek`(手写 HTTP/SSE)与 `packages/llm/llm-pi-ai`(封装 LLM 库)。请先阅读 `packages/llm/llm/src/types.ts` 中的 `StreamChunk` 文档——它记录了两个适配器都经过验证的协议约定。 + +## 基本形态 + +```ts ignore-check +class MyAdapter extends LlmAdapter { + async * stream(options: GenerateOptions): AsyncIterable { … } +} + +export const name = 'llm-myprovider' +export const inject = ['llm'] +export const Config: z = z.object({ apiKey: z.string(), … }) + +export function apply(ctx: Context, config: Config) { + ctx.llm.registerAdapter(['model-a', 'model-b'], new MyAdapter(…)) +} +``` + +注册基于副作用(HMR 安全);每个模型名称对应一个适配器,重复注册会抛出异常。密钥采用 Cordis 原生方式管理:schemastery Config 带环境变量回退,通过 cordis.yml 的 `!!js process.env.MY_KEY` 注入。代码中禁止临时读取密钥文件。 + +## 协议义务(两个实现共同验证的契约) + +- 在 `finish` **之前**发出 `usage`;`finish` 之后**不再发出任何内容**。稳健做法:缓冲 finish/usage 直到提供方的流结束标记,再统一 flush(可处理提供方在末尾发送仅含 usage 的分片的情况)。 +- 工具调用的 `arguments` 全程为原始 JSON 字符串;流式片段以 `argumentsDelta` 发送。如果你的提供方返回已解析的对象,请在 `block-end` 时重新 stringify。 +- 按首次出现的流顺序分配块 `index`;同一个块的每次 delta 复用该 index。 +- 错误有且仅有两条合法路径:从 `stream()` **抛出**(传输与协议故障——使用带稳定 code 的 `LlmError`),或以 `finish {kind: 'error' | 'aborted'}` 结束流(提供方带内故障)。消费方两者都处理;按故障类别选择路径并加以文档化。 +- 遵守 `options.signal`(将其传递给 fetch 或你的 SDK)。 +- 如果 `GenerateOptions` 中某个字段你的提供方无法支持(例如提供方不支持 stop sequences 时收到 `stop` 列表):抛出 `LlmError(..., 'UNSUPPORTED')`,而非静默丢弃。 + +提供方特有的请求旋钮(thinking 模式、effort 级别)放在**适配器**的 Config 中,而非 `GenerateOptions` 中——核心词汇保持提供方无关。 + +## 经验证有效的结构 + +将适配器拆分为可测试的阶段(llm-deepseek 的布局):协议格式(wire format)类型(`types.ts`,豁免覆盖率)→ 请求序列化器 → SSE/传输解析器 → 分片转换状态机 → 一个将它们串联的薄适配器类。每个阶段配备独立的单元测试套件。 + +## 测试 + +- **单元测试:mock 提供方,而非 harness。** 用脚本化的 `node:http` 服务器模拟提供方的协议格式,覆盖正常路径、所有错误状态码、畸形载荷、连接提前关闭和中止——无需网络,且能满足 100% 逐文件覆盖率门禁。对基于 SDK 的适配器同样适用(将 SDK 的 baseURL 指向 mock 服务器)。 +- **恶意分帧测试。** 在任意字节位置(包括 UTF-8 字符中间)切割流载荷——真实网络环境正是如此。 +- **E2E:`tests/*.e2e.ts`**,通过 `pnpm run test:e2e` 运行,以 `describe.skipIf(!process.env.MY_KEY)` 守卫,确保无密钥的 CI 保持绿色。覆盖你映射的每个模型 × 每种提供方模式(thinking 开/关、effort 级别)、一次包含后续轮次(历史中带工具结果)的工具调用往返,以及仅做宽松断言(子串/结构匹配、有界的 maxTokens——真实模型是非确定性的)。 +- 在 `knip.json` 中注册 e2e 文件模式(per-workspace `entry` 覆盖),否则 knip 会将其标记为未使用。 diff --git a/docs/cookbook/extension-cookbook.i18n.yaml b/docs/cookbook/extension-cookbook.i18n.yaml new file mode 100644 index 0000000000..e9c78d627b --- /dev/null +++ b/docs/cookbook/extension-cookbook.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +extension-cookbook.md: 620d8957c360a755ab2d868a636044b3b755442e +extension-cookbook.zh.md: c2ce077d37155cdbadc707994fea1c9a53b71fb3 diff --git a/docs/cookbook/extension-cookbook.md b/docs/cookbook/extension-cookbook.md index a2aa255ccf..620d8957c3 100644 --- a/docs/cookbook/extension-cookbook.md +++ b/docs/cookbook/extension-cookbook.md @@ -1,5 +1,7 @@ # Cookbook: extension plugin shapes +English | [中文](extension-cookbook.zh.md) + The three plugin shapes you write against the harness extension surface, as illustrative snippets (elided imports and helper stubs — not copy-paste-complete). For the full step-by-step guides see [adding a package](./adding-a-package.md), [adding a tool](./adding-a-tool.md), and [adding an LLM adapter](./adding-an-llm-adapter.md); for the seams these hook into see [docs/architecture.md](../architecture.md). ## A tool plugin diff --git a/docs/cookbook/extension-cookbook.zh.md b/docs/cookbook/extension-cookbook.zh.md new file mode 100644 index 0000000000..c2ce077d37 --- /dev/null +++ b/docs/cookbook/extension-cookbook.zh.md @@ -0,0 +1,123 @@ +# 实操手册:扩展插件形态 + +[English](extension-cookbook.md) | 中文 + +针对 harness 扩展表面编写的三种插件形态,以示意性代码片段呈现(省略了 import 和辅助桩——不可直接复制运行)。完整的分步指南见[添加 package](./adding-a-package.md)、[添加工具](./adding-a-tool.md)和[添加 LLM(大语言模型)适配器](./adding-an-llm-adapter.md);这些插件所挂接的 seam 见 [docs/architecture.md](../architecture.md)。 + +## 工具插件 + +工具在 `ctx.tools` 上注册。带注解的 `defineTool` 示例(类型化的 `execute` 参数、结果塑形、`run_in_background` 模式)见 [adding-a-tool.md](./adding-a-tool.md)——该指南是工具形态的真源。`ctx.tools.register()` 也直接接受原始 JSON-Schema `ToolDefinition`(MCP 来源的工具就是这样到达的);`defineTool` 是为第一方工具提供的类型化语法糖。 + +## 钩子插件(权限门禁) + +钩子从 `tools/pre-execute` 门禁返回一个类型化的决策,用于允许或拒绝一次调用——这是沙箱、权限和 plan-mode 插件所在的 seam。(所谓"原生钩子"就是这样:一个挂在拦截 seam 上、返回类型化决策的普通 Cordis 插件,无需外部协议。) + +```ts +import type { Context } from 'cordis' +import type { PreToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools' + +declare function isAllowed(exec: ToolExecution): Promise + +export const name = 'permission-gate' + +export function apply(ctx: Context) { + ctx.on('tools/pre-execute', async (exec, next): Promise => { + if (!(await isAllowed(exec))) { + return { kind: 'deny', reason: 'Denied by policy.' } + } + return next() + }) +} +``` + +这个 waterfall(瀑布式事件)是可重排的策略层。当不变式需要单调的最终拒绝时使用 `ctx.tools.guard()`;当插件需要包裹实际分发生命周期时(超时/重试/指标;仅 `exec.signal` 可替换)使用 `tools/execute`;显式结果变换使用 `tools/post-execute`;对不可变最终结果的受限观察使用 `tools/result`。选择规则见[添加工具指南](./adding-a-tool.md#execution-policy-and-observation)。 + +## UI 插件 + +UI 插件从 `session/event` 事件流渲染(助手 token 流以 `assistant/chunk` 形式到达,加上轮次/步骤边界与工具活动),并通过 `agent.send()` / `agent.steer()` 将输入驱动回去。 + +```ts +import type { Context } from 'cordis' +import { AgentId } from '@deepseek-ai/dsh-agent' + +declare function render(text: string): void +declare function onUserInput(handler: (text: string) => void): void + +export const name = 'my-ui' +export const inject = ['agents'] + +export function apply(ctx: Context) { + ctx.on('session/event', (_session, event) => { + if (event.type === 'assistant/chunk' && event.data.chunk.type === 'text-delta') { + render(event.data.chunk.text) + } + }) + onUserInput(text => ctx.agents.get(AgentId('main'))?.send([{ type: 'text', text }])) +} +``` + +## 客户端驱动插件(外部协议桥接) + +*客户端驱动*是面向协议格式(wire format)对端的 UI 插件。它拥有 stdio,因此必须禁用 stdout 日志;通过工厂创建或恢复 agent(智能体);将 harness 事件映射为协议消息;将请求映射为 `send()` 或 `cancel()`。每个请求从持久的 `turn/end` 恰好结算一次(即使渲染失败),并通过 `AgentHandle.dispose()` 拆除 agent 以使 dispose(资源释放)达到静止状态。 + +`packages/ui/acp` 是完整的工作示例:它将 agent 桥接到 ACP(Agent Client Protocol)(基于 stdio 的 JSON-RPC),使 Zed 及其他 ACP 编辑器能够驱动它。其 README 描述了完整的方法接口以及它在审批 seam 上注册的权限提示应答器。 + +```ts +import type { Context } from 'cordis' + +export const name = 'my-protocol-bridge' +export const inject = ['agents', 'sessions', 'sessionPersistence'] + +export function apply(ctx: Context) { + // Stream every logged assistant text/reasoning delta out to the client. + ctx.on('session/event', (_session, event) => { + if (event.type === 'assistant/chunk') { + const chunk = event.data.chunk + if (chunk.type === 'text-delta') { + // sendToClient({ kind: 'message_chunk', text: chunk.text }) + } + } + }) + // Inbound "prompt": create/resume an agent and feed it; settle on turn end. + // Teardown reaches quiescence via AgentHandle.dispose() (stop + await exit). +} +``` + +## 可运行的组装示例 + +三个完整示例从 `cordis.yml` 加载各自的插件树:[`examples/echo-agent`](../../examples/echo-agent)(mock 模型 + echo 工具——全 mock 骨架检查,`pnpm run demo:echo`)、[`examples/coding-agent`](../../examples/coding-agent)(DeepSeek V4 + bash 工具套件,配合终端 REPL UI,`pnpm run demo:repl`)、[`examples/acp-agent`](../../examples/acp-agent)(通过 JSON-RPC stdio 暴露为 ACP 服务器的 agent——客户端驱动形态,`pnpm run demo:acp`)。每个叶子只是其可替换后端加一个 app-package 入口:stdio 演示加载 [`@deepseek-ai/dsh-stdio-agent`](../../packages/ui/stdio-agent),ACP 演示加载 [`@deepseek-ai/dsh-acp-agent`](../../packages/ui/acp-agent),两个 app package 通过 [`@deepseek-ai/dsh-agent-core`](../../packages/core/agent-core) bundle 共享主干。 + +## 功能→机制映射 + +每个产品功能都映射到一个文档化扩展 seam 上的监听器——微内核声明由此可验证([微内核 RFC](../rfc/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md))。没有任何一行修改循环本身。 + +`system-prompt/assemble` 是一个专家协作式的整体装配变换:其返回的装配结果具有权威性,因此监听器作者有责任保留活跃的 Code Mode 和结构化输出协议的贡献。对于需要在展示、查找和执行之间保持对齐的工具过滤,优先使用 `ctx.tools.restrict()`。 + +| 产品功能 | 插件机制 | +|---|---| +| 钩子系统(用户级 + 项目级) | `agent/session-start`、`agent/prompt-submit`、`agent/request`、`agent/step-result`、`tools/pre-execute`、`tools/post-execute`、`agent/turn-continuation` 上的监听器——每个拦截 waterfall 返回一个类型化 Decision;`dsh-hooks-claude` / `dsh-hooks-codex` 桥接器将钩子配置文件映射到这些 seam 上 | +| `/goal` | 通过 `agent/turn-continuation` 强制继续 + `steer()` 提醒 | +| `/loop` | 在 `turn/end` 会话事件上 `send()` 下一次迭代;或强制继续 | +| 动态工作流 | `ctx.workflows` + worker-thread 引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的 prompt/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和终端 `agent/turn-stop` 来强制输出 | +| 排队消息 + steering(中途引导) | 核心 `Agent.send()` / `Agent.steer()` | +| 上下文压缩(context compaction)(自动 + 手动) | `ctx.compact` seam + 串行 `agent/pre-step` seam 上的后端(`dsh-compact-basic`);自动 = 每步之前的 token 压力检查;手动触发调用同一个 `ctx.compact` 例程([压缩 RFC](../rfc/implemented/feature/2026-06-18-compaction-capability-seam.md)——面向模型的 `/compact` 消费方工具已推迟) | +| 系统提示词可配置性 | `ctx.systemPrompt.section()`,支持排序与作用域局部覆盖 | +| AGENTS.md(根目录) | 一个读取该文件的 section provider | +| AGENTS.md(子目录,按需触发)+ 文件变更通知 | 从 watcher / tool-result 监听器调用 `agent.inject()` | +| 内置工具 | `ctx.tools.register()`;schema 自动流入装配——`dsh-tool-*` 系列(bash、fs、web、subagent、todo)是已交付的示例 | +| ToolSearch / 渐进式披露 | 当可见集变化时替换一个作用域化的 `ctx.tools.restrict()` 注册;注册表保持展示、查找和执行三者对齐 | +| 工具截止时间 / 重试 / 指标 | 用 `tools/execute` 包裹核心分发;包装器可替换 `exec.signal`、委托执行,并在同一词法生命周期内检视规范化结果 | +| 最终工具结果指标 / 审计 / 捕获 | 用 `tools/result` 观察不可变的权威结果;仅当插件需要变换结果或附加上下文时才使用 `tools/post-execute` | +| 单调终端轮次策略 | 从串行 `agent/turn-stop` 返回 `{ action: 'stop' }`,此时 continuation 和 steering 已折叠完毕 | +| 子进程沙箱(landlock / sandbox-exec) | 通过 `dsh-bash-sandbox` 使用 `ctx.sandbox` 后端;能力级别的拒绝使用 `tools/pre-execute` | +| 权限系统 / AskUserQuestion | 从 `tools/pre-execute` 返回 `ask` 并通过 `ctx.approval` 应答;为普通用户提问注册一个独立的面向模型的 ask 工具 | +| Plan mode | `tools/pre-execute`(拒绝写操作)+ 通过 `ctx.systemPrompt.section()` 或 `agent.inject()` 注入模式提示词段(model-visible ⟺ logged:`agent/request` 仅塑形调用配置) | +| 子 agent 委派 | `ctx.subagents` 提供方注册表(`dsh-subagent-spawn`/`-fork`/`-acp`)+ `dsh-tool-subagent` 向模型暴露一个已配置的提供方 | +| MCP | 每个服务器一个插件:发现工具 → `ctx.tools.register()` | +| Skill(技能) | section + 工具注册;调用时通过 `inject()` 注入 skill 内容 | +| 记忆 | section provider + 工具 | +| 定时任务(cron) | 插件注册面向模型的调度工具;定时器触发 → 空闲时 `send(…, {source: {kind: 'cron', …}})`/忙碌时 `inject()` 通知 | +| UI(GUI;CLI 输出 JSONL) | 监听 `session/event`(助手分片、边界、工具活动);输入 → `send()` | +| 遥测 / 可回放 trace | `session/event` → JSONL;回放 = `sessions.create(id, { seed })` | +| 模型适配器 | 通过 `registerAdapter` 注册 `LlmAdapter` 子类(`dsh-llm-deepseek`、`dsh-llm-pi-ai`) | +| 插件热重载 | 每个注册都是一个 `ctx.effect` → vendor 的 HMR(热模块替换)直接生效 | diff --git a/docs/cookbook/responding-to-pr-review-on-a-stack.i18n.yaml b/docs/cookbook/responding-to-pr-review-on-a-stack.i18n.yaml new file mode 100644 index 0000000000..b14f340a33 --- /dev/null +++ b/docs/cookbook/responding-to-pr-review-on-a-stack.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +responding-to-pr-review-on-a-stack.md: 96dfc594d26ffa8a2d44a1aff8d4a6918c88cc82 +responding-to-pr-review-on-a-stack.zh.md: 7a5c764bef0a5844128f8df0fa190a046d7b7ce2 diff --git a/docs/cookbook/responding-to-pr-review-on-a-stack.md b/docs/cookbook/responding-to-pr-review-on-a-stack.md index 4eb7dc482d..96dfc594d2 100644 --- a/docs/cookbook/responding-to-pr-review-on-a-stack.md +++ b/docs/cookbook/responding-to-pr-review-on-a-stack.md @@ -1,5 +1,7 @@ # Responding to review across a stacked PR chain +English | [中文](responding-to-pr-review-on-a-stack.zh.md) + A wave of review comments lands across several PRs in a dependent stack (`A ← B ← C …`). This is the discipline for resolving it without corrupting the stack. The two invariants it rests on are standing orders in the root [AGENTS.md](../../AGENTS.md) § Conventions: merge commits only, and never rewrite a pushed branch. ## Ground rules diff --git a/docs/cookbook/responding-to-pr-review-on-a-stack.zh.md b/docs/cookbook/responding-to-pr-review-on-a-stack.zh.md new file mode 100644 index 0000000000..7a5c764bef --- /dev/null +++ b/docs/cookbook/responding-to-pr-review-on-a-stack.zh.md @@ -0,0 +1,26 @@ +# 在堆叠 PR 链中回应评审意见 + +[English](responding-to-pr-review-on-a-stack.md) | 中文 + +一波评审意见同时落在一条依赖堆叠(`A ← B ← C …`)中的多个 PR(Pull Request)上。本文规定在不破坏堆叠的前提下解决这些意见的纪律。它依赖的两个不变式是根 [AGENTS.md](../../AGENTS.md) § Conventions 中的常设指令:只用 merge commit,以及永远不改写已推送的分支。 + +## 基本规则 + +1. **每个 PR 分支一个 worktree。** 每个 PR 的修复在该 PR 自己的 worktree 中进行;并行修复绝不共享同一个 checkout。 +2. **通过将父分支向下合并来更新子分支**(在子分支中执行 `git merge `,产生一个新的 merge commit)。绝不对已推送的分支做 rebase/amend/force-push:改写会使分支与父 PR 及 GitHub 记录的内容产生分歧,破坏堆叠合并图,并抹去评审修复历史。 +3. **修复落在引入问题的那个 PR 上,然后向下流动。** 当 PR `B` 上的评论指向 `B` 引入的代码时,在 `B` 上修复,再将 `B` 合并到 `C`——即使 `C` 也包含该文件。把修复发起在下游会导致 `B` 带着未修复的代码交付,并对 `B` 的评审者隐藏修复。 +4. **每个评审修复是一个独立 commit,绝不 amend。** "修复评审发现"的 commit 记录了评审捕获的内容。只有你自己尚未推送、尚未评审的工作才可以 amend。 + +## 处理评审浪潮 + +1. 在行动之前先就事论事地审视每条评论:对照代码验证其论断——评审者指出了正确的症状,但仍可能误诊原因。 +2. 将每个被接受的发现映射到其发起 PR,在那里修复,然后按顺序沿链向下合并。 +3. 委派的修复需要信任但验证:子 agent(智能体)的报告描述的是意图,不一定是实际落地的内容。请亲自在实际代码树上重新运行门禁;对于回归守卫,要证明它在未修复的代码上**失败**(引入回归、观察变红、再还原)——两种情况都通过的守卫什么也守不住。子 agent 将问题重新定性为「已处理」时,这是一个需要亲自深入的信号。 +4. 在评审线程中回复(`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`),而非发顶层评论;说明修复内容及承载修复的 commit。 +5. 合并堆叠之前,检查依赖方:删除一个 PR 的 base 分支会自动关闭依赖它的 PR。用 `gh pr list --state open --base --json number --jq length` 检查每个分支(非零 = 有打开的依赖方),当子 PR 仍以该分支为 base 时,合并时不带 `--delete-branch`。完整的落地流程见 [dsh-merging-stacked-prs](../../.agents/skills/dsh-merging-stacked-prs/SKILL.md) skill(技能)。 + +## 验证 + +- 每个已修复的 PR 显示一个新 commit(PR 时间线中没有 force-push 图标)。 +- 每个子 PR 相对其父 PR 的 diff 仍然只包含自身的变更。 +- 门禁在堆叠中的每个 PR 上都通过,而不仅仅是顶部。 diff --git a/docs/i18n/translation-prompt.md b/docs/i18n/translation-prompt.md index a300ef64f4..60a023979a 100644 --- a/docs/i18n/translation-prompt.md +++ b/docs/i18n/translation-prompt.md @@ -79,6 +79,11 @@ You are a senior technical translator specializing in LLM and agent development #### When translating into English - Use half-width English punctuation and standard English spacing. Preserve full-width punctuation only in verbatim Chinese text. +- Convert enumeration commas (、) to English commas; convert 「」 quotes to English double quotes. +- Render the terminology table's English column the same way the Chinese column binds the other direction: listed terms use exactly the table's English form; first-occurrence glosses do not carry over (English prose never glosses an English term with Chinese). +- Chinese topic-comment sentences and dropped subjects become explicit English subjects; prefer concise declaratives over nominalizations. +- Do not transliterate Chinese engineering idioms literally: render the underlying concept (误报 → false positive, 执行红线 → enforcement frontier), consulting the terminology table first. +- Keep the register of institutional developer documentation: contractions are acceptable, marketing language and hedging (very, quite, simply) are not. ## Terminology diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json index cea3eb393a..35a957e57d 100644 --- a/scripts/translation-pairing.manifest.json +++ b/scripts/translation-pairing.manifest.json @@ -2,6 +2,12 @@ "requiredSince": "2026-07-14", "required": [ "README.md", + "docs/cookbook/adding-a-package.md", + "docs/cookbook/adding-a-tool.md", + "docs/cookbook/adding-a-vendored-package.md", + "docs/cookbook/adding-an-llm-adapter.md", + "docs/cookbook/extension-cookbook.md", + "docs/cookbook/responding-to-pr-review-on-a-stack.md", "docs/development.md", "docs/i18n/README.md", "docs/i18n/translation-rules.md",