Skip to content

feat: Add memory service and code indexer (Phase 9)#62

Open
Finfinder wants to merge 7 commits into
0.2.0from
feat/0.2.0/Issue/9
Open

feat: Add memory service and code indexer (Phase 9)#62
Finfinder wants to merge 7 commits into
0.2.0from
feat/0.2.0/Issue/9

Conversation

@Finfinder

Copy link
Copy Markdown
Owner

Summary

  • Added @agentdeck/memory-service\ package — agent memory management with listing, read, propose/apply edits, conflict resolution, and redaction
  • Added @agentdeck/code-indexer\ package — code indexing with chunking, language detection, index building, and retrieval
  • Added Phase 9 IPC handlers (listMemories, readMemory, proposeMemoryChange, applyMemoryChange, memoryConflictResolve, indexCodeFile, retrieveCode, rebuildCodeIndex)
  • Added \MemoryReviewDialog\ for browsing and managing memory files
  • Added 17 unit tests for memory-service, code-indexer, redaction, local-store, and MemoryReviewDialog
  • Updated CHANGELOG.md with Phase 9 entries

Test plan

  • Run
    pm test\ and verify all tests pass
  • Run
    pm run lint\ and verify no lint errors
  • Run
    pm run typecheck\ and verify no type errors
  • Verify SonarQube quality gate is green

}

function extractMarkdownTags(content: string): string[] {
const match = /(?:^|\n)\s*tags\s*:\s*([^\n\r;]{1,120})/i.exec(content);
Copilot AI review requested due to automatic review settings June 20, 2026 10:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Phase 9 “agent memory + code retrieval” capabilities by adding two new packages—@agentdeck/memory-service and @agentdeck/code-indexer—and wiring them into the desktop app via new IPC channels, preload API additions, and a UI dialog for memory-conflict review. It also expands test coverage and updates build/test configuration to support the new modules (including a dedicated migrations test config).

Changes:

  • Added @agentdeck/memory-service (memory read/list, propose/apply edits, conflict reporting, redaction, and a SQLite-backed local store).
  • Added @agentdeck/code-indexer (tree-sitter-based chunking, indexing, retrieval, and rebuild support).
  • Added Phase 9 IPC channels/handlers, preload API wiring (partial), UI MemoryReviewDialog, and multiple new unit tests + Vitest config adjustments.

Reviewed changes

Copilot reviewed 48 out of 50 changed files in this pull request and generated 32 comments.

Show a summary per file
File Description
vitest.migrations.config.ts Adds a dedicated Vitest config for running SQLite migrations tests in Node.
vitest.config.ts Updates unit-test config with new aliases, mocks, exclusions, and coverage settings for Phase 9.
tsconfig.json Adds TS project references for the new packages.
tsconfig.base.json Adds TS path aliases for the new packages.
tests/unit/tool-router-deep-coverage.test.ts Adds deep-coverage tests for ToolRouter, including memory-service tool paths.
tests/unit/settings-service.test.ts Adds validation/defaulting tests for theme settings read/write.
tests/unit/redaction.test.ts Adds redaction unit tests for memory-service redaction logic.
tests/unit/redaction-more.test.ts Adds additional redaction pattern tests.
tests/unit/phase9-type-guards.test.ts Adds tests for newly introduced Phase 9 type guards.
tests/unit/permission-broker.test.ts Updates expected classification count for new tools.
tests/unit/memory-service.test.ts Adds basic MemoryService unit tests (creation/diff/metadata extraction).
tests/unit/memory-review-dialog.test.tsx Adds UI tests for the new memory conflict dialog.
tests/unit/local-store.test.ts Adds tests for local-store constants/embedding helpers.
tests/unit/local-store-migrations.test.ts Adds integration-like tests for SQLite schema/migrations.
tests/unit/event-log-service.test.ts Expands event-log sanitization tests (JWT, connection strings, filePath sanitization, etc.).
tests/unit/code-indexer.test.ts Adds tests for code-indexer utility functions.
tests/unit/code-indexer-errors.test.ts Adds error-path tests for indexing/rebuild/retrieve edge cases.
tests/unit/code-indexer-class.test.ts Adds broader CodeIndexer tests including store integration.
tests/unit/code-indexer-chunking.test.ts Adds chunking tests (tree-sitter and fallback chunking).
tests/unit/app-coverage-deep.test.tsx Adjusts deep-coverage tests in App to match updated handler types/usages.
tests/mocks/node-sqlite.ts Adds a node:sqlite mock so Vitest can bundle tests.
packages/workbench/src/MemoryReviewDialog.tsx Adds a dialog component to review memory conflicts in the UI.
packages/workbench/src/App.tsx Wires memory-conflict events into the UI and renders MemoryReviewDialog.
packages/shared/src/ipc.ts Adds Phase 9 IPC channels, types, and guards (memory + code-indexer).
packages/shared/src/index.ts Re-exports Phase 9 types and guards from shared.
packages/services/src/tool-router.ts Adds ToolRouter support for propose/apply memory-change tools and event logging integration.
packages/services/src/phase9-exports.ts Adds services-level re-exports for Phase 9 APIs.
packages/services/src/permission-broker.ts Adds tool classifications for proposeMemoryChange and applyMemoryChange.
packages/services/src/index.ts Exposes Phase 9 creation helpers from services.
packages/services/src/event-log-service.ts Adds message/filePath sanitization plus expanded diff sanitization patterns.
packages/memory-service/tsconfig.json Adds TS config for the memory-service package.
packages/memory-service/src/redaction.ts Implements secret redaction used by memory/local-store flows.
packages/memory-service/src/memory-service.ts Implements MemoryService (read/list/propose/apply edits, conflict detection).
packages/memory-service/src/local-store.ts Implements SQLite local store with migrations, embeddings, chunk storage/search.
packages/memory-service/src/index.ts Exports memory-service/local-store/redaction public surface.
packages/memory-service/package.json Declares the @agentdeck/memory-service package.
packages/code-indexer/tsconfig.json Adds TS config for the code-indexer package.
packages/code-indexer/src/utils.ts Implements language detection and deterministic hashing helpers.
packages/code-indexer/src/index.ts Exports the code-indexer public surface.
packages/code-indexer/src/code-indexer.ts Implements CodeIndexer (index/rebuild/retrieve + optional store/memory-service integration).
packages/code-indexer/src/chunking.ts Implements tree-sitter chunking and line-based fallback chunking.
packages/code-indexer/package.json Declares the @agentdeck/code-indexer package.
packages/agent-runtime/src/session-broker.ts Adds event-message sanitization before runtime events are stored/emitted.
package.json Adds new scripts and dependencies needed for Phase 9.
package-lock.json Locks new dependencies and local package links.
eslint.config.js Ignores the migrations Vitest config in linting.
electron.vite.config.ts Adds alias entries for the new packages in Electron Vite config.
CHANGELOG.md Documents Phase 9 additions under Unreleased.
apps/desktop/src/preload/index.ts Adds preload API methods for memory apply/conflict handling (partial Phase 9).
apps/desktop/src/main/index.ts Registers Phase 9 IPC handlers and initializes local-store/memory/indexer services.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +131 to +134
// No conflict — apply directly
const newContent = this.extractNewContent(proposal.patch);
const entry = await this.write(proposal.scope, proposal.filePath, newContent);
return { status: "ok", entry };
Comment on lines +179 to +183
async write(scope: MemoryScope, filePath: string, content: string): Promise<MemoryEntry> {
await mkdir(dirname(filePath), { recursive: true });
await writeFile(filePath, redactSecrets(content), 'utf8');
return this.describeEntry(scope, filePath, content);
}
Comment on lines +49 to +53
async ensureScope(scope: MemoryScope): Promise<string> {
const scopeDir = join(this.baseDir, 'memories', scope);
await mkdir(scopeDir, { recursive: true });
return scopeDir;
}
Comment on lines +71 to +79
async list(scope?: MemoryScope): Promise<ListMemoryFilesResult> {
try {
const root = scope === undefined ? join(this.baseDir, 'memories') : join(this.baseDir, 'memories', scope);
const entries = await collectMarkdownEntries(root, scope);
return { status: 'ok', entries };
} catch (error) {
return { status: 'error', code: 'UNKNOWN', message: String(error) };
}
}
Comment on lines +18 to +21
}

return sanitized.trimStart();
}
Comment on lines +1465 to +1473
ipcMain.handle(IPC_CHANNELS.rebuildCodeIndex, async (_event, roots: unknown) => {
try {
const rootArray = Array.isArray(roots) ? roots as string[] : [];
const result = await codeIndexer.rebuildIndex(rootArray);
return { status: 'ok', chunks: result.chunks, stats: result.stats };
} catch (error) {
return { status: 'error', code: 'UNKNOWN', message: String(error) };
}
});
Comment on lines +1393 to +1402
ipcMain.handle(IPC_CHANNELS.proposeMemoryChange, async (_event, edit: unknown) => {
try {
if (!isRecord(edit) || typeof edit.filePath !== 'string' || typeof edit.text !== 'string') {
return { status: 'error', code: 'INVALID_INPUT', message: 'Invalid edit payload.' };
}
return await memoryService.proposeEdit({
scope: edit.scope as MemoryScope,
filePath: edit.filePath,
text: edit.text
});
Comment on lines +1393 to +1402
ipcMain.handle(IPC_CHANNELS.proposeMemoryChange, async (_event, edit: unknown) => {
try {
if (!isRecord(edit) || typeof edit.filePath !== 'string' || typeof edit.text !== 'string') {
return { status: 'error', code: 'INVALID_INPUT', message: 'Invalid edit payload.' };
}
return await memoryService.proposeEdit({
scope: edit.scope as MemoryScope,
filePath: edit.filePath,
text: edit.text
});
Comment on lines +436 to +440
listChunks(filters?: SearchEmbeddingFilters): readonly IndexChunk[] {
const { where, params } = buildChunkFilters(filters);
const rows = this.db.prepare(`select * from index_chunks ${where} order by file_path, start_line, id`).all(...(params as unknown as SQLInputValue[])) as ChunkRow[];
return rows.map(rowToIndexChunk);
}
Comment on lines +568 to +571
if (filters?.folders !== undefined && filters.folders.length > 0) {
clauses.push(`(c.metadata_json is null or c.metadata_json like ?)`);
params.push(...filters.folders.map(folder => `%${escapeLike(folder)}%`));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants