From 045abd630c7be58858f568187beb446eedbe06fb Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Thu, 12 Feb 2026 21:25:59 +0000 Subject: [PATCH 1/2] test: integration tests for hook entry points (GIT-58) Add end-to-end integration tests exercising hooks as child processes against real temp git repos: - session-start: stdout with memories, stderr summary, empty store graceful - session-stop: capture via liberate pipeline, summary output - prompt-submit: context surfacing when enabled, silent when disabled - init-hooks: file creation, correct schemas, remove cleanup, preserves other tools' hooks Shared helpers use tsx binary to support cwd in temp directories. Co-Authored-By: Claude Opus 4.6 --- tests/integration/hooks/helpers.ts | 103 ++++++++++ .../integration/hooks/hook-init-hooks.test.ts | 186 ++++++++++++++++++ .../hooks/hook-prompt-submit.test.ts | 119 +++++++++++ .../hooks/hook-session-start.test.ts | 109 ++++++++++ .../hooks/hook-session-stop.test.ts | 75 +++++++ 5 files changed, 592 insertions(+) create mode 100644 tests/integration/hooks/helpers.ts create mode 100644 tests/integration/hooks/hook-init-hooks.test.ts create mode 100644 tests/integration/hooks/hook-prompt-submit.test.ts create mode 100644 tests/integration/hooks/hook-session-start.test.ts create mode 100644 tests/integration/hooks/hook-session-stop.test.ts diff --git a/tests/integration/hooks/helpers.ts b/tests/integration/hooks/helpers.ts new file mode 100644 index 00000000..b3947b9e --- /dev/null +++ b/tests/integration/hooks/helpers.ts @@ -0,0 +1,103 @@ +/** + * Shared helpers for hook integration tests. + * + * Provides child-process wrappers to invoke `git-mem hook ` + * and `git-mem init-hooks` against real temp git repos. + */ + +import { spawnSync } from 'child_process'; +import { execFileSync } from 'child_process'; +import { mkdtempSync, writeFileSync, rmSync } from 'fs'; +import { join, resolve } from 'path'; +import { tmpdir } from 'os'; + +const PROJECT_ROOT = resolve(__dirname, '../../..'); +const CLI_PATH = resolve(PROJECT_ROOT, 'src/cli.ts'); + +// Use tsx binary from project node_modules — works even when cwd is a temp dir +const TSX_BIN = resolve(PROJECT_ROOT, 'node_modules/.bin/tsx'); + +export interface IRunResult { + stdout: string; + stderr: string; + status: number; +} + +/** Run `git-mem hook ` with JSON piped to stdin. */ +export function runHook(eventName: string, input: Record): IRunResult { + const result = spawnSync(TSX_BIN, [CLI_PATH, 'hook', eventName], { + input: JSON.stringify(input), + encoding: 'utf8', + timeout: 15_000, + }); + + return { + stdout: result.stdout ?? '', + stderr: result.stderr ?? '', + status: result.status ?? 1, + }; +} + +/** Run `git-mem ` as a child process. */ +export function runCli(args: string[], opts?: { cwd?: string; input?: string }): IRunResult { + const result = spawnSync(TSX_BIN, [CLI_PATH, ...args], { + input: opts?.input, + cwd: opts?.cwd, + encoding: 'utf8', + timeout: 15_000, + }); + + return { + stdout: result.stdout ?? '', + stderr: result.stderr ?? '', + status: result.status ?? 1, + }; +} + +function git(args: string[], cwd: string): string { + return execFileSync('git', args, { encoding: 'utf8', cwd }).trim(); +} + +/** Create a temp git repo with an initial commit. Returns dir and HEAD sha. */ +export function createTestRepo(prefix = 'git-mem-hook-integ-'): { dir: string; sha: string } { + const dir = mkdtempSync(join(tmpdir(), prefix)); + + git(['init'], dir); + git(['config', 'user.email', 'test@test.com'], dir); + git(['config', 'user.name', 'Test User'], dir); + + writeFileSync(join(dir, 'file.txt'), 'initial content'); + git(['add', '.'], dir); + git(['commit', '-m', 'feat: initial commit'], dir); + const sha = git(['rev-parse', 'HEAD'], dir); + + return { dir, sha }; +} + +/** Add a commit to an existing test repo. Returns the new sha. */ +export function addCommit(dir: string, filename: string, content: string, message: string): string { + writeFileSync(join(dir, filename), content); + git(['add', '.'], dir); + git(['commit', '-m', message], dir); + return git(['rev-parse', 'HEAD'], dir); +} + +/** Write .git-mem.json into a directory with optional overrides. */ +export function writeGitMemConfig(dir: string, overrides?: Record): void { + const defaults = { + hooks: { + enabled: true, + sessionStart: { enabled: true, memoryLimit: 20 }, + sessionStop: { enabled: true, autoLiberate: true, threshold: 3 }, + promptSubmit: { enabled: false, recordPrompts: false, surfaceContext: true }, + }, + }; + + const config = overrides ? { ...defaults, hooks: { ...defaults.hooks, ...overrides } } : defaults; + writeFileSync(join(dir, '.git-mem.json'), JSON.stringify(config, null, 2) + '\n'); +} + +/** Remove a temp directory. */ +export function cleanupRepo(dir: string): void { + rmSync(dir, { recursive: true, force: true }); +} diff --git a/tests/integration/hooks/hook-init-hooks.test.ts b/tests/integration/hooks/hook-init-hooks.test.ts new file mode 100644 index 00000000..ec1f73b3 --- /dev/null +++ b/tests/integration/hooks/hook-init-hooks.test.ts @@ -0,0 +1,186 @@ +/** + * Integration test: init-hooks command + * + * Exercises `git-mem init-hooks --yes` and `git-mem init-hooks --remove` + * end-to-end in temp directories. Verifies file creation, schema + * correctness, and cleanup. + */ + +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { mkdtempSync, rmSync } from 'fs'; +import { tmpdir } from 'os'; +import { runCli } from './helpers'; + +describe('Integration: init-hooks', () => { + let workDir: string; + + before(() => { + workDir = mkdtempSync(join(tmpdir(), 'git-mem-init-hooks-')); + }); + + after(() => { + rmSync(workDir, { recursive: true, force: true }); + }); + + describe('install mode (--yes)', () => { + it('should create .claude/settings.json and .git-mem.json', () => { + const result = runCli(['init-hooks', '--yes'], { cwd: workDir }); + + assert.equal(result.status, 0); + assert.ok(existsSync(join(workDir, '.claude', 'settings.json')), '.claude/settings.json should exist'); + assert.ok(existsSync(join(workDir, '.git-mem.json')), '.git-mem.json should exist'); + }); + + it('should write correct hook commands in settings.json', () => { + const settingsPath = join(workDir, '.claude', 'settings.json'); + const settings = JSON.parse(readFileSync(settingsPath, 'utf8')); + + assert.ok(settings.hooks, 'settings should have hooks key'); + assert.ok(settings.hooks.SessionStart, 'should have SessionStart'); + assert.ok(settings.hooks.SessionStop, 'should have SessionStop'); + assert.ok(settings.hooks.UserPromptSubmit, 'should have UserPromptSubmit'); + + // Verify hook command format + const startHook = settings.hooks.SessionStart[0]; + assert.equal(startHook.matcher, ''); + assert.equal(startHook.hooks[0].type, 'command'); + assert.equal(startHook.hooks[0].command, 'git-mem hook session-start'); + + const stopHook = settings.hooks.SessionStop[0]; + assert.equal(stopHook.hooks[0].command, 'git-mem hook session-stop'); + + const promptHook = settings.hooks.UserPromptSubmit[0]; + assert.equal(promptHook.hooks[0].command, 'git-mem hook prompt-submit'); + }); + + it('should write correct defaults in .git-mem.json', () => { + const configPath = join(workDir, '.git-mem.json'); + const config = JSON.parse(readFileSync(configPath, 'utf8')); + + assert.ok(config.hooks, 'should have hooks key'); + assert.equal(config.hooks.enabled, true); + + assert.equal(config.hooks.sessionStart.enabled, true); + assert.equal(config.hooks.sessionStart.memoryLimit, 20); + + assert.equal(config.hooks.sessionStop.enabled, true); + assert.equal(config.hooks.sessionStop.autoLiberate, true); + assert.equal(config.hooks.sessionStop.threshold, 3); + + assert.equal(config.hooks.promptSubmit.enabled, false); + assert.equal(config.hooks.promptSubmit.recordPrompts, false); + assert.equal(config.hooks.promptSubmit.surfaceContext, true); + }); + }); + + describe('remove mode (--remove)', () => { + let removeDir: string; + + before(() => { + removeDir = mkdtempSync(join(tmpdir(), 'git-mem-init-hooks-remove-')); + + // First install hooks + runCli(['init-hooks', '--yes'], { cwd: removeDir }); + // Verify they exist before testing removal + assert.ok(existsSync(join(removeDir, '.claude', 'settings.json'))); + assert.ok(existsSync(join(removeDir, '.git-mem.json'))); + }); + + after(() => { + rmSync(removeDir, { recursive: true, force: true }); + }); + + it('should remove both config files', () => { + const result = runCli(['init-hooks', '--remove'], { cwd: removeDir }); + + assert.equal(result.status, 0); + assert.ok(!existsSync(join(removeDir, '.git-mem.json')), '.git-mem.json should be removed'); + // settings.json is deleted entirely when only git-mem hooks were present + assert.ok(!existsSync(join(removeDir, '.claude', 'settings.json')), 'settings.json should be removed'); + }); + }); + + describe('preserves other tools hooks', () => { + let preserveDir: string; + + before(() => { + preserveDir = mkdtempSync(join(tmpdir(), 'git-mem-init-hooks-preserve-')); + + // Create existing settings.json with another tool's hooks + const claudeDir = join(preserveDir, '.claude'); + mkdirSync(claudeDir, { recursive: true }); + + const existingSettings = { + hooks: { + SessionStart: [ + { + matcher: '', + hooks: [{ type: 'command', command: 'other-tool start' }], + }, + ], + PreToolUse: [ + { + matcher: 'Bash', + hooks: [{ type: 'command', command: 'other-tool guard' }], + }, + ], + }, + }; + writeFileSync( + join(claudeDir, 'settings.json'), + JSON.stringify(existingSettings, null, 2) + '\n', + ); + }); + + after(() => { + rmSync(preserveDir, { recursive: true, force: true }); + }); + + it('should preserve other tools hooks on install', () => { + const result = runCli(['init-hooks', '--yes'], { cwd: preserveDir }); + assert.equal(result.status, 0); + + const settings = JSON.parse(readFileSync(join(preserveDir, '.claude', 'settings.json'), 'utf8')); + + // SessionStart should have both: other-tool entry + git-mem entry + const sessionStartEntries = settings.hooks.SessionStart; + assert.equal(sessionStartEntries.length, 2); + + const otherToolEntry = sessionStartEntries.find( + (e: Record) => Array.isArray(e.hooks) && (e.hooks as Array>).some((h) => h.command === 'other-tool start'), + ); + assert.ok(otherToolEntry, 'other-tool SessionStart entry should be preserved'); + + const gitMemEntry = sessionStartEntries.find( + (e: Record) => Array.isArray(e.hooks) && (e.hooks as Array>).some((h) => h.command === 'git-mem hook session-start'), + ); + assert.ok(gitMemEntry, 'git-mem SessionStart entry should be added'); + + // PreToolUse should be untouched + assert.ok(settings.hooks.PreToolUse, 'PreToolUse should be preserved'); + assert.equal(settings.hooks.PreToolUse[0].hooks[0].command, 'other-tool guard'); + }); + + it('should preserve other tools hooks on remove', () => { + const result = runCli(['init-hooks', '--remove'], { cwd: preserveDir }); + assert.equal(result.status, 0); + + const settings = JSON.parse(readFileSync(join(preserveDir, '.claude', 'settings.json'), 'utf8')); + + // Other tool's SessionStart entry should survive + assert.ok(settings.hooks.SessionStart, 'SessionStart should still exist'); + assert.equal(settings.hooks.SessionStart.length, 1); + assert.equal(settings.hooks.SessionStart[0].hooks[0].command, 'other-tool start'); + + // PreToolUse untouched + assert.ok(settings.hooks.PreToolUse); + + // git-mem event types with no remaining entries should be removed + assert.ok(!settings.hooks.SessionStop, 'SessionStop should be removed (was only git-mem)'); + assert.ok(!settings.hooks.UserPromptSubmit, 'UserPromptSubmit should be removed (was only git-mem)'); + }); + }); +}); diff --git a/tests/integration/hooks/hook-prompt-submit.test.ts b/tests/integration/hooks/hook-prompt-submit.test.ts new file mode 100644 index 00000000..8a9fcdf3 --- /dev/null +++ b/tests/integration/hooks/hook-prompt-submit.test.ts @@ -0,0 +1,119 @@ +/** + * Integration test: prompt-submit hook + * + * Exercises `git-mem hook prompt-submit` end-to-end against a real + * git repo. Verifies context surfacing when enabled and silent + * exit when disabled (the default). + */ + +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { writeFileSync } from 'fs'; +import { join } from 'path'; +import { MemoryService } from '../../../src/application/services/MemoryService'; +import { MemoryRepository } from '../../../src/infrastructure/repositories/MemoryRepository'; +import { NotesService } from '../../../src/infrastructure/services/NotesService'; +import { + runHook, + createTestRepo, + cleanupRepo, +} from './helpers'; + +describe('Integration: hook prompt-submit', () => { + describe('when enabled with stored memories', () => { + let repoDir: string; + let commitSha: string; + + before(() => { + const repo = createTestRepo('git-mem-hook-prompt-'); + repoDir = repo.dir; + commitSha = repo.sha; + + // Store a memory + const notesService = new NotesService(); + const memoryRepo = new MemoryRepository(notesService); + const memoryService = new MemoryService(memoryRepo); + + memoryService.remember('Always validate user input at API boundary', { + sha: commitSha, + type: 'convention', + tags: 'security', + cwd: repoDir, + }); + + // Write config with promptSubmit ENABLED + const config = { + hooks: { + enabled: true, + sessionStart: { enabled: true, memoryLimit: 20 }, + sessionStop: { enabled: true, autoLiberate: true, threshold: 3 }, + promptSubmit: { enabled: true, recordPrompts: false, surfaceContext: true }, + }, + }; + writeFileSync(join(repoDir, '.git-mem.json'), JSON.stringify(config, null, 2) + '\n'); + }); + + after(() => { + cleanupRepo(repoDir); + }); + + it('should return context in stdout', () => { + const result = runHook('prompt-submit', { + session_id: 'test-prompt-1', + prompt: 'How should I validate user input?', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + assert.ok( + result.stdout.includes('validate') || result.stdout.includes('input') || result.stdout.includes('security'), + `stdout should contain relevant memory context, got: ${result.stdout}`, + ); + }); + + it('should exit with code 0', () => { + const result = runHook('prompt-submit', { + session_id: 'test-prompt-2', + prompt: 'test prompt', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + }); + }); + + describe('when disabled (default)', () => { + let repoDir: string; + + before(() => { + const repo = createTestRepo('git-mem-hook-prompt-disabled-'); + repoDir = repo.dir; + + // Default config has promptSubmit.enabled: false + const config = { + hooks: { + enabled: true, + sessionStart: { enabled: true, memoryLimit: 20 }, + sessionStop: { enabled: true, autoLiberate: true, threshold: 3 }, + promptSubmit: { enabled: false, recordPrompts: false, surfaceContext: true }, + }, + }; + writeFileSync(join(repoDir, '.git-mem.json'), JSON.stringify(config, null, 2) + '\n'); + }); + + after(() => { + cleanupRepo(repoDir); + }); + + it('should produce no output when disabled', () => { + const result = runHook('prompt-submit', { + session_id: 'test-prompt-disabled', + prompt: 'some prompt', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), ''); + }); + }); +}); diff --git a/tests/integration/hooks/hook-session-start.test.ts b/tests/integration/hooks/hook-session-start.test.ts new file mode 100644 index 00000000..ddec6203 --- /dev/null +++ b/tests/integration/hooks/hook-session-start.test.ts @@ -0,0 +1,109 @@ +/** + * Integration test: session-start hook + * + * Exercises `git-mem hook session-start` end-to-end against a real + * git repo with stored memories. Verifies stdout/stderr/exit code. + */ + +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { MemoryService } from '../../../src/application/services/MemoryService'; +import { MemoryRepository } from '../../../src/infrastructure/repositories/MemoryRepository'; +import { NotesService } from '../../../src/infrastructure/services/NotesService'; +import { + runHook, + createTestRepo, + writeGitMemConfig, + cleanupRepo, +} from './helpers'; + +describe('Integration: hook session-start', () => { + let repoDir: string; + let commitSha: string; + + before(() => { + const repo = createTestRepo('git-mem-hook-start-'); + repoDir = repo.dir; + commitSha = repo.sha; + }); + + after(() => { + cleanupRepo(repoDir); + }); + + describe('with stored memories', () => { + before(() => { + // Store a memory in the test repo + const notesService = new NotesService(); + const memoryRepo = new MemoryRepository(notesService); + const memoryService = new MemoryService(memoryRepo); + + memoryService.remember('Use JWT for stateless auth', { + sha: commitSha, + type: 'decision', + tags: 'auth', + cwd: repoDir, + }); + + // Write .git-mem.json with hooks enabled + writeGitMemConfig(repoDir); + }); + + it('should output formatted memories to stdout', () => { + const result = runHook('session-start', { + session_id: 'test-session-1', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + assert.ok(result.stdout.includes('JWT'), `stdout should contain memory content, got: ${result.stdout}`); + }); + + it('should output summary to stderr', () => { + const result = runHook('session-start', { + session_id: 'test-session-2', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + assert.ok( + result.stderr.includes('Memory loaded'), + `stderr should contain summary, got: ${result.stderr}`, + ); + }); + + it('should exit with code 0', () => { + const result = runHook('session-start', { + session_id: 'test-session-3', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + }); + }); + + describe('with empty memory store', () => { + let emptyRepoDir: string; + + before(() => { + const repo = createTestRepo('git-mem-hook-start-empty-'); + emptyRepoDir = repo.dir; + writeGitMemConfig(emptyRepoDir); + }); + + after(() => { + cleanupRepo(emptyRepoDir); + }); + + it('should exit gracefully with no stdout output', () => { + const result = runHook('session-start', { + session_id: 'test-session-empty', + cwd: emptyRepoDir, + }); + + assert.equal(result.status, 0); + // No memories → no formatted output, no "Memory loaded." stderr + assert.equal(result.stdout.trim(), ''); + }); + }); +}); diff --git a/tests/integration/hooks/hook-session-stop.test.ts b/tests/integration/hooks/hook-session-stop.test.ts new file mode 100644 index 00000000..b5db0d7b --- /dev/null +++ b/tests/integration/hooks/hook-session-stop.test.ts @@ -0,0 +1,75 @@ +/** + * Integration test: session-stop hook + * + * Exercises `git-mem hook session-stop` end-to-end against a real + * git repo with conventional commits. Verifies memory capture via + * SessionCaptureService → LiberateService pipeline. + */ + +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { + runHook, + createTestRepo, + addCommit, + writeGitMemConfig, + cleanupRepo, +} from './helpers'; + +describe('Integration: hook session-stop', () => { + let repoDir: string; + + before(() => { + const repo = createTestRepo('git-mem-hook-stop-'); + repoDir = repo.dir; + + // Add some conventional commits that should score well in triage + addCommit(repoDir, 'auth.ts', 'export function login() { /* JWT auth */ }', 'feat: add JWT authentication with token refresh'); + addCommit(repoDir, 'config.ts', 'export const DB_HOST = "localhost";', 'fix: database connection timeout handling'); + addCommit(repoDir, 'api.ts', 'export function getUsers() {}', 'feat: implement user API endpoints with pagination'); + + // Enable hooks with autoLiberate + writeGitMemConfig(repoDir); + }); + + after(() => { + cleanupRepo(repoDir); + }); + + it('should exit with code 0', () => { + const result = runHook('session-stop', { + session_id: 'test-session-stop-1', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + }); + + it('should output capture summary to stderr', () => { + const result = runHook('session-stop', { + session_id: 'test-session-stop-2', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + assert.ok( + result.stderr.includes('Session capture complete') || result.stderr.includes('git-mem:'), + `stderr should contain capture summary, got: ${result.stderr}`, + ); + }); + + it('should produce stdout output with capture results', () => { + const result = runHook('session-stop', { + session_id: 'test-session-stop-3', + cwd: repoDir, + }); + + assert.equal(result.status, 0); + // SessionStopHandler returns summary string as output + // It may say "Captured N memories" or "Scanned N commits, no new memories" + assert.ok( + result.stdout.includes('commits') || result.stdout.includes('memories') || result.stdout.trim() === '', + `stdout should contain capture results or be empty, got: ${result.stdout}`, + ); + }); +}); From 5e4d8a90ba95f7896909c9397e5a08f522144bc6 Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Thu, 12 Feb 2026 21:35:01 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20?= =?UTF-8?q?deep=20merge=20config,=20consolidate=20imports,=20use=20helper?= =?UTF-8?q?=20(GIT-58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- tests/integration/hooks/helpers.ts | 26 ++++++++++++++----- .../integration/hooks/hook-init-hooks.test.ts | 3 +-- .../hooks/hook-prompt-submit.test.ts | 23 +++------------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/tests/integration/hooks/helpers.ts b/tests/integration/hooks/helpers.ts index b3947b9e..808a0f64 100644 --- a/tests/integration/hooks/helpers.ts +++ b/tests/integration/hooks/helpers.ts @@ -5,8 +5,7 @@ * and `git-mem init-hooks` against real temp git repos. */ -import { spawnSync } from 'child_process'; -import { execFileSync } from 'child_process'; +import { spawnSync, execFileSync } from 'child_process'; import { mkdtempSync, writeFileSync, rmSync } from 'fs'; import { join, resolve } from 'path'; import { tmpdir } from 'os'; @@ -82,8 +81,11 @@ export function addCommit(dir: string, filename: string, content: string, messag return git(['rev-parse', 'HEAD'], dir); } -/** Write .git-mem.json into a directory with optional overrides. */ -export function writeGitMemConfig(dir: string, overrides?: Record): void { +/** Write .git-mem.json into a directory with optional per-hook overrides. */ +export function writeGitMemConfig( + dir: string, + overrides?: Partial>, +): void { const defaults = { hooks: { enabled: true, @@ -93,8 +95,20 @@ export function writeGitMemConfig(dir: string, overrides?: Record { }); // Write config with promptSubmit ENABLED - const config = { - hooks: { - enabled: true, - sessionStart: { enabled: true, memoryLimit: 20 }, - sessionStop: { enabled: true, autoLiberate: true, threshold: 3 }, - promptSubmit: { enabled: true, recordPrompts: false, surfaceContext: true }, - }, - }; - writeFileSync(join(repoDir, '.git-mem.json'), JSON.stringify(config, null, 2) + '\n'); + writeGitMemConfig(repoDir, { promptSubmit: { enabled: true, recordPrompts: false, surfaceContext: true } }); }); after(() => { @@ -90,15 +81,7 @@ describe('Integration: hook prompt-submit', () => { repoDir = repo.dir; // Default config has promptSubmit.enabled: false - const config = { - hooks: { - enabled: true, - sessionStart: { enabled: true, memoryLimit: 20 }, - sessionStop: { enabled: true, autoLiberate: true, threshold: 3 }, - promptSubmit: { enabled: false, recordPrompts: false, surfaceContext: true }, - }, - }; - writeFileSync(join(repoDir, '.git-mem.json'), JSON.stringify(config, null, 2) + '\n'); + writeGitMemConfig(repoDir); }); after(() => {