跳转到内容

测试与工程化

旧版 eec0843c 源码提交 eec0843ce422
状态已完成
难度入门
预计阅读30 分钟
  • 章节 ID:13-testing-engineering
  • 章节摘要:沿一次 package 级改动的验证路径,理解 workspace、Turbo 任务图、测试根目录保护、类型检查和 SDK 生成如何形成工程信任链。
  • 教程版本:eec0843c
  • 源码基线:eec0843ce42298080569ca31a6455bc3f699d213
  • 章节元数据:/versions/eec0843c/data/chapters.json
  • 源码映射:/versions/eec0843c/data/source-map.json
  • package.json
  • turbo.json
  • tsconfig.json
  • AGENTS.md
  • packages/opencode/package.json
  • packages/sdk/js/package.json

源码基线:eec0843ce422。本章描述的是仓库声明的任务边界;未实际运行的 OpenCode 测试不会写成“已经通过”。

读完本章,你应该能:

  • 画出 workspace、catalog、package scripts 与 Turbo task graph 的关系。
  • 解释为什么 OpenCode 故意禁止从仓库根目录运行测试。
  • 为一次核心 runtime 改动选择正确的 typecheck、test 与 build 边界。
  • 区分“任务存在”“任务被编排”“本次已经执行并通过”。
  • 为 mini agent 设计一条按风险递增的验证阶梯。

OpenCode 的工程化不是一个万能的根目录命令,而是“根目录管理包与任务图,子 package 对自己的类型、测试和构建负责”,让验证范围与真实模块边界对齐。

中心问题是:在一个包含 runtime、SDK 和多个 UI 的 monorepo 里,怎样既避免漏测,又避免每改一行都盲跑全世界?

root package.json
+-- workspaces:有哪些 package
+-- catalog:共享依赖版本
+-- root scripts:跨包入口与保护栏
|
v
turbo.json
任务依赖与产物
|
+------+----------------+
v v
packages/opencode packages/sdk/js
typecheck/test/build typecheck/build

根目录决定“有哪些队伍、公共版本是什么、跨队依赖怎样排”;package 决定“本队如何验证”。

Java 开发者可以类比 Gradle multi-project + version catalog + task graph。但 Bun workspace 脚本不是 Maven 生命周期,不能凭 test 这个名字猜执行范围。

3. 最小机制:按影响面选择任务

Section titled “3. 最小机制:按影响面选择任务”

一个可迁移的最小工程化策略是:

change(files):
owners = packagesOwning(files)
run fast static checks for owners
run focused tests near changed behavior
run dependent builds/tests from task graph
only then run broader regression required for delivery

OpenCode 把部分依赖写进 turbo.json:例如 opencode#test 依赖上游 package build,见 turbo.json:11-15

turbo.json turbo.json:11-15
11    "opencode#test": {12      "dependsOn": ["^build"],13      "outputs": [],14      "passThroughEnv": ["*"]15    },

这不是“永远只跑最小测试”。它是先用依赖图找到必要范围,再根据发布风险扩展回归。

看到什么能证明什么不能证明什么
package 有 test script项目定义了测试入口当前测试已经通过
Turbo 有 dependsOn经 Turbo 调度时存在依赖手工进入 package 执行会自动跑上游
dist/** 声明为 outputsTurbo 可把它当构建产物产物内容正确、可发布

工程文档最常见的失真,是把“配置上存在”写成“本次验证成功”。本章只做源码声明解读。

  1. package.json:7-21:runtime 版本、根脚本和禁止 root test。
package.json package.json:7-21
7  "packageManager": "bun@1.3.14",8  "scripts": {项目脚本入口。9    "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",常用工程命令。10    "dev:desktop": "bun --cwd packages/desktop dev",11    "dev:web": "bun --cwd packages/app dev",12    "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",13    "dev:storybook": "bun --cwd packages/storybook storybook",14    "lint": "oxlint",常用工程命令。15    "typecheck": "bun turbo typecheck",常用工程命令。16    "upgrade-opentui": "bun run script/upgrade-opentui.ts",17    "postinstall": "bun run --cwd packages/opencode fix-node-pty",18    "prepare": "husky",19    "random": "echo 'Random script'",20    "hello": "echo 'Hello World!'",21    "test": "echo 'do not run tests from root' && exit 1"常用工程命令。
  1. package.json:23-87:workspace 与依赖 catalog。
package.json package.json:23-87
23  "workspaces": {声明工作区范围。24    "packages": [25      "packages/*",26      "packages/console/*",27      "packages/sdk/js",28      "packages/slack"29    ],30    "catalog": {31      "@effect/opentelemetry": "4.0.0-beta.65",32      "@effect/platform-node": "4.0.0-beta.65",33      "@npmcli/arborist": "9.4.0",34      "@types/bun": "1.3.13",35      "@types/cross-spawn": "6.0.6",36      "@octokit/rest": "22.0.0",37      "@hono/zod-validator": "0.4.2",38      "@opentui/core": "0.2.14",39      "@opentui/keymap": "0.2.14",40      "@opentui/solid": "0.2.14",41      "ulid": "3.0.1",42      "@kobalte/core": "0.13.11",43      "@types/luxon": "3.7.1",44      "@types/node": "24.12.2",45      "@types/semver": "7.7.1",46      "@tsconfig/node22": "22.0.2",47      "@tsconfig/bun": "1.0.9",48      "@cloudflare/workers-types": "4.20251008.0",49      "@openauthjs/openauth": "0.0.0-20250322224806",50      "@pierre/diffs": "1.1.0-beta.18",51      "opentui-spinner": "0.0.6",52      "@solid-primitives/storage": "4.3.3",53      "@tailwindcss/vite": "4.1.11",54      "diff": "8.0.2",55      "dompurify": "3.3.1",56      "drizzle-kit": "1.0.0-beta.19-d95b7a4",57      "drizzle-orm": "1.0.0-beta.19-d95b7a4",58      "effect": "4.0.0-beta.65",59      "ai": "6.0.168",60      "cross-spawn": "7.0.6",61      "hono": "4.10.7",62      "hono-openapi": "1.1.2",63      "fuzzysort": "3.1.0",64      "luxon": "3.6.1",65      "marked": "17.0.1",66      "marked-shiki": "1.2.1",67      "remend": "1.3.0",68      "@playwright/test": "1.59.1",69      "semver": "7.7.4",70      "typescript": "5.8.2",71      "@typescript/native-preview": "7.0.0-dev.20251207.1",72      "zod": "4.1.8",73      "remeda": "2.26.0",74      "shiki": "3.20.0",75      "solid-list": "0.3.0",76      "tailwindcss": "4.1.11",77      "virtua": "0.49.1",78      "vite": "7.1.4",79      "@solidjs/meta": "0.29.4",80      "@solidjs/router": "0.15.4",81      "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",82      "@sentry/solid": "10.36.0",83      "@sentry/vite-plugin": "4.6.0",84      "solid-js": "1.9.10",85      "vite-plugin-solid": "2.11.10",86      "@lydell/node-pty": "1.2.0-beta.10"87    }
  1. turbo.json:5-42:task graph、依赖和 outputs。
turbo.json turbo.json:5-42
5  "tasks": {6    "typecheck": {},常用工程命令。7    "build": {常用工程命令。8      "dependsOn": [],9      "outputs": ["dist/**"]10    },11    "opencode#test": {12      "dependsOn": ["^build"],13      "outputs": [],14      "passThroughEnv": ["*"]15    },16    "test:ci": {17      "outputs": [".artifacts/unit/junit.xml"],18      "passThroughEnv": ["*"]19    },20    "opencode#test:ci": {21      "dependsOn": ["^build"],22      "outputs": [".artifacts/unit/junit.xml"],23      "passThroughEnv": ["*"]24    },25    "@opencode-ai/app#test": {26      "dependsOn": ["^build"],27      "outputs": []28    },29    "@opencode-ai/app#test:ci": {30      "dependsOn": ["^build"],31      "outputs": [".artifacts/unit/junit.xml"],32      "passThroughEnv": ["*"]33    },34    "@opencode-ai/ui#test": {35      "dependsOn": ["^build"],36      "outputs": []37    },38    "@opencode-ai/ui#test:ci": {39      "dependsOn": ["^build"],40      "outputs": [".artifacts/unit/junit.xml"],41      "passThroughEnv": ["*"]42    }
  1. tsconfig.json:1-5:根 TypeScript 基线有意保持很薄。
  2. AGENTS.md:119-127:测试与 typecheck 的人工契约。
AGENTS.md AGENTS.md:119-127
119## Testing文档标题层级。120121- Avoid mocks as much as possible列表里的一个要点。122- Test actual implementation, do not duplicate logic into tests列表里的一个要点。123- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.列表里的一个要点。124125## Type Checking文档标题层级。126127- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.列表里的一个要点。
  1. packages/opencode/package.json:8-19:核心 runtime 的验证入口。
packages/opencode/package.json packages/opencode/package.json:8-19
8  "scripts": {项目脚本入口。9    "typecheck": "tsgo --noEmit",常用工程命令。10    "test": "bun test --timeout 30000",常用工程命令。11    "test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",12    "test:httpapi": "bun run script/httpapi-exercise.ts --mode coverage --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode auth --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode effect --fail-on-missing --fail-on-skip",13    "bench:test": "bun run script/bench-test-suite.ts",14    "profile:test": "bun run script/profile-test-files.ts",15    "build": "bun run script/build.ts",常用工程命令。16    "fix-node-pty": "bun run script/fix-node-pty.ts",17    "dev": "bun run --conditions=browser ./src/index.ts",常用工程命令。18    "dev:temporary": "bun run --conditions=browser ./src/temporary.ts",19    "db": "bun drizzle-kit"
  1. packages/opencode/package.json:21-37:bin 与 Bun/Node 条件导入。
packages/opencode/package.json packages/opencode/package.json:21-37
21  "bin": {22    "opencode": "./bin/opencode"23  },24  "exports": {包对外暴露入口。25    "./*": "./src/*.ts"26  },27  "imports": {28    "#db": {29      "bun": "./src/storage/db.bun.ts",30      "node": "./src/storage/db.node.ts",31      "default": "./src/storage/db.bun.ts"32    },33    "#pty": {34      "bun": "./src/pty/pty.bun.ts",35      "node": "./src/pty/pty.node.ts",36      "default": "./src/pty/pty.bun.ts"37    }
  1. packages/sdk/js/package.json:7-18:SDK 的 typecheck、生成构建和 exports。
packages/sdk/js/package.json packages/sdk/js/package.json:7-18
7  "scripts": {项目脚本入口。8    "typecheck": "tsgo --noEmit",常用工程命令。9    "build": "bun ./script/build.ts"常用工程命令。10  },11  "exports": {包对外暴露入口。12    ".": "./src/index.ts",13    "./client": "./src/client.ts",14    "./server": "./src/server.ts",15    "./v2": "./src/v2/index.ts",16    "./v2/client": "./src/v2/client.ts",17    "./v2/gen/client": "./src/v2/gen/client/index.ts",18    "./v2/server": "./src/v2/server.ts"

6. 一条具体工程旅程:修改 session API 后该怎么验证

Section titled “6. 一条具体工程旅程:修改 session API 后该怎么验证”

假设你修改了核心 runtime 的 session HTTP API,并因此需要重新生成 JS SDK。下面是从仓库契约推导出的验证路径,不表示这些命令在本章编写时已经执行。

服务端改动属于 packages/opencode;generated client 属于 packages/sdk/js。根 workspaces.packages 明确包含 packages/*packages/sdk/js,见 package.json:23-29

package.json package.json:23-29
23  "workspaces": {声明工作区范围。24    "packages": [25      "packages/*",26      "packages/console/*",27      "packages/sdk/js",28      "packages/slack"29    ],

所以这是跨两个 package 的改动,不能只对根 tsconfig.json 跑一次编译就结束。

第二步:用 package 声明的方式做 typecheck

Section titled “第二步:用 package 声明的方式做 typecheck”

核心包声明:

typecheck = tsgo --noEmit

packages/opencode/package.json:8-10。SDK 也有自己的 tsgo --noEmit,见 packages/sdk/js/package.json:7-10

packages/opencode/package.json packages/opencode/package.json:8-10
8  "scripts": {项目脚本入口。9    "typecheck": "tsgo --noEmit",常用工程命令。10    "test": "bun test --timeout 30000",常用工程命令。
packages/sdk/js/package.json packages/sdk/js/package.json:7-10
7  "scripts": {项目脚本入口。8    "typecheck": "tsgo --noEmit",常用工程命令。9    "build": "bun ./script/build.ts"常用工程命令。10  },

仓库规范进一步要求从 package 目录运行 bun typecheck,不要直接调用 tsc,见 AGENTS.md:125-127。原因不是语法偏好,而是 package script 才包含仓库选择的编译器和参数。

AGENTS.md AGENTS.md:125-127
125## Type Checking文档标题层级。126127- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.列表里的一个要点。

第三步:重新生成 SDK 是显式步骤

Section titled “第三步:重新生成 SDK 是显式步骤”

SDK package 的 build 指向 bun ./script/build.ts,见 packages/sdk/js/package.json:7-10;根 AGENTS.md:1 也明确指出 SDK 生成入口。

packages/sdk/js/package.json packages/sdk/js/package.json:7-10
7  "scripts": {项目脚本入口。8    "typecheck": "tsgo --noEmit",常用工程命令。9    "build": "bun ./script/build.ts"常用工程命令。10  },
AGENTS.md AGENTS.md:1
1- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.列表里的一个要点。

因此,API contract 改了但没有更新 generated client,是一个可预见的交付缺口。生成物是否变化、类型是否对应,都应该在 diff 与 SDK typecheck 中验证。

第四步:核心测试必须在 package 内运行

Section titled “第四步:核心测试必须在 package 内运行”

package.json 的 test 会打印禁止信息并退出 1,见 package.json:8-21AGENTS.md 同时要求尽量少 mock、测试真实实现,并从具体 package 运行测试,见 AGENTS.md:119-123

package.json package.json:8-21
8  "scripts": {项目脚本入口。9    "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",常用工程命令。10    "dev:desktop": "bun --cwd packages/desktop dev",11    "dev:web": "bun --cwd packages/app dev",12    "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",13    "dev:storybook": "bun --cwd packages/storybook storybook",14    "lint": "oxlint",常用工程命令。15    "typecheck": "bun turbo typecheck",常用工程命令。16    "upgrade-opentui": "bun run script/upgrade-opentui.ts",17    "postinstall": "bun run --cwd packages/opencode fix-node-pty",18    "prepare": "husky",19    "random": "echo 'Random script'",20    "hello": "echo 'Hello World!'",21    "test": "echo 'do not run tests from root' && exit 1"常用工程命令。
AGENTS.md AGENTS.md:119-123
119## Testing文档标题层级。120121- Avoid mocks as much as possible列表里的一个要点。122- Test actual implementation, do not duplicate logic into tests列表里的一个要点。123- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.列表里的一个要点。

核心包提供:

  • test:Bun test,30 秒 timeout;
  • test:ci:额外生成 JUnit;
  • test:httpapi:coverage、auth、effect 三类 HTTP API exercise;
  • bench:testprofile:test:性能调查入口。

packages/opencode/package.json:8-15

packages/opencode/package.json packages/opencode/package.json:8-15
8  "scripts": {项目脚本入口。9    "typecheck": "tsgo --noEmit",常用工程命令。10    "test": "bun test --timeout 30000",常用工程命令。11    "test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",12    "test:httpapi": "bun run script/httpapi-exercise.ts --mode coverage --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode auth --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode effect --fail-on-missing --fail-on-skip",13    "bench:test": "bun run script/bench-test-suite.ts",14    "profile:test": "bun run script/profile-test-files.ts",15    "build": "bun run script/build.ts",常用工程命令。

对于 session HTTP API 改动,focused test 与 test:httpapi 比“误跑根 test”更接近风险面。最终交付需要多大回归,仍由改动范围与项目要求决定。

第五步:理解 Turbo 会补哪些依赖

Section titled “第五步:理解 Turbo 会补哪些依赖”

经 Turbo 执行 opencode#test 时,会先做 ^build,即上游 workspace 依赖构建,见 turbo.json:11-15。CI 版本也声明 JUnit output,并同样依赖上游 build,见 turbo.json:20-23

turbo.json turbo.json:11-15
11    "opencode#test": {12      "dependsOn": ["^build"],13      "outputs": [],14      "passThroughEnv": ["*"]15    },
turbo.json turbo.json:20-23
20    "opencode#test:ci": {21      "dependsOn": ["^build"],22      "outputs": [".artifacts/unit/junit.xml"],23      "passThroughEnv": ["*"]

这解决了“核心测试引用了尚未构建的 workspace 包”。但如果开发者直接在 packages/opencode 手工运行 package test,就不能假设 Turbo 帮他做了这一步。

可信报告应分别说:

声明检查:脚本/任务存在
实际执行:命令、目录、退出码
覆盖边界:哪些 package / 哪类路径没跑
产物检查:generated diff / dist 是否符合预期

“代码能 typecheck”不等于“HTTP 行为正确”;“单测通过”也不等于“SDK 已更新”。

根 workspace 列出主要 package 集合;catalog 固定 effectai、TypeScript、UI 框架等共享版本,见 package.json:23-87

package.json package.json:23-87
23  "workspaces": {声明工作区范围。24    "packages": [25      "packages/*",26      "packages/console/*",27      "packages/sdk/js",28      "packages/slack"29    ],30    "catalog": {31      "@effect/opentelemetry": "4.0.0-beta.65",32      "@effect/platform-node": "4.0.0-beta.65",33      "@npmcli/arborist": "9.4.0",34      "@types/bun": "1.3.13",35      "@types/cross-spawn": "6.0.6",36      "@octokit/rest": "22.0.0",37      "@hono/zod-validator": "0.4.2",38      "@opentui/core": "0.2.14",39      "@opentui/keymap": "0.2.14",40      "@opentui/solid": "0.2.14",41      "ulid": "3.0.1",42      "@kobalte/core": "0.13.11",43      "@types/luxon": "3.7.1",44      "@types/node": "24.12.2",45      "@types/semver": "7.7.1",46      "@tsconfig/node22": "22.0.2",47      "@tsconfig/bun": "1.0.9",48      "@cloudflare/workers-types": "4.20251008.0",49      "@openauthjs/openauth": "0.0.0-20250322224806",50      "@pierre/diffs": "1.1.0-beta.18",51      "opentui-spinner": "0.0.6",52      "@solid-primitives/storage": "4.3.3",53      "@tailwindcss/vite": "4.1.11",54      "diff": "8.0.2",55      "dompurify": "3.3.1",56      "drizzle-kit": "1.0.0-beta.19-d95b7a4",57      "drizzle-orm": "1.0.0-beta.19-d95b7a4",58      "effect": "4.0.0-beta.65",59      "ai": "6.0.168",60      "cross-spawn": "7.0.6",61      "hono": "4.10.7",62      "hono-openapi": "1.1.2",63      "fuzzysort": "3.1.0",64      "luxon": "3.6.1",65      "marked": "17.0.1",66      "marked-shiki": "1.2.1",67      "remend": "1.3.0",68      "@playwright/test": "1.59.1",69      "semver": "7.7.4",70      "typescript": "5.8.2",71      "@typescript/native-preview": "7.0.0-dev.20251207.1",72      "zod": "4.1.8",73      "remeda": "2.26.0",74      "shiki": "3.20.0",75      "solid-list": "0.3.0",76      "tailwindcss": "4.1.11",77      "virtua": "0.49.1",78      "vite": "7.1.4",79      "@solidjs/meta": "0.29.4",80      "@solidjs/router": "0.15.4",81      "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",82      "@sentry/solid": "10.36.0",83      "@sentry/vite-plugin": "4.6.0",84      "solid-js": "1.9.10",85      "vite-plugin-solid": "2.11.10",86      "@lydell/node-pty": "1.2.0-beta.10"87    }

这类似 dependency management:子 package 可以使用 catalog:,减少同库多版本漂移。代价是根 catalog 变更影响面很大,应视为跨包升级,而不是单包小改。

tsconfig.json 只继承 Bun 基线,没有集中塞入大量 compiler option,见 tsconfig.json:1-5。这反过来强调 package 自己的 typecheck 配置与 script 才是执行边界。

turbo.json 展示了三类配置:

  • typecheck 没声明跨包依赖;
  • build 产出 dist/**
  • 特定 test task 依赖 ^build,CI task还记录 JUnit output。

turbo.json:5-42

turbo.json turbo.json:5-42
5  "tasks": {6    "typecheck": {},常用工程命令。7    "build": {常用工程命令。8      "dependsOn": [],9      "outputs": ["dist/**"]10    },11    "opencode#test": {12      "dependsOn": ["^build"],13      "outputs": [],14      "passThroughEnv": ["*"]15    },16    "test:ci": {17      "outputs": [".artifacts/unit/junit.xml"],18      "passThroughEnv": ["*"]19    },20    "opencode#test:ci": {21      "dependsOn": ["^build"],22      "outputs": [".artifacts/unit/junit.xml"],23      "passThroughEnv": ["*"]24    },25    "@opencode-ai/app#test": {26      "dependsOn": ["^build"],27      "outputs": []28    },29    "@opencode-ai/app#test:ci": {30      "dependsOn": ["^build"],31      "outputs": [".artifacts/unit/junit.xml"],32      "passThroughEnv": ["*"]33    },34    "@opencode-ai/ui#test": {35      "dependsOn": ["^build"],36      "outputs": []37    },38    "@opencode-ai/ui#test:ci": {39      "dependsOn": ["^build"],40      "outputs": [".artifacts/unit/junit.xml"],41      "passThroughEnv": ["*"]42    }

为什么不让所有任务一律 dependsOn: ["^build"]?源码没有写解释。合理的设计解释是:不同任务对上游构建产物的依赖不同,过度串行会拖慢反馈。这个“为什么”是推断,不是代码明示。

packages/opencode/package.json 不只声明脚本,还通过 imports 为 #db#pty 选择 Bun、Node 与 default 实现,见 packages/opencode/package.json:21-37

packages/opencode/package.json packages/opencode/package.json:21-37
21  "bin": {22    "opencode": "./bin/opencode"23  },24  "exports": {包对外暴露入口。25    "./*": "./src/*.ts"26  },27  "imports": {28    "#db": {29      "bun": "./src/storage/db.bun.ts",30      "node": "./src/storage/db.node.ts",31      "default": "./src/storage/db.bun.ts"32    },33    "#pty": {34      "bun": "./src/pty/pty.bun.ts",35      "node": "./src/pty/pty.node.ts",36      "default": "./src/pty/pty.bun.ts"37    }

这提醒测试设计:类型通过不代表所有运行环境都通过。涉及数据库或 PTY 的改动,应按条件导入覆盖实际目标 runtime。

选择好处代价
禁止 root test强迫开发者选择真实 package 边界初学者会觉得入口不直观
catalog 统一版本降低 workspace 依赖漂移升级影响面集中
package scripts 封装工具命令与参数可演进不能凭通用生态习惯直接跑 tsc
Turbo 只声明必要依赖反馈更快、缓存更准确task graph 需要持续维护
SDK 生成作为 build契约变化可机械传播生成物与源码必须一起审查

方法一:让测试入口匹配所有权边界

Section titled “方法一:让测试入口匹配所有权边界”

根目录负责发现与编排,package 负责具体命令。不要设计一个含义模糊的“全测”按钮。

验证问题:失败时,开发者能否立刻知道是哪个 package、哪类检查?

方法二:把生成代码当交付链的一等公民

Section titled “方法二:把生成代码当交付链的一等公民”

contract 变化要能触发生成、diff、typecheck,而不是靠发布前想起来。

验证问题:CI 能否发现 server schema 已变、SDK 生成物没更新?

静态检查快、focused test 定位准、跨包 build 捕获集成问题、E2E 验证用户路径。交付时报告实际跑到哪一级。

验证问题:一个 permission 行为变更为什么不能只靠 typecheck?

请回答:

  1. 为什么根目录可以 typecheck,却故意不能 test?
  2. package script 与 Turbo task 各负责什么?
  3. “配置了 test:httpapi”和“本次 test:httpapi 通过”有什么区别?

练习阶梯:

  • 入门:为 root、opencode、SDK 三层各写一句职责。

  • 进阶:给“新增一个 session endpoint”列出按速度排序的验证阶梯。

  • 源码追踪:从 turbo.json:11 解释 ^build 对直接运行 package test 有何边界。

    turbo.json turbo.json:11
    11    "opencode#test": {
  • 迁移:为 mini agent 设计 root guard,防止开发者在错误目录误跑测试。

最后复盘:工程化是可执行的信任链

Section titled “最后复盘:工程化是可执行的信任链”
文件改动
-> package ownership
-> package typecheck / focused test
-> task graph 补齐上游
-> generated/build 产物
-> 清楚报告已验证与未验证边界

到这里,我们已经读完 runtime 的关键模块与支撑它的工程边界。最后一章不再继续堆 OpenCode 细节,而是做一次删减实验:哪些机制必须保留,才能亲手做出一个小而真的 coding agent?