Reuse unsafeIndexRootReason before scanning indexed subprojects so stray manifests at home or broader roots cannot inject unrelated context. Preserve workspace adoption for #964. Validation: four new regressions fail before the guard and pass after it; 48 relevant tests and npm run build pass. Confirmed the real os.homedir() leak before and after the fix with fixture cleanup. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
co-authored by
Colby McHenry
parent
4453310eef
commit
3193800bc8
@@ -141,6 +141,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
#### MCP / indexing
|
||||
|
||||
- The prompt hook no longer injects unrelated projects when run from your home directory or a broader directory containing a stray workspace manifest. (#1454)
|
||||
|
||||
- Indexing now succeeds when Node.js's SQLite lacks FTS5, with search falling back to name and fuzzy matching; thanks @aniruddhaadak80. (#1532)
|
||||
|
||||
- `codegraph_explore` now makes clear that suggested call counts are advisory, so agents keep exploring when an answer is incomplete; thanks @rongbc. (#1504, #1570)
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
* logic), since the end-to-end hook is validated by a live agent run, not a
|
||||
* unit test.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { planFrontload, findIndexedSubprojectRoots, isStructuralPrompt, hasStructuralKeyword, extractCodeTokens, PROMPT_HOOK_INJECTION_MAX, CLAUDE_CODE_INLINE_HOOK_OUTPUT_LIMIT, capPromptHookInjection } from '../src/directory';
|
||||
import { planFrontload, findIndexedSubprojectRoots, unsafeIndexRootReason, isStructuralPrompt, hasStructuralKeyword, extractCodeTokens, PROMPT_HOOK_INJECTION_MAX, CLAUDE_CODE_INLINE_HOOK_OUTPUT_LIMIT, capPromptHookInjection } from '../src/directory';
|
||||
|
||||
// Make the built-in exports configurable so HOME can point at a real temp
|
||||
// fixture without changing the process environment or the user's home files.
|
||||
vi.mock('os', async (importOriginal) => ({ ...await importOriginal<typeof import('os')>() }));
|
||||
|
||||
/** Make `dir` look indexed (isInitialized needs `.codegraph/codegraph.db`). */
|
||||
function mkIndexed(dir: string): string {
|
||||
@@ -30,7 +34,10 @@ function mkWorkspaceRoot(dir: string): string {
|
||||
describe('planFrontload — front-load hook project resolution (#964)', () => {
|
||||
let tmp: string;
|
||||
beforeEach(() => { tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'cg-frontload-'))); });
|
||||
afterEach(() => { fs.rmSync(tmp, { recursive: true, force: true }); });
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('cwd is itself indexed → front-load cwd (the common single-project case)', () => {
|
||||
mkIndexed(tmp);
|
||||
@@ -92,6 +99,29 @@ describe('planFrontload — front-load hook project resolution (#964)', () => {
|
||||
expect(plan.nudgeProjects).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ root: 'home', manifest: 'package.json', children: 1 },
|
||||
{ root: 'home', manifest: 'package.json', children: 2 },
|
||||
{ root: 'home', manifest: 'WORKSPACE', children: 1 },
|
||||
{ root: 'parent of home', manifest: 'package.json', children: 1 },
|
||||
])('$root with stray $manifest and $children indexed children → no-op (#1454)', ({ root, manifest, children }) => {
|
||||
const homeDir = root === 'home' ? tmp : path.join(tmp, 'user');
|
||||
fs.mkdirSync(homeDir, { recursive: true });
|
||||
vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
|
||||
if (manifest === 'package.json') mkWorkspaceRoot(tmp);
|
||||
else fs.mkdirSync(path.join(tmp, manifest)); // Even a WORKSPACE directory opens the manifest gate.
|
||||
mkIndexed(path.join(tmp, 'packages', 'api'));
|
||||
if (children === 2) mkIndexed(path.join(tmp, 'packages', 'web'));
|
||||
expect(unsafeIndexRootReason(tmp)).toBe(root === 'home' ? 'your home directory' : 'a parent of your home directory');
|
||||
|
||||
expect(planFrontload(tmp, 'how does authentication work end to end?')).toEqual({
|
||||
exploreRoot: null,
|
||||
nudgeProjects: [],
|
||||
viaSubScan: false,
|
||||
});
|
||||
expect(findIndexedSubprojectRoots(tmp)).toEqual([]);
|
||||
});
|
||||
|
||||
it('nothing indexed anywhere → no-op', () => {
|
||||
mkWorkspaceRoot(tmp);
|
||||
fs.mkdirSync(path.join(tmp, 'packages', 'api'), { recursive: true });
|
||||
|
||||
@@ -213,6 +213,8 @@ export function findIndexedSubprojectRoots(
|
||||
root: string,
|
||||
opts: { maxDepth?: number; max?: number } = {},
|
||||
): string[] {
|
||||
// A stray workspace manifest must not enable scanning home or broader roots (#1454).
|
||||
if (unsafeIndexRootReason(root) !== null) return [];
|
||||
const maxDepth = opts.maxDepth ?? 4;
|
||||
const max = opts.max ?? 64;
|
||||
const out: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user