Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Coding Code 的核心哲学是所有行为都可配置。本文档详细介绍
| `mcp.yaml` | `~/.codingcode/mcp.yaml` + `.codingcode/mcp.yaml` | MCP 服务配置 | [→ mcp.md](mcp.md) |
| `hooks.yaml` | `~/.codingcode/hooks.yaml` + `.codingcode/hooks.yaml` | 钩子配置 | [→ hooks.md](hooks.md) |
| `agents/*.md` | `~/.codingcode/agents/` + `.codingcode/agents/` | 子智能体 profile | [→ subagent.md](subagent.md) |
| `memory.md` | `~/.codingcode/memory.md` + `.codingcode/memory.md` | 长期记忆 | [→ memory.md](memory.md) |
| `memory.md` | `./.codingcode/memory.md` | 长期记忆(项目级) | [→ memory.md](memory.md) |

---

Expand All @@ -41,8 +41,6 @@ context:
memory:
enabled: false # 启用长期记忆
model: "" # 记忆提取模型,空字符串回退主模型
projectFile: ".codingcode/memory.md" # 项目记忆文件路径
userFile: "~/.codingcode/memory.md" # 用户记忆文件路径
maxBytes: 16384 # 记忆文件最大字节数
promptMaxBytes: 8192 # 注入提示的最大字节数
extraTypes: [] # 自定义记忆类型
Expand All @@ -60,8 +58,6 @@ memory:
| `context.compactionModel` | `''` | 上下文压缩使用的模型,空字符串回退到主会话 LLM |
| `memory.enabled` | `false` | 是否启用长期记忆系统 |
| `memory.model` | `''` | 记忆提取使用的模型,空字符串回退到主模型 |
| `memory.projectFile` | `'.codingcode/memory.md'` | 项目级记忆文件路径 |
| `memory.userFile` | `'~/.codingcode/memory.md'` | 用户级记忆文件路径 |
| `memory.maxBytes` | `16384` | 单个记忆文件的最大字节数 |
| `memory.promptMaxBytes` | `8192` | 注入 system prompt 的记忆内容最大字节数 |
| `memory.extraTypes` | `[]` | 自定义记忆类型列表 |
Expand Down
9 changes: 1 addition & 8 deletions docs/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ Coding Code 支持跨会话的长期记忆,自动从对话中提取和存储

## 内存类型

| 类型 | 位置 | 作用 |
|---|---|---|
| **用户级** | `~/.codingcode/memory.md` | 跨所有项目的个人知识库 |
| **项目级** | `./.codingcode/memory.md` | 特定项目的上下文 |

路径可在 `codingcode.yaml` 的 `memory.projectFile` 和 `memory.userFile` 中自定义。
记忆文件存储在项目的 `.codingcode/memory.md` 中。

---

Expand Down Expand Up @@ -81,8 +76,6 @@ Agent 在每次会话后自动执行记忆提取:
memory:
enabled: true # 启用长期记忆(默认 false)
model: "" # 记忆提取模型,空字符串回退到主模型
projectFile: ".codingcode/memory.md" # 项目记忆文件路径
userFile: "~/.codingcode/memory.md" # 用户记忆文件路径
maxBytes: 16384 # 记忆文件最大字节数
promptMaxBytes: 8192 # 注入提示的最大字节数
extraTypes: [] # 自定义记忆类型
Expand Down
29 changes: 6 additions & 23 deletions packages/codingcode/src/memory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { findSessionIndex, resolveSessionDir } from '../session/file-ops.js';
import type { SessionEvent } from '../session/types.js';
import {
readMemoryFile,
resolveProjectMemoryPath,
resolveUserMemoryPath,
resolveMemoryPath,
extractAutoBlock,
replaceAutoBlock,
mergeAutoBlocks,
Expand Down Expand Up @@ -38,24 +37,13 @@ export class MemoryService extends Effect.Service<MemoryService>()('Memory', {
if (!getMemoryEnabled()) return '';
const cfg = getMemoryConfig();

const projectPath = resolveProjectMemoryPath(cwd, cfg);
const userPath = resolveUserMemoryPath(cfg);

const projectPath = resolveMemoryPath(cwd);
const projectContent = readMemoryFile(projectPath);
const userContent = readMemoryFile(userPath);

const projectAuto = extractAutoBlock(projectContent);
const userAuto = extractAutoBlock(userContent);

const parts = [];
if (projectAuto) parts.push(projectAuto);
if (userAuto) parts.push(userAuto);

if (parts.length === 0) return '';

const combined = parts.join('\n\n');
const stripped = stripMarkersForPrompt(combined);
if (!projectAuto) return '';

const stripped = stripMarkersForPrompt(projectAuto);
const truncated = truncateForPrompt(stripped, cfg.promptMaxBytes);

return truncated ? `## Long-term Memory\n\n${truncated}` : '';
Expand Down Expand Up @@ -129,15 +117,10 @@ export class MemoryService extends Effect.Service<MemoryService>()('Memory', {
}

const cwd = sessionIndex.cwd;
const projectPath = resolveProjectMemoryPath(cwd, cfg);
const userPath = resolveUserMemoryPath(cfg);
const projectPath = resolveMemoryPath(cwd);

const projectContent = readMemoryFile(projectPath);
const userContent = readMemoryFile(userPath);

const projectAuto = extractAutoBlock(projectContent);
const userAuto = extractAutoBlock(userContent);
const currentAuto = [projectAuto, userAuto].filter(Boolean).join('\n\n');
const currentAuto = extractAutoBlock(projectContent);

try {
let events: SessionEvent[] = [];
Expand Down
23 changes: 2 additions & 21 deletions packages/codingcode/src/memory/storage.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,8 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import type { MemoryConfig } from '@codingcode/infra/config';

// ── Path Resolution ──

export function resolveProjectMemoryPath(cwd: string, cfg: MemoryConfig): string {
const filePath = cfg.projectFile;
if (path.isAbsolute(filePath)) {
return filePath;
}
return path.join(cwd, filePath);
}

export function resolveUserMemoryPath(cfg: MemoryConfig): string {
const filePath = cfg.userFile;
if (filePath.startsWith('~')) {
return path.join(os.homedir(), filePath.slice(1));
}
if (path.isAbsolute(filePath)) {
return filePath;
}
return path.join(os.homedir(), filePath);
export function resolveMemoryPath(cwd: string): string {
return path.join(cwd, '.codingcode', 'memory.md');
}

// ── File Read/Write ──
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/agent-cache-stability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/agent-concurrent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/agent-todo-event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/hooks-deps-type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/loop-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/memory-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down Expand Up @@ -168,7 +166,7 @@
.reverse()
.find((m: any) => m.role === 'user');
expect(lastUserMsg).toBeDefined();
expect(lastUserMsg.content).toContain('<system-reminder>');

Check failure on line 169 in packages/codingcode/test/agent/memory-snapshot.test.ts

View workflow job for this annotation

GitHub Actions / test

packages/codingcode/test/agent/memory-snapshot.test.ts > Memory snapshot stability > injects <system-reminder> when memory changed since snapshot

AssertionError: expected 'hi' to contain '<system-reminder>' Expected: "<system-reminder>" Received: "hi" ❯ packages/codingcode/test/agent/memory-snapshot.test.ts:169:33
expect(lastUserMsg.content).toContain('Updated on disk');
});

Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/agent/stop-hook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
2 changes: 0 additions & 2 deletions packages/codingcode/test/context/append-turn-end.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ vi.mock('@codingcode/infra/config', () => ({
memory: {
enabled: false,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
57 changes: 5 additions & 52 deletions packages/codingcode/test/memory/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ function makeCfg(overrides?: Partial<MemoryConfig>): MemoryConfig {
return {
enabled: true,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand All @@ -45,16 +43,7 @@ describe('Memory Config', () => {

describe('getEffectiveTypes', () => {
it('includes default types when enabled', () => {
const cfg: MemoryConfig = {
enabled: true,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
disabledTypes: [],
};
const cfg: MemoryConfig = makeCfg();

const types = getEffectiveTypes(cfg);
expect(types).toHaveLength(3);
Expand All @@ -71,33 +60,15 @@ describe('Memory Config', () => {
enabled: true,
},
];
const cfg: MemoryConfig = {
enabled: true,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: extra,
disabledTypes: [],
};
const cfg: MemoryConfig = makeCfg({ extraTypes: extra });

const types = getEffectiveTypes(cfg);
expect(types).toHaveLength(4);
expect(types.map((t) => t.name)).toContain('custom');
});

it('filters disabled types', () => {
const cfg: MemoryConfig = {
enabled: true,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
disabledTypes: ['user', 'project'],
};
const cfg: MemoryConfig = makeCfg({ disabledTypes: ['user', 'project'] });

const types = getEffectiveTypes(cfg);
expect(types).toHaveLength(1);
Expand All @@ -112,16 +83,7 @@ describe('Memory Config', () => {
enabled: true,
},
];
const cfg: MemoryConfig = {
enabled: true,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: extra,
disabledTypes: ['custom'],
};
const cfg: MemoryConfig = makeCfg({ extraTypes: extra, disabledTypes: ['custom'] });

const types = getEffectiveTypes(cfg);
expect(types.map((t) => t.name)).not.toContain('custom');
Expand All @@ -135,16 +97,7 @@ describe('Memory Config', () => {
enabled: false,
},
];
const cfg: MemoryConfig = {
enabled: true,
model: '',
projectFile: '',
userFile: '',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: extra,
disabledTypes: [],
};
const cfg: MemoryConfig = makeCfg({ extraTypes: extra });

const types = getEffectiveTypes(cfg);
expect(types.map((t) => t.name)).not.toContain('disabled_custom');
Expand Down
14 changes: 1 addition & 13 deletions packages/codingcode/test/memory/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ vi.mock('../../src/memory/config.js', () => ({
getMemoryConfig: vi.fn(() => ({
enabled: false,
model: '',
projectFile: '.codingcode/memory.md',
userFile: '~/.codingcode/memory.md',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand All @@ -74,8 +72,6 @@ describe('Memory Index', () => {
vi.mocked(getMemoryConfig).mockReturnValue({
enabled: true,
model: '',
projectFile: '.codingcode/memory.md',
userFile: '~/.codingcode/memory.md',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand All @@ -86,13 +82,11 @@ describe('Memory Index', () => {
expect(result).toBe('');
});

it('loads and combines memory from project and user files', async () => {
it('loads memory from project file', async () => {
const { getMemoryConfig } = await import('../../src/memory/config.js');
vi.mocked(getMemoryConfig).mockReturnValue({
enabled: true,
model: '',
projectFile: '.codingcode/memory.md',
userFile: '~/.codingcode/memory.md',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down Expand Up @@ -121,8 +115,6 @@ describe('Memory Index', () => {
vi.mocked(getMemoryConfig).mockReturnValue({
enabled: true,
model: '',
projectFile: '.codingcode/memory.md',
userFile: '~/.codingcode/memory.md',
maxBytes: 16384,
promptMaxBytes: 100,
extraTypes: [],
Expand Down Expand Up @@ -156,8 +148,6 @@ describe('Memory Index', () => {
vi.mocked(getMemoryConfig).mockReturnValue({
enabled: true,
model: '',
projectFile: '.codingcode/memory.md',
userFile: '~/.codingcode/memory.md',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand All @@ -173,8 +163,6 @@ describe('Memory Index', () => {
vi.mocked(getMemoryConfig).mockReturnValue({
enabled: true,
model: '',
projectFile: '.codingcode/memory.md',
userFile: '~/.codingcode/memory.md',
maxBytes: 16384,
promptMaxBytes: 8192,
extraTypes: [],
Expand Down
Loading
Loading