Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b00a6e9
feat: add cli composability helpers — openGraph helper, resolveQueryO…
carlos-alm Mar 16, 2026
a28cf6f
fix: escapeCsv RFC 4180 compliance and column detection from flattene…
carlos-alm Mar 16, 2026
555c821
Merge branch 'main' into feat/cli-composability
carlos-alm Mar 16, 2026
323e704
fix: use reduce instead of spread for column width to avoid stack ove…
carlos-alm Mar 16, 2026
0abea01
Merge branch 'main' into feat/cli-composability
carlos-alm Mar 16, 2026
280acc9
fix: remove redundant double-flattening and tone down RFC 4180 claim
carlos-alm Mar 16, 2026
9a973ec
Merge remote-tracking branch 'origin/main' into feat/cli-composability
carlos-alm Mar 16, 2026
d958251
fix: guard flattenObject against non-POJO objects like Date and RegExp
carlos-alm Mar 16, 2026
5718509
Merge branch 'main' into feat/cli-composability
carlos-alm Mar 16, 2026
a8921bd
Merge branch 'main' into feat/cli-composability
carlos-alm Mar 16, 2026
026d923
fix: exclude booleans from numeric column detection and add try/final…
carlos-alm Mar 16, 2026
95c0853
Merge branch 'feat/cli-composability' of https://github.com/optave/co…
carlos-alm Mar 16, 2026
b3608d3
Merge branch 'main' into feat/cli-composability
carlos-alm Mar 16, 2026
57bab89
fix: guard top-level arrays in flattenObject and emit CSV header for …
carlos-alm Mar 16, 2026
af37e12
merge: resolve conflicts with main (add config to resolveQueryOpts)
carlos-alm Mar 16, 2026
263ebe0
fix: emit table header for empty result sets consistent with CSV path
carlos-alm Mar 16, 2026
bf6f551
fix: address Greptile review feedback on PR #461
carlos-alm Mar 16, 2026
4ab730d
Merge branch 'main' into feat/cli-composability
carlos-alm Mar 16, 2026
f425618
fix: guard vacuous isNumeric on empty result sets
carlos-alm Mar 16, 2026
1336b90
fix: document dot-notation key collision assumption in flattenObject
carlos-alm Mar 17, 2026
b9974e2
fix: add defensive guard for undefined plot HTML output
carlos-alm Mar 17, 2026
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
6 changes: 1 addition & 5 deletions src/cli/commands/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ export const command = {
astQuery(pattern, opts.db, {
kind: opts.kind,
file: opts.file,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
ndjson: opts.ndjson,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
...ctx.resolveQueryOpts(opts),
});
},
};
14 changes: 6 additions & 8 deletions src/cli/commands/audit.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { audit } from '../../presentation/audit.js';
import { explain } from '../../presentation/queries-cli.js';
import { config } from '../shared/options.js';

export const command = {
name: 'audit <target>',
Expand All @@ -24,24 +25,21 @@ export const command = {
}
},
execute([target], opts, ctx) {
const qOpts = ctx.resolveQueryOpts(opts);
if (opts.quick) {
explain(target, opts.db, {
depth: parseInt(opts.depth, 10),
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...qOpts,
});
return;
}
audit(target, opts.db, {
depth: parseInt(opts.depth, 10),
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
config: ctx.config,
noTests: qOpts.noTests,
json: qOpts.json,
config,
});
},
};
6 changes: 1 addition & 5 deletions src/cli/commands/cfg.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ export const command = {
format: opts.format,
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
ndjson: opts.ndjson,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
...ctx.resolveQueryOpts(opts),
});
},
};
22 changes: 7 additions & 15 deletions src/cli/commands/check.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EVERY_SYMBOL_KIND } from '../../domain/queries.js';
import { ConfigError } from '../../shared/errors.js';
import { config } from '../shared/options.js';

export const command = {
name: 'check [ref]',
Expand All @@ -25,6 +26,7 @@ export const command = {
],
async execute([ref], opts, ctx) {
const isDiffMode = ref || opts.staged;
const qOpts = ctx.resolveQueryOpts(opts);

if (!isDiffMode && !opts.rules) {
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
Expand All @@ -36,12 +38,7 @@ export const command = {
manifesto(opts.db, {
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
config: ctx.config,
...qOpts,
});
return;
}
Expand All @@ -55,9 +52,9 @@ export const command = {
signatures: opts.signatures || undefined,
boundaries: opts.boundaries || undefined,
depth: opts.depth ? parseInt(opts.depth, 10) : undefined,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
config: ctx.config,
noTests: qOpts.noTests,
json: qOpts.json,
config,
});

if (opts.rules) {
Expand All @@ -70,12 +67,7 @@ export const command = {
manifesto(opts.db, {
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
config: ctx.config,
...qOpts,
});
}
},
Expand Down
5 changes: 1 addition & 4 deletions src/cli/commands/children.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ export const command = {
children(name, opts.db, {
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
...ctx.resolveQueryOpts(opts),
});
},
};
6 changes: 1 addition & 5 deletions src/cli/commands/communities.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ export const command = {
functions: opts.functions,
resolution: parseFloat(opts.resolution),
drift: opts.drift,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...ctx.resolveQueryOpts(opts),
});
},
};
6 changes: 1 addition & 5 deletions src/cli/commands/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,8 @@ export const command = {
file: opts.file,
kind: opts.kind,
noSource: !opts.source,
noTests: ctx.resolveNoTests(opts),
includeTests: opts.withTestSource,
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...ctx.resolveQueryOpts(opts),
});
},
};
18 changes: 11 additions & 7 deletions src/cli/commands/cycles.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { openReadonlyOrFail } from '../../db/index.js';
import { findCycles, formatCycles } from '../../domain/graph/cycles.js';
import { openGraph } from '../shared/open-graph.js';

export const command = {
name: 'cycles',
Expand All @@ -12,12 +12,16 @@ export const command = {
['-j, --json', 'Output as JSON'],
],
execute(_args, opts, ctx) {
const db = openReadonlyOrFail(opts.db);
const cycles = findCycles(db, {
fileLevel: !opts.functions,
noTests: ctx.resolveNoTests(opts),
});
db.close();
const { db, close } = openGraph(opts);
let cycles;
try {
cycles = findCycles(db, {
fileLevel: !opts.functions,
noTests: ctx.resolveNoTests(opts),
});
} finally {
close();
}

if (opts.json) {
console.log(JSON.stringify({ cycles, count: cycles.length }, null, 2));
Expand Down
6 changes: 1 addition & 5 deletions src/cli/commands/dataflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,9 @@ export const command = {
dataflow(name, opts.db, {
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
ndjson: opts.ndjson,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
impact: opts.impact,
depth: parseInt(opts.depth, 10),
...ctx.resolveQueryOpts(opts),
});
},
};
6 changes: 1 addition & 5 deletions src/cli/commands/deps.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ export const command = {
queryOpts: true,
execute([file], opts, ctx) {
fileDeps(file, opts.db, {
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...ctx.resolveQueryOpts(opts),
});
},
};
7 changes: 1 addition & 6 deletions src/cli/commands/diff-impact.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,8 @@ export const command = {
ref,
staged: opts.staged,
depth: parseInt(opts.depth, 10),
noTests: ctx.resolveNoTests(opts),
json: opts.json,
format: opts.format,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
config: ctx.config,
...ctx.resolveQueryOpts(opts),
});
},
};
63 changes: 33 additions & 30 deletions src/cli/commands/export.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import fs from 'node:fs';
import { openReadonlyOrFail } from '../../db/index.js';
import {
exportDOT,
exportGraphML,
Expand All @@ -8,6 +7,7 @@ import {
exportMermaid,
exportNeo4jCSV,
} from '../../features/export.js';
import { openGraph } from '../shared/open-graph.js';

export const command = {
name: 'export',
Expand All @@ -23,7 +23,7 @@ export const command = {
['-o, --output <file>', 'Write to file instead of stdout'],
],
execute(_args, opts, ctx) {
const db = openReadonlyOrFail(opts.db);
const { db, close } = openGraph(opts);
const exportOpts = {
fileLevel: !opts.functions,
noTests: ctx.resolveNoTests(opts),
Expand All @@ -32,38 +32,41 @@ export const command = {
};

let output;
switch (opts.format) {
case 'mermaid':
output = exportMermaid(db, exportOpts);
break;
case 'json':
output = JSON.stringify(exportJSON(db, exportOpts), null, 2);
break;
case 'graphml':
output = exportGraphML(db, exportOpts);
break;
case 'graphson':
output = JSON.stringify(exportGraphSON(db, exportOpts), null, 2);
break;
case 'neo4j': {
const csv = exportNeo4jCSV(db, exportOpts);
if (opts.output) {
const base = opts.output.replace(/\.[^.]+$/, '') || opts.output;
fs.writeFileSync(`${base}-nodes.csv`, csv.nodes, 'utf-8');
fs.writeFileSync(`${base}-relationships.csv`, csv.relationships, 'utf-8');
db.close();
console.log(`Exported to ${base}-nodes.csv and ${base}-relationships.csv`);
return;
try {
switch (opts.format) {
case 'mermaid':
output = exportMermaid(db, exportOpts);
break;
case 'json':
output = JSON.stringify(exportJSON(db, exportOpts), null, 2);
break;
case 'graphml':
output = exportGraphML(db, exportOpts);
break;
case 'graphson':
output = JSON.stringify(exportGraphSON(db, exportOpts), null, 2);
break;
case 'neo4j': {
const csv = exportNeo4jCSV(db, exportOpts);
if (opts.output) {
const base = opts.output.replace(/\.[^.]+$/, '') || opts.output;
fs.writeFileSync(`${base}-nodes.csv`, csv.nodes, 'utf-8');
fs.writeFileSync(`${base}-relationships.csv`, csv.relationships, 'utf-8');
console.log(`Exported to ${base}-nodes.csv and ${base}-relationships.csv`);
} else {
output = `--- nodes.csv ---\n${csv.nodes}\n\n--- relationships.csv ---\n${csv.relationships}`;
}
break;
}
output = `--- nodes.csv ---\n${csv.nodes}\n\n--- relationships.csv ---\n${csv.relationships}`;
break;
default:
output = exportDOT(db, exportOpts);
break;
}
default:
output = exportDOT(db, exportOpts);
break;
} finally {
close();
}

db.close();
if (output === undefined) return;

if (opts.output) {
fs.writeFileSync(opts.output, output, 'utf-8');
Expand Down
6 changes: 1 addition & 5 deletions src/cli/commands/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@ export const command = {
options: [['--unused', 'Show only exports with zero consumers (dead exports)']],
execute([file], opts, ctx) {
fileExports(file, opts.db, {
noTests: ctx.resolveNoTests(opts),
json: opts.json,
unused: opts.unused || false,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...ctx.resolveQueryOpts(opts),
});
},
};
6 changes: 1 addition & 5 deletions src/cli/commands/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ export const command = {
depth: parseInt(opts.depth, 10),
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...ctx.resolveQueryOpts(opts),
});
},
};
6 changes: 1 addition & 5 deletions src/cli/commands/fn-impact.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ export const command = {
depth: parseInt(opts.depth, 10),
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...ctx.resolveQueryOpts(opts),
});
},
};
6 changes: 1 addition & 5 deletions src/cli/commands/impact.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ export const command = {
queryOpts: true,
execute([file], opts, ctx) {
impactAnalysis(file, opts.db, {
noTests: ctx.resolveNoTests(opts),
json: opts.json,
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
ndjson: opts.ndjson,
...ctx.resolveQueryOpts(opts),
});
},
};
Loading
Loading