# spec —— 语义契约 ## 这个项目在解决什么 教研产出现在是一摞一次性的文档:写完就躺着,改一次要同步很多地方,没法校验、 没法复用、没法追溯。这个项目想把教研产出变成可校验、可复用、能派生多种成品 (讲义、教案、课件、归档)的工程化文件。 但光有工程文件还不够。它要变成一个能卖钱的产品,还得: - 工程文件这种形式,正好适合现在大热的 LLM assistant 来改,从而提效; - 但 LLM 判不了一节课合不合法(它不能真去跑 typst 编译器、不能可靠地断言数据合不合 schema),所以产品里还有一个 rule-based checker:它真跑工具、给确定性的诊断,补上 LLM 判不了的这块。LLM 提效 + checker 兜底,两者一起才是完整的产品闭环。 - 加上一些提升体验的小功能:飞书/企业微信集成、自动化提醒之类; - 如果要做 SaaS,还要有基本的管理概念:权限、LLM API 的配置和用量、费用等。 工程文件是起点,不是终点。 ## spec 是什么 spec/ 是产品语义的契约,用 Lean 写。它定义产品各部件"是什么意思"。 - 它是开发者和 coding assistant 共同的语义依据:两边对某个东西的理解,以这里为准。 - 它不进产品运行时。运行时真正做校验的那个东西用什么技术实现,还没定;但它的语义, 先在这里固定下来。 - 它精确、自洽,机器能校验它内部结构没有漏洞。它不保证实现一定符合它—— 实现和它是否一致,没有自动检查,要靠人工或 coding assistant 不定期(或每次改动后)核对。 ## 人机怎么一起干活 - 凭语义,不凭经验。这个领域新,coding assistant 在这里没有可靠的既有经验, 所以语义以 spec 的注释为准,不要凭训练先验臆测。 - 契约没写的,就是不存在的。遇到没定的点,提出来让开发者定,不要替它选答案。 - 改了实现或 spec 之后,两边是否还对得上,要核对一下;这一致性没有自动闸门。 ## 怎么往 spec 里写 ### 一条东西要不要进 spec 不写它,开发者和 coding assistant 会不会各自做出不同假设?会,就进;不会 (显然的事、纯基础设施、普通 CRUD 字段),就不进。 ### 进了之后,钉到多细 - 现在想清楚的,用 Lean 固定(声明、类型、关系)。 - 没想清楚的,两三句话 prose 占位,标 `OPEN`。等讨论或业务反馈后再细化, 细化结果可以再用 Lean 固定。 - 不在 Lean 里验证实现真的做了这些事。spec 只讲"要做什么、判什么";实现有没有真做, 不形式化证明。 - 形式化定理:不用写;写了不是坏事,但现在很少有能写的定理。定理本身得是产品语义, 不是"实现该满足的性质"。 ### 写的时候 - 每个顶层声明要带 doc 注释,用 `/-- ... -/` 写自然语言,说清这在领域里是什么、为什么。 注释是语义的依据,Lean 类型保证结构,两者缺一不可。 - 在注释里标这条语义的状态: - `PINNED`:已定,契约在此处权威。 - `OPEN`:故意没定。不要假设它的解,遇到要提出来讨论。 - `ADR-NNNN`:指向 `docs/adr/` 下的决策记录。 - 不用 `sorry`。没想清楚的,用 `OPEN` 标在注释里,不要用 `sorry` 假装成立—— 那会让构建通过,却在契约里埋一个谎。 - 不复述文件系统上能直接看到的东西(目录结构、文件名)。文件系统应当自描述,文档讲 context。 - 不写变更史(进 git/ADR)。不搬 ADR 内部黑话,要表达就直说。 不为设计选择辩护("刻意用 X 而非 Y"),只说是什么。 - "为什么"的背景考据(如某工具的内部机制)不进 spec,指向 ADR;但产品行为和设计模式要钉。 ### 命名 - 模块/命名空间:PascalCase,按分层,如 `Spec.System.Run`。 - 类型:PascalCase。 - 谓词(`Prop`):用意图清楚的词,如 `Legal…`、`ValidTransition`、`Can…`。 - 文件粒度:一个带独立不变式的概念一个文件。