GitNexus/gitnexus/test/unit/run-analyze.test.ts
Copilot 2b0392cd83
feat(analyze): preserve existing embeddings by default; --force regenerates them; add --drop-embeddings opt-out (CLI + HTTP API) (#1055)
* Initial plan

* fix(analyze): preserve existing embeddings by default; add --drop-embeddings opt-out

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/da1da041-afcd-4d38-8a2f-39ca52a462ff

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

* analyze: --force on embedded repo now regenerates embeddings (preserve+top-up)

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e2759765-b8f6-453a-8c28-595439d23cb4

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

* analyze: wire dropEmbeddings into HTTP API; log cache-load failures; extract pure deriveEmbeddingMode + behavioral tests; sync GUARDRAILS.md

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7d88e595-cbd8-47b2-ba4f-fb5b9a60cda4

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
2026-04-24 13:07:40 +01:00

95 lines
4.2 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { deriveEmbeddingMode } from '../../src/core/embedding-mode.js';
describe('run-analyze module', () => {
it('exports runFullAnalysis as a function', async () => {
const mod = await import('../../src/core/run-analyze.js');
expect(typeof mod.runFullAnalysis).toBe('function');
});
it('exports PHASE_LABELS', async () => {
const mod = await import('../../src/core/run-analyze.js');
expect(mod.PHASE_LABELS).toBeDefined();
expect(mod.PHASE_LABELS.parsing).toBe('Parsing code');
});
});
describe('deriveEmbeddingMode', () => {
// Default `analyze` on a repo with existing embeddings: must preserve, must
// NOT regenerate, must load the cache so phase 3.5 can re-insert vectors.
it('default + existing>0 → preserve only (load cache, no generation)', () => {
const m = deriveEmbeddingMode({}, 1234);
expect(m.preserveExistingEmbeddings).toBe(true);
expect(m.shouldGenerateEmbeddings).toBe(false);
expect(m.forceRegenerateEmbeddings).toBe(false);
expect(m.shouldLoadCache).toBe(true);
});
it('default + existing=0 → no-op (no preserve, no generation, no cache load)', () => {
const m = deriveEmbeddingMode({}, 0);
expect(m.preserveExistingEmbeddings).toBe(false);
expect(m.shouldGenerateEmbeddings).toBe(false);
expect(m.forceRegenerateEmbeddings).toBe(false);
expect(m.shouldLoadCache).toBe(false);
});
// The headline behavior change requested in PR feedback: --force on an
// already-embedded repo must regenerate (top up new/changed nodes), not
// silently downgrade to "preserve only".
it('--force + existing>0 → forceRegenerate + generate + load cache', () => {
const m = deriveEmbeddingMode({ force: true }, 500);
expect(m.forceRegenerateEmbeddings).toBe(true);
expect(m.shouldGenerateEmbeddings).toBe(true);
expect(m.preserveExistingEmbeddings).toBe(false);
expect(m.shouldLoadCache).toBe(true);
});
it('--force + existing=0 → no embedding work (force keeps prior semantics)', () => {
const m = deriveEmbeddingMode({ force: true }, 0);
expect(m.forceRegenerateEmbeddings).toBe(false);
expect(m.shouldGenerateEmbeddings).toBe(false);
expect(m.preserveExistingEmbeddings).toBe(false);
expect(m.shouldLoadCache).toBe(false);
});
it('--embeddings → generate + load cache (incremental top-up)', () => {
const m = deriveEmbeddingMode({ embeddings: true }, 500);
expect(m.shouldGenerateEmbeddings).toBe(true);
expect(m.preserveExistingEmbeddings).toBe(false);
expect(m.shouldLoadCache).toBe(true);
});
it('--embeddings + existing=0 → generate; cache load still fires (harmless empty load)', () => {
const m = deriveEmbeddingMode({ embeddings: true }, 0);
expect(m.shouldGenerateEmbeddings).toBe(true);
// Cache load is gated at the call site by `existingMeta`, not by count;
// when explicit `--embeddings` is set we always attempt the load so any
// stray vectors from a partial prior run get picked up.
expect(m.shouldLoadCache).toBe(true);
});
// --drop-embeddings is the explicit wipe path; it must suppress cache load
// even when --force is also set (the dominant escape hatch).
it('--drop-embeddings → suppresses cache load, no generation', () => {
const m = deriveEmbeddingMode({ dropEmbeddings: true }, 1234);
expect(m.shouldLoadCache).toBe(false);
expect(m.shouldGenerateEmbeddings).toBe(false);
expect(m.preserveExistingEmbeddings).toBe(false);
expect(m.forceRegenerateEmbeddings).toBe(false);
});
it('--force + --drop-embeddings → drop wins (no cache load, no generation)', () => {
const m = deriveEmbeddingMode({ force: true, dropEmbeddings: true }, 1234);
expect(m.shouldLoadCache).toBe(false);
expect(m.shouldGenerateEmbeddings).toBe(false);
expect(m.forceRegenerateEmbeddings).toBe(false);
});
it('--embeddings + --drop-embeddings → drop suppresses cache load (no preservation)', () => {
// --embeddings still generates, but the prior vectors are wiped first.
const m = deriveEmbeddingMode({ embeddings: true, dropEmbeddings: true }, 1234);
expect(m.shouldLoadCache).toBe(false);
expect(m.shouldGenerateEmbeddings).toBe(true);
expect(m.preserveExistingEmbeddings).toBe(false);
});
});