spec 1.0 · 规范性
内容类别
Leji 定义了五个逻辑内容类别。分类依据是文档为何存在,而不是存放位置:类别名称是供清单、索引和工具使用的稳定标识符,目录名则由团队自行决定。
五个类别#
| 类别 | 属于它的内容 |
|---|---|
domain |
用团队自己的话表达的业务语言与产品语义:核心名词是什么意思、它们之间如何关联、哪些词带有本地含义。业务状态类记录(某项合作的状态、一份市场快照)也归入这里,作为记录。 |
system |
架构及其不变量:服务边界、数据归属、集成契约、一致性模型、失败契约,以及每一次变更都要遵守的约束。技术评估与系统读数归入这里,作为记录。 |
practice |
会被自动套用的约定与模式:代码约定、测试模式,以及已被验证有效的提示词与工作流模式(见下文的沉淀门槛)。方法执行过程的记录(一次复盘、一份操作手册执行日志)归入这里,作为记录。 |
governance |
智能体护栏与运作规则:智能体在无人提示时可以做什么、什么需要人来把关、数据处理规则、上报触发条件、合规控制。治理证据(审计日志、评审报告)归入这里,作为记录。 |
decisions |
带日期的记录,说明事情为何如此,见 decisions.md。 |
意图与记录#
每一份受治理的文档,要么是意图,要么是记录,与它属于哪个类别无关:
- 意图是持续维护的当下真实:术语表、不变量、约定、护栏。读者依赖它是当前有效的,因此当现实发生变化时,文档要被修正。复核期限与时效机制存在的意义,正是为了意图(见 governance.md)。
- 记录在一个明确的时间或事件边界内保存主张:状态、评估、台账、读数、会议结论、归档。后续状态取代记录,而不是修正它;原件仍然是关于它那个时点的有效陈述。记录以其日期标示时效,而不是以复核期限标示。
判断分类时,只需问一个问题:*如果后续信息与这份文档不一致,是因为读者依赖它当前有效,所以必须修正文档;还是因为新信息取代了它,而原文在当时依然成立?*前者是意图,后者是记录。
记录受到与意图完全相同的治理:被索引、被评审、有归属、被路由。区别在于读者可以拿它做什么:读者不得把记录当作当前意图;它是带日期的证据(见 context-layer.md 的“读取一份记录”)。决策记录是记录的正式子类型:它们本质上就是记录,并拥有自己的 schema 与生命周期,见 decisions.md。
关于记录的一些问题被刻意排除在 1.0 之外,且被公开承认而非掩盖:不存在机器层面的记录系列概念(因此工具绝不会认定哪一份记录是“最新的”),没有流式新近度机制(下一份应有的记录是否已经逾期),也没有针对实质上混合了意图与记录内容的文档的章节级类型。混合文档应当拆分;在拆分代价过高时,按下游读者主要依赖的契约来分类。确实无法归入任何类别的内容留作参考资料;分类并不承诺免于判断。
要求#
- 清单必须把它声明的每个类别映射到一个或多个相对仓库根目录的索引文件(
categories.<id>.indexes);每个索引文件应当落在所声明的上下文根目录之下,见 context-layer.md。索引文件声明的是纳入,而不是搬迁:内容仍留在团队原本存放的地方(例如business/、technology/、architecture/),并且同一个目录可以在不做任何重命名的前提下,向多个类别贡献文档。 - 索引文件是经过整理的 markdown,其中携带一个或多个
leji-index围栏代码块。一个块以三个或更多反引号加上该块的信息串的一行开启,并由下一行三个或更多反引号关闭;关闭围栏的反引号数量不必与开启围栏一致。合法的信息串恰好有三种:leji-index(意图块)、leji-index intent(同上,显式写法),以及leji-index record(记录块,其条目解析为记录)。leji-index之后出现任何其他 token 都是解析错误,绝不会被悄悄忽略:这套文法在设计上是有限的。每个块以每行一条的方式列出内容,形如- path: <相对仓库根目录的路径>,其中路径可以是一个目录(其 markdown 被递归纳入)或单个 markdown 文件。路径必须是相对仓库根目录的 POSIX 路径:前导/、..片段或反斜杠都是非法的,会被拒绝。空行与整行#注释被忽略,条目可以在其后携带一个由空白引出的# 注释。这套文法中的空白只有 ASCII 空格(U+0020)与制表符(U+0009),别无其他,在文法所有需要判定空白的位置都是如此:围栏反引号与信息串周围、条目行的前导与尾随填充,以及引出尾随注释的#之前。解析前会先剥除前导的 UTF-8 字节序标记。行按 LF 切分,容忍尾随的 CR,文件为 UTF-8。实现不得在此处使用运行时的空白字符类:任何被某个运行时恰好归为空白的其他字符,包括 U+0085 与 U+00A0,都是普通的路径内容,因此携带这类字符的条目会被报告为路径缺失,而不是被悄悄裁剪。boot-profile.md 的leji-mounts块冻结在同一套字符集上,因此同一个扫描器可以读取两套文法,三个实现也不会对“这里到底有没有围栏”产生分歧。同一文件中的多个块按文档顺序拼接。块前后允许出现散文与标题,因此索引文件同时也是该类别的人类可读地图。扫描是按行进行的,不考察 markdown 结构:一行若在可选的空格或制表符缩进之后带有三个或更多反引号与该标签,无论它位于文档何处,都会开启一个真实的块,包括位于更长的围栏示例内部或列表项内部。因此,只用于举例而非声明的示例要用不同的标签围栏,绝不能在leji-index之后多加一个 token:扫描器匹配的正是标签,所以leji-index example会开启一个真实的块并报出解析错误,而标签为text的围栏什么也不会开启。推荐位置是上下文根目录下的context/<id>.md;该位置可配置,工具绝不将其写死。 - 上下文层必须至少映射
domain或system,再加上decisions,才能声明任何一致性级别(见 conformance.md),并且已填充的domain/system最小集合必须至少包含一份意图文档:只由记录构成的上下文层保存了历史,却不承载任何运作上下文。其余类别随团队遇到真实问题而逐步积累;不得为了满足检查清单而映射一个空类别(其索引文件解析不出任何文档)。 - 一份文档解析到恰好一个类别与一种类型。索引条目是选择器,解析遵循选择器特定性:直接的文件选择器胜过任何目录选择器,更深的目录选择器胜过其祖先目录选择器。覆盖某份文档的最具体选择器决定它的类别与它所在块的类型;一份文档若被较宽泛的选择器覆盖、却由更具体的选择器胜出,那它就根本不属于那个宽泛选择器的内容(记录目录中某个需要保持最新的文件,或某个更大映射树里某个团队自己的决策日志,正是这样在不搬动任何东西的前提下表达出来的)。同等特定性的选择器若对类别或类型意见不一,是错误,绝不按索引顺序来裁决;同等特定性下相同的赋值只解析一次,而同一个索引文件内字面重复的条目会被拒绝。工具应当提示那种“所覆盖的每份文档都被更具体选择器夺走”的选择器(被遮蔽的选择器):那是这张整理过的地图上的冗余,但绝不是错误。除此之外解析是确定性的:目录条目按 POSIX 字典序(依 Unicode 码点;推荐路径保持 ASCII,使顺序在各实现间无歧义)展开为其中的 markdown,而任何真实位置(解析符号链接之后)逃出仓库根目录的路径会被排除,而不是被跟随。索引条目(见 machine-readable-surface.md)携带类别标识符与类型。
- 文档可以在 frontmatter 中声明自己的类型(
kind: intent或kind: record);frontmatter 覆盖胜出选择器所在块的类型,绝不覆盖类别。其他任何kind值都是错误。决策记录不带kind键(它们的 schema 是封闭的,且本质上就是记录)。记录可以携带 frontmatter 中的date(YYYY-MM-DD);工具只从该字段读取记录的日期,绝不从散文、标题约定或文件名读取。记录不得携带freshness.reviewAfter(复核期限是意图的机制;出现在记录上就是在承诺一份文档不可能具备的时效性,这是错误)。 - 描述提示词或工作流模式的 practice 内容应当在该模式至少奏效两次之后再沉淀(“两次验证”门槛)。过早沉淀正是 practice 目录被愿景填满的原因。
注记(非规范性)#
并非每个类别从第一天起就要存在。最小可用的上下文层,只需包含最初一个月的工作真正依赖的内容。设置类别,是为了让人或智能体能够判断“这属于哪一种真实”,进而只加载当前任务所需的内容,而不是整棵文档树。
之所以区分这两种类型,是因为真实仓库的文档通常由两套相互交织、真值模型不同的语料组成。若将偏运营的部分强行归入意图语义,只会导致两种结果:要么作出无法兑现的时效承诺,要么将仓库中的大量内容排除在治理之外。下面是一个完整示例,其中在记录目录内保留了一项意图例外:
# Domain context
```leji-index
- path: docs/glossary.md
```
Operational state is governed as records; the escalation policy stays intent.
```leji-index record
- path: docs/operations/
```
```leji-index intent
- path: docs/operations/escalation-policy.md
```