feat(FN-4063): enforce planning and triage memory write contracts

Enforces memory write contracts for triage and planning agents, adding tests in both `project-memory.test.ts` and `triage.test.ts` to guard the contract boundaries established in `project-memory.ts`.

Fusion-Task-Id: FN-4063

Fusion-Task-Lineage: 5f86295b-d583-4757-9aa5-30a64b2cc2d6
This commit is contained in:
Fusion
2026-05-11 20:40:30 -07:00
committed by gsxdsm
parent 1f7958fc0f
commit 0056b85668
4 changed files with 21 additions and 1 deletions

View File

@@ -536,6 +536,9 @@ describe("project-memory", () => {
const instructions = buildTriageMemoryInstructions(testDir, settings); const instructions = buildTriageMemoryInstructions(testDir, settings);
expect(instructions).toContain(".fusion/memory/MEMORY.md"); expect(instructions).toContain(".fusion/memory/MEMORY.md");
expect(instructions).toContain("## Project Memory"); expect(instructions).toContain("## Project Memory");
expect(instructions).toContain("fn_memory_append");
expect(instructions).toContain("Do **not** write");
expect(instructions).toContain("or any other memory files directly");
}); });
it("includes read-only wording for readonly backend without write directives", () => { it("includes read-only wording for readonly backend without write directives", () => {
@@ -558,6 +561,8 @@ describe("project-memory", () => {
expect(instructions).not.toContain(".fusion/memory/MEMORY.md"); expect(instructions).not.toContain(".fusion/memory/MEMORY.md");
expect(instructions).toContain("memory_search"); expect(instructions).toContain("memory_search");
expect(instructions).toContain("memory_get"); expect(instructions).toContain("memory_get");
expect(instructions).toContain("fn_memory_append");
expect(instructions).toContain("Do **not** write memory files directly");
}); });
it("QMD triage instructions completeness - contains consult guidance", () => { it("QMD triage instructions completeness - contains consult guidance", () => {

View File

@@ -52,7 +52,9 @@ export function createTaskStoreTestHarness() {
afterEach: async () => { afterEach: async () => {
vi.useRealTimers(); vi.useRealTimers();
store.stopWatching(); store.stopWatching();
await new Promise<void>((resolve) => process.nextTick(resolve)); // Yield one microtask tick without relying on process.nextTick,
// which can be faked in timer-heavy suites and hang teardown.
await Promise.resolve();
store.close(); store.close();
await rm(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }); await rm(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
await rm(globalDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }); await rm(globalDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });

View File

@@ -374,6 +374,10 @@ This project has OpenClaw-style memory files:
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search 2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
3. Incorporate relevant learnings into your specification — reference actual patterns, constraints, and conventions documented there 3. Incorporate relevant learnings into your specification — reference actual patterns, constraints, and conventions documented there
**Memory write contract for planning agents:**
- If you need to save durable planning context, use \`fn_memory_append\` (choose scope/layer intentionally)
- Do **not** write \`.fusion/memory/MEMORY.md\`, \`.fusion/memory/YYYY-MM-DD.md\`, or any other memory files directly when \`fn_memory_append\` is available
Do not read all memory directly by default. If memory is irrelevant, skip it. Do not read all memory directly by default. If memory is irrelevant, skip it.
`; `;
} }
@@ -389,6 +393,10 @@ This project has a memory system that stores durable project learnings.
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search 2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
3. Incorporate useful learnings into your specification 3. Incorporate useful learnings into your specification
**Memory write contract for planning agents:**
- If you need to save durable planning context, use \`fn_memory_append\` (choose scope/layer intentionally)
- Do **not** write memory files directly when \`fn_memory_append\` is available
**If the memory contains useful context for this task, reference it in the specification.** **If the memory contains useful context for this task, reference it in the specification.**
`; `;
} }

View File

@@ -418,6 +418,9 @@ describe("buildSpecificationPrompt", () => {
); );
expect(prompt).toContain("## Project Memory"); expect(prompt).toContain("## Project Memory");
expect(prompt).toContain(".fusion/memory/"); expect(prompt).toContain(".fusion/memory/");
expect(prompt).toContain("fn_memory_append");
expect(prompt).toContain("Do **not** write");
expect(prompt).toContain("or any other memory files directly");
}); });
it("includes read-only wording for readonly backend without write directives", () => { it("includes read-only wording for readonly backend without write directives", () => {
@@ -462,6 +465,8 @@ describe("buildSpecificationPrompt", () => {
expect(prompt).not.toContain(".fusion/memory/"); expect(prompt).not.toContain(".fusion/memory/");
expect(prompt).toContain("fn_memory_search"); expect(prompt).toContain("fn_memory_search");
expect(prompt).toContain("fn_memory_get"); expect(prompt).toContain("fn_memory_get");
expect(prompt).toContain("fn_memory_append");
expect(prompt).toContain("Do **not** write memory files directly");
}); });
it("QMD prompt has actionable memory instructions", () => { it("QMD prompt has actionable memory instructions", () => {