前言

本书定义 Rootward 跨语言项目型 CLI 模板体系的公共契约。该体系适用于需要在文件系统项目中运行、读取项目内配置、发现源文件集合、执行业务扫描并暴露稳定命令输出的开发者工具,也规定 Rootward 如何维护、验证、分发和实例化这些语言模板。

Rootward 的对象不是命令行参数解析样板,也不是业务扫描器。Rootward 固定两层契约:生成物契约规定被创建出来的项目型 CLI 如何建立项目语境、读取配置、发现文件、分发 scanner、输出结果和报告错误;创建器契约规定 Rootward Creator CLI 如何通过非交互命令把语言模板源实例化为具体项目。

本书面向三类读者。理解型读者可以通过第一部和第二部理解 Rootward 的对象边界和生成物项目语境。实现型读者可以通过第三部、第四部、第五部和第六部实现 Creator CLI 与语言模板。维护型读者可以通过第七部和附录判断新增设计点是否有资格进入 Rootward 契约。

本书使用中文叙述 Rootward 的对象、规则和实现责任。没有稳定中文译名的工程名、依赖名、命令名、字段名和类型名保留英文;已经进入 Rootward 公共契约的英文术语在首次出现时给出中文解释或语义边界。术语索引见 术语表

README 负责提供入口说明。本书负责提供契约说明。入口说明可以压缩,契约说明必须保留对象定义、公共规则、边界条件、实现责任和验证方式。

第一部:Rootward 的对象

第一部定义 Rootward 的问题世界、人工制品边界、公共契约、投影和完成标准。

1. 项目型 CLI 的问题世界

项目型 CLI 是在文件系统项目中运行的命令行人工制品。它的输入来自命令行参数、当前工作目录、显式项目根、项目内配置文件和项目目录树;它的输出面向终端用户、脚本、CI、编辑器集成和业务工具开发者。

普通 CLI starter 解决命令入口、参数解析和帮助文本。项目型 CLI 还必须解决项目语境。工具必须知道当前命令属于哪个项目,配置从哪里读取,哪些文件进入工具视野,文件发现是否尊重 .gitignore,输出如何被人类和程序稳定理解,错误如何被自动化调用方识别。

项目型 CLI 的问题世界包含以下事实。

  • 用户会在项目根目录运行命令,也会在项目子目录运行命令。

  • 用户需要显式指定项目根,以便从其他目录操作目标项目。

  • 工具行为需要随项目复制、审查和进入 CI。

  • 项目目录包含源码、文档、依赖、构建产物、缓存、虚拟环境和工具状态。

  • .gitignore 已经表达项目不希望普通工具处理的路径。

  • 业务处理器会变化,项目定位、配置读取、文件发现和输出语义会反复出现。

  • 终端用户需要可读输出,脚本和 CI 需要稳定 JSON,编辑器集成需要稳定错误码。

Rootward 介入的对象不是业务处理本身,而是这些项目型 CLI 共同需要的项目语境协议。业务工具可以扫描 Markdown、Python、Rust、Go、配置文件或任意领域文件;Rootward 负责让这些文件集合以稳定方式进入工具视野。

项目型 CLI 的核心摩擦是语境不稳定。当前工作目录不能直接等同项目根;命令行参数不能直接等同配置;文件系统遍历不能直接等同业务输入;人类错误消息不能直接等同机器可读错误;实现物能运行不能直接等同人工制品完成。

Rootward 的公共契约把这些摩擦分成可实现、可观察、可测试的对象。项目根发现提供路径基准;项目内 TOML 配置提供协作基准;source 声明提供文件集合基准;discover 提供文件发现投影;scanner registry 提供业务处理接入点;JSON 输出和错误码提供机器契约。

这些对象形成一条稳定行动轨迹。用户先通过 init 建立项目语境,再通过项目根发现进入同一配置基准,通过 source 声明把文件集合登记为公共输入,通过 discover 检查工具视野,通过 scan 把 discovered source 交给 scanner,通过 JSON、human output 和 error code 消费结果。该轨迹中的每个节点都对应后续章节中的契约对象。

本章定义的问题世界是 Rootward 契约的事实来源。契约中的每个设计点都必须能回到这些事实,或者被明确归入语言实现、业务扩展、相邻对象或未来期权。

2. Rootward 的人工制品边界

Rootward 是一套跨语言项目型 CLI 模板体系。该体系规定项目型 CLI 工具如何建立项目语境、发现项目根、读取项目内配置、声明源文件集合、投影发现结果、分发 scanner、输出稳定 JSON,并规定 Rootward Creator CLI 如何把语言模板源实例化为具体项目。

Rootward 的人工制品不是 TypeScript、Python、Rust 或 Go 中的任意一种实现。语言模板是实现物,Rootward 契约是这些实现物共同承载的对象。Rootward Creator CLI 是模板实例化工具,不是生成后的业务 CLI。一个语言模板只有在保持 Rootward 公共契约并能被 Creator 按 manifest 实例化时,才是 Rootward 语言模板。

Rootward 体系由四个一等对象组成。

  • Rootward Contract:定义生成物必须满足的项目型 CLI 公共契约。

  • Rootward Repository:维护书、创建器、模板源、测试和发布配置的仓库。

  • Rootward Creator CLI:通过 npm 分发的非交互模板实例化命令。

  • Rootward Language Template:TypeScript、Python、Rust、Go 等语言的模板源。

这些对象不能互相冒充。Contract 不是 README;Repository 不是生成物;Creator CLI 不是生成后的业务 CLI;Language Template 不是 npm 创建器本身。Creator 的错误码、输出和测试属于创建器契约;生成物的错误码、输出和测试属于生成物契约。

Rootward 生成物的构成性条件如下。

  • 工具有稳定 CLI name。

  • 工具有稳定 config directory name。

  • 工具有稳定 TOML 配置文件位置。

  • 工具有明确初始化命令。

  • 工具有显式项目根和 CWD 向上发现两种项目定位方式。

  • 工具有运行时配置 schema 和语义校验。

  • 工具有 source 声明模型。

  • 工具有 gitignore-aware 文件发现。

  • 工具有 scanner registry 接入点。

  • 工具有 statusdiscoverscanconfig printdoctor 命令。

  • 工具有稳定错误码和退出码。

  • 工具有稳定 JSON 输出。

  • 核心逻辑可以脱离真实进程入口测试。

这些条件共同定义 Rootward 项目型 CLI 模板。缺少项目根发现,模板无法建立项目语境。缺少项目内配置,工具行为不能随项目复制和审查。缺少 source 声明,文件集合不能成为公共契约。缺少 discover 投影,工具视野不能被验证。缺少稳定 JSON 和错误码,自动化调用方不能可靠消费结果。缺少可测试核心,契约行为不能被持续证明。

Rootward 创建器的构成性条件如下。

  • Creator 通过 npm create package 分发。

  • Creator 提供非交互、可管道的命令表面。

  • Creator 读取语言模板 manifest。

  • Creator 只实例化 implemented 状态的模板。

  • Creator 使用 manifest 显式声明的 identity token 替换生成物身份。

  • Creator 默认拒绝写入非空目标目录。

  • Creator 提供稳定 JSON 输出、错误码和退出码。

  • Creator 通过实例化测试证明模板源可以生成可运行项目。

构成性条件不是功能清单。功能清单可以随业务工具变化,构成性条件决定一个语言模板是否仍然是 Rootward 模板。业务工具可以替换 scanner、默认 source 和业务命令;替换以后仍必须保留项目语境、配置、发现、投影、错误和测试这些 Rootward 条件。

Rootward 不承担以下对象。

  • Rootward 不是通用 CLI framework。

  • Rootward 不是上层软件项目 scaffold。

  • Rootward 不是业务 parser。

  • Rootward 不是插件市场。

  • Rootward 不是全局配置系统。

  • Rootward 不是远程配置服务。

  • Rootward 不是图形界面或语言服务器。

  • Rootward 不是 GitHub template repo 的替代入口。

  • Rootward 不是语言包管理器。

  • Rootward 不是项目迁移器。

  • Rootward Creator 不是生成物运行时依赖。

这些对象可以由具体业务工具实现,也可以作为未来期权研究,但它们不进入 Rootward 核心契约。Rootward 的边界使语言模板保持同构,使业务工具能够在清楚的项目语境上继续构造自己的能力。

Rootward 的对象边界在本书中逐层展开。项目语境契约见 工具身份与项目目录配置模型;发现与扫描契约见 Source 声明模型Doctor 诊断;命令、输出与错误契约见 命令集合错误码与退出码;Creator 与模板分发契约见 Rootward Creator CLI 契约Creator 错误码与退出码;跨语言实现约束见 跨语言同构原则;边界演进规则见 设计点准入规则相邻对象与未来期权

3. 公共契约、投影与完成标准

公共契约(public contract)是 Rootward 模板对消费者提供的可依赖承诺。消费者包括终端用户、脚本、CI、编辑器集成、业务工具开发者和模板维护者。消费者能够依赖的命令、配置字段、输出形状、错误码、退出码和路径语义,都属于公共契约。

投影(projection)是内部状态、事件或结果面向消费者的可见表达。Rootward 的投影包括人类输出、JSON 输出、discover 文件列表、doctor 诊断项、status 项目摘要、config print 规范化配置和错误对象。

公共契约和投影不是内部实现的副产品。它们是 Rootward 人工制品成立的组成部分。项目根发现产生的 ProjectContext 只有通过 status、JSON 输出、错误 details 或下游命令行为被观察时,才成为可检查对象。文件发现结果只有通过 discover 或 scanner 输入被投影时,才能被用户和测试确认。

投影单位必须服务消费者动作。字段、诊断项、输出列、错误 details 和路径字符串进入公共投影以后,消费者会把它们理解为可依赖承诺。公共投影的准入规则见 设计点准入规则;输出形状见 人类输出与 JSON 输出

Rootward 的完成声明必须回到公共契约。一个语言模板能够启动进程、显示帮助文本或执行单个命令,不足以证明模板完成。模板完成必须满足以下条件。

实现物能运行只证明实现物存在。Rootward 模板完成需要证明同一公共契约在该语言中成立。TypeScript、Python、Rust 和 Go 可以采用不同依赖、路径类型、错误类型和测试工具,但它们必须暴露同一命令语义、同一配置语义、同一 JSON envelope 和同一错误语义。

公共投影中的每个单位都需要消费者动作授权。discover --list 的文件路径服务用户确认工具视野;doctor 的诊断级别服务用户判断项目语境是否阻断其他命令;JSON error.code 服务脚本分支;status 的 discovery mode 服务用户确认项目定位来源。不能改变消费者判断、选择、操作或验证的输出单位,不应进入 Rootward 公共投影。

第二部:项目语境契约

第二部定义工具身份、初始化结构、项目根发现和项目内配置模型。

4. 工具身份与项目目录

Rootward 工具身份由 package name、CLI name 和 config directory name 三个对象组成。三者分别属于不同语境,不能自动互相推导。

package name 属于语言包管理和发布语境。TypeScript 模板中的 package name 属于 npm;Python 模板中的 project name 属于 Python packaging;Rust 模板中的 crate name 属于 Cargo;Go 模板中的 module path 属于 Go modules。

CLI name 属于用户命令输入。用户运行 foo initfoo statusfoo discoverfoo scan 时,foo 是命令表面的稳定入口。

config directory name 属于项目文件系统语境。CLI name 为 foo 时,隐藏配置目录固定为 .foo。该目录承载工具项目配置、缓存和本地状态。

三者必须在每个语言模板中以集中常量表达。常量形状如下。

export const packageName = "@scope/foo";
export const cliName = "foo";
export const configDirName = ".foo";
export const configFileName = "config.toml";

隐藏目录不从 package name 自动推导。scoped package、Go module path 和 Python distribution name 可以包含不适合作为项目目录名的字符或路径结构。CLI name 和 config directory name 是用户可见公共契约,必须由模板维护者显式确定。

所有帮助文本、错误消息、初始化路径、项目发现路径和配置路径都从这些身份常量派生。Creator 必须在实例化时替换身份常量、包元数据、README 中的命令名、测试断言和示例路径。身份替换责任见 模板实例化责任

工具身份的稳定性直接影响项目语境。.foo/config.toml 一旦进入项目,脚本、CI、文档和用户习惯都会依赖该路径。工具不应在没有迁移规则的情况下改变 CLI name 或 config directory name。

Rootward 不规定业务工具的品牌命名方式。Rootward 只规定命令身份和项目目录身份必须显式、稳定、可测试。

语言实现不得把身份常量散落在命令、输出、测试和 README 生成逻辑中。散落身份会使 Creator 漏掉替换点,并造成帮助文本、配置路径和测试断言之间的语义分裂。

5. 初始化契约

init 建立工具项目语境。它不创建上层软件项目,不初始化语言工程,不生成业务源码。目标目录必须已经存在,并且必须是目录。

命令表面固定为:

foo init [path] [--force]

未传 path 时,目标目录是当前工作目录。传入 path 时,目标目录是该路径解析后的绝对目录。init 在目标目录下创建工具隐藏目录。

初始化结构固定为:

.foo/
  config.toml
  cache/
    .gitignore
  state/
    .gitignore

.foo/config.toml 是项目配置文件。该文件属于 Rootward 公共契约,可以进入版本控制。它定义 discovery 行为和 source 声明;项目命令通过它解释项目文件集合。

.foo/cache/ 存放可重建缓存。该目录中的内容不进入版本控制。.foo/cache/.gitignore 固定为:

*
!.gitignore

.foo/state/ 存放本地运行状态。该目录中的内容不进入版本控制。.foo/state/.gitignore 使用同一内容。

init 不修改项目根 .gitignore。运行态文件的版本控制边界由隐藏目录内部的局部 .gitignore 表达。该规则避免初始化命令扩大项目根文件的副作用。

如果 .foo/config.toml 已存在,init 返回 PROJECT_ALREADY_INITIALIZED。传入 --force 时,init 覆盖 .foo/config.toml,并保证 .foo/cache/.gitignore.foo/state/.gitignore 存在。

默认配置是初始化契约的一部分。默认配置内容见 默认配置规范。默认配置用于建立可运行项目语境,不代表业务工具支持文件类型的上限。

init 成功的 JSON data 固定为:

type InitData = {
  projectRoot: string;
  configDir: string;
  configPath: string;
  created: Array<"config" | "cache" | "state">;
  overwritten: boolean;
};

projectRoot 是初始化目标目录的绝对路径。configDir 是创建或确认存在的工具隐藏目录。configPath 是创建或覆盖的 TOML 配置文件路径。created 只记录本次初始化新建的公共结构单位;overwritten 表示本次执行是否覆盖已有 config。人类模式下,该信息进入终端摘要。

init 的错误语义必须保持项目语境边界清楚。目标目录不存在或目标路径不是目录时返回 INIT_TARGET_INVALID。配置已存在且未传 --force 时返回 PROJECT_ALREADY_INITIALIZED。文件系统写入失败属于 INTERNAL_ERROR 或具体语言实现归档后的稳定错误映射。语言模板不得在初始化失败时留下半个公共契约,例如只创建 cache/state 而缺少 config 的成功返回。

6. 项目根发现

项目根发现(project root discovery)确定 Rootward 命令解释项目路径、配置文件和 source root 的基准。Rootward 只定义两种项目定位方式:显式项目根和 CWD 向上发现。

显式项目根由 --project <path> 提供。该路径解析后的绝对目录就是 project root。工具只检查 <projectRoot>/.foo/config.toml。配置存在时加载该配置;配置不存在时返回 PROJECT_CONFIG_NOT_FOUND。显式项目根不触发向上搜索。

CWD 向上发现用于没有 --project 的命令。工具从当前工作目录开始,逐级检查 .foo/config.toml。找到第一个配置文件时停止。包含 .foo 的目录就是 project root。搜索到文件系统根仍未找到配置时返回 PROJECT_NOT_FOUND

该规则把用户意图区分成两个稳定层位。--project 表示用户已经指定项目根;CWD 向上发现表示用户让工具按当前位置推导项目根。显式指定和自动推导不混用。

项目定位结果结构固定为:

type ProjectContext = {
  projectRoot: string;
  configDir: string;
  configPath: string;
  discoveryMode: "explicit" | "cwd-upward";
  startDirectory: string;
};

projectRoot 是项目路径基准。配置中的 source.root 全部相对该目录解析。

configDir 是工具隐藏目录。CLI name 为 foo 时,该目录为 <projectRoot>/.foo

configPath 是 TOML 配置路径。CLI name 为 foo 时,该路径为 <projectRoot>/.foo/config.toml

discoveryMode 记录项目定位来源。该字段服务 status 投影和 JSON 调试,不改变下游命令行为。

startDirectory 记录 CWD 向上发现的起点。显式项目根模式下,它等于显式项目根的绝对形式。

所有需要项目语境的命令必须通过同一个项目定位模块。命令层不直接拼接 .foo/config.toml。该规则防止不同命令拥有不同项目解释。

项目根发现是路径规则的前置条件。source root、include/exclude、public path、scanner 输入和错误 details 都必须在同一 ProjectContext 下解释。

项目根发现不读取业务文件,也不解释 source 配置。它只判定项目语境是否存在,并产生后续模块共享的路径基准。配置读取见 配置模型,文件发现见 文件发现语义

7. 配置模型

.foo/config.toml 是 Rootward 工具项目配置的唯一默认来源。配置文件使用 TOML。路径字段按 project root 解释,不按 .foo 目录解释。

配置顶层包含 [discovery] 表和 sources 表数组。

[discovery]
respect_gitignore = true
follow_symlinks = false
include_hidden = false

[[sources]]
id = "docs"
root = "docs"
include = ["**/*.{md,mdx,rst,adoc,txt}"]
exclude = []
scanner = "text"

[discovery] 固定文件发现全局行为。

respect_gitignore 控制文件发现是否尊重项目 .gitignore。默认值是 true。被版本控制明确排除的路径通常承载依赖、构建产物、缓存、虚拟环境和工具状态;默认扫描这些路径会造成性能问题和语义污染。

follow_symlinks 控制文件发现是否跟随符号链接。默认值是 false。符号链接可以跨出项目边界,也可以形成循环。项目型 CLI 默认不跨越这种边界。

include_hidden 控制是否扫描隐藏文件和隐藏目录。默认值是 false。隐藏路径通常承载 VCS、编辑器、工具配置和缓存等项目管理对象。业务文件位于隐藏路径时,用户通过配置或命令行覆盖显式开启。

sources 表数组声明扫描源。source 字段语义在 Source 声明模型 中定义。

缺失 [discovery] 表时,语言实现必须填充内置 discovery defaults。缺失 exclude 字段时,语言实现必须填充空数组。sources 表数组不能为空;没有 source 的配置不能产生可审查的文件集合。

Rootward 的规范化配置形状如下。

type NormalizedConfig = {
  discovery: {
    respectGitignore: boolean;
    followSymlinks: boolean;
    includeHidden: boolean;
  };
  sources: Array<{
    id: string;
    root: string;
    include: string[];
    exclude: string[];
    scanner: string;
  }>;
};

配置 schema 必须在运行时校验。TypeScript 类型、Python type hints、Rust struct 和 Go struct 都不能替代运行时输入校验。TOML 来自磁盘,必须经过解析、结构校验和语义校验,才能进入 NormalizedConfig

source id 必须匹配 CLI 标识符规则:以字母开头,只包含字母、数字、短横线和下划线。该规则使 --source docs、JSON key、日志消息和测试断言保持稳定。

source id 必须在同一配置文件中唯一。重复 id 返回 CONFIG_INVALID

source root 不能是绝对路径。项目配置描述项目内部文件集合,绝对路径会把项目行为绑定到某台机器。

source root 不能包含 .. 越界片段。source root 必须留在 project root 内部。

source root、include pattern 和 exclude pattern 使用 / 作为路径分隔符。配置文件不接受反斜杠作为路径分隔符。实现必须在配置校验阶段拒绝包含反斜杠的 source root、include pattern 或 exclude pattern,而不是在文件发现阶段猜测用户意图。

命令行覆盖可以修改本次执行中的 discovery 行为。覆盖优先级见 命令行覆盖。命令行覆盖不修改 .foo/config.toml

配置解析失败和配置校验失败必须分离。TOML 语法错误返回 CONFIG_PARSE_FAILED;字段类型、缺失必需字段、重复 source id、绝对 source root、越界 source root、反斜杠路径分隔符和非法 source id 返回 CONFIG_INVALID。错误码完整表见 错误码与退出码

第三部:发现、扫描与诊断

第三部定义 source 声明、文件发现、scanner registry、discover 投影和 doctor 诊断。

8. Source 声明模型

source 是进入工具视野的一组文件及其 scanner 分发关系。source 不是目录别名,不是文件扩展名集合,也不是业务动作。

source 字段固定为:

[[sources]]
id = "docs"
root = "docs"
include = ["**/*.{md,mdx,rst,adoc,txt}"]
exclude = []
scanner = "text"

id 是 source 在配置内的稳定标识。命令行 --source <id>、错误 details、JSON 输出、discover 分组、doctor 诊断和测试断言都使用该标识。

root 是 source 根目录。它相对 project root 解析。root = "docs" 表示 <projectRoot>/docs

include 是 glob 数组。它相对 source root 解释,声明进入该 source 的候选文件集合。

exclude 是 glob 数组。它相对 source root 解释,在 include 结果上执行排除。

scanner 是 scanner registry key。它声明该 source 的文件交给哪个 scanner 处理。

scanner key 不等于文件扩展名。一个 scanner 可以处理多个扩展名;一个扩展名也可以在不同业务工具中交给不同 scanner。scanner key 也不等于业务命令。它只负责连接“文件集合”和“处理器”。

source 是文件发现和业务扫描之间的边界对象。文件发现模块负责把 source 配置展开为 DiscoveredSource;scanner 只消费已经发现的文件集合。scanner 不重新读取配置,不重新解释 glob,不重新决定 project root。

Rootward 默认配置中的 docspython source 是模板初始语境,不是 Rootward 支持文件类型的上限。业务工具可以替换默认 source,但不能改变 source 字段的公共语义。

source 进入公共投影以后具有维护成本。新增 source 字段、新增 source 选择规则或新增 scanner 分发语义,都必须经过 设计点准入规则 中的设计点准入判断。

source 的公共身份由 id 承担,不由 rootscanner 承担。同一个 root 可以拆成多个 source,不同 source 也可以使用同一个 scanner。消费者通过 source id 选择、诊断和断言文件集合;因此 source id 的唯一性和稳定性属于配置契约。

9. 文件发现语义

文件发现(file discovery)把 ProjectContextNormalizedConfig 转换为按 source 分组的 discovered files。它是 Rootward 项目型 CLI 的核心行为。

发现输入固定为:

  • ProjectContext

  • NormalizedConfig

  • 可选的 source id

  • 本次执行的 discovery overrides

发现输出固定为:

type DiscoveredFile = {
  path: string;
  sourceId: string;
  scanner: string;
};

type DiscoveredSource = {
  id: string;
  root: string;
  scanner: string;
  include: string[];
  exclude: string[];
  files: DiscoveredFile[];
};

DiscoveredFile.path 是 project-root-relative POSIX path。公共 JSON、测试断言和 scanner 输入都使用该路径作为稳定标识。绝对路径不进入默认公共投影。

每个 source 的发现步骤固定如下。

  1. source.root 解析为 project-root-relative 路径。

  2. 检查 source root 位于 project root 内部。

  3. 检查 source root 存在并且是目录。

  4. 使用 source.include 在 source root 内展开文件。

  5. 使用 source.exclude 排除 include 结果。

  6. respect_gitignore 应用 project root 内部的 gitignore 规则。

  7. include_hidden 排除隐藏路径。

  8. follow_symlinks 控制符号链接遍历。

  9. 将结果规范化为 project-root-relative POSIX path。

--source <id> 指向不存在的 source 时返回 SOURCE_NOT_FOUND。source root 不存在或不是目录时返回 SOURCE_ROOT_NOT_FOUND。glob 展开、gitignore 读取或路径规范化失败时返回 DISCOVERY_FAILED

发现步骤中的路径基准必须保持一致。source.root 相对 project root 解释;includeexclude 相对 source root 解释;返回路径相对 project root 表达。该规则使 docs/index.md 这类公共路径能同时服务 JSON 输出、scanner 输入、测试断言和错误 details。

隐藏路径判断使用 project-root-relative POSIX path。include_hidden = false 时,路径任意 segment 以 . 开头的文件或目录都不进入 discovered files。该规则使 .git/.foo/.venv/docs/.draft.md 在默认发现中保持不可见。

符号链接规则服务项目边界。follow_symlinks = false 时,发现过程不跟随 symlink,也不把 symlink file 当成普通业务文件。follow_symlinks = true 时,语言实现仍必须防止循环、越界和重复路径污染公共结果。指向 project root 外部的 symlink 目标不得进入 discovered files;被纳入结果的文件必须能表示为 project-root-relative POSIX path。遍历库报告 symlink 循环、权限错误或路径规范化失败时返回 DISCOVERY_FAILED

source 匹配为空时,discover 返回成功并显示 0 files。空 source 是合法状态。doctor 会把空 source 报告为 warning,见 Doctor 诊断

respect_gitignore = true 只授权项目边界内的 ignore 语义。语言实现不得让用户全局 gitignore、Rootward 仓库祖先目录 .gitignore 或 project root 外部 ignore 文件影响 generated CLI 的发现结果。project root 内部的层级 .gitignore 属于项目边界内 ignore 语义;语言实现支持该语义时,必须在语言章节和测试中固定。

Rootward 规定发现结果,不规定某个语言必须使用同一个遍历库。TypeScript 模板使用 globby;Python 模板使用 wcmatch 和 pathspec;Rust 模板使用 ignore crate 和 globset;Go 模板使用 filepath.WalkDir、gobwas/glob 和 go-git gitignore。语言实现可以不同,公共路径形状和错误语义必须相同。

文件发现不承担业务解析。Markdown 标题提取、Python AST 分析、Rust crate 解析、Go package 解析都属于 scanner 或业务命令。文件发现只确定哪些项目文件进入工具视野。

10. Scanner Registry

scanner registry 是 Rootward 提供给业务工具的接入点。它把 scanner key 映射到 scanner implementation。

接口形状固定为:

type ScannerContext = {
  projectRoot: string;
  source: DiscoveredSource;
};

type ScannerResult = {
  sourceId: string;
  scanner: string;
  files: number;
  bytes: number;
  lines: number;
};

type Scanner = (context: ScannerContext) => Promise<ScannerResult>;
type ScannerRegistry = Record<string, Scanner>;

scanner 消费 DiscoveredSource。它不处理项目根发现,不加载配置,不展开 glob,不应用 .gitignore,不决定 hidden 或 symlink 规则。

scanner 可以读取文件内容,可以执行业务解析,可以产生业务统计。scanner 不能改变 discovered files,也不能把未发现文件私自加入当前 source。需要额外输入通道的业务工具必须定义独立命令或独立配置字段,并经过 设计点准入规则

source 指向未注册 scanner 时,scan 返回 SCANNER_NOT_REGISTEREDdoctor 把同一问题报告为 error 诊断项。

Rootward 模板默认注册 textpython 两个 scanner key。两者在模板中可以使用同一个基础文本扫描实现,统计文件数、字节数和行数。该默认实现证明 registry 分发结构成立,不表达完整 Markdown、Python、RST 或 AsciiDoc 解析能力。

业务工具由 Creator 实例化模板后,可以替换 scanner 实现,也可以替换默认 registry。替换 scanner 不改变 source 声明模型,不改变文件发现语义,不改变 ScannerResult 的公共字段。业务工具需要额外输出时,应先判断该输出属于 scanner result、业务命令输出、debug 面还是未来期权。

scanner registry 是期权式准备。它保留业务处理替换能力,但不把插件市场、动态加载、远程扩展和生态发布机制写入 Rootward 核心契约。

11. Discover 投影

discover 投影文件发现结果。该命令让用户和自动化调用方确认 Rootward 当前看见哪些 source、哪些文件以及这些文件将交给哪个 scanner。

命令表面为:

foo discover [--project <path>] [--source <id>] [--list] [--json] [discovery overrides]

--project <path> 选择显式项目根。没有该选项时,命令使用 CWD 向上发现。项目根发现语义见 项目根发现

--source <id> 选择 source 子集。不存在的 source id 返回 SOURCE_NOT_FOUND

--list 控制是否列出具体文件。它只改变投影详细程度,不改变发现结果。

--json 选择机器输出投影。它不改变项目定位、配置读取或文件发现行为。

discovery overrides 包括 --no-respect-gitignore--follow-symlinks--include-hidden。覆盖语义见 命令行覆盖

discover 的 JSON data 固定为:

type DiscoverData = {
  projectRoot: string;
  configPath: string;
  sources: DiscoveredSource[];
  totalFiles: number;
};

sources 按配置中的 source 顺序排列;使用 --source <id> 时只包含被选择的 source。每个 source 使用 文件发现语义 中定义的 DiscoveredSourcetotalFiles 是所有返回 source 的 file count 总和。文件路径必须是 project-root-relative POSIX path。

人类输出应包含 project root、config path 和 source 摘要。--list 模式下,人类输出在摘要后按 source 分组列出文件。

discover 的职责是投影工具视野。没有 discover,用户只能通过最终业务结果推测配置是否生效、ignore 是否生效、hidden 文件是否被排除、source root 是否正确。discover 把这些推测转化为可检查投影。

discover 不运行 scanner。需要消费 scanner registry 的命令是 scan,见 Scanner Registry命令集合

discover 的输出不承诺业务解析结果。文件是否存在、是否进入 source、使用哪个 scanner,是 discover 的公共对象;文件内容是否符合某个业务规则,是 scanner 或业务命令的公共对象。

12. Doctor 诊断

doctor 检查 Rootward 项目语境是否可被其他命令使用。它的职责是诊断配置、source root、scanner 注册和发现结果中的阻断问题或警告状态。

命令表面为:

foo doctor [--project <path>] [--json] [discovery overrides]

doctor 诊断项固定包含以下字段。

type Diagnostic = {
  level: "info" | "warning" | "error";
  code: string;
  message: string;
  sourceId?: string;
};

level 表示诊断级别。info 表示可用事实,warning 表示不阻断其他命令但需要注意的状态,error 表示阻断其他命令的项目语境问题。

code 是诊断项的稳定标识。它不一定等同 CLI error code,但阻断性诊断会映射到 CLI error code。

message 面向人类。程序不得依赖 message 文本判断诊断类型。

sourceId 在诊断项与特定 source 相关时出现。

doctor 的 JSON data 固定为:

type DoctorData = {
  projectRoot: string;
  configPath: string;
  diagnostics: Diagnostic[];
};

doctor 成功时,DoctorData 进入 JSON success envelope 的 data。doctor 失败时,同一 diagnostics 数组进入 JSON failure envelope 的 error.details.diagnostics。该规则使调用方在成功和失败路径中使用同一诊断项结构。

Rootward doctor 至少报告以下诊断 code。

诊断 code level 触发条件

CONFIG_OK

info

配置已解析并通过校验。

SOURCE_ROOT_NOT_FOUND

error

source root 不存在或不是目录。

SCANNER_NOT_REGISTERED

error

source 使用未注册 scanner。

SOURCE_EMPTY

warning

source 匹配为空。

doctor 和 discover 对同一问题的处理不同。discover 负责产生发现结果,source root 不存在时无法继续,因此返回 SOURCE_ROOT_NOT_FOUND。doctor 负责收集项目健康状态,source root 不存在时生成 error 诊断项,并在最终结果中保留完整 diagnostics。

如果存在 error 诊断项,doctor 命令失败,并把 diagnostics 放入错误 details。主要错误码由首个阻断诊断映射:scanner 未注册映射为 SCANNER_NOT_REGISTERED,source root 问题映射为 SOURCE_ROOT_NOT_FOUND

如果只存在 warning 诊断项,doctor 命令成功。空 source 是合法配置状态,但它表示该 source 当前没有可扫描输入,因此作为 warning 投影。

Rootward 层 doctor 不默认承担业务环境诊断。网络连接、外部服务凭据、数据库迁移、语言工具链版本和业务 parser 健康状态属于具体业务工具。业务工具可以扩展 doctor,但扩展项必须说明它服务的消费者动作、阻断语义和验证方式。

doctor diagnostics 是公共投影。新增诊断 code、改变 level、删除 sourceId 或改变阻断映射都会影响用户和自动化调用方。诊断项扩展必须遵守 设计点准入规则,并在测试契约中增加对应断言。

第四部:命令、输出与错误

第四部定义命令集合、命令行覆盖、人类输出、JSON 输出、错误码和退出码。

13. 命令集合

Rootward 模板固定六组基础命令:initstatusdiscoverscanconfig printdoctor

foo init [path] [--force]
foo status [--project <path>] [--json]
foo discover [--project <path>] [--source <id>] [--list] [--json] [discovery overrides]
foo scan [--project <path>] [--source <id>] [--json] [discovery overrides]
foo config print [--project <path>] [--json] [discovery overrides]
foo doctor [--project <path>] [--json] [discovery overrides]

init 创建工具项目语境。它是唯一不要求已有 Rootward 配置的基础命令。初始化契约见 初始化契约

status 投影当前项目语境。它用于确认工具当前解释的是哪个项目。status 投影见 Status 投影

discover 投影文件发现结果。它加载项目配置,展开 sources,应用 include、exclude、gitignore、hidden 和 symlink 规则,并输出每个 source 匹配的文件数量。discover 投影见 Discover 投影

scan 消费 scanner registry 和发现结果。它先执行文件发现,再把每个 DiscoveredSource 分发给对应 scanner。未注册 scanner 返回 SCANNER_NOT_REGISTERED。scan 投影见 Scan 投影

config print 输出规范化配置。它显示 TOML 配置经过 schema 校验、默认值填充和 CLI 覆盖后的结果。人类输出使用 TOML,JSON 输出使用规范化对象。

doctor 检查项目配置健康状态。它报告配置解析、source root、空 source、scanner 注册和发现语义中的问题。doctor 诊断见 Doctor 诊断

Rootward 基础命令不是业务工具能力上限。业务工具可以新增命令,但新增命令不得改变基础命令的项目定位、配置读取、文件发现、输出和错误语义。

命令层只承担命令表面。项目发现属于 core/project,配置属于 core/config,文件发现属于 core/discovery,scanner 分发属于 core/scanners,输出投影属于 io/output。语言实现可以调整模块命名,但不能把这些职责混入命令层。

基础命令之间存在执行依赖。status 依赖项目定位和配置读取;discover 依赖项目定位、配置读取和文件发现;scan 依赖 discover 结果和 scanner registry;doctor 依赖配置、source root、scanner registry 和发现状态。命令实现应复用同一 core 模块,而不是为每个命令复制判断逻辑。

13.1. Status 投影

status 投影项目语境摘要。该命令加载项目配置并检查 source root 的文件系统状态,但不展开 glob,不应用 .gitignore,不运行 scanner,也不把 source root 缺失作为命令失败。配置解析或配置校验失败仍按配置错误返回。

status 的 JSON data 固定为:

type StatusData = {
  projectRoot: string;
  configPath: string;
  discoveryMode: "explicit" | "cwd-upward";
  sourceCount: number;
  sources: Array<{
    id: string;
    root: string;
    scanner: string;
    rootStatus: "directory" | "missing" | "not-directory";
  }>;
};

projectRootconfigPathdiscoveryMode 来自 ProjectContextsourceCount 等于规范化配置中的 source 数量。sources[].root 使用配置中的 project-root-relative POSIX path,不使用绝对路径。rootStatus 只描述 source root 路径本身,不描述 include/exclude 匹配结果。

人类输出必须包含 project root、config path、discovery mode 和 source 摘要。source 摘要至少包含 source id、root、scanner 和 root status。机器消费者必须使用 JSON data,不得解析人类输出。

13.2. Scan 投影

scan 投影 scanner registry 对 discovered sources 的处理结果。该命令先执行文件发现,再按 source 的 scanner key 分发给 scanner。scan 不改变文件发现结果,不重新解释配置,不把 scanner 输出写回配置。

scan 的 JSON data 固定为:

type ScanData = {
  projectRoot: string;
  configPath: string;
  results: ScannerResult[];
  totals: {
    sources: number;
    files: number;
    bytes: number;
    lines: number;
  };
};

results 按 discovered source 顺序排列。每个元素使用 Scanner Registry 中定义的 ScannerResulttotals.sources 等于 results.lengthtotals.filestotals.bytestotals.lines 是所有 scanner result 的对应字段总和。

人类输出必须投影每个 source 的 scanner、files、bytes、lines 和总计。scanner 需要新增业务字段时,该字段属于 scanner result 或业务命令输出的公共扩展,必须通过 设计点准入规则

14. 命令行覆盖

Rootward 覆盖优先级固定为:

CLI flag > config.toml > built-in defaults

命令行覆盖只影响本次执行。它不修改 .foo/config.toml,不创建隐式配置,不把命令行变成配置编辑器。

--project <path> 覆盖项目定位来源。它不修改配置。显式项目根语义见 项目根发现

--source <id> 选择 source 子集。它不修改配置中的 sources。不存在的 source id 返回 SOURCE_NOT_FOUND

--json 选择机器输出投影。它不修改业务行为,不改变命令结果,只改变投影。

--list 控制 discover 是否列出具体文件。它不修改发现结果。

--no-respect-gitignore 覆盖 discovery.respect_gitignorefalse

--follow-symlinks 覆盖 discovery.follow_symlinkstrue

--include-hidden 覆盖 discovery.include_hiddentrue

Rootward 不提供通过命令行增删 source 的基础命令。source 注册属于 TOML 配置契约,必须通过编辑 .foo/config.toml 完成。该规则防止命令行参数成为隐式项目状态。

命令行覆盖必须进入规范化配置投影。config print 在使用 discovery override 时,应显示覆盖后的规范化配置。该行为使 CI 和用户能够确认本次执行的实际配置。

命令行 usage error 映射为 USAGE_ERROR 和退出码 2。缺少 --project--source 的参数值、未知命令、未知 option 和无效 option 值都属于 usage error。

命令行覆盖不改变配置文件的身份。config print 展示覆盖后的规范化配置,是本次执行的观察面,不是配置写回操作。语言模板不得因为用户传入 discovery override 而修改 .foo/config.toml

15. 人类输出与 JSON 输出

Rootward 命令核心返回结构化 result。输出层把 result 投影为人类文本或 JSON。核心模块不直接写 stdout 或 stderr。

命令结果结构固定为:

type CommandResult<T> =
  | {
      ok: true;
      code: 0;
      data: T;
    }
  | {
      ok: false;
      code: ExitCode;
      error: CliError;
    };

JSON 成功输出固定为:

{
  "ok": true,
  "data": {}
}

JSON 失败输出固定为:

{
  "ok": false,
  "error": {
    "code": "PROJECT_NOT_FOUND",
    "message": "No .foo/config.toml was found from the current directory upward.",
    "details": {}
  }
}

成功 JSON 写入 stdout。失败 JSON 写入 stderr。该规则使脚本能够把成功数据流和失败诊断流分开处理。

details 字段在没有结构化上下文时可以省略。存在 details 时,它必须是 JSON object。语言实现不得把 stack trace、未结构化错误文本或语言原生异常对象直接放入 details

人类输出服务终端阅读。人类输出可以使用摘要表、分组列表或 TOML 文本。机器消费者不得解析人类输出。机器消费者必须使用 --json

人类错误输出不暴露内部 stack trace。普通错误输出包含错误 code、message 和必要 details。内部调试信息不进入 Rootward 默认公共输出。

status 人类输出应投影 project root、config path、discovery mode 和 sources 状态。status JSON data 见 Status 投影

discover 人类输出应投影 project root、config path、source、scanner、root 和 file count。--list 模式下应列出 project-root-relative POSIX path。

config print 人类输出使用 TOML。JSON 模式输出规范化配置对象。

scan 人类输出应投影每个 source 的 scanner、files、bytes、lines 和总计。scan JSON data 见 Scan 投影

doctor 人类输出应投影整体状态和诊断项。诊断项至少包含 level、code、source id 和 message。

输出投影是公共契约。新增 JSON 字段、删除 JSON 字段、改变错误 envelope、改变路径格式和改变 stdout/stderr 归属,都会影响消费者,必须经过 设计点准入规则 的准入判断。

JSON 字段命名属于公共契约。Rootward 默认使用 lower camel case 的结构化字段,例如 projectRootconfigPathtotalFilessourceId。配置文件仍使用 TOML snake_case 字段,例如 respect_gitignore。语言实现不得把本语言命名习惯泄漏为 JSON 公共字段。

16. 错误码与退出码

Rootward 错误对象固定为:

type CliError = {
  code: CliErrorCode;
  message: string;
  details?: Record<string, unknown>;
};

code 面向程序。自动化调用方使用它判断错误类型。

message 面向人类。程序不得依赖 message 文本。

details 提供结构化上下文。它可以包含 path、sourceId、scanner、diagnostics、schema issues 或 failure reason。

退出码固定为:

退出码 名称 含义

0

success

命令成功。

1

runtime failure

未映射的运行时失败。

2

usage error

命令行参数错误、未知命令、缺少必填参数或无效 option 值。

3

project discovery error

项目发现或初始化状态错误。

4

config error

配置读取、解析、schema 或 source 选择错误。

5

discovery or scanning error

文件发现、scanner 注册、scanner 执行或 doctor 阻断诊断错误。

错误码固定为:

错误码 退出码 触发条件

USAGE_ERROR

2

命令行参数错误、未知命令、缺少 option 值或无效 option 值。

PROJECT_NOT_FOUND

3

CWD 向上发现没有找到 .foo/config.toml

PROJECT_CONFIG_NOT_FOUND

3

显式 --project 指定的根目录下不存在 .foo/config.toml

INIT_TARGET_INVALID

3

init 目标路径不存在或不是目录。

PROJECT_ALREADY_INITIALIZED

3

init 目标目录已有配置且未传 --force

CONFIG_READ_FAILED

4

配置文件存在但不可读取。

CONFIG_PARSE_FAILED

4

TOML 语法无法解析。

CONFIG_INVALID

4

TOML 语法正确但 schema 或语义校验不成立。

SOURCE_NOT_FOUND

4

--source <id> 指向不存在的 source。

SOURCE_ROOT_NOT_FOUND

5

source root 在文件系统中不存在或不是目录。

SCANNER_NOT_REGISTERED

5

source 使用的 scanner key 没有注册。

DISCOVERY_FAILED

5

glob 展开、gitignore 读取或路径规范化失败。

SCAN_FAILED

5

scanner 执行失败。

INTERNAL_ERROR

1

未归档运行时失败。

语言实现必须把内部错误映射到这些稳定错误码。TypeScript 的 exception、Python 的 exception、Rust 的 error enum 和 Go 的 error value 都不能直接泄漏为公共错误类型。

未映射运行时失败使用 INTERNAL_ERROR 和退出码 1。该分支表示模板实现存在未归档错误路径,不是业务错误的常规表达方式。语言模板的测试应覆盖已知错误码,减少运行时失败分支。

错误码表的快速索引见 错误码表

错误码和诊断 code 是不同对象。错误码决定命令失败的程序分支;诊断 code 描述 doctor 检查项。阻断性 doctor 诊断需要映射为某个 CLI 错误码,但非阻断 warning 不应制造失败错误码。

第五部:Rootward Creator 与模板分发

第五部定义 Rootward Creator CLI、npm 分发面、模板 manifest、身份替换、目标目录规则和创建器错误语义。

17. Rootward Creator CLI 契约

Rootward Creator CLI 是 Rootward 的默认模板实例化入口。它通过 npm create package 分发,把 Rootward language template 实例化到调用方指定的目标目录。Creator CLI 不参与生成物运行时,不读取生成物项目配置,不执行生成物 init,不替用户运行依赖安装、测试或 git 初始化。

Creator package name 固定为 create-rootward。Creator bin name 固定为 create-rootwardnpm create rootwardpnpm create rootwardnpx create-rootwardpnpm dlx create-rootward 都指向同一个 Creator 命令面。

Creator npm package 必须包含 Creator runtime 和 packaged template asset。packaged template asset 来源于同一版本的仓库模板源,并随 Creator package 版本一起分发。模板源不作为独立 npm 包发布,也不在 Creator package source tree 中维护第二份副本。Creator package version 是 Creator runtime 与 packaged template asset 的共同版本边界。

命令表面固定为:

create-rootward <template> <target-dir> --cli-name <name> [identity options] [--json]

<template> 是模板 id。本书定义的模板 id 集合固定为 typescriptpythonrustgotypescriptrust 是可实例化模板。pythongo 在仓库中以 reserved 状态存在;Creator 请求 reserved 模板时返回 TEMPLATE_NOT_IMPLEMENTED

<target-dir> 是生成物目录。相对路径按 Creator 当前工作目录解析;绝对路径按宿主文件系统语义解析。解析后的目标目录形成 Creator 的写入边界。Creator 只允许写入不存在目录或空目录,并且不得把模板文件、替换结果、postcheck 或临时写入投影到目标目录之外。目标路径存在且非空时返回 TARGET_NOT_EMPTY。目标路径不可创建、不是目录或不能形成稳定目标目录边界时返回 TARGET_INVALID。Creator 不提供 merge、overwrite、force 或迁移语义。

--cli-name <name> 是生成物 CLI name。该身份对所有语言模板必需。生成物 config directory name 由 Creator 按 .${cliName} 派生,并作为显式值写入生成物。生成物运行时不得从 package name、crate name、module path 或 distribution name 推导 config directory name。

TypeScript 模板额外要求 --package-name <name>。Rust 模板额外要求 --crate-name <name>--bin-name <name>。Python 模板的身份槽位是 --project-name <name>--module-name <name>。Go 模板的身份槽位是 --module-path <path>。未实现模板的身份槽位仍由 manifest 声明;Creator 在 reserved 状态下不生成这些模板。

Creator core 按 manifest identity 解释必需身份槽位、派生身份槽位和格式化身份槽位。命令表面可以只暴露 implemented 模板需要的身份 option;reserved 模板中的身份槽位是模板设计登记,不构成可生成承诺。

--json 只改变 Creator 输出投影,不改变生成行为。Creator 不提供交互式选择,不读取 TTY 输入,不显示 prompt,不要求终端能力。Creator 的所有行为必须能通过 argv、stdout、stderr 和 exit code 稳定测试。

Creator 成功的人类输出必须包含 template id、target dir、关键身份和 next commands。Creator 成功 JSON 固定为:

{
  "ok": true,
  "data": {
    "template": "typescript",
    "targetDir": "/repo/my-tool",
    "identity": {
      "cliName": "my-tool",
      "configDirName": ".my-tool",
      "packageName": "my-tool"
    },
    "filesWritten": 42,
    "nextCommands": [
      "cd /repo/my-tool",
      "pnpm install",
      "pnpm test"
    ]
  }
}

Creator 失败 JSON 固定为:

{
  "ok": false,
  "error": {
    "code": "TARGET_NOT_EMPTY",
    "message": "Target directory is not empty.",
    "details": {
      "targetDir": "/repo/my-tool"
    }
  }
}

成功 JSON 写 stdout。失败 JSON 写 stderr。--json 失败输出不得混入命令解析库、人类 usage 文本、stack trace 或非 JSON 诊断行。

Creator 的 next commands 是生成后建议,不是 Creator 自动执行的动作。Creator 完成目标目录写入和 postcheck 后返回。依赖安装、测试、构建和 git 初始化属于生成物消费者后续动作。

18. 模板 Manifest 与模板源

Rootward language template 是 Creator 可以读取并实例化的模板对象。Rootward 区分仓库模板源(repository template source)和打包模板资产(packaged template asset)。仓库模板源是维护者编辑、审查和演进的唯一模板源;打包模板资产是 Creator package artifact 中供发布态 Creator runtime 读取的生成资产。

Rootward repository 的模板体系目录固定为:

packages/
  create-rootward/
templates/
  typescript/
  python/
  rust/
  go/

packages/create-rootward/ 承载 npm Creator package 的源码、构建脚本和测试。templates/ 承载仓库模板源。维护者只在 templates/ 中编辑 manifest 和 template root。

packages/create-rootward/templates/ 不属于 Rootward repository 的 source layout。Rootward 不通过 package-local template source 维护发布资产。若实现需要在 package 构建期间准备模板文件,该文件集合必须位于 ignored build output 或 pack staging 内,并且必须能由 templates/ 重新生成。

Creator package artifact 必须包含从仓库模板源生成的 packaged template asset。该资产位于 Creator runtime 可读的构建输出或 npm tarball 路径中,例如 dist/templates/。packaged template asset 不具有源码身份;它可以被删除,并由构建流程从 templates/ 重新生成。Creator runtime 在仓库开发态读取仓库模板源,在发布态读取 packaged template asset。两种读取位置必须投影同一 manifest、template root、reserved 状态和 manifest token 集合。

templates/<template-id>/
  manifest.json
  template/

manifest.json 是 Creator 读取的模板元数据。template/ 是复制给用户的生成物源。Rootward 仓库维护脚本、Creator 测试 fixture、构建产物和临时目录不得进入 template/,除非它们本来就是生成物应拥有的文件。

manifest 顶层字段固定为:

{
  "id": "typescript",
  "language": "typescript",
  "status": "implemented",
  "templateRoot": "template",
  "identity": {},
  "tokens": {},
  "exclude": [],
  "postcheck": [],
  "nextCommands": []
}

id 是 Creator 命令中的 template id。language 是语言分类。status 只能是 implementedreservedimplemented 表示 Creator 可以实例化该模板。reserved 表示仓库保留该语言模板目录,但 Creator 不承诺生成该语言项目。

本书定义的模板状态为:TypeScript 和 Rust 模板 status 是 implemented,Python 和 Go 模板 status 是 reserved。reserved 目录必须包含 manifest 和 template root;Creator 请求 reserved 模板时返回 TEMPLATE_NOT_IMPLEMENTED,不得生成空项目或半成品项目。

identity 定义模板需要的身份槽位。TypeScript manifest 的身份槽位固定为:

{
  "cliName": {
    "required": true,
    "pattern": "^[a-z][a-z0-9-]*$"
  },
  "configDirName": {
    "derivedFrom": "cliName",
    "format": ".{cliName}"
  },
  "packageName": {
    "required": true
  }
}

Rust manifest 的身份槽位固定为:

{
  "cliName": {
    "required": true,
    "pattern": "^[a-z][a-z0-9-]*$"
  },
  "configDirName": {
    "derivedFrom": "cliName",
    "format": ".{cliName}"
  },
  "crateName": {
    "required": true,
    "pattern": "^[a-z][a-z0-9_-]*$"
  },
  "binName": {
    "required": true,
    "pattern": "^[a-z][a-z0-9-]*$"
  }
}

tokens 定义模板源中的机器占位符。模板源必须使用 manifest 显式声明的 token,不得依赖自然示例词替换。token 字面形态可以按语言和文件语法选择;它必须能够被 Creator 精确替换,不能与同一模板内其他 token 形成前缀重叠,并且不得阻止仓库模板源作为该语言的有效项目接受验证。

TypeScript 模板固定使用以下 token:

__ROOTWARD_CLI_NAME__
__ROOTWARD_CONFIG_DIR_NAME__
__ROOTWARD_PACKAGE_NAME__

Rust 模板使用 Cargo 语法合法的 token,例如:

rootward-token-cli-name
.rootward-token-config-dir-name
rootward_token_crate_name
rootward-token-bin-name

foo 可以在本书中作为 CLI name 的解释性例子存在;模板源中不得把 foo 当作待替换身份。Creator 替换完成后,生成物中不得残留 manifest tokens 中声明的任一 token。残留 token 返回 TOKEN_RESIDUE_FOUND

exclude 定义复制时排除的路径。TypeScript 模板固定排除:

[
  "node_modules",
  "dist",
  "coverage",
  "temporary",
  ".turbo"
]

postcheck 定义生成后必须存在的路径。TypeScript 模板至少检查以下路径:

package.json
README.md
src/bin.ts
src/cli.ts
src/core/constants.ts
src/index.test.ts
tsconfig.json
tsdown.config.ts
vitest.config.ts

Rust 模板至少排除以下路径:

[
  "target",
  "temporary"
]

Rust 模板至少检查以下路径:

Cargo.toml
README.md
src/main.rs
src/lib.rs
src/cli.rs
src/core/constants.rs
tests/cli_contract.rs

nextCommands 定义 Creator 成功输出中的建议命令。Creator 不自动执行这些命令。

模板源不得依赖 Rootward 仓库内部 workspace package。生成后的项目必须能在目标目录中独立安装依赖、运行测试和构建。

19. Creator 错误码与退出码

Creator 错误对象固定为:

type CreatorError = {
  code: CreatorErrorCode;
  message: string;
  details?: Record<string, unknown>;
};

Creator 错误码属于模板实例化契约。生成物 CLI 错误码属于项目型 CLI 运行契约。两者服务不同消费者动作,不能共用错误码表。

Creator 退出码固定为:

退出码 名称 含义

0

success

模板实例化成功。

1

internal failure

未归档运行时失败。

2

usage error

命令行参数错误、未知 option、缺少 option 值或身份参数无效。

3

target error

目标目录不可写入、非空或路径语义不合法。

4

template error

template id、manifest 或模板状态不满足契约。

5

write or rewrite error

模板复制或身份替换失败。

6

postcheck error

生成后结构检查或 token residue 检查失败。

Creator 错误码固定为:

错误码 退出码 触发条件

USAGE_ERROR

2

参数错误、未知 option、缺少 option 值或身份参数无效。

TEMPLATE_NOT_FOUND

4

template id 不存在。

TEMPLATE_NOT_IMPLEMENTED

4

template id 存在但 manifest status 为 reserved

TEMPLATE_INVALID

4

manifest 或模板源结构不符合 Creator 契约。

TARGET_NOT_EMPTY

3

目标目录存在且非空。

TARGET_INVALID

3

目标路径不可创建、不是目录、路径语义不合法或不能形成稳定目标目录边界。

COPY_FAILED

5

模板源复制失败。

REWRITE_FAILED

5

身份占位符替换失败。

TOKEN_RESIDUE_FOUND

6

生成物仍残留 manifest tokens 中声明的 identity token。

POSTCHECK_FAILED

6

生成后结构检查失败。

INTERNAL_ERROR

1

未归档运行时失败。

Creator 实现必须把命令解析库错误、文件系统错误、manifest 校验错误和 token 替换错误映射到这些稳定错误码。Creator --json 失败输出只包含 Creator JSON failure envelope,不暴露 stack trace,不混入命令解析库默认错误文本。

第六部:跨语言实现

第六部定义跨语言同构原则,并说明 TypeScript、Python、Rust 和 Go 模板如何承载同一契约。

20. 跨语言同构原则

Rootward 生成物契约的本体是项目型 CLI 公共行为,不是任意一种语言实现。TypeScript、Python、Rust 和 Go 语言模板在生成物层必须共享同一组公共行为,并允许各语言使用符合自身生态的实现工具。

跨语言必须同构的对象包括:

  • 命令集合和命令职责。

  • 项目根发现语义。

  • .foo/config.toml 配置模型。

  • [discovery] 表和 sources 表数组的字段语义。

  • source id、source root 和 glob 路径规则。

  • 文件发现结果的 project-root-relative POSIX path。

  • scanner registry 的边界。

  • discoverstatusconfig printscandoctor 的公共投影。

  • JSON success 和 failure envelope。

  • 错误码和退出码。

  • 测试契约。

跨语言允许差异的对象包括:

  • 命令行解析库。

  • TOML 解析和序列化库。

  • 运行时 schema 或语义校验方式。

  • 文件遍历和 glob 库。

  • gitignore 实现库。

  • 内部路径类型。

  • 内部错误类型。

  • identity token 的字面形态。

  • 测试框架和构建命令。

  • 包管理和发布元数据。

允许差异不能改变生成物公共契约。Python 的 Typer、Rust 的 clap、Go 的 Cobra 和 TypeScript 的 Commander 都只承担命令表面。它们不能改变 Rootward 的命令语义、错误码、JSON envelope 或项目发现规则。

语言实现章节不提供通用编程教程。它们定义每个依赖在 Rootward 中承担的对象责任。实现者需要阅读相应语言官方文档来掌握 API,但不能用官方文档中的默认习惯覆盖 Rootward 契约。

新增语言模板必须先满足本章同构原则。语言生态中的常见做法只有在不破坏 Rootward 公共契约时,才能进入该语言模板。

跨语言同构通过测试和附录清单持续检查。测试行为见 测试契约;契约压缩清单见 Rootward Contract 清单;语言依赖职责见 语言依赖表。语言实现章节只能细化这些对象,不能重新定义它们。

21. TypeScript 模板实现

TypeScript 模板是 Rootward language template 的一个实现。该模板由 Creator 实例化为独立 Node 项目。生成物使用 Node 24、TypeScript、ESM、Commander、Zod、smol-toml、globby、ignore、tsdown、Vitest 和 Biome。

TypeScript 模板源位于:

templates/typescript/
  manifest.json
  template/

templates/typescript/manifest.json 的 status 必须是 implementedtemplate/ 中的生成物源必须使用 manifest 声明的 identity tokens。Creator 生成后必须替换 package name、bin key、constants、README 和测试断言。

TypeScript 模板源固定使用以下 token:

__ROOTWARD_PACKAGE_NAME__
__ROOTWARD_CLI_NAME__
__ROOTWARD_CONFIG_DIR_NAME__

template/package.jsonnamebin 必须使用 token。template/src/core/constants.ts 必须显式写入 package name、CLI name 和 config directory name token。模板源不得依赖 Rootward 仓库内部 workspace package,不得残留外部模板身份、外部 workspace package 或外部 tsconfig 依赖。

依赖职责如下。

  • Commander 承担命令表面:command、subcommand、option、help、version 和 usage error。

  • Zod 承担运行时 schema 校验:配置文件、规范化配置、命令结果和错误对象边界。

  • smol-toml 承担 TOML 解析和序列化:init 生成默认配置,config print 投影规范化配置。

  • globby 承担 include、exclude、hidden 和 symlink 相关 glob 展开。

  • ignore 承担项目根 .gitignore pattern matching。TypeScript 实现不得让宿主仓库的祖先 .gitignore 污染被生成项目的发现结果。

  • Vitest 承担契约测试。

  • tsdown 承担 ESM 构建和类型声明输出。

  • Biome 承担 TypeScript 项目的格式和检查。

生成物源码结构固定为:

src/
  bin.ts
  cli.ts
  index.ts
  commands/
    init.ts
    status.ts
    discover.ts
    scan.ts
    config-print.ts
    doctor.ts
    shared.ts
  core/
    constants.ts
    errors.ts
    result.ts
    project.ts
    config.ts
    discovery.ts
    scanners.ts
  io/
    filesystem.ts
    output.ts

bin.ts 是真实进程入口。它读取 process.argvprocess.cwd(),调用可测试的 runCli,写 stdout/stderr,并设置 process.exitCode

cli.ts 组装 Commander program。它注册命令、选项、help、version 和 usage error handler。它不读取配置文件,不展开 glob,不运行 scanner。Commander 的默认错误输出不得直接写入 --json 失败 stderr。

commands/ 实现命令编排。每个 command action 调用 core 模块,并返回 CommandResult。命令层不拥有项目发现规则。

core/project.ts 实现显式项目根和 CWD 向上发现。

core/config.ts 实现 TOML 读写、Zod schema、规范化配置和配置错误映射。

core/discovery.ts 实现 source 展开、路径规范化、gitignore-aware 扫描和 discovered result。

core/scanners.ts 定义 scanner registry、内置 scanner 和 scanner 错误映射。

io/filesystem.ts 定义文件系统 adapter。真实入口使用 Node fs;测试可以通过临时目录和 adapter 保持行为接近真实环境。

io/output.ts 定义人类输出和 JSON 输出投影。核心模块不直接调用 console.log

TypeScript 模板不得把 Commander option object 直接当成核心配置。CLI option 需要先转换为 Rootward discovery overrides,再交给配置层合并。该规则防止 Commander 的命名、默认值和错误对象泄漏到 Rootward core。

TypeScript 生成物必须满足以下附加约束。

  • --json 下的 unknown command 和 unknown option 失败输出只包含 JSON failure envelope。

  • 未归档运行时失败使用 INTERNAL_ERROR,不得伪装成 SCAN_FAILED

  • init 目标不存在或不是目录时使用 INIT_TARGET_INVALID

  • source root、include pattern 和 exclude pattern 中的反斜杠返回 CONFIG_INVALID

  • symlink 发现测试必须覆盖不跟随 symlink、跟随 symlink 时排除 project root 外部目标、循环或遍历失败映射为 DISCOVERY_FAILED

  • 生成物测试必须能在 Creator 实例化后的真实目标目录中运行。

22. Python 模板实现

Python 模板使用 uv、Typer、Pydantic v2、tomlkit、wcmatch、pathspec、pytest、Ruff 和 ty。该组合承载 Rootward 契约,不引入全局配置系统、插件平台或业务 parser。

工程依赖形状如下。

[project]
requires-python = ">=3.13"
dependencies = [
  "typer",
  "pydantic",
  "tomlkit",
  "wcmatch",
  "pathspec",
]

[dependency-groups]
dev = [
  "pytest",
  "ruff",
  "ty",
]

uv 承担 Python 项目管理、依赖同步、环境运行和命令执行。模板工程命令应使用 uv run

Typer 承担命令表面。它负责 subcommand、option、argument、help 和 usage error。Typer 不承担项目发现、配置读取、文件发现、scanner 分发或输出投影。

Pydantic v2 承担运行时配置边界。.foo/config.toml 来自磁盘,Python type hints 本身不能校验运行时输入。DiscoveryConfigSourceConfigNormalizedConfigCliError 应通过 Pydantic model 或等价的 Pydantic 校验进入核心层。

tomlkit 承担 TOML 读取和写入。标准库 tomllib 只读 TOML,不能满足 init 生成配置和 config print 输出 TOML 的职责。

wcmatch 承担 globstar 和 brace pattern。Rootward 默认 include 包含 */.{md,mdx,rst,adoc,txt},Python 标准库 glob 不能完整承载该 surface。

pathspec 承担 gitignore-style pattern matching。Python discovery 应在 source include/exclude 后应用 pathspec 过滤 .gitignore

pytest 承担契约测试。Ruff 承担格式和 lint。ty 承担开发期类型检查。

源码结构固定为:

src/rootward_foo/
  __init__.py
  __main__.py
  cli.py
  commands/
    init.py
    status.py
    discover.py
    scan.py
    config_print.py
    doctor.py
    shared.py
  core/
    constants.py
    errors.py
    result.py
    project.py
    config.py
    discovery.py
    scanners.py
  io/
    filesystem.py
    output.py

tests/
  test_cli_contract.py

main.py 是真实进程入口。它读取 argv 和 cwd,调用可测试的 run_cli,写 stdout/stderr,并返回 exit code。

cli.py 组装 Typer app。它捕获 Typer usage error,并映射为 USAGE_ERROR

core/discovery.py 使用 wcmatch 展开 include/exclude,使用 pathspec 应用 .gitignore,并输出 project-root-relative POSIX path。

core/scanners.py 默认注册 textpython scanner。两者可以共享文本统计实现。

验证命令固定为:

uv run ruff format --check .
uv run ruff check .
uv run ty check
uv run pytest

Python 模板不使用 argparse、直接 Click、Black/isort/flake8 组合或只读 tomllib 作为核心方案。排除这些对象不是技术偏好陈述,而是为了保持 Rootward 的命令表面、TOML 写入、现代检查和运行时校验边界清楚。

Python 模板的 run_cli 应返回结构化运行结果,而不是让 Typer 直接控制进程退出。真实入口负责把 run_cli 的 stdout、stderr 和 exit code 投影到进程;测试优先调用 run_cli,只在少量集成测试中执行真实入口。该边界与 人类输出与 JSON 输出 的输出规则一致。

23. Rust 模板实现

Rust 模板是 Rootward language template 的一个实现。该模板由 Creator 实例化为独立 Cargo 项目。生成物使用 Rust 2024、Cargo、clap derive、serde、serde_json、toml、ignore、globset、thiserror、camino、assert_cmd 和 assert_fs。该组合承载 Rootward 的单 binary、项目边界内 discovery、稳定 JSON 投影和稳定错误语义。

Rust 模板源必须是有效 Cargo 项目。仓库维护者必须能在 templates/rust/template 内直接运行标准 Cargo 验证命令。该规则要求 Rust identity token 使用 Cargo 语法合法的字面形态;Rust 模板不得为了复用 TypeScript token 形态而让 Cargo.toml、binary target 或测试入口处于不可解析状态。

依赖形状如下。

[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "1"
ignore = "0.4"
globset = "0.4"
thiserror = "2"
camino = { version = "1", features = ["serde1"] }

[dev-dependencies]
assert_cmd = "2"
assert_fs = "1"

clap derive 承担命令表面。ParserSubcommand 映射 Rootward 命令树。clap 不承担项目发现、配置读取、文件发现或输出投影。

serde 承担结构化数据边界。配置输入、规范化配置、JSON 输出、diagnostics 和 error envelope 都应通过 serde 结构表达。

toml crate 承担 TOML 解析和序列化。它服务 .foo/config.toml 读取、默认配置投影和 config print。Rust 模板使用当前稳定主版本,不把历史示例版本号作为契约。

ignore crate 承担递归目录遍历、hidden 过滤、symlink 遍历控制和 project root 内部 gitignore-aware discovery。Rust 模板不得让用户全局 gitignore 或 project root 祖先目录 .gitignore 污染 generated CLI 的发现结果。

globset 承担 source include 和 exclude pattern matching。Rootward 默认 include 使用 globstar 和 brace pattern;Rust 模板不使用手写字符串匹配承载该语义。

thiserror 承担内部错误类型。Rootward 错误码是公共契约,Rust 实现应定义 error enum,并把内部错误映射到稳定 CliError。任意错误上下文不能直接泄漏为公共错误。

camino 承担 UTF-8 路径投影。Rootward 公共路径是 project-root-relative POSIX string;camino 使该约束在 Rust 类型层更清楚。

assert_cmdassert_fs 承担 CLI 契约测试。assert_cmd 运行生成物 binary 并断言退出码、stdout 和 stderr;assert_fs 创建受控临时项目。Rust 测试优先对 JSON envelope、错误码、退出码和 project-root-relative POSIX path 做结构化断言。需要逐字符固定 human output 时,维护者可以引入 insta,但快照不是 Rust 模板成立的构成性条件。

源码结构固定为:

src/
  main.rs
  lib.rs
  cli.rs
  commands/
    mod.rs
    init.rs
    status.rs
    discover.rs
    scan.rs
    config_print.rs
    doctor.rs
    shared.rs
  core/
    mod.rs
    constants.rs
    errors.rs
    result.rs
    project.rs
    config.rs
    discovery.rs
    scanners.rs
  io/
    mod.rs
    filesystem.rs
    output.rs

tests/
  cli_contract.rs

main.rs 是真实进程入口。它读取 argv 和 cwd,调用 run_cli,写 stdout/stderr,并返回 exit code。lib.rs 导出可测试 API。cli.rs 只处理 clap 命令表面,并把 clap 解析结果转换为 Rootward command options。core 层不依赖 clap types。

Rust 模板的 Cargo.toml 必须使用 manifest token 表达 crate name 和 binary name。crate name token 必须满足 Cargo package name 语法;binary name token 必须满足 Cargo binary target name 语法。Rust 模板的 constants 必须显式写入 CLI name 和 config directory name token;运行时不得从 crate name 或 binary name 推导 config directory name。

clap usage error 必须映射为 USAGE_ERROR--json 下的 unknown command、unknown option 和缺少 option value 失败输出只包含 Rootward JSON failure envelope,不得混入 clap 默认 error text、help text、panic 或 backtrace。

验证命令固定为:

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo build

cargo test 是 Rust 模板的基础测试命令。仓库维护者可以额外使用 cargo-nextest 加速或增强测试运行,但 generated CLI 的 README 和 Rootward 完成判断不得要求普通消费者安装 nextest。

Rust 模板不使用 structopt。clap derive 已经承担该位置。Rust 模板不使用 anyhow 作为公共错误模型。Rootward 的错误码和退出码必须由显式错误映射控制。

Rust 模板可以在内部使用 Result<T, RootwardError> 组织控制流,但公共输出必须经过 CommandResultCliError 投影。panic、debug display、backtrace 和 anyhow-style context 不进入默认 human output 或 JSON error details。

24. Go 模板实现

Go 模板使用 Go modules、Cobra、go-toml/v2、gobwas/glob、go-git gitignore、go-billy/osfs 和标准 testing。该组合承载 Rootward 的平台工具语境,并保持 Go 原生构建和测试工作流。

依赖形状如下。

require (
    github.com/spf13/cobra v1
    github.com/pelletier/go-toml/v2 v2
    github.com/gobwas/glob v0.2.3
    github.com/go-git/go-git/v5 v5
    github.com/go-git/go-billy/v5 v5
)

Cobra 承担命令表面。它负责 subcommand、flags、help 和 usage error。Go 模板不使用 Viper,因为 Rootward 的配置来源必须固定为项目内 .foo/config.toml;多来源配置会扩大公共契约。

go-toml/v2 承担 TOML 解析和序列化。它服务配置读取、默认配置生成和 config print

gobwas/glob 承担 globstar 和 brace alternatives。Rootward 默认 include 包含 **{md,mdx,rst,adoc,txt},Go 标准库 filepath.Match 不能完整承载该 surface。

go-git 的 gitignore 包承担 .gitignore pattern matching。go-billy/osfs 提供 go-git ignore 读取所需的文件系统适配。

Go 标准库 encoding/json 承担 JSON 输出。标准库 testingt.TempDir()osexec 承担测试。Go 模板不引入第三方测试框架。

源码结构固定为:

cmd/foo/main.go
internal/cli/cli.go
internal/commands/
  init.go
  status.go
  discover.go
  scan.go
  config_print.go
  doctor.go
  shared.go
internal/core/
  constants.go
  errors.go
  result.go
  project.go
  config.go
  discovery.go
  scanners.go
internal/io/
  filesystem.go
  output.go

cmd/foo/main.go 是真实进程入口。internal/cli 组装 Cobra command tree。internal/core 承担 Rootward 契约。internal/io 承担文件系统和输出投影。

Go 配置校验采用手写 Validate()。配置字段有限,手写语义能直接表达 source id、重复 id、root 相对路径和 root 不越界规则,不需要反射 validator。

Go 错误模型使用自定义结构体。CliError 固定 code、message 和 details;Failure 固定 error 和 exit code。Cobra error 不直接成为公共错误。

验证命令固定为:

gofmt -w .
go test ./...
go vet ./...
go build ./cmd/foo

Go 模板不使用标准库 flag 作为基础命令层,因为 Rootward 有稳定子命令树和二级命令 config print。Go 模板不使用 Viper,不使用反射 validator,不使用第三方测试 DSL。

Go 模板的 Cobra command 应把解析后的参数转换为 Rootward command options,再调用 internal/coreinternal/commands。Cobra 不直接写 stdout/stderr,不直接读取 .foo/config.toml,也不直接展开 glob。该边界使 Go 实现与 跨语言同构原则 保持同构。

第七部:验证、演进与边界控制

第七部定义测试契约、设计点准入规则、模板实例化责任、相邻对象和未来期权。

25. 测试契约

Rootward 测试证明公共契约成立。测试分为生成物契约测试和 Creator 契约测试。生成物契约测试证明项目语境、配置、发现、scanner、输出和错误语义在语言实现中稳定。Creator 契约测试证明 Rootward 能通过非交互命令把语言模板源实例化为具体项目。

所有实现状态为 implemented 的语言模板必须覆盖以下生成物测试组。

初始化测试:

  • init 在目标目录创建 .foo/config.toml

  • init 创建 .foo/cache/.gitignore

  • init 创建 .foo/state/.gitignore

  • init --json 输出 project root、config dir、config path、created 和 overwritten。

  • init 目标不存在或不是目录时返回 INIT_TARGET_INVALID

  • 已初始化目录再次 init 返回 PROJECT_ALREADY_INITIALIZED

  • init --force 覆盖配置文件,并保留 cache/state gitignore 结构。

项目定位测试:

  • 从项目根运行 status 找到配置。

  • 从子目录运行 status 向上找到配置。

  • status --json 输出 project root、config path、discovery mode、source count 和 source root status。

  • source root 缺失时 status 成功,并把该 source 的 root status 投影为 missing

  • --project 指定项目根时只读取该根配置。

  • --project 指定错误目录时返回 PROJECT_CONFIG_NOT_FOUND

  • 未初始化目录运行项目命令返回 PROJECT_NOT_FOUND

配置测试:

  • 默认 TOML 通过 schema 和语义校验。

  • TOML 语法错误返回 CONFIG_PARSE_FAILED

  • schema 或语义错误返回 CONFIG_INVALID

  • source id 重复返回 CONFIG_INVALID

  • source root 使用绝对路径返回 CONFIG_INVALID

  • source root 越界返回 CONFIG_INVALID

  • source root、include pattern 或 exclude pattern 包含反斜杠时返回 CONFIG_INVALID

发现测试:

  • discover 按 include 展开文件。

  • discover 按 exclude 排除文件。

  • discover --source <id> 只展开指定 source。

  • discover --json 输出 project root、config path、sources 和 total files。

  • respect_gitignore = true 时排除 project root 内部 gitignore 规则声明的路径。

  • project root 外部的 gitignore 规则不影响发现结果。

  • --no-respect-gitignore 覆盖配置。

  • include_hidden = false 时不扫描隐藏路径。

  • --include-hidden 覆盖配置。

  • follow_symlinks = false 时不跟随 symlink,也不把 symlink file 当成普通文件。

  • --follow-symlinks 不把 project root 外部 symlink 目标放入 discovered files。

  • source root 不存在返回 SOURCE_ROOT_NOT_FOUND

  • source 匹配为空时 discover 成功并输出 0 files

scanner 测试:

  • scan 按 source scanner 分发。

  • 未注册 scanner 返回 SCANNER_NOT_REGISTERED

  • 内置 scanner 返回文件数、字节数和行数。

  • scan --json 输出每个 source 的 scanner result 和 totals。

输出测试:

  • 成功 JSON 输出包含 ok: truedata

  • 失败 JSON 输出包含 ok: false 和稳定 error code。

  • 成功 JSON 写 stdout。

  • 失败 JSON 写 stderr。

  • --json 下 unknown command 和 unknown option 不混入人类错误文本。

  • 人类输出不包含 stack trace。

doctor 测试:

  • 配置有效时包含 CONFIG_OK 诊断。

  • source root 缺失时包含 error 诊断,并返回阻断错误。

  • source 使用未注册 scanner 时包含 error 诊断,并返回阻断错误。

  • 空 source 产生 warning,但 doctor 命令成功。

  • doctor --json 成功时 diagnostics 位于 data.diagnostics

  • doctor --json 失败时 diagnostics 位于 error.details.diagnostics

usage error 测试:

  • 缺少 --project 值返回 USAGE_ERROR 和退出码 2

  • 缺少 --source 值返回 USAGE_ERROR 和退出码 2

  • 未知命令或未知 option 返回 USAGE_ERROR 和退出码 2

Creator 契约测试必须覆盖以下行为。

  • Creator development tests 从仓库 templates/ 实例化模板,不从 package-local template source 实例化模板。

  • create-rootward typescript <dir> --cli-name <name> --package-name <name> 创建目标目录。

  • create-rootward rust <dir> --cli-name <name> --crate-name <name> --bin-name <name> 创建目标目录。

  • 非空目标目录返回 TARGET_NOT_EMPTY

  • 目标路径是已存在文件或不能形成稳定目标目录边界时返回 TARGET_INVALID

  • 未知 template 返回 TEMPLATE_NOT_FOUND

  • reserved template 返回 TEMPLATE_NOT_IMPLEMENTED

  • 缺少 --cli-name 返回 USAGE_ERROR

  • 生成物不残留 manifest tokens 中声明的 token。

  • TypeScript 生成物 package.json 的 namebin 与身份一致。

  • Rust 生成物 Cargo.toml 的 package name 和 bin name 与身份一致。

  • 生成物 constants 中的 CLI name 和 config directory name 与身份一致。

  • --json 成功只在 stdout 输出 JSON。

  • --json 失败只在 stderr 输出 JSON,不混入命令解析库错误文本。

  • Package asset tests 构建 Creator package artifact,并验证 packaged template asset 与仓库 templates/ 的 manifest 和 template root 集合一致。

  • Pack smoke tests 检查 npm tarball 携带 packaged template asset,且不携带 package-local template source、临时目录、依赖目录、覆盖率目录或构建缓存。

  • 生成后的 TypeScript 项目通过依赖安装、测试和构建。

  • Rust 模板源通过 cargo fmt --checkcargo clippy --all-targets --all-features — -D warningscargo testcargo build

  • 生成后的 Rust 项目通过 cargo fmt --checkcargo clippy --all-targets --all-features — -D warningscargo testcargo build

测试 fixture 必须位于当前项目或模板目录内部。测试不得跨盘扫描系统目录、用户目录或工作区外路径。临时项目应创建在模板项目内的 temporary/tmp/ 或语言测试框架提供的受控临时目录中。

每种语言可以使用不同测试工具。TypeScript 使用 Vitest,Python 使用 pytest,Rust 使用 assert_cmd/assert_fs,Go 使用标准 testing。Rust 可以在需要逐字符固定 human output 时使用 insta。工具差异不能改变测试覆盖的 Rootward 行为。

测试断言应优先使用公共契约面。JSON 测试断言 okdataerror.codedetails 和 project-root-relative POSIX path;human output 测试只断言关键公共文本和不含 stack trace;内部函数测试只服务无法通过公共命令稳定观察的路径规则、配置校验和错误映射。

测试应证明负路径稳定。生成物错误码 PROJECT_NOT_FOUNDPROJECT_CONFIG_NOT_FOUNDINIT_TARGET_INVALIDCONFIG_PARSE_FAILEDCONFIG_INVALIDSOURCE_NOT_FOUNDSOURCE_ROOT_NOT_FOUNDSCANNER_NOT_REGISTEREDDISCOVERY_FAILEDSCAN_FAILEDINTERNAL_ERROR 的触发条件一旦进入契约,就必须有对应断言或明确的人工构造路径。Creator 错误码 TEMPLATE_NOT_FOUNDTEMPLATE_NOT_IMPLEMENTEDTEMPLATE_INVALIDTARGET_NOT_EMPTYTARGET_INVALIDCOPY_FAILEDREWRITE_FAILEDTOKEN_RESIDUE_FOUNDPOSTCHECK_FAILEDINTERNAL_ERROR 的触发条件一旦进入契约,也必须有对应断言或明确的人工构造路径。没有测试的错误码会退化为文档承诺。

26. 设计点准入规则

Rootward 的每个设计点进入核心契约之前,必须取得判断资格。判断资格由事实、目标、规约、成本和验证共同构成。

新增设计点必须回答以下问题。

  • 它回应哪个项目型 CLI 事实?

  • 它服务哪个消费者动作?

  • 它改变哪个公共契约或内部实现?

  • 它引入哪些实现、测试、维护、学习或兼容成本?

  • 它如何被观察和验证?

  • 它属于 Rootward 本体、语言实现、业务工具扩展、相邻对象,还是未来期权?

不能回答这些问题的设计点,不能进入 Rootward 核心契约。它可以保留为研究材料、业务扩展、语言实现细节、相邻对象或未来期权。

新增配置字段必须说明它改变哪个 discovery、source、scanner 或输出行为。配置字段一旦进入 .foo/config.toml,就成为项目公共契约。配置字段不能仅因某个语言实现方便而进入 Rootward。

新增命令必须说明它服务哪个消费者动作,并且不得破坏基础命令的职责。业务命令可以存在,但基础命令的项目定位、配置读取、输出和错误语义必须保持稳定。

新增 JSON 字段必须说明消费者如何使用它。JSON 字段不是 debug dump。进入 JSON 输出的字段会被脚本、CI 和编辑器集成依赖。

新增错误码必须说明它与现有错误码的区别。错误码过细会增加消费者分支成本;错误码过粗会降低自动化判断能力。

新增依赖必须说明它承担的 Rootward 职责。依赖不能因为“以后可能有用”进入模板。语言生态常见工具只有在服务 Rootward 契约时才进入实现。

全局配置不进入 Rootward 默认契约。项目型工具的默认行为必须随项目提交、审查和复制。用户级凭据或偏好可以由具体业务工具定义自己的输入通道。

插件市场不进入 Rootward 默认契约。scanner registry 已经提供业务处理替换点;动态插件发现、发布、版本协商和安全边界属于更大的相邻对象。

doctor 扩展项必须区分 Rootward 层诊断和业务层诊断。Rootward 层 doctor 检查项目语境;业务工具可以添加业务诊断,但必须标明阻断语义和消费者动作。

设计点准入不是减少能力。它保护 Rootward 的公共契约,使跨语言模板保持同构,使业务工具能在稳定基础上扩展。

准入判断应留下可审查记录。进入核心契约的设计点需要更新对应章节、附录清单和测试契约;只进入某个语言实现的设计点需要更新该语言章节和测试;只属于业务工具的设计点不应改变 Rootward 契约章节。文档位置本身是层位归档的一部分。

27. 模板实例化责任

Rootward 模板是业务工具的起点。Creator 实例化模板时,必须替换工具身份、生成物包元数据和文档表面,同时保留 Rootward 基础契约。手工复制不是 Rootward 默认消费路径。Creator 实例化时读取的模板内容必须来自仓库模板源或 packaged template asset;维护者不得通过 package-local template source 改变 Creator 行为。

必须替换的对象包括:

  • package name、crate name、module path 或 distribution name。

  • CLI name。

  • config directory name。

  • README 中的命令名、配置目录和示例。

  • 测试断言中的命令名和配置目录。

  • 默认 source 配置。

  • scanner registry 中的业务 scanner。

  • 包发布元数据和 license 信息。

可以替换的对象包括:

  • 默认 source 列表。

  • 默认 scanner 实现。

  • 业务命令集合。

  • 人类输出中的业务说明。

  • README 的业务教程部分。

不应改变的对象包括:

  • init 创建隐藏目录、config、cache 和 state 的结构。

  • 显式项目根和 CWD 向上发现语义。

  • .foo/config.toml 作为唯一默认项目配置来源的规则。

  • [discovery] 字段语义。

  • source 字段语义。

  • project-root-relative POSIX path 投影。

  • JSON envelope。

  • Rootward 基础错误码和退出码。

  • scanner 不重新解释配置和 glob 的边界。

业务工具可以新增配置字段,但新增字段属于业务工具契约。新增字段不得改变 Rootward 基础字段语义。业务工具可以新增 error code,但不得改变 Rootward 基础错误码的触发条件。

业务工具可以替换默认 source。替换 source 不改变 source 模型。id 仍是稳定标识,root 仍相对 project root,include/exclude 仍相对 source root,scanner 仍指向 registry key。

业务工具可以扩展 doctor。扩展诊断项必须明确 level、code、message 和阻断语义。业务诊断不能让 Rootward 层诊断消失。

Creator 实例化模板后的第一组测试应先证明 Rootward 基础契约仍成立,再证明业务能力成立。业务测试不能替代 Rootward 契约测试。

生成后的文档也属于公共契约。README、help text、错误 message、默认配置注释和示例命令必须使用替换后的工具身份。生成物不得残留 manifest tokens 中声明的 identity token。残留 token 表示 Creator 替换失败,必须返回 TOKEN_RESIDUE_FOUND

28. 相邻对象与未来期权

Rootward 的边界通过正面定义成立,也通过相邻对象归档保持稳定。相邻对象不是错误对象,但它们不属于 Rootward 核心契约。

通用 CLI framework 是相邻对象。Rootward 使用语言生态中的 CLI 库,但不提供框架级插件、生命周期、命令发现或扩展协议。

上层软件项目 scaffold 是相邻对象。Rootward 的 init 建立工具项目语境,不创建应用项目、库项目、包管理器工程或语言工作区。

业务 parser 是相邻对象。Rootward 默认 scanner 只证明 registry 分发结构。Markdown AST、Python AST、Rust crate metadata、Go package graph 和任意领域 parser 属于业务工具。

插件市场是相邻对象。Rootward 的 scanner registry 是本地实现接入点,不承诺动态插件加载、远程插件搜索、版本协商、沙箱或信任模型。

全局配置系统是相邻对象。Rootward 默认配置随项目走。用户 home 配置、环境变量优先级、组织级配置和远程配置需要由具体业务工具另行定义。

图形界面和语言服务器是相邻对象。它们可以消费 Rootward JSON 输出和错误码,但不进入 Rootward 模板核心。

包发布平台是相邻对象。npm、PyPI、crates.io 和 Go module distribution 各自有发布规则。Rootward 只规定模板内部的项目型 CLI 契约。

未来期权必须保持期权身份。某个能力可能有价值,不等于它已经进入当前公共契约。保留期权的方式包括清晰模块边界、可测试核心、稳定 JSON、scanner registry 和明确的配置模型;过早承诺的方式包括提前发布插件市场、多配置来源、复杂缓存协议或语言服务器接口。

相邻对象进入 Rootward 之前,必须通过 设计点准入规则。进入失败的对象不应以“后续支持”“第一版先不做”“默认可以扩展”等模糊表达留在核心定义层。

相邻对象可以被引用,但不能替代 Rootward 定义。文档可以说明 GUI 消费 JSON、语言服务器消费 diagnostics、插件市场复用 scanner registry;这些说明必须保持消费者关系,不得把相邻对象写成 Rootward 的当前构成性条件。

Appendix A: Rootward Contract 清单

生成物符合 Rootward 语言模板契约时,应满足以下清单。

  • 提供稳定 CLI name。

  • 提供稳定 config directory name。

  • init 创建 .foo/config.toml.foo/cache/.gitignore.foo/state/.gitignore

  • 项目命令支持 --project <path>

  • --project 时从 CWD 向上寻找 .foo/config.toml

  • 配置使用 TOML。

  • 配置包含 [discovery] 表和 sources 表数组。

  • source id 唯一,并符合 CLI 标识符规则。

  • source root 相对 project root,且不能越界。

  • include/exclude 相对 source root。

  • 文件发现默认尊重 project root 内部 gitignore 规则。

  • 文件发现默认不跟随 symlink。

  • 文件发现默认不包含 hidden path。

  • 发现结果使用 project-root-relative POSIX path。

  • scanner registry 按 source scanner key 分发。

  • scanner 不重新解释项目发现和配置。

  • 提供 statusdiscoverscanconfig printdoctor

  • status 投影项目语境摘要和 source root status。

  • scan 投影 scanner results 和 totals。

  • JSON success 使用 { "ok": true, "data": …​ }

  • JSON failure 使用 { "ok": false, "error": …​ }

  • 错误 code 稳定。

  • 退出码稳定。

  • 人类错误输出不暴露 stack trace。

  • 测试覆盖 测试契约 中的行为。

该清单只判断 Rootward 基础契约。业务工具在该清单之外增加的 parser、命令、配置字段和诊断项,应由业务工具自己的契约和测试覆盖。

Creator 符合 Rootward 创建器契约时,应满足以下清单。

  • npm package name 是 create-rootward

  • bin name 是 create-rootward

  • repository source layout 只包含一份仓库模板源:templates/

  • Creator package source tree 不包含 packages/create-rootward/templates/ 维护路径。

  • Creator build 生成 packaged template asset。

  • npm package 携带 Creator runtime 和 packaged template asset。

  • packaged template asset 与同版本仓库模板源一致。

  • 命令表面是 create-rootward <template> <target-dir> --cli-name <name> [identity options] [--json]

  • Creator 不读取 TTY,不显示 prompt,不提供交互式选择。

  • template id 集合包含 typescriptpythonrustgo

  • reserved 模板返回 TEMPLATE_NOT_IMPLEMENTED

  • 目标目录是 Creator 的写入边界。

  • 非空目标目录返回 TARGET_NOT_EMPTY

  • 生成物身份通过 manifest identity slots 和 manifest tokens 替换。

  • 生成物不得残留 manifest tokens 中声明的 token。

  • Creator JSON success 使用 { "ok": true, "data": …​ }

  • Creator JSON failure 使用 { "ok": false, "error": …​ }

  • Creator 错误码和退出码稳定。

  • Creator 测试覆盖 测试契约 中的创建器行为。

Appendix B: 默认配置规范

Rootward 模板默认配置如下。

[discovery]
respect_gitignore = true
follow_symlinks = false
include_hidden = false

[[sources]]
id = "docs"
root = "docs"
include = ["**/*.{md,mdx,rst,adoc,txt}"]
exclude = []
scanner = "text"

[[sources]]
id = "python"
root = "src"
include = ["**/*.py"]
exclude = ["**/__pycache__/**"]
scanner = "python"

该配置用于初始化可运行项目语境。docspython source 是模板默认输入,不是 Rootward 支持文件类型的上限。

业务工具由 Creator 实例化模板后可以替换默认 source。替换 source 不改变 source 模型。

字段语义见 配置模型Source 声明模型。文件发现执行语义见 文件发现语义

Appendix C: 错误码表

错误码 退出码 消费者解释

USAGE_ERROR

2

命令表面错误。用户或调用方需要修正参数、命令或 option。

PROJECT_NOT_FOUND

3

当前目录向上没有 Rootward 配置。用户需要进入已初始化项目,或运行 init,或使用 --project

PROJECT_CONFIG_NOT_FOUND

3

显式项目根缺少 Rootward 配置。调用方指定了错误项目根,或目标项目尚未初始化。

INIT_TARGET_INVALID

3

init 目标路径不存在或不是目录。用户需要选择已经存在的目录作为初始化目标。

PROJECT_ALREADY_INITIALIZED

3

目标项目已有配置。用户需要停止重复初始化,或使用 --force

CONFIG_READ_FAILED

4

配置路径存在但不可读取。用户需要检查权限或文件状态。

CONFIG_PARSE_FAILED

4

配置不是合法 TOML。用户需要修正 TOML 语法。

CONFIG_INVALID

4

配置结构或语义不符合 Rootward schema。用户需要修正字段、source id、root 或 scanner 声明。

SOURCE_NOT_FOUND

4

命令选择了不存在的 source。用户需要检查 --source 值或配置。

SOURCE_ROOT_NOT_FOUND

5

source root 不存在或不是目录。用户需要创建目录或修正配置。

SCANNER_NOT_REGISTERED

5

source 指向未注册 scanner。维护者需要注册 scanner 或修正配置。

DISCOVERY_FAILED

5

文件发现执行失败。用户或维护者需要检查 glob、gitignore 读取和路径状态。

SCAN_FAILED

5

scanner 执行失败。维护者需要检查 scanner 实现或输入文件。

INTERNAL_ERROR

1

未归档运行时失败。维护者需要把该路径归档为稳定错误码或修复实现缺陷。

错误对象结构、退出码分层和诊断 code 边界见 错误码与退出码

Appendix D: 语言依赖表

Rootward Creator 层依赖如下。

层位 依赖 Rootward 职责

Creator

Commander

创建器命令表面。

Creator

Zod

manifest、Creator 输出和 Creator 错误对象的运行时边界。

Creator

tsdown

npm 创建器包的 ESM 构建和类型声明输出。

Creator

Vitest

创建器契约测试。

Rootward 生成物层依赖如下。

语言 依赖 Rootward 职责

TypeScript

Commander

命令表面。

TypeScript

Zod

运行时 schema 校验。

TypeScript

smol-toml

TOML 解析和序列化。

TypeScript

globby

glob 展开、hidden 和 symlink-aware 文件发现。

TypeScript

ignore

项目根 .gitignore pattern matching。

Python

uv

项目管理、依赖同步和命令运行。

Python

Typer

命令表面。

Python

Pydantic v2

运行时配置边界。

Python

tomlkit

TOML 读取和写入。

Python

wcmatch

globstar 和 brace pattern。

Python

pathspec

gitignore-style pattern matching。

Rust

clap derive

命令表面。

Rust

serde / serde_json

配置、诊断、JSON envelope 和结构化输出。

Rust

toml

TOML 解析和序列化。

Rust

ignore

递归遍历、hidden 过滤、symlink 遍历控制和 project root 内部 gitignore-aware 文件发现。

Rust

globset

source include 和 exclude pattern matching。

Rust

thiserror

内部错误类型到稳定错误码的映射。

Rust

camino

UTF-8 路径投影。

Go

Cobra

命令表面。

Go

go-toml/v2

TOML 解析和序列化。

Go

gobwas/glob

globstar 和 brace alternatives。

Go

go-git gitignore

gitignore pattern matching。

Go

go-billy/osfs

go-git ignore 读取所需的文件系统适配。

依赖表只列 Rootward Creator 层和语言模板生成物层依赖。业务工具可以增加业务 parser、远程客户端、数据库驱动或 UI 依赖;新增依赖必须通过 设计点准入规则 判断其层位。

Appendix E: 实现者检查表

实现某个 Rootward 语言模板时,按以下顺序检查。

  1. 工具身份已替换。

  2. 包元数据已替换。

  3. init 创建结构正确。

  4. init 目标错误映射到 INIT_TARGET_INVALID

  5. 默认配置与 默认配置规范 一致,或业务模板明确替换默认 source。

  6. 项目根发现只包含显式项目根和 CWD 向上发现。

  7. 配置解析错误映射到 CONFIG_PARSE_FAILED

  8. 配置 schema 和语义错误映射到 CONFIG_INVALID

  9. source root 校验禁止绝对路径、越界路径和反斜杠路径分隔符。

  10. include/exclude pattern 校验禁止反斜杠路径分隔符。

  11. 文件发现输出 project-root-relative POSIX path。

  12. project root 内部 gitignore 规则默认生效。

  13. project root 外部 gitignore 规则不影响发现结果。

  14. hidden path 默认排除。

  15. symlink 默认不跟随。

  16. symlink 跟随模式不把 project root 外部目标放入 discovered files。

  17. discover --list 能列出文件。

  18. scan 使用 scanner registry。

  19. 未注册 scanner 返回 SCANNER_NOT_REGISTERED

  20. doctor 保留 diagnostics。

  21. JSON success 和 failure envelope 稳定。

  22. 失败 JSON 写 stderr。

  23. --json 下 usage error 不混入命令解析库默认错误文本。

  24. 人类错误不包含 stack trace。

  25. 测试覆盖 测试契约

  26. implemented 模板源能通过该语言的标准构建、格式、检查和测试命令。

  27. Creator 实例化后的 generated CLI 能通过该语言的标准构建和测试命令。

  28. 文档说明语言依赖的 Rootward 职责。

检查失败项不得以“后续补齐”归入完成声明。未满足 Rootward 基础契约的语言实现只能被标记为不完整实现、实验实现或业务草稿,不能被标记为 Rootward 模板完成。

Appendix F: 术语表

术语 含义

Rootward 契约

跨语言项目型 CLI 模板共同承载的公共规则集合。该契约定义项目语境、配置、文件发现、scanner 分发、输出投影、错误语义和测试标准。

Rootward Creator CLI

通过 npm 分发的非交互模板实例化命令。它读取模板 manifest,把 language template 写入目标目录,并提供独立的 Creator 输出和错误语义。

language template

Creator 可以实例化的语言模板源。每个 language template 包含 manifest 和 template root。

generated CLI

Creator 实例化 language template 后得到的项目型 CLI。generated CLI 服从生成物契约,不依赖 Rootward 仓库内部路径。

manifest

描述 language template 身份、状态、template root、identity slots、tokens、exclude、postcheck 和 next commands 的 JSON 文件。

identity token

模板源中由 Creator 替换的 manifest 显式声明占位符,例如 TypeScript 的 ROOTWARD_CLI_NAME 或 Rust 的 rootward-token-cli-name。生成物不得残留 manifest tokens 中声明的 identity token。

项目型 CLI

在文件系统项目中运行,并以项目根、项目配置和项目目录树为输入语境的命令行人工制品。

project root

Rootward 命令解释配置路径、source root、公共路径和 scanner 输入时使用的项目路径基准。

ProjectContext

项目根发现产生的结构化上下文。它包含 project root、config dir、config path、discovery mode 和 start directory。

source

进入工具视野的一组文件及其 scanner 分发关系。source 由 id、root、include、exclude 和 scanner 字段定义。

discovery

把项目语境和规范化配置转换为按 source 分组的 discovered files 的过程。

scanner registry

scanner key 到 scanner implementation 的映射。它是业务处理接入点,不承担项目发现和配置解释。

projection

内部状态、事件或结果面向消费者的可见表达。Rootward 的投影包括 human output、JSON output、discover 文件列表、doctor diagnostics 和 error object。

public contract

消费者可以依赖的命令、配置字段、输出形状、错误码、退出码和路径语义。

project-root-relative POSIX path

相对 project root 的 / 分隔路径字符串。Rootward 默认公共投影使用该路径形状,不暴露绝对路径。

discovery overrides

本次命令执行中覆盖 [discovery] 配置的命令行选项,包括 --no-respect-gitignore--follow-symlinks--include-hidden

参考资料