# 约定（Conventions）

> 这些是硬约定。`journal.py check` 按本文件实现校验；偏离前先想清楚代价，偏离后要在记录里写明。

## 目录布局

记录体系收在**一个**容器目录里：过程记录、台账、经验层、日志都在它下面。

```
<项目根>/
├── README.md                 ← 项目 README（加一行指向 work_log/README.md）
└── work_log/                 ← 容器（默认名，可配置）
    ├── README.md             ← 台账（建议）：索引表 + 同主题簇 + 滚动待办 + 当前状态（可选）
    ├── 0001-<slug>.md        ← 过程记录，**就在容器根**（编号式；一篇 = 一个可交付单元）
    ├── YYYY-MM-DD-<slug>.md  ← 过程记录（日期式，同一容器里建议只用一种）
    ├── STATE-HISTORY.md      ← 被替换下来的旧「当前状态」块
    ├── LEGACY.md             ← 旧记录声明（可选，见「渐进原则」）
    ├── _inbox.md             ← 踩坑当场记一行（可选；**不是记录**，收尾时整理进 lessons/）
    ├── 目标.md               ← 长期目标 → 阶段的两层表（可选，见「目标」；需要时才建）
    ├── .config.json          ← 项目配置文件（可选，见「项目配置文件」；缺它 = 全部内置默认）
    ├── <YYYY>/…              ← 按年分卷后的记录（`split --by-year`）
    ├── <stage>/…             ← 归档记录：阶段目录直接建在容器下（短项目就够）
    ├── archive/…             ← 归档层（可选，多主题长项目用它把归档收在一处）
    │   ├── <阶段>/…             两种都行：archive/<阶段>/（一层）
    │   ├── <主题>/<阶段>/…      或 archive/<主题>/<阶段>/（两层，主题可选）
    │   ├── README.md            归档索引：一行一个阶段（**人写为主**，`archive --stage` 只补行）
    │   └── ARCHIVE.md           遗留的旧归档索引（**可选、可删**；见「辅助文档的落点」）
    ├── lessons/              ← 经验层（分册索引 + 分册 + 阶段复盘）
    │   ├── README.md
    │   ├── 01-<topic>.md
    │   └── 99-retrospectives.md
    └── logs/                           ← 日志与运行产物（按来源分子目录，见「日志归位要求」）
        └── <来源>/
```

- 容器名默认 `work_log/`，可用 `--work-log NAME`（旧写法 `--journal NAME`，等价）改名；
  经验子目录默认 `lessons/`，可用 `--lessons NAME` 改名。
- **记录一律住容器根**（分卷后是 `<YYYY>/…`）
  ——正好是旧布局 `journal/0001-x.md` / `journal/2026/0001-x.md` 的下一层，记录发现逻辑不变。
- **归档 = 搬进容器下的归档目录**：默认直接建阶段目录（`work_log/<stage>/`），也可以先建一个
  **归档层** `archive/` 把全部归档收在一处（`archive/<阶段>/` 或 `archive/<主题>/<阶段>/`）——
  两种都只是「容器下的一个子目录」，工具对它们没有任何区别对待，选哪种由项目自己定。
  由此推出一条判据（`journal.py` 的 `is_active_entry` / `is_archived_entry` 就是它）：
  **相对容器路径的第一段是 4 位年份 ⇒ 活跃；其余子目录 ⇒ 已归档**。
  `lessons/`、`logs/` 是容器内的**非记录**目录；`README.md`、`STATE-HISTORY.md`、`ARCHIVE.md`、
  `LEGACY.md`、`_inbox.md`、`目标.md` 也不是记录（**住在 `archive/` 里的 `ARCHIVE.md` / `README.md` 同样不是**，
  文件名不对形或路径被排除）——它们各自按文件名与路径排除，绝不能被当成「一个已归档阶段」。
  ⚠ 这条判据的代价是：**不要在容器下另建目录来放记录**（草稿也放容器根）。
  容器下的子目录只有这几类——年份卷 `<YYYY>/`、归档阶段 `<stage>/`、可选的归档层 `archive/`、
  以及 `lessons/`、`logs/`；外加**可选、按需生成**的 `memory-cards/`（卡投影的**生成物**，
  用完就删，不建议常驻 —— 2026-10-17 起工作区里不再保留它）；在别处塞记录文件会被当成「已归档」。
  **日期式记录不参与归档索引的篇号/篇数两列**（它们没有篇号）——见下面「归档索引」一节。
- **归档可以多级**（`--stage <主题>/<阶段>`，2026-10-14 起）：长期项目里"一阶段一个平铺目录"会平铺一大片，
  所以 `--stage` 接受带 `/` 的相对路径，**逐段**套用上面那套名字规则，另加两条防越界：
  `.` / `..` 段一律拒绝、绝对路径一律拒绝。归档表与索引里写的都是**完整相对路径**
  （`研究/01-采样/`，或用了归档层时的 `archive/研究/01-采样/`）；
  同一个目录**分几次**归档只占**一行**（篇号与篇数按磁盘重算，日期取并集）。`index compact --depth N` 折的是
  「标题里 `/` 恰好 N−1 个」的小节，所以折叠层级 = **路径段数 + 1**：
  平铺 `### 05-设置页返工/` 用 `--depth 2`，用了主题 `### 工作记录/05-设置页返工/` 用 `--depth 3`，
  用了归档层 `### archive/工作记录/05-设置页返工/` 用 `--depth 4`（工具会提示"要折得加 `--depth N`"）。


### 子目录自述：每个子目录有自己的 `README.md`

容器根台账**不重复子目录里的条目**：根 README 只写一句指针（这个目录装什么、去哪个文件看明细），
明细住在子目录自己的 `README.md` 里。这样台账的体积不随归档量增长 —— 冷数据不进热面。

| 子目录 | 自己的索引 | 容器根 README 该写什么 |
|---|---|---|
| `archive/` | `archive/README.md`（一行一个阶段：目录 / 篇号 / 时间 / 内容 / 归档判据） | 一句指针 + 两条主题线的名字 |
| `lessons/` | `lessons/README.md`（分册索引） | 一句指针 |
| `logs/` | `logs/README.md`（按来源分类） | 需要时按需引用，不必常驻 |
| `memory-cards/`（仅按需生成，默认不存在） | `INDEX.md` + `*.pointer.md` / `*.full.md`（**全是生成物**，整目录可删） | 不需要常驻；要用时现生成、用完删 |

**归档明细的落点是 `archive/README.md`，不是容器根**（2026-10-16 起）。它**不是**旧的
`ARCHIVE.md`：旧表标题是 `# 归档区`、写的是**相对容器根**的路径（`archive/worklog/01-…/`）；
新索引标题是 `# work_log/archive/ —— 已归档记录`、那一列写的是**相对 `archive/`** 的路径
（`worklog/01-…/`）。两者混起来会得出全错的读数，所以 `check` 用**标题**区分它们。

这份新索引是**人写为主、工具只补行**：自述、备注、别的表格都随人写，`archive --stage`
只在表里补/并一行（同一个阶段分几次归档仍是**一行**；日期区间取并集，**篇号与篇数按目录里
实际有几篇编号式记录重算** —— 工具不做算术，见 [templates.md](templates.md) §3）；
文件不存在才建一份最小骨架（标题 + 表头 + 第一行）。**人写的「归档判据」会被保留** ——
工具合并那一行时沿用原值，只有它空着才填默认的 `阶段收口`（2026-10-17 改：
从前这里写"会被重置成默认值"，那是把当时的缺陷写成了规格；判据本来就是人写的那一列，
工具没有理由丢掉它）。「篇号」仍按同一个方法重算：连续写区间、有断档写穷举。



### 辅助文档的落点

两份辅助文档留容器根（状态史、冷存清单）；归档索引早就搬去 `archive/README.md` 了，
容器根那份 `ARCHIVE.md` 是**遗留产物**：

| 文档 | 落点 | 谁写 | 谁读 |
|---|---|---|---|
| 状态历史 | `<容器>/STATE-HISTORY.md`（旧：`archive/STATUS-HISTORY.md`） | `status --roll` | 人 |
| 冷存清单 | `<容器>/COLD-STORE.md`（旧：`archive/COLD-STORE.md`） | `prune --zip --apply` | 人 |
| 归档索引 | **`<容器>/archive/README.md`**（一行一个阶段；见上节） | 人 + `archive --stage`（只补行） | 人 |
| 遗留归档索引 | `<容器>/ARCHIVE.md` 或 `<容器>/archive/ARCHIVE.md`（标题 `# 归档区`） | **没人写了**（见下） | `check` 只为提示 |

沿用既有文件而不是另起一份，是**不把同一段历史劈成两份**（判据在 `journal.py:_aux_path`，
它只管状态史与冷存清单这两份 —— 归档索引已经不走它了）。
要换落点，**搬文件**即可（搬到新位置就按新的算）；几种布局都跑得动。

**旧的 `ARCHIVE.md` 已经退场**（2026-10-16）：它是**只写不读**的产物 —— `index compact`
折归档行读的是 README 索引里的那些小节 + 直接数磁盘篇号（`stage_numbers`），从不读这张表；
而它记的「目录 / 篇号区间 / 篇数」在 README 索引里**早有一行**（一小节一行）。两份就是双写，
双写必然漂移：实测 `ops/08-other-plugins/` 被拆成三条主题线之后，旧表那一行没人改，表就成了假的。

所以现在的规矩是：

- **`archive` 不再写它** —— 归档时改为维护 `archive/README.md` 的那张表（只补行、不动正文）；
- 活跃记录的索引行由 `index sync` 维护，归档小节的折叠由 `index compact --stage X` 负责；
- 老容器里已有的那份，`check` **只出一条 INFO**（顺便点出「表指空」「目录没登记」两类漂移），
  **不报 ERROR/WARN** —— 一份可选、可删、与 `archive/README.md` 重复的产物，不该让任何容器变红；
- 确认 `archive/README.md` 齐全后，**直接删掉它**即可（工具不会再建）。

### 两种命名约定（工具两种都识别）



| 约定 | 文件名 | H1 必须写成 | 实测范围 |
|---|---|---|---|
| **编号式** | `NNN-<slug>.md`，编号 1–4 位（也允许纯编号 `0007.md`） | `# NNN · <标题>` | 原型 207 篇 + `comfy` 30 篇 |
| **日期式** | `YYYY-MM-DD-<slug>.md`，日期必填 | `# YYYY-MM-DD <标题>`；日期后可接一个行内实验号，如 `# 2026-09-20 R21：标题` | `embeding try` 71 篇（68 篇带日期） |

规则：

1. **H1 与文件名必须一致**。编号式比篇号，日期式比日期；不一致是 error。
2. **文件名两种都不匹配的，不是记录**，工具直接忽略——不半解析、不猜。
   这一条是必需的：`2026-09-02-x.md` 若被 `^(\d+)-` 认成「编号 2026」，同一容器里
   所有日期式文件会互相重号，报出「编号 2026 重复」这种假错误（实测 11 个文件踩过）。
3. **编号单调递增、永不复用、归档不改号**——**只在编号式容器里生效**。日期式容器不分配编号。
4. 同一日期可以有多篇（实测 `2_multi_attention` 一天 8 篇），靠标题区分；日期式容器内排序天然按日期。
5. **一个容器内建议只用一种**：混用会让排序与索引变乱。工具两种都认，混用**不报错**，只给一句 info。
6. 标题用中文，可含英文/代码词；避免 Windows 非法字符 `\ / : * ? " < > |`。
7. 回指地址：**编号式写 `wl/NNNN`**，**日期式写文件名**（`2026-09-06-门控两段式.md`）。
   **不引入短锚点编号**——文件名/编号已经唯一自明，再加一层是多余。

### 旧布局的读取回退（只读，不迁移）

老项目还在 `journal/` + 顶层 `lessons/` 时，**不改名、不移动、不警告**，直接按下面的顺序解析：

| 解析对象 | 顺序 |
|---|---|
| 容器 | 显式 `--work-log NAME` / `--journal NAME` → `<根>/work_log/` → `<根>/journal/` → `<根>/work-log/` |
| 经验目录 | 显式 `--lessons NAME` → 配置文件里的 `lessons` → `<容器>/lessons/` → `<根>/lessons/` |

- 归档目录两种写法等价看待：`journal/archive/<stage>/`（旧）与 `work_log/archive/<stage>/`（新，
  即用了归档层）都按「第一段不是年份 ⇒ 已归档」判为归档，**无需特判** —— `archive` 只是容器下的一个
  普通子目录名，不占特殊语义；用不用它由项目决定（见「目录布局」）。
- 旧布局的 `journal/archive/STATUS-HISTORY.md`、`journal/archive/README.md`、
  `journal/archive/COLD-STORE.md` 已存在时**继续沿用原文件**（`status --roll`、`archive`、`prune` 都如此），
  避免把同一段历史劈成两份。文件不存在时才落到容器根的新位置。
- 迁移留给人：用户明确要求时再搬；工具不做自动迁移。

## 项目配置文件（`<容器>/.config.json`）

项目级的工具行为存在**容器根**的一个文件里。它可选，随项目进版本控制，**改完立即生效**（不必重启任何东西）。

```json
{
  "mode": "digest",
  "container": "work_log",
  "lessons": "lessons",
  "legacy": ["0007-*", "archive/**"],
  "snapshotEntries": 12
}
```

| 字段 | 类型 | 内置默认 | 管什么 |
|---|---|---|---|
| `mode` | `full` / `session` / `digest` / `milestone` | `full` | 台账**没有** `精细度` 字段时的默认档（见「记录精细度」） |
| `container` | string | `work_log` | **只是备注**：新项目的容器该叫什么 |
| `lessons` | string | `lessons` | 经验目录的候选名 |
| `legacy` | string[] | `[]` | 渐进原则的旧记录清单（同 `--legacy`） |
| `snapshotEntries` | number | `12` | `snapshot` 的默认显示篇数 |

### 优先级只有三层

```
命令行参数  >  <容器>/.config.json  >  内置默认
```

不重叠、不叠加。**缺这个文件不是错误**——它等于"全部取内置默认"，行为与今天完全一致。
逐字段看生效值与来源用 `python scripts/journal.py config`。

### `container` / `lessons` 的不对称（重要）

这两个字段与其他字段**不一样**：它们命名的正是配置文件**自己所在的目录**。
读配置文件之前就得先知道它们，所以它们只能按目录发现。

- `container`：生效值永远来自目录发现（`--work-log` → `work_log/` → `journal/` → `work-log/`）。
  配置文件里写的值**改不了它**——它连自己所在的目录都指不动。不一致时**只报、不用、不报错**。
- `lessons`：顺序是 `--lessons` → 配置里的 `lessons` → `<容器>/lessons/` → `<根>/lessons/`。
  每个候选名都先在容器内找、再在项目根找（旧布局）。
  配置里的名字只是"上哪儿找"的提示，**目录真的存在才算数**；不一致时以实际目录为准并报出来。

所以这两个字段的正确读法是"**新项目该叫什么**"，不是"这个项目现在叫什么"。
这也正是插件在项目初始化时把它们写进配置文件的原因。
已经建好的项目改名不会被追认：决定读哪儿的一直是目录本身。

### `mode` 与台账的关系

`mode` 有**两个不同的问题**，不要混：

| 问题 | 答案在哪 | 谁写 |
|---|---|---|
| 这个项目**当前**用哪档 | 台账 `## 当前状态` 的 `精细度` 字段 | `journal.py mode --set` |
| **新项目**默认用哪档 | 配置文件 `.config.json` 的 `mode` | 初始化时写入；`journal.py config --set mode=…` |

台账**有** `精细度` → 台账说了算，配置的 `mode` 不参与。
台账**没有** → 用配置的 `mode`；配置也没有才用内置默认 `full`。

**代价（要知道）**：改配置的 `mode` 不会追溯已经跑起来的项目——那些项目的台账里通常已经有 `精细度`。
这是刻意的，否则会出现"我改了默认档，老项目行为突然变了"。

## 两个计数器（不要混）

| 名字 | 含义 | 在哪 | 规则 |
|---|---|---|---|
| **篇号** | 记录文件编号，索引与回指的地址（**只有编号式容器有**） | 文件名 + 标题 `# 0007 · ...` | 每写一篇 +1；永不复用；跨归档不变 |
| **迭代号** | 项目层面的工作单元编号（一个可交付的迭代） | 记录入口的迭代字段（默认 `迭代: N`） | 一个迭代可以对应多篇（如"执行 + 验收修复"）；一次性/不编号的工作写 `迭代: -` |

回指：编号式写 `wl/NNNN`（篇号），日期式写文件名。不要写"迭代 N"，否则两者会混淆。

**两个计数器独立发展，从不对应。** 原型 `README.md` 曾写"编号与变更集对应"，
实测**推翻**：`变更集` 号等于篇号 **0 例**；篇 207 → 变更集 182（差 25）；有 `变更集` 字段的只有 26/162，
其中 **146 篇写「变更集：无」**。这条铁律（篇号与迭代号分开）存在的意义正是**防止**你以为它们对应。

`变更集` 只是迭代字段的一个**别名**，而且是**可选**的：它是软件项目的叫法，
实测 146 篇写「无」说明这份记录远不止追踪代码变更——它是**工作记录**，不是变更日志。
缺这个字段**不算错**。

**迭代字段的别名**（解析时都认，写入时用默认的 `迭代：`）：`迭代 / 变更集 / 批次 / 阶段 / 版本 / 里程碑`。
不同领域用不同叫法：软件用「变更集/版本」，研究用「批次/阶段」，写作/设计可能用「里程碑」。

## 记录精细度（一篇 = 什么）

一篇记录覆盖多长的工作，由**台账里的一个字段**决定，不靠临时商量。

| 档位 | 一篇 = | 一次会话的预期产出 |
|---|---|---|
| **`full`（默认）** | 一个可交付的子单元 | 2–3 篇；同一天多篇很正常 |
| `session` | 一次会话，或一个可交付成果 | 最多 1 篇；同一会话的第二件事**追加到同一篇** |
| `digest` | 一个阶段（跨多次会话） | 多次会话并成一篇 |
| `milestone` | 一个阶段收口 | 只留决策与经验 |

**为什么默认是 `full`**：实测原型 111 篇带日期的记录分布在 12 个日期上（平均 9.2 篇/天，
最多一天 **34 篇**），`embeding try` 也有一天 8 篇。`session` 档会把那 34 篇压成一篇——
那不是更简洁，那是把 34 个可独立查阅的单元埋进一个文件。粗档位留给确实低强度的项目。

**存在哪**：台账 `## 当前状态` 的第八个固定字段 `精细度`。值一律写拉丁规范值
（`full` / `session` / `digest` / `milestone`）；命令也认中文别名（`完整` / `会话` / `摘要` / `里程碑`），
写入时规范化成拉丁值。可选的一行原因跟在同一个值里：`session（原因：阶段收口）`。

不在状态块里的情况**不算错**：字段缺失、或值认不出来时按默认档 `full` 处理，
`mode` 会说明「这是默认档」。所以这次改动之前写下的台账一律照常工作。

**不变量：档位只放宽「要不要另起一篇」，不放宽验证要求。**
任何档位下，一篇记录都必须有可核对的验证小节（见「验证段（必填）」）。
档位影响的是记录**数量**，以及可选叙述写多细——不是能不能少写验证。

**粗档位的额外义务**：`digest` / `milestone` 必须写明这一轮**刻意没有记录什么**，
例如 `未记录：本轮的两处配置调整，见提交 abc1234`。写在正文任意位置即可，
写成 `## 未记录` 小节或行内一句都认（`lint` 认 `未记录` / `未记` / `未收录` 三种写法）。
理由：粗档位把多轮会话并成一篇，一定有东西没进正文；不写这一句，读者无法区分
「确实没有」和「忘了记」。

命令：

```bash
python scripts/journal.py mode                     # 看当前档位 + 一行释义
python scripts/journal.py mode --set digest        # 切档位
python scripts/journal.py mode --why "阶段收口"     # 只补一行原因，不动档位
```

- `--set` 拒绝未知值（非零退出码），不改任何文件。`--dry-run` 只打印将要写入的值。
- 没有容器时与其他命令一样报「找不到记录容器」并退出 1。
- 档位随容器走：进版本控制，`brief` 里直接看得到。

**怎么切**：会话里口头说一句（"这次记详细点" → `full`）只管这一轮；
要让之后的会话都按新档位走，就改台账（命令如上）。
新项目的初始档位由 `.config.json` 的 `mode` 提供（见「项目配置文件」）；台账一旦有 `精细度`，台账说了算。

## 记录生命周期

```
草稿（进行中）  →  收口（closed）  →  （可选）归档
```

- **草稿**：标题下方元数据可边做边补；允许写"未验证/待确认"。
- **收口**：收尾清单（见 SKILL.md）全部完成后才算 closed。收口后**不改写已发生的结论**。
- **更正**：新证据推翻旧结论时，**追加** `## 更正` 段（日期 / 原结论 / 新证据 / 现结论），并同步台账与 lessons；
  绝不允许把旧文字直接抹掉。
- **归档**：阶段收口、索引表停止增长时，把整阶段的记录 move 到 `work_log/<stage>/`；
  同步改索引链接、把索引里那一节折成一行（`journal.py index compact --stage …`），并跑一次 `journal.py check`。

## 日志归位要求

**任何项目产生的持久日志 / 运行产物都进容器，并且分类存放**：

- 位置固定为 `work_log/logs/`，**一个来源一个子目录**：`work_log/logs/<工具名>/`、`work_log/logs/<服务名>/`。
  例：`work_log/logs/build/`（构建输出）、`work_log/logs/crawler/`（采集记录）、`work_log/logs/bench/`（压测结果）。
- **不许**把 trace、`.log`、导出文件随手丢在项目根，也**不许**写到项目之外（家目录、`/tmp`、桌面、
  系统临时目录）——那等于把证据放在版本控制与备份之外，别人接手时找不到。
- 日志目录**不是记录**：`logs/` 下的文件不编号、不进索引表、不参与编号断档检查
  （`journal.py` 的扫描明确跳过 `logs/` 与 `lessons/`）。日志与记录的关系是"记录引用日志"，
  不是"日志也算一篇"：在记录里写清路径与关键结论即可。
- **测试用的临时夹具不属于这里**：丢弃型 fixture、一次性样本、可随时重建的中间产物，不是记录，
  不要写进 `work_log/`（更不要提交）。
- **日志里不得出现绝对机器路径**（`C:\Users\<人名>\...`、`/home/<人名>/...`）。记录本来就要可移植，
  日志同理：一律写**相对项目根**的路径，或写明环境变量；脱敏后再落盘。

## 整理与清理（量增长后）

**证据不删**是硬规则：清理 = 移走 + 汇总 + 留清单，不是删除。

| 动作 | 命令 | 说明 |
|---|---|---|
| 阶段归档 | `journal.py archive --stage X --from N --to M` | 移动 + 全仓链接重写 + 死链自检；再 `index compact` 折索引行 |
| 索引瘦身 | `journal.py index compact` | 已归档小节折叠成一行区间；混合/死链小节自动跳过 |
| 按年分卷 | `journal.py split --by-year` | 移进 `work_log/<YYYY>/`；`wl/NNNN` 回指不受影响 |
| 冷存 | `journal.py prune [--zip Z] [--apply]` | 默认只报告；打包后才移出，并写 `COLD-STORE.md` |

判据与顺序：

1. **归档判据 = 阶段收口**（索引表不再增长），不用"多少天没引用"这类经验值。
2. 归档后立刻 `index compact`：否则索引会随归档线性膨胀（实测每篇约 600 字符）。
3. 冷存判据：已归档 + **未被 lessons 引用** + 早于 `--older-than`（默认 180 天）。
   未被引用 ≠ 没价值，所以 `prune` **默认只报告**，要人确认后才 `--apply`。
4. 搬家后必跑 `check`（0 死链）；有死链就报错退出（内容还在，git 可回退）。

> `wl/NNNN` 是纯编号引用，不受目录变化影响；这也是为什么归档/分卷不会破坏经验层的回指。

## 记录入口（元信息，**都建议、都不强制**）

两种真实写法，按容器里已有的风格选一种（也可以混写）：

**字段行式**（原型与 `comfy` 在用）：

```markdown
# 0007 · <一句话标题>

日期：YYYY-MM-DD
迭代：N                          # ← 只写一个标签；领域叫法见下
触发：<用户请求 / 缺陷 / 计划项，一句话>
范围：<改了什么、明确不做什么>
结论：<一句话结果 + 可核对的信息，如「抽样 30 条全部通过」「构建 0/0、测试 312 PASS」>
```

**引用块式**（实测 `embeding try` 在用）：

```markdown
# 2026-09-20 R21：<一句话标题>

> 状态：**已完成**／进行中／已放弃
> 工具：`<本次用到的脚本或命令>`
> 产物：`<产出的文件或数据>`
```

规则：

1. **字段名不固定，按本次情况选。** `日期 / 迭代 / 触发 / 范围 / 结论 / 来源 / 状态 / 工具 / 产物 / 环境`
   都是可认的字段名；解析时也认英文（`Date / Trigger / Scope / Conclusion / Source / Status`）。
2. **`日期：` 是可选字段。** 日期式记录的日期在**文件名与 H1**里，两处都有就不该另写一行；
   把日期同时写到三处也不算错。真正没有任何日期（文件名、H1、入口行都没有）才会报缺日期。
   —— 编号式容器里建议保留 `日期：`，因为文件名里没有日期。
3. **迭代行只写一个标签**。`迭代 / 变更集 / 批次 / 阶段 / 版本 / 里程碑`（英文 `Iteration / Milestone`）
   是**可互换的同义词**，解析时认全部，但**不能写成 `迭代 / 批次：3`**——那是多个标签并排，
   解析器只认 `标签：值`，会整行读不出来。挑你领域的那个词，其余用的时候说明一次即可。
4. **`变更集` 是可选的**，缺了不算错（实测 162 篇里 146 篇写「无」）。
5. **新记录建议写入口，旧记录不要求补。**

## 小节结构（自由）

**不规定**节次的数量、名称与顺序。唯一硬要求：

> **一个验证类小节**：标题含
> `验证 / 复核 / 检查 / 评审 / 结果 / 证据 / 评估 / 确认 / 实测 / 审查 / 实验 / 判定 / 评测`
> 之一（英文 `Verification / Review / Results / Evidence / Tests`），h2–h4 都算。

为什么松动：实测 `embeding try` 71 篇里出现了 **409 个不同的 `##` 标题**，而最高频的那个也只出现 2 次
——结构是长出来的，强行套模板只会让人绕开它。原型那套六段式（背景→事实→方案→执行→验证→遗留）
**仍然可用，但现在是"模板之一"**（见 [templates.md](templates.md)），不是必填。

## 验证段（必填）

必须留下**可核对**的东西（不必是命令——数字、百分比、引用输出、表格、样本、评审意见都算）：

```markdown
## 验证

- 方式：<命令 / 数据 / 引用 / 样本 / 评审>
- 结果：<实测输出要点 / 通过数 / 数字 / 结论>
- 未覆盖：<明说没验的部分与原因；不许写"应该没问题">
```

**光有标题不够，只有设置也不算。** `## 评测设置` 这种标题**只交代做法**，
所以不能拿一句命令顶数：必须有实测数字/百分比、或结果表格。判据与已知误报风险见
[commands.md](commands.md) 的「验证小节实质要求」。默认只报 **WARN**，`--strict` 才抬成 ERROR
——工具只提醒，判决由人下。

只有"构建通过"或"我觉得没问题"不算验证。软件项目给可复现命令与通过数；研究项目给样本量与数据；
写作/设计项目给评审意见与处理结果。

**这一节在任何档位下都必填**（见「记录精细度」）。另外，`digest` / `milestone` 档位下还要写明
这一轮**刻意没有记录什么**，例如：

```markdown
未记录：本轮的两处配置调整，见提交 abc1234。
```

## 台账（`work_log/README.md`，**建议但不强制**）

台账的职责是"这条线的背景与关键事实 + 导航"。实测五条线里有四条**根本没有台账文件**，
记录照样在写——所以缺台账只报 info，不是错误。

```markdown
# 记录 / Worklog

本文件夹用于记录 <这条线在做什么>。

## 项目背景
- 目标：
- 方案：

## 关键事实（已核实）
- <核实过的、以后会反复用到的结论>

## 文件索引
### A. <阶段名>（YYYY-MM-DD ~ ）
| 文件 | 方面 |
|---|---|
| [0001-示例.md](0001-示例.md) | <一句话> |

## 同主题簇
- **<主题>**：0007、0012 —— <一句话演进>

## 待办（滚动清单）
- [ ] <未完成项>

## 当前状态（YYYY-MM-DD）

- 阶段 / 版本：
- 迭代：
- 产出：
- 核对 / 验证：
- 交付物与指纹：
- 环境：
- 阻塞 / 等待：
- 精细度：full
```

规则：

1. **`## 当前状态` 是容器自选的**。台账里**有**它 → `check` 校验新鲜度（日期不早于最后一篇记录）
   且全文件**只能有一个**；台账里**没有** → 只报一句 info，**不报错**。
   —— 实测 `embeding try` 把状态写在每篇开头的引用块里（`> 状态：…`），台账里根本没有状态块。
   三种形态（集中式 / 分散式 / 无）都得过得去。
2. **没有索引表也不算错**：整份台账缺 `## 文件索引` 时，`check` 只报一次 info
   （"索引层缺位，N 篇记录都没有索引行"），而不是逐篇报几十行"未出现在索引中"。
   有索引表时，漏索引才逐篇报 WARN。
3. 状态块只写**当前坐标**，不写历史；历史属于记录和 `STATE-HISTORY.md`。
4. `精细度` 是第八个字段：写 `full` / `session` / `digest` / `milestone`，可选附一行原因
   （见「记录精细度」）。**缺失不算错**，按默认档 `full` 处理。
5. 待办清单只留未完成项 + 最近完成的 3–5 项（`- [x]`），旧勾选项删掉，别越堆越长。
6. 索引表每行：`| [NNN-slug.md](NNN-slug.md) | 一句话方面 |`（日期式就是 `YYYY-MM-DD-slug.md`）；
   新增记录必须补一行。
7. 「同主题簇」：同一件事跨多篇时追加编号（日期式写日期或文件名），按发生顺序排。

## 目标（`work_log/目标.md`，**需要时才建**）

台账装得下「**一个**长期目标按阶段推进」：`## 项目背景` 的 `目标：` + 索引里的
`### <阶段名>` + 状态块的 `阶段 / 版本` 就是它。它装不下「**多个并行**长期目标各自处于
不同阶段」——状态块全文件只能有一个，里面的 `阶段 / 版本` 也只有一个。多个目标并行时才建这个文件；
只有一个目标时**不要建**，台账已经够了。

**它住在既有容器里，既不是新文件夹，也不是新容器。** 再开一个容器会破坏「一个项目一个容器」
这条告诉工具去哪找东西的约定，还会把目标的进展与作为证据的记录劈成两处——
同一件事两个答案，正是本项目反复在修的那类缺陷。

**缺这个文件是正常的**：三个真实语料里一个都没有，`check` 报 0 个问题。
它**不是必填项**，也**不进索引**（索引只管记录）；它是内容，所以也不写进 `.config.json`。

### 表格形式

两层：**目标 → 阶段**。一个目标有多个阶段时它占多行，**目标名只在首行写一次**，续行的目标格留空。

```markdown
# 目标

| 目标 | 阶段 | 状态 | 相关记录 |
|---|---|---|---|
| 让 worklog 可公开发布 | 规范重写 | 已完成 | 0007, 0012 |
|  | 设置页 | 进行中 | 0021 |
|  | 文档收尾 | 未开始 | — |
| 支持多语言 | — | 进行中 | 0009 |
```

| 列 | 必填 | 写法 |
|---|---|---|
| 目标 | **首行必填** | 长期目标名；续行留空 = 这一行是该目标的下一阶段 |
| 阶段 | 可选 | 该目标的一个阶段；单阶段目标写 `—` |
| 状态 | 必填 | `未开始` / `进行中` / `已完成` / `已放弃`（四选一） |
| 相关记录 | 可选 | 篇号，逗号分隔（`0007, 0012`；也认本体系通用的 `wl/0007`）；没有写 `—` |

### 状态是人工维护的

**不从引用的记录推导。** 推导规则一旦复杂就会算错，而算错的目标表比手写的更不可信。

### `check` 只校验三条机械规则

1. **相关记录里引用的篇号必须真实存在** —— 这是核心价值：否则目标表会悄悄腐化成一张
   写着不存在篇号的清单。只有**整格就是一篇号**（`0007` 或 `wl/0007`）才判；
   `—`、自由文字、日期式文件名一律跳过不判（否则日期式容器里的文件名会被误报成"篇号 2026"）。
2. **状态必须是那四个之一** —— 拼错或发明新状态就报出来。
3. **一个目标的第一行必须写目标名** —— 在第一个目标名之前出现空的目标格就报出来：
   那一行没有父行。

三条都是 **ERROR**（判据是机械的，不必开 `--strict`）。

**明确不校验**（判定不了，或校验了只会教人忽略告警）：

- 阶段名与索引里的 `### <阶段名>` 是否一致；
- 状态与记录内容是否相符（比如"引用篇号都已归档，状态还写进行中"）；
- 目标是否重复。

## 渐进原则（老记录只报不拦）

新写的记录按新规格判 error；**改动之前写下的记录只报 info**。
理由是实测结论：三个语料里老记录有 124 篇缺入口日期行、31 篇缺验证小节，
新规格一上来就全判 error，用户会面对一片红，然后学会忽略这个工具。

判定依据是**显式清单**，不是日期启发式（日期不可靠：实测同一份语料里日期字段只覆盖一半）。

| 来源 | 写法 | 优先级 |
|---|---|---|
| **命令行** | `check --legacy "0007-*,archive/**"`；`--legacy ""` 表示没有旧记录 | 最高（给了就以它为准，空串也算给了） |
| **项目配置文件** | `<容器>/.config.json` 的 `legacy` 数组 | 次高 |
| **容器声明** | 容器根 `LEGACY.md`，每行一条（`- 0007-*` / `- archive/**`）；支持 `#` 注释 | 中 |
| **规模兜底** | 记录 **≥ 5 篇**的容器整批算旧记录；**少于 5 篇**算新项目 | 最低（前三者都没有时） |

规模兜底为什么这么定：**新项目该红就红，老项目零配置也不该是一片红**。
实测三个语料是 30 / 71 / 207 篇，刚建的项目是 1–3 篇，所以用"记录还少不少"当信号
（**不是**用日期——日期字段在同一份语料里只覆盖一半）。
代价写在明处：这条兜底**宁可多报，也不放过新记录**，所以一个体量小的老项目会被当成新项目多报几条；
用 `LEGACY.md` 或配置文件里的 `legacy` 一次就能收准，要彻底按新格式判就用 `--legacy ""`
（配置文件里写 `"legacy": []` 是同一件事：显式声明"没有旧记录"）。

只有「因旧格式而失败」的三类发现受它影响：

| 规则 | 说的是什么 |
|---|---|
| **H1 形制** | H1 必须与文件名一致（编号式带篇号 / 日期式带日期） |
| **入口日期行** | 旧技能要求的 `日期：` 字段行 / `结论：` 为空 |
| **验证小节** | 旧技能要求的六段式里的验证小节（含"只有设置没有结果"） |

**死链、漏索引、编号重复/断档、lessons 来源**这些**从来就有**的客观规则**不受影响**，永远照报。
降级的条目照样打印，级别是 `[INFO]`；`check` 末尾还会告诉你降了几条，免得误以为问题消失了。

## 经验层（`work_log/lessons/`）

条目分**两级**，默认仍是一行条目；只有**复发过或代价极大**的才写卡式。

**第一级 · 一行条目**（默认，绝大多数）：

```markdown
- **<症状>**：<观察到的现象>。根因：<机制>。做法：<正确做法>。（`wl/0042`）
```

**第二级 · 卡式条目**（主行不变，后面跟**缩进子条目**：2 个空格或 1 个 tab）：

```markdown
- **<症状>**：<现象>。根因：<机制>。做法：<正确做法>。（`wl/0042`）
  - 触发：<任务描述里会出现的现象词，用「/」连同义词>
  - 验证：<怎么确认这次真的成了；别拿「没报错」当成了>
  - 别再来：<试过不通的路 1>（<报错原文 / 机制原因>）；<试过不通的路 2>（<原因>）
```

三个字段的语义：

- **触发**：写"任务里会出现的词"，不写学科名。反例 `触发：客户端插件样式`；正例
  `触发：改完 lib/client.js，邻居界面丢样式`。一条现象可以有多个同义词，用「/」分隔。
  只写**这台机器 / 这个宿主 / 这个项目**真踩过的，不写任何机器都成立的常识。
- **验证**：一句**可执行**的核对（命令、表达式、看到什么算成了），不是"注意一下"。
- **别再来**：反面清单 —— 试过不通的路，附报错原文或机制原因。这是卡相对技能体系的唯一增量
  （技能写"怎么做"，卡写"上次怎么趟过来的、哪条路是死的"）。多条用「；」分隔。

为什么是缩进子条目，而不是独立卡片文件或 H3 大块：不破坏现有列表与小节结构，`grep "触发："` 能直接命中，
**零常驻成本**；也不需要新目录 / 新索引，不会和 `lessons/` 形成第二套真相。

规则：

1. **每条必须有来源**（编号式 `wl/NNNN`、日期式文件名，或外部链接）；无来源的结论不许进 lessons。
   `check` 只校验编号式引用（`wl/NNNN` 能不能指到真实记录）；日期式容器靠人工核对文件名。
2. 只写**可复用**的结论，不复制记录里的过程与数字。
3. 分册边界清晰，按项目实际领域划分（示例：构建与环境、外部契约与接口、验证与安全、事故与流程、阶段复盘）。
4. 新增经验先写分册，再回填来源；来源编号必须真实存在（`check` 会校验）。
5. 阶段复盘（`99-retrospectives.md`）写阶段表、成果、可复用发现、遗留；**不要**在别处再写一份。
6. **卡式形制由 `check` 校验**：只认缩进（2 个空格或 1 个 tab）的 `- 触发：` 与 `- 验证：` 子条目；两者只出现一个
   报 WARN，`--strict` 下升为 ERROR。**按条目判** —— 同一分册里 A 条写触发、B 条写验证**不算**成对
   （这正是 2026-10-18 审查 M1 指出的静默放行）；WARN 会给出条目起始行号。
   旧条目里的「**触发条件**」不是这个形制，**不会**被命中。
   用 `journal.py lesson add --trigger … --verify … --avoid …` 生成，别手抄；`lesson add` 会拒绝
   **空白值**与**多行值**（多行会写坏缩进形制）。
   卡投影会读这三个字段：**触发 → 生效条件、验证 → 验证方式、别再来 → 卡尾独立一节**
   （「别再来」不是适用性边界，不写进「不适用条件」）；它们都不再被并进「执行」小节。
7. **采集时机**：收尾补写经验不可靠（实测"收尾补卡"两次都没自发）。踩坑当场先往容器根的
   `_inbox.md` 记一行，收尾时再整理成条目 —— 见 `templates.md` §9 的 `_inbox.md` 模板。

### 经验层之外：全局记忆（默认 `<用户主目录>/dsh-lore-memory/`）

经验层只在**这个工作区**里可见。要把一条确证过的教训带到**别的工作区**，走全局记忆那一层
（`python scripts/journal.py memory …`）—— 它是**同一件事的上一层**，不是另一套格式：
两边都是「一句话教训 + 来源」，只是准入与可见范围不同。

| | 工作区经验层 | 全局记忆 |
|---|---|---|
| 位置 | `<容器>/lessons/` | 默认 `<用户主目录>/dsh-lore-memory/`，可用 `memoryDir` 改（工作区**之外**） |
| 谁看得见 | 只有本工作区 | 每个工作区（**只能经工具**读） |
| 准入 | 必须带来源 | **来源分档** + 发布清单点名 |
| 出口 | —— | `<容器>/发布.md`（**唯一**获准被读的工作区文件，双向白名单） |
| 索引 | 分册索引 `README.md` | `INDEX.md`（**生成物**，只注入一笔） |

**两层之间是一道单向的门**：经验层里带 `wl/NNNN` 的条目会被 `memory publish`
抄成**草稿**清单行；人把它改成**能带走**的一句话（清单里那句话就是全局条目的正文，
全局层读不到别的文件），才由 `memory collect` 收进全局记忆。
所以「工作区里写过」**不等于**「外面看得到」—— 那道门由该工作区自己把着。

条目格式、来源分档、生命周期与校验表见 [memory.md](memory.md)。

## 索引与链接

- 相对链接一律相对**当前文件**；跨目录用 `../`。
- 归档后旧链接要改指 `<stage>/`；**git 提交信息里的旧路径不追改**（那是历史）。
- 每次收尾跑 `journal.py check`：死链、漏索引、标题形制、验证小节、无来源经验都会报出来。
  老记录只报 info（见「渐进原则」）。

## 命名与语言

- 记录文件名：两种约定之一（见「两种命名约定」），**建议 ASCII slug + 中文标题**；
  已有中文名不强制改。纯中文标题在编号式容器里会退化成 `0007.md`（篇号本身就是地址）。
- 脚本类新增文件遵守仓库既有编码约定（例如本工作区要求 `.ps1` 纯 ASCII）；Python/JS 用 UTF-8。
