load-plain-reference

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

PLAIN_REFERENCE.md

PLAIN_REFERENCE.md

Project Overview

项目概述

This repository is a workspace for writing and managing ***plain (codeplain) specifications. ***plain is a specification-driven language powered by AI that generates production-ready code from
.plain
spec files.
The
.plain
files in this repository are the source of truth. They describe what the software should do, how it should be built, and how it should be tested. The generated code is a read-only artifact produced by the renderer.
本仓库是用于编写和管理*****plain**(codeplain)规范的工作区。***plain是一种由AI驱动的规范语言,可通过
.plain
规范文件生成可直接用于生产的代码。
本仓库中的
.plain
文件是事实来源,它们描述了软件应该实现的功能、构建方式以及测试方法。生成的代码是渲染器产出的只读产物。

***plain Language Reference

***plain语言参考

***plain is a specification language designed for writing software requirements in a clear, structured format. It generates production-ready code from
.plain
spec files using AI. Full documentation: https://plainlang.org/docs/language-guide/
***plain是一种规范语言,旨在以清晰、结构化的格式编写软件需求。它借助AI从
.plain
规范文件生成可用于生产的代码。完整文档:https://plainlang.org/docs/language-guide/

.plain File Structure

.plain文件结构

A
.plain
file consists of an optional YAML frontmatter section followed by standardized sections marked with
***section name***
headers. There are four types of specification sections:
  • ***definitions***
    — declares concepts used throughout the specification
  • ***implementation reqs***
    — non-functional requirements about how the software should be built
  • ***test reqs***
    — requirements for conformance testing
  • ***functional specs***
    — describes what the software should do
Every plain source file requires at least one functional spec and an associated implementation req. Functional specs must reside in leaf sections while other specifications can be placed also in non-leaf sections. Specifications in non-leaf sections apply to all of their subsections.
一个
.plain
文件包含一个可选的YAML前置元数据部分,其后是带有
***section name***
标题的标准化章节。规范章节分为四种类型:
  • ***definitions***
    — 声明整个规范中使用的概念
  • ***implementation reqs***
    — 关于软件构建方式的非功能性需求
  • ***test reqs***
    — 一致性测试的需求
  • ***functional specs***
    — 描述软件应实现的功能
每个plain源文件至少需要一个功能规范和对应的实现要求。功能规范必须位于叶子章节中,而其他规范也可放置在非叶子章节中。非叶子章节中的规范适用于其所有子章节。

Concept Notation

概念表示法

Concepts are the building blocks of ***plain specifications. They are written between colons:
:ConceptName:
. Valid characters include letters, digits, plus, minus, dot, and underscore.
Concepts must be defined in
***definitions***
before being referenced in other sections. Concept names must be globally unique across the specification and its imports. Concept references must not form cycles — if concept A references concept B, then concept B must not reference concept A (directly or indirectly).
Example:
plain
***definitions***
- :User: is the user of :App:
- :Task: describes an activity that needs to be done by :User:. :Task: has:
  - Name - a short description (required)
  - Notes - additional details (optional)
  - Due Date - completion deadline (optional)
- :TaskList: is a list of :Task: items.
  - Initially :TaskList: should be empty.
概念是***plain规范的构建块,它们写在冒号之间:
:ConceptName:
。有效字符包括字母、数字、加号、减号、点和下划线。
概念必须在
***definitions***
中定义后,才能在其他章节中引用。概念名称在整个规范及其导入内容中必须全局唯一。概念引用不能形成循环——如果概念A引用概念B,那么概念B不能直接或间接引用概念A。
示例:
plain
***definitions***
- :User: is the user of :App:
- :Task: describes an activity that needs to be done by :User:. :Task: has:
  - Name - a short description (required)
  - Notes - additional details (optional)
  - Due Date - completion deadline (optional)
- :TaskList: is a list of :Task: items.
  - Initially :TaskList: should be empty.

Predefined Concepts

预定义概念

***plain provides predefined concepts available in all specifications without needing to be defined:
ConceptMeaning
:plainDefinitions:
Content of the
***definitions***
section
:plainImplementationReqs:
Content of the
***implementation reqs***
section
:plainFunctionality:
Content of the
***functional specs***
section
:plainTestReqs:
Content of the
***test reqs***
section
:Implementation:
The system implementing
:plainFunctionality:
:plainImplementationCode:
The generated implementation code
:UnitTests:
Auto-generated unit tests for individual functionalities - their usage goes in in the implementation reqs section
:ConformanceTests:
Auto-generated tests that verify implementation conforms to specs
:AcceptanceTest:
/
:AcceptanceTests:
Tests that validate specific aspects of the implementation
***plain提供了预定义概念,无需定义即可在所有规范中使用:
概念含义
:plainDefinitions:
***definitions***
章节的内容
:plainImplementationReqs:
***implementation reqs***
章节的内容
:plainFunctionality:
***functional specs***
章节的内容
:plainTestReqs:
***test reqs***
章节的内容
:Implementation:
实现
:plainFunctionality:
的系统
:plainImplementationCode:
生成的实现代码
:UnitTests:
针对单个功能自动生成的单元测试——其使用方式需在implementation reqs章节中说明
:ConformanceTests:
验证实现是否符合规范的自动生成测试
:AcceptanceTest:
/
:AcceptanceTests:
验证实现特定方面的测试

Definitions Section

定义章节

Declares concepts used throughout the specification. A concept must be defined before it can be referenced in any section. The definition can come from the module's own
***definitions***
section, from an
import
ed module's definitions, or from a
require
d module's
exported_concepts
. Attributes and constraints can be nested as sub-bullets.
plain
***definitions***
- :ConceptName: is a description of the concept.
  - Additional details or attributes can be nested
  - Multiple attributes can be listed
声明整个规范中使用的概念。概念必须先定义,才能在任何章节中引用。定义可来自模块自身的
***definitions***
章节、
import
模块的定义,或
require
模块的
exported_concepts
。属性和约束可作为子项目嵌套。
plain
***definitions***
- :ConceptName: is a description of the concept.
  - Additional details or attributes can be nested
  - Multiple attributes can be listed

Implementation Reqs Section

实现要求章节

A free-form section for any instructions that steer code generation. Common uses include technology choices, architectural constraints, coding standards, and naming conventions, but it can also contain detailed implementation guidance — data formats, error handling strategies, algorithm descriptions, or any other context the renderer needs to produce correct code. These describe HOW to build the software, not WHAT it should do.
plain
***implementation reqs***
- :Implementation: should be in Python.
- :MainExecutableFile: of :App: should be called "hello_world.py".
- :Implementation: should include :Unittests: using Unittest framework!
这是一个自由格式章节,用于提供指导代码生成的任何指令。常见用途包括技术选择、架构约束、编码标准和命名约定,但也可包含详细的实现指导——数据格式、错误处理策略、算法描述或渲染器生成正确代码所需的任何其他上下文。这些内容描述的是如何构建软件,而非软件应实现什么功能
plain
***implementation reqs***
- :Implementation: should be in Python.
- :MainExecutableFile: of :App: should be called "hello_world.py".
- :Implementation: should include :Unittests: using Unittest framework!

Test Reqs Section

测试要求章节

Specifies requirements for conformance testing — test frameworks, execution methods, and testing constraints. Only used when writing and fixing conformance tests (not unit tests).
plain
***test reqs***
- :ConformanceTests: of :App: should be implemented in Python using Unittest framework.
- :ConformanceTests: will be run using "python -m unittest discover" command.
- :ConformanceTests: must be implemented and executed - do not use unittest.skip().
指定一致性测试的需求——测试框架、执行方法和测试约束。仅在编写和修复一致性测试时使用(不适用于单元测试)。
plain
***test reqs***
- :ConformanceTests: of :App: should be implemented in Python using Unittest framework.
- :ConformanceTests: will be run using "python -m unittest discover" command.
- :ConformanceTests: must be implemented and executed - do not use unittest.skip().

Functional Specs Section

功能规范章节

Describes what the software should do. Each bullet point is a single piece of functionality that will be implemented. Functional specs are rendered incrementally one by one — earlier specs cannot reference later specs.
Each functional spec must be limited in complexity. If a spec is too complex, the renderer responds with "Functional spec too complex!" and it must be broken down into smaller specs.
Functional specs are in chronological order — earlier specs are rendered before later ones. Functional specs defined in
requires
modules are considered previous functional specs relative to the current module's specs. This ordering matters for incremental rendering and for detecting conflicts between specs.
The renderer has no knowledge of future functional specs. When a functional spec is being implemented, only the previous functional specs (those already rendered) are in the renderer's context. Specs that come later in the list are invisible to the renderer at that point. This means each spec is implemented without any awareness of what will come next.
plain
***functional specs***
- Implement the entry point for :App:.
- Show :TaskList:.
- :User: should be able to add :Task:. Only valid :Task: items can be added.
- :User: should be able to delete :Task:.
Each functional spec must be unambiguous. If a single line is not enough to fully disambiguate the behavior, use nested sub-bullets to add detail. Nested lines clarify the parent spec — they do not introduce separate functionality. Even with nested detail, the spec must still respect the complexity limit.
plain
***functional specs***
- :User: should be able to send a :Message: to a :Conversation:.
  - A :Message: must have non-empty content.
  - The :Message: is appended to the end of the :Conversation:.
  - All :Participant: members of the :Conversation: can see the new :Message:.
描述软件应实现的功能。每个项目是一项将被实现的独立功能。功能规范会逐个增量渲染——早期规范不能引用后期规范。
每个功能规范的复杂度必须受限。如果规范过于复杂,渲染器会返回“Functional spec too complex!”,此时必须将其拆分为更小的规范。
功能规范按时间顺序排列——早期规范先于后期规范渲染。
requires
模块中定义的功能规范相对于当前模块的规范被视为先前功能规范。这种顺序对增量渲染和检测规范之间的冲突至关重要。
渲染器不知道未来的功能规范。当某个功能规范正在被实现时,只有先前的功能规范(已渲染的规范)在渲染器的上下文中。列表中后续的规范在此时对渲染器不可见。这意味着每个规范的实现都不会知晓后续内容。
plain
***functional specs***
- Implement the entry point for :App:.
- Show :TaskList:.
- :User: should be able to add :Task:. Only valid :Task: items can be added.
- :User: should be able to delete :Task:.
每个功能规范必须明确无歧义。如果单行内容不足以完全明确行为,可使用嵌套子项目添加细节。嵌套行用于澄清父规范——它们不会引入独立功能。即使有嵌套细节,规范仍需遵守复杂度限制。
plain
***functional specs***
- :User: should be able to send a :Message: to a :Conversation:.
  - A :Message: must have non-empty content.
  - The :Message: is appended to the end of the :Conversation:.
  - All :Participant: members of the :Conversation: can see the new :Message:.

Acceptance Tests

验收测试

Nested under individual functional specs to specify how to verify correct implementation. They extend conformance tests and are implemented according to the
***test reqs***
specification.
plain
***functional specs***
- Display "hello, world"

    ***acceptance tests***
    - :App: should exit with status code 0 indicating successful execution.
    - :App: should complete execution in under 1 second.
嵌套在单个功能规范下,用于指定如何验证实现的正确性。它们扩展了一致性测试,并根据
***test reqs***
规范实现。
plain
***functional specs***
- Display "hello, world"

    ***acceptance tests***
    - :App: should exit with status code 0 indicating successful execution.
    - :App: should complete execution in under 1 second.

YAML Frontmatter

YAML前置元数据

The frontmatter is enclosed between
---
markers and supports:
  • import
    — includes definitions, implementation reqs, and test reqs from templates. Imported modules must not contain functional specs. The default import directory is
    template/
    — the
    template/
    prefix is not needed (e.g.,
    airplain
    resolves to
    template/airplain.plain
    ).
  • requires
    — specifies dependencies on other root-level modules that must be built first. Unlike
    import
    , required modules can contain functional specs and represent complete software modules. Requires paths point to root-level modules (e.g.,
    auth
    ,
    messaging
    ).
  • description
    — optional description of the specification.
  • required_concepts
    — concepts that must be defined by any module that imports this spec.
  • exported_concepts
    — concepts made available to modules that
    require
    this one. Exports are not transitive. A concept exported from module
    A
    is visible only to the modules that
    requires: A
    directly. If module
    B
    requires: A
    and module
    C
    requires: B
    , the concepts
    A
    exports are not automatically visible to
    C
    — only the concepts
    B
    itself re-exports are. To pass a concept further down the chain, the intermediate module must re-declare it in its own
    exported_concepts
    list (and define / forward it in its own
    ***definitions***
    as needed). This applies at every hop: each module is responsible for explicitly exporting whatever it wants its own
    requires
    -ers to see.
    When a concept needs to live in more than just the immediately next module, don't propagate it by chained re-exports — that turns every intermediate module into bookkeeping for a concept it doesn't itself use, and any missing hop silently drops the concept from downstream modules. Use a shared import module instead:
    1. Create an import module under
      template/
      (e.g.
      template/shared_domain.plain
      ) and put the concept's
      ***definitions***
      entry there. Import modules carry definitions, implementation reqs, and test reqs only — never functional specs.
    2. In every module that needs the concept (no matter how deep in the
      requires
      chain), add the import module to its frontmatter
      import:
      list. The concept is then visible in that module directly, without any
      exported_concepts
      plumbing.
    3. None of the
      requires
      -chained modules need to re-export the concept anymore — each one imports what it actually uses.
    Use the
    create-import-module
    skill to scaffold this, and
    consolidate-concepts
    when you discover the same concept has been scattered across several modules and needs to be pulled back into a single shared import.
    Rule of thumb: if a concept crosses one hop,
    exported_concepts
    is fine. If it crosses two or more hops, or is needed by sibling modules at the same depth, lift it into an import module.
前置元数据包裹在
---
标记之间,支持以下内容:
  • import
    — 从模板中导入定义、实现要求和测试要求。导入的模块不能包含功能规范。默认导入目录为
    template/
    ——无需添加
    template/
    前缀(例如,
    airplain
    会解析为
    template/airplain.plain
    )。
  • requires
    — 指定对其他根级模块的依赖,这些模块必须先构建完成。与
    import
    不同,被依赖的模块可以包含功能规范,代表完整的软件模块。依赖路径指向根级模块(例如,
    auth
    messaging
    )。
  • description
    — 规范的可选描述。
  • required_concepts
    — 任何导入此规范的模块必须定义的概念。
  • exported_concepts
    — 提供给
    require
    此模块的模块使用的概念。导出不具有传递性。模块A导出的概念仅对直接
    requires: A
    的模块可见。如果模块B
    requires: A
    且模块C
    requires: B
    ,则A导出的概念不会自动对C可见——只有B自身重新导出的概念才会对C可见。要将概念沿链进一步传递,中间模块必须在其自身的
    exported_concepts
    列表中重新声明该概念(并根据需要在其自身的
    ***definitions***
    中定义或转发)。这适用于每一个环节:每个模块负责显式导出希望其依赖者可见的内容。
    当一个概念需要在多个模块中使用,而不仅仅是紧邻的下一个模块时,不要通过链式重新导出传播它——这会让每个中间模块都成为它本身并不使用的概念的簿记员,任何缺失的环节都会导致概念在下游模块中无声消失。改用共享导入模块
    1. template/
      下创建一个导入模块(例如
      template/shared_domain.plain
      ),并将概念的
      ***definitions***
      条目放在其中。导入模块仅包含定义、实现要求和测试要求——绝不包含功能规范。
    2. 在每个需要该概念的模块中(无论在
      requires
      链中处于多深的位置),将导入模块添加到其前置元数据的
      import:
      列表中。这样该概念就可直接在该模块中使用,无需任何
      exported_concepts
      的复杂设置。
    3. requires
      链中的模块无需再重新导出该概念——每个模块仅导入实际需要的内容。
    使用
    create-import-module
    技能来搭建此结构,当发现同一概念分散在多个模块中且需要整合到单个共享导入模块时,使用
    consolidate-concepts
    技能。
    经验法则:如果概念跨越一个环节,使用
    exported_concepts
    即可。如果跨越两个或更多环节,或被同一层级的兄弟模块需要,则将其提升到导入模块中。

Linked Resources

链接资源

Specifications can reference external files using markdown link syntax. The linked resource is passed along with the spec to the renderer. File paths are resolved relative to the
.plain
file location. Only files in the same folder (and subfolders) are supported; no external URLs.
plain
- :User: should be able to add :Task:. The details of the user interface
  are provided in the file [task_modal_specification.yaml](task_modal_specification.yaml).
Structured protocol artifacts must be linked resources, never transcribed into prose. Anything that has a formal machine-readable shape — JSON Schema, OpenAPI / Swagger specs, GraphQL SDL, Protobuf / gRPC
.proto
files, Avro / Thrift schemas, XML XSDs, AsyncAPI specs, JSON-RPC method definitions, wire-protocol descriptions, payload examples, etc. — belongs in a file under
resources/
(or a subfolder of the
.plain
file's directory), and the spec refers to it via a markdown link. Do not restate the schema's fields, types, or constraints inline in functional specs, implementation reqs, or definitions. Reasons:
  • One source of truth. A re-typed copy of a schema in prose drifts as soon as the real schema evolves. Both the renderer and downstream tooling (codegen, validators, API clients, IDE plugins) need the same canonical file.
  • Machine-readable. The renderer and the generated code can both consume the file directly — a JSON Schema linked from a spec can drive request/response validation in the implementation and assertions in conformance tests, with no prose-to-code translation step in between.
  • Reviewable as a diff. Schema changes show up cleanly in PRs as edits to a single file, instead of as a scatter of edits across multiple
    .plain
    sections.
The accompanying spec line should describe the role of the artifact ("the request body conforms to ...", "the public API surface is defined in ...") rather than its contents. If the artifact is referenced from more than one place, follow the single-reference + concept rule below.
plain
***definitions***

- :TaskCreateRequest: is the JSON payload for creating a task, defined by
  [resources/task_create_request.schema.json](resources/task_create_request.schema.json).
- :TasksAPI: is the public HTTP surface for tasks, defined by
  [resources/tasks_openapi.yaml](resources/tasks_openapi.yaml).

***functional specs***

- :User: should be able to add :Task: by POSTing :TaskCreateRequest: to the
  `POST /tasks` endpoint of :TasksAPI:. The endpoint responds per :TasksAPI:.
Each linked resource must be referenced from exactly one place in the project — a single functional spec, implementation requirement, or
***definitions***
entry. Linking the same file from two functional specs (or from a functional spec and a requirement, etc.) creates two independent sources of truth: any later edit to one site silently diverges from the other, and the renderer has no way to know which one is canonical.
If a resource needs to inform multiple parts of the project, don't repeat the link — instead, attach the resource to a concept and let the rest of the project refer to that concept:
  1. Define a concept under
    ***definitions***
    whose explanation links the resource exactly once.
  2. Use the concept token (
    :ConceptName:
    ) wherever the resource was previously inlined.
For example, instead of linking
task_modal_specification.yaml
from two different functional specs:
plain
***definitions***

- :TaskModalSpec: is the user-interface contract for the task modal,
  fully described in [task_modal_specification.yaml](task_modal_specification.yaml).

***functional specs***

- :User: should be able to add :Task: using :TaskModalSpec:.
- :User: should be able to edit :Task: using :TaskModalSpec:.
This keeps the resource link in one place, makes the dependency explicit through the concept token, and means a change to the file only ever needs to be reconciled against one spec site. If you find yourself about to paste the same
[name](path)
link a second time, stop — create the concept first.
规范可使用Markdown链接语法引用外部文件。链接的资源会随规范一起传递给渲染器。文件路径相对于
.plain
文件的位置解析。仅支持同一文件夹(及子文件夹)中的文件;不支持外部URL。
plain
- :User: should be able to add :Task:. The details of the user interface
  are provided in the file [task_modal_specification.yaml](task_modal_specification.yaml).
结构化协议工件必须作为链接资源,绝不能转录为文字。任何具有正式机器可读格式的内容——JSON Schema、OpenAPI/Swagger规范、GraphQL SDL、Protobuf/gRPC
.proto
文件、Avro/Thrift模式、XML XSD、AsyncAPI规范、JSON-RPC方法定义、有线协议描述、负载示例等——都应放在
resources/
(或
.plain
文件目录的子文件夹)下的文件中,规范通过Markdown链接引用它。不要在功能规范、实现要求或定义中逐行重述模式的字段、类型或约束。原因如下:
  • 单一事实来源。文字中重新输入的模式副本会在实际模式演变时出现偏差。渲染器和下游工具(代码生成器、验证器、API客户端、IDE插件)都需要相同的规范文件。
  • 机器可读。渲染器和生成的代码都可直接使用该文件——规范中链接的JSON Schema可驱动实现中的请求/响应验证以及一致性测试中的断言,无需中间的文字到代码转换步骤。
  • 可通过差异对比审阅。模式更改在PR中会清晰地显示为单个文件的编辑,而非分散在多个
    .plain
    章节中的编辑。
规范中对应的行应描述工件的作用(“请求体符合……”、“公共API表面由……定义”),而非其内容。如果工件被多个地方引用,请遵循下面的单一引用+概念规则。
plain
***definitions***

- :TaskCreateRequest: is the JSON payload for creating a task, defined by
  [resources/task_create_request.schema.json](resources/task_create_request.schema.json).
- :TasksAPI: is the public HTTP surface for tasks, defined by
  [resources/tasks_openapi.yaml](resources/tasks_openapi.yaml).

***functional specs***

- :User: should be able to add :Task: by POSTing :TaskCreateRequest: to the
  `POST /tasks` endpoint of :TasksAPI:. The endpoint responds per :TasksAPI:.
每个链接资源必须在项目中被引用且仅被引用一次——单个功能规范、实现要求或
***definitions***
条目。从两个功能规范(或一个功能规范和一个要求等)链接同一文件会创建两个独立的事实来源:对其中一个位置的后续编辑会与另一个位置无声地产生差异,渲染器无法知晓哪个是规范的版本。
如果资源需要为项目的多个部分提供信息,不要重复链接——而是将资源附加到一个概念上,让项目的其他部分引用该概念:
  1. ***definitions***
    下定义一个概念,其解释中仅链接一次资源。
  2. 在之前内联资源的所有位置使用概念标记(
    :ConceptName:
    )。
例如,不要从两个不同的功能规范链接
task_modal_specification.yaml
plain
***definitions***

- :TaskModalSpec: is the user-interface contract for the task modal,
  fully described in [task_modal_specification.yaml](task_modal_specification.yaml).

***functional specs***

- :User: should be able to add :Task: using :TaskModalSpec:.
- :User: should be able to edit :Task: using :TaskModalSpec:.
这样可将资源链接保持在一个位置,通过概念标记明确依赖关系,并且文件的更改只需与一个规范位置协调。如果发现自己要第二次粘贴相同的
[name](path)
链接,请停止——先创建概念。

Template System

模板系统

***plain supports template inclusion using
{% include %}
syntax:
plain
{% include "python-console-app-template.plain", main_executable_file_name: "my_app.py" %}
Parameters are passed as key-value pairs. Inside the template, they are accessed using variable syntax (
{{ variable_name }}
). Only variables are supported — conditionals, loops, and other Liquid features are not available.
***plain支持使用
{% include %}
语法包含模板:
plain
{% include "python-console-app-template.plain", main_executable_file_name: "my_app.py" %}
参数以键值对形式传递。在模板内部,使用变量语法(
{{ variable_name }}
)访问它们。仅支持变量——不支持条件语句、循环和其他Liquid特性。

Comments

注释

Lines starting with
>
are ignored when rendering:
plain
> This is a comment in ***plain
>
开头的行在渲染时会被忽略:
plain
> This is a comment in ***plain

Best Practices

最佳实践

  1. Reference concepts consistently — use
    :ConceptName:
    notation to disambiguate key concepts
  2. Keep it simple — specs should be readable by both humans and AI
  3. Leverage templates — use the standard template library for common patterns
  4. Use acceptance tests — add them for requirements that need verification
  5. Be specific — write clear, testable requirements in functional specs
  6. Define before use — always define concepts in
    ***definitions***
    before referencing them
  7. Start with imports — import relevant templates before defining your own concepts
  1. 一致引用概念——使用
    :ConceptName:
    表示法明确关键概念
  2. 保持简洁——规范应同时便于人类和AI阅读
  3. 利用模板——针对常见模式使用标准模板库
  4. 使用验收测试——为需要验证的需求添加验收测试
  5. 具体明确——在功能规范中编写清晰、可测试的需求
  6. 先定义后使用——始终在
    ***definitions***
    中定义概念,然后再引用
  7. 从导入开始——在定义自己的概念之前,先导入相关模板

Repository Structure

仓库结构

*.plain                  # Specification files (the source of truth)
template/*.plain         # Reusable template specs imported by module specs
plain_modules/           # Generated code output (one folder per .plain spec)
resources/               # Schemas, API specs, transforms, test fixtures
conformance_tests/       # Generated conformance tests (one folder per module)
test_scripts/            # Scripts for running unit and conformance tests
config.yaml              # Codeplain configuration
Generated artifacts (gitignored):
  • plain_modules/<module_name>/
    — generated project for each
    .plain
    spec (implementation + unit tests)
  • conformance_tests/<module_name>/
    — generated conformance tests for each module
*.plain                  # 规范文件(事实来源)
template/*.plain         # 模块规范导入的可复用模板规范
plain_modules/           # 生成的代码输出(每个.plain规范对应一个文件夹)
resources/               # 模式、API规范、转换规则、测试夹具
conformance_tests/       # 生成的一致性测试(每个模块对应一个文件夹)
test_scripts/            # 运行单元测试和一致性测试的脚本
config.yaml              # Codeplain配置
生成的工件(已加入git忽略):
  • plain_modules/<module_name>/
    — 每个.plain规范对应的生成项目(实现+单元测试)
  • conformance_tests/<module_name>/
    — 每个模块对应的生成一致性测试

How Modules Work

模块工作原理

There are two types of modules:
模块分为两种类型:

Import Modules

导入模块

An import module lives in the
template/
directory and contains only
***definitions***
,
***implementation reqs***
, and/or
***test reqs***
. It must not contain
***functional specs***
and must not use
requires
. It may optionally
import
other templates for layered reuse.
When a module
import
s
another, it gains access to the imported module's definitions, implementation reqs, and test reqs — but not its functional specs. The default import directory is
template/
, so the
template/
prefix is not needed (e.g.,
airplain
).
导入模块位于**
template/
目录中,仅包含
***definitions***
***implementation reqs***
和/或
***test reqs***
。它必须包含
***functional specs***
,且必须
不**使用
requires
。它可选择性地
import
其他模板以实现分层复用。
当一个模块**
import
**另一个模块时,它会获得对导入模块的定义、实现要求和测试要求的访问权限——但不包括其功能规范。默认导入目录为
template/
,因此无需添加
template/
前缀(例如,
airplain
)。

Requires Modules

依赖模块

requires
establishes a build ordering between modules. The required module is built before the current one. This does not necessarily mean the current module extends or depends on the required module's code — it may be completely independent. The
requires
relationship ensures the build order is correct.
When a module
requires
another:
  • The required module's generated code (
    plain_modules/<required_module>
    ) is copied as the starting point.
  • The required module's
    ***functional specs***
    become visible as previous functional specs.
  • Only
    exported_concepts
    from the required module are available (not its full definitions).
A module can use both
requires
and
import
together.
requires
points to other root-level modules (e.g.,
auth
,
messaging
);
import
resolves from the default
template/
directory without needing the prefix (e.g.,
airplain
). Modules with functional specs live at the repository root. Import modules (templates) live in
template/
.
requires
modules must share the same tech stack.
Because the required module's generated code is copied as the starting point and the renderer continues building on top of it with one language/framework toolchain, two modules can only be linked with
requires
when they target the same language, framework, and runtime. A runtime/network dependency between systems is not a reason to use
requires
. For example, a React frontend that talks to a Python/FastAPI backend over HTTP must not
requires: [backend]
— the stacks differ. Model that pair as two independent root modules (each with its own
config.yaml
and test scripts), and express the contract between them through a shared API schema in
resources/
or shared concepts in an
import
ed template, not through
requires
.
requires
用于建立模块之间的构建顺序。被依赖的模块会先于当前模块构建。这并不一定意味着当前模块扩展或依赖于被依赖模块的代码——它们可能完全独立。
requires
关系确保构建顺序正确。
当一个模块**
requires
**另一个模块时:
  • 被依赖模块的生成代码(
    plain_modules/<required_module>
    )会被复制作为起始点。
  • 被依赖模块的
    ***functional specs***
    会作为先前功能规范可见。
  • 仅能使用被依赖模块的
    exported_concepts
    (而非其完整定义)。
一个模块可同时使用
requires
import
requires
指向其他根级模块(例如,
auth
messaging
);
import
从默认的
template/
目录解析,无需添加前缀(例如,
airplain
)。包含功能规范的模块位于仓库根目录。导入模块(模板)位于
template/
中。
requires
模块必须共享相同的技术栈
。因为被依赖模块的生成代码会被复制作为起始点,渲染器会使用同一语言/框架工具链在其基础上继续构建,因此只有当两个模块针对相同的语言、框架和运行时,才能使用
requires
链接它们。系统之间的运行时/网络依赖不是使用
requires
的理由。例如,通过HTTP与Python/FastAPI后端通信的React前端绝不能
requires: [backend]
——它们的技术栈不同。应将这两者建模为两个独立的根模块(每个模块都有自己的
config.yaml
和测试脚本),并通过
resources/
中的共享API模式或
import
模板中的共享概念来表达它们之间的契约,而非通过
requires

Contracts Between Modules

模块间契约

Modules can use
required_concepts
and
exported_concepts
to enforce contracts between them. A template declaring
required_concepts
means any module that imports it must define those concepts. A module declaring
exported_concepts
controls which concepts are visible to modules that
require
it.
Exported concepts are not transitive. If module A exports a concept and module B
requires
A, module B can use that concept — but if module C
requires
B, it does not automatically gain access to A's exported concepts. If a concept needs to be shared across multiple
requires
modules, define it in a common import module and have each module
import
that shared template.
模块可使用
required_concepts
exported_concepts
来强制执行模块间的契约。声明
required_concepts
的模板意味着任何导入它的模块必须定义这些概念。声明
exported_concepts
的模块控制哪些概念对
require
它的模块可见。
导出的概念不具有传递性。如果模块A导出一个概念,模块B
requires
A,那么模块B可以使用该概念——但如果模块C
requires
B,它不会自动获得对A导出概念的访问权限。如果一个概念需要在多个
requires
模块之间共享,请在公共导入模块中定义它,并让每个模块
import
该共享模板。

Running Tests

运行测试

Test scripts live in
test_scripts/
and are run from the repo root:
bash
undefined
测试脚本位于
test_scripts/
中,需从仓库根目录运行:
bash
undefined

Run all unit tests for a module

运行某个模块的所有单元测试

./test_scripts/run_unittests.sh <module_name>
./test_scripts/run_unittests.sh <module_name>

Run a single unit test

运行单个单元测试

./test_scripts/run_unittests_single.sh <module_name>
./test_scripts/run_unittests_single.sh <module_name>

Run conformance tests

运行一致性测试

./test_scripts/run_conformance_tests.sh <module_name> <conformance_tests_folder>
undefined
./test_scripts/run_conformance_tests.sh <module_name> <conformance_tests_folder>
undefined

Testing Scripts

测试脚本

Every ***plain project ships shell scripts under
test_scripts/
that the user (and the renderer) call into to verify the generated code. There are three kinds, each authored by a dedicated skill — use the corresponding skill as the source of truth whenever you create, regenerate, or adapt a script.
每个***plain项目都在
test_scripts/
下附带shell脚本,用户(和渲染器)可调用这些脚本来验证生成的代码。共有三种脚本,每种由专门的技能编写——在创建、重新生成或调整脚本时,请以对应的技能为事实来源。

Why these scripts exist (and why they're shaped the way they are)

这些脚本存在的原因(以及它们为何是这种形式)

The primary purpose of these scripts is automated execution by the renderer, not manual invocation by a developer. The user can run them by hand (see Running Tests), but the renderer runs them many times more often — once for every functional spec it processes — as part of its incremental rendering loop. The contract between the scripts and the renderer is shaped by that execution model:
  • Conformance tests are per-functional-spec. Each functional spec in a module has its own folder under
    conformance_tests/<module>/<spec>/
    . After the renderer finishes generating code for a new functional spec, it runs the conformance tests of every previous functional spec in the same module to detect regressions — see Conformance Test Workflow. For a module with N functional specs, the conformance script gets invoked on the order of N times per render, each invocation pointing at a different spec's test folder.
  • Each per-spec invocation is independent. The conformance script does not know that it's the second invocation in a long sequence; from its point of view, each invocation is a cold start against a single spec's tests. That's the right design — it keeps each invocation hermetic and lets the renderer reorder or skip specs without breaking anything.
  • Per-spec independence is also what makes dependency installation expensive. A naive conformance runner would re-install all of the project's runtime dependencies (Python venv +
    pip install
    , Maven dependency tree,
    npm ci
    ,
    cargo build
    , ...) on every one of those N invocations. That's
    N × install-cost
    of wasted work for every render.
  • That is exactly why
    prepare_environment_<lang>
    exists.
    Its only job is to amortize the install cost: install once at the start of a render, populate
    .tmp/<lang>_<arg>/
    with the warmed dependency cache and build artifacts, then let the conformance runner attach to that working folder on each of the N per-spec invocations instead of re-installing. The conformance runner's activate-only variant does precisely that. When no prepare script exists, the conformance runner falls back to the install-inline variant and pays the install cost N times — acceptable for tiny projects, costly for anything realistic.
  • The unit-test runner has a different execution model, because unit tests live in a different place. Unit tests are part of the generated codebase itself — they sit directly inside
    plain_modules/<module>/
    next to the implementation they exercise — whereas conformance tests live outside the codebase, in their own per-spec folders under
    conformance_tests/<module>/<spec>/
    . As a result, the unit-test runner doesn't have a per-spec axis to iterate over: it just runs against the whole
    plain_modules/<module>/
    build in one go, gets invoked far fewer times per render, and has no amortization gain to chase. That's why the unit-test runner is always self-contained and there is no
    prepare_environment
    -equivalent for it.
Keep this framing in mind when you author or adapt any of these scripts. The decisions about working-folder paths, isolation locations, exit codes, and the activate-only-vs-install-inline split are not arbitrary house style — they are what makes the renderer's per-spec loop tractable.
这些脚本的主要用途是由渲染器自动执行,而非供开发人员手动调用。用户可以手动运行它们(请参阅运行测试部分),但渲染器运行它们的频率要高得多——在其增量渲染循环中,每个功能规范处理一次。脚本与渲染器之间的契约由这种执行模式决定:
  • 一致性测试按功能规范划分。模块中的每个功能规范在
    conformance_tests/<module>/<spec>/
    下都有自己的文件夹。渲染器完成新功能规范的代码生成后,会运行同一模块中所有先前功能规范的一致性测试以检测回归——请参阅一致性测试工作流。对于有N个功能规范的模块,一致性脚本会被调用大约N次/每次渲染,每次调用指向不同规范的测试文件夹。
  • 每次按规范的调用都是独立的。一致性脚本不知道自己是长序列中的第二次调用;从它的角度来看,每次调用都是针对单个规范测试的冷启动。这是正确的设计——它使每次调用都独立封装,让渲染器可以重新排序或跳过规范而不会破坏任何内容。
  • 按规范独立调用也会导致依赖安装成本高昂。一个简单的一致性运行器会在N次调用中的每次调用都重新安装项目的所有运行时依赖(Python虚拟环境+
    pip install
    、Maven依赖树、
    npm ci
    cargo build
    等)。每次渲染都会浪费
    N × 安装成本
    的工作量。
  • 这正是
    prepare_environment_<lang>
    存在的原因
    。它的唯一工作是分摊安装成本:在渲染开始时安装一次,将预热的依赖缓存和构建工件填充到
    .tmp/<lang>_<arg>/
    中,然后让一致性运行器在N次后续按规范调用中附加到该工作文件夹,而非重新安装。一致性运行器的仅激活变体正是这样做的。当不存在准备脚本时,一致性运行器会回退到内联安装变体,并支付N次安装成本——对于小型项目来说可以接受,但对于任何实际项目来说成本很高。
  • 单元测试运行器有不同的执行模型,因为单元测试位于不同的位置。单元测试是生成代码库的一部分——它们直接位于
    plain_modules/<module>/
    中,与它们测试的实现相邻——而一致性测试位于代码库外部,在
    conformance_tests/<module>/<spec>/
    下的每个规范文件夹中。因此,单元测试运行器没有按规范迭代的轴:它只需一次性针对整个
    plain_modules/<module>/
    构建运行,每次渲染调用的次数要少得多,也没有可追求的分摊收益。这就是为什么单元测试运行器始终是自包含的,没有针对它的
    prepare_environment
    等效项。
在编写或调整任何这些脚本时,请牢记这个框架。关于工作文件夹路径、隔离位置、退出代码以及仅激活与内联安装拆分的决策并非随意的风格——它们是使渲染器的按规范循环可行的关键。

The three scripts

三种脚本

  • run_unittests_<lang>.sh
    /
    .ps1
    — runs the auto-generated unit tests in
    plain_modules/<module>/
    . Authored by the
    implement-unit-testing-script
    skill. Receives one positional argument: the source build folder name. Invoked roughly once per render. Fully self-contained: it installs its own dependencies inline (via
    pip install -r requirements.txt
    ,
    npm ci
    ,
    mvn
    ,
    cargo fetch
    , etc.) and never relies on any other script having run first.
  • run_conformance_tests_<lang>.sh
    /
    .ps1
    — runs the conformance tests in
    conformance_tests/<module>/<spec>/
    against the generated implementation. Authored by the
    implement-conformance-testing-script
    skill. Receives two positional arguments: the source build folder and the conformance tests folder. Invoked once per previous functional spec on every render — i.e. roughly N times for a module with N functional specs.
  • prepare_environment_<lang>.sh
    /
    .ps1
    optional one-time setup that runs before the conformance script and only the conformance script. Invoked once per render to install the build's dependencies and pre-warm build artifacts into
    .tmp/<lang>_<arg>/
    so the N subsequent conformance invocations can attach to the warmed environment instead of re-installing. Authored by the
    implement-prepare-environment-script
    skill. Receives one positional argument: the source build folder name. It does not feed the unit-test script — see
    prepare_environment
    is conformance-only
    below.
  • run_unittests_<lang>.sh
    /
    .ps1
    — 运行
    plain_modules/<module>/
    中自动生成的单元测试。由
    implement-unit-testing-script
    技能编写。接收一个位置参数:源构建文件夹名称。大约每次渲染调用一次。完全自包含:它会内联安装自己的依赖(通过
    pip install -r requirements.txt
    npm ci
    mvn
    cargo fetch
    等),从不依赖于其他脚本已先运行。
  • run_conformance_tests_<lang>.sh
    /
    .ps1
    — 针对生成的实现运行
    conformance_tests/<module>/<spec>/
    中的一致性测试。由
    implement-conformance-testing-script
    技能编写。接收两个位置参数:源构建文件夹和一致性测试文件夹。每次渲染针对每个先前功能规范调用一次——即对于有N个功能规范的模块,大约调用N次。
  • prepare_environment_<lang>.sh
    /
    .ps1
    可选的一次性设置,在一致性脚本之前运行,且仅为一致性脚本运行。每次渲染调用一次,用于安装构建依赖并将构建工件预热到
    .tmp/<lang>_<arg>/
    中,以便N次后续一致性调用可以附加到预热环境,而非重新安装。由
    implement-prepare-environment-script
    技能编写。接收一个位置参数:源构建文件夹名称。它不为单元测试脚本提供支持——请参阅下面的
    prepare_environment
    仅用于一致性测试(常见错误)

prepare_environment
is conformance-only (common mistake)

prepare_environment
仅用于一致性测试(常见错误)

It is a common and costly mistake to assume that
prepare_environment_<lang>
is a generic "warm up the environment for all the testing scripts" step that the unit-test runner can also lean on. It is not. The hard rule:
prepare_environment_<lang>
exists solely to set up the working folder that
run_conformance_tests_<lang>
then attaches to (the activate-only variant). The unit-test runner (
run_unittests_<lang>
) is completely independent of it — it does not read from
prepare
's working folder, does not require
prepare
to have run, and must install whatever dependencies it needs on its own.
Why:
  • Unit tests run against
    plain_modules/<module>/
    , conformance tests run against
    .tmp/<lang>_<arg>/
    .
    The two scripts stage into different places.
    prepare_environment
    populates
    .tmp/<lang>_<arg>/
    for conformance; the unit-test script does its own staging into its own
    .tmp/<lang>_<arg>/
    working folder and installs its own dependencies there.
  • The unit-test runner must work in isolation. Users and CI systems run unit tests as a quick smoke check without ever invoking conformance. If
    run_unittests_<lang>
    depended on
    prepare_environment
    having run, those one-off unit-test invocations would silently fail (or be "fixed" by a misguided edit to make it depend on
    prepare
    ).
  • The skill contract enforces it.
    implement-unit-testing-script
    emits a fully self-contained script every time: toolchain check → stage → install dependencies inline → run tests. It never emits an activate-only variant. The two-variant pattern is exclusive to the conformance runner.
If you find yourself authoring (or asked to author) a
prepare_environment
script that handles unit-test dependencies too, stop. The unit-test script handles its own dependencies. Adding a unit-test path into
prepare_environment
couples scripts that should stay independent, and breaks the activate-only contract between
prepare
and
conformance
.
认为
prepare_environment_<lang>
是通用的“为所有测试脚本预热环境”步骤,单元测试运行器也可以依赖它,这是一个常见且代价高昂的错误。事实并非如此。硬规则:
prepare_environment_<lang>
的存在是为
run_conformance_tests_<lang>
设置其随后附加的工作文件夹(仅激活变体)。单元测试运行器(
run_unittests_<lang>
完全独立于它——它不会读取
prepare
的工作文件夹,不需要
prepare
已运行,必须自行安装所需的任何依赖。
原因:
  • 单元测试针对
    plain_modules/<module>/
    运行,一致性测试针对
    .tmp/<lang>_<arg>/
    运行
    。两个脚本部署到不同的位置。
    prepare_environment
    为一致性测试填充
    .tmp/<lang>_<arg>/
    ;单元测试脚本在其自己的
    .tmp/<lang>_<arg>/
    工作文件夹中进行自己的部署,并在那里安装自己的依赖。
  • 单元测试运行器必须能够独立工作。用户和CI系统会在不调用一致性测试的情况下运行单元测试作为快速冒烟测试。如果
    run_unittests_<lang>
    依赖于
    prepare_environment
    已运行,那么这些一次性单元测试调用会无声失败(或被误导性地修改为依赖
    prepare
    )。
  • 技能契约强制执行此规则
    implement-unit-testing-script
    每次都会生成一个完全自包含的脚本:工具链检查→部署→内联安装依赖→运行测试。它从不生成仅激活变体。双变体模式是一致性运行器独有的。
如果发现自己正在编写(或被要求编写)一个也处理单元测试依赖的
prepare_environment
脚本,请停止。单元测试脚本会自行处理其依赖。在
prepare_environment
中添加单元测试路径会将本应保持独立的脚本耦合在一起,并破坏
prepare
与一致性测试之间的仅激活契约。

Shared rules across all three scripts

所有三种脚本的共享规则

Anything not listed here is documented in the individual skill file:
  • Input folders are read-only — hard rule. The build folder (and, for conformance, the conformance tests folder too) is input only. Every install, build artifact, cache, log, JUnit XML, coverage report, compiled test class, and temp file must land inside
    .tmp/<lang>_<arg>
    , never inside the input folder. The build folder is shared with the renderer and with the user's version control; writing into it corrupts both. If you find yourself about to issue a command whose
    cwd
    is an input folder, or whose target path starts with the input folder, stop — the write has to go into
    .tmp/<lang>_<arg>
    .
  • Shell flavor matches the host.
    .sh
    on macOS / Linux,
    .ps1
    on Windows. A project intended for both OSes ships both files in matching pairs (
    prepare
    +
    conformance
    for each language must agree on working-folder name and isolation paths).
  • Exit codes are part of the contract.
    69
    for unrecoverable errors (missing toolchain, bad args, can't enter the working folder, install failed);
    1
    for the "no tests discovered" guard in the conformance runner (and bad usage in the unit-test runner); any other non-zero code is propagated from the underlying test command. Other skills — notably
    plain-healthcheck
    and
    check-plain-env
    — branch on these codes.
  • Wired in via
    config.yaml
    .
    Each script that is actually generated must be referenced from the relevant
    config.yaml
    via the
    unittests-script:
    ,
    conformance-tests-script:
    , and
    prepare-environment-script:
    keys respectively. See the
    init-config-file
    skill for the canonical assembly. If
    prepare-environment-script
    is declared,
    conformance-tests-script
    must be declared too
    — a prepare script only makes sense in service of conformance, and
    plain-healthcheck
    will hard-fail a project that violates this.
  • Conformance scripts come in two variants — unit-test scripts do not. When a
    prepare_environment_<lang>
    script exists, the conformance script is the activate-only variant (it attaches to the env prepare populated in
    .tmp/
    ). When no prepare exists, the conformance script is the install-inline variant (it stages and installs in one shot). The
    implement-conformance-testing-script
    skill picks the right variant automatically based on whether a prepare script is already on disk. The unit-test script has no activate-only variant — it is always self-contained, regardless of whether a
    prepare_environment_<lang>
    script exists.
  • Dependency isolation is project-local. Each language's package cache / virtual env / build repo lives inside the working folder (
    ./.venv
    for Python,
    ./node_modules
    for Node,
    ./.m2
    for Java,
    ./.gocache
    for Go,
    ./.cargo
    for Rust,
    ./.pub-cache
    for Flutter, ...) — never in the user's home directory. The conformance script reads from the same project-local location prepare wrote to; the unit-test script uses its own working folder and its own copy of the isolated dependencies.
  • No language-package checks live in these scripts. The scripts themselves install language packages via
    pip install -r requirements.txt
    ,
    npm ci
    ,
    mvn -Dmaven.repo.local=...
    ,
    go mod download
    ,
    cargo fetch
    , etc. They do not pre-verify individual packages; that's the package manager's job. The host-level checks for the toolchains and external dependencies belong in
    check-plain-env
    , not in these scripts.
For implementation details — the exact step sequence, toolchain checks, language-specific install / test commands, working-folder lifecycle, anti-patterns — open the corresponding
implement-*-testing-script
skill. Do not hand-author a testing script from scratch; route every creation or modification through the matching skill so the shared rules above are enforced uniformly.
未在此列出的内容在各个技能文件中有记录:
  • 输入文件夹是只读的——硬规则。构建文件夹(对于一致性测试,还包括一致性测试文件夹)仅作为输入。所有安装、构建工件、缓存、日志、JUnit XML、覆盖率报告、编译测试类和临时文件必须放在
    .tmp/<lang>_<arg>
    内,绝不能放在输入文件夹内。构建文件夹与渲染器和用户的版本控制系统共享;写入其中会损坏两者。如果发现自己要发出一个以输入文件夹为
    cwd
    或目标路径以输入文件夹开头的命令,请停止——写入操作必须指向
    .tmp/<lang>_<arg>
  • Shell风格与主机匹配。macOS/Linux上使用
    .sh
    ,Windows上使用
    .ps1
    。针对两种操作系统的项目会附带成对的文件(每种语言的
    prepare
    +
    conformance
    必须在工作文件夹名称和隔离路径上保持一致)。
  • 退出代码是契约的一部分
    69
    表示不可恢复的错误(缺少工具链、参数错误、无法进入工作文件夹、安装失败);
    1
    表示一致性运行器中的“未发现测试”保护(以及单元测试运行器中的错误用法);任何其他非零代码都会从底层测试命令传播。其他技能——尤其是
    plain-healthcheck
    check-plain-env
    ——会根据这些代码分支。
  • 通过
    config.yaml
    连接
    。实际生成的每个脚本必须通过
    unittests-script:
    conformance-tests-script:
    prepare-environment-script:
    键分别在相关的
    config.yaml
    中引用。请参阅
    init-config-file
    技能了解规范配置。如果声明了
    prepare-environment-script
    ,则必须同时声明
    conformance-tests-script
    ——准备脚本仅对一致性测试有意义,
    plain-healthcheck
    会使违反此规则的项目无法通过检查。
  • 一致性脚本有两种变体——单元测试脚本没有。当存在
    prepare_environment_<lang>
    脚本时,一致性脚本是仅激活变体(它附加到
    .tmp/
    prepare
    填充的环境)。当不存在准备脚本时,一致性脚本是内联安装变体(它在一次操作中完成部署和安装)。
    implement-conformance-testing-script
    技能会根据磁盘上是否已存在准备脚本自动选择正确的变体。单元测试脚本没有仅激活变体——无论是否存在
    prepare_environment_<lang>
    脚本,它始终是自包含的。
  • 依赖隔离是项目本地的。每种语言的包缓存/虚拟环境/构建仓库位于工作文件夹内(Python为
    ./.venv
    ,Node为
    ./node_modules
    ,Java为
    ./.m2
    ,Go为
    ./.gocache
    ,Rust为
    ./.cargo
    ,Flutter为
    ./.pub-cache
    等)——绝不在用户的主目录中。一致性脚本从
    prepare
    写入的同一项目本地位置读取;单元测试脚本使用其自己的工作文件夹和其自己的隔离依赖副本。
  • 这些脚本中不包含语言包检查。脚本本身通过
    pip install -r requirements.txt
    npm ci
    mvn -Dmaven.repo.local=...
    go mod download
    cargo fetch
    等安装语言包。它们预先验证单个包;这是包管理器的工作。工具链和外部依赖的主机级检查属于
    check-plain-env
    ,而非这些脚本。
有关实现细节——确切的步骤序列、工具链检查、特定语言的安装/测试命令、工作文件夹生命周期、反模式——请打开相应的
implement-*-testing-script
技能。不要从头开始手动编写测试脚本;所有创建或修改都应通过匹配的技能进行,以确保上述共享规则得到统一执行。

Writing Functional Specs

编写功能规范

  • Each functional spec must imply a maximum of 200 changed lines of code. This is a hard limit — if a spec would result in more than 200 lines of changes, it must be broken down into smaller, independent specs. This limit also helps avoid "Functional spec too complex!" errors from the renderer.
  • Conflicting specs must be avoided at all costs. Functional specs should be written so that no conflicts exist between them. If two specs appear to conflict, they must be clarified by adding more detail and context to the specs until all possible conflicts are resolved. Prevention is always preferable to debugging conflicts after rendering.
  • Specs should be language-agnostic. Avoid using programming language-specific terminology (e.g., generics syntax, framework annotations, language-specific collection types) in functional specs and definitions. Write specs in terms of behavior, concepts, and domain logic — not implementation constructs. General technical terms that are not language-specific are fine (e.g., null values, JSON types, HTTP status codes, REST api endpoints etc.). The
    ***implementation reqs***
    section is the appropriate place for language-specific guidance.
  • Keep sentences short and clear — but never at the cost of ambiguity. Spec lines should be easy to read and understand at a glance. Prefer short, direct sentences and plain words over long sentences and jargon — if a 10-cent word and a 50-cent word say the same thing, use the 10-cent one. This applies to every spec section, not only functional specs:
    ***definitions***
    ,
    ***implementation reqs***
    ,
    ***test reqs***
    , and
    ***acceptance tests***
    should all be as concise as they can be while staying unambiguous. The hard constraint is in the second half of that rule: wordy-but-precise always beats terse-but-ambiguous. If trimming a clause, a qualifier, or a sub-bullet would leave the spec open to more than one reasonable interpretation, leave it in. When a sentence starts to grow because the behavior is genuinely complex, split it into two short sentences (or into a parent line + sub-bullets) rather than dropping detail. Concision is in service of clarity, never the other way around.
  • Specs must be deterministic enough to both run and use the software without reading the generated code. A developer should be able to figure out, from the specs alone, two distinct things:
    1. How to run the built software — the entry-point command (e.g.
      python -m app
      ,
      uvicorn app.main:app
      ,
      ./my-cli
      ), prerequisites (required runtime versions, package managers, system binaries), required environment variables, ports the software listens on, configuration file paths and shapes, and any default arguments.
    2. How to use the running software — the full interaction surface. For a REST API: every endpoint path, HTTP method, request body shape, response body shape, status codes, and authentication scheme. For a CLI tool: every command, its arguments and flags, the expected output (including exit codes), and the input it reads (stdin, files, env vars). For a library: every public function/class, its signature, the inputs it accepts, the outputs it returns, and the errors it can raise.
    Concretely, a reader should never have to open
    plain_modules/
    to answer "how do I start this?" or "how do I call this endpoint?" — those answers must already live in the specs. Never leave runtime or interface details up to the renderer's discretion — if the spec doesn't pin them down, two renders can produce two different shapes, and any human or automated consumer of the software is now coupled to an undocumented choice.
  • Encapsulate functionality in functional specs.
    requires
    modules import only functional specs. It is therefore important that the functionality is encapsulated in the functional specs and not in implementation reqs, as those will not be in the context of future functional specs when fixing previous conformance tests of previous functional specs.
  • 每个功能规范对应的代码变更行数最多为200行。这是硬限制——如果一个规范会导致超过200行的变更,必须将其拆分为更小的独立规范。此限制还有助于避免渲染器返回“Functional spec too complex!”错误。
  • 必须不惜一切代价避免冲突规范。功能规范的编写应确保它们之间不存在冲突。如果两个规范似乎存在冲突,必须通过为规范添加更多细节和上下文来澄清,直到所有可能的冲突都得到解决。预防始终优于渲染后调试冲突。
  • 规范应与语言无关。避免在功能规范和定义中使用特定编程语言的术语(例如,泛型语法、框架注解、特定语言的集合类型)。应根据行为、概念和领域逻辑编写规范——而非实现结构。不特定于语言的通用技术术语是可以的(例如,空值、JSON类型、HTTP状态码、REST API端点等)。
    ***implementation reqs***
    章节是提供特定语言指导的合适位置。
  • 保持句子简短清晰——但绝不能以牺牲明确性为代价。规范行应易于快速阅读和理解。优先使用简短、直接的句子和通俗词汇,而非长句和行话——如果一个简单词汇和一个复杂词汇表达的意思相同,使用简单词汇。这适用于每个规范章节,不仅限于功能规范:
    ***definitions***
    ***implementation reqs***
    ***test reqs***
    ***acceptance tests***
    都应尽可能简洁,同时保持明确。规则的后半部分是硬约束:冗长但精确始终优于简洁但模糊。如果删除一个从句、限定词或子项目会使规范存在多种合理的解释,请保留它。当句子因行为确实复杂而开始变长时,将其拆分为两个短句(或一个父行+子项目),而非省略细节。简洁是为清晰服务,而非相反。
  • 规范必须足够确定,无需阅读生成的代码即可运行使用软件。开发人员应能够仅从规范中找出两个不同的信息:
    1. 如何运行构建后的软件——入口点命令(例如
      python -m app
      uvicorn app.main:app
      ./my-cli
      )、先决条件(所需的运行时版本、包管理器、系统二进制文件)、所需的环境变量、软件监听的端口、配置文件路径和格式,以及任何默认参数。
    2. 如何使用运行中的软件——完整的交互表面。对于REST API:每个端点路径、HTTP方法、请求体格式、响应体格式、状态码和认证方案。对于CLI工具:每个命令、其参数和标志、预期输出(包括退出代码)以及它读取的输入(标准输入、文件、环境变量)。对于库:每个公共函数/类、其签名、接受的输入、返回的输出以及可能引发的错误。
    具体而言,读者永远不必打开
    plain_modules/
    来回答“如何启动这个软件?”或“如何调用这个端点?”——这些答案必须已经在规范中。绝不能让运行时或接口细节由渲染器自行决定——如果规范没有明确它们,两次渲染可能会产生两种不同的结果,软件的任何人工或自动化使用者现在都依赖于一个未记录的选择。
  • 将功能封装在功能规范中
    requires
    模块仅导入功能规范。因此,重要的是将功能封装在功能规范中,而非实现要求中,因为在修复先前功能规范的一致性测试时,实现要求不会在未来功能规范的上下文中。

Working with Specs

使用规范

  • The
    .plain
    files are the source of truth. Modify specs to change behavior, then re-render.
  • The
    template/
    directory contains reusable template specs that define common patterns.
  • The
    resources/
    directory contains schemas, API specs, transforms, and test fixtures referenced by the specs.
  • Generated code in
    plain_modules/
    should not be manually edited — changes will be overwritten on the next render.
  • .plain
    文件是事实来源。修改规范以更改行为,然后重新渲染。
  • template/
    目录包含定义常见模式的可复用模板规范。
  • resources/
    目录包含规范引用的模式、API规范、转换规则和测试夹具。
  • plain_modules/
    中的生成代码不应手动编辑——更改会在下次渲染时被覆盖。

Read-Only Generated Artifacts

只读生成工件

All code in
plain_modules/
and
conformance_tests/
is generated and must never be modified directly — not the implementation code, not the unit tests, not the conformance tests. These artifacts can only be:
  • Read — to understand what the generated code does, inspect behavior, and identify ambiguities in the specs.
  • Tested — unit tests and conformance tests can be executed to verify correctness.
  • Debugged — test failures and unexpected behavior should be traced through the generated code to understand root causes, but fixes must always be applied in the
    .plain
    specs, never in the generated code.
Each module has its own folder under
plain_modules/<module_name>/
containing the generated project (implementation + unit tests). Each module also has its own folder under
conformance_tests/<module_name>/
, with individual subfolders per functionality for conformance tests. Conformance tests may also include generated
***acceptance tests***
— these are equally read-only and serve the same purpose: gathering information and debugging the specs.
To change the generated code, only the corresponding
.plain
spec files may be edited
:
  • To change implementation or unit tests → modify the
    ***functional specs***
    ,
    ***implementation reqs***
    or
    ***definitions***
    sections of the spec.
  • To guide conformance test generation → modify the
    ***test reqs***
    section of the spec.
  • To guide acceptance test generation → modify the
    ***acceptance tests***
    subsections under functional specs.
The
test_scripts/
folder contains shell scripts for running unit tests and conformance tests against the generated code. These scripts are the entry point for test execution — see the Running Tests section for usage.
The workflow is: read the generated code to understand what it does, identify what is ambiguous or incorrect in the specs, then make changes exclusively in the
.plain
files and re-render.
plain_modules/
conformance_tests/
中的所有代码都是生成的,绝不能直接修改——包括实现代码、单元测试和一致性测试。这些工件只能:
  • 读取——了解生成代码的功能、检查行为并识别规范中的模糊之处。
  • 测试——可执行单元测试和一致性测试以验证正确性。
  • 调试——可通过生成代码追踪测试失败和意外行为以了解根本原因,但修复必须始终应用于
    .plain
    规范,而非生成代码。
每个模块在
plain_modules/<module_name>/
下有自己的文件夹,包含生成的项目(实现+单元测试)。每个模块在
conformance_tests/<module_name>/
下也有自己的文件夹,每个功能对应一个子文件夹用于一致性测试。一致性测试可能还包括生成的
***acceptance tests***
——这些同样是只读的,用途相同:收集信息并调试规范。
要更改生成的代码,只能编辑对应的
.plain
规范文件
  • 要更改实现或单元测试→修改规范的
    ***functional specs***
    ***implementation reqs***
    ***definitions***
    章节。
  • 要指导一致性测试生成→修改规范的
    ***test reqs***
    章节。
  • 要指导验收测试生成→修改功能规范下的
    ***acceptance tests***
    子章节。
test_scripts/
文件夹包含用于针对生成代码运行单元测试和一致性测试的shell脚本。这些脚本是测试执行的入口点——请参阅运行测试部分了解用法。
工作流程是:读取生成代码以了解其功能,识别规范中的模糊或错误之处,然后仅在
.plain
文件中进行更改并重新渲染。

Conformance Test Workflow

一致性测试工作流

Each functional spec in a module has its own set of conformance tests, generated per functional spec per module. After a new functional spec is rendered (i.e., its implementation code is generated), conformance tests for that spec are also rendered. Before proceeding, all previous conformance tests (from earlier functional specs in the same module) are run. Ideally, all conformance tests of all previous functional specs pass without any changes. If any previously passing conformance test now fails, the failure must be resolved before moving on. Resolution means one of three things: fixing the conformance test, fixing the implementation code (by adjusting the spec), or identifying conflicting specs.
If conformance tests of a previous functional spec need to be changed in order to pass, this is a strong indicator that the functional specs themselves may need to be amended. Needing to modify earlier conformance tests suggests the new functional spec has introduced behavior that is inconsistent with what was previously specified — the specs should be reviewed and clarified to eliminate the ambiguity or conflict.
模块中的每个功能规范都有自己的一致性测试集,按每个模块的每个功能规范生成。新功能规范渲染完成后(即其实现代码已生成),该规范的一致性测试也会被渲染。在继续之前,会运行所有先前的一致性测试(同一模块中早期功能规范的测试)。理想情况下,所有先前功能规范的一致性测试无需任何更改即可通过。如果任何先前通过的一致性测试现在失败,必须在继续之前解决失败问题。解决方式包括三种:修复一致性测试、修复实现代码(通过调整规范)或识别冲突规范。
如果需要更改先前功能规范的一致性测试才能通过,这强烈表明功能规范本身可能需要修改。需要修改早期一致性测试表明新功能规范引入了与先前规范不一致的行为——应审查并澄清规范以消除模糊或冲突。

Conflicting Specs and Conformance Test Debugging

冲突规范与一致性测试调试

The renderer can detect conflicting specs. Two functional specs may be in conflict if conformance tests for a previously passing spec begin to fail after a new spec is rendered. When a conformance test failure occurs, the first step is to determine where the issue lies. There are three possible outcomes:
  1. The implementation is incorrect — the generated code does not correctly implement the functional spec. Fix the spec to clarify intent and re-render.
  2. The conformance tests are incorrect — the generated tests do not accurately verify the spec. Adjust
    ***test reqs***
    or
    ***acceptance tests***
    to guide better test generation and re-render.
  3. The requirements conflict — the two functional specs are inherently contradictory. One or both specs must be revised to resolve the conflict before re-rendering.
Conflicting specs are the most costly outcome and should be prevented proactively. When writing or modifying functional specs, carefully consider how each spec interacts with all previous specs. If ambiguity exists, add explicit detail to the spec to eliminate any possible interpretation that could conflict with earlier specs.
渲染器可以检测冲突规范。如果新规范渲染后,先前通过的规范的一致性测试开始失败,则两个功能规范可能存在冲突。当一致性测试失败时,第一步是确定问题所在。有三种可能的结果:
  1. 实现不正确——生成的代码未正确实现功能规范。修复规范以明确意图并重新渲染。
  2. 一致性测试不正确——生成的测试未准确验证规范。调整
    ***test reqs***
    ***acceptance tests***
    以指导生成更好的测试并重新渲染。
  3. 需求冲突——两个功能规范本质上相互矛盾。必须修改其中一个或两个规范以解决冲突,然后再重新渲染。
冲突规范是代价最高的结果,应主动预防。在编写或修改功能规范时,仔细考虑每个规范与所有先前规范的交互方式。如果存在模糊之处,为规范添加明确的细节以消除任何可能与早期规范冲突的解释。

Common mistakes

常见错误

  • Usage of concepts before defining them
BAD
***functional specs***
- Implement :Message:
GOOD
***definitions***
- :Message: is an interface of communication between two users. 

***functional specs***
- Implement :Message:
  • Cyclic definitons
BAD
***definitions***
- :Message: has an :Author:
- :Author: can create a :Message:
GOOD
***definitions***
- :Message: is an interface of communication between two users. 
- :Author: can create a :Message:
  • Conflicting implementation requirements
BAD — both reqs in the same module
***implementation reqs***
- :Implementation: should be in python
- :Implementation: should be in react
GOOD — split into two independent root modules
backend.plain
***implementation reqs***
- :Implementation: should be in python
frontend.plain
***implementation reqs***
- :Implementation: should be in react
  • 在定义概念之前使用概念
错误示例
***functional specs***
- Implement :Message:
正确示例
***definitions***
- :Message: is an interface of communication between two users. 

***functional specs***
- Implement :Message:
  • 循环定义
错误示例
***definitions***
- :Message: has an :Author:
- :Author: can create a :Message:
正确示例
***definitions***
- :Message: is an interface of communication between two users. 
- :Author: can create a :Message:
  • 冲突的实现要求
错误示例——同一模块中的两个要求
***implementation reqs***
- :Implementation: should be in python
- :Implementation: should be in react
正确示例——拆分为两个独立的根模块
backend.plain
***implementation reqs***
- :Implementation: should be in python
frontend.plain
***implementation reqs***
- :Implementation: should be in react