# 0.6.2 计划：设计 RAG 层（审美参考库）

> 状态：**已实现（0.6.2）** · 目标版本：`0.6.2` · 前置：`0.6.1`（审美学习 v1）、`0.5.0`（断言化）
> 范围：设计卡片库 / 确定性检索 / 双 Agent 设计交互 / 四维闸门接入 / 反馈回路 / 新工具 `dwguard_design`。
> 一句话：**让两个 Agent 在「做什么样」这件事上引用同一份可追溯的参考，而不是各自凭印象。**

## 实现进度

| 阶段 | 内容 | 状态 |
| --- | --- | --- |
| P0 | `src/design.ts` + 第 15 个工具 `dwguard_design`（library init/ingest/list、search、stats）；生成只读 `query.mjs`；RAG-A/B/C/E 夹具测试 | ✅ 已实现 |
| P1 | `tasks/design-brief.md`、`inspiration` / `design-request` 协议 kind、dispatch 自动引用简报、status 列待应答请求 | ✅ 已实现 |
| P2 | `designRefs` 接入 `dwguard_spec` / `dwguard_audit`、报告「参考对照」列、`audit.json.design.refs` | ✅ 已实现 |
| P3 | feedback（useful/off/block/unblock）、capture 基线→项目卡、feedback.jsonl 账本 | ✅ 已实现 |

## 0. 为什么

`0.6.1` 解决了「记住用户确认过的审美取舍」（档案 + 基线），但没有解决「参考什么」：

- 审美判据仍然靠截图 + 个人经验，两个 Agent 没有共同的参考物；
- 执行者在编码时找不到方向，审计者在判定时找不到对照，争论只能回到「我觉得」；
- 通过验收的设计没有沉淀成可复用资产，下一轮又从零开始；
- 官方 UX 文档、规则库、用户收藏的灵感图各自散落，无法在需要的那一刻被检索到。

0.6.2 把「参考」变成一等公民：**可摄取、可检索、可引用、可反馈、可追溯**，而且同时服务 DSH（编排/审计）和 DevEco（实现）两侧。

## 1. 设计原则

1. **参考是证据，不是判据**：`designRef` 只能支撑讨论与对照，不能替代设备取证；判据仍然必须可证伪。
2. **确定性优先**：默认不联网、不调用模型、不引三方依赖；同一输入必须得到同一结果与同一分数。
3. **一个索引，两个读者**：DSH 走插件工具，DevEco 走生成的自足 CLI，读同一份 `.shared/design-library/`。
4. **可追溯**：每张卡片有 `source` + `license` + `sha256`；每条检索结果有分数分解，能解释「为什么它排第一」。
5. **反馈可撤销**：`useful / off / block` 只追加历史、调整权重或状态，不物理删除；被 block 的永不返回。
6. **失败降级**：库缺失、索引损坏、`query.mjs` 被改坏，都不影响 `dwguard_spec` / `dwguard_audit`，只降级为 warning。
7. **不制造权威**：检索结果带 `license: unknown` 或 `source` 缺失时，默认不进简报；引用它必须在报告里写明风险。

## 2. 数据模型与目录

```text
.shared/design-library/
├─ manifest.json          # schemaVersion / cards[] / builtAt / counts / engineSha256
├─ chunks.jsonl           # {chunkId, cardId, section, text, sha}
├─ index.json             # postings(term→[[chunkId,tf]]) / docLen / avgLen / idf / filters
├─ feedback.jsonl          # 追加式：useful / off / block / unblock / capture
├─ query.mjs               # 自足只读 CLI（无依赖，两个 Agent 都能跑）
├─ refs/<sha256>.<ext>     # 参考图规范副本（截图 / 灵感图）
└─ inbox/                  # 用户投递：<name>.png + <name>.meta.json（未摄取）
```

```jsonc
// manifest.json 里的一张卡片
{
  "id": "DSG-0007",
  "title": "杂志感卡片 · 大留白 + 20vp 圆角",
  "summary": "暖白底、较大行高、单张卡片只放一个主操作",
  "tags": ["card", "magazine", "light", "spacing"],
  "source": {
    "kind": "official",            // official | rule | user | project
    "ref": "华为 UX · 卡片式布局",
    "license": "CC-BY-4.0"         // 必填；unknown 默认不进 brief
  },
  "tokens": {
    "palette": [{ "name": "background", "value": "#FAF8F4" }],
    "spacing": [{ "name": "page", "value": 20, "unit": "vp" }],
    "radius": [{ "name": "card", "value": 20, "unit": "vp" }],
    "type": [{ "name": "body", "value": 16, "unit": "vp" }],
    "motion": [{ "name": "enter", "value": 240, "unit": "ms" }]
  },
  "rationale": "留白承担层级，圆角只做一处强调；不使用阴影叠加。",
  "dos": ["主操作唯一", "卡片内边距 ≥ 16vp"],
  "donts": ["同屏出现两种主色", "圆角与描边同时强调"],
  "images": [{ "path": "refs/9f2c….png", "sha256": "9f2c…", "alt": "首页卡片" }],
  "status": "active",              // active | blocked
  "weight": 1.0,                   // 0.5–1.5，反馈调整
  "createdAt": "2026-11-03T…",
  "updatedAt": "2026-11-03T…"
}
```

卡片按段落切成 chunk（`tokens` / `rationale` / `dos` / `donts`，每段 ≤ 400 字符），chunk 文本归一化（小写、折叠空白）后记 sha256；**同一来源字节重复摄取必须被跳过**。

## 3. 摄取（ingest）

| 来源 | 输入 | 产出 | 说明 |
| --- | --- | --- | --- |
| `rules` | `src/rules.ts` 的 `DW-AES-*` / `DW-A11Y-*` | 规则卡（阈值 + 官方 citation） | 冷启动种子，≥ 20 张 |
| `docs` | `docsDir` 里的官方 UX 文档 | 每篇 1 张卡（摘要 + 关键数值） | 复用 `dwguard_rules search` 的语料 |
| `inbox` | `.shared/design-library/inbox/` 图片 + `<name>.meta.json` | 用户灵感卡 | meta 必填 `title` / `tags` / `source` / `license`；缺 license 拒绝 |
| `project` | 通过审计 + 用户确认后的基线 | 项目模式卡（tokens 来自 `visual-baseline`） | `capture` 动作，需 `confirmed: true` |

约束：

- 所有路径经 `resolveSharedPath` 校验，禁止 `.shared` 外读写；
- 图片只做**复制 + sha256 + 头部尺寸解析**（PNG/JPEG 头 20 行），不提取像素特征（留给 0.6.3）；
- 同 sha256 只存一份；卡片 id（`DSG-0001`）递增且永不复用；
- 重复 `ingest` 同输入：`manifest.json` 字节不变（可断言），不产生新卡；
- `license: unknown` 的卡片可以入库，但默认**不进简报**、检索降权并给 warning。

## 4. 检索：确定性 BM25 + 结构化加权

### 4.1 分词

- CJK 走 **bigram**（「间距」→ `间距`、`距` 组合），英文/数字按词；查询与文档同一套；
- 极简停用词表（的 / 了 / 和 / 一个 / 我们…）；
- 同义词表从规则目录生成：间距 ↔ 留白 ↔ margin/padding/gap；圆角 ↔ radius/corner；字号 ↔ font/type/行高；配色 ↔ 主色/强调色/背景色；动效 ↔ 时长/easing。

### 4.2 打分

```text
score = weight × ( BM25(k1=1.2, b=0.75) + tokenBoost + profileBoost )
```

| 项 | 规则 |
| --- | --- |
| `bm25` | 标准 BM25，按 chunk 计分后取卡片内最大 chunk |
| `tokenBoost` | 查询含颜色/数值时，卡片 tokens 精确命中 +0.5；含领域词命中 tags +0.3 |
| `profileBoost` | 查询领域命中 `0.6.1` 审美档案的 active area，卡片同 area +0.3 |
| `weight` | 用户反馈：useful ×1.1、off ×0.8、block 直接排除；夹在 0.5–1.5 |

- 排序稳定：分数相同按 `cardId` 升序；输出 `score` 与 `breakdown`；
- 默认 `topK=5`；`maxChunks=2000`（超出警告，仍可查）；
- **默认不启用语义重排**：`designRerank: off` 预留；打开时必须可关闭，且报告里标注「模型辅助排序」；
- 性能预算：2000 chunks 单次查询 ≤ 300ms（本机，含读索引），索引构建 ≤ 2s。

### 4.3 输出

```jsonc
{
  "query": "深色 首页 卡片 间距",
  "specVersion": 3,
  "results": [
    {
      "cardId": "DSG-0012",
      "score": 4.82,
      "breakdown": { "bm25": 3.41, "tokenBoost": 0.6, "profileBoost": 0.3, "weight": 1.0 },
      "snippet": "深色模式下卡片边界用 1vp 描边代替阴影…",
      "tokens": { "radius": [{ "value": 16, "unit": "vp" }] },
      "images": [{ "path": "refs/….png", "sha256": "…" }],
      "source": { "kind": "official", "ref": "…", "license": "CC-BY-4.0" }
    }
  ]
}
```

结果只读、可 JSON 也可 Markdown 表；DSH 工具与生成 CLI 输出同构。

### 4.4 为什么默认不用向量数据库（以及什么时候用）

先分清三件事：**向量检索**（embedding + 余弦）、**向量数据库**（持久化 / 索引 / 过滤服务）、**语义模型**（产出 embedding 的模型或端点）。0.6.2 不上前两者，理由不是「向量不好」，而是**验收闸门需要确定性**：

| 维度 | BM25（0.6.2 默认） | 向量检索 + DB |
| --- | --- | --- |
| 可解释 / 可断言 | 分数可拆到每个 term；固定夹具能钉死 top-3 | 余弦分不可解释；模型或版本一变，邻居就变，回归测试无法钉死 |
| 依赖 / 安装 | 纯 Node、零依赖、离线可用 | 需要 embedding 端点或本地模型运行时（几十～几百 MB、平台相关） |
| 双 Agent 一致 | `query.mjs` 一个小文件即可复刻 | DevEco 侧也要有同样的模型/DB，无法保证 |
| 规模 | 2k chunks 内存内毫秒级 | 为 10^6+ 设计；2k 规模是杀鸡用牛刀 |
| 隐私 / 版权 | 数据不出 `.shared` | 远程 embedding 会把灵感图和文本发出去 |
| 中文领域词 | bigram + 同义词表足够（间距/圆角/字号/深色） | 语义泛化更好，但对「圆角 20vp」这类精确约束没有优势 |
| 图像灵感 | 不支持 | 需 CLIP 类模型；pHash/色彩直方图可零依赖先做 |

关键区分：**向量数据库 ≠ 向量检索**。几千条 chunk 用 `vectors.jsonl` + 纯 JS 暴力余弦就够，不需要 DB；DB 只在单库 chunk > 10k、或多项目共享库需要持久化/并发/过滤时才值得引入，且应作为**可重建缓存**（源数据仍是 `chunks.jsonl`）。

**分阶段路线**：

1. **0.6.2**：BM25 作为默认与兜底；`ranker` 接口预留 `lexical | hybrid`，检索结果结构里留 `rerank` 字段；
2. **0.6.3（可选）**：hybrid = BM25 先召回 top-30 → 可选 embedding 重排（DSH 侧，可关；DevEco CLI 保持 lexical）。报告标注 model id/version 与 `reranked: true`；**只有同一模型版本的结果才进回归基线**；
3. **视觉灵感**：先做 pHash（dHash）+ 色彩直方图（PNG 零依赖）解决「和这张图相似」；CLIP 类模型等有稳定本地运行时再评估；
4. **模型升级 = 重建索引 + 重新基线**，不迁移「旧分数」；索引随时可由 chunks 重建，避免把模型版本焊进数据。

如果 0.6.2 就要语义，也不上 DB：先做 `designRanker: hybrid` + 可选 embedding 端点 + 本地缓存，语义只做**重排**、不替代可断言的词法召回——RAG-B/E 的验收契约不变。

## 5. 双 Agent 设计交互（核心）

两个 Agent 不共享上下文，只共享 `.shared/`。0.6.2 用**一条索引 + 四个通道**让双方都能围绕设计对话：

### 5.1 简报（DSH → DevEco，主动）

`dwguard_design brief` 按任务包标题/需求/规格 + 审美档案领域检索，生成 `tasks/design-brief.md`：

- 3–5 张卡（id / 标题 / 截图或路径 / tokens / 一句理由 / do-don't / 来源 + license）；
- 每张卡带 `refId`，可在交付记录与审计 check 里引用；
- 同时在 `outbox-dsh.md` 追加 `inspiration` 记录（协议 v2.1 新 kind），`re=#d-N` 指向本轮 dispatch；
- `dwguard_dispatch` 生成的 Goal 简报里固定引用该文件；库为空时跳过并写一句「无参考库」。

### 5.2 自服务（DevEco 直接查，被动）

`library init` 生成 `.shared/design-library/query.mjs`（无依赖、只读、跨平台）：

```bash
node .shared/design-library/query.mjs "深色 卡片 间距" --top 5 --json
node .shared/design-library/query.mjs "圆角 20vp" --markdown
```

- 与工具同一份索引、同一套打分；`tests/design.test.mjs` 做 **parity 测试**（5 条查询的 top-5 id/score 必须完全一致）；
- 索引损坏/为空时 exit code 2 + 可操作提示；不写任何文件。

### 5.3 问答（DevEco → DSH，按需）

DevEco 在 `outbox-deveco.md` 追加 `design-request` 记录（协议 v2.1 新 kind）：

```markdown
## [2026-11-03T12:00:00.000Z] design-request · #q-1 · from=deveco to=dsh · re=#d-1 | 深色模式参考
- 场景: 深色模式 · 首页卡片 · 列表空态
- 期望: 3 张以内，偏克制
```

DSH 用 `dwguard_design brief --request #q-1` 应答：更新简报 + 回 `inspiration · re=#q-1`；`dwguard_status` 把未应答的 `design-request` 列入「待处理」，超过 `stallMinutes` 标卡住。

### 5.4 对照与反馈（DSH 审计 + 用户裁决）

- 审美判据/检查可带 `designRefs: ["DSG-0007"]`；审计与验收报告新增「参考对照」列：参考 tokens vs 实测 `actual`；
- 报告里的参考只是对照证据，驳回必须给设备证据（截图/布局树/数值）；
- `feedback`：用户说「这张参考不对味」→ `off`（降权）或 `block`（不再出现），追加 `feedback.jsonl`；
- 轮次通过 + 用户确认后 `capture`：把基线里的 tokens 存成 `project` 卡，下一轮/下个项目可检索到——**库越用越像这个项目**。

### 5.5 协议扩展（v2.1）

| kind | 方向 | 用途 | 回执 |
| --- | --- | --- | --- |
| `inspiration` | dsh → deveco | 简报就绪 / 问答应答（带 `re=`） | 可选 |
| `design-request` | deveco → dsh | 编码中的设计参考请求 | 必须回 `inspiration re=#q-N` |

老记录/老读法不受影响：未知 kind 只按普通记录计数，不参与回执判定。

## 6. 工作流落点

| 阶段 | 0.6.2 的变化 | 闸门/提示 |
| --- | --- | --- |
| 立项（intake） | 问到「想要什么风格/参考」；没有就明确说「无参考」 | 不阻塞 |
| 任务包（spec） | 审美判据可带 `designRefs`；库非空时生成简报 | ref 不存在 / blocked → **error**；审美 must 无 ref 且库非空 → `W_AEST_NO_DESIGN_REF` |
| 预审（prereview） | 检查简报是否生成、license 是否 unknown、参考与需求是否矛盾 | license 风险可记 blocker；参考冲突记 finding |
| 派发（dispatch） | Goal 简报包含 design-brief 路径与卡片摘要 | 无库时明确跳过 |
| 影子（shadow） | 不改 | — |
| 审计（audit） | check 可带 `designRef`；报告「参考对照」列；`audit.json` 记 `design.refs` + `retrievalQuery` | ref 不存在/blocked → **error**；命中参考却未引用任何 ref → warning |
| 返工（rework） | 返工单按失败判据附 1–3 条相关参考 | 参考不是免责理由 |
| 沉淀（knowledge） | 记录「哪张参考误导了」→ `off/block` + risk 条目 | — |

## 7. 工具 `dwguard_design`（第 15 个）

| action | 作用 | 约束 |
| --- | --- | --- |
| `library init` | 建目录、写 `query.mjs`、`manifest.json`（空库） | 只写 `.shared/design-library/` |
| `library ingest` | 摄取 `rules` / `docs` / `inbox` / `manifest` | 同 sha 去重；缺 license 拒绝 |
| `library list` | 列卡片（按状态/来源过滤） | 只读 |
| `library block` / `unblock` | 状态切换 + 历史 | 不删卡 |
| `search` | 检索（DSH 侧 / 调试） | 只读；输出分数分解 |
| `brief` | 生成 / 更新 `tasks/design-brief.md`；可带 `request` | 写简报 + outbox `inspiration` |
| `feedback` | useful / off / block | 追加 `feedback.jsonl`，改 weight/status |
| `capture` | 从通过审计的基线生成 project 卡 | 需 `confirmed: true` |
| `stats` | 卡片/块/索引/命中统计 | 只读 |

配置新增（`cordis.patch.yml`）：

```yaml
designLibraryDir: .shared/design-library
designTopK: 5
designBriefCards: 5
designMaxChunks: 2000
designLicenseRequired: true      # unknown license 的卡片是否禁止进简报/引用
designRanker: lexical            # lexical（默认，确定性）| hybrid（预留：BM25 召回 + 可选语义重排）
designEmbedding: {}              # 预留：{ endpoint, model, dim, cacheDir }；空 = 不做语义
designCaptureOnConfirm: false    # 通过确认后是否自动 capture（默认手动）
```

## 8. 验收标准（可证伪）

| ID | 判据 | 验证方式 |
| --- | --- | --- |
| RAG-A | 同输入重复 ingest：manifest 字节不变、无重复卡/块 | 单测：两次 ingest 后比对 sha256 |
| RAG-B | 固定夹具 24 卡 / 8 条查询：每条 top-3 命中指定卡；两次查询结果与分数完全一致 | 单测：precision@3 = 8/8 + 稳定性断言 |
| RAG-C | ingest/query 拒绝 `.shared` 外路径；query 不写文件 | 单测：路径穿越 + 只读断言 |
| RAG-D | `brief` 产出 3–5 张卡，字段含 id/标题/图片路径/tokens/来源/license；图片路径存在 | 单测：解析简报 + 文件存在断言 |
| RAG-E | 生成的 `query.mjs` 与工具对 5 条查询的 top-5（id + score）一致；空库/坏索引 exit 2 | 单测：parity + 子进程退出码 |
| RAG-F | spec 接受 `designRefs`；未知/blocked → error；审美 must 无 ref 且库非空 → warning | 单测：三种输入 |
| RAG-G | audit 未知/blocked `designRef` → error；报告含「参考对照」与 ref tokens；`audit.json.design.refs` 非空 | 单测：跑完整审计 |
| RAG-H | `feedback block` 后下一次检索不再出现；`unblock` 恢复；history 追加 | 单测：三连操作 |
| RAG-I | 2000 chunks 索引单次查询 ≤ 300ms（CI 上放宽到 800ms） | 单测：合成索引计时 |
| RAG-J | 删除/损坏索引后 spec/audit 仍成功，只出 warning；`status` 报库不可用 | 单测：坏索引场景 |
| RAG-K | 缺 license 的摄取被拒绝；`unknown` 卡片不进简报并 warning | 单测：两种输入 |
| RAG-L | 通过审计 + 用户确认后 `capture` 生成 project 卡（tokens 来自基线）；未确认被拒绝 | 单测：两阶段 |
| RAG-M | DevEco 写 `design-request` → `brief --request` 更新简报并回 `inspiration re=#q-1`；`status` 显示已应答 | 单测：outbox 往返 |

## 9. 完整示例（一轮）

> 需求：「做一个拍照打卡 App，想要杂志感，深色模式也要好看。」

1. `dwguard_spec`：审美判据带 `designRefs: ["DSG-0007"]`，档案 `AES-SPACING-01` 生效；
2. `dwguard_design brief`：检索出 5 张卡（杂志卡、深色卡、空态卡、动效卡、配色卡），写 `tasks/design-brief.md` + `inspiration #i-1`；
3. DevEco 编码中执行 `query.mjs "深色 列表 空态"`，拿到 `DSG-0012/0015`，写 `design-request #q-1`；
4. DSH `brief --request #q-1` 补充深色参考，回 `inspiration re=#q-1`；
5. 审计：A-02 引用 `DSG-0007`，报告「参考对照」里参考 20vp / 实测 19vp（断言 18–22 通过）；发现未引用的深色卡命中但被忽略 → warning；
6. 用户确认 → `capture` 生成 `DSG-0021`（本项目首页模式）；下一轮检索直接命中；
7. 用户说「DSG-0015 太花了」→ `feedback off`，下一轮降权。

## 10. 不做什么

- 不做像素级视觉相似度/CLIP 检索（0.6.3 评估，先看文本 + token 检索够不够）；
- 不训练/微调模型，不引入向量数据库；
- 不联网抓图、不自动下载版权素材；图片由用户/项目提供；
- 不把参考当判据，不用参考替代设备取证；
- 默认不做跨项目自动共享（配置可显式指向共享目录，0.7.0 再评估模板化）；
- 不在默认路径上调用模型：语义重排只作为可关闭的预留项。

## 11. 兼容性与风险

- 新增目录/新 kind/新字段全部可选；老任务包、老审计记录照常读；工具数 14 → 15；
- 风险 → 缓解：

| 风险 | 缓解 |
| --- | --- |
| 检索噪声被当成「设计正确」 | 参考只是对照证据；驳回必须给设备证据；报告分开「已引用 / 仅推荐」 |
| 中文 bigram 召回差 | 固定夹具 RAG-B 守住 precision@3；同义词表随规则库补充 |
| 版权/来源不清 | ingest 必填 source + license；unknown 不进简报；报告展示来源 |
| 索引膨胀/变慢 | maxChunks + 去重 + 性能断言 RAG-I |
| 双实现（工具 vs query.mjs）漂移 | RAG-E parity 测试；`manifest.engineSha256` 记录生成版本 |
| 两个 Agent 对参考理解不一致 | 简报 + 问答 + outbox 记录；状态把未应答请求列出来 |
| 预览/像素特征缺失 | 明确写「图片仅作人工复核」，不承诺视觉相似度 |

## 12. 版本计划

| 版本 | 内容 |
| --- | --- |
| `0.6.2` | 本文：卡片库 + BM25 检索 + 双 Agent 通道 + designRef 接入 + 反馈/捕获（P0–P3） |
| `0.6.3` | 候选：hybrid 重排（可选 embedding，只做重排）、PNG pHash + 色彩直方图、「同屏组合」推荐 |
| `0.7.0` | 设备矩阵批量排程 + 跨项目审美模板（复用 0.6.2 的库格式） |

实现顺序建议：P0 库 + 检索 + `query.mjs` → P1 简报/问答 + 协议 kind → P2 spec/audit 的 `designRefs` + 报告列 → P3 feedback/capture + 统计与降级。
