Skip to content

Commit 3b29420

Browse files
committed
feat(cli): support project-configured memory db path
1 parent ea84756 commit 3b29420

File tree

11 files changed

+341
-46
lines changed

11 files changed

+341
-46
lines changed

docs/ai/implementation/feature-memory-db-path-config.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,29 @@ description: Implementation notes for project-configurable memory database paths
1212

1313
## Code Structure
1414
- Config shape and parsing live in the CLI package.
15-
- Effective database path selection must be shared by all memory entry points.
15+
- Effective database path selection is resolved in the CLI and passed explicitly into the memory package.
1616

1717
## Implementation Notes
1818
### Core Features
1919
- Add typed support for `memory.path` in project config.
2020
- Resolve relative configured paths from the project root.
21-
- Pass the resolved path into every memory operation entry point.
21+
- Pass the resolved path into `ai-devkit memory store`, `search`, and `update`.
2222

2323
### Patterns & Best Practices
2424
- Keep `DEFAULT_DB_PATH` as the fallback constant.
25-
- Avoid duplicating path-resolution logic across command handlers and server code.
25+
- Avoid duplicating path-resolution logic across CLI command handlers.
2626

2727
## Integration Points
2828
- `.ai-devkit.json`
2929
- `ConfigManager`
3030
- memory CLI command adapters
31-
- memory MCP server
3231

3332
## Error Handling
3433
- Invalid or absent `memory.path` should not break memory commands; fall back to the default path.
3534

3635
## Performance Considerations
37-
- Path resolution should happen once per command/tool invocation before opening the database.
36+
- Path resolution should happen once per CLI command invocation before opening the database.
3837

3938
## Security Notes
4039
- Treat `memory.path` as a filesystem path only; no shell execution or interpolation.
40+
- Standalone `@ai-devkit/memory` server behavior remains unchanged in this feature.

docs/ai/planning/feature-memory-db-path-config.md

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,31 @@ description: Task breakdown for project-configurable memory database paths
77
# Project Planning & Task Breakdown
88

99
## Milestones
10-
- [ ] Milestone 1: Project config schema and parsing support `memory.path`
11-
- [ ] Milestone 2: Memory command and server flows consume resolved database path
12-
- [ ] Milestone 3: Tests cover configured path and fallback behavior
10+
- [x] Milestone 1: Project config schema and parsing support `memory.path`
11+
- [x] Milestone 2: CLI memory command flows consume resolved database path
12+
- [x] Milestone 3: Tests cover configured path and fallback behavior
1313

1414
## Task Breakdown
1515

1616
### Phase 1: Config Support
17-
- [ ] Task 1.1: Extend `packages/cli/src/types.ts` to type optional `memory.path`
18-
- [ ] Task 1.2: Add `ConfigManager.getMemoryDbPath()` in `packages/cli/src/lib/Config.ts`
19-
- [ ] Task 1.3: Add unit tests for project config parsing, including missing, invalid, absolute, and relative path cases
17+
- [x] Task 1.1: Extend `packages/cli/src/types.ts` to type optional `memory.path`
18+
- [x] Task 1.2: Add `ConfigManager.getMemoryDbPath()` in `packages/cli/src/lib/Config.ts`
19+
- [x] Task 1.3: Add unit tests for project config parsing, including missing, invalid, absolute, and relative path cases
2020

2121
### Phase 2: Memory Path Wiring
22-
- [ ] Task 2.1: Introduce a shared path-resolution flow that combines project config override with `DEFAULT_DB_PATH`
23-
- [ ] Task 2.2: Update `packages/memory/src/api.ts` and related entry points so store/search/update use the resolved path consistently
24-
- [ ] Task 2.3: Update the memory MCP server startup/tool handling to honor the same project-configured database path
22+
- [x] Task 2.1: Introduce a CLI-owned path-resolution flow that combines project config override with `DEFAULT_DB_PATH`
23+
- [x] Task 2.2: Update `packages/memory/src/api.ts` and CLI entry points so store/search/update use the resolved path consistently
24+
- [x] Task 2.3: Removed from scope during Phase 2 review. Standalone `@ai-devkit/memory` MCP server remains unchanged for this feature.
2525

2626
### Phase 3: Verification
27-
- [ ] Task 3.1: Add or update CLI tests covering memory commands with configured `memory.path`
28-
- [ ] Task 3.2: Add or update memory package tests covering fallback to `~/.ai-devkit/memory.db`
29-
- [ ] Task 3.3: Run targeted verification for docs lint and relevant automated tests
27+
- [x] Task 3.1: Add or update CLI tests covering memory commands with configured `memory.path`
28+
- [x] Task 3.2: Add or update memory package tests covering explicit `dbPath` wiring and configured-path persistence
29+
- [x] Task 3.3: Run targeted verification for docs lint and relevant automated tests
3030

3131
## Dependencies
3232
- Task 1.1 precedes Task 1.2 because config typing should match the new parser surface.
3333
- Task 1.2 precedes Task 2.1 and Task 2.2 because runtime resolution depends on the config accessor.
34-
- Task 2.1 should land before Task 2.2 and Task 2.3 so all memory entry points share one resolution rule.
34+
- Task 2.1 should land before Task 2.2 so CLI and memory API use one resolution rule.
3535
- Verification tasks depend on both config support and runtime wiring being complete.
3636

3737
## Timeline & Estimates
@@ -40,8 +40,8 @@ description: Task breakdown for project-configurable memory database paths
4040
- Phase 3: Small to medium effort depending on current test coverage for memory command setup
4141

4242
## Risks & Mitigation
43-
- Risk: CLI commands honor config but MCP server still uses the default database.
44-
Mitigation: Define one shared resolver and cover both entry points in tests.
43+
- Risk: CLI commands honor config but the standalone MCP server still uses the default database.
44+
Mitigation: This is intentional and documented as out of scope for the feature.
4545
- Risk: Relative paths resolve from process cwd instead of project root.
4646
Mitigation: Resolve from the config file directory and add explicit unit tests.
4747
- Risk: Invalid config values break existing users.
@@ -51,3 +51,8 @@ description: Task breakdown for project-configurable memory database paths
5151
- Existing config loading utilities in `packages/cli/src/lib/Config.ts`
5252
- Existing database connection behavior in `packages/memory/src/database/connection.ts`
5353
- Existing memory command tests in `packages/cli/src/__tests__/commands/memory.test.ts`
54+
55+
## Progress Summary
56+
- Completed implementation for project-configured `memory.path` in `ai-devkit` CLI flows.
57+
- Preserved standalone `@ai-devkit/memory` behavior as approved during requirements review.
58+
- Verified with targeted CLI tests, memory integration tests, feature doc lint, package builds, and a real built-CLI store/search run against a temporary project config.

docs/ai/testing/feature-memory-db-path-config.md

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,53 @@ description: Testing plan for project-configurable memory database paths
99
## Test Coverage Goals
1010
- Cover 100% of new and changed code related to config parsing and path resolution.
1111
- Verify both configured-path and default-path flows.
12+
- Keep standalone `@ai-devkit/memory` server behavior unchanged.
1213

1314
## Unit Tests
1415
### Config parsing
15-
- [ ] Reads `memory.path` when it is a non-empty string
16-
- [ ] Ignores missing, blank, and non-string `memory.path`
17-
- [ ] Resolves relative `memory.path` from the project config directory
16+
- [x] Reads `memory.path` when it is a non-empty string
17+
- [x] Ignores missing, blank, and non-string `memory.path`
18+
- [x] Resolves relative `memory.path` from the project config directory
19+
Implemented in `packages/cli/src/__tests__/lib/Config.test.ts`
1820

1921
### Memory command resolution
20-
- [ ] `memory store` uses configured path when project config exists
21-
- [ ] `memory search` uses configured path when project config exists
22-
- [ ] `memory update` uses configured path when project config exists
23-
- [ ] Commands fall back to `~/.ai-devkit/memory.db` when no project override exists
22+
- [x] `memory store` uses configured path when project config exists
23+
- [x] `memory search` uses configured path when project config exists
24+
- [x] `memory update` uses configured path when project config exists
25+
- [x] Commands fall back to `~/.ai-devkit/memory.db` when no project override exists
26+
Verified by default `dbPath: undefined` expectations and configured-path expectations in `packages/cli/src/__tests__/commands/memory.test.ts`
2427

2528
## Integration Tests
26-
- [ ] Memory MCP server tool calls use the same resolved path as CLI commands
27-
- [ ] Schema initialization still succeeds when the configured path points to a new file
29+
- [x] Schema initialization succeeds when the configured path points to a new file
30+
- [x] Memory API store/search/update calls use an explicit configured `dbPath`
31+
Implemented in `packages/memory/tests/integration/api.test.ts`
32+
- [x] Standalone memory MCP server remains out of scope and unchanged
33+
Covered by design/requirements scope, not by new behavior tests
2834

2935
## End-to-End Tests
30-
- [ ] Manual smoke test with a checked-in `.ai-devkit.json` using a repo-local memory DB
36+
- [x] Automated CLI e2e test uses a temp-project `.ai-devkit.json` with repo-local `memory.path`
37+
Implemented in `e2e/cli.e2e.ts`
38+
- [x] Manual smoke test with a checked-in `.ai-devkit.json` using a repo-local memory DB
39+
Verified via built CLI store/search run in a temporary project directory with `.ai-devkit.json` pointing to `.ai-devkit/project-memory.db`
3140

3241
## Test Data
3342
- Temporary project directories with generated `.ai-devkit.json`
3443
- Temporary database file paths for isolated runs
3544

3645
## Test Reporting & Coverage
37-
- Run targeted CLI and memory package tests plus docs lint for this feature
46+
- Ran `npm test -- --runInBand Config.test.ts memory.test.ts` in `packages/cli`
47+
- Ran `npm test -- --runInBand tests/integration/api.test.ts` in `packages/memory`
48+
- Ran `npm run test:e2e -- cli.e2e.ts`
49+
- Ran `npx ai-devkit@latest lint --feature memory-db-path-config`
50+
- Did not run a full coverage report command in this phase; targeted suites were used for feature verification
3851

3952
## Manual Testing
40-
- Confirm a repo-local configured DB file is created on first write
41-
- Confirm commands revert to the home-directory database when config is removed
53+
- Confirmed a repo-local configured DB file is created on first write
54+
- Confirmed built CLI search reads back from the configured repo-local DB
55+
- Default fallback is covered by unit tests rather than a separate manual run
4256

4357
## Performance Testing
4458
- No dedicated performance testing required beyond regression confidence
4559

4660
## Bug Tracking
47-
- Watch for regressions where one memory entry point still opens `~/.ai-devkit/memory.db`
61+
- Watch for regressions where one CLI memory subcommand omits `dbPath` and reverts to `~/.ai-devkit/memory.db`

e2e/cli.e2e.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,22 @@ describe('lint command', () => {
148148
describe('memory commands', () => {
149149
let projectDir: string;
150150
let uid: string;
151+
let projectMemoryDbPath: string;
151152

152153
beforeEach(() => {
153154
projectDir = createTempProject();
154155
uid = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
156+
projectMemoryDbPath = join(projectDir, '.ai-devkit', 'memory.db');
157+
writeConfigFile(projectDir, {
158+
version: '1.0.0',
159+
environments: [],
160+
phases: [],
161+
memory: {
162+
path: '.ai-devkit/memory.db'
163+
},
164+
createdAt: new Date().toISOString(),
165+
updatedAt: new Date().toISOString()
166+
});
155167
});
156168

157169
afterEach(() => {
@@ -168,6 +180,7 @@ describe('memory commands', () => {
168180
const stored = JSON.parse(storeResult.stdout.trim());
169181
expect(stored.success).toBe(true);
170182
expect(stored.id).toBeDefined();
183+
expect(existsSync(projectMemoryDbPath)).toBe(true);
171184

172185
const searchResult = run(`memory search -q "${title}"`, { cwd: projectDir });
173186
expect(searchResult.exitCode).toBe(0);

packages/cli/src/__tests__/commands/memory.test.ts

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@ import { registerMemoryCommand } from '../../commands/memory';
44
import { memorySearchCommand, memoryStoreCommand, memoryUpdateCommand } from '@ai-devkit/memory';
55
import { ui } from '../../util/terminal-ui';
66

7+
const mockGetMemoryDbPath = jest.fn<() => Promise<string | undefined>>();
8+
const mockConfigManager = {
9+
getMemoryDbPath: mockGetMemoryDbPath
10+
};
11+
712
jest.mock('@ai-devkit/memory', () => ({
813
memoryStoreCommand: jest.fn(),
914
memorySearchCommand: jest.fn(),
1015
memoryUpdateCommand: jest.fn()
16+
}), { virtual: true });
17+
18+
jest.mock('../../lib/Config', () => ({
19+
ConfigManager: jest.fn(() => mockConfigManager)
1120
}));
1221

1322
jest.mock('../../util/terminal-ui', () => ({
@@ -27,6 +36,7 @@ describe('memory command', () => {
2736

2837
beforeEach(() => {
2938
jest.clearAllMocks();
39+
mockGetMemoryDbPath.mockResolvedValue(undefined);
3040
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
3141
});
3242

@@ -51,10 +61,42 @@ describe('memory command', () => {
5161
'This is a valid content body long enough to satisfy constraints.'
5262
]);
5363

54-
expect(mockedMemoryStoreCommand).toHaveBeenCalled();
64+
expect(mockedMemoryStoreCommand).toHaveBeenCalledWith({
65+
title: 'A valid title 123',
66+
content: 'This is a valid content body long enough to satisfy constraints.',
67+
tags: undefined,
68+
scope: 'global',
69+
dbPath: undefined
70+
});
5571
expect(consoleLogSpy).toHaveBeenCalledWith(JSON.stringify(result, null, 2));
5672
});
5773

74+
it('passes resolved project dbPath to memory store', async () => {
75+
mockGetMemoryDbPath.mockResolvedValue('/repo/.ai-devkit/project-memory.db');
76+
mockedMemoryStoreCommand.mockReturnValue({
77+
success: true,
78+
id: 'mem-1',
79+
message: 'stored'
80+
});
81+
82+
const program = new Command();
83+
registerMemoryCommand(program);
84+
await program.parseAsync([
85+
'node',
86+
'test',
87+
'memory',
88+
'store',
89+
'--title',
90+
'A valid title 123',
91+
'--content',
92+
'This is a valid content body long enough to satisfy constraints.'
93+
]);
94+
95+
expect(mockedMemoryStoreCommand).toHaveBeenCalledWith(expect.objectContaining({
96+
dbPath: '/repo/.ai-devkit/project-memory.db'
97+
}));
98+
});
99+
58100
it('handles store errors by showing error and exiting', async () => {
59101
mockedMemoryStoreCommand.mockImplementation(() => {
60102
throw new Error('store failed');
@@ -104,7 +146,14 @@ describe('memory command', () => {
104146
'Updated title for testing',
105147
]);
106148

107-
expect(mockedMemoryUpdateCommand).toHaveBeenCalled();
149+
expect(mockedMemoryUpdateCommand).toHaveBeenCalledWith({
150+
id: 'mem-1',
151+
title: 'Updated title for testing',
152+
content: undefined,
153+
tags: undefined,
154+
scope: undefined,
155+
dbPath: undefined
156+
});
108157
expect(consoleLogSpy).toHaveBeenCalledWith(JSON.stringify(result, null, 2));
109158
});
110159

@@ -161,7 +210,8 @@ describe('memory command', () => {
161210
query: 'dto',
162211
tags: undefined,
163212
scope: undefined,
164-
limit: 5
213+
limit: 5,
214+
dbPath: undefined
165215
});
166216
expect(consoleLogSpy).toHaveBeenCalledWith(JSON.stringify(result, null, 2));
167217
expect(mockedUi.table).not.toHaveBeenCalled();
@@ -191,7 +241,8 @@ describe('memory command', () => {
191241
query: 'memory',
192242
tags: undefined,
193243
scope: undefined,
194-
limit: 3
244+
limit: 3,
245+
dbPath: undefined
195246
});
196247
expect(mockedUi.table).toHaveBeenCalledWith({
197248
headers: ['id', 'title', 'scope'],
@@ -200,6 +251,36 @@ describe('memory command', () => {
200251
expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('"results"'));
201252
});
202253

254+
it('passes resolved project dbPath to memory search and update', async () => {
255+
mockGetMemoryDbPath.mockResolvedValue('/repo/.ai-devkit/project-memory.db');
256+
mockedMemorySearchCommand.mockReturnValue({
257+
results: [],
258+
totalMatches: 0,
259+
query: 'dto'
260+
});
261+
mockedMemoryUpdateCommand.mockReturnValue({
262+
success: true,
263+
id: 'mem-1',
264+
message: 'Knowledge updated successfully'
265+
});
266+
267+
let program = new Command();
268+
registerMemoryCommand(program);
269+
await program.parseAsync(['node', 'test', 'memory', 'search', '--query', 'dto']);
270+
271+
expect(mockedMemorySearchCommand).toHaveBeenCalledWith(expect.objectContaining({
272+
dbPath: '/repo/.ai-devkit/project-memory.db'
273+
}));
274+
275+
program = new Command();
276+
registerMemoryCommand(program);
277+
await program.parseAsync(['node', 'test', 'memory', 'update', '--id', 'mem-1', '--title', 'Updated title for testing']);
278+
279+
expect(mockedMemoryUpdateCommand).toHaveBeenCalledWith(expect.objectContaining({
280+
dbPath: '/repo/.ai-devkit/project-memory.db'
281+
}));
282+
});
283+
203284
it('shows a warning when --table has no matching results', async () => {
204285
mockedMemorySearchCommand.mockReturnValue({
205286
results: [],

0 commit comments

Comments
 (0)