Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/cli/commands/ast.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { ConfigError } from '../../shared/errors.js';

export const command = {
Expand All @@ -6,7 +7,7 @@ export const command = {
queryOpts: true,
options: [
['-k, --kind <kind>', 'Filter by AST node kind (call, new, string, regex, throw, await)'],
['-f, --file <path>', 'Scope to file (partial match)'],
['-f, --file <path>', 'Scope to file (partial match, repeatable)', collectFile],
],
async execute([pattern], opts, ctx) {
const { AST_NODE_KINDS, astQuery } = await import('../../ast.js');
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/audit.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { audit } from '../../presentation/audit.js';
import { explain } from '../../presentation/queries-cli.js';
Expand All @@ -10,7 +11,7 @@ export const command = {
['-d, --db <path>', 'Path to graph.db'],
['--quick', 'Structural summary only (skip impact analysis and health metrics)'],
['--depth <n>', 'Impact/explain depth', '3'],
['-f, --file <path>', 'Scope to file (partial match)'],
['-f, --file <path>', 'Scope to file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
['-T, --no-tests', 'Exclude test/spec files from results'],
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/batch.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'node:fs';
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { BATCH_COMMANDS, multiBatchData, splitTargets } from '../../features/batch.js';
import { batch } from '../../presentation/batch.js';
Expand All @@ -12,7 +13,7 @@ export const command = {
['--from-file <path>', 'Read targets from file (JSON array or newline-delimited)'],
['--stdin', 'Read targets from stdin (JSON array)'],
['--depth <n>', 'Traversal depth passed to underlying command'],
['-f, --file <path>', 'Scope to file (partial match)'],
['-f, --file <path>', 'Scope to file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
['-T, --no-tests', 'Exclude test/spec files from results'],
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/cfg.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';

export const command = {
Expand All @@ -6,7 +7,7 @@ export const command = {
queryOpts: true,
options: [
['--format <fmt>', 'Output format: text, dot, mermaid', 'text'],
['-f, --file <path>', 'Scope to file (partial match)'],
['-f, --file <path>', 'Scope to file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
],
validate([_name], opts) {
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/check.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { ConfigError } from '../../shared/errors.js';
import { config } from '../shared/options.js';
Expand Down Expand Up @@ -31,7 +32,7 @@ export const command = {
['--signatures', 'Assert no function declaration lines were modified'],
['--boundaries', 'Assert no cross-owner boundary violations'],
['--depth <n>', 'Max BFS depth for blast radius (default: 3)'],
['-f, --file <path>', 'Scope to file (partial match, manifesto mode)'],
['-f, --file <path>', 'Scope to file (partial match, repeatable, manifesto mode)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind (manifesto mode)'],
['-T, --no-tests', 'Exclude test/spec files from results'],
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
Expand Down
7 changes: 6 additions & 1 deletion src/cli/commands/children.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { children } from '../../presentation/queries-cli.js';

Expand All @@ -6,7 +7,11 @@ export const command = {
description: 'List parameters, properties, and constants of a symbol',
options: [
['-d, --db <path>', 'Path to graph.db'],
['-f, --file <path>', 'Scope search to symbols in this file (partial match)'],
[
'-f, --file <path>',
'Scope search to symbols in this file (partial match, repeatable)',
collectFile,
],
['-k, --kind <kind>', 'Filter to a specific symbol kind'],
['-T, --no-tests', 'Exclude test/spec files from results'],
['-j, --json', 'Output as JSON'],
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/complexity.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';

export const command = {
Expand All @@ -13,7 +14,7 @@ export const command = {
],
['--above-threshold', 'Only functions exceeding warn thresholds'],
['--health', 'Show health metrics (Halstead, MI) columns'],
['-f, --file <path>', 'Scope to file (partial match)'],
['-f, --file <path>', 'Scope to file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
['-T, --no-tests', 'Exclude test/spec files from results'],
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
Expand Down
7 changes: 6 additions & 1 deletion src/cli/commands/context.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { context } from '../../presentation/queries-cli.js';

Expand All @@ -7,7 +8,11 @@ export const command = {
queryOpts: true,
options: [
['--depth <n>', 'Include callee source up to N levels deep', '0'],
['-f, --file <path>', 'Scope search to functions in this file (partial match)'],
[
'-f, --file <path>',
'Scope search to functions in this file (partial match, repeatable)',
collectFile,
],
['-k, --kind <kind>', 'Filter to a specific symbol kind'],
['--no-source', 'Metadata only (skip source extraction)'],
['--with-test-source', 'Include test source code'],
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/dataflow.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';

export const command = {
name: 'dataflow <name>',
description: 'Show data flow for a function: parameters, return consumers, mutations',
queryOpts: true,
options: [
['-f, --file <path>', 'Scope to file (partial match)'],
['-f, --file <path>', 'Scope to file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
['--impact', 'Show data-dependent blast radius'],
['--depth <n>', 'Max traversal depth', '5'],
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/flow.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';

export const command = {
Expand All @@ -8,7 +9,7 @@ export const command = {
options: [
['--list', 'List all entry points grouped by type'],
['--depth <n>', 'Max forward traversal depth', '10'],
['-f, --file <path>', 'Scope to a specific file (partial match)'],
['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
],
validate([name], opts) {
Expand Down
7 changes: 6 additions & 1 deletion src/cli/commands/fn-impact.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { fnImpact } from '../../presentation/queries-cli.js';

Expand All @@ -7,7 +8,11 @@ export const command = {
queryOpts: true,
options: [
['--depth <n>', 'Max transitive depth', '5'],
['-f, --file <path>', 'Scope search to functions in this file (partial match)'],
[
'-f, --file <path>',
'Scope search to functions in this file (partial match, repeatable)',
collectFile,
],
['-k, --kind <kind>', 'Filter to a specific symbol kind'],
],
validate([_name], opts) {
Expand Down
6 changes: 4 additions & 2 deletions src/cli/commands/owners.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { collectFile } from '../../db/query-builder.js';

export const command = {
name: 'owners [target]',
description: 'Show CODEOWNERS mapping for files and functions',
options: [
['-d, --db <path>', 'Path to graph.db'],
['--owner <owner>', 'Filter to a specific owner'],
['--boundary', 'Show cross-owner boundary edges'],
['-f, --file <path>', 'Scope to a specific file'],
['-f, --file <path>', 'Scope to a specific file (repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
['-T, --no-tests', 'Exclude test/spec files'],
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
Expand All @@ -16,7 +18,7 @@ export const command = {
owners(opts.db, {
owner: opts.owner,
boundary: opts.boundary,
file: opts.file || target,
file: opts.file && opts.file.length > 0 ? opts.file : target,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
Expand Down
7 changes: 6 additions & 1 deletion src/cli/commands/query.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { fnDeps, symbolPath } from '../../presentation/queries-cli.js';

Expand All @@ -7,7 +8,11 @@ export const command = {
queryOpts: true,
options: [
['--depth <n>', 'Transitive caller depth', '3'],
['-f, --file <path>', 'Scope search to functions in this file (partial match)'],
[
'-f, --file <path>',
'Scope search to functions in this file (partial match, repeatable)',
collectFile,
],
['-k, --kind <kind>', 'Filter to a specific symbol kind'],
['--path <to>', 'Path mode: find shortest path to <to>'],
['--kinds <kinds>', 'Path mode: comma-separated edge kinds to follow (default: calls)'],
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/roles.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { VALID_ROLES } from '../../domain/queries.js';
import { roles } from '../../presentation/queries-cli.js';

Expand All @@ -7,7 +8,7 @@ export const command = {
options: [
['-d, --db <path>', 'Path to graph.db'],
['--role <role>', `Filter by role (${VALID_ROLES.join(', ')})`],
['-f, --file <path>', 'Scope to a specific file (partial match)'],
['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
['-T, --no-tests', 'Exclude test/spec files'],
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
['-j, --json', 'Output as JSON'],
Expand Down
10 changes: 8 additions & 2 deletions src/cli/commands/search.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { search } from '../../domain/search/index.js';

export const command = {
Expand All @@ -11,7 +12,7 @@ export const command = {
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
['--min-score <score>', 'Minimum similarity threshold', '0.2'],
['-k, --kind <kind>', 'Filter by kind: function, method, class'],
['--file <pattern>', 'Filter by file path pattern'],
['--file <pattern>', 'Filter by file path pattern (repeatable)', collectFile],
['--rrf-k <number>', 'RRF k parameter for multi-query ranking', '60'],
['--mode <mode>', 'Search mode: hybrid, semantic, keyword (default: hybrid)'],
['-j, --json', 'Output as JSON'],
Expand All @@ -25,14 +26,19 @@ export const command = {
}
},
async execute([query], opts, ctx) {
// --file collects into an array; pass single element unwrapped for single
// value, or pass the raw array for multi-file scoping.
const fileArr = opts.file || [];
const filePattern =
fileArr.length === 1 ? fileArr[0] : fileArr.length > 1 ? fileArr : undefined;
await search(query, opts.db, {
limit: parseInt(opts.limit, 10),
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
noTests: ctx.resolveNoTests(opts),
minScore: parseFloat(opts.minScore),
model: opts.model,
kind: opts.kind,
filePattern: opts.file,
filePattern,
rrfK: parseInt(opts.rrfK, 10),
mode: opts.mode,
json: opts.json,
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/sequence.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';

export const command = {
Expand All @@ -7,7 +8,7 @@ export const command = {
options: [
['--depth <n>', 'Max forward traversal depth', '10'],
['--dataflow', 'Annotate with parameter names and return arrows from dataflow table'],
['-f, --file <path>', 'Scope to a specific file (partial match)'],
['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind'],
],
validate([_name], opts) {
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/triage.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND, VALID_ROLES } from '../../domain/queries.js';
import { ConfigError } from '../../shared/errors.js';

Expand Down Expand Up @@ -53,7 +54,7 @@ export const command = {
],
['--min-score <score>', 'Only show symbols with risk score >= threshold'],
['--role <role>', 'Filter by role (entry, core, utility, adapter, leaf, dead)'],
['-f, --file <path>', 'Scope to a specific file (partial match)'],
['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
['-k, --kind <kind>', 'Filter by symbol kind (function, method, class)'],
['-T, --no-tests', 'Exclude test/spec files from results'],
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
Expand Down
64 changes: 60 additions & 4 deletions src/db/query-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,55 @@ export function escapeLike(s) {
return s.replace(/[%_\\]/g, '\\$&');
}

/**
* Normalize a file filter value (string, string[], or falsy) into a flat array.
* Returns an empty array when the input is falsy.
* @param {string|string[]|undefined|null} file
* @returns {string[]}
*/
export function normalizeFileFilter(file) {
if (!file) return [];
return Array.isArray(file) ? file : [file];
}

/**
* Build a SQL condition + params for a multi-value file LIKE filter.
* Returns `{ sql: '', params: [] }` when the filter is empty.
*
* @param {string|string[]} file - One or more partial file paths
* @param {string} [column='file'] - The column name to filter on (e.g. 'n.file', 'a.file')
* @returns {{ sql: string, params: string[] }}
*/
export function buildFileConditionSQL(file, column = 'file') {
validateColumn(column);
const files = normalizeFileFilter(file);
if (files.length === 0) return { sql: '', params: [] };
if (files.length === 1) {
return {
sql: ` AND ${column} LIKE ? ESCAPE '\\'`,
params: [`%${escapeLike(files[0])}%`],
};
}
const clauses = files.map(() => `${column} LIKE ? ESCAPE '\\'`);
return {
sql: ` AND (${clauses.join(' OR ')})`,
params: files.map((f) => `%${escapeLike(f)}%`),
};
}

/**
* Commander option accumulator for repeatable `--file` flag.
* Use as: `['-f, --file <path>', 'Scope to file (partial match, repeatable)', collectFile]`
* @param {string} val - New value from Commander
* @param {string[]} acc - Accumulated values (undefined on first call)
* @returns {string[]}
*/
export function collectFile(val, acc) {
acc = acc || [];
acc.push(val);
return acc;
}

// ─── Standalone Helpers ──────────────────────────────────────────────

/**
Expand Down Expand Up @@ -171,11 +220,18 @@ export class NodeQuery {
return this;
}

/** WHERE n.file LIKE ? (no-op if falsy). Escapes LIKE wildcards in the value. */
/** WHERE n.file LIKE ? (no-op if falsy). Accepts a single string or string[]. */
fileFilter(file) {
if (!file) return this;
this.#conditions.push("n.file LIKE ? ESCAPE '\\'");
this.#params.push(`%${escapeLike(file)}%`);
const files = normalizeFileFilter(file);
if (files.length === 0) return this;
if (files.length === 1) {
this.#conditions.push("n.file LIKE ? ESCAPE '\\'");
this.#params.push(`%${escapeLike(files[0])}%`);
} else {
const clauses = files.map(() => "n.file LIKE ? ESCAPE '\\'");
this.#conditions.push(`(${clauses.join(' OR ')})`);
this.#params.push(...files.map((f) => `%${escapeLike(f)}%`));
}
return this;
}

Expand Down
Loading
Loading