Curia 2.2 — 可移植的文档管理方案:两根布局、9 类文档、3 行头块、零依赖检查器与脚手架,人和 AI agent 按同一套规则读写。
简体中文 | English
目录:是什么 · 适合谁 · 核心设计 · 装上去 · 跑完怎么确认装对了 · 采用到已有仓库 · 常见问题 · 维护这个包的人读这里
中文是默认语言,也是维护契约的权威版本;English 为逐节对照翻译,如有歧义以中文为准。
Curia 2.2 是一套可移植的文档管理方案:两根布局——.agents/ 管过程与制度(计划、调研、决策笔记、技能、归档),docs/ 管外部事实与知识;9 类文档各有家与骨架;类型化文档统一带 3 行头块;配 1 支零依赖检查脚本 check-docs.mjs(Node ≥ 18,不联网)。采用后,文档放哪、长什么样、怎么引用、过时了怎么退役,都有明文,人和 agent 按同一份规则读写。
create-curia 是它的本地分发包,也是一个生成器:把载荷复制进你的仓库、建好三条桥符号链接(CLAUDE.md、.claude/skills、.codex/skills——让 Claude Code、Codex 等 CLI 直接读到规则与技能)、打印下一步。它不提问、不装钩子、不运行载荷内任何脚本;一次跑完即 fork-and-own——生成后的文档树归你维护,本包没有 upgrade / check 驻留子命令。
要不要用,看两条:你想让项目的文档有固定归属、固定格式,且希望人和 agent 按同一套规则帮你写与查——用它;你在找流程引擎、门禁体系、审批流或权限模型——这些不在 Curia 里,规范开篇就写明它不是治理平台、流程引擎或门禁体系。上手成本低:有 Node ≥ 18 就跑生成器;没有也能用——直拷载荷、只采用文本约定(见「备选:直接拷贝」)。
适合。
- 多个会话(人 + Claude Code / Codex 等)同时写文档,开始出现「这份放哪、那份过时没有」的分歧。
- 想让文档结构可机械检查,但不想引入平台、服务、数据库或 CI 依赖。
- 接受 fork-and-own:拿走一份载荷,之后按自己的节奏维护。
不适合。
- 需要审批流、权限模型、发布门禁、注册表或流程引擎——Curia 明确不提供这些。
- 想找文档自动生成、迁移工具或批量修复——本包不做。
- 团队还没有任何文档习惯,指望一套目录结构自动解决协作问题。
| 根 | 装什么 | 不装什么 |
|---|---|---|
.agents/ |
过程与制度:计划、调研、决策笔记、技能、归档。 | 外部事实登记、面向最终读者的发布内容。 |
docs/ |
知识与外部事实:仓外系统的登记与定义(资源 ID、字段映射、端点、协议);当前版即唯一真相,无状态机。 | 计划与提案、决策因果、原始事实快照。 |
受管面封顶这两根,不再设第三根;项目可以只启用其中一根。
- 没做的 →
plans/(proposal / spec / plan) - 做完的 →
research/(survey / evidence / postmortem / reference) - 为什么 →
notes/(决策因果,永远不进 research) - 过时的 → 状态翻转优先,冷历史进
archive/封存;永不删除
补:可复用的干法 → skills/(skill / runbook);外部事实 → 根 docs/。
| Type | 家 | 是什么 | 建议节 |
|---|---|---|---|
proposal |
plans/ |
立项前对齐:为什么值得做、做成什么样算成功、有哪些候选路线。 | 背景、目标、成功判据、方案选项 |
spec |
plans/ |
长期契约:做成什么样——字段、接口、验收标准;done 不是退役。 |
背景、范围、契约、验收标准、适用边界 |
plan |
plans/ |
分几步做、做到哪了;active 时「未完成事项」是唯一活跃清单。 |
背景、步骤、未完成事项 |
survey |
research/ |
一次性调研:问题、方法、结论与适用边界。 | 问题、方法与来源、结论、证据与出处、适用边界 |
evidence |
research/ |
原始事实快照:什么时候、用什么命令、看到了什么。 | 采集内容、采集方式、适用边界 |
postmortem |
research/ |
事故复盘:时间线、因果链、影响、预防与行动项。 | 事故概述、时间线、影响、因果链、预防、行动项 |
reference |
research/ |
长期事实参考:持续维护的映射与导览。 | 事实映射、方法与来源、维护方式、适用边界 |
runbook |
skills/ |
人向操作手册:前置条件、步骤、每步验证、失败恢复。 | 前置条件、步骤、验证、失败与恢复 |
skill |
skills/ |
Agent 按需加载的工作流:frontmatter 加步骤加边界。 | 步骤、边界 |
「建议节」是骨架默认,不是机器配额;检查脚本不查节的完整性。类型表是上界不是配额——短文档为主的项目可以删减类型与建议节。
类型化文档(plans/、research/、skills/ 家内的 Markdown)统一带固定三行,顺序不可换:
# <标题>
Type: <9 类之一>
Status: <该类型允许值>
Updated: yyyy-mm-ddStatus: superseded 必须带 Superseded-by: <相对链接>;归档件必须带 Archived: yyyy-mm-dd。笔记用两行头块(标题加 Status:),docs/ 无头块。完整规则见载荷内 .agents/README.md。
node .agents/tools/check-docs.mjs # 报告模式:列发现,不失败
node .agents/tools/check-docs.mjs --strict # 严格模式:任一发现即失败只查机械子集:编码、头块与生命周期、家与命名、相对链接。零依赖、不联网、不执行文档内代码。检查通过不等于文档合规——节的完整性、事实真假、结论质量、时效新鲜度都不在机器判定面。
生成器会建三条符号链接,让不同 CLI 读到同一份规则与技能:
| 桥 | 指向 | 谁读 |
|---|---|---|
CLAUDE.md |
AGENTS.md |
Claude Code |
.claude/skills |
../.agents/skills |
Claude Code |
.codex/skills |
../.agents/skills |
Codex |
桥是指针,不藏内容;改规则只改 AGENTS.md 与 .agents/。
包里内容很少:bin/init.mjs(生成器)、template/(载荷,21 个文件加 3 条桥符号链接)、README.md、README.en.md、package.json、LICENSE——整个目录自足,拷到你机器上任意位置即可用,不需要网络与任何外部件。
前置只有一条:Node ≥ 18。用法如下(<包路径> 指包目录在你机器上的路径):
node <包路径>/bin/init.mjs --dir <目标仓路径>不指定 --dir 时,目标是当前工作目录——也可以先 cd 进目标仓,再运行包内入口。参数只有三个:
--dir <path>— 目标目录。--defaults— 兼容保留:init 无提问,等价默认行为。-h, --help— 帮助与退出码。
一次运行做三件事:复制 21 个载荷文件(检查脚本 check-docs.mjs 与 7 个 .gitkeep 占位都在这 21 件之内)、按需创建三条桥符号链接、打印「下一步」。既有文件一律不覆盖——被保留的、被占用的都会在输出里写明,见「采用到已有仓库」。
<目标仓>/
├── AGENTS.md # Curia 通用规则集(8 条)
├── CLAUDE.md # -> AGENTS.md(桥)
├── .agents/
│ ├── README.md # 规范全文(唯一权威,骨架文本在内)
│ ├── GLOSSARY.md # 术语种子
│ ├── notes/AGENTS.md # 笔记树入口(agent 原生读取)
│ ├── tools/check-docs.mjs
│ ├── plans/ research/ notes/ archive/ # 计划 / 调研 / 笔记 / 归档(各带 README 与 .gitkeep 占位)
│ └── skills/ # write-doc / write-note / prose-standard
├── .claude/skills # -> ../.agents/skills(桥)
├── .codex/skills # -> ../.agents/skills(桥)
└── docs/README.md # 外部事实与知识的根约定(按需启用,见文件内说明)
这就是全部新增:21 个文件加 3 条桥。宿主已有同名文件时,那几件保持原样、不在此列。
不跑生成器、把载荷原样拷过去也可以。源一定写 template/.——末尾的 /. 让 cp 连点目录一起拷;整目录拷进空仓即为可用工作区(21 个文件加 3 条桥链接):
cp -an template/. <目标仓>/cp -R template/* 会静默漏掉 .agents/、.claude/、.codex/ 三个点目录——退出码 0、不报错,落出一个没有框架的树;不要用这种写法。cp -R(不带 -n)还会覆盖宿主的同名文件——包括未提交的改动与未跟踪的文件,那些字节在 git 历史里也找不回来。直拷一律带 -n,且拷之前先 git status 确认没有未提交内容。
直拷的短处是不报告跳过了什么:目标已有同名文件时 cp -an 静默跳过(实测 macOS 的 BSD cp 以退出码 1 结束、stderr 为空),三条桥可能静默缺失——所以直拷后不能只看退出码,务必按「跑完怎么确认装对了」把三条桥数一遍。目标仓已有自己的 AGENTS.md、CLAUDE.md 或 .claude/skills 时,优先用 init:它会逐条报告哪条桥没接上。
另外,直拷只对源码树形态成立:npm pack 出的 tarball 载荷里没有 3 条桥符号链接,拿到 tarball 就一律走 init(见「分发边界」)。
第一看退出码与汇总行。空仓上汇总行应当是:
完成:目标 <路径> — 新建 21、已一致 0、保留 0;桥 3 条:新建 3、已就位 0、未接上 0
退出码:
| 退出码 | 含义 |
|---|---|
0 |
载荷落地且三条桥全部接上 |
1 |
复制失败——I/O 出错、落点解析到目标仓之外(.agents/、docs/ 之类被预先做成指向仓外的符号链接)、或落点在另一个 git 仓里(.agents/ 是子模块或嵌套克隆) |
2 |
用法与守卫(参数写错、目标落在包内、Node 版本不足等) |
3 |
桥未接上——写操作完成但机制没接上 |
判据是退出码 0 与「未接上 0」这两项;汇总行里的「新建 / 已一致 / 保留」随宿主形状变(有主仓上「新建」不会是 21、「保留」不会是 0),别拿它们逐字对。
把 init 接进 CI 时:三条桥路径都空闲时退出码 0、流水线照常;宿主已有桥路径被占用时返回 3、流水线变红——接入前先按「采用到已有仓库」清障。注意:退出码 0 回答的是「没有复制失败、三条桥全部接上」——「保留 N 项」不改变它;宿主原有的 AGENTS.md 被保留时,8 条通用规则集并没有装上,见下一节。
第二,核对三条桥都接上了:
ls -l CLAUDE.md .claude/skills .codex/skills三条都要在,且分别指向 AGENTS.md、../.agents/skills、../.agents/skills。光看 readlink 不够——要打开它:ls .claude/skills 应能列出 write-doc、write-note、prose-standard 三个技能目录。字符串对而打不开是可能的——父级目录(如 .claude)本身是指向仓外的符号链接时,链接会被建到仓外、按落点解析后悬空。init 现在按「真解析得到、且落在目标仓内」判定,所以它报「未接上」就是真没接上;这种形态下它也不会在仓外写任何东西。
第三,跑一次自检(在目标仓根):
node .agents/tools/check-docs.mjs --strict退出码 0 即通过。判据是退出码,不是篇数——纯载荷树输出「检查 12 篇 / 告警 0 条」,你往 docs/ 放一份自己的文档就变 13 篇,篇数随你的树走。
第四,重跑幂等:对同一目标再跑一次 init,退出码应回到 0 且「未接上」为 0(空仓上是「新建 0、已一致 21、保留 0;桥 3 条:新建 0、已就位 3、未接上 0」;有主仓上数字随保留项变化)——重跑是安全的,已一致的文件会自动跳过。
init 的原则是不覆盖既有文件——这对安全是好事,但意味着「退出码 0」不等于「一切都装上了」。逐种情况说清。
宿主已有自己的 AGENTS.md(真实宿主最常见)。 init 会保留它并列入「已存在且内容不同,未覆盖」,载荷里的 8 条通用规则集不会装入——三条桥照常接上,退出码仍可能是 0。规则集需自行并入:把包内 template/AGENTS.md 的「通用规则」「桥」「结构速览」三节一起并进你的 AGENTS.md(三节要同批并——「下一步」里让你补的项目名占位在「结构速览」里,只并「通用规则」会把那条指引指空)。核对一条就够:在你的 AGENTS.md 里搜「一事实一归属」(规则第 1 条)——搜不到就是没装上。并入之后,「已存在且内容不同,未覆盖」这条清单每次重跑都会照旧出现,属预期,不是没生效。
宿主已有 CLAUDE.md(真文件,不是桥链接)。 桥建不上,init 会逐条警告并具名列出,汇总退出码 3。做法:把它备份移走、内容并入 AGENTS.md,重跑 init(已一致的文件自动跳过)。桥的语义是 CLAUDE.md 成为 AGENTS.md 的指针——指针不藏内容。
宿主已有 .claude/skills 目录(或指向别处的链接)。 同样退出码 3。做法:把其中有用的技能内容迁入 .agents/skills/,备份移走占用路径,重跑 init。init 打印的手工命令形如 mv <路径> <路径>.pre-curia && ln -s <目标> <路径>,可直接照抄(先备份再建链,两条一起跑)。不要自己写裸的 ln -s <目标> <路径>:路径已是目录时,它会在目录里建出一个同名子链接、退出码 0,桥却没接上——静默假成功。
宿主根上 .claude 或 .codex 本身是指向仓外的符号链接(dotfiles 共享配置的常见形态)。这时相对目标 ../.agents/skills 会按落点解析到仓外、必然悬空,init 直接判「未接上」并说明父级解析到了哪里,不在仓外写任何东西。要接上得先决定那个符号链接的去留(删掉换真目录,或换一种暴露方式)。这是本包早期版本的又一个静默假成功:桥被建到仓外、readlink 字符串逐字正确、退出码 0、自检全绿,而桥是死的且根本不在仓里。
宿主已有 docs/README.md。 影响最小:它是 docs/ 的根约定,该目录按需启用、不启用也不影响 .agents/ 完整使用——并入与否都不挡路。
备份放哪。 没有约定:挑一个仓外位置(如 ~/.curia-backup-<日期>/)或仓内已被忽略的目录。init 不管备份,也不删你的任何东西——mv 那一步是你自己执行的。
宿主把 .agents/ 或 docs/ 预先做成了指向仓外的符号链接。 载荷会整份写到仓外、git 完全看不见——本包早期版本在这里报「完成」、退出码 0。现在落点按「上溯到最近的存在祖先再真解析」判定,落在仓外一律拦下、逐件具名、退出码 1,一个字节都不写到仓外(嵌套形态如 .agents/plans/ 指向仓外同样拦)。修法同样是先决定那些符号链接的去留,再重跑。
宿主把 .agents/ 做成了 git 子模块(或嵌套克隆)。 载荷会整份写进那个仓的工作树,宿主仓 git 永远追不到它(git add -A 只登记子模块指针)——而 init 会报「完成」。落点判据现已扩到 git 边界:落点在另一个 git 仓里一律拦下、逐件具名、退出码 1。做法:先决定 .agents 是留作子模块还是换成普通目录。
没有 Node ≥ 18 又有占用路径。 这一组合最麻烦:直拷不报告冲突、init 要 Node。出路是手工挑拣——把 template/ 下的载荷件逐件比对后拷进去,再手工补三条桥 ln -s。
宿主已有存量文档树要纳入 Curia。 纳入纪律(登记根、规则不回溯、闸门只挂默认面等六条)见规范「存量树只读纳管」节(目标仓 .agents/README.md,包内 template/.agents/README.md);本包不提供迁移工具与批量修复。
处理完任何一项,都用「跑完怎么确认装对了」的桥核对与自检收尾。
它是运行时或平台吗? 不是。没有服务、数据库、网络调用、权限模型或审批流;检查器只做静态自检。
会覆盖我已有的文件吗? 不会。已有且内容不同的文件一律保留并列出;桥被占用时打印手工命令后跳过。
没有 Node ≥ 18 能用吗? 能。直拷 template/. 并手工补三条桥,或只采用文本约定、不跑检查器。
9 类文档必须全用吗? 不必。类型表是上界不是配额;可以删减类型与建议节,只保留你实际会写的。
生成后还能升级吗? 没有 upgrade 子命令。fork-and-own:重跑 init 到新目录做 diff,或 git cherry-pick。
能用在 monorepo 吗? 能。工作区根放在你希望管理的目录;默认扫描该根的 .agents/ 与 docs/,其他根用显式路径巡检。
检查器会改我的文件吗? 不会。只报不改,没有 --fix,从不写回文件。
采用方读到这里就够了;以下各节讲这个包自身的契约、发布与验收。
本仓是 Curia MD 的公开母仓,独立于任何采用方发展。本仓只保留公开分发包所需的内容:规范、检查脚本、脚手架、双语 README 与元数据,不包含设计史料、研究笔记或内部实现记录。
- 生成器,不是运行时:
init只复制载荷、建桥与打印下一步——不提问、不装钩子、不运行载荷内任何脚本;生成物不import本包任何模块。 - 零依赖:本包
dependencies为空,只用 Node 内置模块(Node ≥ 18);载荷脚本同样零依赖、不联网。 - fork-and-own:生成后目标仓与本包再无关系;没有
upgrade/check驻留子命令,同步靠重开 init 到新目录 diff,或 git cherry-pick。 - 不覆盖:目标已存在且内容不同的文件一律保留并列出;桥被占用时打印「先备份移走、再
ln -s」的手工命令后跳过。
package.json 的 version 是制品身份,不是依赖关系——包内任何文件的改动必须同批跳 patch 号,使「create-curia@<版本>」唯一对应一份字节。判据是制品不是载荷:只改本 README 也要跳号,因为 tarball 的字节变了。此前 0.3.0 曾跨三份不同字节(2.2 落地之后载荷又改过两次),「无损迁移」因此没有可核对的参照物;本纪律自 0.3.1 起生效。
发布制品时记下 shasum -a 512,采用方记一行「采用 create-curia@<版本>,sha512-…」即获得可复核的身份。发布前另跑一次核对(npm pack 丢符号链接是预期行为,不是错误):
npm pack --pack-destination <tmp>
tar -xzf <tmp>/create-curia-<版本>.tgz -C <tmp>
diff -rq <tmp>/package/template template # 预期差异恰为 3 条桥(CLAUDE.md、.claude/skills、.codex/skills)以下判据逐条可跑,验收对象是载荷自身,不构成采用方义务;check-docs 指载荷内 .agents/tools/check-docs.mjs。
- 空集断言:
mkdir -p /tmp/empty-docs && node .agents/tools/check-docs.mjs --strict /tmp/empty-docs→ 输出「未检查任何文件」,退出码 2。 - 正例:在载荷树(
template/)或载荷生成的仓库里、于该树根下跑node .agents/tools/check-docs.mjs --strict→ 退出码 0。 - 根锚点断言:在自身不含
.agents/的目录里跑默认调用(如cd /tmp && node <路径>/check-docs.mjs)→ 输出「根锚点缺失」,退出码 2,不静默降级、不向父目录上溯。 - 零依赖:
rg -n "from ['\"]|require\(|import\(" template/.agents/tools/check-docs.mjs→ 只应出现node:前缀的内置模块名。 - 可移植:把
.agents/拷进无 git、无node_modules的临时目录,跑node .agents/tools/check-docs.mjs --strict→ 与源目录同一判定。 - 桥断言:
init到空宿主 → 退出码 0、汇总行「桥 3 条:新建 3、已就位 0、未接上 0」,对同一空宿主重跑 → 「新建 0、已一致 21、保留 0;桥 3 条:新建 0、已就位 3、未接上 0」、退出码 0;init到已有CLAUDE.md真文件的宿主 → 退出码 3 并具名列出未接上的桥(该宿主重跑仍为退出码 3——占用路径没清障前不会自己变好)。 - 退出码断言:
node bin/init.mjs --bogus→ 退出码 2;node bin/init.mjs --dir <包内路径>→ 退出码 2;node bin/init.mjs --help→ 退出码 0。 - 版本纪律目检(发布前,人工):
git log -1 --stat显示本仓有改动(template/、bin/、README.md任一),而package.json的version未随之跳号 → 制品身份与字节脱钩,不许发布。 - 桥解析断言:
init到「.claude是指向目标仓之外符号链接」的宿主 → 退出码 3、具名报出「父级目录……解析到目标仓之外」,且仓外那个目录零写入(前后ls -A计数不变)。 - 结构组假绿断言:造一棵只有
.agents/README.md与.agents/tools/check-docs.mjs、五个家(plans/ research/ notes/ skills/ archive/)一个都没有的树,跑默认调用 → 汇总行带「结构组无判定对象」(是标注,退出码仍按告警数走)。 - 面外类型化断言:把一份带
Type: plan三行头块的文档放进docs/、同内容放.agents/plans/→ 组 2 只报docs/里那份(「类型plan的文档住在.agents/之外」),--strict退出码 1。 - 落点断言:宿主把
.agents/(或docs/,或嵌套的.agents/plans/)预先做成指向仓外的符号链接 →init退出码1、逐件具名「落点……解析到目标仓之外」,且仓外那个目录零写入(前后ls -A计数不变)。 - git 边界断言:宿主把
.agents做成 git 子模块 →init退出码1、逐件具名「在另一个 git 仓里」,且子模块工作树零写入。
本包不分发到公共 registry,分发方式是源码入口、载荷直拷与发布制品(tarball,见「版本纪律」)三条。发布制品以 curia-v<版本> tag 与 tarball asset 的形态发布在本仓;本仓的公开分发从 create-curia@0.3.16(curia-v0.3.16)起,更早的版本与设计史料不随公开仓分发。package.json 的 private: true 是常设的防误发限制,不是待移除的过渡标记——不要移除它。
npm pack 会静默丢弃载荷里的 3 个桥符号链接(源载荷 3 条 → tarball 内 0 条,零 warning)——tarball 采用方一律走 init(建桥由桥步完成,不依赖载荷里的链接),或手工补三条 ln -s;从 tarball 直拷得到的是零桥工作区。
包名 create-curia 在公共 registry 无人占用,而机器默认指向公共源——一旦被抢注,npm create curia 会在配置如默认的机器上静默拉取并执行第三方代码。本包因此不宣传 npm create curia 这条用法(它在本包不发布的前提下本来也不可用)。repository 字段的占位值不影响使用。
MIT,见 LICENSE。