跳转至

开发指南

从源码构建

前置要求

  • Node.js 20.0.0 或更高版本
  • npm(随 Node.js 附带)
  • Git

克隆与构建

# Clone repository
git clone https://github.com/thedotmack/claude-mem.git
cd claude-mem

# Install dependencies
npm install

# Build all components
npm run build

构建流程

构建流程使用 esbuild 编译 TypeScript:

  1. 将 TypeScript 编译为 JavaScript
  2. 为 plugin/scripts/ 中的每个 hook 创建独立可执行文件
  3. 将 MCP 搜索服务器打包为 plugin/scripts/mcp-server.cjs
  4. 将 worker service 打包为 plugin/scripts/worker-service.cjs
  5. 将 web viewer UI 打包为 plugin/ui/viewer.html

构建输出: - Hook 可执行文件:*-hook.js(ESM 格式) - 安装版本检查:version-check.js(ESM 格式,手动编写;安装缺失的插件依赖并记录安装标记) - Worker service:worker-service.cjs(CJS 格式) - MCP server:mcp-server.cjs(CJS 格式) - Viewer UI:viewer.html(自包含 HTML bundle)

已提交的发布 bundle

Marketplace 安装会运行 plugin/scripts/ 中已提交的文件。当更改发布版本或 worker 生命周期代码时,请使用 npm run build 重新生成并提交受影响的运行时 bundle。较新的 manifest 与较旧的 worker bundle 搭配使用,可能会触发重复的版本不匹配重启。

CI 会在重新构建之前检查已提交的 bundle 版本:

bun test tests/infrastructure/version-consistency.test.ts

如果替换后的 worker 仍然报告旧版本,hooks 会在数据目录中记录失败的回收尝试,并跳过对该未更改 bundle 的进一步回收。重新构建或重新安装 bundle 以修正版本;已更改的 bundle 允许再次进行回收尝试。

构建脚本

# Build everything
npm run build

# Build only hooks
npm run build:hooks

# The build script is defined in scripts/build-hooks.js

开发工作流

1. 进行更改

编辑 src/ 中的 TypeScript 源文件:

src/
├── hooks/           # Hook implementations (entry points + logic)
├── services/        # Worker service and database
├── servers/         # MCP search server
├── sdk/             # Claude Agent SDK integration
├── shared/          # Shared utilities
├── ui/
│   └── viewer/      # React web viewer UI components
└── utils/           # General utilities

2. 构建

npm run build

3. 测试

# Run all tests
npm test

# Test specific file
node --test tests/session-lifecycle.test.ts

# Test context injection
npm run test:context

# Verbose context test
npm run test:context:verbose

4. 手动测试

# Start worker manually
npm run worker:start

# Check worker status
npm run worker:status

# View logs
npm run worker:logs

# Test hooks manually
echo '{"session_id":"test-123","cwd":"'$(pwd)'","source":"startup"}' | node plugin/scripts/context-hook.js

5. 迭代

重复步骤 1-4,直到你的更改按预期工作。

Viewer UI 开发

使用 React Viewer

Web viewer UI 是一个 React 应用,被打包成自包含的 HTML bundle。

位置:src/ui/viewer/

结构:

src/ui/viewer/
├── index.tsx              # Entry point
├── App.tsx                # Main application component
├── components/            # React components
│   ├── Header.tsx         # Header with logo and actions
│   ├── Sidebar.tsx        # Project filter sidebar
│   ├── Feed.tsx           # Main feed with infinite scroll
│   ├── cards/             # Card components
│   │   ├── ObservationCard.tsx
│   │   ├── PromptCard.tsx
│   │   ├── SummaryCard.tsx
│   │   └── SkeletonCard.tsx
├── hooks/                 # Custom React hooks
│   ├── useSSE.ts          # Server-Sent Events connection
│   ├── usePagination.ts   # Infinite scroll pagination
│   ├── useSettings.ts     # Settings persistence
│   └── useStats.ts        # Database statistics
├── utils/                 # Utilities
│   ├── constants.ts       # Constants (API URLs, etc.)
│   ├── formatters.ts      # Date/time formatting
│   └── merge.ts           # Data merging and deduplication
└── assets/                # Static assets (fonts, logos)

构建 Viewer UI

# Build everything including viewer
npm run build

# The viewer is built to plugin/ui/viewer.html
# It's a self-contained HTML file with inlined JS and CSS

测试 Viewer 更改

  1. 在 src/ui/viewer/ 中对 React 组件进行更改
  2. 构建:npm run build
  3. 同步到已安装的插件:npm run sync-marketplace
  4. 重启 worker:npm run worker:restart
  5. 刷新 npm run worker:status 打印的 worker URL

热重载:目前不支持。更改需要完全重新构建 + 重启。

添加新的 Viewer 功能

示例:添加新的卡片类型

  1. 在 src/ui/viewer/components/cards/YourCard.tsx 中创建组件:
import React from 'react';

export interface YourCardProps {
  // Your data structure
}

export const YourCard: React.FC<YourCardProps> = ({ ... }) => {
  return (
    <div className="card">
      {/* Your UI */}
    </div>
  );
};
  1. 在 Feed.tsx 中导入并使用:
import { YourCard } from './cards/YourCard';

// In render logic:
{item.type === 'your_type' && <YourCard {...item} />}
  1. 如有需要,在 src/ui/viewer/types.ts 中更新类型

  2. 重新构建并测试

Viewer UI 架构

数据流: 1. Worker service 公开 HTTP + SSE 端点 2. React 应用通过 HTTP(分页)获取初始数据 3. SSE 连接提供实时更新 4. 自定义 hooks 处理状态管理和数据合并 5. 组件根据条目类型渲染卡片

关键模式: - 无限滚动:usePagination hook 搭配 Intersection Observer - 实时更新:useSSE hook 支持自动重连 - 去重:merge.ts 工具函数防止重复条目 - 设置持久化:useSettings hook 搭配 localStorage - 主题支持:CSS 变量,支持浅色/深色/系统主题

添加新功能

添加新 Hook

  1. 在 src/hooks/your-hook.ts 中创建 Hook 实现:
#!/usr/bin/env node
import { readStdin } from '../shared/stdin';

async function main() {
  const input = await readStdin();

  // Hook implementation
  const result = {
    hookSpecificOutput: 'Optional output'
  };

  console.log(JSON.stringify(result));
}

main().catch(console.error);

注意:从 v4.3.1 开始,Hook 是自包含文件。shebang 将在构建过程中由 esbuild 自动添加。

  1. 添加到 plugin/hooks/hooks.json:
{
  "YourHook": [{
    "hooks": [{
      "type": "command",
      "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/your-hook.js",
      "timeout": 120
    }]
  }]
}
  1. 重新构建:
npm run build

修改数据库 Schema

  1. 向 src/services/sqlite/migrations.ts 添加迁移:
export const migration011: Migration = {
  version: 11,
  up: (db: Database) => {
    db.run(`
      ALTER TABLE observations ADD COLUMN new_field TEXT;
    `);
  },
  down: (db: Database) => {
    // Optional: define rollback
  }
};
  1. 更新 src/services/sqlite/types.ts 中的类型:
export interface Observation {
  // ... existing fields
  new_field?: string;
}
  1. 更新 src/services/sqlite/SessionStore.ts 中的数据库方法:
createObservation(obs: Observation) {
  // Include new_field in INSERT
}
  1. 测试迁移:
# Backup database first!
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup

# Run tests
npm test

扩展 SDK Prompts

  1. 修改 src/sdk/prompts.ts 中的 Prompts:
export function buildObservationPrompt(observation: Observation): string {
  return `
    <observation>

    </observation>
  `;
}
  1. 更新 src/sdk/parser.ts 中的解析器:
export function parseObservation(xml: string): ParsedObservation {
  // Parse new XML fields
}
  1. 测试:
npm test

添加 MCP 搜索工具

  1. 在 src/servers/mcp-server.ts 中添加工具定义:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'your_new_tool') {
    // Implement tool logic
    const results = await search.yourNewSearch(params);
    return formatResults(results);
  }
});
  1. 在 src/services/sqlite/SessionSearch.ts 中添加搜索方法:
yourNewSearch(params: YourParams): SearchResult[] {
  // Implement FTS5 search
}
  1. 重新构建并测试:
npm run build
npm test

测试

测试理念

Claude-mem 依赖真实使用场景和手动测试,而非传统的单元测试。项目理念优先考虑:

  1. 手动验证 - 在实际的 Claude Code 会话中测试功能
  2. 集成测试 - 端到端运行完整系统
  3. 数据库检查 - 通过 SQLite 查询验证数据正确性
  4. CLI 工具 - 用于检查系统状态的交互式工具
  5. 可观测性 - 全面的日志记录和 worker 健康检查

选择这种方法是因为: - Hook 行为在很大程度上依赖于 Claude Code 的运行时环境 - SDK 交互需要真实的 API 调用和响应 - SQLite 和 Bun 运行时提供了稳定性保证 - 手动测试能捕获单元测试遗漏的集成问题

手动测试工作流程

在开发新功能时:

  1. 构建并同步:

    npm run build
    npm run sync-marketplace
    npm run worker:restart
    

  2. 在真实会话中测试:

  3. 启动 Claude Code
  4. 触发你要测试的功能
  5. 验证预期行为

  6. 检查数据库状态:

    sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM your_table;"
    

  7. 监控 worker 日志:

    npm run worker:logs
    

  8. 验证队列健康状态(用于恢复功能):

    bun scripts/check-pending-queue.ts
    

测试工具

健康检查:

# Worker status
npm run worker:status

# Queue inspection
WORKER_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
curl http://127.0.0.1:$WORKER_PORT/api/pending-queue

# Database integrity
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;"

Hook 测试:

# Test context hook manually
echo '{"session_id":"test-123","cwd":"'$(pwd)'","source":"startup"}' | node plugin/scripts/context-hook.js

# Test new hook
echo '{"session_id":"test-123","cwd":"'$(pwd)'","prompt":"test"}' | node plugin/scripts/new-hook.js

数据验证:

# Check recent observations
sqlite3 ~/.claude-mem/claude-mem.db "
  SELECT id, tool_name, created_at
  FROM observations
  ORDER BY created_at_epoch DESC
  LIMIT 10;
"

# Check summaries
sqlite3 ~/.claude-mem/claude-mem.db "
  SELECT id, request, completed
  FROM session_summaries
  ORDER BY created_at_epoch DESC
  LIMIT 5;
"

恢复功能测试

具体针对手动恢复功能:

  1. 模拟卡住的消息:

    # Manually create stuck message (for testing only)
    sqlite3 ~/.claude-mem/claude-mem.db "
      UPDATE pending_messages
      SET status = 'processing',
          started_processing_at_epoch = strftime('%s', 'now', '-10 minutes') * 1000
      WHERE id = 123;
    "
    

  2. 测试恢复:

    bun scripts/check-pending-queue.ts
    

  3. 验证结果:

    WORKER_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
    curl http://127.0.0.1:$WORKER_PORT/api/pending-queue | jq '.queue'
    

回归测试

在发布之前:

  1. 测试所有 Hook 触发器:
  2. SessionStart:启动新的 Claude Code 会话
  3. UserPromptSubmit:提交一个 Prompt
  4. PostToolUse:使用 Read 之类的工具
  5. Summary:让会话完成
  6. SessionEnd:关闭 Claude Code

  7. 测试核心功能:

  8. 上下文注入(显示最近的会话)
  9. Observation 处理(生成 summaries)
  10. MCP 搜索工具(搜索返回结果)
  11. Viewer UI(在 worker URL 加载)
  12. 手动恢复(恢复卡住的消息)

  13. 测试边缘情况:

  14. Worker 崩溃恢复
  15. 数据库锁
  16. 端口冲突
  17. 大型数据库

  18. 跨平台(如适用):

  19. macOS
  20. Linux
  21. Windows

代码风格

TypeScript 指南

  • 使用 TypeScript 严格模式
  • 为所有数据结构定义接口
  • 对异步代码使用 async/await
  • 显式处理错误
  • 为公共 API 添加 JSDoc 注释

格式化

  • 遵循现有的代码格式
  • 使用 2 空格缩进
  • 字符串使用单引号
  • 在对象/数组末尾添加尾随逗号

示例

/**
 * Create a new observation in the database
 */
export async function createObservation(
  obs: Observation
): Promise<number> {
  try {
    const result = await db.insert('observations', {
      session_id: obs.session_id,
      tool_name: obs.tool_name,
      // ...
    });
    return result.id;
  } catch (error) {
    logger.error('Failed to create observation', error);
    throw error;
  }
}

调试

启用调试日志

export DEBUG=claude-mem:*
npm run worker:restart
npm run worker:logs

检查数据库

sqlite3 ~/.claude-mem/claude-mem.db

# View schema
.schema observations

# Query data
SELECT * FROM observations LIMIT 10;

追踪观测记录

使用关联 ID 在管道中追踪观测记录:

sqlite3 ~/.claude-mem/claude-mem.db
SELECT correlation_id, tool_name, created_at
FROM observations
WHERE session_id = 'YOUR_SESSION_ID'
ORDER BY created_at;

调试 Hooks

使用测试输入手动运行 hooks:

# Test context hook
echo '{"session_id":"test-123","cwd":"'$(pwd)'","source":"startup"}' | node plugin/scripts/context-hook.js

# Test new hook
echo '{"session_id":"test-123","cwd":"'$(pwd)'","prompt":"test"}' | node plugin/scripts/new-hook.js

发布

发布仅从 main 分支进行,拉取请求也以 main 为目标。

版本号更新

更新每个包含包版本的 manifest:

  • package.json
  • plugin/package.json
  • .claude-plugin/marketplace.json
  • .claude-plugin/plugin.json
  • plugin/.claude-plugin/plugin.json
  • .codex-plugin/plugin.json
  • plugin/.codex-plugin/plugin.json
  • openclaw/openclaw.plugin.json

确认旧版本已全部移除,新版本出现在所有预期位置:

git grep -n '"version": "NEW_VERSION"'
git grep -n '"version": "OLD_VERSION"'

构建、打标签和发布

npm run build-and-sync
git add -A
git commit -m "chore: bump version to NEW_VERSION"
git tag -a vNEW_VERSION -m "Version NEW_VERSION"
git push origin main
git push origin vNEW_VERSION

发布到 npm 需要维护者的 npm 凭据和双重身份验证(2FA):

npm publish
npm view claude-mem@NEW_VERSION version

只有在 npm 确认版本可用之后,再创建 GitHub release 并重新生成变更日志:

gh release create vNEW_VERSION --title "vNEW_VERSION" --notes "..."
npm run changelog:generate
git add CHANGELOG.md
git commit -m "docs: regenerate changelog for vNEW_VERSION"
git push origin main

请勿手动编辑 CHANGELOG.md;它由 GitHub releases 自动生成。

贡献

贡献流程

  1. Fork 仓库
  2. 创建功能分支(git checkout -b feature/amazing-feature)
  3. 进行你的更改
  4. 编写测试
  5. 更新文档
  6. 提交你的更改(git commit -m 'Add amazing feature')
  7. 推送到分支(git push origin feature/amazing-feature)
  8. 开启一个 Pull Request

Pull Request 指南

  • 清晰的标题:描述 PR 的作用
  • 描述:解释为什么需要此更改
  • 测试:包含新功能的测试
  • 文档:根据需要更新文档
  • 变更日志:请勿手动编辑 CHANGELOG.md;它在发布时自动生成
  • 提交信息:使用清晰、描述性的提交信息

代码审查流程

  1. 自动化测试必须通过
  2. 维护者进行代码审查
  3. 处理反馈
  4. 最终批准
  5. 合并到 main

开发工具

  • TypeScript
  • ESLint
  • Prettier
  • SQLite Viewer

常用命令

# Check TypeScript types
npx tsc --noEmit

# Lint code (if configured)
npm run lint

# Format code (if configured)
npm run format

# Clean build artifacts
rm -rf plugin/scripts/*.js plugin/scripts/*.cjs

开发故障排查

构建失败

  1. 清理 node_modules:

    rm -rf node_modules
    npm install
    

  2. 检查 Node.js 版本:

    node --version  # Should be >= 20.0.0
    

  3. 检查语法错误:

    npx tsc --noEmit
    

测试失败

  1. 检查数据库:

    rm ~/.claude-mem/claude-mem.db
    npm test
    

  2. 检查 Worker 状态:

    npm run worker:status
    

  3. 查看日志:

    npm run worker:logs
    

Worker 无法启动

  1. 终止现有进程:

    npm run worker:stop
    

  2. 检查端口:

    WORKER_PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
    lsof -i :$WORKER_PORT
    

  3. 尝试自定义端口:

    export CLAUDE_MEM_WORKER_PORT=38000
    npm run worker:start
    

后续步骤

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/claude-mem