gitnexus 1.6.6-rc.96 → 1.6.6-rc.97

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.
package/README.md CHANGED
@@ -170,6 +170,13 @@ gitnexus clean --all --force # Delete all indexes
170
170
  gitnexus wiki [path] # Generate LLM-powered docs from knowledge graph
171
171
  gitnexus wiki --model <model> # Wiki with custom LLM model (default: gpt-4o-mini)
172
172
 
173
+ # Direct graph queries — the same tools the MCP server exposes, no MCP daemon needed
174
+ gitnexus query "<concept>" # Process-grouped hybrid search
175
+ gitnexus context <symbol> [--uid <uid> | --file <path>] # 360° symbol view; flags disambiguate a shared name
176
+ gitnexus impact <symbol> [--uid <uid> | --file <path> | --kind <kind>] # Blast radius; flags disambiguate a shared name
177
+ gitnexus detect-changes # Map the working-tree diff to affected symbols and execution flows
178
+ gitnexus cypher "<query>" # Run a raw Cypher query against the knowledge graph
179
+
173
180
  # Repository groups (multi-repo / monorepo service tracking)
174
181
  gitnexus group create <name> # Create a repository group
175
182
  gitnexus group add <group> <groupPath> <registryName> # Add a repo to a group. <groupPath> is a hierarchy path (e.g. hr/hiring/backend); <registryName> is the repo's name from the registry (see `gitnexus list`)
@@ -97,6 +97,9 @@ const OPTION_DESCRIPTION_KEYS = {
97
97
  'context|--content': 'help.option.content',
98
98
  'impact|-d, --direction <dir>': 'help.option.impact.direction',
99
99
  'impact|-r, --repo <name>': 'help.option.repo.target',
100
+ 'impact|-u, --uid <uid>': 'help.option.context.uid',
101
+ 'impact|-f, --file <path>': 'help.option.context.file',
102
+ 'impact|--kind <kind>': 'help.option.impact.kind',
100
103
  'impact|--depth <n>': 'help.option.impact.depth',
101
104
  'impact|--include-tests': 'help.option.impact.includeTests',
102
105
  'impact|--limit <n>': 'help.option.impact.limit',
@@ -41,8 +41,9 @@ export declare const en: {
41
41
  readonly 'tool.noIndexed': "GitNexus: No indexed repositories found. Run: gitnexus analyze";
42
42
  readonly 'tool.usage.query': "Usage: gitnexus query <search_query>";
43
43
  readonly 'tool.usage.context': "Usage: gitnexus context <symbol_name> [--uid <uid>] [--file <path>]";
44
- readonly 'tool.usage.impact': "Usage: gitnexus impact <symbol_name> [--direction upstream|downstream]";
44
+ readonly 'tool.usage.impact': "Usage: gitnexus impact <symbol_name> [--uid <uid>] [--file <path>] [--kind <kind>] [--direction upstream|downstream]";
45
45
  readonly 'tool.usage.cypher': "Usage: gitnexus cypher <cypher_query>";
46
+ readonly 'tool.warn.unknownKind': "--kind '{{kind}}' is not a known symbol kind (e.g. Function, Class, Method); it will not narrow the result.";
46
47
  readonly 'tool.detectChanges.noChanges': "No changes detected.";
47
48
  readonly 'tool.detectChanges.changesSummary': "Changes: {{files}} files, {{symbols}} symbols";
48
49
  readonly 'tool.detectChanges.affectedProcesses': "Affected processes: {{count}}";
@@ -177,6 +178,7 @@ export declare const en: {
177
178
  readonly 'help.option.repo.target': "Target repository";
178
179
  readonly 'help.option.context.uid': "Direct symbol UID (zero-ambiguity lookup)";
179
180
  readonly 'help.option.context.file': "File path to disambiguate common names";
181
+ readonly 'help.option.impact.kind': "Kind filter to disambiguate common names (e.g. Function, Class, Method)";
180
182
  readonly 'help.option.impact.direction': "upstream (dependants) or downstream (dependencies)";
181
183
  readonly 'help.option.impact.depth': "Max relationship depth (default: 3)";
182
184
  readonly 'help.option.impact.includeTests': "Include test files in results";
@@ -41,8 +41,9 @@ export const en = {
41
41
  'tool.noIndexed': 'GitNexus: No indexed repositories found. Run: gitnexus analyze',
42
42
  'tool.usage.query': 'Usage: gitnexus query <search_query>',
43
43
  'tool.usage.context': 'Usage: gitnexus context <symbol_name> [--uid <uid>] [--file <path>]',
44
- 'tool.usage.impact': 'Usage: gitnexus impact <symbol_name> [--direction upstream|downstream]',
44
+ 'tool.usage.impact': 'Usage: gitnexus impact <symbol_name> [--uid <uid>] [--file <path>] [--kind <kind>] [--direction upstream|downstream]',
45
45
  'tool.usage.cypher': 'Usage: gitnexus cypher <cypher_query>',
46
+ 'tool.warn.unknownKind': "--kind '{{kind}}' is not a known symbol kind (e.g. Function, Class, Method); it will not narrow the result.",
46
47
  'tool.detectChanges.noChanges': 'No changes detected.',
47
48
  'tool.detectChanges.changesSummary': 'Changes: {{files}} files, {{symbols}} symbols',
48
49
  'tool.detectChanges.affectedProcesses': 'Affected processes: {{count}}',
@@ -177,6 +178,7 @@ export const en = {
177
178
  'help.option.repo.target': 'Target repository',
178
179
  'help.option.context.uid': 'Direct symbol UID (zero-ambiguity lookup)',
179
180
  'help.option.context.file': 'File path to disambiguate common names',
181
+ 'help.option.impact.kind': 'Kind filter to disambiguate common names (e.g. Function, Class, Method)',
180
182
  'help.option.impact.direction': 'upstream (dependants) or downstream (dependencies)',
181
183
  'help.option.impact.depth': 'Max relationship depth (default: 3)',
182
184
  'help.option.impact.includeTests': 'Include test files in results',
@@ -42,8 +42,9 @@ export declare const cliResources: {
42
42
  readonly 'tool.noIndexed': "GitNexus: No indexed repositories found. Run: gitnexus analyze";
43
43
  readonly 'tool.usage.query': "Usage: gitnexus query <search_query>";
44
44
  readonly 'tool.usage.context': "Usage: gitnexus context <symbol_name> [--uid <uid>] [--file <path>]";
45
- readonly 'tool.usage.impact': "Usage: gitnexus impact <symbol_name> [--direction upstream|downstream]";
45
+ readonly 'tool.usage.impact': "Usage: gitnexus impact <symbol_name> [--uid <uid>] [--file <path>] [--kind <kind>] [--direction upstream|downstream]";
46
46
  readonly 'tool.usage.cypher': "Usage: gitnexus cypher <cypher_query>";
47
+ readonly 'tool.warn.unknownKind': "--kind '{{kind}}' is not a known symbol kind (e.g. Function, Class, Method); it will not narrow the result.";
47
48
  readonly 'tool.detectChanges.noChanges': "No changes detected.";
48
49
  readonly 'tool.detectChanges.changesSummary': "Changes: {{files}} files, {{symbols}} symbols";
49
50
  readonly 'tool.detectChanges.affectedProcesses': "Affected processes: {{count}}";
@@ -178,6 +179,7 @@ export declare const cliResources: {
178
179
  readonly 'help.option.repo.target': "Target repository";
179
180
  readonly 'help.option.context.uid': "Direct symbol UID (zero-ambiguity lookup)";
180
181
  readonly 'help.option.context.file': "File path to disambiguate common names";
182
+ readonly 'help.option.impact.kind': "Kind filter to disambiguate common names (e.g. Function, Class, Method)";
181
183
  readonly 'help.option.impact.direction': "upstream (dependants) or downstream (dependencies)";
182
184
  readonly 'help.option.impact.depth': "Max relationship depth (default: 3)";
183
185
  readonly 'help.option.impact.includeTests': "Include test files in results";
@@ -253,6 +255,7 @@ export declare const cliResources: {
253
255
  'tool.usage.context': string;
254
256
  'tool.usage.impact': string;
255
257
  'tool.usage.cypher': string;
258
+ 'tool.warn.unknownKind': string;
256
259
  'tool.detectChanges.noChanges': string;
257
260
  'tool.detectChanges.changesSummary': string;
258
261
  'tool.detectChanges.affectedProcesses': string;
@@ -387,6 +390,7 @@ export declare const cliResources: {
387
390
  'help.option.repo.target': string;
388
391
  'help.option.context.uid': string;
389
392
  'help.option.context.file': string;
393
+ 'help.option.impact.kind': string;
390
394
  'help.option.impact.direction': string;
391
395
  'help.option.impact.depth': string;
392
396
  'help.option.impact.includeTests': string;
@@ -43,6 +43,7 @@ export declare const zhCN: {
43
43
  'tool.usage.context': string;
44
44
  'tool.usage.impact': string;
45
45
  'tool.usage.cypher': string;
46
+ 'tool.warn.unknownKind': string;
46
47
  'tool.detectChanges.noChanges': string;
47
48
  'tool.detectChanges.changesSummary': string;
48
49
  'tool.detectChanges.affectedProcesses': string;
@@ -177,6 +178,7 @@ export declare const zhCN: {
177
178
  'help.option.repo.target': string;
178
179
  'help.option.context.uid': string;
179
180
  'help.option.context.file': string;
181
+ 'help.option.impact.kind': string;
180
182
  'help.option.impact.direction': string;
181
183
  'help.option.impact.depth': string;
182
184
  'help.option.impact.includeTests': string;
@@ -41,8 +41,9 @@ export const zhCN = {
41
41
  'tool.noIndexed': 'GitNexus:未找到已索引仓库。请运行:gitnexus analyze',
42
42
  'tool.usage.query': '用法:gitnexus query <搜索词>',
43
43
  'tool.usage.context': '用法:gitnexus context <符号名> [--uid <uid>] [--file <路径>]',
44
- 'tool.usage.impact': '用法:gitnexus impact <符号名> [--direction upstream|downstream]',
44
+ 'tool.usage.impact': '用法:gitnexus impact <符号名> [--uid <uid>] [--file <路径>] [--kind <类型>] [--direction upstream|downstream]',
45
45
  'tool.usage.cypher': '用法:gitnexus cypher <Cypher 查询>',
46
+ 'tool.warn.unknownKind': "--kind '{{kind}}' 不是已知的符号类型(如 Function、Class、Method),不会用于缩小结果范围。",
46
47
  'tool.detectChanges.noChanges': '未检测到变更。',
47
48
  'tool.detectChanges.changesSummary': '变更:{{files}} 个文件,{{symbols}} 个符号',
48
49
  'tool.detectChanges.affectedProcesses': '受影响流程:{{count}}',
@@ -177,6 +178,7 @@ export const zhCN = {
177
178
  'help.option.repo.target': '目标仓库',
178
179
  'help.option.context.uid': '直接符号 UID(零歧义查找)',
179
180
  'help.option.context.file': '用于消除常见名称歧义的文件路径',
181
+ 'help.option.impact.kind': '用于消除常见名称歧义的类型过滤(如 Function、Class、Method)',
180
182
  'help.option.impact.direction': 'upstream(依赖它的项)或 downstream(它依赖的项)',
181
183
  'help.option.impact.depth': '最大关系遍历深度(默认:3)',
182
184
  'help.option.impact.includeTests': '在结果中包含测试文件',
package/dist/cli/index.js CHANGED
@@ -142,10 +142,13 @@ program
142
142
  .option('--content', 'Include full symbol source code')
143
143
  .action(createLbugLazyAction(() => import('./tool.js'), 'contextCommand'));
144
144
  program
145
- .command('impact <target>')
145
+ .command('impact [target]')
146
146
  .description('Blast radius analysis: what breaks if you change a symbol')
147
147
  .option('-d, --direction <dir>', 'upstream (dependants) or downstream (dependencies)', 'upstream')
148
148
  .option('-r, --repo <name>', 'Target repository')
149
+ .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
150
+ .option('-f, --file <path>', 'File path to disambiguate common names')
151
+ .option('--kind <kind>', 'Kind filter to disambiguate common names (e.g. Function, Class, Method)')
149
152
  .option('--depth <n>', 'Max relationship depth (default: 3)')
150
153
  .option('--include-tests', 'Include test files in results')
151
154
  .option('--limit <n>', 'Max symbols per depth level (default: 100)')
@@ -27,9 +27,12 @@ export declare function contextCommand(name: string, options?: {
27
27
  uid?: string;
28
28
  content?: boolean;
29
29
  }): Promise<void>;
30
- export declare function impactCommand(target: string, options?: {
30
+ export declare function impactCommand(target?: string, options?: {
31
31
  direction?: string;
32
32
  repo?: string;
33
+ uid?: string;
34
+ file?: string;
35
+ kind?: string;
33
36
  depth?: string;
34
37
  includeTests?: boolean;
35
38
  limit?: string;
package/dist/cli/tool.js CHANGED
@@ -15,8 +15,8 @@
15
15
  * See the output() function for details (#324).
16
16
  */
17
17
  import { writeSync } from 'node:fs';
18
- import { LocalBackend } from '../mcp/local/local-backend.js';
19
- import { cliErrorKey } from './cli-message.js';
18
+ import { LocalBackend, VALID_NODE_LABELS } from '../mcp/local/local-backend.js';
19
+ import { cliErrorKey, cliWarnKey } from './cli-message.js';
20
20
  import { formatDetectChangesResult } from './detect-changes-format.js';
21
21
  let _backend = null;
22
22
  async function getBackend() {
@@ -72,6 +72,11 @@ export async function queryCommand(queryText, options) {
72
72
  output(result);
73
73
  }
74
74
  export async function contextCommand(name, options) {
75
+ // Reject a `--`-prefixed uid swallowed from a following flag (see impactCommand).
76
+ if (options?.uid?.startsWith('--')) {
77
+ cliErrorKey('tool.usage.context');
78
+ process.exit(1);
79
+ }
75
80
  if (!name?.trim() && !options?.uid) {
76
81
  cliErrorKey('tool.usage.context');
77
82
  process.exit(1);
@@ -87,10 +92,25 @@ export async function contextCommand(name, options) {
87
92
  output(result);
88
93
  }
89
94
  export async function impactCommand(target, options) {
90
- if (!target?.trim()) {
95
+ // A `--`-prefixed uid means Commander swallowed a following flag as the uid
96
+ // value (e.g. `impact --uid --file x` → uid === '--file'). Reject it rather
97
+ // than forwarding a garbage uid that would silently resolve to not-found.
98
+ if (options?.uid?.startsWith('--')) {
91
99
  cliErrorKey('tool.usage.impact');
92
100
  process.exit(1);
93
101
  }
102
+ // Target is an optional positional: a uid alone is enough to resolve (parity
103
+ // with `context [name]`). Only error when neither a target nor a uid is given.
104
+ if (!target?.trim() && !options?.uid) {
105
+ cliErrorKey('tool.usage.impact');
106
+ process.exit(1);
107
+ }
108
+ // Soft-validate --kind: an unknown kind is a no-op hint (the backend scores
109
+ // it but it matches nothing), so warn and proceed rather than rejecting —
110
+ // parity with the lenient MCP surface and forward-compatible with new labels.
111
+ if (options?.kind && !VALID_NODE_LABELS.has(options.kind)) {
112
+ cliWarnKey('tool.warn.unknownKind', { kind: options.kind });
113
+ }
94
114
  try {
95
115
  const backend = await getBackend();
96
116
  const rawLimit = parseInt(options?.limit ?? '', 10);
@@ -98,7 +118,10 @@ export async function impactCommand(target, options) {
98
118
  const parsedLimit = Number.isFinite(rawLimit) ? rawLimit : undefined;
99
119
  const parsedOffset = Number.isFinite(rawOffset) ? rawOffset : undefined;
100
120
  const result = await backend.callTool('impact', {
101
- target,
121
+ target: target || undefined,
122
+ target_uid: options?.uid,
123
+ file_path: options?.file,
124
+ kind: options?.kind,
102
125
  direction: options?.direction || 'upstream',
103
126
  maxDepth: options?.depth ? parseInt(options.depth, 10) : undefined,
104
127
  includeTests: options?.includeTests ?? false,
@@ -2379,8 +2379,14 @@ export class LocalBackend {
2379
2379
  const rawOffset = typeof opts.offset === 'number' && Number.isFinite(opts.offset) ? opts.offset : 0;
2380
2380
  const paginationOffset = Math.max(0, Math.trunc(rawOffset));
2381
2381
  const summaryOnly = opts.summaryOnly ?? false;
2382
- const relTypeFilter = relationTypes.map((t) => `'${t}'`).join(', ');
2383
- const confidenceFilter = minConfidence > 0 ? ` AND r.confidence >= ${minConfidence}` : '';
2382
+ // Bind the BFS frontier query's filters as parameters (#1907 review F5):
2383
+ // node ids and relation types as bound lists, the confidence floor as a
2384
+ // bound number — no string interpolation reaches the query text. Preserve
2385
+ // the original "no confidence clause when minConfidence <= 0" behavior: an
2386
+ // unconditional `>= 0` would wrongly exclude NULL-confidence edges that the
2387
+ // unfiltered query includes.
2388
+ const safeMinConfidence = Number.isFinite(minConfidence) ? minConfidence : 0;
2389
+ const confidenceFilter = safeMinConfidence > 0 ? ' AND r.confidence >= $minConfidence' : '';
2384
2390
  const symId = sym.id || sym[0];
2385
2391
  const impacted = [];
2386
2392
  const visited = new Set([symId]);
@@ -2449,13 +2455,17 @@ export class LocalBackend {
2449
2455
  }
2450
2456
  for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
2451
2457
  const nextFrontier = [];
2452
- // Batch frontier nodes into a single Cypher query per depth level
2453
- const idList = frontier.map((id) => `'${id.replace(/'/g, "''")}'`).join(', ');
2458
+ // Batch frontier nodes into a single Cypher query per depth level.
2459
+ // ids/types/confidence are bound parameters (see above) no interpolation.
2454
2460
  const query = direction === 'upstream'
2455
- ? `MATCH (caller)-[r:CodeRelation]->(n) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, caller.id AS id, caller.name AS name, labels(caller)[0] AS type, caller.filePath AS filePath, r.type AS relType, r.confidence AS confidence`
2456
- : `MATCH (n)-[r:CodeRelation]->(callee) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, callee.id AS id, callee.name AS name, labels(callee)[0] AS type, callee.filePath AS filePath, r.type AS relType, r.confidence AS confidence`;
2461
+ ? `MATCH (caller)-[r:CodeRelation]->(n) WHERE n.id IN $frontierIds AND r.type IN $relTypes${confidenceFilter} RETURN n.id AS sourceId, caller.id AS id, caller.name AS name, labels(caller)[0] AS type, caller.filePath AS filePath, r.type AS relType, r.confidence AS confidence`
2462
+ : `MATCH (n)-[r:CodeRelation]->(callee) WHERE n.id IN $frontierIds AND r.type IN $relTypes${confidenceFilter} RETURN n.id AS sourceId, callee.id AS id, callee.name AS name, labels(callee)[0] AS type, callee.filePath AS filePath, r.type AS relType, r.confidence AS confidence`;
2457
2463
  try {
2458
- const related = await executeQuery(repo.id, query);
2464
+ const related = await executeParameterized(repo.id, query, {
2465
+ frontierIds: frontier,
2466
+ relTypes: relationTypes,
2467
+ ...(safeMinConfidence > 0 ? { minConfidence: safeMinConfidence } : {}),
2468
+ });
2459
2469
  for (const rel of related) {
2460
2470
  const relId = rel.id || rel[1];
2461
2471
  const filePath = rel.filePath || rel[4] || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.96",
3
+ "version": "1.6.6-rc.97",
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",