diff --git a/gitnexus/src/cli/help-i18n.ts b/gitnexus/src/cli/help-i18n.ts index def76601b..f33c88067 100644 --- a/gitnexus/src/cli/help-i18n.ts +++ b/gitnexus/src/cli/help-i18n.ts @@ -100,6 +100,7 @@ const OPTION_DESCRIPTION_KEYS = { 'wiki|--lang ': 'help.option.wiki.lang', 'publish|--id ': 'help.option.publish.id', 'publish|--skip-git': 'help.option.skipGit', + 'query|-q, --query ': 'help.option.query.flag', 'query|-r, --repo ': 'help.option.repo.targetOmitOne', 'query|--branch ': 'help.option.branch', 'query|-c, --context ': 'help.option.query.context', @@ -110,6 +111,7 @@ const OPTION_DESCRIPTION_KEYS = { 'context|--branch ': 'help.option.branch', 'context|-u, --uid ': 'help.option.context.uid', 'context|-f, --file ': 'help.option.context.file', + 'context|-l, --limit ': 'help.option.context.limit', 'context|--content': 'help.option.content', 'impact|-d, --direction ': 'help.option.impact.direction', 'impact|-r, --repo ': 'help.option.repo.target', @@ -124,8 +126,10 @@ const OPTION_DESCRIPTION_KEYS = { 'impact|--summary-only': 'help.option.impact.summaryOnly', 'cypher|-r, --repo ': 'help.option.repo.target', 'cypher|--branch ': 'help.option.branch', + 'cypher|-l, --limit ': 'help.option.cypher.limit', 'detect-changes|-s, --scope ': 'help.option.detectChanges.scope', 'detect-changes|-b, --base-ref ': 'help.option.detectChanges.baseRef', + 'detect-changes|-l, --limit ': 'help.option.detectChanges.limit', 'detect-changes|-r, --repo ': 'help.option.repo.target', 'detect-changes|--branch ': 'help.option.branch', 'check|--cycles': 'help.option.check.cycles', diff --git a/gitnexus/src/cli/i18n/en.ts b/gitnexus/src/cli/i18n/en.ts index caf61ab83..c73fa4da6 100644 --- a/gitnexus/src/cli/i18n/en.ts +++ b/gitnexus/src/cli/i18n/en.ts @@ -244,6 +244,8 @@ export const en = { 'help.option.branch': 'Scope to a specific branch index (multi-branch repos)', 'help.option.context.uid': 'Direct symbol UID (zero-ambiguity lookup)', 'help.option.context.file': 'File path to disambiguate common names', + 'help.option.context.limit': 'Max callers/callees/processes to return', + 'help.option.query.flag': 'Search query (alias for positional argument)', 'help.option.impact.kind': 'Kind filter to disambiguate common names (e.g. Function, Class, Method)', 'help.option.impact.direction': 'upstream (dependants) or downstream (dependencies)', @@ -260,6 +262,8 @@ export const en = { 'help.option.trace.includeTests': 'Traverse through test-file symbols (default: false)', 'help.option.detectChanges.scope': 'What to analyze: unstaged, staged, all, or compare', 'help.option.detectChanges.baseRef': 'Branch/commit for compare scope (e.g. main)', + 'help.option.detectChanges.limit': 'Max changed symbols to return', + 'help.option.cypher.limit': 'Max result rows to return', 'help.option.check.cycles': 'Detect circular imports and fail when any are found', 'help.option.evalServer.host': 'Bind address (default: 127.0.0.1, use 0.0.0.0 to expose to all interfaces)', diff --git a/gitnexus/src/cli/i18n/zh-CN.ts b/gitnexus/src/cli/i18n/zh-CN.ts index 01abeedc1..c7064fe7e 100644 --- a/gitnexus/src/cli/i18n/zh-CN.ts +++ b/gitnexus/src/cli/i18n/zh-CN.ts @@ -228,6 +228,8 @@ export const zhCN = { 'help.option.branch': '将查询限定到指定分支的索引(多分支仓库)', 'help.option.context.uid': '直接符号 UID(零歧义查找)', 'help.option.context.file': '用于消除常见名称歧义的文件路径', + 'help.option.context.limit': '最多返回的调用者/被调用者/流程数', + 'help.option.query.flag': '搜索词(位置参数的别名)', 'help.option.impact.kind': '用于消除常见名称歧义的类型过滤(如 Function、Class、Method)', 'help.option.impact.direction': 'upstream(依赖它的项)或 downstream(它依赖的项)', 'help.option.impact.depth': '最大关系遍历深度(默认:3)', @@ -243,6 +245,8 @@ export const zhCN = { 'help.option.trace.includeTests': '遍历时包含测试文件中的符号(默认:false)', 'help.option.detectChanges.scope': '分析范围:unstaged、staged、all 或 compare', 'help.option.detectChanges.baseRef': 'compare 范围的分支/提交(例如 main)', + 'help.option.detectChanges.limit': '最多返回的已变更符号数', + 'help.option.cypher.limit': '最多返回的结果行数', 'help.option.check.cycles': '检测循环导入,并在发现循环时失败', 'help.option.evalServer.host': '绑定地址(默认:127.0.0.1;用 0.0.0.0 暴露到所有网卡)', 'help.option.evalServer.idleTimeout': '空闲 N 秒后自动关闭(0 = 禁用)', diff --git a/gitnexus/src/cli/index.ts b/gitnexus/src/cli/index.ts index 72e4da365..86daf2aae 100644 --- a/gitnexus/src/cli/index.ts +++ b/gitnexus/src/cli/index.ts @@ -314,8 +314,9 @@ program // These invoke LocalBackend directly for use in eval, scripts, and CI. program - .command('query ') + .command('query [search_query]') .description('Search the knowledge graph for execution flows related to a concept') + .option('-q, --query ', 'Search query (alias for positional argument)') .option('-r, --repo ', 'Target repository (omit if only one indexed)') .option('--branch ', 'Scope to a specific branch index (multi-branch repos)') .option('-c, --context ', 'Task context to improve ranking') @@ -331,6 +332,7 @@ program .option('--branch ', 'Scope to a specific branch index (multi-branch repos)') .option('-u, --uid ', 'Direct symbol UID (zero-ambiguity lookup)') .option('-f, --file ', 'File path to disambiguate common names') + .option('-l, --limit ', 'Max callers/callees/processes to return') .option('--content', 'Include full symbol source code') .action(createLbugLazyAction(() => import('./tool.js'), 'contextCommand')); @@ -380,6 +382,7 @@ program .description('Execute raw Cypher query against the knowledge graph') .option('-r, --repo ', 'Target repository') .option('--branch ', 'Scope to a specific branch index (multi-branch repos)') + .option('-l, --limit ', 'Max result rows to return') .action(createLbugLazyAction(() => import('./tool.js'), 'cypherCommand')); program @@ -390,6 +393,7 @@ program .option('-b, --base-ref ', 'Branch/commit for compare scope (e.g. main)') .option('-r, --repo ', 'Target repository') .option('--branch ', 'Scope to a specific branch index (multi-branch repos)') + .option('-l, --limit ', 'Max changed symbols to return') .action(createLbugLazyAction(() => import('./tool.js'), 'detectChangesCommand')); program diff --git a/gitnexus/src/cli/tool.ts b/gitnexus/src/cli/tool.ts index 34b633956..cbeaf7f48 100644 --- a/gitnexus/src/cli/tool.ts +++ b/gitnexus/src/cli/tool.ts @@ -61,6 +61,7 @@ function output(data: any): void { export async function queryCommand( queryText: string, options?: { + query?: string; repo?: string; branch?: string; context?: string; @@ -69,7 +70,8 @@ export async function queryCommand( content?: boolean; }, ): Promise { - if (!queryText?.trim()) { + const resolvedQuery = queryText?.trim() || options?.query?.trim(); + if (!resolvedQuery) { cliErrorKey('tool.usage.query'); process.exit(1); } @@ -77,7 +79,7 @@ export async function queryCommand( const backend = await getBackend(); const result = await backend.callTool('query', { // #2175: canonical param is search_query; the backend still accepts legacy "query". - search_query: queryText, + search_query: resolvedQuery, task_context: options?.context, goal: options?.goal, limit: options?.limit ? parseInt(options.limit) : undefined, @@ -95,6 +97,7 @@ export async function contextCommand( branch?: string; file?: string; uid?: string; + limit?: string; content?: boolean; }, ): Promise { @@ -108,6 +111,7 @@ export async function contextCommand( process.exit(1); } + const limit = options?.limit ? Math.max(0, parseInt(options.limit, 10)) : undefined; const backend = await getBackend(); const result = await backend.callTool('context', { name: name || undefined, @@ -117,6 +121,15 @@ export async function contextCommand( repo: options?.repo, branch: options?.branch, }); + if (limit) { + if (result.incoming?.calls && Array.isArray(result.incoming.calls)) + result.incoming.calls = result.incoming.calls.slice(0, limit); + if (result.outgoing?.calls && Array.isArray(result.outgoing.calls)) + result.outgoing.calls = result.outgoing.calls.slice(0, limit); + if (result.outgoing?.accesses && Array.isArray(result.outgoing.accesses)) + result.outgoing.accesses = result.outgoing.accesses.slice(0, limit); + if (Array.isArray(result.processes)) result.processes = result.processes.slice(0, limit); + } output(result); } @@ -209,6 +222,7 @@ export async function cypherCommand( options?: { repo?: string; branch?: string; + limit?: string; }, ): Promise { if (!query?.trim()) { @@ -216,6 +230,7 @@ export async function cypherCommand( process.exit(1); } + const limit = options?.limit ? Math.max(0, parseInt(options.limit, 10)) : undefined; const backend = await getBackend(); const result = await backend.callTool('cypher', { // #2175: canonical param is statement; the backend still accepts legacy "query". @@ -223,6 +238,15 @@ export async function cypherCommand( repo: options?.repo, branch: options?.branch, }); + if (limit) { + if (Array.isArray(result)) { + result.splice(limit); + } else if (result && typeof result === 'object' && typeof result.row_count === 'number') { + // Cypher returns {markdown, row_count} — rows are embedded in markdown string. + // We can't slice the markdown, but cap the reported row_count. + result.row_count = Math.min(result.row_count, limit); + } + } output(result); } @@ -231,7 +255,9 @@ export async function detectChangesCommand(options?: { baseRef?: string; repo?: string; branch?: string; + limit?: string; }): Promise { + const limit = options?.limit ? Math.max(0, parseInt(options.limit, 10)) : undefined; const backend = await getBackend(); const result = await backend.callTool('detect_changes', { scope: options?.scope || 'unstaged', @@ -239,6 +265,12 @@ export async function detectChangesCommand(options?: { repo: options?.repo, branch: options?.branch, }); + if (limit) { + if (Array.isArray(result.changed_symbols)) + result.changed_symbols = result.changed_symbols.slice(0, limit); + if (Array.isArray(result.affected_processes)) + result.affected_processes = result.affected_processes.slice(0, limit); + } output(formatDetectChangesResult(result)); } diff --git a/gitnexus/test/unit/cli-index-help.test.ts b/gitnexus/test/unit/cli-index-help.test.ts index 8bd6fcee9..04a386541 100644 --- a/gitnexus/test/unit/cli-index-help.test.ts +++ b/gitnexus/test/unit/cli-index-help.test.ts @@ -141,7 +141,7 @@ describe('CLI help surface', () => { const result = runHelp('query', { GITNEXUS_LANG: 'zh-CN' } as NodeJS.ProcessEnv); expect(result.status).toBe(0); - expect(result.stdout).toContain('用法: gitnexus query [options] '); + expect(result.stdout).toContain('用法: gitnexus query [options] [search_query]'); expect(result.stdout).toContain('搜索知识图谱中与概念相关的执行流程'); expect(result.stdout).toContain('-r, --repo 目标仓库(仅有一个已索引仓库时可省略)'); expect(result.stdout).toContain('-l, --limit 最多返回的流程数(默认:5)');