mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-29 01:41:42 +00:00
chore: prettier + eslint --fix + conflict resolution after rebase onto main
This commit is contained in:
parent
1cc6888e19
commit
f5cbcb9d30
25 changed files with 286 additions and 179 deletions
|
|
@ -211,7 +211,14 @@ const AppContent = () => {
|
|||
setProgress(null);
|
||||
}, ERROR_RESET_DELAY_MS);
|
||||
});
|
||||
}, [handleServerConnect, setProgress, setViewMode, setServerBaseUrl, setAvailableRepos, connectToGroup]);
|
||||
}, [
|
||||
handleServerConnect,
|
||||
setProgress,
|
||||
setViewMode,
|
||||
setServerBaseUrl,
|
||||
setAvailableRepos,
|
||||
connectToGroup,
|
||||
]);
|
||||
|
||||
const handleFocusNode = useCallback((nodeId: string) => {
|
||||
graphCanvasRef.current?.focusNode(nodeId);
|
||||
|
|
|
|||
|
|
@ -306,9 +306,11 @@ export const DropZone = ({ onServerConnect, onGroupSelect }: DropZoneProps) => {
|
|||
<RepoLanding
|
||||
repos={detectedRepos}
|
||||
onSelectRepo={connectToRepo}
|
||||
onSelectGroup={onGroupSelect
|
||||
? (groupName) => onGroupSelect(groupName, detectedBackendUrl ?? undefined)
|
||||
: undefined}
|
||||
onSelectGroup={
|
||||
onGroupSelect
|
||||
? (groupName) => onGroupSelect(groupName, detectedBackendUrl ?? undefined)
|
||||
: undefined
|
||||
}
|
||||
onAnalyzeComplete={connectToRepo}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,9 @@ const TreeItem = ({
|
|||
)}
|
||||
|
||||
{/* Name */}
|
||||
<span className={`truncate font-mono text-xs ${node.path.startsWith('repo::') ? 'font-semibold' : ''}`}>
|
||||
<span
|
||||
className={`truncate font-mono text-xs ${node.path.startsWith('repo::') ? 'font-semibold' : ''}`}
|
||||
>
|
||||
{node.name}
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@ export const GraphCanvas = forwardRef<GraphCanvasHandle>((_, ref) => {
|
|||
}
|
||||
const node = nodeById.get(nodeId);
|
||||
setHoveredNodeName(node ? node.properties.name : null);
|
||||
const repo = node ? (node.properties as Record<string, unknown>)._repo as string | undefined : undefined;
|
||||
const repo = node
|
||||
? ((node.properties as Record<string, unknown>)._repo as string | undefined)
|
||||
: undefined;
|
||||
setHoveredNodeRepo(repo ?? null);
|
||||
},
|
||||
[graph, nodeById],
|
||||
|
|
|
|||
|
|
@ -228,7 +228,9 @@ export const Header = ({
|
|||
{g}
|
||||
</span>
|
||||
{g === projectName && (
|
||||
<span className="shrink-0 font-mono text-[10px] text-amber-400">active</span>
|
||||
<span className="shrink-0 font-mono text-[10px] text-amber-400">
|
||||
active
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@
|
|||
import { Sparkles, ArrowRight, GitBranch, FileCode, Layers, FolderOpen } from '@/lib/lucide-icons';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { RepoAnalyzer } from './RepoAnalyzer';
|
||||
import { fetchGroups, fetchGroupStatus, type BackendRepo, type GroupStatus } from '../services/backend-client';
|
||||
import {
|
||||
fetchGroups,
|
||||
fetchGroupStatus,
|
||||
type BackendRepo,
|
||||
type GroupStatus,
|
||||
} from '../services/backend-client';
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -113,13 +118,9 @@ function GroupCard({
|
|||
</h3>
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-3 pl-6">
|
||||
{repoCount > 0 && (
|
||||
<span className="text-xs text-text-muted">{repoCount} repos</span>
|
||||
)}
|
||||
{repoCount > 0 && <span className="text-xs text-text-muted">{repoCount} repos</span>}
|
||||
{lastSync && (
|
||||
<span className="text-xs text-text-muted">
|
||||
synced {formatRelativeTime(lastSync)}
|
||||
</span>
|
||||
<span className="text-xs text-text-muted">synced {formatRelativeTime(lastSync)}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -172,7 +173,7 @@ export const RepoLanding = ({
|
|||
}, [activeTab, groups, groupStatuses]);
|
||||
|
||||
return (
|
||||
<div className="relative max-h-[80vh] animate-fade-in overflow-y-auto rounded-3xl bg-surface p-7 shadow-2xl shadow-black/60 ring-1 ring-border-subtle">
|
||||
<div className="relative max-h-[80vh] animate-fade-in overflow-y-auto rounded-3xl bg-surface p-7 shadow-2xl ring-1 shadow-black/60 ring-border-subtle">
|
||||
{/* Ambient glows */}
|
||||
<div className="pointer-events-none absolute -top-28 -right-28 h-72 w-72 rounded-full bg-accent/6 blur-3xl" />
|
||||
<div className="pointer-events-none absolute -bottom-24 -left-24 h-56 w-56 rounded-full bg-node-function/6 blur-3xl" />
|
||||
|
|
@ -255,7 +256,9 @@ export const RepoLanding = ({
|
|||
<FolderOpen className="mx-auto mb-3 h-8 w-8 text-text-muted/50" />
|
||||
<p className="text-sm text-text-muted">No groups configured yet.</p>
|
||||
<p className="mt-1 text-xs text-text-muted/70">
|
||||
Run <code className="rounded bg-void px-1.5 py-0.5">gitnexus group auto-discover</code> to create one.
|
||||
Run{' '}
|
||||
<code className="rounded bg-void px-1.5 py-0.5">gitnexus group auto-discover</code>{' '}
|
||||
to create one.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ export const knowledgeGraphToGraphology = (
|
|||
const nodePositions = new Map<string, { x: number; y: number }>();
|
||||
|
||||
// Helper to get repo name from a node
|
||||
const getNodeRepo = (node: typeof knowledgeGraph.nodes[0]): string | undefined => {
|
||||
const getNodeRepo = (node: (typeof knowledgeGraph.nodes)[0]): string | undefined => {
|
||||
const repo = (node.properties as Record<string, unknown>)._repo as string | undefined;
|
||||
if (repo) return repo;
|
||||
const sep = node.id.indexOf('::');
|
||||
|
|
@ -217,9 +217,8 @@ export const knowledgeGraphToGraphology = (
|
|||
const scaledSize = getScaledNodeSize(baseSize, nodeCount);
|
||||
|
||||
// In multi-repo mode, color File/Folder nodes by repo for visual distinction
|
||||
const structColor = isMultiRepo && repoCenter
|
||||
? repoCenter.color
|
||||
: NODE_COLORS[node.label] || '#9ca3af';
|
||||
const structColor =
|
||||
isMultiRepo && repoCenter ? repoCenter.color : NODE_COLORS[node.label] || '#9ca3af';
|
||||
|
||||
graph.addNode(node.id, {
|
||||
x,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,7 @@ import {
|
|||
getRepoColor,
|
||||
type EdgeType,
|
||||
} from '../../src/lib/constants';
|
||||
import {
|
||||
knowledgeGraphToGraphology,
|
||||
type SigmaNodeAttributes,
|
||||
} from '../../src/lib/graph-adapter';
|
||||
import { knowledgeGraphToGraphology, type SigmaNodeAttributes } from '../../src/lib/graph-adapter';
|
||||
import type { GraphNode, GraphRelationship } from 'gitnexus-shared';
|
||||
import type { KnowledgeGraph } from '../../src/core/graph/types';
|
||||
|
||||
|
|
@ -65,7 +62,12 @@ describe('getRepoColor', () => {
|
|||
|
||||
// ── Graph adapter tests ────────────────────────────────────────────────────
|
||||
|
||||
function makeNode(id: string, label: string, name: string, props?: Record<string, unknown>): GraphNode {
|
||||
function makeNode(
|
||||
id: string,
|
||||
label: string,
|
||||
name: string,
|
||||
props?: Record<string, unknown>,
|
||||
): GraphNode {
|
||||
return {
|
||||
id,
|
||||
label: label as GraphNode['label'],
|
||||
|
|
@ -92,8 +94,12 @@ function makeGraph(nodes: GraphNode[], relationships: GraphRelationship[]): Know
|
|||
return {
|
||||
nodes,
|
||||
relationships,
|
||||
get nodeCount() { return nodes.length; },
|
||||
get relationshipCount() { return relationships.length; },
|
||||
get nodeCount() {
|
||||
return nodes.length;
|
||||
},
|
||||
get relationshipCount() {
|
||||
return relationships.length;
|
||||
},
|
||||
addNode: () => {},
|
||||
addRelationship: () => {},
|
||||
};
|
||||
|
|
@ -144,9 +150,7 @@ describe('knowledgeGraphToGraphology - multi-repo support', () => {
|
|||
makeNode('repoA::fn1', 'Function', 'funcA', { _repo: 'repoA' }),
|
||||
makeNode('repoB::fn1', 'Function', 'funcB', { _repo: 'repoB' }),
|
||||
];
|
||||
const rels = [
|
||||
makeRel('repoB::fn1', 'repoA::fn1', 'CROSS_REPO_IMPORT'),
|
||||
];
|
||||
const rels = [makeRel('repoB::fn1', 'repoA::fn1', 'CROSS_REPO_IMPORT')];
|
||||
const graph = knowledgeGraphToGraphology(makeGraph(nodes, rels));
|
||||
|
||||
// Edge should exist
|
||||
|
|
@ -173,10 +177,7 @@ describe('knowledgeGraphToGraphology - multi-repo support', () => {
|
|||
});
|
||||
|
||||
it('works correctly in single-repo mode (no repoName assigned)', () => {
|
||||
const nodes = [
|
||||
makeNode('fn1', 'Function', 'funcA'),
|
||||
makeNode('fn2', 'Function', 'funcB'),
|
||||
];
|
||||
const nodes = [makeNode('fn1', 'Function', 'funcA'), makeNode('fn2', 'Function', 'funcB')];
|
||||
const graph = knowledgeGraphToGraphology(makeGraph(nodes, []));
|
||||
|
||||
const attr = graph.getNodeAttributes('fn1') as SigmaNodeAttributes;
|
||||
|
|
@ -195,9 +196,7 @@ describe('knowledgeGraphToGraphology - multi-repo support', () => {
|
|||
const posB = graph.getNodeAttributes('repoB::folder1') as SigmaNodeAttributes;
|
||||
|
||||
// Different repos should be positioned in different regions
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(posA.x - posB.x, 2) + Math.pow(posA.y - posB.y, 2),
|
||||
);
|
||||
const distance = Math.sqrt(Math.pow(posA.x - posB.x, 2) + Math.pow(posA.y - posB.y, 2));
|
||||
expect(distance).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,9 +58,7 @@ export function registerGroupCommands(program: Command): void {
|
|||
}
|
||||
}
|
||||
if (result.synced) {
|
||||
console.log(
|
||||
`\nSync: ${result.contracts} contracts, ${result.crossLinks} cross-links`,
|
||||
);
|
||||
console.log(`\nSync: ${result.contracts} contracts, ${result.crossLinks} cross-links`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,7 @@ export class CodeDepExtractor implements ContractExtractor {
|
|||
/**
|
||||
* Extract provider contracts: exported symbols from this repo that other repos can import.
|
||||
*/
|
||||
private async extractProviders(
|
||||
dbExecutor: CypherExecutor | null,
|
||||
): Promise<ExtractedContract[]> {
|
||||
private async extractProviders(dbExecutor: CypherExecutor | null): Promise<ExtractedContract[]> {
|
||||
if (!this.ownPackageName || !dbExecutor) return [];
|
||||
|
||||
let rows: Record<string, unknown>[];
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export interface GroupToolPort {
|
|||
include_content?: boolean;
|
||||
},
|
||||
): Promise<unknown>;
|
||||
impact(repo: GroupRepoHandle, params: Record<string, unknown>): Promise<unknown>;
|
||||
impactByUid(
|
||||
repoId: string,
|
||||
uid: string,
|
||||
|
|
@ -519,8 +520,7 @@ export class GroupService {
|
|||
const symbol = String(params.symbol ?? '').trim();
|
||||
const repoParam = typeof params.repo === 'string' ? params.repo.trim() : undefined;
|
||||
const depth = typeof params.depth === 'number' ? Math.min(params.depth, 2) : 1;
|
||||
const direction =
|
||||
typeof params.direction === 'string' ? params.direction : 'both';
|
||||
const direction = typeof params.direction === 'string' ? params.direction : 'both';
|
||||
|
||||
if (!name || !symbol) return { error: 'name and symbol are required' };
|
||||
|
||||
|
|
@ -579,23 +579,22 @@ export class GroupService {
|
|||
// Find cross-repo connections via CrossLinks
|
||||
const crossConnections: Array<{
|
||||
direction: 'outgoing' | 'incoming';
|
||||
link: typeof registry.crossLinks[0];
|
||||
link: (typeof registry.crossLinks)[0];
|
||||
remoteRepo: string;
|
||||
remoteContext: unknown;
|
||||
}> = [];
|
||||
|
||||
const visited = new Set<string>([sourceRepo.name]);
|
||||
|
||||
const findConnections = async (
|
||||
repoName: string,
|
||||
currentDepth: number,
|
||||
): Promise<void> => {
|
||||
const findConnections = async (repoName: string, currentDepth: number): Promise<void> => {
|
||||
if (currentDepth > depth) return;
|
||||
|
||||
for (const link of registry.crossLinks) {
|
||||
const isFrom = link.from.repo === repoName ||
|
||||
const isFrom =
|
||||
link.from.repo === repoName ||
|
||||
Object.entries(config.repos).some(([gp, rn]) => gp === link.from.repo && rn === repoName);
|
||||
const isTo = link.to.repo === repoName ||
|
||||
const isTo =
|
||||
link.to.repo === repoName ||
|
||||
Object.entries(config.repos).some(([gp, rn]) => gp === link.to.repo && rn === repoName);
|
||||
|
||||
let remoteRepoGroupPath: string | null = null;
|
||||
|
|
@ -646,8 +645,9 @@ export class GroupService {
|
|||
};
|
||||
|
||||
// Find connections from source repo
|
||||
const sourceGroupPath = Object.entries(config.repos)
|
||||
.find(([, rn]) => rn === sourceRepo!.name)?.[0] || sourceRepo.name;
|
||||
const sourceGroupPath =
|
||||
Object.entries(config.repos).find(([, rn]) => rn === sourceRepo!.name)?.[0] ||
|
||||
sourceRepo.name;
|
||||
await findConnections(sourceGroupPath, 1);
|
||||
|
||||
return {
|
||||
|
|
@ -680,7 +680,8 @@ export class GroupService {
|
|||
const force = Boolean(params.force);
|
||||
const skipSync = Boolean(params.skipSync);
|
||||
|
||||
if (!directory && repoPaths.length === 0) return { error: 'directory or repoPaths is required' };
|
||||
if (!directory && repoPaths.length === 0)
|
||||
return { error: 'directory or repoPaths is required' };
|
||||
|
||||
const repos: Record<string, string> = {};
|
||||
const packages: Record<string, Record<string, string>> = {};
|
||||
|
|
@ -704,7 +705,9 @@ export class GroupService {
|
|||
// Not indexed
|
||||
}
|
||||
if (!metaExists) {
|
||||
return { error: `Repo at ${resolvedPath} is not indexed. Run 'gitnexus analyze' there first.` };
|
||||
return {
|
||||
error: `Repo at ${resolvedPath} is not indexed. Run 'gitnexus analyze' there first.`,
|
||||
};
|
||||
}
|
||||
|
||||
const dirName = path.basename(resolvedPath);
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export async function syncGroup(config: GroupConfig, opts?: SyncOptions): Promis
|
|||
const openPoolIds: string[] = [];
|
||||
|
||||
// Build package map for code-dep extraction (auto-discover when config.packages is empty)
|
||||
let packageMap = new Map<string, string>();
|
||||
const packageMap = new Map<string, string>();
|
||||
if (config.detect.shared_libs) {
|
||||
const hasExplicitPackages =
|
||||
Object.keys(config.packages).length > 0 &&
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import {
|
|||
type RegistryEntry,
|
||||
} from '../../storage/repo-manager.js';
|
||||
import { GroupService, type GroupToolPort } from '../../core/group/service.js';
|
||||
import { resolveAtGroupMemberRepoPath } from '../../core/group/resolve-at-member.js';
|
||||
import { collectBestChunks } from '../../core/embeddings/types.js';
|
||||
import {
|
||||
rankExactEmbeddingRows,
|
||||
|
|
@ -231,6 +230,7 @@ export class LocalBackend {
|
|||
const port: GroupToolPort = {
|
||||
resolveRepo: (p) => this.resolveRepo(p),
|
||||
query: (r, p) => this.query(r as RepoHandle, p),
|
||||
impact: (r, p) => this.impact(r as RepoHandle, p as Parameters<LocalBackend['impact']>[1]),
|
||||
impactByUid: (id, uid, d, o) => this.impactByUid(id, uid, d, o),
|
||||
context: (r, p) => this.context(r as RepoHandle, p),
|
||||
};
|
||||
|
|
@ -639,15 +639,6 @@ export class LocalBackend {
|
|||
return this.handleGroupTool(method, params || {});
|
||||
}
|
||||
|
||||
const p = params && typeof params === 'object' ? (params as Record<string, unknown>) : {};
|
||||
if (
|
||||
(method === 'impact' || method === 'query' || method === 'context') &&
|
||||
typeof p.repo === 'string' &&
|
||||
p.repo.startsWith('@')
|
||||
) {
|
||||
return this.callToolAtGroupRepo(method, p);
|
||||
}
|
||||
|
||||
// Resolve repo from optional param (re-reads registry on miss)
|
||||
const repo = await this.resolveRepo((params as { repo?: string } | undefined)?.repo);
|
||||
|
||||
|
|
@ -3061,14 +3052,9 @@ export class LocalBackend {
|
|||
return this.getGroupService().groupQuery(params);
|
||||
}
|
||||
|
||||
private static formatGroupResourcePayload(raw: unknown): string {
|
||||
if (raw && typeof raw === 'object' && 'error' in raw) {
|
||||
const err = (raw as { error?: unknown }).error;
|
||||
if (typeof err === 'string' && err.length > 0) {
|
||||
return `error: ${err}`;
|
||||
}
|
||||
}
|
||||
return JSON.stringify(raw, null, 2);
|
||||
private async groupStatus(params: Record<string, unknown>): Promise<unknown> {
|
||||
await this.refreshRepos();
|
||||
return this.getGroupService().groupStatus(params);
|
||||
}
|
||||
|
||||
private async groupGraph(params: Record<string, unknown>): Promise<unknown> {
|
||||
|
|
|
|||
|
|
@ -238,10 +238,7 @@ export async function readResource(uri: string, backend: LocalBackend): Promise<
|
|||
}
|
||||
|
||||
if (parsed.kind === 'group') {
|
||||
if (parsed.resourceType === 'contracts') {
|
||||
return backend.readGroupContractsResource(parsed.groupName, parsed.contractsFilter);
|
||||
}
|
||||
return backend.readGroupStatusResource(parsed.groupName);
|
||||
return 'error: group resources are not supported in this build; use repo "@<groupName>" syntax instead';
|
||||
}
|
||||
|
||||
const repoName = parsed.repoName;
|
||||
|
|
|
|||
|
|
@ -563,7 +563,10 @@ WHEN TO USE: After group_sync, to explore how a symbol in one repo connects to s
|
|||
properties: {
|
||||
name: { type: 'string', description: 'Group name' },
|
||||
symbol: { type: 'string', description: 'Symbol name to search for' },
|
||||
repo: { type: 'string', description: 'Repo containing the symbol (optional, searches all if omitted)' },
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repo containing the symbol (optional, searches all if omitted)',
|
||||
},
|
||||
depth: { type: 'number', description: 'Cross-repo traversal depth (default: 1, max: 2)' },
|
||||
direction: {
|
||||
type: 'string',
|
||||
|
|
@ -582,7 +585,10 @@ WHEN TO USE: When a user wants to see a combined knowledge graph from multiple i
|
|||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
directory: { type: 'string', description: 'Parent directory containing repos (alternative to repoPaths)' },
|
||||
directory: {
|
||||
type: 'string',
|
||||
description: 'Parent directory containing repos (alternative to repoPaths)',
|
||||
},
|
||||
repoPaths: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
|
|
|
|||
|
|
@ -746,7 +746,12 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
|
|||
|
||||
const allNodes: GraphNode[] = [];
|
||||
const allRelationships: GraphRelationship[] = [];
|
||||
const repoSummaries: Array<{ name: string; groupPath: string; nodeCount: number; edgeCount: number }> = [];
|
||||
const repoSummaries: Array<{
|
||||
name: string;
|
||||
groupPath: string;
|
||||
nodeCount: number;
|
||||
edgeCount: number;
|
||||
}> = [];
|
||||
|
||||
// Load graph from each repo and namespace node IDs
|
||||
for (const [groupPath, registryName] of Object.entries(config.repos)) {
|
||||
|
|
@ -794,10 +799,8 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
|
|||
// Add cross-repo edges from contracts.json
|
||||
if (registry) {
|
||||
for (const link of registry.crossLinks) {
|
||||
const fromRepoName =
|
||||
config.repos[link.from.repo] || link.from.repo;
|
||||
const toRepoName =
|
||||
config.repos[link.to.repo] || link.to.repo;
|
||||
const fromRepoName = config.repos[link.from.repo] || link.from.repo;
|
||||
const toRepoName = config.repos[link.to.repo] || link.to.repo;
|
||||
|
||||
allRelationships.push({
|
||||
id: `cross::${link.contractId}::${fromRepoName}::${toRepoName}`,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import { GroupService, type GroupToolPort, type GroupRepoHandle } from '../../../src/core/group/service.js';
|
||||
import {
|
||||
GroupService,
|
||||
type GroupToolPort,
|
||||
type GroupRepoHandle,
|
||||
} from '../../../src/core/group/service.js';
|
||||
import { listGroups } from '../../../src/core/group/storage.js';
|
||||
|
||||
describe('Group auto-discover integration', () => {
|
||||
|
|
@ -97,7 +101,10 @@ console.log(formatDate(new Date()));`,
|
|||
}
|
||||
// Try matching by path
|
||||
for (const handle of Object.values(repoHandles)) {
|
||||
if (nameOrPath && (handle.repoPath === nameOrPath || handle.repoPath.includes(nameOrPath))) {
|
||||
if (
|
||||
nameOrPath &&
|
||||
(handle.repoPath === nameOrPath || handle.repoPath.includes(nameOrPath))
|
||||
) {
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +187,10 @@ console.log(formatDate(new Date()));`,
|
|||
// Add a non-indexed directory
|
||||
const nonIndexed = path.join(tmpDir, 'repos', 'not-indexed');
|
||||
fs.mkdirSync(nonIndexed, { recursive: true });
|
||||
fs.writeFileSync(path.join(nonIndexed, 'package.json'), JSON.stringify({ name: 'not-indexed' }));
|
||||
fs.writeFileSync(
|
||||
path.join(nonIndexed, 'package.json'),
|
||||
JSON.stringify({ name: 'not-indexed' }),
|
||||
);
|
||||
// No .gitnexus/meta.json
|
||||
|
||||
const service = new GroupService(makeMockPort());
|
||||
|
|
@ -225,10 +235,7 @@ console.log(formatDate(new Date()));`,
|
|||
it('returns an error when one of the explicit repoPaths is not indexed', async () => {
|
||||
const unindexed = path.join(tmpDir, 'repos', 'unindexed');
|
||||
fs.mkdirSync(unindexed, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(unindexed, 'package.json'),
|
||||
JSON.stringify({ name: 'unindexed' }),
|
||||
);
|
||||
fs.writeFileSync(path.join(unindexed, 'package.json'), JSON.stringify({ name: 'unindexed' }));
|
||||
|
||||
const service = new GroupService(makeMockPort());
|
||||
const result = (await service.groupDiscover({
|
||||
|
|
|
|||
|
|
@ -185,9 +185,7 @@ export function getVersion() { return VERSION; }`,
|
|||
}
|
||||
|
||||
// Verify from/to directions
|
||||
const formatDateLink = result.crossLinks.find((l) =>
|
||||
l.contractId.includes('formatDate'),
|
||||
);
|
||||
const formatDateLink = result.crossLinks.find((l) => l.contractId.includes('formatDate'));
|
||||
expect(formatDateLink).toBeDefined();
|
||||
expect(formatDateLink!.from.repo).toBe('apps/web'); // consumer
|
||||
expect(formatDateLink!.to.repo).toBe('libs/shared'); // provider
|
||||
|
|
|
|||
|
|
@ -133,7 +133,13 @@ describe('Group graph traversal integration', () => {
|
|||
repos: { 'libs/shared': 'shared-utils', 'apps/web': 'web-app' },
|
||||
links: [],
|
||||
packages: {},
|
||||
detect: { http: false, grpc: false, topics: false, shared_libs: true, embedding_fallback: false },
|
||||
detect: {
|
||||
http: false,
|
||||
grpc: false,
|
||||
topics: false,
|
||||
shared_libs: true,
|
||||
embedding_fallback: false,
|
||||
},
|
||||
matching: { bm25_threshold: 0.7, embedding_threshold: 0.65, max_candidates_per_step: 3 },
|
||||
};
|
||||
fs.writeFileSync(path.join(groupDir, 'group.yaml'), yaml.dump(config), 'utf-8');
|
||||
|
|
@ -154,19 +160,27 @@ describe('Group graph traversal integration', () => {
|
|||
return {
|
||||
resolveRepo: async (nameOrPath?: string): Promise<GroupRepoHandle> => {
|
||||
if (nameOrPath === 'shared-utils') {
|
||||
return { id: 'shared-utils', name: 'shared-utils', repoPath: '/mock/shared', storagePath: '/mock/shared/.gitnexus' };
|
||||
return {
|
||||
id: 'shared-utils',
|
||||
name: 'shared-utils',
|
||||
repoPath: '/mock/shared',
|
||||
storagePath: '/mock/shared/.gitnexus',
|
||||
};
|
||||
}
|
||||
if (nameOrPath === 'web-app') {
|
||||
return { id: 'web-app', name: 'web-app', repoPath: '/mock/web', storagePath: '/mock/web/.gitnexus' };
|
||||
return {
|
||||
id: 'web-app',
|
||||
name: 'web-app',
|
||||
repoPath: '/mock/web',
|
||||
storagePath: '/mock/web/.gitnexus',
|
||||
};
|
||||
}
|
||||
throw new Error(`Repo not found: ${nameOrPath}`);
|
||||
},
|
||||
query: async (_repo, params) => {
|
||||
const queryText = (params as { query: string }).query;
|
||||
return {
|
||||
processes: [
|
||||
{ name: `process-${queryText}`, summary: `Mock process for ${queryText}` },
|
||||
],
|
||||
processes: [{ name: `process-${queryText}`, summary: `Mock process for ${queryText}` }],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -195,9 +209,7 @@ describe('Group graph traversal integration', () => {
|
|||
|
||||
// shared-utils is the provider; apps/web is the consumer
|
||||
// Direction from shared-utils perspective: incoming (apps/web imports from us)
|
||||
const conn = result.crossConnections.find((c) =>
|
||||
c.contractId.includes('formatDate'),
|
||||
);
|
||||
const conn = result.crossConnections.find((c) => c.contractId.includes('formatDate'));
|
||||
expect(conn).toBeDefined();
|
||||
expect(conn!.contractType).toBe('lib');
|
||||
expect(conn!.confidence).toBe(1.0);
|
||||
|
|
@ -269,10 +281,7 @@ describe('Group graph traversal integration', () => {
|
|||
});
|
||||
|
||||
describe('depth traversal', () => {
|
||||
async function writeFixture(
|
||||
repos: Record<string, string>,
|
||||
registry: ContractRegistry,
|
||||
) {
|
||||
async function writeFixture(repos: Record<string, string>, registry: ContractRegistry) {
|
||||
const { createRequire } = await import('node:module');
|
||||
const _require = createRequire(import.meta.url);
|
||||
const yaml = _require('js-yaml') as typeof import('js-yaml');
|
||||
|
|
@ -303,9 +312,7 @@ describe('Group graph traversal integration', () => {
|
|||
return handles[name];
|
||||
},
|
||||
query: async (_repo, params) => ({
|
||||
processes: [
|
||||
{ name: `proc-${(params as { query: string }).query}`, summary: 'mock' },
|
||||
],
|
||||
processes: [{ name: `proc-${(params as { query: string }).query}`, summary: 'mock' }],
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
@ -364,9 +371,24 @@ describe('Group graph traversal integration', () => {
|
|||
};
|
||||
|
||||
const chainHandles: Record<string, GroupRepoHandle> = {
|
||||
'web-app': { id: 'web-app', name: 'web-app', repoPath: '/mock/web', storagePath: '/mock/web/.gitnexus' },
|
||||
'shared-utils': { id: 'shared-utils', name: 'shared-utils', repoPath: '/mock/shared', storagePath: '/mock/shared/.gitnexus' },
|
||||
'deep-lib': { id: 'deep-lib', name: 'deep-lib', repoPath: '/mock/deep', storagePath: '/mock/deep/.gitnexus' },
|
||||
'web-app': {
|
||||
id: 'web-app',
|
||||
name: 'web-app',
|
||||
repoPath: '/mock/web',
|
||||
storagePath: '/mock/web/.gitnexus',
|
||||
},
|
||||
'shared-utils': {
|
||||
id: 'shared-utils',
|
||||
name: 'shared-utils',
|
||||
repoPath: '/mock/shared',
|
||||
storagePath: '/mock/shared/.gitnexus',
|
||||
},
|
||||
'deep-lib': {
|
||||
id: 'deep-lib',
|
||||
name: 'deep-lib',
|
||||
repoPath: '/mock/deep',
|
||||
storagePath: '/mock/deep/.gitnexus',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -431,7 +453,11 @@ describe('Group graph traversal integration', () => {
|
|||
contracts: [],
|
||||
crossLinks: [
|
||||
{
|
||||
from: { repo: 'apps/a', symbolUid: 'uid-a', symbolRef: { filePath: 'a.ts', name: 'a' } },
|
||||
from: {
|
||||
repo: 'apps/a',
|
||||
symbolUid: 'uid-a',
|
||||
symbolRef: { filePath: 'a.ts', name: 'a' },
|
||||
},
|
||||
to: { repo: 'apps/b', symbolUid: 'uid-b', symbolRef: { filePath: 'b.ts', name: 'b' } },
|
||||
type: 'lib',
|
||||
contractId: 'lib::a-to-b',
|
||||
|
|
@ -439,7 +465,11 @@ describe('Group graph traversal integration', () => {
|
|||
confidence: 1.0,
|
||||
},
|
||||
{
|
||||
from: { repo: 'apps/b', symbolUid: 'uid-b', symbolRef: { filePath: 'b.ts', name: 'b' } },
|
||||
from: {
|
||||
repo: 'apps/b',
|
||||
symbolUid: 'uid-b',
|
||||
symbolRef: { filePath: 'b.ts', name: 'b' },
|
||||
},
|
||||
to: { repo: 'apps/a', symbolUid: 'uid-a', symbolRef: { filePath: 'a.ts', name: 'a' } },
|
||||
type: 'lib',
|
||||
contractId: 'lib::b-to-a',
|
||||
|
|
@ -452,8 +482,18 @@ describe('Group graph traversal integration', () => {
|
|||
await writeFixture({ 'apps/a': 'repo-a', 'apps/b': 'repo-b' }, cycleRegistry);
|
||||
|
||||
const cyclePort = makeHandlePort({
|
||||
'repo-a': { id: 'repo-a', name: 'repo-a', repoPath: '/mock/a', storagePath: '/mock/a/.gitnexus' },
|
||||
'repo-b': { id: 'repo-b', name: 'repo-b', repoPath: '/mock/b', storagePath: '/mock/b/.gitnexus' },
|
||||
'repo-a': {
|
||||
id: 'repo-a',
|
||||
name: 'repo-a',
|
||||
repoPath: '/mock/a',
|
||||
storagePath: '/mock/a/.gitnexus',
|
||||
},
|
||||
'repo-b': {
|
||||
id: 'repo-b',
|
||||
name: 'repo-b',
|
||||
repoPath: '/mock/b',
|
||||
storagePath: '/mock/b/.gitnexus',
|
||||
},
|
||||
});
|
||||
|
||||
const service = new GroupService(cyclePort);
|
||||
|
|
|
|||
|
|
@ -82,9 +82,7 @@ describe('group CLI', () => {
|
|||
await runCommand(['group', 'create', 'team-a']);
|
||||
|
||||
expect(mockCreateGroupDir).toHaveBeenCalledWith('/home/.gitnexus', 'team-a', undefined);
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Created group "team-a"'),
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Created group "team-a"'));
|
||||
});
|
||||
|
||||
it('forwards --force to createGroupDir', async () => {
|
||||
|
|
@ -125,20 +123,24 @@ describe('group CLI', () => {
|
|||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Discovering indexed repos'));
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Repos (2)'));
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('shared-utils (@test/shared)'));
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('5 contracts, 3 cross-links'),
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('5 contracts, 3 cross-links'));
|
||||
expect(mockBackend.dispose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('prints JSON when --json is set', async () => {
|
||||
const payload = { group: 'workspace', groupDir: '/g', repoCount: 0, repos: [], packageMappings: {} };
|
||||
const payload = {
|
||||
group: 'workspace',
|
||||
groupDir: '/g',
|
||||
repoCount: 0,
|
||||
repos: [],
|
||||
packageMappings: {},
|
||||
};
|
||||
mockGroupService.groupDiscover.mockResolvedValueOnce(payload);
|
||||
|
||||
await runCommand(['group', 'auto-discover', '/repos', '--json']);
|
||||
|
||||
const jsonCall = logSpy.mock.calls.find((c) =>
|
||||
typeof c[0] === 'string' && c[0].trim().startsWith('{'),
|
||||
const jsonCall = logSpy.mock.calls.find(
|
||||
(c) => typeof c[0] === 'string' && c[0].trim().startsWith('{'),
|
||||
);
|
||||
expect(jsonCall).toBeDefined();
|
||||
expect(JSON.parse(jsonCall![0] as string)).toEqual(payload);
|
||||
|
|
@ -189,7 +191,13 @@ describe('group CLI', () => {
|
|||
repos: {},
|
||||
links: [],
|
||||
packages: {},
|
||||
detect: { http: false, grpc: false, topics: false, shared_libs: false, embedding_fallback: false },
|
||||
detect: {
|
||||
http: false,
|
||||
grpc: false,
|
||||
topics: false,
|
||||
shared_libs: false,
|
||||
embedding_fallback: false,
|
||||
},
|
||||
matching: { bm25_threshold: 0.7, embedding_threshold: 0.65, max_candidates_per_step: 3 },
|
||||
});
|
||||
|
||||
|
|
@ -214,7 +222,13 @@ describe('group CLI', () => {
|
|||
repos: { 'apps/web': 'web-app', 'libs/shared': 'shared-utils' },
|
||||
links: [],
|
||||
packages: {},
|
||||
detect: { http: false, grpc: false, topics: false, shared_libs: false, embedding_fallback: false },
|
||||
detect: {
|
||||
http: false,
|
||||
grpc: false,
|
||||
topics: false,
|
||||
shared_libs: false,
|
||||
embedding_fallback: false,
|
||||
},
|
||||
matching: { bm25_threshold: 0.7, embedding_threshold: 0.65, max_candidates_per_step: 3 },
|
||||
});
|
||||
|
||||
|
|
@ -234,15 +248,19 @@ describe('group CLI', () => {
|
|||
repos: { 'libs/shared': 'shared-utils' },
|
||||
links: [],
|
||||
packages: {},
|
||||
detect: { http: false, grpc: false, topics: false, shared_libs: false, embedding_fallback: false },
|
||||
detect: {
|
||||
http: false,
|
||||
grpc: false,
|
||||
topics: false,
|
||||
shared_libs: false,
|
||||
embedding_fallback: false,
|
||||
},
|
||||
matching: { bm25_threshold: 0.7, embedding_threshold: 0.65, max_candidates_per_step: 3 },
|
||||
});
|
||||
|
||||
await runCommand(['group', 'remove', 'team-a', 'apps/web']);
|
||||
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('not found in group "team-a"'),
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(expect.stringContaining('not found in group "team-a"'));
|
||||
expect(process.exitCode).toBe(1);
|
||||
expect(mockFsWriteFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -264,9 +282,7 @@ describe('group CLI', () => {
|
|||
|
||||
await runCommand(['group', 'list']);
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('No groups configured'),
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('No groups configured'));
|
||||
});
|
||||
|
||||
it('with name: prints repos and manifest links', async () => {
|
||||
|
|
@ -276,10 +292,22 @@ describe('group CLI', () => {
|
|||
description: 'A team group',
|
||||
repos: { 'apps/web': 'web-app' },
|
||||
links: [
|
||||
{ from: 'apps/web', to: 'services/api', type: 'http', contract: 'GET::/x', role: 'consumer' },
|
||||
{
|
||||
from: 'apps/web',
|
||||
to: 'services/api',
|
||||
type: 'http',
|
||||
contract: 'GET::/x',
|
||||
role: 'consumer',
|
||||
},
|
||||
],
|
||||
packages: {},
|
||||
detect: { http: false, grpc: false, topics: false, shared_libs: false, embedding_fallback: false },
|
||||
detect: {
|
||||
http: false,
|
||||
grpc: false,
|
||||
topics: false,
|
||||
shared_libs: false,
|
||||
embedding_fallback: false,
|
||||
},
|
||||
matching: { bm25_threshold: 0.7, embedding_threshold: 0.65, max_candidates_per_step: 3 },
|
||||
});
|
||||
|
||||
|
|
@ -306,8 +334,18 @@ describe('group CLI', () => {
|
|||
});
|
||||
mockGroupService.groupStatus.mockResolvedValueOnce({
|
||||
repos: {
|
||||
'apps/web': { indexStale: false, contractsStale: false, missing: false, commitsBehind: 0 },
|
||||
'libs/shared': { indexStale: true, contractsStale: true, missing: false, commitsBehind: 3 },
|
||||
'apps/web': {
|
||||
indexStale: false,
|
||||
contractsStale: false,
|
||||
missing: false,
|
||||
commitsBehind: 0,
|
||||
},
|
||||
'libs/shared': {
|
||||
indexStale: true,
|
||||
contractsStale: true,
|
||||
missing: false,
|
||||
commitsBehind: 3,
|
||||
},
|
||||
'legacy/old': { indexStale: false, contractsStale: false, missing: true },
|
||||
},
|
||||
missingRepos: ['legacy/old'],
|
||||
|
|
@ -345,16 +383,18 @@ describe('group CLI', () => {
|
|||
repos: { 'apps/web': 'web-app' },
|
||||
links: [],
|
||||
packages: {},
|
||||
detect: { http: false, grpc: false, topics: false, shared_libs: false, embedding_fallback: false },
|
||||
detect: {
|
||||
http: false,
|
||||
grpc: false,
|
||||
topics: false,
|
||||
shared_libs: false,
|
||||
embedding_fallback: false,
|
||||
},
|
||||
matching: { bm25_threshold: 0.7, embedding_threshold: 0.65, max_candidates_per_step: 3 },
|
||||
});
|
||||
mockSyncGroup.mockResolvedValueOnce({
|
||||
contracts: [{ id: 1 }, { id: 2 }],
|
||||
crossLinks: [
|
||||
{ matchType: 'exact' },
|
||||
{ matchType: 'exact' },
|
||||
{ matchType: 'manifest' },
|
||||
],
|
||||
crossLinks: [{ matchType: 'exact' }, { matchType: 'exact' }, { matchType: 'manifest' }],
|
||||
unmatched: [{ id: 3 }],
|
||||
missingRepos: [],
|
||||
repoSnapshots: {},
|
||||
|
|
@ -381,7 +421,13 @@ describe('group CLI', () => {
|
|||
repos: {},
|
||||
links: [],
|
||||
packages: {},
|
||||
detect: { http: false, grpc: false, topics: false, shared_libs: false, embedding_fallback: false },
|
||||
detect: {
|
||||
http: false,
|
||||
grpc: false,
|
||||
topics: false,
|
||||
shared_libs: false,
|
||||
embedding_fallback: false,
|
||||
},
|
||||
matching: { bm25_threshold: 0.7, embedding_threshold: 0.65, max_candidates_per_step: 3 },
|
||||
});
|
||||
const result = {
|
||||
|
|
@ -395,8 +441,8 @@ describe('group CLI', () => {
|
|||
|
||||
await runCommand(['group', 'sync', 'team-a', '--json']);
|
||||
|
||||
const jsonCall = logSpy.mock.calls.find((c) =>
|
||||
typeof c[0] === 'string' && c[0].trim().startsWith('{'),
|
||||
const jsonCall = logSpy.mock.calls.find(
|
||||
(c) => typeof c[0] === 'string' && c[0].trim().startsWith('{'),
|
||||
);
|
||||
expect(jsonCall).toBeDefined();
|
||||
});
|
||||
|
|
@ -427,10 +473,16 @@ describe('group CLI', () => {
|
|||
});
|
||||
|
||||
await runCommand([
|
||||
'group', 'graph', 'team-a', 'mySymbol',
|
||||
'--repo', 'web-app',
|
||||
'--depth', '2',
|
||||
'--direction', 'both',
|
||||
'group',
|
||||
'graph',
|
||||
'team-a',
|
||||
'mySymbol',
|
||||
'--repo',
|
||||
'web-app',
|
||||
'--depth',
|
||||
'2',
|
||||
'--direction',
|
||||
'both',
|
||||
]);
|
||||
|
||||
expect(mockGroupService.groupGraph).toHaveBeenCalledWith({
|
||||
|
|
@ -493,7 +545,10 @@ describe('group CLI', () => {
|
|||
{ summary: 'flow A', _repo: 'apps/web', _rrf_score: 0.0164 },
|
||||
{ name: 'flow B', _repo: 'libs/shared', _rrf_score: 0.0161 },
|
||||
],
|
||||
per_repo: [{ repo: 'apps/web', count: 1 }, { repo: 'libs/shared', count: 1 }],
|
||||
per_repo: [
|
||||
{ repo: 'apps/web', count: 1 },
|
||||
{ repo: 'libs/shared', count: 1 },
|
||||
],
|
||||
});
|
||||
|
||||
await runCommand(['group', 'query', 'team-a', 'auth', '--limit', '10', '--subgroup', 'apps']);
|
||||
|
|
@ -513,9 +568,7 @@ describe('group CLI', () => {
|
|||
|
||||
await runCommand(['group', 'query', 'team-a', 'nothing']);
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('No matching execution flows'),
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('No matching execution flows'));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -563,9 +616,12 @@ describe('group CLI', () => {
|
|||
mockGroupService.groupContracts.mockResolvedValueOnce({ contracts: [], crossLinks: [] });
|
||||
|
||||
await runCommand([
|
||||
'group', 'contracts', 'team-a',
|
||||
'group',
|
||||
'contracts',
|
||||
'team-a',
|
||||
'--unmatched',
|
||||
'--repo', 'apps/web',
|
||||
'--repo',
|
||||
'apps/web',
|
||||
'--json',
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,9 +47,7 @@ describe('statusCommand', () => {
|
|||
const { statusCommand } = await import('../../src/cli/status.js');
|
||||
await statusCommand();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('stale KuzuDB index'),
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('stale KuzuDB index'));
|
||||
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('rebuilds the index'));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -79,9 +79,7 @@ describe('CLI tool commands', () => {
|
|||
it('errors when both name and uid are empty', async () => {
|
||||
const { contextCommand } = await import('../../src/cli/tool.js');
|
||||
await expect(contextCommand('')).rejects.toThrow('__exit__');
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('gitnexus context'),
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(expect.stringContaining('gitnexus context'));
|
||||
});
|
||||
|
||||
it('accepts uid without a name', async () => {
|
||||
|
|
@ -117,9 +115,7 @@ describe('CLI tool commands', () => {
|
|||
it('errors when target is empty', async () => {
|
||||
const { impactCommand } = await import('../../src/cli/tool.js');
|
||||
await expect(impactCommand('')).rejects.toThrow('__exit__');
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('gitnexus impact'),
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(expect.stringContaining('gitnexus impact'));
|
||||
});
|
||||
|
||||
it('defaults direction to upstream and passes depth as integer', async () => {
|
||||
|
|
@ -168,9 +164,7 @@ describe('CLI tool commands', () => {
|
|||
it('errors when query is empty', async () => {
|
||||
const { cypherCommand } = await import('../../src/cli/tool.js');
|
||||
await expect(cypherCommand('')).rejects.toThrow('__exit__');
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('gitnexus cypher'),
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(expect.stringContaining('gitnexus cypher'));
|
||||
});
|
||||
|
||||
it('forwards --repo option', async () => {
|
||||
|
|
@ -192,9 +186,7 @@ describe('CLI tool commands', () => {
|
|||
const { queryCommand } = await import('../../src/cli/tool.js');
|
||||
|
||||
await expect(queryCommand('x')).rejects.toThrow('__exit__');
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('No indexed repositories found'),
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(expect.stringContaining('No indexed repositories found'));
|
||||
});
|
||||
|
||||
it('reuses the same backend across calls', async () => {
|
||||
|
|
|
|||
|
|
@ -220,10 +220,7 @@ import { Logger } from '@acme/shared/logging';
|
|||
|
||||
const results = await scanRepoForImports(tmpDir, targets);
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results.map((r) => r.filePath).sort()).toEqual([
|
||||
'src/app.ts',
|
||||
'src/utils/helper.ts',
|
||||
]);
|
||||
expect(results.map((r) => r.filePath).sort()).toEqual(['src/app.ts', 'src/utils/helper.ts']);
|
||||
});
|
||||
|
||||
it('ignores node_modules', async () => {
|
||||
|
|
|
|||
9
package-lock.json
generated
9
package-lock.json
generated
|
|
@ -5,6 +5,9 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"name": "gitnexus-monorepo",
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
|
|
@ -1927,6 +1930,12 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
|
|
|||
|
|
@ -26,5 +26,8 @@
|
|||
"prettier --write"
|
||||
],
|
||||
"*.{js,jsx,mjs,json,css,yml,yaml}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue