From ddf702b4af11e79a5a41038958222251aa9b3111 Mon Sep 17 00:00:00 2001 From: ivkond Date: Sun, 19 Apr 2026 14:10:17 +0300 Subject: [PATCH] style(#794): apply prettier --write to files added/changed by this PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the `quality / format` CI job. `npx prettier --check .` reported 29 files repo-wide; this commit reformats only the 9 files that this PR authored/modified: - gitnexus/src/cli/group.ts - gitnexus/src/core/group/{cross-impact,service}.ts - gitnexus/src/mcp/{tools,resources}.ts - gitnexus/src/mcp/local/local-backend.ts - gitnexus/test/unit/group/{cross-impact,service}.test.ts - gitnexus/test/unit/mcp/group-repo-routing.test.ts The remaining 20 prettier offenders are pre-existing on `main` (`.claude-plugin/marketplace.json`, `.mcp.json`, several `gitnexus-claude-plugin/**/*.json`, `gitnexus-cursor-integration/hooks/hooks.json`, `gitnexus/test/unit/{ast-cache,embedder,lazy-action,pipeline-exports}.test.ts`, `gitnexus/test/utils/hook-test-helpers.ts`, `gitnexus/tsconfig.test.json`, `gitnexus-web/tsconfig.node.json`, `.github/FUNDING.yml`) and out of scope for this PR. Re-verified after format: - `npx tsc --noEmit` (in gitnexus/) — clean. - `npx vitest run --pool=forks` over the #794 verification block — 9 files / 111 tests passed. Refs #794 Made-with: Cursor --- gitnexus/src/cli/group.ts | 5 +- gitnexus/src/core/group/cross-impact.ts | 70 +++++++++---------- gitnexus/src/core/group/service.ts | 36 ++++++---- gitnexus/src/mcp/local/local-backend.ts | 12 ++-- gitnexus/src/mcp/resources.ts | 14 +++- gitnexus/src/mcp/tools.ts | 6 +- gitnexus/test/unit/group/cross-impact.test.ts | 7 +- gitnexus/test/unit/group/service.test.ts | 5 +- .../test/unit/mcp/group-repo-routing.test.ts | 39 +++++++---- 9 files changed, 118 insertions(+), 76 deletions(-) diff --git a/gitnexus/src/cli/group.ts b/gitnexus/src/cli/group.ts index d9929cc1f..eb0dffc3d 100644 --- a/gitnexus/src/cli/group.ts +++ b/gitnexus/src/cli/group.ts @@ -194,7 +194,10 @@ export function registerGroupCommands(program: Command): void { ) .option('--direction ', 'upstream or downstream', 'upstream') .option('--service ', 'Optional monorepo service directory prefix (path filter)') - .option('--subgroup ', 'Optional prefix limiting which group repos participate in cross fan-out') + .option( + '--subgroup ', + 'Optional prefix limiting which group repos participate in cross fan-out', + ) .option('--max-depth ', 'Max graph traversal depth') .option('--cross-depth ', 'Cross-repository hop depth') .option('--min-confidence ', 'Minimum relation confidence (0–1)') diff --git a/gitnexus/src/core/group/cross-impact.ts b/gitnexus/src/core/group/cross-impact.ts index 6b549422d..fe9db5cc8 100644 --- a/gitnexus/src/core/group/cross-impact.ts +++ b/gitnexus/src/core/group/cross-impact.ts @@ -18,12 +18,7 @@ import type { GroupRepoHandle, GroupToolPort } from './service.js'; import { loadGroupConfig } from './config-parser.js'; import { fileMatchesServicePrefix, normalizeServicePrefix } from './group-path-utils.js'; import { getGroupDir } from './storage.js'; -import { - closeBridgeDb, - openBridgeDbReadOnly, - queryBridge, - readBridgeMeta, -} from './bridge-db.js'; +import { closeBridgeDb, openBridgeDbReadOnly, queryBridge, readBridgeMeta } from './bridge-db.js'; import { BRIDGE_SCHEMA_VERSION } from './bridge-schema.js'; /** Cross-boundary hops beyond this value are clamped (multi-hop reserved for future work). */ @@ -98,29 +93,36 @@ function clampCrossDepth(raw: unknown): { depth: number; warning?: string } { return { depth: d }; } -export function validateGroupImpactParams(params: Record): { - ok: true; - name: string; - repoPath: string; - target: string; - direction: 'upstream' | 'downstream'; - maxDepth: number; - crossDepth: number; - crossDepthWarning?: string; - relationTypes?: string[]; - includeTests: boolean; - minConfidence: number; - service?: string; - subgroup?: string; - timeoutMs: number; -} | { ok: false; error: string } { +export function validateGroupImpactParams(params: Record): + | { + ok: true; + name: string; + repoPath: string; + target: string; + direction: 'upstream' | 'downstream'; + maxDepth: number; + crossDepth: number; + crossDepthWarning?: string; + relationTypes?: string[]; + includeTests: boolean; + minConfidence: number; + service?: string; + subgroup?: string; + timeoutMs: number; + } + | { ok: false; error: string } { const name = String(params.name ?? '').trim(); const repoPath = String(params.repo ?? '').trim(); const target = String(params.target ?? '').trim(); if (!name) return { ok: false, error: 'name is required' }; - if (!repoPath) return { ok: false, error: 'repo is required (group repo path, e.g. app/backend)' }; + if (!repoPath) + return { ok: false, error: 'repo is required (group repo path, e.g. app/backend)' }; if (!target) return { ok: false, error: 'target is required' }; - if (params.service !== undefined && params.service !== null && String(params.service).trim() === '') { + if ( + params.service !== undefined && + params.service !== null && + String(params.service).trim() === '' + ) { return { ok: false, error: 'service must not be an empty string' }; } const direction = parseDirection(params.direction); @@ -292,7 +294,8 @@ function rowToNeighbor(r: Record): BridgeNeighborRow | null { return { neighborRepo, neighborUid, - neighborFilePath: r.neighborFilePath !== undefined ? String(r.neighborFilePath) : String(r[2] ?? ''), + neighborFilePath: + r.neighborFilePath !== undefined ? String(r.neighborFilePath) : String(r[2] ?? ''), matchType: String(r.matchType ?? r[3] ?? 'exact'), confidence: Number(r.confidence ?? r[4] ?? 0), contractId: String(r.contractId ?? r[5] ?? ''), @@ -500,17 +503,12 @@ export async function runGroupImpact( continue; } - const fan = await deps.port.impactByUid( - neighborHandle.id, - n.neighborUid, - direction, - { - maxDepth, - relationTypes: relationTypes ?? [], - minConfidence, - includeTests, - }, - ); + const fan = await deps.port.impactByUid(neighborHandle.id, n.neighborUid, direction, { + maxDepth, + relationTypes: relationTypes ?? [], + minConfidence, + includeTests, + }); if (fan == null) { truncatedRepos.push(n.neighborRepo); continue; diff --git a/gitnexus/src/core/group/service.ts b/gitnexus/src/core/group/service.ts index 066d61d2e..3111849f3 100644 --- a/gitnexus/src/core/group/service.ts +++ b/gitnexus/src/core/group/service.ts @@ -8,12 +8,7 @@ import path from 'node:path'; import { checkStaleness } from '../git-staleness.js'; import { loadGroupConfig } from './config-parser.js'; import { fileMatchesServicePrefix, normalizeServicePrefix } from './group-path-utils.js'; -import { - getDefaultGitnexusDir, - getGroupDir, - listGroups, - readContractRegistry, -} from './storage.js'; +import { getDefaultGitnexusDir, getGroupDir, listGroups, readContractRegistry } from './storage.js'; import { syncGroup } from './sync.js'; import type { ContractRegistry, @@ -109,11 +104,17 @@ function isStoredContract(raw: unknown): raw is StoredContract { } function filterQueryByServicePrefix( - queryResult: { processes?: Array>; process_symbols?: Array> }, + queryResult: { + processes?: Array>; + process_symbols?: Array>; + }, servicePrefix: string, ): { processes: Array>; process_symbols: Array> } { const symbols = (queryResult.process_symbols || []).filter((s) => - fileMatchesServicePrefix(typeof s.filePath === 'string' ? s.filePath : undefined, servicePrefix), + fileMatchesServicePrefix( + typeof s.filePath === 'string' ? s.filePath : undefined, + servicePrefix, + ), ); const allowed = new Set( symbols.map((s) => String((s as { process_id?: string }).process_id ?? '')).filter(Boolean), @@ -135,8 +136,7 @@ function isCrossLink(raw: unknown): raw is CrossLink { async function loadContractRegistryResilient( groupDir: string, ): Promise< - | { ok: true; registry: ContractRegistry; skippedCorrupt: number } - | { ok: false; error: string } + { ok: true; registry: ContractRegistry; skippedCorrupt: number } | { ok: false; error: string } > { const filePath = path.join(groupDir, 'contracts.json'); let raw: string; @@ -206,9 +206,7 @@ async function loadContractRegistryResilient( base.repoSnapshots && typeof base.repoSnapshots === 'object' && base.repoSnapshots !== null ? (base.repoSnapshots as Record) : {}, - missingRepos: Array.isArray(base.missingRepos) - ? (base.missingRepos as string[]) - : [], + missingRepos: Array.isArray(base.missingRepos) ? (base.missingRepos as string[]) : [], contracts, crossLinks, }; @@ -295,7 +293,11 @@ export class GroupService { const uid = typeof params.uid === 'string' ? params.uid.trim() : undefined; const file_path = typeof params.file_path === 'string' ? params.file_path : undefined; const include_content = Boolean(params.include_content); - if (params.service !== undefined && params.service !== null && String(params.service).trim() === '') { + if ( + params.service !== undefined && + params.service !== null && + String(params.service).trim() === '' + ) { return { group: name || '', error: 'service must not be an empty string', results: [] }; } const servicePrefix = normalizeServicePrefix(params.service); @@ -367,7 +369,11 @@ export class GroupService { const name = String(params.name ?? '').trim(); const queryText = String(params.query ?? '').trim(); if (!name || !queryText) return { error: 'name and query are required' }; - if (params.service !== undefined && params.service !== null && String(params.service).trim() === '') { + if ( + params.service !== undefined && + params.service !== null && + String(params.service).trim() === '' + ) { return { error: 'service must not be an empty string' }; } const servicePrefix = normalizeServicePrefix(params.service); diff --git a/gitnexus/src/mcp/local/local-backend.ts b/gitnexus/src/mcp/local/local-backend.ts index c37ebf4a9..129cda678 100644 --- a/gitnexus/src/mcp/local/local-backend.ts +++ b/gitnexus/src/mcp/local/local-backend.ts @@ -2892,7 +2892,8 @@ export class LocalBackend { if (params.relationTypes !== undefined) impactArgs.relationTypes = params.relationTypes; if (params.includeTests !== undefined) impactArgs.includeTests = params.includeTests; if (params.minConfidence !== undefined) impactArgs.minConfidence = params.minConfidence; - if (params.service !== undefined && params.service !== null) impactArgs.service = params.service; + if (params.service !== undefined && params.service !== null) + impactArgs.service = params.service; if (typeof params.subgroup === 'string') impactArgs.subgroup = params.subgroup; if (params.timeoutMs !== undefined) impactArgs.timeoutMs = params.timeoutMs; if (params.timeout !== undefined) impactArgs.timeout = params.timeout; @@ -2908,7 +2909,8 @@ export class LocalBackend { if (typeof params.limit === 'number') queryArgs.limit = params.limit; if (typeof params.max_symbols === 'number') queryArgs.max_symbols = params.max_symbols; if (params.include_content !== undefined) queryArgs.include_content = params.include_content; - if (params.service !== undefined && params.service !== null) queryArgs.service = params.service; + if (params.service !== undefined && params.service !== null) + queryArgs.service = params.service; if (memberRest !== undefined) { queryArgs.subgroup = memberRest; queryArgs.subgroupExact = true; @@ -2928,8 +2930,10 @@ export class LocalBackend { }; if (typeof params.uid === 'string') contextArgs.uid = params.uid; if (typeof params.file_path === 'string') contextArgs.file_path = params.file_path; - if (params.include_content !== undefined) contextArgs.include_content = params.include_content; - if (params.service !== undefined && params.service !== null) contextArgs.service = params.service; + if (params.include_content !== undefined) + contextArgs.include_content = params.include_content; + if (params.service !== undefined && params.service !== null) + contextArgs.service = params.service; if (memberRest !== undefined) { contextArgs.subgroup = memberRest; contextArgs.subgroupExact = true; diff --git a/gitnexus/src/mcp/resources.ts b/gitnexus/src/mcp/resources.ts index 6fbab99ce..88e7a99cc 100644 --- a/gitnexus/src/mcp/resources.ts +++ b/gitnexus/src/mcp/resources.ts @@ -153,7 +153,10 @@ export function parseResourceUri(uri: string): ParsedGitnexusResource { } if (u.hostname === 'group') { - const segments = u.pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean); + const segments = u.pathname + .replace(/^\/+|\/+$/g, '') + .split('/') + .filter(Boolean); if (segments.length < 2) { throw new Error( `Invalid group resource URI (expected gitnexus://group/{name}/contracts or .../status): ${uri}`, @@ -186,7 +189,10 @@ export function parseResourceUri(uri: string): ParsedGitnexusResource { } if (u.hostname === 'repo') { - const segments = u.pathname.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean); + const segments = u.pathname + .replace(/^\/+|\/+$/g, '') + .split('/') + .filter(Boolean); if (segments.length < 2) { throw new Error(`Unknown resource URI: ${uri}`); } @@ -342,7 +348,9 @@ async function getContextResource(backend: LocalBackend, repoName?: string): Pro lines.push(` - gitnexus://repo/${context.projectName}/processes: All execution flows`); lines.push(` - gitnexus://repo/${context.projectName}/cluster/{name}: Module details`); lines.push(` - gitnexus://repo/${context.projectName}/process/{name}: Process trace`); - lines.push(' - gitnexus://group/{name}/contracts: Group contract registry (optional ?type=&repo=&unmatchedOnly=)'); + lines.push( + ' - gitnexus://group/{name}/contracts: Group contract registry (optional ?type=&repo=&unmatchedOnly=)', + ); lines.push(' - gitnexus://group/{name}/status: Group index / contract staleness'); return lines.join('\n'); diff --git a/gitnexus/src/mcp/tools.ts b/gitnexus/src/mcp/tools.ts index f5fc31f47..491c24557 100644 --- a/gitnexus/src/mcp/tools.ts +++ b/gitnexus/src/mcp/tools.ts @@ -357,7 +357,8 @@ SERVICE: optional monorepo path prefix (case-sensitive path segments). When "rep includeTests: { type: 'boolean', description: 'Include test files (default: false)' }, minConfidence: { type: 'number', - description: 'Minimum edge confidence 0–1 (default: 0 when omitted; server clamps to 0–1)', + description: + 'Minimum edge confidence 0–1 (default: 0 when omitted; server clamps to 0–1)', default: 0, minimum: 0, maximum: 1, @@ -380,7 +381,8 @@ SERVICE: optional monorepo path prefix (case-sensitive path segments). When "rep }, timeoutMs: { type: 'number', - description: 'Wall-clock budget in milliseconds for the Phase-1 local impact leg (default 30000)', + description: + 'Wall-clock budget in milliseconds for the Phase-1 local impact leg (default 30000)', minimum: 1, maximum: 3600000, }, diff --git a/gitnexus/test/unit/group/cross-impact.test.ts b/gitnexus/test/unit/group/cross-impact.test.ts index 869130357..3d78ff1cf 100644 --- a/gitnexus/test/unit/group/cross-impact.test.ts +++ b/gitnexus/test/unit/group/cross-impact.test.ts @@ -53,7 +53,12 @@ describe('cross-impact', () => { }); it('test_validateGroupImpactParams_rejects_bad_direction', () => { - const r = validateGroupImpactParams({ name: 'g', repo: 'a', target: 't', direction: 'sideways' }); + const r = validateGroupImpactParams({ + name: 'g', + repo: 'a', + target: 't', + direction: 'sideways', + }); expect(r.ok).toBe(false); if (!r.ok) expect(r.error).toContain('direction'); }); diff --git a/gitnexus/test/unit/group/service.test.ts b/gitnexus/test/unit/group/service.test.ts index 56a8811a6..8c5c0ed6d 100644 --- a/gitnexus/test/unit/group/service.test.ts +++ b/gitnexus/test/unit/group/service.test.ts @@ -398,7 +398,10 @@ repos: query: 'x', subgroup: 'app/frontend', })) as { per_repo: Array<{ repo: string }> }; - expect(prefixOnly.per_repo.map((r) => r.repo).sort()).toEqual(['app/frontend', 'app/frontend/mobile']); + expect(prefixOnly.per_repo.map((r) => r.repo).sort()).toEqual([ + 'app/frontend', + 'app/frontend/mobile', + ]); const exact = (await svc.groupQuery({ name: 'nest-group', diff --git a/gitnexus/test/unit/mcp/group-repo-routing.test.ts b/gitnexus/test/unit/mcp/group-repo-routing.test.ts index 69ca0d085..ddf7d03a5 100644 --- a/gitnexus/test/unit/mcp/group-repo-routing.test.ts +++ b/gitnexus/test/unit/mcp/group-repo-routing.test.ts @@ -63,8 +63,12 @@ repos: `, ); vi.stubEnv('GITNEXUS_HOME', tmpDir); - groupSpyQuery = vi.spyOn(GroupService.prototype, 'groupQuery').mockResolvedValue({ via: 'query' }); - groupSpyImpact = vi.spyOn(GroupService.prototype, 'groupImpact').mockResolvedValue({ via: 'impact' }); + groupSpyQuery = vi + .spyOn(GroupService.prototype, 'groupQuery') + .mockResolvedValue({ via: 'query' }); + groupSpyImpact = vi + .spyOn(GroupService.prototype, 'groupImpact') + .mockResolvedValue({ via: 'impact' }); groupSpyContext = vi.spyOn(GroupService.prototype, 'groupContext').mockResolvedValue({ group: 'g1', results: [], @@ -139,7 +143,9 @@ repos: const backend = new LocalBackend(); const out = await backend.callTool('query', { repo: '@no-such-group', query: 'x' }); expect(out).toHaveProperty('error'); - expect(String((out as { error: string }).error)).toMatch(/not found|no such|unknown|exist|ENOENT/i); + expect(String((out as { error: string }).error)).toMatch( + /not found|no such|unknown|exist|ENOENT/i, + ); }); it('returns error for unknown member path', async () => { @@ -165,7 +171,9 @@ repos: it('removed group_contracts mentions migration', async () => { const backend = new LocalBackend(); - await expect(backend.callTool('group_contracts', { name: 'g1' })).rejects.toThrow(/Removed tools/); + await expect(backend.callTool('group_contracts', { name: 'g1' })).rejects.toThrow( + /Removed tools/, + ); }); it('removed group_status mentions migration', async () => { @@ -204,14 +212,19 @@ repos: params: { repo: '@myproduct', target: 'UserService.login', service: 'app/backend' }, spy: () => groupSpyContext, }, - ])('$method with repo "@myproduct" routes to GroupService and forwards service', async ({ method, params, spy }) => { - const backend = new LocalBackend(); - await backend.callTool(method, params); - expect(spy()).toHaveBeenCalledWith( - expect.objectContaining({ name: 'myproduct', service: 'app/backend' }), - ); - const callArg = spy().mock.calls[0][0] as Record; - expect(typeof callArg.repo === 'string' ? (callArg.repo as string).startsWith('@') : false).toBe(false); - }); + ])( + '$method with repo "@myproduct" routes to GroupService and forwards service', + async ({ method, params, spy }) => { + const backend = new LocalBackend(); + await backend.callTool(method, params); + expect(spy()).toHaveBeenCalledWith( + expect.objectContaining({ name: 'myproduct', service: 'app/backend' }), + ); + const callArg = spy().mock.calls[0][0] as Record; + expect( + typeof callArg.repo === 'string' ? (callArg.repo as string).startsWith('@') : false, + ).toBe(false); + }, + ); }); });