From 4679c9e1b94ec473c8036715f2aec3d688063060 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Sat, 30 May 2026 08:11:04 +0000 Subject: [PATCH] feat(cli): soft-validate impact --kind (U4, #1907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An unknown --kind value was silently a no-op. Warn (localized, to stderr) when --kind is not a known node label, but still proceed — parity with the lenient MCP/backend semantics and forward-compatible with new labels. Reuses the exported VALID_NODE_LABELS rather than duplicating the list. Co-Authored-By: Claude Opus 4.8 (1M context) --- gitnexus/src/cli/i18n/en.ts | 2 ++ gitnexus/src/cli/i18n/zh-CN.ts | 2 ++ gitnexus/src/cli/tool.ts | 10 +++++-- .../unit/cli-impact-disambiguation.test.ts | 28 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/gitnexus/src/cli/i18n/en.ts b/gitnexus/src/cli/i18n/en.ts index dc65280ea..e4778fb58 100644 --- a/gitnexus/src/cli/i18n/en.ts +++ b/gitnexus/src/cli/i18n/en.ts @@ -46,6 +46,8 @@ export const en = { 'tool.usage.impact': 'Usage: gitnexus impact [--uid ] [--file ] [--kind ] [--direction upstream|downstream]', 'tool.usage.cypher': 'Usage: gitnexus cypher ', + 'tool.warn.unknownKind': + "--kind '{{kind}}' is not a known symbol kind (e.g. Function, Class, Method); it will not narrow the result.", 'tool.detectChanges.noChanges': 'No changes detected.', 'tool.detectChanges.changesSummary': 'Changes: {{files}} files, {{symbols}} symbols', 'tool.detectChanges.affectedProcesses': 'Affected processes: {{count}}', diff --git a/gitnexus/src/cli/i18n/zh-CN.ts b/gitnexus/src/cli/i18n/zh-CN.ts index 5b3992d72..cd488de0a 100644 --- a/gitnexus/src/cli/i18n/zh-CN.ts +++ b/gitnexus/src/cli/i18n/zh-CN.ts @@ -50,6 +50,8 @@ export const zhCN = { 'tool.usage.impact': '用法:gitnexus impact <符号名> [--uid ] [--file <路径>] [--kind <类型>] [--direction upstream|downstream]', 'tool.usage.cypher': '用法:gitnexus cypher ', + 'tool.warn.unknownKind': + "--kind '{{kind}}' 不是已知的符号类型(如 Function、Class、Method),不会用于缩小结果范围。", 'tool.detectChanges.noChanges': '未检测到变更。', 'tool.detectChanges.changesSummary': '变更:{{files}} 个文件,{{symbols}} 个符号', 'tool.detectChanges.affectedProcesses': '受影响流程:{{count}}', diff --git a/gitnexus/src/cli/tool.ts b/gitnexus/src/cli/tool.ts index 0d9ad22f6..5801677b9 100644 --- a/gitnexus/src/cli/tool.ts +++ b/gitnexus/src/cli/tool.ts @@ -16,8 +16,8 @@ */ import { writeSync } from 'node:fs'; -import { LocalBackend } from '../mcp/local/local-backend.js'; -import { cliErrorKey } from './cli-message.js'; +import { LocalBackend, VALID_NODE_LABELS } from '../mcp/local/local-backend.js'; +import { cliErrorKey, cliWarnKey } from './cli-message.js'; import { formatDetectChangesResult } from './detect-changes-format.js'; let _backend: LocalBackend | null = null; @@ -143,6 +143,12 @@ export async function impactCommand( cliErrorKey('tool.usage.impact'); process.exit(1); } + // Soft-validate --kind: an unknown kind is a no-op hint (the backend scores + // it but it matches nothing), so warn and proceed rather than rejecting — + // parity with the lenient MCP surface and forward-compatible with new labels. + if (options?.kind && !VALID_NODE_LABELS.has(options.kind)) { + cliWarnKey('tool.warn.unknownKind', { kind: options.kind }); + } try { const backend = await getBackend(); diff --git a/gitnexus/test/unit/cli-impact-disambiguation.test.ts b/gitnexus/test/unit/cli-impact-disambiguation.test.ts index 6362f4fbc..5c20b6bc0 100644 --- a/gitnexus/test/unit/cli-impact-disambiguation.test.ts +++ b/gitnexus/test/unit/cli-impact-disambiguation.test.ts @@ -23,6 +23,8 @@ vi.mock('../../src/mcp/local/local-backend.js', () => ({ init = init; callTool = callTool; }, + // U4: impactCommand imports VALID_NODE_LABELS to soft-validate --kind. + VALID_NODE_LABELS: new Set(['Function', 'Class', 'Interface', 'Method', 'Constructor']), })); // impactCommand prints its result via fs.writeSync(fd 1, …). Silence that so @@ -109,4 +111,30 @@ describe('CLI impact disambiguation flags (#1907)', () => { exitSpy.mockRestore(); }); + + // U4 (#1914 review F3): an unknown --kind warns to stderr but still resolves. + it('warns on an unknown --kind but still forwards the request', async () => { + const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); + + await impactCommand('login', { kind: 'Funktion', direction: 'upstream' }); + + expect(callTool).toHaveBeenCalledTimes(1); + const stderr = stderrSpy.mock.calls.map((c) => String(c[0])).join(''); + expect(stderr).toContain('Funktion'); + expect(stderr).toContain('not a known symbol kind'); + + stderrSpy.mockRestore(); + }); + + it('does not warn for a known --kind', async () => { + const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); + + await impactCommand('login', { kind: 'Function', direction: 'upstream' }); + + expect(callTool).toHaveBeenCalledTimes(1); + const stderr = stderrSpy.mock.calls.map((c) => String(c[0])).join(''); + expect(stderr).not.toContain('not a known symbol kind'); + + stderrSpy.mockRestore(); + }); });