CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
remember.test.ts387 linesDownload Raw Back to memory
1/**2 * @license3 * Copyright 2026 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import * as fs from 'node:fs/promises';8import * as os from 'node:os';9import * as path from 'node:path';10import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';11import type { Config } from '../config/config.js';12import type { PermissionManager } from '../permissions/permission-manager.js';13import type { ForkedAgentResult } from '../utils/forkedAgent.js';14import { runForkedAgent } from '../utils/forkedAgent.js';15import {16  buildBareRememberPrompt,17  buildManagedRememberPrompt,18  runManagedRememberByAgent,19} from './remember.js';20import {21  clearAutoMemoryRootCache,22  getAutoMemoryRoot,23  getUserAutoMemoryRoot,24} from './paths.js';25import {26  rebuildManagedAutoMemoryIndex,27  rebuildUserAutoMemoryIndex,28} from './indexer.js';29 30vi.mock('../utils/forkedAgent.js', () => ({31  runForkedAgent: vi.fn(),32}));33 34vi.mock('./indexer.js', () => ({35  rebuildManagedAutoMemoryIndex: vi.fn(),36  rebuildUserAutoMemoryIndex: vi.fn(),37}));38 39function createConfig(projectRoot: string, managed = true): Config {40  return {41    isManagedMemoryAvailable: vi.fn().mockReturnValue(managed),42    getProjectRoot: vi.fn().mockReturnValue(projectRoot),43    getUserMemory: vi.fn().mockReturnValue('QWEN/AGENTS guidance'),44  } as unknown as Config;45}46 47describe('remember memory helper', () => {48  const originalMemoryBase = process.env['QWEN_CODE_MEMORY_BASE_DIR'];49  let tempDir: string;50  let projectRoot: string;51 52  beforeEach(async () => {53    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'remember-helper-'));54    projectRoot = path.join(tempDir, 'project');55    await fs.mkdir(projectRoot, { recursive: true });56    process.env['QWEN_CODE_MEMORY_BASE_DIR'] = path.join(tempDir, 'memory');57    clearAutoMemoryRootCache();58    vi.mocked(runForkedAgent).mockReset();59    vi.mocked(rebuildManagedAutoMemoryIndex).mockReset();60    vi.mocked(rebuildUserAutoMemoryIndex).mockReset();61    vi.mocked(rebuildManagedAutoMemoryIndex).mockResolvedValue('');62    vi.mocked(rebuildUserAutoMemoryIndex).mockResolvedValue('');63  });64 65  afterEach(async () => {66    if (originalMemoryBase === undefined) {67      delete process.env['QWEN_CODE_MEMORY_BASE_DIR'];68    } else {69      process.env['QWEN_CODE_MEMORY_BASE_DIR'] = originalMemoryBase;70    }71    clearAutoMemoryRootCache();72    await fs.rm(tempDir, { recursive: true, force: true });73  });74 75  it('builds the same managed and bare prompts used by /remember', () => {76    const managed = buildManagedRememberPrompt(77      '  prefers focused tests  ',78      projectRoot,79    );80 81    expect(managed).toContain(82      'Please save the following to your memory system.',83    );84    expect(managed).toContain('USER memory at');85    expect(managed).toContain('PROJECT memory at');86    expect(managed).toContain(getAutoMemoryRoot(projectRoot));87    expect(managed).toContain('prefers focused tests');88    expect(managed).not.toContain('<user-content>');89    expect(managed).not.toContain('</user-content>');90    expect(managed).not.toContain('  prefers focused tests  ');91 92    const wrapped = buildManagedRememberPrompt(93      '  hidden context  ',94      projectRoot,95      { wrapUserContent: true },96    );97    expect(wrapped).toContain(98      '<user-content>\nhidden context\n</user-content>',99    );100 101    const bare = buildBareRememberPrompt('  appends to qwen  ');102    expect(bare).toBe(103      'Please save the following fact to memory (e.g. append to QWEN.md in the project root):\n\nappends to qwen',104    );105  });106 107  it('runs clean context with managed-memory tools only', async () => {108    const touched = path.join(getAutoMemoryRoot(projectRoot), 'project.md');109    vi.mocked(runForkedAgent).mockResolvedValue({110      status: 'completed',111      finalText: 'Saved project memory.',112      filesTouched: [touched],113      filesWritten: [touched],114    } satisfies ForkedAgentResult);115 116    const result = await runManagedRememberByAgent({117      config: createConfig(projectRoot),118      projectRoot,119      content: 'Remember the project uses vitest.',120      contextMode: 'clean',121    });122 123    expect(result).toEqual({124      summary: 'Memory update completed.',125      filesTouched: [touched],126      touchedScopes: ['project'],127    });128    expect(runForkedAgent).toHaveBeenCalledTimes(1);129    const params = vi.mocked(runForkedAgent).mock.calls[0]?.[0] as {130      config: Config;131      extraHistory?: unknown[];132      preserveEmptyExtraHistory?: boolean;133      systemPrompt: string;134      taskPrompt: string;135      tools: string[];136    };137    expect(params.extraHistory).toEqual([]);138    expect(params.preserveEmptyExtraHistory).toBe(true);139    expect(params.tools).toEqual([140      'read_file',141      'grep_search',142      'list_directory',143      'write_file',144      'edit',145    ]);146    expect(params.config.getUserMemory()).toBe('');147    expect(params.config.getDisableAllHooks()).toBe(true);148    expect(params.config.getHookSystem()).toBeUndefined();149    expect(params.config.getMessageBus()).toBeUndefined();150    const pm = params.config.getPermissionManager() as PermissionManager;151    await expect(152      pm.evaluate({153        toolName: 'grep_search',154        filePath: getAutoMemoryRoot(projectRoot),155      }),156    ).resolves.toBe('allow');157    await expect(158      pm.evaluate({159        toolName: 'list_directory',160        filePath: getUserAutoMemoryRoot(),161      }),162    ).resolves.toBe('allow');163    await expect(164      pm.evaluate({165        toolName: 'grep_search',166        filePath: path.join(projectRoot, 'src'),167      }),168    ).resolves.toBe('deny');169    expect(params.systemPrompt).toContain('managed auto-memory system only');170    expect(params.taskPrompt).toContain('Remember the project uses vitest.');171    expect(params.taskPrompt).toContain('<user-content>');172    expect(rebuildManagedAutoMemoryIndex).toHaveBeenCalledWith(projectRoot);173  });174 175  it('classifies only successful memory writes', async () => {176    const projectFile = path.join(getAutoMemoryRoot(projectRoot), 'project.md');177    vi.mocked(runForkedAgent).mockResolvedValue({178      status: 'completed',179      finalText: 'Saved project memory.',180      filesTouched: [path.join(projectRoot, 'README.md'), projectFile],181      filesWritten: [projectFile],182    } satisfies ForkedAgentResult);183 184    const result = await runManagedRememberByAgent({185      config: createConfig(projectRoot),186      projectRoot,187      content: 'Remember write-only paths.',188      contextMode: 'workspace',189    });190 191    expect(result).toEqual({192      summary: 'Memory update completed.',193      filesTouched: [projectFile],194      touchedScopes: ['project'],195    });196    const params = vi.mocked(runForkedAgent).mock.calls[0]?.[0] as {197      extraHistory?: unknown[];198      preserveEmptyExtraHistory?: boolean;199    };200    expect(params.extraHistory).toBeUndefined();201    expect(params.preserveEmptyExtraHistory).toBe(false);202    expect(rebuildManagedAutoMemoryIndex).toHaveBeenCalledWith(projectRoot);203  });204 205  it('disables chat recording for hidden remember agents', async () => {206    const touched = path.join(getAutoMemoryRoot(projectRoot), 'project.md');207    vi.mocked(runForkedAgent).mockResolvedValue({208      status: 'completed',209      filesTouched: [touched],210      filesWritten: [touched],211    } satisfies ForkedAgentResult);212 213    await runManagedRememberByAgent({214      config: createConfig(projectRoot),215      projectRoot,216      content: 'Remember without creating a visible session.',217      contextMode: 'workspace',218    });219 220    const params = vi.mocked(runForkedAgent).mock.calls[0]?.[0] as {221      config: Config;222      suppressChatRecording?: boolean;223    };224    expect(params.config.getChatRecordingService()).toBeUndefined();225    expect(params.config.getTranscriptPath()).toBe('');226    expect(params.suppressChatRecording).toBe(true);227  });228 229  it('rebuilds touched project indexes and best-effort user indexes', async () => {230    const projectFile = path.join(getAutoMemoryRoot(projectRoot), 'project.md');231    const userFile = path.join(getUserAutoMemoryRoot(), 'user.md');232    vi.mocked(rebuildUserAutoMemoryIndex).mockRejectedValue(233      new Error('user index unavailable'),234    );235    vi.mocked(runForkedAgent).mockResolvedValue({236      status: 'completed',237      filesTouched: [userFile, projectFile],238      filesWritten: [userFile, projectFile],239    } satisfies ForkedAgentResult);240 241    const result = await runManagedRememberByAgent({242      config: createConfig(projectRoot),243      projectRoot,244      content: 'Remember both scopes.',245      contextMode: 'workspace',246    });247 248    expect(result.touchedScopes).toEqual(['project', 'user']);249    expect(rebuildManagedAutoMemoryIndex).toHaveBeenCalledWith(projectRoot);250    expect(rebuildUserAutoMemoryIndex).toHaveBeenCalledTimes(1);251  });252 253  it('classifies symlinked project memory paths by realpath', async () => {254    const projectMemoryRoot = getAutoMemoryRoot(projectRoot);255    await fs.mkdir(projectMemoryRoot, { recursive: true });256    const linkedMemoryRoot = path.join(tempDir, 'linked-project-memory');257    await fs.symlink(projectMemoryRoot, linkedMemoryRoot, 'dir');258    const touched = path.join(linkedMemoryRoot, 'project.md');259    await fs.writeFile(touched, 'memory');260    vi.mocked(runForkedAgent).mockResolvedValue({261      status: 'completed',262      filesTouched: [touched],263      filesWritten: [touched],264    } satisfies ForkedAgentResult);265 266    const result = await runManagedRememberByAgent({267      config: createConfig(projectRoot),268      projectRoot,269      content: 'Remember symlinked memory.',270      contextMode: 'workspace',271    });272 273    expect(result.touchedScopes).toEqual(['project']);274    expect(rebuildManagedAutoMemoryIndex).toHaveBeenCalledWith(projectRoot);275  });276 277  it('rejects when managed memory is unavailable', async () => {278    await expect(279      runManagedRememberByAgent({280        config: createConfig(projectRoot, false),281        projectRoot,282        content: 'Remember me.',283        contextMode: 'workspace',284      }),285    ).rejects.toMatchObject({ code: 'managed_memory_unavailable' });286    expect(runForkedAgent).not.toHaveBeenCalled();287  });288 289  it('fails if the hidden agent touches a non-memory path', async () => {290    vi.mocked(runForkedAgent).mockResolvedValue({291      status: 'completed',292      filesTouched: [path.join(projectRoot, 'README.md')],293      filesWritten: [path.join(projectRoot, 'README.md')],294    } satisfies ForkedAgentResult);295 296    await expect(297      runManagedRememberByAgent({298        config: createConfig(projectRoot),299        projectRoot,300        content: 'Remember me.',301        contextMode: 'workspace',302      }),303    ).rejects.toMatchObject({ code: 'remember_path_escape' });304    expect(rebuildManagedAutoMemoryIndex).not.toHaveBeenCalled();305  });306 307  it('propagates failed termination reasons before auditing written paths', async () => {308    vi.mocked(runForkedAgent).mockResolvedValue({309      status: 'failed',310      terminateReason: 'max turns exceeded',311      filesTouched: [path.join(projectRoot, 'README.md')],312      filesWritten: [path.join(projectRoot, 'README.md')],313    } satisfies ForkedAgentResult);314 315    await expect(316      runManagedRememberByAgent({317        config: createConfig(projectRoot),318        projectRoot,319        content: 'Remember me.',320        contextMode: 'workspace',321      }),322    ).rejects.toThrow('max turns exceeded');323    expect(rebuildManagedAutoMemoryIndex).not.toHaveBeenCalled();324  });325 326  it('propagates failed and cancelled agent termination reasons', async () => {327    vi.mocked(runForkedAgent).mockResolvedValueOnce({328      status: 'failed',329      terminateReason: 'max turns exceeded',330      filesTouched: [],331    } satisfies ForkedAgentResult);332 333    await expect(334      runManagedRememberByAgent({335        config: createConfig(projectRoot),336        projectRoot,337        content: 'Remember me.',338        contextMode: 'workspace',339      }),340    ).rejects.toThrow('max turns exceeded');341 342    vi.mocked(runForkedAgent).mockResolvedValueOnce({343      status: 'cancelled',344      terminateReason: 'aborted',345      filesTouched: [],346    } satisfies ForkedAgentResult);347 348    await expect(349      runManagedRememberByAgent({350        config: createConfig(projectRoot),351        projectRoot,352        content: 'Remember me.',353        contextMode: 'workspace',354      }),355    ).rejects.toThrow('aborted');356  });357 358  it('remember agent always receives the full protocol even when all indexes are empty', async () => {359    const touched = path.join(getAutoMemoryRoot(projectRoot), 'user.md');360    vi.mocked(runForkedAgent).mockResolvedValue({361      status: 'completed',362      finalText: 'Saved.',363      filesTouched: [touched],364      filesWritten: [touched],365    } satisfies ForkedAgentResult);366 367    await runManagedRememberByAgent({368      config: createConfig(projectRoot),369      projectRoot,370      content: 'Remember this fact.',371      contextMode: 'clean',372    });373 374    const params = vi.mocked(runForkedAgent).mock.calls[0]?.[0] as {375      systemPrompt: string;376    };377    // Full-protocol markers must be present (forceFullProtocol: true)378    expect(params.systemPrompt).toContain('## Types of memory');379    expect(params.systemPrompt).toContain('## What NOT to save in memory');380    expect(params.systemPrompt).toContain('## When to access memories');381    expect(params.systemPrompt).toContain('## Before recommending from memory');382    // Condensed-only markers must NOT appear383    expect(params.systemPrompt).not.toContain('## Memory types');384    expect(params.systemPrompt).not.toContain('## Do not save');385  });386});387 
basant307/AI_Governance_Project · CoolFace