Files
cs-note/claude-code-best/docs/features/teammem.md
T

5.8 KiB
Raw Blame History

tags, create time
tags create time
teammem
团队记忆
GitHub
同步
feature-flag
2026-06-09 22:30

TEAMMEM — 团队共享记忆

概述

TEAMMEM 实现基于 GitHub 仓库的团队共享记忆系统。memory/team/ 目录中的文件双向同步到 Anthropic 服务器,团队所有认证成员可共享项目知识。

[!info] Feature Flag: FEATURE_TEAMMEM=1 实现状态:完整可用(需要 Anthropic OAuth + GitHub remote)

正文

核心特性

  • 增量同步:只上传内容哈希变化的文件(delta upload)
  • 冲突解决:基于 ETag 的乐观锁 + 412 冲突重试
  • 密钥扫描:上传前检测并跳过包含密钥的文件(PSR M22174)
  • 路径穿越防护:所有写入路径验证在 memory/team/ 边界内
  • 分批上传:自动拆分超过 200KB 的 PUT 请求避免网关拒绝

用户交互

同步行为

事件 行为
项目启动 自动 pull 团队记忆到 memory/team/
本地文件编辑 watcher 检测变更,自动 push
服务端更新 下次 pull 时覆盖本地(server-wins)
密钥检测 跳过该文件,记录警告,不阻止其他文件同步

API 端点

GET  /api/claude_code/team_memory?repo={owner/repo}             -> 完整数据 + entryChecksums
GET  /api/claude_code/team_memory?repo={owner/repo}&view=hashes -> 仅 checksums(冲突解决用)
PUT  /api/claude_code/team_memory?repo={owner/repo}             -> 上传 entries(upsert 语义)

实现架构

同步状态

type SyncState = {
  lastKnownChecksum: string | null    // ETag 条件请求
  serverChecksums: Map<string, string> // sha256:<hex> 逐文件哈希
  serverMaxEntries: number | null      // 从 413 学习的服务端容量
}

Pull 流程(Server -> Local)

文件:src/services/teamMemorySync/index.ts:770-867

flowchart TD
    A["pullTeamMemory(state)"] --> B["检查 OAuth + GitHub remote"]
    B --> C["fetchTeamMemory(state, repo, etag)"]
    C -->|"304 Not Modified"| D["返回 无变化"]
    C -->|"404"| E["返回 服务端无数据"]
    C -->|"200"| F["解析 TeamMemoryData"]
    F --> G["刷新 serverChecksums per-key hashes"]
    G --> H["writeRemoteEntriesToLocal(entries)"]
    H --> I["路径穿越验证 validateTeamMemKey"]
    H --> J["文件大小检查 > 250KB 跳过"]
    H --> K["内容比较 相同则跳过写入"]
    H --> L["并行写入 Promise.all"]

Push 流程(Local -> Server)

文件:src/services/teamMemorySync/index.ts:889-1146

flowchart TD
    A["pushTeamMemory(state)"] --> B["readLocalTeamMemory(maxEntries)"]
    B --> C["递归扫描 memory/team/ 目录"]
    B --> D["跳过超大文件 > 250KB"]
    B --> E["密钥扫描 scanForSecrets gitleaks 规则"]
    B --> F["按 serverMaxEntries 截断"]
    C --> G["计算 delta = 本地文件 - serverChecksums"]
    G --> H["batchDeltaByBytes(delta) 拆分为 <=200KB 的批次"]
    H --> I["逐批 uploadTeamMemory(state, repo, batch, etag)"]
    I -->|"200 成功"| J["更新 serverChecksums"]
    I -->|"412 冲突"| K["fetchTeamMemoryHashes() 刷新 checksums"]
    K --> L["重试 delta 计算 最多 2 次"]
    I -->|"413 超容量"| M["学习 serverMaxEntries"]

密钥扫描

文件:src/services/teamMemorySync/secretScanner.ts

使用 gitleaks 规则模式扫描文件内容。检测到密钥时:

  • 跳过该文件(不上传)
  • 记录 tengu_team_mem_secret_skipped 事件(仅记录规则 ID,不记录值)
  • 不阻止其他文件同步

文件监视

文件:src/services/teamMemorySync/watcher.ts

监视 memory/team/ 目录变更,触发自动 push。抑制由 pull 写入引起的假变更。

路径安全

文件:src/memdir/teamMemPaths.ts

  • validateTeamMemKey(relPath) — 验证相对路径不超出 memory/team/ 边界
  • getTeamMemPath() — 返回 team memory 根目录路径

关键设计决策

  1. Server-wins on pull, Local-wins on push:pull 时服务端内容覆盖本地;push 时本地编辑覆盖服务端。本地用户正在编辑,不应被静默丢弃
  2. Delta upload:只上传哈希变化的条目,节省带宽。首次 push 为全量,后续增量
  3. 分批 PUT:单次 PUT <=200KB,避免 API 网关(~256-512KB)拒绝。每批独立 upsert,部分失败不影响已提交批次
  4. 密钥扫描在上传前:PSR M22174 要求密钥永不离开本机。扫描在 readLocalTeamMemory 中执行,密钥文件不进入上传集
  5. ETag 乐观锁:push 使用 If-Match header。412 时 probe ?view=hashes(只获取 checksums,不下载内容),刷新后重试
  6. 服务端容量动态学习:不假设客户端容量上限,从 413 的 extra_details.max_entries 学习

使用方式

# 启用 feature
FEATURE_TEAMMEM=1 bun run dev

# 前提条件:
# 1. 已通过 Anthropic OAuth 登录
# 2. 项目有 GitHub remote(git remote -v 显示 origin)
# 3. memory/team/ 目录自动创建

外部依赖

依赖 说明
Anthropic OAuth first-party 认证
GitHub Remote getGithubRepo() 获取 owner/repo 作为同步 scope
Team Memory API /api/claude_code/team_memory 端点

文件索引

文件 行数 职责
src/services/teamMemorySync/index.ts 1257 核心同步逻辑(pull/push/sync)
src/services/teamMemorySync/watcher.ts — 文件监视 + 自动同步触发
src/services/teamMemorySync/secretScanner.ts — gitleaks 密钥扫描
src/services/teamMemorySync/types.ts — Zod schema + 类型定义
src/services/teamMemorySync/teamMemSecretGuard.ts — 密钥防护辅助
src/memdir/teamMemPaths.ts — 路径验证 + 目录管理

关联笔记