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:
+26
-10
@@ -2,6 +2,7 @@ import type { Project, Task, UsageTokens } from '../model/types.js';
|
||||
import { git, type WorktreeInfo } from './worktree.js';
|
||||
import { runClaude } from './cc.js';
|
||||
import { pickModel } from './models.js';
|
||||
import { readGlobalAgentRules } from './protocol.js';
|
||||
|
||||
export { transcriptDir } from './cc.js';
|
||||
|
||||
@@ -25,6 +26,19 @@ const DEFAULT_MAX_TURNS = 100;
|
||||
/** 若项目未配置 timeoutMs 时的全局兜底超时(30min) */
|
||||
const DEFAULT_TIMEOUT_MS = 30 * 60_000;
|
||||
|
||||
/**
|
||||
* L4+L2 记忆注入:prompt 头部规范段。全局规范(L4,maestro 自维护 agent-global.md)在前,
|
||||
* 项目规范(L2,project.agentRules)在后。两者皆空 → 空数组。
|
||||
*/
|
||||
export function rulesHeaderLines(project?: Project, globalRules?: string | null): string[] {
|
||||
const out: string[] = [];
|
||||
const g = globalRules?.trim();
|
||||
if (g) out.push('## 全局执行规范(所有任务通用,必须遵守)', g, '');
|
||||
const pr = project?.agentRules?.trim();
|
||||
if (pr) out.push('## 项目规范(本项目专属,必须遵守)', pr, '');
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* L1 记忆注入:若任务带「上次失败原因」(重试任务),生成一段提醒;否则空数组。
|
||||
* 由 daemon 在 claimOne 时把 store.lastRunErrorOf 填进 task.lastRunError 下发(见 orchestrator)。
|
||||
@@ -42,13 +56,14 @@ function lastErrorLines(task: Task): string[] {
|
||||
];
|
||||
}
|
||||
|
||||
/** 组装任务提示词:标题 + operations/spec 全文 + 上次失败(L1) + 执行约束 */
|
||||
export function buildPrompt(task: Task): string {
|
||||
/** 组装任务提示词:标题 + 全局/项目规范(L4/L2) + operations/spec 全文 + 上次失败(L1) + 执行约束 */
|
||||
export function buildPrompt(task: Task, project?: Project, globalRules?: string | null): string {
|
||||
const body = task.operations ?? task.spec ?? task.plan ?? '';
|
||||
return [
|
||||
`# 任务:${task.title}`,
|
||||
`任务 ID:${task.id}`,
|
||||
'',
|
||||
...rulesHeaderLines(project, globalRules),
|
||||
'## 任务内容',
|
||||
body || '(无详细说明,按标题完成)',
|
||||
'',
|
||||
@@ -81,7 +96,7 @@ export async function runTask(task: Task, project: Project, worktree: WorktreeIn
|
||||
const model = pickModel(task, project, 'executor');
|
||||
const timeoutMs = project.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||
const cc = await runClaude({
|
||||
prompt: buildPrompt(task),
|
||||
prompt: buildPrompt(task, project, readGlobalAgentRules()),
|
||||
cwd: worktree.dir,
|
||||
model,
|
||||
runId,
|
||||
@@ -133,13 +148,13 @@ export interface PlannerResult {
|
||||
export type PlannerFn = (task: Task, project: Project, kind: PlanKind, runId: string) => Promise<PlannerResult>;
|
||||
|
||||
/** planner 提示词:只读代码库,spec=写改动方案;decompose=拆子任务并在末尾输出 fenced JSON。 */
|
||||
export function buildPlannerPrompt(task: Task, kind: PlanKind): string {
|
||||
const head = [`# 任务:${task.title}`, `任务 ID:${task.id}`];
|
||||
export function buildPlannerPrompt(task: Task, kind: PlanKind, project?: Project, globalRules?: string | null): string {
|
||||
const head = [`# 任务:${task.title}`, `任务 ID:${task.id}`, '', ...rulesHeaderLines(project, globalRules)];
|
||||
const errLines = lastErrorLines(task);
|
||||
if (kind === 'spec') {
|
||||
if (task.spec) head.push('', '## 现有方案草稿(可改进/替换)', task.spec);
|
||||
if (task.spec) head.push('## 现有方案草稿(可改进/替换)', task.spec, '');
|
||||
return [
|
||||
...head, '',
|
||||
...head,
|
||||
...errLines,
|
||||
'## 你的角色:方案作者(只读,不改任何文件)',
|
||||
'只读这个代码库,为上述任务写一份「具体改动方案」。不要编辑文件、不要执行有副作用的命令。',
|
||||
@@ -186,7 +201,7 @@ export async function runPlanner(task: Task, project: Project, kind: PlanKind, r
|
||||
const model = pickModel(task, project, 'planner');
|
||||
const timeoutMs = project.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||
const cc = await runClaude({
|
||||
prompt: buildPlannerPrompt(task, kind),
|
||||
prompt: buildPlannerPrompt(task, kind, project, readGlobalAgentRules()),
|
||||
cwd: project.repoPath, // 只读跑在主仓(planner 不改文件,无需 worktree)
|
||||
model,
|
||||
runId,
|
||||
@@ -199,11 +214,12 @@ export async function runPlanner(task: Task, project: Project, kind: PlanKind, r
|
||||
}
|
||||
|
||||
/** 解冲突任务的提示词:worker 已在 worktree 内触发 git merge 制造冲突态,CC 负责逐个解决。 */
|
||||
export function buildConflictPrompt(task: Task, conflictFiles: string[]): string {
|
||||
export function buildConflictPrompt(task: Task, conflictFiles: string[], project?: Project, globalRules?: string | null): string {
|
||||
return [
|
||||
`# 合并冲突解决:${task.title}`,
|
||||
`任务 ID:${task.id}`,
|
||||
'',
|
||||
...rulesHeaderLines(project, globalRules),
|
||||
'## 背景',
|
||||
'你的工作目录(git worktree)已执行 `git merge` 并产生合并冲突。你的任务是解决全部冲突,保留双方改动的意图,然后提交。',
|
||||
'',
|
||||
@@ -237,7 +253,7 @@ export async function runConflict(
|
||||
const model = pickModel(task, project, 'conflict' as import('./models.js').ModelRole);
|
||||
const timeoutMs = project.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||
const cc = await runClaude({
|
||||
prompt: buildConflictPrompt(task, conflictFiles),
|
||||
prompt: buildConflictPrompt(task, conflictFiles, project, readGlobalAgentRules()),
|
||||
cwd: worktree.dir,
|
||||
model,
|
||||
runId,
|
||||
|
||||
Reference in New Issue
Block a user