gitnexus 1.6.9-rc.37 → 1.6.9-rc.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,18 +20,24 @@ export function formatDetectChangesResult(result) {
20
20
  const changed = Array.isArray(payload.changed_symbols) ? payload.changed_symbols : [];
21
21
  if (changed.length > 0) {
22
22
  lines.push(t('tool.detectChanges.changedSymbols'));
23
- for (const symbol of changed.slice(0, 15)) {
23
+ const shown = changed.slice(0, 15);
24
+ for (const symbol of shown) {
24
25
  lines.push(` ${symbol.type ?? 'Symbol'} ${symbol.name ?? '?'} → ${symbol.filePath ?? '?'}`);
25
26
  }
26
- if (changed.length > 15) {
27
- lines.push(t('tool.detectChanges.overflowMore', { count: changed.length - 15 }));
27
+ // Overflow is measured against the TRUE total (summary.changed_count), not
28
+ // the array length the array may already be `--limit`-sliced, so using its
29
+ // length would under-report (or hide) how many symbols are not shown.
30
+ const totalChanged = summary.changed_count ?? changed.length;
31
+ if (totalChanged > shown.length) {
32
+ lines.push(t('tool.detectChanges.overflowMore', { count: totalChanged - shown.length }));
28
33
  }
29
34
  lines.push('');
30
35
  }
31
36
  const affected = Array.isArray(payload.affected_processes) ? payload.affected_processes : [];
32
37
  if (affected.length > 0) {
33
38
  lines.push(t('tool.detectChanges.affectedExecutionFlows'));
34
- for (const processInfo of affected.slice(0, 10)) {
39
+ const shownAffected = affected.slice(0, 10);
40
+ for (const processInfo of shownAffected) {
35
41
  const changedSteps = Array.isArray(processInfo.changed_steps)
36
42
  ? processInfo.changed_steps
37
43
  : [];
@@ -40,6 +46,10 @@ export function formatDetectChangesResult(result) {
40
46
  count: processInfo.step_count ?? 0,
41
47
  })}) — ${t('tool.detectChanges.changedSteps', { steps })}`);
42
48
  }
49
+ const totalAffected = summary.affected_count ?? affected.length;
50
+ if (totalAffected > shownAffected.length) {
51
+ lines.push(t('tool.detectChanges.overflowMore', { count: totalAffected - shownAffected.length }));
52
+ }
43
53
  }
44
54
  return lines.join('\n').trim();
45
55
  }
@@ -96,6 +96,7 @@ const OPTION_DESCRIPTION_KEYS = {
96
96
  'wiki|--lang <lang>': 'help.option.wiki.lang',
97
97
  'publish|--id <owner/repo>': 'help.option.publish.id',
98
98
  'publish|--skip-git': 'help.option.skipGit',
99
+ 'query|-q, --query <text>': 'help.option.query.flag',
99
100
  'query|-r, --repo <name>': 'help.option.repo.targetOmitOne',
100
101
  'query|--branch <name>': 'help.option.branch',
101
102
  'query|-c, --context <text>': 'help.option.query.context',
@@ -106,6 +107,7 @@ const OPTION_DESCRIPTION_KEYS = {
106
107
  'context|--branch <name>': 'help.option.branch',
107
108
  'context|-u, --uid <uid>': 'help.option.context.uid',
108
109
  'context|-f, --file <path>': 'help.option.context.file',
110
+ 'context|-l, --limit <n>': 'help.option.context.limit',
109
111
  'context|--content': 'help.option.content',
110
112
  'impact|-d, --direction <dir>': 'help.option.impact.direction',
111
113
  'impact|-r, --repo <name>': 'help.option.repo.target',
@@ -115,13 +117,15 @@ const OPTION_DESCRIPTION_KEYS = {
115
117
  'impact|--kind <kind>': 'help.option.impact.kind',
116
118
  'impact|--depth <n>': 'help.option.impact.depth',
117
119
  'impact|--include-tests': 'help.option.impact.includeTests',
118
- 'impact|--limit <n>': 'help.option.impact.limit',
120
+ 'impact|-l, --limit <n>': 'help.option.impact.limit',
119
121
  'impact|--offset <n>': 'help.option.impact.offset',
120
122
  'impact|--summary-only': 'help.option.impact.summaryOnly',
121
123
  'cypher|-r, --repo <name>': 'help.option.repo.target',
122
124
  'cypher|--branch <name>': 'help.option.branch',
125
+ 'cypher|-l, --limit <n>': 'help.option.cypher.limit',
123
126
  'detect-changes|-s, --scope <scope>': 'help.option.detectChanges.scope',
124
127
  'detect-changes|-b, --base-ref <ref>': 'help.option.detectChanges.baseRef',
128
+ 'detect-changes|-l, --limit <n>': 'help.option.detectChanges.limit',
125
129
  'detect-changes|-r, --repo <name>': 'help.option.repo.target',
126
130
  'detect-changes|--branch <name>': 'help.option.branch',
127
131
  'check|--cycles': 'help.option.check.cycles',
@@ -48,7 +48,7 @@ export declare const en: {
48
48
  readonly 'remove.removed': "Removed: {{name}}";
49
49
  readonly 'remove.failed': "Failed to remove {{name}}: {{message}}";
50
50
  readonly 'tool.noIndexed': "GitNexus: No indexed repositories found. Run: gitnexus analyze";
51
- readonly 'tool.usage.query': "Usage: gitnexus query <search_query>";
51
+ readonly 'tool.usage.query': "Usage: gitnexus query [search_query] or gitnexus query --query <text>";
52
52
  readonly 'tool.usage.context': "Usage: gitnexus context <symbol_name> [--uid <uid>] [--file <path>]";
53
53
  readonly 'tool.usage.impact': "Usage: gitnexus impact <symbol_name> [--uid <uid>] [--file <path>] [--kind <kind>] [--direction upstream|downstream]";
54
54
  readonly 'tool.usage.trace': "Usage: gitnexus trace <from> <to> [--from-uid <uid>] [--to-uid <uid>] [--depth <n>]";
@@ -198,11 +198,13 @@ export declare const en: {
198
198
  readonly 'help.option.branch': "Scope to a specific branch index (multi-branch repos)";
199
199
  readonly 'help.option.context.uid': "Direct symbol UID (zero-ambiguity lookup)";
200
200
  readonly 'help.option.context.file': "File path to disambiguate common names";
201
+ readonly 'help.option.context.limit': "Max callers/callees/processes to return";
202
+ readonly 'help.option.query.flag': "Search query (alias for positional argument)";
201
203
  readonly 'help.option.impact.kind': "Kind filter to disambiguate common names (e.g. Function, Class, Method)";
202
204
  readonly 'help.option.impact.direction': "upstream (dependants) or downstream (dependencies)";
203
205
  readonly 'help.option.impact.depth': "Max relationship depth (default: 3)";
204
206
  readonly 'help.option.impact.includeTests': "Include test files in results";
205
- readonly 'help.option.impact.limit': "Max symbols per depth level (default: 100)";
207
+ readonly 'help.option.impact.limit': "Max symbols per depth level and affected processes/modules to return (default: 100)";
206
208
  readonly 'help.option.impact.offset': "Skip N symbols per depth level for pagination";
207
209
  readonly 'help.option.impact.summaryOnly': "Return counts and risk only, omit symbol list";
208
210
  readonly 'help.option.trace.fromUid': "Source symbol UID (zero-ambiguity lookup)";
@@ -213,6 +215,8 @@ export declare const en: {
213
215
  readonly 'help.option.trace.includeTests': "Traverse through test-file symbols (default: false)";
214
216
  readonly 'help.option.detectChanges.scope': "What to analyze: unstaged, staged, all, or compare";
215
217
  readonly 'help.option.detectChanges.baseRef': "Branch/commit for compare scope (e.g. main)";
218
+ readonly 'help.option.detectChanges.limit': "Max changed symbols to return";
219
+ readonly 'help.option.cypher.limit': "Max result rows to return";
216
220
  readonly 'help.option.check.cycles': "Detect circular imports and fail when any are found";
217
221
  readonly 'help.option.evalServer.host': "Bind address (default: 127.0.0.1, use 0.0.0.0 to expose to all interfaces)";
218
222
  readonly 'help.option.evalServer.idleTimeout': "Auto-shutdown after N seconds idle (0 = disabled)";
@@ -48,7 +48,7 @@ export const en = {
48
48
  'remove.removed': 'Removed: {{name}}',
49
49
  'remove.failed': 'Failed to remove {{name}}: {{message}}',
50
50
  'tool.noIndexed': 'GitNexus: No indexed repositories found. Run: gitnexus analyze',
51
- 'tool.usage.query': 'Usage: gitnexus query <search_query>',
51
+ 'tool.usage.query': 'Usage: gitnexus query [search_query] or gitnexus query --query <text>',
52
52
  'tool.usage.context': 'Usage: gitnexus context <symbol_name> [--uid <uid>] [--file <path>]',
53
53
  'tool.usage.impact': 'Usage: gitnexus impact <symbol_name> [--uid <uid>] [--file <path>] [--kind <kind>] [--direction upstream|downstream]',
54
54
  'tool.usage.trace': 'Usage: gitnexus trace <from> <to> [--from-uid <uid>] [--to-uid <uid>] [--depth <n>]',
@@ -198,11 +198,13 @@ export const en = {
198
198
  'help.option.branch': 'Scope to a specific branch index (multi-branch repos)',
199
199
  'help.option.context.uid': 'Direct symbol UID (zero-ambiguity lookup)',
200
200
  'help.option.context.file': 'File path to disambiguate common names',
201
+ 'help.option.context.limit': 'Max callers/callees/processes to return',
202
+ 'help.option.query.flag': 'Search query (alias for positional argument)',
201
203
  'help.option.impact.kind': 'Kind filter to disambiguate common names (e.g. Function, Class, Method)',
202
204
  'help.option.impact.direction': 'upstream (dependants) or downstream (dependencies)',
203
205
  'help.option.impact.depth': 'Max relationship depth (default: 3)',
204
206
  'help.option.impact.includeTests': 'Include test files in results',
205
- 'help.option.impact.limit': 'Max symbols per depth level (default: 100)',
207
+ 'help.option.impact.limit': 'Max symbols per depth level and affected processes/modules to return (default: 100)',
206
208
  'help.option.impact.offset': 'Skip N symbols per depth level for pagination',
207
209
  'help.option.impact.summaryOnly': 'Return counts and risk only, omit symbol list',
208
210
  'help.option.trace.fromUid': 'Source symbol UID (zero-ambiguity lookup)',
@@ -213,6 +215,8 @@ export const en = {
213
215
  'help.option.trace.includeTests': 'Traverse through test-file symbols (default: false)',
214
216
  'help.option.detectChanges.scope': 'What to analyze: unstaged, staged, all, or compare',
215
217
  'help.option.detectChanges.baseRef': 'Branch/commit for compare scope (e.g. main)',
218
+ 'help.option.detectChanges.limit': 'Max changed symbols to return',
219
+ 'help.option.cypher.limit': 'Max result rows to return',
216
220
  'help.option.check.cycles': 'Detect circular imports and fail when any are found',
217
221
  'help.option.evalServer.host': 'Bind address (default: 127.0.0.1, use 0.0.0.0 to expose to all interfaces)',
218
222
  'help.option.evalServer.idleTimeout': 'Auto-shutdown after N seconds idle (0 = disabled)',
@@ -49,7 +49,7 @@ export declare const cliResources: {
49
49
  readonly 'remove.removed': "Removed: {{name}}";
50
50
  readonly 'remove.failed': "Failed to remove {{name}}: {{message}}";
51
51
  readonly 'tool.noIndexed': "GitNexus: No indexed repositories found. Run: gitnexus analyze";
52
- readonly 'tool.usage.query': "Usage: gitnexus query <search_query>";
52
+ readonly 'tool.usage.query': "Usage: gitnexus query [search_query] or gitnexus query --query <text>";
53
53
  readonly 'tool.usage.context': "Usage: gitnexus context <symbol_name> [--uid <uid>] [--file <path>]";
54
54
  readonly 'tool.usage.impact': "Usage: gitnexus impact <symbol_name> [--uid <uid>] [--file <path>] [--kind <kind>] [--direction upstream|downstream]";
55
55
  readonly 'tool.usage.trace': "Usage: gitnexus trace <from> <to> [--from-uid <uid>] [--to-uid <uid>] [--depth <n>]";
@@ -199,11 +199,13 @@ export declare const cliResources: {
199
199
  readonly 'help.option.branch': "Scope to a specific branch index (multi-branch repos)";
200
200
  readonly 'help.option.context.uid': "Direct symbol UID (zero-ambiguity lookup)";
201
201
  readonly 'help.option.context.file': "File path to disambiguate common names";
202
+ readonly 'help.option.context.limit': "Max callers/callees/processes to return";
203
+ readonly 'help.option.query.flag': "Search query (alias for positional argument)";
202
204
  readonly 'help.option.impact.kind': "Kind filter to disambiguate common names (e.g. Function, Class, Method)";
203
205
  readonly 'help.option.impact.direction': "upstream (dependants) or downstream (dependencies)";
204
206
  readonly 'help.option.impact.depth': "Max relationship depth (default: 3)";
205
207
  readonly 'help.option.impact.includeTests': "Include test files in results";
206
- readonly 'help.option.impact.limit': "Max symbols per depth level (default: 100)";
208
+ readonly 'help.option.impact.limit': "Max symbols per depth level and affected processes/modules to return (default: 100)";
207
209
  readonly 'help.option.impact.offset': "Skip N symbols per depth level for pagination";
208
210
  readonly 'help.option.impact.summaryOnly': "Return counts and risk only, omit symbol list";
209
211
  readonly 'help.option.trace.fromUid': "Source symbol UID (zero-ambiguity lookup)";
@@ -214,6 +216,8 @@ export declare const cliResources: {
214
216
  readonly 'help.option.trace.includeTests': "Traverse through test-file symbols (default: false)";
215
217
  readonly 'help.option.detectChanges.scope': "What to analyze: unstaged, staged, all, or compare";
216
218
  readonly 'help.option.detectChanges.baseRef': "Branch/commit for compare scope (e.g. main)";
219
+ readonly 'help.option.detectChanges.limit': "Max changed symbols to return";
220
+ readonly 'help.option.cypher.limit': "Max result rows to return";
217
221
  readonly 'help.option.check.cycles': "Detect circular imports and fail when any are found";
218
222
  readonly 'help.option.evalServer.host': "Bind address (default: 127.0.0.1, use 0.0.0.0 to expose to all interfaces)";
219
223
  readonly 'help.option.evalServer.idleTimeout': "Auto-shutdown after N seconds idle (0 = disabled)";
@@ -437,6 +441,8 @@ export declare const cliResources: {
437
441
  'help.option.branch': string;
438
442
  'help.option.context.uid': string;
439
443
  'help.option.context.file': string;
444
+ 'help.option.context.limit': string;
445
+ 'help.option.query.flag': string;
440
446
  'help.option.impact.kind': string;
441
447
  'help.option.impact.direction': string;
442
448
  'help.option.impact.depth': string;
@@ -452,6 +458,8 @@ export declare const cliResources: {
452
458
  'help.option.trace.includeTests': string;
453
459
  'help.option.detectChanges.scope': string;
454
460
  'help.option.detectChanges.baseRef': string;
461
+ 'help.option.detectChanges.limit': string;
462
+ 'help.option.cypher.limit': string;
455
463
  'help.option.check.cycles': string;
456
464
  'help.option.evalServer.host': string;
457
465
  'help.option.evalServer.idleTimeout': string;
@@ -198,6 +198,8 @@ export declare const zhCN: {
198
198
  'help.option.branch': string;
199
199
  'help.option.context.uid': string;
200
200
  'help.option.context.file': string;
201
+ 'help.option.context.limit': string;
202
+ 'help.option.query.flag': string;
201
203
  'help.option.impact.kind': string;
202
204
  'help.option.impact.direction': string;
203
205
  'help.option.impact.depth': string;
@@ -213,6 +215,8 @@ export declare const zhCN: {
213
215
  'help.option.trace.includeTests': string;
214
216
  'help.option.detectChanges.scope': string;
215
217
  'help.option.detectChanges.baseRef': string;
218
+ 'help.option.detectChanges.limit': string;
219
+ 'help.option.cypher.limit': string;
216
220
  'help.option.check.cycles': string;
217
221
  'help.option.evalServer.host': string;
218
222
  'help.option.evalServer.idleTimeout': string;
@@ -48,7 +48,7 @@ export const zhCN = {
48
48
  'remove.removed': '已移除:{{name}}',
49
49
  'remove.failed': '移除 {{name}} 失败:{{message}}',
50
50
  'tool.noIndexed': 'GitNexus:未找到已索引仓库。请运行:gitnexus analyze',
51
- 'tool.usage.query': '用法:gitnexus query <搜索词>',
51
+ 'tool.usage.query': '用法:gitnexus query [搜索词] 或 gitnexus query --query <文本>',
52
52
  'tool.usage.context': '用法:gitnexus context <符号名> [--uid <uid>] [--file <路径>]',
53
53
  'tool.usage.impact': '用法:gitnexus impact <符号名> [--uid <uid>] [--file <路径>] [--kind <类型>] [--direction upstream|downstream]',
54
54
  'tool.usage.trace': '用法:gitnexus trace <起点> <终点> [--from-uid <uid>] [--to-uid <uid>] [--depth <n>]',
@@ -198,11 +198,13 @@ export const zhCN = {
198
198
  'help.option.branch': '将查询限定到指定分支的索引(多分支仓库)',
199
199
  'help.option.context.uid': '直接符号 UID(零歧义查找)',
200
200
  'help.option.context.file': '用于消除常见名称歧义的文件路径',
201
+ 'help.option.context.limit': '最多返回的调用者/被调用者/流程数',
202
+ 'help.option.query.flag': '搜索词(位置参数的别名)',
201
203
  'help.option.impact.kind': '用于消除常见名称歧义的类型过滤(如 Function、Class、Method)',
202
204
  'help.option.impact.direction': 'upstream(依赖它的项)或 downstream(它依赖的项)',
203
205
  'help.option.impact.depth': '最大关系遍历深度(默认:3)',
204
206
  'help.option.impact.includeTests': '在结果中包含测试文件',
205
- 'help.option.impact.limit': '每层深度最大符号数(默认:100)',
207
+ 'help.option.impact.limit': '每层深度最大符号数及最多返回的受影响流程/模块数(默认:100)',
206
208
  'help.option.impact.offset': '每层深度跳过 N 个符号(分页用)',
207
209
  'help.option.impact.summaryOnly': '仅返回计数和风险等级,省略符号列表',
208
210
  'help.option.trace.fromUid': '源符号 UID(零歧义查找)',
@@ -213,6 +215,8 @@ export const zhCN = {
213
215
  'help.option.trace.includeTests': '遍历时包含测试文件中的符号(默认:false)',
214
216
  'help.option.detectChanges.scope': '分析范围:unstaged、staged、all 或 compare',
215
217
  'help.option.detectChanges.baseRef': 'compare 范围的分支/提交(例如 main)',
218
+ 'help.option.detectChanges.limit': '最多返回的已变更符号数',
219
+ 'help.option.cypher.limit': '最多返回的结果行数',
216
220
  'help.option.check.cycles': '检测循环导入,并在发现循环时失败',
217
221
  'help.option.evalServer.host': '绑定地址(默认:127.0.0.1;用 0.0.0.0 暴露到所有网卡)',
218
222
  'help.option.evalServer.idleTimeout': '空闲 N 秒后自动关闭(0 = 禁用)',
package/dist/cli/index.js CHANGED
@@ -202,8 +202,9 @@ program
202
202
  // ─── Direct Tool Commands (no MCP overhead) ────────────────────────
203
203
  // These invoke LocalBackend directly for use in eval, scripts, and CI.
204
204
  program
205
- .command('query <search_query>')
205
+ .command('query [search_query]')
206
206
  .description('Search the knowledge graph for execution flows related to a concept')
207
+ .option('-q, --query <text>', 'Search query (alias for positional argument)')
207
208
  .option('-r, --repo <name>', 'Target repository (omit if only one indexed)')
208
209
  .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
209
210
  .option('-c, --context <text>', 'Task context to improve ranking')
@@ -218,6 +219,7 @@ program
218
219
  .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
219
220
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
220
221
  .option('-f, --file <path>', 'File path to disambiguate common names')
222
+ .option('-l, --limit <n>', 'Max callers/callees/processes to return')
221
223
  .option('--content', 'Include full symbol source code')
222
224
  .action(createLbugLazyAction(() => import('./tool.js'), 'contextCommand'));
223
225
  program
@@ -233,7 +235,7 @@ program
233
235
  .option('--kind <kind>', 'Kind filter to disambiguate common names (e.g. Function, Class, Method)')
234
236
  .option('--depth <n>', 'Max relationship depth (default: 3)')
235
237
  .option('--include-tests', 'Include test files in results')
236
- .option('--limit <n>', 'Max symbols per depth level (default: 100)')
238
+ .option('-l, --limit <n>', 'Max symbols per depth level and affected processes/modules to return (default: 100)')
237
239
  .option('--offset <n>', 'Skip N symbols per depth level for pagination')
238
240
  .option('--summary-only', 'Return counts and risk only, omit symbol list')
239
241
  .action(createLbugLazyAction(() => import('./tool.js'), 'impactCommand'));
@@ -254,6 +256,7 @@ program
254
256
  .description('Execute raw Cypher query against the knowledge graph')
255
257
  .option('-r, --repo <name>', 'Target repository')
256
258
  .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
259
+ .option('-l, --limit <n>', 'Max result rows to return')
257
260
  .action(createLbugLazyAction(() => import('./tool.js'), 'cypherCommand'));
258
261
  program
259
262
  .command('detect-changes')
@@ -263,6 +266,7 @@ program
263
266
  .option('-b, --base-ref <ref>', 'Branch/commit for compare scope (e.g. main)')
264
267
  .option('-r, --repo <name>', 'Target repository')
265
268
  .option('--branch <name>', 'Scope to a specific branch index (multi-branch repos)')
269
+ .option('-l, --limit <n>', 'Max changed symbols to return')
266
270
  .action(createLbugLazyAction(() => import('./tool.js'), 'detectChangesCommand'));
267
271
  program
268
272
  .command('check')
@@ -14,7 +14,8 @@
14
14
  * native module which captures the Node.js process.stdout stream during init.
15
15
  * See the output() function for details (#324).
16
16
  */
17
- export declare function queryCommand(queryText: string, options?: {
17
+ export declare function queryCommand(queryText: string | undefined, options?: {
18
+ query?: string;
18
19
  repo?: string;
19
20
  branch?: string;
20
21
  context?: string;
@@ -27,6 +28,7 @@ export declare function contextCommand(name: string, options?: {
27
28
  branch?: string;
28
29
  file?: string;
29
30
  uid?: string;
31
+ limit?: string;
30
32
  content?: boolean;
31
33
  }): Promise<void>;
32
34
  export declare function impactCommand(target?: string, options?: {
@@ -47,12 +49,14 @@ export declare function impactCommand(target?: string, options?: {
47
49
  export declare function cypherCommand(query: string, options?: {
48
50
  repo?: string;
49
51
  branch?: string;
52
+ limit?: string;
50
53
  }): Promise<void>;
51
54
  export declare function detectChangesCommand(options?: {
52
55
  scope?: string;
53
56
  baseRef?: string;
54
57
  repo?: string;
55
58
  branch?: string;
59
+ limit?: string;
56
60
  }): Promise<void>;
57
61
  export declare function checkCommand(options?: {
58
62
  cycles?: boolean;
package/dist/cli/tool.js CHANGED
@@ -55,18 +55,46 @@ function output(data) {
55
55
  process.stderr.write(text + '\n');
56
56
  }
57
57
  }
58
+ /**
59
+ * Parse a `--limit` CLI option into a positive row cap, or `undefined` when the
60
+ * flag is absent, non-numeric, zero, or negative.
61
+ *
62
+ * Treating invalid / 0 / negative input as "no limit" — rather than the old
63
+ * `options.limit ? Math.max(0, parseInt(...)) : undefined` path, where a string
64
+ * like `"abc"` is truthy and yields `NaN`, then `slice(0, NaN)` silently EMPTIES
65
+ * the result with exit 0 — keeps the guardrail commands (impact / context /
66
+ * detect-changes) honest: a bad `--limit` shows everything, never nothing.
67
+ */
68
+ function parseLimit(raw) {
69
+ if (raw === undefined)
70
+ return undefined;
71
+ const n = Number(raw);
72
+ return Number.isInteger(n) && n > 0 ? n : undefined;
73
+ }
74
+ /**
75
+ * Parse an `--offset` CLI option into a non-negative pagination start, or
76
+ * `undefined` when the flag is absent or invalid. Mirrors {@link parseLimit};
77
+ * offset `0` is valid ("start at the beginning"), so the guard is `>= 0`.
78
+ */
79
+ function parseOffset(raw) {
80
+ if (raw === undefined)
81
+ return undefined;
82
+ const n = Number(raw);
83
+ return Number.isInteger(n) && n >= 0 ? n : undefined;
84
+ }
58
85
  export async function queryCommand(queryText, options) {
59
- if (!queryText?.trim()) {
86
+ const resolvedQuery = queryText?.trim() || options?.query?.trim();
87
+ if (!resolvedQuery) {
60
88
  cliErrorKey('tool.usage.query');
61
89
  process.exit(1);
62
90
  }
63
91
  const backend = await getBackend();
64
92
  const result = await backend.callTool('query', {
65
93
  // #2175: canonical param is search_query; the backend still accepts legacy "query".
66
- search_query: queryText,
94
+ search_query: resolvedQuery,
67
95
  task_context: options?.context,
68
96
  goal: options?.goal,
69
- limit: options?.limit ? parseInt(options.limit) : undefined,
97
+ limit: parseLimit(options?.limit),
70
98
  include_content: options?.content ?? false,
71
99
  repo: options?.repo,
72
100
  branch: options?.branch,
@@ -83,6 +111,7 @@ export async function contextCommand(name, options) {
83
111
  cliErrorKey('tool.usage.context');
84
112
  process.exit(1);
85
113
  }
114
+ const limit = parseLimit(options?.limit);
86
115
  const backend = await getBackend();
87
116
  const result = await backend.callTool('context', {
88
117
  name: name || undefined,
@@ -92,6 +121,25 @@ export async function contextCommand(name, options) {
92
121
  repo: options?.repo,
93
122
  branch: options?.branch,
94
123
  });
124
+ if (limit !== undefined) {
125
+ // Bound every array-valued category under incoming/outgoing (calls, accesses,
126
+ // imports, extends, uses, …) — categorize() buckets by relType, so the prior
127
+ // hardcoded calls/accesses missed the rest (e.g. incoming.accesses) — plus
128
+ // typed_properties and processes, so --limit caps the whole context payload.
129
+ for (const dir of [result.incoming, result.outgoing]) {
130
+ if (!dir)
131
+ continue;
132
+ for (const key of Object.keys(dir)) {
133
+ const bucket = dir[key];
134
+ if (Array.isArray(bucket))
135
+ dir[key] = bucket.slice(0, limit);
136
+ }
137
+ }
138
+ if (Array.isArray(result.typed_properties))
139
+ result.typed_properties = result.typed_properties.slice(0, limit);
140
+ if (Array.isArray(result.processes))
141
+ result.processes = result.processes.slice(0, limit);
142
+ }
95
143
  output(result);
96
144
  }
97
145
  export async function impactCommand(target, options) {
@@ -116,10 +164,8 @@ export async function impactCommand(target, options) {
116
164
  }
117
165
  try {
118
166
  const backend = await getBackend();
119
- const rawLimit = parseInt(options?.limit ?? '', 10);
120
- const rawOffset = parseInt(options?.offset ?? '', 10);
121
- const parsedLimit = Number.isFinite(rawLimit) ? rawLimit : undefined;
122
- const parsedOffset = Number.isFinite(rawOffset) ? rawOffset : undefined;
167
+ const parsedLimit = parseLimit(options?.limit);
168
+ const parsedOffset = parseOffset(options?.offset);
123
169
  // `--line` is a PDG-only statement anchor (1-based source line). Parse it to
124
170
  // an integer when provided and thread it ONLY when present, so the backend's
125
171
  // line-without-pdg / non-positive-integer validation fires on the real value
@@ -145,6 +191,15 @@ export async function impactCommand(target, options) {
145
191
  offset: parsedOffset,
146
192
  summaryOnly: options?.summaryOnly ?? undefined,
147
193
  });
194
+ // Client-side cap of the affected-list payload to --limit (parity with the
195
+ // other tool commands). The backend already paginates byDepth per level to
196
+ // the same limit, so byDepth needs no client-side re-slice.
197
+ if (parsedLimit !== undefined) {
198
+ if (Array.isArray(result.affected_processes))
199
+ result.affected_processes = result.affected_processes.slice(0, parsedLimit);
200
+ if (Array.isArray(result.affected_modules))
201
+ result.affected_modules = result.affected_modules.slice(0, parsedLimit);
202
+ }
148
203
  output(result);
149
204
  }
150
205
  catch (err) {
@@ -164,6 +219,7 @@ export async function cypherCommand(query, options) {
164
219
  cliErrorKey('tool.usage.cypher');
165
220
  process.exit(1);
166
221
  }
222
+ const limit = parseLimit(options?.limit);
167
223
  const backend = await getBackend();
168
224
  const result = await backend.callTool('cypher', {
169
225
  // #2175: canonical param is statement; the backend still accepts legacy "query".
@@ -171,9 +227,30 @@ export async function cypherCommand(query, options) {
171
227
  repo: options?.repo,
172
228
  branch: options?.branch,
173
229
  });
230
+ if (limit !== undefined) {
231
+ if (Array.isArray(result)) {
232
+ // Non-tabular result: a raw row array.
233
+ result.splice(limit);
234
+ }
235
+ else if (result && typeof result === 'object' && typeof result.row_count === 'number') {
236
+ // Tabular result: { markdown, row_count }. The markdown is a table built as
237
+ // [header, separator, ...dataRows].join('\n'), so slice it to `limit` data
238
+ // rows (keeping the 2 header lines) and report a row_count that matches what
239
+ // is actually printed — otherwise `--limit 2` over 50 rows prints all 50 but
240
+ // claims row_count: 2.
241
+ if (typeof result.markdown === 'string' && result.row_count > limit) {
242
+ result.markdown = result.markdown
243
+ .split('\n')
244
+ .slice(0, 2 + limit)
245
+ .join('\n');
246
+ }
247
+ result.row_count = Math.min(result.row_count, limit);
248
+ }
249
+ }
174
250
  output(result);
175
251
  }
176
252
  export async function detectChangesCommand(options) {
253
+ const limit = parseLimit(options?.limit);
177
254
  const backend = await getBackend();
178
255
  const result = await backend.callTool('detect_changes', {
179
256
  scope: options?.scope || 'unstaged',
@@ -181,6 +258,12 @@ export async function detectChangesCommand(options) {
181
258
  repo: options?.repo,
182
259
  branch: options?.branch,
183
260
  });
261
+ if (limit !== undefined) {
262
+ if (Array.isArray(result.changed_symbols))
263
+ result.changed_symbols = result.changed_symbols.slice(0, limit);
264
+ if (Array.isArray(result.affected_processes))
265
+ result.affected_processes = result.affected_processes.slice(0, limit);
266
+ }
184
267
  output(formatDetectChangesResult(result));
185
268
  }
186
269
  export async function checkCommand(options) {
@@ -72,10 +72,13 @@ export interface LbugConnectionHandle {
72
72
  conn: lbug.Connection;
73
73
  }
74
74
  /**
75
- * Return true when the error message indicates that a LadybugDB file lock
76
- * could not be acquired — either at construction time
77
- * (`new lbug.Database(...)` raises from `local_file_system.cpp`) or during
78
- * a query (another writer holds the exclusive lock).
75
+ * Return true when the error message indicates that a LadybugDB write
76
+ * transaction could not proceed due to lock contention — either a file
77
+ * lock that could not be acquired (either at construction time,
78
+ * `new lbug.Database(...)` raising from `local_file_system.cpp`, or during
79
+ * a query, another writer holds the exclusive lock), or a same-process
80
+ * write transaction rejected because another write transaction is already
81
+ * active on the connection.
79
82
  *
80
83
  * Lives here (not in `lbug-adapter.ts`) so both the construction-time
81
84
  * retry (`openWithLockRetry` in this file) and the query-time retry
@@ -338,10 +338,13 @@ export const isLbugCheckpointIoError = (err) => {
338
338
  return LBUG_CHECKPOINT_PERMISSIVE_RE.test(msg);
339
339
  };
340
340
  /**
341
- * Return true when the error message indicates that a LadybugDB file lock
342
- * could not be acquired — either at construction time
343
- * (`new lbug.Database(...)` raises from `local_file_system.cpp`) or during
344
- * a query (another writer holds the exclusive lock).
341
+ * Return true when the error message indicates that a LadybugDB write
342
+ * transaction could not proceed due to lock contention — either a file
343
+ * lock that could not be acquired (either at construction time,
344
+ * `new lbug.Database(...)` raising from `local_file_system.cpp`, or during
345
+ * a query, another writer holds the exclusive lock), or a same-process
346
+ * write transaction rejected because another write transaction is already
347
+ * active on the connection.
345
348
  *
346
349
  * Lives here (not in `lbug-adapter.ts`) so both the construction-time
347
350
  * retry (`openWithLockRetry` in this file) and the query-time retry
@@ -353,10 +356,19 @@ export const isDbBusyError = (err) => {
353
356
  // `lock` already subsumes `could not set lock`; the broader term is kept
354
357
  // because graph-DB transient errors include "deadlock", "lock contention",
355
358
  // and the LadybugDB native module's "could not set lock on file" — all of
356
- // which deserve a retry. If a non-transient lock-shaped error ever
357
- // surfaces (e.g., "lock file missing" during recovery), tighten this
358
- // matcher rather than raising the retry budget.
359
- return msg.includes('busy') || msg.includes('lock') || msg.includes('already in use');
359
+ // which deserve a retry. LadybugDB also reports same-process writer
360
+ // contention without the words "busy" or "lock".
361
+ //
362
+ // "only one write transaction at a time" was observed against LadybugDB
363
+ // 0.18.0 (see gitnexus/package.json @ladybugdb/core).
364
+ //
365
+ // If a non-transient lock-shaped error ever surfaces (e.g., "lock file
366
+ // missing" during recovery), tighten this matcher rather than raising the
367
+ // retry budget.
368
+ return (msg.includes('busy') ||
369
+ msg.includes('lock') ||
370
+ msg.includes('already in use') ||
371
+ msg.includes('only one write transaction at a time'));
360
372
  };
361
373
  export function createLbugDatabase(lbugModule, databasePath, options = {}) {
362
374
  // .d.ts declares fewer args than the native constructor accepts.
@@ -1924,7 +1924,11 @@ export class LocalBackend {
1924
1924
  return '';
1925
1925
  if (typeof v === 'object')
1926
1926
  return JSON.stringify(v);
1927
- return String(v);
1927
+ // Collapse newlines so a multi-line cell value (e.g. a symbol's
1928
+ // `content`) stays on one physical line. Otherwise the rendered row
1929
+ // spans multiple lines, which corrupts the table and breaks the
1930
+ // CLI's `--limit` line-based slicing (#2310 review).
1931
+ return String(v).replace(/\r?\n/g, ' ');
1928
1932
  })
1929
1933
  .join(' | ') +
1930
1934
  ' |');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.37",
3
+ "version": "1.6.9-rc.39",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -83,6 +83,7 @@ const LBUG_NATIVE = [
83
83
  // quoting, path resolution, signal handling)
84
84
  const SPAWN_CLI = [
85
85
  'test/integration/cli-e2e.test.ts',
86
+ 'test/integration/cli-limit-e2e.test.ts',
86
87
  'test/integration/hooks-e2e.test.ts',
87
88
  'test/integration/skills-e2e.test.ts',
88
89
  'test/integration/server-http-startup.test.ts',