refactor(shared): Centralise $TMPDIR/repomix umbrella in shared/tmpDir

intent(tmp-dir): a single grep should find every consumer of the `$TMPDIR/repomix/` umbrella; previously the path was rebuilt independently in mcpToolRuntime and tokenCountCache, with no shared anchor preventing future drift
decision(api-shape): expose only `getRepomixTmpDir()` + the `REPOMIX_TMP_DIR_NAME` constant — callers append their own subdirectory (`mcp-outputs`, `cache`) and own their mkdir/mkdtemp/permissions, since each consumer's lifecycle and creation pattern differs
rejected(scope): folding `--remote`'s `mkdtemp(repomix-)` into the umbrella was considered but skipped — it uses a different prefix-based scheme at the $TMPDIR root, not the shared umbrella, and changing it is a user-visible path change worth handling separately
This commit is contained in:
Kazuki Yamada
2026-05-10 15:06:48 +09:00
parent 3af0012e84
commit 72b75f607b
5 changed files with 39 additions and 9 deletions
+4 -6
View File
@@ -1,8 +1,8 @@
import { createHash } from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { logger } from '../../shared/logger.js';
import { getRepomixTmpDir } from '../../shared/tmpDir.js';
import type { TokenEncoding } from './tokenEncodings.js';
// Cache schema version. Bump when the on-disk format changes incompatibly so
@@ -14,10 +14,8 @@ const CACHE_VERSION = 1;
// cap is exceeded the oldest entries are dropped at save time.
export const MAX_CACHE_ENTRIES = 100_000;
// Cache lives under $TMPDIR/repomix/cache/ to share the `repomix/` parent
// directory with other ephemeral state (e.g. mcp-outputs/), so all repomix
// temp artifacts on a host are siblings under one umbrella.
const CACHE_DIR_NAME = 'repomix';
// Cache lives under $TMPDIR/repomix/cache/, sharing the `repomix/` umbrella
// (see shared/tmpDir.ts) with other ephemeral state such as mcp-outputs/.
const CACHE_SUBDIR_NAME = 'cache';
const CACHE_FILE_NAME = 'token-counts.json';
@@ -49,7 +47,7 @@ let state = createState();
export const getCacheFilePath = (): string => {
const override = process.env.REPOMIX_TOKEN_CACHE_PATH;
if (override) return override;
return path.join(os.tmpdir(), CACHE_DIR_NAME, CACHE_SUBDIR_NAME, CACHE_FILE_NAME);
return path.join(getRepomixTmpDir(), CACHE_SUBDIR_NAME, CACHE_FILE_NAME);
};
/**
+2 -2
View File
@@ -1,10 +1,10 @@
import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { generateTreeString } from '../../core/file/fileTreeGenerate.js';
import type { ProcessedFile } from '../../core/file/fileTypes.js';
import { getRepomixTmpDir } from '../../shared/tmpDir.js';
// Map to store generated output files
const outputFileRegistry = new Map<string, string>();
@@ -48,7 +48,7 @@ type McpToolStructuredContent = (BaseMcpToolResponse & Record<string, unknown>)
*/
export const createToolWorkspace = async (): Promise<string> => {
try {
const tmpBaseDir = path.join(os.tmpdir(), 'repomix', 'mcp-outputs');
const tmpBaseDir = path.join(getRepomixTmpDir(), 'mcp-outputs');
await fs.mkdir(tmpBaseDir, { recursive: true });
const tempDir = await fs.mkdtemp(`${tmpBaseDir}/`);
return tempDir;
+16
View File
@@ -0,0 +1,16 @@
import os from 'node:os';
import path from 'node:path';
// Shared umbrella directory under $TMPDIR for all repomix temp artifacts
// (MCP outputs, token-count cache, future ephemeral state). Centralised here
// so a single grep finds every consumer and the umbrella never drifts.
export const REPOMIX_TMP_DIR_NAME = 'repomix';
/**
* Returns `$TMPDIR/repomix`. Callers append their own subdirectory and are
* responsible for creating it (recursive mkdir, mkdtemp, permissions etc.)
* because each consumer has different needs.
*/
export const getRepomixTmpDir = (): string => {
return path.join(os.tmpdir(), REPOMIX_TMP_DIR_NAME);
};
+4 -1
View File
@@ -63,7 +63,10 @@ describe('mcpToolRuntime', () => {
const tempDir = await createToolWorkspace();
expect(os.tmpdir).toHaveBeenCalled();
expect(path.join).toHaveBeenCalledWith('/tmp', 'repomix', 'mcp-outputs');
// path.join is now invoked twice: once inside shared/tmpDir.getRepomixTmpDir
// to build the umbrella, then again here to append the mcp-outputs subdir.
expect(path.join).toHaveBeenCalledWith('/tmp', 'repomix');
expect(path.join).toHaveBeenCalledWith('/tmp/repomix', 'mcp-outputs');
expect(fs.mkdir).toHaveBeenCalledWith('/tmp/repomix/mcp-outputs', { recursive: true });
expect(fs.mkdtemp).toHaveBeenCalledWith('/tmp/repomix/mcp-outputs/');
expect(tempDir).toBe('/tmp/repomix/mcp-outputs/temp-dir');
+13
View File
@@ -0,0 +1,13 @@
import os from 'node:os';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { getRepomixTmpDir, REPOMIX_TMP_DIR_NAME } from '../../src/shared/tmpDir.js';
describe('shared/tmpDir', () => {
it('returns a path under os.tmpdir() ending with the umbrella name', () => {
const dir = getRepomixTmpDir();
expect(dir.startsWith(os.tmpdir())).toBe(true);
expect(path.basename(dir)).toBe(REPOMIX_TMP_DIR_NAME);
expect(REPOMIX_TMP_DIR_NAME).toBe('repomix');
});
});