feat(agent): L2/L4 记忆注入——项目规范 + 全局规范文件喂进 4 处 prompt

- L2 projects.agent_rules 列(schema+迁移+类型+mapper+patchProject+API 透传)
- L4 protocol.readGlobalAgentRules():worker 直接读 <data>/agent-global.md(非DB,
  不读用户 ~/.claude/CLAUDE.md,避免个人习惯与受控执行冲突)
- runner.rulesHeaderLines 注入 executor/planner/conflict;reviewer 同注入
- 顺序:全局规范(L4)→项目规范(L2)→任务内容→上次失败(L1)→执行约束

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-24 22:29:29 +08:00
parent ff2d7c22e7
commit fe7923a618
10 changed files with 81 additions and 13 deletions
@@ -2,7 +2,11 @@ import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buildPrompt, buildPlannerPrompt, buildConflictPrompt } from '../src/executor/runner.js';
import { Store } from '../src/store/index.js';
import type { Task } from '../src/model/types.js';
import type { Task, Project } from '../src/model/types.js';
function fakeProject(over: Partial<Project> = {}): Project {
return { defaultBranch: 'main', agentRules: null, ...over } as Project;
}
function fakeTask(over: Partial<Task> = {}): Task {
return {
@@ -43,6 +47,28 @@ test('L1: 超长错误被截断到 2000 字符', () => {
assert.ok(!p.includes('X'.repeat(2100)));
});
test('L2: project.agentRules 注入 executor/planner/conflict/,未设则不注入', () => {
const t = fakeTask();
const withRules = fakeProject({ agentRules: '本项目禁止改 schema.sql' });
assert.ok(buildPrompt(t, withRules).includes('项目规范'));
assert.ok(buildPrompt(t, withRules).includes('禁止改 schema.sql'));
assert.ok(buildPlannerPrompt(fakeTask({ complexity: 'hard' }), 'decompose', withRules).includes('禁止改 schema.sql'));
assert.ok(buildConflictPrompt(t, ['a.ts'], withRules).includes('禁止改 schema.sql'));
// 未设规范 → 不出现「项目规范」标题
assert.ok(!buildPrompt(t, fakeProject()).includes('项目规范'));
assert.ok(!buildPrompt(t).includes('项目规范')); // 连 project 都没传
});
test('L4: globalRules 注入且排在项目规范之前', () => {
const t = fakeTask();
const p = buildPrompt(t, fakeProject({ agentRules: 'PROJ_RULE' }), 'GLOBAL_RULE');
assert.ok(p.includes('全局执行规范'));
assert.ok(p.includes('GLOBAL_RULE'));
assert.ok(p.indexOf('GLOBAL_RULE') < p.indexOf('PROJ_RULE'), '全局规范在项目规范之前');
// 全局规范在任务内容之前(注入顺序:L4→L2→任务内容)
assert.ok(p.indexOf('GLOBAL_RULE') < p.indexOf('任务内容'));
});
test('lastRunErrorOf 取最近一次失败 run 的错误(任意 kind', () => {
const s = new Store(':memory:');
const p = s.createProject({ name: 'l1', repoPath: '/tmp/l1-' + Math.random() });