mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-10 22:43:40 +00:00
Trimmed rebuild of 6ea5aa44 (fork PR #4): Move core (gitnexus/src/core/move/)
and Move tests, plus the functional minimum elsewhere.
Dropped from the original commit:
- .gitignore .codex/ entry (unrelated)
- cli/analyze.ts env-constant swap (cosmetic; same string literal)
- core/logger.ts warnRespectingProgressBar extraction and the
filesystem-walker.ts reuse of it (Move code never imports the helper)
- scope-resolution callable-value-flow warning aggregation and pipeline/run.ts
progress-warning formatting, with their two tests (warning-UX hardening,
not Move-functional)
- process-detection interface doc-comment retuning (DEFAULT_CONFIG runtime
values already match upstream; region left byte-identical to upstream)
Kept outside src/core/move/ and Move tests (one line each):
- gitnexus-shared/src/graph/types.ts: 'external' locationFidelity for
dependency symbols
- gitnexus-shared/src/lbug/schema-constants.ts: 'Type' node table registration
- core/lbug/schema.ts: Type table schema + Move rel-table endpoint pairs
- core/lbug/node-table-layout.ts: TYPE_LAYOUT CSV layout
- core/lbug/csv-generator.ts: 'Type' in MULTI_LANG_TYPES routing
- core/lbug/lbug-adapter.ts: deleteAllExternalNodes + shared delete-by-label
mechanics; Type backtick entry
- core/incremental/subgraph-extract.ts: external nodes get the
delete-all-then-rebuild treatment
- core/run-analyze.ts: Move consistency digest to meta.json;
deleteAllExternalNodes call; grouped Move meta; finally-based shutdown
- storage/repo-manager.ts: moveConsistency in RepoMeta;
INCREMENTAL_SCHEMA_VERSION 13 (Type/EnumVariant persistence)
- core/ingestion/pipeline.ts: generic TStandaloneIngest output threading
- types/pipeline.ts: PipelineResult.standaloneIngest replaces ingestWarnings
- core/ingestion/process-processor.ts: ENTRY_POINT_OF explicit graph roots
- tests: schema, node-table-layout, process-processor,
repo-manager-reconcile, call-summary-schema-version,
incremental-subgraph-extract, run-analyze-fts-repair (unit);
lbug-core-adapter (integration)
Gates: tsc clean; Move unit 185/185; full unit suite green except three
pre-existing Darwin-environment failures reproduced on the pristine base;
integration (move + lbug-core-adapter) 29/29.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
233 lines
8.8 KiB
TypeScript
233 lines
8.8 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import type {
|
|
MoveFlowMcpClient,
|
|
ResolvedMoveFlowClient,
|
|
} from '../../../src/core/move/mcp-client.js';
|
|
import {
|
|
ensureMoveFlowRuntime,
|
|
type MoveFlowProvisionDependencies,
|
|
} from '../../../src/core/move/provision.js';
|
|
import type { VerifiedMoveFlowBinary } from '../../../src/core/move/install.js';
|
|
|
|
const originalMoveFlow = process.env.MOVE_FLOW;
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
vi.restoreAllMocks();
|
|
if (originalMoveFlow === undefined) delete process.env.MOVE_FLOW;
|
|
else process.env.MOVE_FLOW = originalMoveFlow;
|
|
});
|
|
|
|
const client = {} as MoveFlowMcpClient;
|
|
const resolved: ResolvedMoveFlowClient = { client, version: '2.0.0' };
|
|
const cached: VerifiedMoveFlowBinary = {
|
|
binaryPath: '/cache/move-flow',
|
|
version: '2.0.0',
|
|
fingerprint: 'release-fingerprint',
|
|
};
|
|
|
|
const dependencies = (
|
|
overrides: Partial<MoveFlowProvisionDependencies> = {},
|
|
): MoveFlowProvisionDependencies => ({
|
|
resolveClient: vi.fn(() => null),
|
|
createClient: vi.fn(() => client),
|
|
findCached: vi.fn(async () => null),
|
|
install: vi.fn(async () => ({ status: 'failed', message: 'offline' })),
|
|
...overrides,
|
|
});
|
|
|
|
describe('ensureMoveFlowRuntime', () => {
|
|
it('does not install when an explicit or PATH binary already resolves', async () => {
|
|
const deps = dependencies({ resolveClient: vi.fn(() => resolved) });
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toMatchObject({ client });
|
|
expect(deps.install).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('logs when the resolved binary cannot be read for fingerprinting', async () => {
|
|
// A resolver that answers for a locator whose file does not exist: the
|
|
// digest falls back to a random 'unverifiable' value, which churns the
|
|
// fingerprint (full re-index every run) - that must not stay silent.
|
|
process.env.MOVE_FLOW = '/missing/move-flow';
|
|
const deps = dependencies({ resolveClient: vi.fn(() => resolved) });
|
|
const onLog = vi.fn();
|
|
|
|
const runtime = await ensureMoveFlowRuntime({ onLog }, deps);
|
|
|
|
expect(runtime?.client).toBe(client);
|
|
expect(onLog).toHaveBeenCalledWith(
|
|
expect.stringContaining('could not be read for fingerprinting'),
|
|
);
|
|
});
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'fingerprints local compiler bytes, not only path and version',
|
|
async () => {
|
|
const directory = await mkdtemp(path.join(os.tmpdir(), 'move-flow-identity-'));
|
|
const binary = path.join(directory, 'move-flow');
|
|
try {
|
|
process.env.MOVE_FLOW = binary;
|
|
await writeFile(binary, '#!/bin/sh\necho first\n');
|
|
await chmod(binary, 0o755);
|
|
const deps = dependencies({ resolveClient: vi.fn(() => resolved) });
|
|
const first = await ensureMoveFlowRuntime({}, deps);
|
|
|
|
await writeFile(binary, '#!/bin/sh\necho second\n');
|
|
await chmod(binary, 0o755);
|
|
const second = await ensureMoveFlowRuntime({}, deps);
|
|
expect(second?.identity.fingerprint).not.toBe(first?.identity.fingerprint);
|
|
} finally {
|
|
await rm(directory, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
it('fingerprints a Windows PATH locator that already includes its executable extension', async () => {
|
|
const directory = await mkdtemp(path.join(os.tmpdir(), 'move-flow-windows-identity-'));
|
|
const binary = path.join(directory, 'move-flow.exe');
|
|
try {
|
|
await writeFile(binary, 'stable move-flow bytes');
|
|
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');
|
|
vi.stubEnv('MOVE_FLOW', 'move-flow.exe');
|
|
vi.stubEnv('PATH', directory);
|
|
vi.stubEnv('PATHEXT', '.exe');
|
|
const deps = dependencies({ resolveClient: vi.fn(() => resolved) });
|
|
|
|
const first = await ensureMoveFlowRuntime({}, deps);
|
|
const second = await ensureMoveFlowRuntime({}, deps);
|
|
|
|
expect(second?.identity.fingerprint).toBe(first?.identity.fingerprint);
|
|
} finally {
|
|
await rm(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('keeps an invalid explicit MOVE_FLOW authoritative', async () => {
|
|
process.env.MOVE_FLOW = '/missing/move-flow';
|
|
const deps = dependencies();
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toBeNull();
|
|
expect(deps.install).not.toHaveBeenCalled();
|
|
expect(process.env.MOVE_FLOW).toBe('/missing/move-flow');
|
|
});
|
|
|
|
it('supports local-only resolution without starting installation', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const deps = dependencies();
|
|
|
|
await expect(ensureMoveFlowRuntime({ install: false }, deps)).resolves.toBeNull();
|
|
expect(deps.install).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('installs once and returns the verified release identity', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const deps = dependencies({
|
|
install: vi.fn(async () => ({ status: 'installed' as const, binary: cached })),
|
|
});
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toEqual({
|
|
client,
|
|
identity: {
|
|
version: '2.0.0',
|
|
source: 'release',
|
|
fingerprint: 'release-fingerprint',
|
|
},
|
|
});
|
|
expect(deps.install).toHaveBeenCalledOnce();
|
|
expect(deps.createClient).toHaveBeenCalledWith('/cache/move-flow');
|
|
expect(deps.resolveClient).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not retry a rejected installation within the same process', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const install = vi
|
|
.fn<MoveFlowProvisionDependencies['install']>()
|
|
.mockRejectedValue(new Error('offline'));
|
|
const deps = dependencies({ install });
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toBeNull();
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toBeNull();
|
|
expect(install).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('does not retry a soft installation failure within the same process', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const install = vi
|
|
.fn<MoveFlowProvisionDependencies['install']>()
|
|
.mockResolvedValue({ status: 'failed', message: 'checksum service unavailable' });
|
|
const deps = dependencies({ install });
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toBeNull();
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toBeNull();
|
|
expect(install).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('shares only an in-flight install attempt', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
let finish!: (value: { status: 'installed'; binary: VerifiedMoveFlowBinary }) => void;
|
|
const pending = new Promise<{ status: 'installed'; binary: VerifiedMoveFlowBinary }>(
|
|
(resolve) => {
|
|
finish = resolve;
|
|
},
|
|
);
|
|
const install = vi.fn(() => pending);
|
|
const deps = dependencies({ install });
|
|
|
|
const first = ensureMoveFlowRuntime({}, deps);
|
|
const second = ensureMoveFlowRuntime({}, deps);
|
|
await vi.waitFor(() => expect(install).toHaveBeenCalledOnce());
|
|
finish({ status: 'installed', binary: cached });
|
|
|
|
await expect(Promise.all([first, second])).resolves.toHaveLength(2);
|
|
expect(deps.createClient).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('clears a successful install attempt so a later cache miss can reinstall', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const install = vi.fn(async () => ({ status: 'installed' as const, binary: cached }));
|
|
const deps = dependencies({ install });
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toMatchObject({ client });
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toMatchObject({ client });
|
|
expect(install).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('uses the verified cache without another version probe', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const deps = dependencies({ findCached: vi.fn(async () => cached) });
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toMatchObject({
|
|
client,
|
|
identity: { source: 'release', fingerprint: 'release-fingerprint' },
|
|
});
|
|
expect(deps.resolveClient).not.toHaveBeenCalled();
|
|
expect(deps.createClient).toHaveBeenCalledWith('/cache/move-flow');
|
|
expect(deps.install).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rejects an incompatible verified cache without constructing a client', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const deps = dependencies({
|
|
findCached: vi.fn(async () => ({ ...cached, version: '3.0.0' })),
|
|
});
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toBeNull();
|
|
expect(deps.createClient).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rejects an incompatible installed result without constructing a client', async () => {
|
|
delete process.env.MOVE_FLOW;
|
|
const deps = dependencies({
|
|
install: vi.fn(async () => ({
|
|
status: 'installed' as const,
|
|
binary: { ...cached, version: '3.0.0' },
|
|
})),
|
|
});
|
|
|
|
await expect(ensureMoveFlowRuntime({}, deps)).resolves.toBeNull();
|
|
expect(deps.createClient).not.toHaveBeenCalled();
|
|
});
|
|
});
|