fix(cli): make node symbol positional optional so node -f <file> works (#1044) (#1051)

`codegraph node` was defined with a required `<name>` positional, so
commander.js rejected `codegraph node -f <file>` with "missing required
argument 'name'" before the action ran — making file-read mode (the CLI
face of the codegraph_node MCP tool's file mode) unreachable. The action
body already handled an absent name.

Make `name` optional (`[name]`), validate that a symbol or a file is
supplied (friendly usage hint instead of a cryptic commander error when
neither is), and guard the name-based arg branches so they never run on
undefined. Adds an end-to-end regression test across all four paths.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-28 21:54:26 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 0da2dcec8e
commit 0d331b9017
3 changed files with 97 additions and 5 deletions
+16 -5
View File
@@ -1140,21 +1140,32 @@ program
});
/**
* codegraph node <name>
* codegraph node [name]
*
* The CLI face of the MCP codegraph_node tool: one symbol's source +
* caller/callee trail, or a whole file with line numbers + dependents
* (Read-parity). Same subagent/non-MCP rationale as `explore`.
*
* `name` is OPTIONAL because `--file` (file-read mode) carries no symbol —
* a required `<name>` made `codegraph node -f <file>` unreachable (#1044).
*/
program
.command('node <name>')
.command('node [name]')
.description('One symbol\'s source + caller/callee trail, or read a file with line numbers + dependents (same output as the codegraph_node MCP tool)')
.option('-p, --path <path>', 'Project path')
.option('-f, --file <file>', 'Treat as file mode (or disambiguate a symbol to this file)')
.option('--offset <number>', 'File mode: 1-based start line')
.option('--limit <number>', 'File mode: maximum lines')
.option('--symbols-only', 'File mode: just the symbol map + dependents')
.action(async (name: string, options: { path?: string; file?: string; offset?: string; limit?: string; symbolsOnly?: boolean }) => {
.action(async (name: string | undefined, options: { path?: string; file?: string; offset?: string; limit?: string; symbolsOnly?: boolean }) => {
// Need a symbol (positional) OR a file (--file / a path-like positional).
// With [name] optional, a bare `codegraph node` reaches here with neither
// and must be told what to pass, rather than crashing downstream.
if (!name && !options.file) {
error("Pass a symbol name (e.g. 'codegraph node parseToken') or a file (e.g. 'codegraph node -f src/auth.ts', or 'codegraph node src/auth.ts').");
process.exit(1);
}
const projectPath = resolveProjectPath(options.path);
try {
@@ -1177,9 +1188,9 @@ program
if (options.file) {
args.file = options.file;
if (name && name !== options.file) args.symbol = name;
} else if (name.includes('/') || name.includes('\\')) {
} else if (name && (name.includes('/') || name.includes('\\'))) {
args.file = name.replace(/\\/g, '/');
} else {
} else if (name) {
args.symbol = name;
args.includeCode = true;
}