# KB 变更目录时间前缀（change_id）

所有**新建** KB 变更目录（`/kb-propose`、`/kb-lite` 等）必须遵循本节；沿用已有目录的流程（`/kb-revise`、`/kb-design` 等）**不得**更换前缀。

## 格式

| 项 | 要求 |
|----|------|
| 目录名 | `<change_id>-<中文名称>` |
| `change_id` | **14 位数字**，`YYYYMMDDHHMMSS`（年月日时分秒，无分隔符、无 `-`/`:`） |
| 时区 | **Asia/Shanghai（UTC+8）** |
| 与 manifest 对齐 | 目录前缀与 `01-proposal.md` 头部「变更 ID」、首次写入的 `updated_at` 为**同一时刻** |

## 生成方式（必遵：先查重，再原子创建，最后自检）

创建目录由**单一子 Agent 串行执行**；主 Agent **禁止**为同一中文议题并行或重复派发「创建目录」子任务（重复派发是产生「2 份相同内容变更 ID」的首要原因）。执行者**必须**在 shell 获取前缀，**禁止**脑填、禁止用会话里的「今日日期」或 user_info 日期代替。

**必须在同一段 shell 中一次性完成「查重（含 tombstone）→ 取前缀 → 创建 → 唯一性自检」**，不得拆成多轮、不得在两段独立命令之间留出可被并发抢跑的窗口：

```bash
NAME="<中文名称>"
ARCHIVED="knowledge/变更/归档"
TOMBSTONES="$ARCHIVED/.tombstones"

# 1a) 查重：进行中 + 归档实体目录，命中即复用（绝不新建第二前缀）
EXIST="$(ls -d knowledge/变更/进行中/*-"$NAME" "$ARCHIVED"/*-"$NAME" 2>/dev/null | head -n1)"
if [ -n "$EXIST" ]; then
  KB_DIR="$EXIST"
  echo "已存在同名变更目录，复用（不新建）：$KB_DIR"   # 继续走 /kb-revise
else
  # 1b) 查重：purge tombstone 的 name 字段（同名中文名）
  TOMB_HIT=""
  if [ -d "$TOMBSTONES" ]; then
    TOMB_HIT="$(python3 -c "
import json, glob, sys
name = sys.argv[1]
for p in glob.glob(sys.argv[2] + '/*.json'):
    if p.endswith('_audit.jsonl'):
        continue
    try:
        d = json.load(open(p, encoding='utf-8'))
    except Exception:
        continue
    if d.get('name') == name:
        print(p)
        print(d.get('change_id',''))
        print(d.get('dirname',''))
        print(d.get('purged_at',''))
        break
" "$NAME" "$TOMBSTONES" 2>/dev/null)"
  fi
  if [ -n "$TOMB_HIT" ]; then
    echo "❌ 存在 purge tombstone（同名已批清），禁止静默新建 / 禁止把 KB_DIR 设成已删除的归档路径："
    echo "$TOMB_HIT"
    echo "请用户换名，或显式确认「作为全新同名议题」后再新建；未确认前停止。"
    # 勿 mkdir；勿设置 KB_DIR 为 $ARCHIVED/<旧dirname>
  else
    # 2) 仅当确无同名目录且无 tombstone 同名命中时，取前缀并创建
    KB_CHANGE_ID="$(TZ=Asia/Shanghai date +%Y%m%d%H%M%S)"   # 须为 14 位数字
    KB_DIR="knowledge/变更/进行中/${KB_CHANGE_ID}-${NAME}"
    mkdir -p "$KB_DIR"
    echo "已创建变更目录：$KB_DIR"
  fi
fi

# 3) 唯一性自检：进行中 + 归档下同名目录必须恰为 1 个（tombstone 阻断分支无 KB_DIR 时跳过）
if [ -n "${KB_DIR:-}" ]; then
  CNT="$(ls -d knowledge/变更/进行中/*-"$NAME" "$ARCHIVED"/*-"$NAME" 2>/dev/null | wc -l)"
  [ "$CNT" -eq 1 ] || echo "❌ 检测到 $CNT 个同名变更目录，禁止继续：须先合并/删除多余目录，仅保留一个再推进"
fi
```

**硬约束**：

- 若步骤 1b 输出 `❌`（tombstone 同名命中），**立即停止**：向用户展示 tombstone 路径与 `change_id` / `dirname` / `purged_at`；**禁止**静默新建；**禁止**把 `KB_DIR` 设成已不存在的归档路径。须用户确认换名，或显式说「作为全新同名议题」后再新建。
- 若步骤 3 输出 `❌`（同名目录 > 1），**立即停止**后续阶段，向用户报告重复目录清单并请其确认保留哪一个，清理后只在唯一目录继续；**禁止**在存在重复目录的情况下推进阶段 1～11。
- 子 Agent 必须向主 Agent 回报**实际**的 `KB_DIR`（复用还是新建）与唯一性自检结果；tombstone 阻断时明确回报「等待用户确认」；主 Agent 在收到回报前不得宣称已创建具体目录、不得再派发第二个创建任务。

`00-manifest.json` 的 `updated_at`（与目录创建同轮）：

```bash
TZ=Asia/Shanghai date +%Y-%m-%dT%H:%M:%S%z
# 示例：2026-05-30T11:28:35+0800
```

`01-proposal.md` 头部变更 ID 必须为：`${KB_CHANGE_ID}-<中文名称>`（与目录 basename 一致）。

## 禁止

- 手写、猜测、四舍五入到整点（如 `…120000`）、仅用 8 位日期（`YYYYMMDD`）
- 使用固定占位（如 `20260101000000`）或非当前时刻的历史前缀
- 为**同一中文议题**在 `进行中/` 再建第二个不同前缀目录（应继续在原目录用 `/kb-revise`）
- **未先查重就直接 `mkdir`**：必须按上节顺序「先 `ls` 查重命中则复用；再查 `.tombstones/*.json` 的 `name`」，未命中才创建
- **命中 purge tombstone 后静默新建**，或把 `KB_DIR` 设成已硬删的归档路径（细则 [kb-archive-purge.md](kb-archive-purge.md)）
- **主 Agent 并行或重复派发「创建目录」子任务**：同一中文议题只允许一个子 Agent 串行创建；已得到 `KB_DIR` 回报后不得再派发创建任务
- 主 Agent 在未看到子 Agent 回报的实际 `KB_CHANGE_ID` / `KB_DIR` 前，向用户宣称已创建的具体目录路径

## 创建后自检（必做）

格式自检与上节「唯一性自检」均须执行：

```bash
basename "$KB_DIR" | grep -Eq '^[0-9]{14}-[^/]+$' || echo "目录名不符合 YYYYMMDDHHMMSS-中文名称"
```

## 引用

- [`/kb-propose`](../../../commands/kb-propose.md) 步骤 1
- [`/kb-archive-purge`](../../../commands/kb-archive-purge.md) / [`kb-archive-purge.md`](kb-archive-purge.md)（tombstone 查重与批清）
- [`kb-external-sync.md`](kb-external-sync.md)（外部登记可选）
- [`kb-workflow` SKILL](../SKILL.md) 变更目录结构
