Files
my-vault/06_Metadata/RESTRUCTURE_PLAN_2026-09-25.md
T
windyboy b182762e14 refactor(vault): Phase 3 — metadata convergence
- Backfill `created` frontmatter on 266 active notes (git date or mtime)
- Normalize `status` to 4 values: draft/active/done/archived (11 notes)
  - Active/进行中/needs-review → active
  - archive → archived
  - 待执行/conditional → draft
  - 完成/accepted → done
- Declare clipper boundary: 04_Archive/Inbox-Clippings/** exempt from migration
- Update depth rule: max 3 → max 4 levels (new notes only)
- Add metadata-converge.mjs script for reproducibility
2026-09-26 11:37:19 +08:00

21 KiB
Raw Blame History

created, updated, type, status, tags
created updated type status tags
2026-09-25 2026-09-26 plan active
restructure
security
maintenance

重构方案 2026-09-25

完成后删除本文件。 不再往 06_Metadata/ 里加计划文档 —— 见「五、文档瘦身」。 依据:Compass 对照评审 + 本机实测(2026-09-25)。


定义完成(Definition of Done)

每一条都能一条命令验证。达不到就不算完成。

# 条件 验证命令 期望
1 没有「已忽略但被跟踪」的文件 git ls-files -z | git check-ignore -z --no-index --stdin | tr '\0' '\n' | wc -l 0
2 高置信密钥零命中 node .scripts/verify-vault.mjs 退出码 0
3 根目录只剩 PARA + 支持目录 ls -1 非 dotfiles 只有 00–06、契约三件套、package.json、一个 lockfile(点文件用 ls -A 对照清单抽查)
4 契约只有一份 git ls-files | grep -c 'CLAUDE\.md$' 1
5 元数据收敛 活笔记(00–03,排除 00_Inbox/Clippings/)frontmatter 每篇含 created;status ∈ 4 值;内容性键(title/source/date/author/published/description/aliases)不删
6 06_Metadata 瘦身 git ls-files '06_Metadata/**' | wc -l ≤ 14
7 深度基线守恒 git -c core.quotePath=false ls-files '*.md' | grep -v '^04_Archive/' | awk -F/ 'NF>=6' | wc -l ≤ 40(基线 40,2026-09-26 实测;≤4 层新规则只约束新笔记,存量不压平)

零、诊断

这个仓库同时是四样东西,抢一个根目录:

身份 证据 现状
A 知识库 PARA,704 篇 md 主体,但 90 天只提交 27 次
B 代码仓库 .scripts/memory/(pgvector + uv + schema.sql)、specs/、initdb/、compose.yml、两个 lockfile 最后动于 2026-04-13
C 工作运行手册 01_Projects/Work/(含雇主凭据),最后动于 2026-09-23 最活跃的目录
D Obsidian 分发 .obsidian/ 108 个文件、39 MB 插件 JS 对一个私人仓库无意义

真正的病:文档代替动作

06_Metadata/ 42 个文件里,有 4 份工作流文档(WORKFLOWS / WORKFLOW_SIMPLIFICATION_ANALYSIS / WORKFLOW_TRANSITION_GUIDE / ULTRA_SIMPLE_WORKFLOW)、2 份评审报告、IMPROVEMENT_PLAN、FIX_TASK_BREAKDOWN、REFACTOR_SUMMARY。

二月写了评审 → 写了改进计划 → 写了任务拆解 → 写了重构总结 —— 密钥还在 git 里。政策也被写了三遍:.claude/project-instructions.md 和 .claude/memory/instructions/safety.md 几乎逐字重复,AGENTS.md 是另一套(JS 代码规范)。

所以本方案的产出形式是清单和脚本,不是又一份散文。执行完删掉本文件。

一处更正

上一轮我说「定时备份会把取消跟踪的文件重新加回来」—— 这是错的。实测:

  • crontab -l 为空,无 systemd timer
  • obsidian-git autoSaveInterval: 0、autoPushInterval: 0、autoBackupAfterFileChange: false
  • git add . 和 obsidian-git 都遵守 .gitignore

所以 git rm --cached + ignore 规则是永久有效的。文件在 git 里,唯一原因是它们在 ignore 规则写下来之前就被提交过,之后没人取消跟踪。

好消息:止血比我说的一小时更便宜,而且不需要改任何自动化。

自动化里唯一真实存在的是 web clipper:00_Inbox/Clippings/ 里已经有 2025 和 2026 两代新 clippings,说明它按 YYYY/MM/ 往 inbox 写。这个要改。


阶段 0 · 轮换密钥(只有你能做)

按「攻击者能拿到什么」排序,不按文件类型。

顺序 目标 在哪 时间
1 Vaultwarden admin token + SMTP 密码 01_Projects/Infrastructure/Services/vaultwarden.md 20 min
2 AWS access key、GitHub PAT、GitLab token 03_Resources/Community/Matrix Server.md、01_Projects/Personal-Tech/Development/Github.md、03_Resources/Development/DevOps/Gitlab/rest api.md 30 min
3 工作/政府项目运行手册密码 01_Projects/Work/Government-Projects/(Virtual-Data-Center/prod.md、Municipal-Development-Reform/production.md+deploy.md、Industry-Info/Deployment.md) 60 min + 通报决定
4 OpenRouter + DeepSeek 商家后台 5 min
5 Local REST API key + 证书 插件设置里重新生成 10 min

第 1 条优先,因为它是密码管理器本身,守着其他所有条目。第 3 条带一个非技术问题:要不要向雇主通报,先决定再动。

轮换完再确认:06_Metadata/SECURITY_ROTATION_LOG_2026-02.md 里"notes 侧已完成"那行与事实不符,改成实际状态,勾掉历史清理的复选框(它们本来就该是未勾选的)。


阶段 1 · 止血:让泄漏无法复发

事实

75 个文件被跟踪,同时又被 .gitignore 覆盖:

数量 类别
26 .obsidian/plugins/*/manifest.json
17 .obsidian/plugins/*/data.json
14 .claude/sessions/*.jsonl
6 .scripts/memory/__pycache__/*.pyc
6 .memory-changes-*.txt
6 .obsidian/{types,graph,daily-notes,core-plugins,community-plugins,backlink}.json

另有 3 个路径根本没被任何规则覆盖:

.qmd/index.sqlite-wal
.qmd/index.sqlite-shm
.memory-index.lock

步骤

  1. 标签存档点(回滚用)

    git tag pre-restructure-2026-09-25 && git push origin pre-restructure-2026-09-25
    
  2. 补 3 条 ignore 规则 → .qmd/、.memory-index.lock、*.sqlite-wal、*.sqlite-shm

  3. 摘掉全部 75 + 3 个文件

    git ls-files -z | git check-ignore -z --no-index --stdin | xargs -0 git rm --cached
    git rm --cached .qmd/index.sqlite-wal .qmd/index.sqlite-shm .memory-index.lock
    

    --cached 只动索引,工作区文件保留。

  4. 删掉文件名黑名单式 ignore 现在 .gitignore 里有 **/*password*.md、**/*secret*.md、**/*token*.md。这批密钥所在的文件叫 prod.md、deployment.md、Matrix.md —— 黑名单一条都没拦住,还误伤正常笔记。改用内容扫描,见第 5 步。

  5. 写 .scripts/verify-vault.mjs(仓库脚本目录是 .scripts/,根目录无 scripts/),接进 CI,与 lint 并列

    // .scripts/verify-vault.mjs — 只报路径与行号,绝不打印匹配到的内容
    import { execSync } from 'node:child_process'
    import { readFileSync } from 'node:fs'
    
    const PATTERNS = [
      ['openrouter', /sk-or-v1-[A-Za-z0-9]{32,}/],
      ['openai', /sk-[A-Za-z0-9]{32,}/],
      ['aws', /AKIA[0-9A-Z]{16}/],
      ['github-pat', /gh[pousr]_[A-Za-z0-9]{36,}/],
      ['gitlab-pat', /glpat-[A-Za-z0-9_-]{20,}/],
      ['private-key', /-----BEGIN [A-Z ]*PRIVATE KEY-----/],
      ['slack', /xox[baprs]-[A-Za-z0-9-]{10,}/],
    ]
    // 路径限定规则:.obsidian/** 下 64 位 hex 即报 —— 覆盖 local-rest-api
    // 那类无前缀 apiKey(本次泄漏类型之一,通用模式抓不到)
    
    let fail = 0
    
    // 1. 已忽略但被跟踪(-z 变体,免疫 quotePath 对非 ASCII 路径的转义)
    const trackedIgnored = execSync(
      'git ls-files -z | git check-ignore -z --no-index --stdin',
      { encoding: 'utf8' },
    ).split('\0').filter(Boolean)
    if (trackedIgnored.length) {
      fail = 1
      console.error(`✗ ${trackedIgnored.length} 个已忽略文件仍被跟踪:`)
      trackedIgnored.forEach((f) => console.error(`    ${f}`))
    }
    
    // 2. 内容扫描 —— 只扫 git 跟踪的文件(git ls-files),不扫工作区
    //    未跟踪文件:否则摘除索引后仍在磁盘上的 data.json(真密钥)
    //    会让「退出码 0」永远达不到
    const files = execSync('git ls-files "*.md" "*.json" "*.yml" "*.yaml"', {
      encoding: 'utf8',
    }).trim().split('\n').filter(Boolean)
    for (const f of files) {
      let body
      try { body = readFileSync(f, 'utf8') } catch { continue }
      for (const [name, re] of PATTERNS) {
        const m = re.exec(body)
        if (m) {
          fail = 1
          const line = body.slice(0, m.index).split('\n').length
          console.error(`✗ ${name} 命中 ${f}:${line}`)
        }
      }
    }
    
    console.log(fail ? 'verifier 失败' : '✓ verifier 通过')
    process.exit(fail)
    
  6. 改 .github/workflows/lint.yml,加一步 node .scripts/verify-vault.mjs(在 lint 之前)

  7. 改 clipper 输出目录 / 删 FIRST_RUN + SessionStart hook → 已移出(审核 2026-09-26,止血只管 git 密钥):clipper 目录 → 阶段 2;FIRST_RUN/SessionStart → 阶段 5

时间:我 40 分钟,你验证 10 分钟。


阶段 2 · 分区:清空根目录

目标根目录

00_Inbox/  01_Projects/  02_Areas/  03_Resources/
04_Archive/  05_Attachments/  06_Metadata/

AGENTS.md  CLAUDE.md  README.md          # 契约三件套
.obsidian/ .claude/ .github/ .config/ .scripts/ .codex/ .zcode/
.gitignore  .prettierignore              # 点文件按现状保留
package.json  pnpm-lock.yaml             # 只留一个 lockfile

迁移表

现在 处置 理由
.scripts/memory/(pgvector 服务) 先判定死活(见下),死则删,活则迁出仓库 最后动于 2026-04-13
compose.yml、initdb/ 随 .scripts/memory/ 一起走 是同一套服务
specs/001-opencode-client/ 删或迁出 一个规格,最后动于 2026-01-31
memory_eval/ 删 与 .qmd/.scripts/memory 三者重复
package-lock.json 和 pnpm-lock.yaml 删 package-lock.json 二选一
未命名.md、未命名 1.md、Untitled Kanban.md 删(空文件) 根目录散件
install.sh、LICENSE 删(人工确认) 上游 claudesidian 残留("Claudesidian Setup Script";MIT © Noah Brier / Alephic),与阶段 5 去残留一致
b02685b5772985d358ab7092c07d5058.png 删或移 05_Attachments/ 无意义文件名
chinese-to-english-mapping.csv 移 .scripts/ 是脚本的输入数据
web clipper 输出目录(浏览器扩展设置,非仓库文件) 改到 04_Archive/Inbox-Clippings/,修 2024/11/2023/ 自嵌套;剪一篇测试文章验证落盘 04_Archive/Inbox-Clippings/YYYY/MM/ 从阶段 1 移入(审核 2026-09-26)
.failed_images.txt、.upgrade-checklist.md 删 临时文件
.zcode/、.codex/ 留在根(点目录) 工具配置

判定 .scripts/memory 死活

crontab -l 2>/dev/null | grep -i memory
ls -la .memory-changes-*.txt | tail -1
grep -rn "install-hook" ~/.claude/settings.json .claude/settings.json 2>/dev/null

最后一份 .memory-changes-* 是 2026-04 的、crontab 为空 → 死代码,直接删。 如果还挂在某个 hook 上 → 迁到独立仓库,别留在笔记库里。

时间:我 30 分钟,你确认删除清单 15 分钟。


阶段 3 · 元数据收敛

关键发现:不是三套 schema,是一套 + 一个被隔离的导入格式

352 篇有 frontmatter(50.0%)里,174 篇在 04_Archive,全是 clippings。

键 篇数 实际归属
date / page-title / url 192 / 178 / 178 clipper,几乎全在 04_Archive
created / tags / type / status / updated 129 / 128 / 91 / 67 / 54 活笔记,已经是一套自洽的五键
title / modified 87 / 5 混合 / 基本没用

03_Resources 121 个文件里只有 10 篇有 frontmatter —— 活笔记的元数据比想象中干净得多。

步骤

  1. 宣布边界,不是迁移:04_Archive/Inbox-Clippings/** 与 00_Inbox/Clippings/** 永久使用 clipper schema(date/page-title/url),永不迁移 —— 04_Archive 之外 00_Inbox/Clippings/ 还有 10 篇 2026 年新剪藏,边界必须同时覆盖两处。一步砍掉一半"混乱"。
  2. 活笔记补 created(机检口径:PARA 00–03 排除 00_Inbox/Clippings/,共 367 篇,101 篇已有,266 篇待补):是补齐不是重写;不新增其他必备键,不删内容性键(title/description/source/date/author/published/aliases 是内容事实)。脚本先 dry-run 输出 diff,可回滚。
  3. status 收敛到 4 值:draft / active / done / archived。 现状有 14 种:active(51)、archive(4),外加 review、done、needs-review、accepted、conditional、reading、evergreen、draft、Active、待执行、进行中、完成 各一。写成脚本映射,可回滚。
  4. 修深度违规:.claude/memory/instructions/organization.md 白纸黑字写着「Flat structure over deep nesting (max 3 levels)」,实际 247 个文件在 5 层、75 个在 7 层。要么改规则到 4 层,要么压平 clippings。选改规则,只约束新笔记 —— 200 篇 clippings 压平不值得;活跃区现存 40 个 ≥6 层文件(LLM_Evaluation 引用树、Work ref)保留不动,属「704 篇不迁移」,验收按基线 40 守恒。
  5. 模板去重 → 移入阶段 4(审核 2026-09-26,与 06_Metadata 瘦身是同一批文件操作,归一处)。

时间:写脚本 1 小时 + 脚本跑 5 分钟,可回滚。


阶段 4 · 文档瘦身

06_Metadata 42 → ≤14。

保留 数量
Templates/:Daily Note Template、Project Template、quick-note、book-note(均被 .obsidian 接线)+ meeting、person(待接线,人工确认是否还要) 6
Reference/:PARA_METHOD.md、GIT_WORKFLOW.md(重写)、TROUBLESHOOTING.md 3
SECURITY_ROTATION_LOG_2026-02.md(改到与实际一致) 1
本文件(完成即删) 1
归档到 04_Archive/ 或删除 理由
daily-note.md、project.md → 并入被接线的 Daily Note Template.md / Project Template.md 后删 重复对,接线的那份留
Research Note Template.md 未接线未用,删(人工确认)
4 份工作流文档 → 合 1 份进 AGENTS.md 互不一致
2 份 REVIEW_REPORT_* 历史记录,进 04_Archive
IMPROVEMENT_PLAN、FIX_TASK_BREAKDOWN、REFACTOR_SUMMARY 已执行或已作废
CHANGELOG、CONTRIBUTING、CLAUDE-BOOTSTRAP、claudian-phase1-complete、claudian-token-optimization-testing 上游模板残留
CLAUDE.md.archived-2026-01-07 归档文件本身该归档
Hermes * Template.md(3 份) 未在使用
DATAVIEW_VALIDATION、WORKFLOW_SIMPLIFICATION_ANALYSIS、WORKFLOW_TRANSITION_GUIDE、ULTRA_SIMPLE_WORKFLOW、INBOX_TRIAGE 一次性过程文档

模板去重(从阶段 3 移入):保留被 .obsidian 接线的那份 —— daily-notes.json 接线 Daily Note Template.md,插件接线 Project Template.md/quick-note.md/book-note.md;删任何模板前先查 daily-notes.json / plugins/*/data.json。

时间:我 45 分钟,你逐条确认删除。


阶段 5 · 一份 agent 契约

事实

  • AGENTS.md 是 TypeScript/Python 代码规范(2026-01-06)
  • 15 个 CLAUDE.md,包括 .obsidian/plugins/claudian/ 和 specs/.../contracts/ 里的
  • .claude/project-instructions.md 与 .claude/memory/instructions/safety.md 逐字重复
  • .claude/settings.json:deny 和 ask 都是空的,allow 里有 Bash(git add:*)、Bash(find:*)、Edit(.obsidian/plugins/quickadd/data.json)、Edit(04_Archive/Projects/Airport/Chengdu/Office Test Env.md)
  • 16 个 .claude/commands/*.md,没有一个带 risk 或 writes 字段

目标

  1. AGENTS.md = 唯一的操作手册:目录表 + 每个目录允许什么操作 + 属性前缀 + 读后写 + 改前问 + 绝不重写归档。 JS/Python 规范挪到 .config/README.md 或一节「scripts」。

  2. CLAUDE.md 和 GEMINI.md = 一行指针:See AGENTS.md。

  3. 删掉 14 个 stub CLAUDE.md(含插件目录里的)。

  4. 合并 project-instructions.md + .claude/memory/instructions/*.md → AGENTS.md,删原件。

  5. 权限表(照 Compass 的目录表,不是照搬内容):

    目录 agent 可以
    00_Inbox/ 自由追加
    01_Projects/、03_Resources/ 按要求编辑
    02_Areas/ 编辑前问
    04_Archive/、.obsidian/、06_Metadata/Templates/ 不重写
    任何 .env、data.json、凭据文件 永不读写
  6. settings.json 收紧:

    • deny 加:Edit(.obsidian/plugins/*/data.json)(只锁含密钥的插件配置)、Edit(04_Archive/**)、Bash(git push:*)、Bash(git add .:*)
    • ask 加:Edit(.obsidian/**)(其余 .obsidian 编辑弹窗确认,与权限表「.obsidian 不重写」对齐 —— 密钥硬锁、其余兜底)
    • 删掉 allow 里那两个无理由的 Edit(...) 条目
  7. 命令加元数据:每个 .claude/commands/*.md 顶部加 writes: 和 risk:,让一个按钮不能悄悄变成删除。

  8. 改前门:README.md 一句 claudesidian 都不保留,改成描述这个仓库;package.json 的 name / version 更新;/init-bootstrap、clone URL、upgrade 命令全部下线或归档。

  9. **(从阶段 1 移入)**删 FIRST_RUN,改 .claude/settings.json 的 SessionStart hook(现在会往每个会话灌 "Welcome to Claudesidian")。

时间:1 小时。


六、顺序、时间与回滚

顺序(依赖关系)

0 轮换 ──► 1 止血 ──► 2 分区 ──► 3 元数据 ──► 4 文档 ──► 5 契约 ──► 6 历史重写
  你        我/你       我/你       脚本         我/你       我        维护窗口

阶段 1 必须在最前(除轮换外):verifier 装上之后,后面每一步都不会再把新密钥提交进去。 阶段 6 必须最后:等目录定下来,filter-repo 只需跑一次。

时间

阶段 你的时间 我的时间
0 轮换 2–3 小时 —
1 止血 10 min 40 min
2 分区 15 min 30 min
3 元数据 5 min 1 小时
4 文档 20 min 45 min
5 契约 10 min 1 小时
6 历史重写 1 小时 辅助
合计 约 4 小时 约 4 小时

历史重写(阶段 6,单独维护窗口)

  1. 先做全量异地备份:git clone --mirror 到外置盘。这一步不可跳过 —— 笔记库可能只有这一份。
  2. git filter-repo --replace-text 或 --path 移除阶段 1 的路径
  3. force push,然后每个克隆重新拉取(git fetch --all && git reset --hard origin/main 或重新 clone)
  4. 提醒:任何旧的 fork、备份、CI 缓存都还留着旧对象
  5. 回滚:如果 filter-repo 结果异常(密钥未清除或笔记丢失),从异地 mirror 重新 clone 恢复,验证干净后替换当前仓库;mirror 在轮换确认完成前不可销毁

回滚

  • 阶段 1–5 全部可逆:git tag pre-restructure-2026-09-25 是还原点
  • 阶段 1 的 git rm --cached 不删工作区文件
  • 阶段 3 的 frontmatter 改写用脚本做,脚本先 dry-run 输出 diff
  • 阶段 6 不可逆 → 先备份

七、不要做

  1. 不要现在拆成两个 git 仓库。 历史里还有密钥,同时动两件事风险叠加。等 verifier 稳定跑一个月。
  2. 不要搬 Compass 的生活主题文件夹、wheel-of-life 属性、Bible reading 工作区。那是它的工作,不是这个库的。
  3. 不要一次上 16 个 agent job 和 DataviewJS 仪表盘。 Compass 自己的指南说整套系统做了几年,一个周末抄完必然失败。
  4. 不要加新插件。 27 个装了,10 个开着;Tasks / QuickAdd / Calendar / Kanban / Copilot 装了没开。要么开要么删,装了不开只是噪音。
  5. 不要再往 06_Metadata/ 写计划文档然后不执行。 这是这个库最主要的失败模式 —— 已经有 4 份工作流文档和 2 份评审报告为证。

八、关于"岛屿"

数据:604 条 wikilink / 704 篇(0.86/篇)。活笔记 427 篇里 77% 零入链;704 篇里 573 篇零出链。

但分布是双峰的:73 篇有 ≥3 条出链,且几乎全在 01_Projects/Personal-Tech/LLM_Evaluation(48 个文件,含 roadmap、worksheet、practice 模板)。

结论:链接不是"太少",是只发生在一个项目里。真正在运转的循环是 LLM_Evaluation,不是 inbox→note。

所以:先承认已有的循环,再决定要不要新循环。 评审建议你上一个 inbox→链接笔记的每日循环 —— 但没检查已有的那个。上第二个循环不会让第一个变好。

补充:[[README]] 被引用 42 次,是"最高入链目标" —— 但那是十几个同名 README 的计数糊在一起。同名笔记会污染任何链接健康指标,做度量时先按路径区分。


九、下一步

只做一件事:阶段 0 的第 1 条 —— 轮换 Vaultwarden 的 admin token 和 SMTP 密码。

它是密码管理器本身。它守着列表上其他每一条。20 分钟。

做完告诉我,我立刻执行阶段 1(摘除 78 个文件 + 装 verifier + 接 CI)。