docs(rfc): formalize SDK follow-up design

This commit is contained in:
Tianyi Cui
2026-07-19 21:21:39 +08:00
parent 7a5d3ae5ce
commit fe5c3e2a7a
5 changed files with 243 additions and 196 deletions

View File

@@ -15,6 +15,7 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand;
| [SQLite FTS5 session search](proposed/feature/2026-07-10-sqlite-session-query-provider.md) | 2026-07-10 |
| [Stream workflow progress through tool calls](proposed/feature/2026-07-13-stream-workflow-progress-through-tool-calls.md) | 2026-07-13 |
| [Developer-owned SDK projects](proposed/feature/2026-07-14-sdk-developer-projects.md) | 2026-07-14 |
| [SDK follow-up capabilities](proposed/feature/2026-07-17-sdk-follow-up-capabilities.md) | 2026-07-17 |
### Simplification

View File

@@ -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
2026-07-17-sdk-follow-up-capabilities.md: 8fa83018b3eace1844148ed02cc77ac67571a67c
2026-07-17-sdk-follow-up-capabilities.zh.md: b6cd56ac315ff758c128eade671544ae3820e8e9

View File

@@ -0,0 +1,118 @@
# RFC: SDK follow-up capabilities
Status: proposed
English | [中文](2026-07-17-sdk-follow-up-capabilities.zh.md)
## Problem
The first SDK release creates and edits developer-owned Cordis projects through the shared model defined by the [developer-project RFC](2026-07-14-sdk-developer-projects.md) and the [project-editing architecture](../architecture/2026-07-15-sdk-project-editing-architecture.md). Its create and config workflows are interactive, external Cordis plugins require manual dependency and configuration edits, command-line telemetry has no owning boundary, and interactive branches lack a stable test strategy.
These gaps are coupled. Create and config already share questions, feature configuration, and `ProjectEditSession`; adding separate automation paths would duplicate that domain logic. External-plugin installation must update both the package manager's files and `cordis.yml`. Telemetry must observe commands such as create and build that do not boot Cordis. Interactive testing must exercise Harness behavior without making terminal rendering a brittle product contract.
## Proposal
The SDK extends the existing prompt and project-editing boundaries instead of creating parallel workflows. A non-interactive prompt port and structured feature plan drive create and config, `dsh-sdk create <source>` delegates dependency resolution to the project package manager before mounting the resolved package through `ProjectEditSession`, launcher-side telemetry wraps every `dsh-sdk` command, and injected prompt streams provide the primary interactive-test seam.
| Capability | Product entrypoint | Owning mechanism | Required outcome |
|---|---|---|---|
| Headless project creation | `create-sdk --config <file>` or `--config-json <json>` with optional `--json` | `HeadlessPromptPort`, structured project answers, and a complete feature plan | No terminal blocking; missing required input is explicit |
| External Cordis plugin installation | `dsh-sdk create <source>` | Native package-manager `add` plus `ProjectEditSession` | The dependency and `cordis.yml` entry identify the package manager's resolved package |
| Developer-cycle telemetry | Every `dsh-sdk` command | Launcher-side consent, payload, redaction, anonymous identity, and delivery services | Reporting is best-effort and cannot change the command result |
| Interactive regression coverage | Create and config tests | Injected `PromptPort` input/output and filesystem assertions | Tests cover Harness decisions and generated files without snapshotting terminal repainting |
## Shared headless workflow
### Structured input and lifecycle events
Headless create accepts a JSON object either inline through `--config-json` or from a file through `--config`. Scalar fields supply the ordinary create answers, while `features` supplies the complete selected feature set, feature options, secrets, and dedicated values. Defaults remain valid only where the owning question declares one; the headless path never invents an answer for a required prompt.
With `--json`, stdout is an NDJSON event stream. `done` means creation and any requested setup completed, `action-required` names an unanswered required prompt, and `error` reports another failure. Human-readable progress and package-manager output go to stderr so every stdout line remains parseable as one event. A caller responds to `action-required` by adding the missing value and running the command again.
Create and config consume the same feature-plan shape. Create exposes it through the command-line inputs above; config uses it at the shared workflow boundary so a later automation entrypoint does not need a second feature-selection model.
### Prompt and project-editing boundaries
`PromptPort` remains the only boundary between SDK questions and an interaction implementation. `ClackPromptPort` handles terminals. `HeadlessPromptPort` consumes defaults exposed by the question contract and otherwise fails with the unanswered prompt; prefilled values normally prevent the port from being called.
Both paths use the same `Question` objects, `FeatureConfigurator`, `SdkProject`, and `ProjectEditSession`. The headless path therefore changes how answers arrive, not how features are interpreted or files are committed.
### Agent skill
The repository ships a thin `SKILL.md` that teaches an agent to construct the structured input, request NDJSON, fill an `action-required` value, and retry. The skill invokes the public CLI and does not import an internal SDK API or introduce another project specification.
## External Cordis plugin installation
`dsh-sdk create <source>` accepts a package-manager-native npm specifier such as `pkg@version` or a GitHub specifier such as `github:owner/repo#ref`. After confirmation, it asks the project's package manager to add the source, compares the direct dependency names before and after the operation, reopens the project, and mounts each newly resolved package in `cordis.yml` through `ProjectEditSession`.
The package manager owns source parsing, version or commit resolution, integrity data, lockfile updates, and any build policy. The SDK does not download or unpack a second copy through giget or pacote. An external plugin remains a dependency under `node_modules`; local plugin scaffolding remains a separate project-creation concern.
## Launcher telemetry
### Consent and collection
Telemetry wraps the `dsh-sdk` launcher command lifecycle because create and build do not reliably boot Cordis. One event records the command name, duration, success, a random per-user anonymous identifier, and redacted `cordis.yml` and `package.json` text when those project files are eligible.
Reporting is enabled unless a present telemetry config entry is explicitly disabled. `DO_NOT_TRACK` and CI deny reporting regardless of project configuration. A missing `cordis.yml` does not itself deny the event, but `package.json` content is included only when `cordis.yml` establishes that the directory is an SDK project.
### Safety and delivery
The payload builder never reads `.env`. It redacts secret-shaped keys and values, known token forms, PEM blocks, URL credentials, and high-entropy opaque strings in the two eligible text files. Redaction is a safety backstop rather than a guarantee; SDK projects must keep credentials in `.env`.
The reporter uses a fixed endpoint and resolves every send path without throwing. Command dispatch records success or failure in a `finally` path, starts reporting after the command outcome is known, and drains within a bounded interval. Consent parsing, payload construction, storage, or network failures are swallowed only at this telemetry boundary and never alter the command's exit code.
## Interactive workflow testing
Create and config tests inject a `PromptPort` and scripted input/output streams into the existing workflows. Parameterized scenarios cover feature selection, feature options, secrets, cancellation, review, and apply behavior, then assert the resulting `cordis.yml` and other project files. The stable product assertion is the generated project state, not clack's ANSI redraw sequence.
One or two optional real-PTY smoke tests may cover the shipped binary and TTY guard that injection cannot reproduce. Native PTY tooling does not belong on the required path unless it is reliable across the repository's supported Node and host versions.
## Deferred work
- Extend the headless create specification to express local `plugin` or `tool` scaffolding instead of defaulting that interactive choice to none.
- Expose the telemetry opt-out in create and config while preserving the consent representation in which only a disabled telemetry entry is written.
- Define whether GitHub source dependencies must be prebuilt or may run package-manager-controlled preparation scripts, and surface the policy before installation.
- Replace the telemetry package's `.invalid` endpoint placeholder with the production endpoint before release.
## Alternatives considered
**Build a separate headless creation engine.** This would duplicate questions, feature requirements, configuration behavior, and project-editing rules. Reusing the prompt and edit-session boundaries keeps one implementation of project semantics.
**Make a specification file the primary automation interface.** Agents can pass the same typed JSON object inline, while people and CI may still use a file. A file-only protocol adds persistence and cleanup without adding semantics.
**Use `npx skills add` as the project creator.** The skills CLI installs Markdown skills; it does not create SDK projects or install npm packages. The agent skill therefore drives the SDK initializer instead of replacing it.
**Fetch GitHub and npm sources through giget or pacote.** A second fetch layer would duplicate package-manager resolution, integrity, lockfile, and lifecycle policy. Native dependency specifiers keep those decisions in the selected package manager.
**Implement telemetry as a Cordis runtime plugin.** Create and build do not necessarily boot Cordis, so a runtime plugin cannot observe the complete developer command cycle. The launcher is the boundary shared by those commands.
**Derive the anonymous identifier from git metadata.** Repository remotes can identify a project or organization. A random per-user identifier supports aggregation without encoding repository identity.
**Collect only aggregate counters.** Aggregate-only events reduce exposure but cannot answer which plugins, dependencies, and configuration shapes developers actually use. This proposal accepts collection of redacted project text and makes that exposure explicit.
**Use real PTYs and transcript snapshots as the primary test strategy.** Native PTY dependencies and terminal repaint sequences add platform and rendering instability while mostly testing clack. Injected interaction plus generated-file assertions tests the SDK-owned behavior directly.
## Acceptance criteria
- Create runs without a TTY from a complete structured input, emits only NDJSON on stdout under `--json`, and reports missing required input as `action-required` without writing a partial project.
- Create and config resolve the same feature-plan contract through the shared question, feature-configuration, and project-editing code paths.
- `dsh-sdk create <source>` uses the selected project package manager, mounts the dependency name that operation actually added, and fails loudly when no new dependency can be identified.
- Every `dsh-sdk` command reaches one best-effort telemetry completion path; an explicit disabled entry, `DO_NOT_TRACK`, or CI prevents delivery, and telemetry failures never change the command result.
- Telemetry never reads `.env`, withholds unrelated `package.json` content when no `cordis.yml` exists, redacts both eligible text payloads, and uses an identifier unrelated to git metadata.
- Interactive tests cover create and config decisions through injected interaction and assert committed project files; any real-PTY coverage remains a narrow smoke layer.
- The agent skill documents the public structured-input and event contracts without depending on private package exports.
## Risks
- Full redacted `cordis.yml` and `package.json` text still reveals plugin and dependency names, URLs, paths, and configuration values to the endpoint operator, and heuristic redaction can miss a secret.
- Default-on reporting may surprise developers when no telemetry entry exists; the CLI must make the opt-out discoverable before release.
- A package-manager add can change `package.json`, the lockfile, and installed files before `ProjectEditSession` mounts the plugin, so a later mount failure can leave dependency changes that require manual recovery.
- GitHub dependencies may execute preparation or lifecycle code according to package-manager policy; an unresolved build policy is a supply-chain and reproducibility risk.
- Injected prompt tests do not prove raw-mode, signal, or repaint behavior in a real terminal; the optional smoke layer must cover only those residual contracts.
## References
- [Vercel Eve](https://github.com/vercel/eve) and [Vercel Labs Skills](https://github.com/vercel-labs/skills) for the distinction between a headless initializer and skill distribution.
- [npm package specifications](https://docs.npmjs.com/cli/v11/using-npm/package-spec), [pnpm add](https://pnpm.io/cli/add), and [Yarn add](https://yarnpkg.com/cli/add) for package-manager-native sources.
- [Console Do Not Track](https://consoledonottrack.com/) for the environment-level opt-out convention.
- [Clack](https://github.com/bombshell-dev/clack) and [Vitest snapshots](https://vitest.dev/guide/snapshot) for injected prompts and generated-file assertions.

View File

@@ -0,0 +1,118 @@
# RFC: SDK 后续功能
Status: proposed
[English](2026-07-17-sdk-follow-up-capabilities.md) | 中文
## 问题
首个 SDK 版本通过[开发者工程 RFC](2026-07-14-sdk-developer-projects.md) 和 [SDK 工程编辑架构](../architecture/2026-07-15-sdk-project-editing-architecture.md)定义的共享模型创建和编辑开发者拥有的 Cordis 工程。create 和 config 工作流仅支持交互调用,接入外部 Cordis 插件需要手工修改依赖和配置,命令行遥测没有明确的所属边界,交互分支也缺少稳定的测试策略。
这些缺口彼此关联。create 和 config 已经共享问题、功能配置和 `ProjectEditSession`;若另建自动化路径,就会复制领域逻辑。安装外部插件必须同时修改包管理器文件和 `cordis.yml`。遥测需要观察 create、build 等不会启动 Cordis 的命令。交互测试需要覆盖 Harness 自身行为,同时避免把终端渲染固化成脆弱的产品契约。
## 提案
SDK 扩展现有提示词与工程编辑边界,不另建平行工作流。非交互式 `PromptPort` 实现和结构化功能计划驱动 create 与 config`dsh-sdk create <source>` 先把依赖解析交给工程的包管理器,再通过 `ProjectEditSession` 挂载解析所得的包;`dsh-sdk` 启动器侧的遥测包住每个命令;交互测试主要通过注入的提示词输入输出流完成。
| 功能 | 产品入口 | 所属机制 | 必须达到的结果 |
|---|---|---|---|
| Headless 工程创建 | `create-sdk --config <file>``--config-json <json>`,可搭配 `--json` | `HeadlessPromptPort`、结构化工程答案和完整功能计划 | 不阻塞等待终端;明确报告缺失的必答输入 |
| 外部 Cordis 插件安装 | `dsh-sdk create <source>` | 包管理器原生 `add``ProjectEditSession` | 依赖和 `cordis.yml` 配置项指向包管理器解析出的包 |
| 开发周期遥测 | 每个 `dsh-sdk` 命令 | 启动器侧的上报条件判断、遥测内容构建、脱敏、匿名身份和传输服务 | 上报采用尽力而为语义,不能改变命令结果 |
| 交互回归覆盖 | create 和 config 测试 | 注入的 `PromptPort` 输入输出和文件系统断言 | 测试覆盖 Harness 决策与生成文件,不快照终端重绘 |
## 共享 headless 工作流
### 结构化输入和生命周期事件
Headless create 通过 `--config-json` 接收内联 JSON 对象,或通过 `--config` 从文件读取。标量字段提供普通 create 答案,`features` 提供完整的已选功能、功能选项、secret密钥和专用值。只有所属问题明确声明的默认值才有效headless 路径绝不为必答问题臆造答案。
使用 `--json`stdout 是 NDJSON 事件流。`done` 表示创建及要求执行的安装和构建均已完成,`action-required` 指明一个尚未回答的必答问题,`error` 报告其他失败。面向人的进度信息和包管理器输出写入 stderr确保 stdout 每一行都能解析成一个事件。调用方收到 `action-required` 后补充缺失值,再次运行命令。
Create 和 config 使用相同的功能计划形状。create 通过上述命令行输入公开该形状config 在共享工作流边界使用同一形状,使后续自动化入口无需另建功能选择模型。
### Prompt 与工程编辑边界
`PromptPort` 仍是 SDK 问题与交互实现之间的唯一边界。`ClackPromptPort` 负责终端交互。`HeadlessPromptPort` 使用问题契约公开的默认值,否则通过未回答问题快速失败;预填值通常会让流程根本不调用该 port。
两条路径使用相同的 `Question` 对象、`FeatureConfigurator``SdkProject``ProjectEditSession`。因此headless 路径只改变答案的到达方式,不改变功能解释或文件提交方式。
### Agent skill
仓库提供一份轻量 `SKILL.md`,指导 agent skill智能体技能构造结构化输入、请求 NDJSON、补充 `action-required` 指明的值并重试。该 skill 调用公开 CLI不导入 SDK 内部 API也不引入另一套工程规格。
## 外部 Cordis 插件安装
`dsh-sdk create <source>` 接受包管理器原生的 npm package specifier例如 `pkg@version`,也接受 `github:owner/repo#ref` 等 GitHub package specifier。用户确认后命令要求工程包管理器添加来源对比操作前后的直接依赖名重新打开工程再通过 `ProjectEditSession` 把每个新增且已解析的包挂载进 `cordis.yml`
包管理器负责来源解析、版本或 commit 解析、`integrity` 数据、lockfile 更新和构建策略。SDK 不再通过 giget 或 pacote 下载、解压第二份副本。外部插件是 `node_modules` 下的依赖;本地插件脚手架仍属于独立的工程创建问题。
## Launcher 遥测
### Consent 与采集
遥测包住 `dsh-sdk` launcher 的命令生命周期,因为 create 和 build 不会稳定地启动 Cordis。每个事件记录命令名、时长、成败、随机生成的用户级匿名标识符以及符合条件时经过脱敏的 `cordis.yml``package.json` 文本。
除非当前存在的遥测配置项被明确禁用,否则允许上报。`DO_NOT_TRACK` 和 CI 无论工程配置如何都禁止上报。缺少 `cordis.yml` 本身不会禁止事件,但只有 `cordis.yml` 能证明目录是 SDK 工程时,遥测内容才包含 `package.json` 文本。
### 安全与传输
Payload 构建器绝不读取 `.env`。它会脱敏两个符合条件的文本文件中的疑似密钥键和值、已知 token 形式、PEM 块、URL 凭据和高熵不透明字符串。脱敏只是安全兜底不能提供绝对保证SDK 工程必须把凭据放进 `.env`
`TelemetryReporter` 使用固定 endpoint每条发送路径都会正常结束且不抛错。命令分发通过 `finally` 路径记录成败,在命令结果已确定后启动上报,并在有界时间内等待传输结束。只有遥测边界会吞掉上报条件解析、遥测内容构建、存储或网络错误,这些错误绝不改变命令退出码。
## 交互工作流测试
Create 和 config 测试向现有工作流注入 `PromptPort` 和脚本化输入输出流。参数化场景覆盖功能选择、功能选项、secret、取消、评审和应用行为再断言最终的 `cordis.yml` 及其他工程文件。稳定的产品断言是生成后的工程状态,不是 clack 的 ANSI 重绘序列。
可以用一到两个可选的真实 PTY 冒烟测试覆盖注入无法复现的发布二进制和 TTY 检查。除非原生 PTY 工具在仓库支持的 Node 与宿主版本上足够可靠,否则它不进入必跑路径。
## 延后工作
- 扩展 headless create 规格,使其能表达本地 `plugin``tool` 脚手架,而不是把该交互选择默认为 none。
- 在 create 和 config 中公开遥测关闭选项,同时保留只有禁用时才写入遥测配置项的上报许可表示。
- 明确 GitHub 来源依赖必须预先构建,还是允许运行由包管理器控制的 preparation script准备脚本并在安装前向用户展示该策略。
- 发布前把遥测包中的 `.invalid` endpoint 占位符替换为生产端点。
## 曾考虑的替代方案
**另建 headless 创建引擎。** 该方案会复制问题、功能依赖、配置行为和工程编辑规则。复用提示词与编辑会话边界,可以保证工程语义只有一份实现。
**把规格文件作为主要自动化接口。** Agent 可以内联传入相同的类型化 JSON 对象,人和 CI 仍可选用文件。文件专用协议会增加持久化与清理工作,却不增加语义。
**使用 `npx skills add` 创建工程。** Skills CLI 只安装 Markdown skill不创建 SDK 工程,也不安装 npm 包。因此agent skill 驱动 SDK 初始化命令,而不是取代它。
**通过 giget 或 pacote 获取 GitHub 与 npm 来源。** 第二套获取层会复制包管理器的解析、完整性、lockfile 和生命周期策略。原生 package specifier 让这些决策留在所选包管理器中。
**把遥测实现成 Cordis 运行时插件。** Create 和 build 不一定启动 Cordis因此运行时插件无法观察完整的开发命令周期。Launcher 是这些命令共用的边界。
**从 git 元数据派生匿名标识符。** 仓库的 git remote 可能识别工程或组织。随机的用户级标识符能够支持聚合,同时不编码仓库身份。
**只采集聚合计数。** 仅聚合事件可以降低暴露,但无法回答开发者实际使用哪些插件、依赖和配置形状。本提案接受采集脱敏后的工程文本,并明确记录这项暴露。
**把真实 PTY 和 transcript文本记录快照作为主要测试策略。** 原生 PTY 依赖与终端重绘序列会带来平台和渲染不稳定性,而且主要是在测试 clack。注入交互并断言生成文件可以直接测试 SDK 拥有的行为。
## 验收标准
- Create 能依据完整结构化输入在没有 TTY 时运行;使用 `--json` 时 stdout 只输出 NDJSON缺少必答输入时通过 `action-required` 报告,且不写入部分工程。
- Create 和 config 通过共享的问题、功能配置和工程编辑代码路径解析相同的功能计划契约。
- `dsh-sdk create <source>` 使用工程选定的包管理器,挂载该操作实际新增的依赖名;无法识别新增依赖时快速失败。
- 每个 `dsh-sdk` 命令都进入同一条尽力而为的遥测收尾路径;明确禁用的配置项、`DO_NOT_TRACK` 或 CI 会阻止传输,遥测失败绝不改变命令结果。
- 遥测绝不读取 `.env`;没有 `cordis.yml` 时不发送无关的 `package.json` 内容;两个符合条件的文本都经过脱敏;匿名标识符与 git 元数据无关。
- 交互测试通过注入交互覆盖 create 和 config 决策,并断言已提交的工程文件;真实 PTY 覆盖只作为窄范围冒烟层。
- Agent skill 说明公开的结构化输入与事件契约,不依赖包的私有导出。
## 风险
- 即使经过脱敏,完整的 `cordis.yml``package.json` 文本仍会向 endpoint 运营方暴露插件名、依赖名、URL、路径和配置值启发式脱敏也可能漏掉 secret。
- 没有遥测配置项时默认上报可能让开发者意外;发布前 CLI 必须让关闭方法易于发现。
-`ProjectEditSession` 挂载插件前,包管理器的 add 操作已经可能修改 `package.json`、lockfile 和安装文件;后续挂载失败会留下需要手工恢复的依赖改动。
- GitHub 依赖可能按包管理器策略执行 preparation 或 lifecycle script尚未解决的构建策略会带来供应链与可复现性风险。
- 注入提示词交互的测试无法证明真实终端中的 raw mode、signal 或重绘行为;可选冒烟层只应覆盖这些残余契约。
## 参考资料
- [Vercel Eve](https://github.com/vercel/eve) 与 [Vercel Labs Skills](https://github.com/vercel-labs/skills) 用于区分 headless 初始化命令与 skill 分发。
- [npm package specifications](https://docs.npmjs.com/cli/v11/using-npm/package-spec)、[pnpm add](https://pnpm.io/cli/add)和 [Yarn add](https://yarnpkg.com/cli/add)说明包管理器原生来源。
- [Console Do Not Track](https://consoledonottrack.com/)定义环境级关闭约定。
- [Clack](https://github.com/bombshell-dev/clack) 和 [Vitest snapshots](https://vitest.dev/guide/snapshot) 说明注入提示词交互与生成文件断言。

View File

@@ -1,196 +0,0 @@
# DeepSeek Harness SDK 后续工作设计
> 状态:设计成稿,供通读与评审。定案后由 ccyu 转正式 RFC 并双语化。本文件为临时设计文档,不走 doc-sync / 文档预算门禁。
>
> 一句话:**SDK 初版已合并;本轮把"创建项目""创建插件""遥测""交互测试"四块补齐,核心是抽出一个既撑交互又撑 headless 的创建内核,其余三块围绕它扩展。**
## 0. 总览(一屏读完)
抽一个既撑交互又撑 headless 的**创建内核**`Prompter` seam + 复用已有 `ProjectEditSession` + `CreationDriver` 发 NDJSON四块活围绕它扩展。
| 块 | 做什么 | 核心对象 | 结论要点 |
|---|---|---|---|
| **#1 headless + skill** | create/config 非交互化agent 端到端建项目 | `HeadlessPrompter` + `CreationDriver`(NDJSON) | 无 spec 文件、传结构化对象;薄 SKILL.md 入口beyond-eve |
| **#2 建插件** | `dsh-sdk create <github\|npm>` 加依赖并挂载 | PM 原生 `add` + `ProjectEditSession` cordis 挂载 | npm/pnpm 原生依赖(`github:#sha` / `pkg@version`),不用 giget/pacote |
| **#3 遥测** | 每个 `dsh-sdk` 命令上报 | `TelemetryReporter` / `ConsentResolver` / `SecretRedactor` | 发 cordis.yml+package.json 全文;不发 `.env`、疑似密钥脱敏;关闭 = cordis.yml 有明确 disabled 的遥测条目(甲)|
| **#4 交互测试** | 覆盖 wizard 各分支、快照 cordis.yml | `WizardHarness` + clack mock 注入 | 注入流为主、真 PTY 仅 12 个可选 smoke |
**节奏**:先做地基(`Prompter` 重构,同时解锁 #1+#4,不并行)→ 再 fan out 四棵 teammate worktree。
细节见下文;不想通读的话,读完本节 + §5砍掉的路线+ §6节奏即可。
## 1. 背景与现状
SDK 初版(`packages/sdk/*`)已经落地三个包:
| 包 | 职责 | 当前局限 |
|---|---|---|
| `@deepseek-ai/create-sdk` | `npm create @deepseek-ai/sdk` 交互式建项目 | **TTY-only**flag 只能预填问题创建仍须交互终端feature 配置面窄,复杂 feature 难在命令行表达 |
| `@deepseek-ai/dsh-helper` | 项目领域模型feature 催表、蓝图、`ProjectEditSession`(唯一的改写/提交边界) | create-sdk 与 `dsh-sdk config` 共用同一套催表与 configurator但目前只被交互式 wizard 驱动 |
| `@deepseek-ai/dsh-scripts` | `dsh-sdk` launcher`start` / `dev` / `build` / `config` | `config` 同样 **TTY-only**`build` 只跑 tsdown、`create` 时项目尚不存在——两者都不 boot cordis |
四块后续工作:
1. **非交互headless创建 + 通过 skill 创建**#1):去掉 create/config 的 TTY-only 限制,让 agent 能端到端把项目建出来。
2. **`dsh-sdk create <source>` 从 github/npm 建插件**#2):把外部插件拉进现有项目并接线。
3. **遥测**#3):每次 `dsh-sdk <command>`(含 create/首次初始化)上报开发者周期数据。
4. **终端交互测试**#4):给 clack wizard 加可回归、可核对的交互测试。
## 2. 边界澄清:三件事分开
调研 `vercel/eve` 及其引用的 `skills` CLI 后,确认三件事必须分开、不能混:
| 轨 | 命令入口 | 产物 |
|---|---|---|
| **建项目** | `npm create @deepseek-ai/sdk`(现有 wizard+ 其 headless 形态(本轮新增) | 一个新的 SDK 项目 |
| **建插件** | `dsh-sdk create <github\|npm>`(现有 create-plugin 扩展) | 现有项目里多一个接好线的插件 |
| **分发 skill** | 发 SKILL.md → agent `npx skills add deepseek-ai/<repo>` | 任意 agent 拉到我们的 skill playbook |
事实依据:`skills`vercel-labs/skills是 markdown SKILL.md 的包管理器,来源只认 github/git/本地、不认 npm scope只往 agent 的 skills 目录丢文件,不建项目、不 installeve 建项目走的是独立的 `npx eve init`。因此 `npx skills add @deepseek-ai/sdk`(把"装 SDK"和 `skills add` 揉在一起)不是真实用法,本设计据此拆开。
## 3. 总体架构
### 3.1 核心洞见:一个创建内核,四条轨围绕它
四块活看似独立,实则都咬合在同一组对象上。**建项目和改配置的本质是"问答 → 改项目文件"**,而 `dsh-helper` 已经有唯一的改写边界 `ProjectEditSession`。本轮把"问答"这一侧也抽成 seam就能让交互与 headless 共用一套逻辑,测试和 skill 顺势接上去。
```
┌───────────────────────────────┐
│ CreationDriver / SetupRunner │ 编排:问答序列 → 组装 spec → 驱动改写
│ (emits NDJSON) │
└───────────────┬───────────────┘
借助 │ 驱动
┌─────────────────────┐ │ ┌────────────────────────────┐
│ Prompter (seam) │◄──────────┘ │ ProjectEditSession (已有) │ 唯一改写/提交边界
├─────────────────────┤ └────────────────────────────┘
│ InteractivePrompter │ clack + 注入 input/output解锁 #4 测试)
│ HeadlessPrompter │ 永不阻塞:答案取自结构化 spec缺必答项→抛错/发 action-required解锁 #1 + skill
└─────────────────────┘
```
- **`Prompter`(新 seam**:把"向用户要一个答案"抽象出来。两个实现:
- `InteractivePrompter`clack 实现,接受注入的 `input`/`output` 流(不再硬绑 `process.stdin/stdout`)。这正是 #4 交互测试的前置。
- `HeadlessPrompter`:永不阻塞——答案取自调用方传入的结构化 spec遇到未提供的必答项直接**响亮失败**(抛错 / 发 `action-required` 事件),不猜默认。这是 #1 headless 与 skill 驱动的地基。
- **`ProjectEditSession`(复用已有)**:唯一的改写/提交边界。create、config、以及 #2 建插件改 cordis.yml全部经它落盘。
- **`CreationDriver`(新,或改造现有 wizard 编排)**:跑问答序列、组装项目 spec、驱动 `ProjectEditSession`headless 模式下向外发 **NDJSON 生命周期事件**`action-required` / `done` / `error` / 进度)。
- **skill 路径**agent import 这个内核、传结构化 config 对象、读 NDJSON 事件;附一层薄 SKILL.md 教 agent 怎么驱动。
**扩展点**:新增一个 feature 只改 `dsh-helper` 的催表/催配置器;交互与 headless 两条路都自动获得它,不需各改一遍。
> **读码修正(重要,落地以此为准)**:上文 `Prompter`/`InteractivePrompter`/`CreationDriver` 是概念名,对应现有代码:
> - **seam 已存在**`dsh-helper` 的 `PromptPort``questions/prompt-port.ts`)即 `Prompter`;问答走 `Question.resolve(port, prefilled?)`——给了 prefill 就不碰 port。
> - **交互实现 + 注入已存在**`ClackPromptPort` 构造函数已接受注入 `input`/`output`(源码注释即 "for snapshots and tests"`CreateWizard` 与 `ConfigWorkflow` 都已接受注入的 `PromptPort` + `output`。
> - **⇒ #4 交互测试不被地基阻塞**:注入点今天就有,已单独开 teammate 并行做(覆盖 create + config 两个 wizard
> - **地基真正要做的(比原设想小)**:新增 `HeadlessPromptPort implements PromptPort`(缺项 fail-fast + 发 NDJSON+ 补全 prefill 覆盖——目前 feature 选择走原始 `nestedMultiselect`、`FeatureConfigurator` 的 valueInputs 无 prefill、suggests 确认无 prefillheadless 要让结构化 spec 喂满这些点。
### 3.2 四块如何咬合到这组对象
| 块 | 落在哪个对象 | 关系 |
|---|---|---|
| #1 headless + skill | `HeadlessPromptPort`(实现已有 `PromptPort`+ prefill 补全 + NDJSON | 地基(缩小版)|
| #4 交互测试 | 注入 `ClackPromptPort(mockIn, mockOut)` 进已有 `CreateWizard`/`ConfigWorkflow` + `WizardHarness` | **注入点已存在,不阻塞,已并行开工** |
| #2 建插件 | `PluginSource` + `PluginFetcher` + 经 `ProjectEditSession` 接线 | 复用改写边界 |
| #3 遥测 | launcher 侧 `TelemetryReporter` + `ConsentResolver` + `SecretRedactor` | 独立于内核,挂在 launcher 命令生命周期 |
## 4. 详细设计
### 4.1 headless 创建 + skill#1
**目标**`create-sdk`(建新项目)与 `dsh-sdk config`改现有项目都能非交互运行agent 能端到端把项目建完。
**设计**
- **Prompter seam**:如 §3。交互走 `InteractivePrompter`headless 走 `HeadlessPrompter`fail-fast + NDJSON二者背后是同一套 `dsh-helper` 催表和同一个 `ProjectEditSession`
- **输入编码**(次要、可解耦):
- **agent 路径**:传结构化 config 对象(程序化,或 `--config-json '{...}'`+ 读 NDJSON。**不需要 spec 文件**——与 eve 一致;我们 feature 比 eve 重(嵌套有限选项 + 密钥),结构化对象比一长串扁平 flag 干净。
- **人 / CI 路径**(可选):`--config <file>`yaml/json或 flags只是喂给同一内核的另一种编码。
- **skill**:核心是 headless 内核agent 传参直接建完,缺必答项就响亮失败让 agent 补答。附一层**薄 SKILL.md**(指向内核、教 agent 驱动),让"通过 skill 创建"字面落地。
- **比 eve 更进一步**eve 把 headless 原语(`runHeadless` + 非阻塞 Prompter + NDJSON造好了却没接到它的 skill——它的 SKILL.md 只指向半交互 CLI且 agent 跑 `eve init` 时只打印指引、打回给人。我们把 **skill → headless 内核接通**,才真正做到"headless 为 skill 服务"。
### 4.2 `dsh-sdk create <source>` 建插件(#2简单版
**目标**:把一个 github repo 或 npm 包当**依赖**加进现有项目并挂载;用包管理器原生能力,**不引 giget/pacote**。
**设计PM 原生依赖 + cordis 挂载)**
- **来源**npm`pkg@version`)或 github`github:owner/repo#ref`,推荐锁 commit SHA。npm/pnpm/yarn 原生支持这两种依赖来源,自己解析包名、把 commit/integrity 钉进 lockfile。
- **流程**`dsh-sdk create <source>` → 确认TTY guard 同 config→ 用项目的包管理器 `add <source>`PM 解析名字 / 装依赖 / 写 lockfile→ 读回新增依赖名 → 经 `ProjectEditSession` 挂一条 cordis 条目引用它 → commit。
- **不落 `plugins/`**:外部插件是 node_modules 依赖,不是本地生成插件(后者才走 `LocalPluginBlueprint` + 文件生成)。
- **构建张力(暂缓)**:源码型 github 插件装时要 `prepare` buildpnpm 10.26 默认禁、需 `allowBuilds` 放行)——"预编译-only vs 允许构建"的取舍留到以后;先做能跑的简单版,交给 PM 默认行为。
- **弃用**:早期设计的 `PluginSource`/`GigetFetcher`/`PacoteFetcher`(抓 tarball 到 temp 再接线)已随 `dsh-plugin-fetch` 包一起撤掉——PM 原生依赖覆盖了它。
### 4.3 遥测(#3
**目标**:每次 `dsh-sdk <command>`create / dev / build / config / start / 首次初始化)上报当前 `cordis.yml` + `package.json` 内容。
**设计**
- **上报器位置**:在**我们自己的代码执行时机**里——`create-sdk` 进程 + `dsh-scripts` launcher 进程,包住命令生命周期。
- 理由:`build` 只跑 tsdown、`create` 时项目还不存在,都不 boot cordis写在 cordis.yml 里的 cordis 插件抓不到它们。调研的全部工具Next/Astro/Nuxt/Vite/Angular/Gatsby/Turbo/Homebrew无一例外把上报器放 CLI/launcher从不放 app 运行时。
- **`TelemetryReporter`launcher 侧)**:包住命令,收集 `{command, 时长, 成败, cordis.yml 内容, package.json 内容}`
- **`ConsentResolver`**:在每个命令**解析(非 boot`cordis.yml`**,读遥测插件状态当 consent。**关闭ccyu 拍板)= cordis.yml 里有一条明确 `disabled` 的遥测条目**;其余一切(无 cordis.yml / 有文件但无遥测条目 / 有 enabled 条目)都上报——唯一的关只有"存在且 disabled",无不对称。(可选、近零成本补充,留到实现定:额外认 `DO_NOT_TRACK` / CI 自动关。)
- **`SecretRedactor`(安全硬线)**:绝不发 `.env`cordis.yml / package.json 里若出现类似密钥的值,**脱敏替换**redact 占位,不整段丢)。依赖 SDK 约定——密钥只进 `.env`、cordis.yml 只引用 env 不内联——脱敏是兜底。
- **匿名 id**:全局配置里的随机 UUID**绝不从 git remote / repo URL 派生**。
- **endpoint**:内置在代码里。
- **consent 承载**:遥测作为 `create` 时默认打开的 feature 写进 cordis.yml对用户可见、随项目
> **接线现状(读码修正)**launcher 上报已接通——`runDshSdkCommand` 计时包住每条命令,`finally` 里 resolve consent→ 建 redacted payloadcordis.yml+package.json 全文、不读 .env→ fire-and-forget 上报 + flushbest-effort 永不影响命令结果。**默认开**:无遥测条目 → 甲 → 上报。**opt-out 现状**:在 cordis.yml 手动加一条 `disabled` 的 `@deepseek-ai/dsh-telemetry` 条目即关(`ConsentResolver` 读到 disabled → 不报disabled 条目 cordis 不加载,故不会因"它不是运行时插件"而 boot 失败)。
>
> **暂缓(催表 opt-out 开关)**:把"关遥测"做成 config/create 向导里的勾选项还没做。关键约束:`@deepseek-ai/dsh-telemetry` 是 **launcher 库、不是 cordis 运行时插件**,所以 consent 条目只能以 **disabled 形态**存在enabled=无条目=甲默认报;要关才写 disabled 条目),不能像普通 feature 那样挂一个 enabled 的可 boot 条目。向导化这个"只在关闭时才出现条目"的特殊语义留作后续。
**在案取舍**:发全文会把第三方(含私有 scoped包名、cordis 配置值base-url/路径)暴露给 endpoint 持有方主流工具都不发这些Turbo 排除包名、Angular 禁模块名。ccyu 作为本 SDK 维护者接受此暴露——目的即掌握开发者用了哪些 plugin/依赖/配置。
### 4.4 交互测试(#4
**目标**CI 仅 mac/linux覆盖 create wizard 主流程 + config wizard 各选择分支;产出不同选择下的 `cordis.yml` 快照便于核对。
**设计clack mock 注入,不上真 PTY 打头阵)**
- **主力**:进程内注入 mock stdin/stdout。`@clack/prompts` 官方支持 `input`/`output` 注入,`isTTY=false` 时自动跳过 raw mode——零原生依赖、mac/linux 天然确定。**前置已就绪**`ClackPromptPort` 已接受注入流,`CreateWizard`/`ConfigWorkflow` 已接受注入 `PromptPort`
- **`WizardHarness`(测试工具)**:用脚本化 keypress 序列(`input.emit('keypress', …)` 走 down/space/return驱动 wizard、写到 temp 目录、返回生成的 `cordis.yml`
- **断言**`test.each(选择组合)``toMatchFileSnapshot('<combo>.cordis.yml')`**快照生成的 cordis.yml**,不快照交互 transcripttranscript 脆、且是在测 clack 自己)。
- **可选 12 个真 PTY smoke**:仅覆盖"真二进制 + interactive-vs-CI TTY gate"这条注入测不到的分支node-pty 在我们 Node`^22.19 || >=24`)上有原生编译风险,**挪出关键路径**,缺工具链 self-skip。
## 5. 砍掉的路线(调研依据)
| 砍掉的路线 | 理由 |
|---|---|
| #4 用真 PTYnode-pty打头阵 | clack 官方支持注入流,无需真 TTYnode-pty 在我们 Node 版本上有原生编译风险。真 PTY 降级为 12 个可选 smoke |
| #4 快照交互 transcript | transcript 受重绘/spinner/ANSI 影响脆弱,且主要在测 clack 渲染而非我们的生成逻辑 |
| #3 上报器做成 cordis 插件 | `build`/`create` 不 boot cordis插件抓不到无一主流工具用 app 内插件做遥测 |
| #3 匿名 id 从 git remote 派生 | 会让"匿名"变假Next 因此挨批) |
| #1 headless 以 spec 文件为主 | eve 没有 spec 文件agent 路径传结构化对象更干净spec 文件退成人/CI 可选 |
| #2 从 npm/github 拉完自动 install+build | 会执行被拉代码的 postinstall供应链风险改为只解压 + 显式接线 + `--ignore-scripts` |
| #1`npx skills add` 创建项目 | `skills` 是 markdown SKILL.md 包管理器、不建项目、不认 npm scope属概念混淆 |
## 6. 推进节奏:地基缩小,已并发开工
**读码后修正**`PromptPort` seam + 可注入的 `ClackPromptPort` + 可注入的 `CreateWizard`/`ConfigWorkflow` 都已存在,所以地基比原设想小,且 #4 不再被它阻塞。当前并发结构:
1. **地基(主线程,我做)**:新增 `HeadlessPromptPort implements PromptPort`(缺项 fail-fast + 发 NDJSON+ 把 prefill 覆盖补全feature 选择、valueInputs、suggests 确认),让结构化 spec 能喂满 create/config顺带把 launcher 命令注册、helper 催表扩展点留成清晰 seam。这是 #1 headless 与 #2/#3 接线步的公共前置。
2. **已并行开工的 teammate worktree**(与地基低冲突,只碰各自新模块):
- **树 B 建插件**`PluginSource` + `GigetFetcher`/`PacoteFetcher`greenfield`dsh-sdk create` 命令注册 + cordis.yml 接线等地基后再做)。
- **树 A 遥测**`SecretRedactor` / `ConsentResolver` / `TelemetryPayload` / `TelemetryReporter`greenfieldlauncher 接线 + 催表加 feature 等地基后再做)。
- **树 C 交互测试**`WizardHarness` + create/config 的 `test.each` cordis.yml 快照(注入点已就绪,可全量做)。
3. **地基落地后的收尾**(接线,碰共享文件):#2`dsh-sdk create` 命令注册 + cordis.yml 接线;#3 的 launcher 上报接线 + 催表遥测 feature#1 的 skill 薄封装 + `--config-json`/`--config` 入口。
4. **合并**stacked PR地基在底其余 rebase 到地基上,逐层向上同步。
- **树 D 薄 SKILL.md**SKILL.md + 相关文档(并入第 3 步收尾)。
> 冲突面:树 A/B 的接线步都会碰 `dsh-scripts` 命令注册与 `dsh-helper` 催表——地基里须把命令注册做成可加式各命令自注册、催表扩展点清晰接线才能真正独立、rebase 顺滑。greenfield 模块阶段(当前)不碰这些共享文件,所以能安全并行。
## 7. 各轨改动清单
| 轨 | 主要包 | 关键改动 |
|---|---|---|
| 地基 | dsh-helper, create-sdk, dsh-scripts | `HeadlessPromptPort`(实现已有 `PromptPort`+ prefill 补全 + NDJSON + 命令注册/催表扩展点整理 |
| #1 headless+skill | create-sdk, dsh-scripts, skill 包 | 结构化 spec 入口 `--config-json`/`--config`、薄 SKILL.md |
| #2 建插件 | dsh-scripts, dsh-helper | `dsh-sdk create <source>` 命令、PM `add`、经 `ProjectEditSession` 挂 cordis 条目、无新依赖 |
| #3 遥测 | telemetry 包, dsh-scripts, dsh-helper | `TelemetryReporter`/`ConsentResolver`/`SecretRedactor`、launcher 接线、催表遥测 feature、内置 endpoint、全局 UUID |
| #4 测试 | packages/support, 已可注入create-sdk/dsh-scripts | `WizardHarness`、create/config 的 `test.each` cordis.yml 快照、可选 PTY smoke |
## 8. 调研来源
- eve / skillshttps://github.com/vercel/eve · https://github.com/vercel-labs/skills
- 从源拉取https://github.com/unjs/giget · https://github.com/npm/pacote · https://github.com/Rich-Harris/degit
- 遥测规范https://nextjs.org/telemetry · https://astro.build/telemetry/ · https://github.com/nuxt/telemetry · https://angular.dev/cli/analytics · https://turborepo.dev/docs/telemetry · https://consoledonottrack.com
- PTY / clack 测试https://github.com/bombshell-dev/clack · https://vitest.dev/guide/snapshot · https://github.com/microsoft/node-pty