@autodev/codebase 1.0.0 → 1.0.1

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
@@ -140,33 +140,33 @@ codebase outline --clear-summarize-cache
140
140
 
141
141
  ### 🔗 Call Graph Analysis
142
142
  ```bash
143
- # Analyze function call relationships
144
- codebase call --query="functionA,functionB"
145
-
146
- # Analyze specific directory
147
- codebase call src/commands
148
-
149
- # Export analysis results
150
- codebase call --output=graph.json
151
-
152
- # Open interactive graph viewer
153
- codebase call --open
154
-
155
- # Set analysis depth
156
- codebase call --query="main" --depth=3
157
-
158
- # Specify workspace path
159
- codebase call --path=/my/project
143
+ # 📊 Statistics Overview (no --query)
144
+ codebase call # Show statistics overview
145
+ codebase call --json # JSON format
146
+ codebase call src/commands # Analyze specific directory
147
+
148
+ # 🔍 Function Query (with --query)
149
+ codebase call --query="getUser" # Single function call tree (default depth: 3)
150
+ codebase call --query="main" --depth=5 # Custom depth
151
+ codebase call --query="getUser,validateUser" # Multi-function connections (default depth: 10)
152
+
153
+ # 🎨 Visualization
154
+ codebase call --viz graph.json # Export Cytoscape.js format
155
+ codebase call --open # Open interactive viewer
156
+ codebase call --viz graph.json --open # Export and open
157
+
158
+ # Specify workspace (works for both modes)
159
+ codebase call --path=/my/project --query="main"
160
160
  ```
161
161
 
162
162
  **Query Patterns:**
163
- - **Exact match**: `--query="functionName"` or `--query="ClassName.methodName"`
163
+ - **Exact match**: `--query="functionName"` or `--query="*ClassName.methodName"`
164
164
  - **Wildcards**: `*` (any characters), `?` (single character)
165
165
  - Examples: `--query="get*"`, `--query="*User*"`, `--query="*.*.get*"`
166
- - **Single pattern**: `--query="main"` - Shows dependency tree (what it calls, who calls it)
167
- - Use `--depth` to control tree depth (default: 10)
168
- - **Multiple patterns**: `--query="main,helper"` - Analyzes connections between functions
169
- - Connection search depth is fixed at 10 (--depth is ignored)
166
+ - **Single function**: `--query="main"` - Shows call tree (upward + downward)
167
+ - Default depth: **3** (avoids excessive output)
168
+ - **Multiple functions**: `--query="main,helper"` - Analyzes connection paths between functions
169
+ - Default depth: **10** (deeper search needed for path finding)
170
170
 
171
171
  **Supported Languages:**
172
172
  - **TypeScript/JavaScript** (.ts, .tsx, .js, .jsx)
@@ -327,7 +327,7 @@ codebase search "auth" --json
327
327
  - `--limit` / `-l <number>` - Maximum number of search results (default: from config, max 50)
328
328
  - `--min-score` / `-S <number>` - Minimum similarity score for search results (0-1, default: from config)
329
329
  - `--query <patterns>` - Query patterns for call graph analysis (comma-separated)
330
- - `--output <file>` - Export analysis results to JSON file
330
+ - `--viz <file>` - Export full dependency data for visualization (cannot use with --query)
331
331
  - `--open` - Open interactive graph viewer
332
332
  - `--depth <number>` - Set analysis depth for call graphs
333
333
  - `--help` - Show all available options
package/dist/cli.js CHANGED
@@ -52907,7 +52907,7 @@ function parseMarkdown(content) {
52907
52907
  }
52908
52908
 
52909
52909
  // Private constant
52910
- const DEFAULT_MIN_COMPONENT_LINES_VALUE = 4;
52910
+ const DEFAULT_MIN_COMPONENT_LINES_VALUE = 2;
52911
52911
  // Getter function for MIN_COMPONENT_LINES (for easier testing)
52912
52912
  let currentMinComponentLines = DEFAULT_MIN_COMPONENT_LINES_VALUE;
52913
52913
  /**
@@ -63955,10 +63955,10 @@ function parsePathFilters(filtersString) {
63955
63955
  return filters;
63956
63956
  }
63957
63957
  /**
63958
- * Check whether a string contains glob pattern characters.
63958
+ * Check whether a string contains glob pattern characters or comma-separated patterns.
63959
63959
  */
63960
63960
  function isGlobPattern(input) {
63961
- return /[*?{}\[\]]/.test(input);
63961
+ return /[*?{}\[\],]/.test(input);
63962
63962
  }
63963
63963
 
63964
63964
  /**
@@ -131821,8 +131821,6 @@ class BaseAnalyzer {
131821
131821
  // ═══════════════════════════════════════════════════════
131822
131822
  async analyze() {
131823
131823
  try {
131824
- // 0. Create module node for tracking top-level calls
131825
- this.createModuleNode();
131826
131824
  const tree = this.parser.parse(this.content);
131827
131825
  const root = tree.rootNode;
131828
131826
  // 1. Extract imports first (for resolution)
@@ -131890,8 +131888,8 @@ class BaseAnalyzer {
131890
131888
  if (calleeInfo) {
131891
131889
  // 使用 CallInfo 进行过滤判断
131892
131890
  if (!this.shouldFilterCall(node, calleeInfo)) {
131893
- // Use currentFunc if inside a function, otherwise use module node ID
131894
- const caller = currentFunc || this.getModuleNodeId();
131891
+ // Use currentFunc if inside a function, otherwise ensure module node exists
131892
+ const caller = currentFunc || this.ensureModuleNode();
131895
131893
  // 根据调用类型决定如何传递 callee 参数
131896
131894
  if (calleeInfo.isGlobalCall) {
131897
131895
  // 全局直接调用(如 setTimeout):尝试用 importMap 解析
@@ -132003,6 +132001,24 @@ class BaseAnalyzer {
132003
132001
  getModuleNodeId() {
132004
132002
  return this.getModulePath();
132005
132003
  }
132004
+ /**
132005
+ * Ensure module node exists, creating it lazily if needed.
132006
+ * Returns the module node ID.
132007
+ *
132008
+ * This method is called when a top-level call is detected.
132009
+ * By creating module nodes on-demand, we avoid creating nodes for files
132010
+ * that don't have any top-level calls, reducing graph noise.
132011
+ */
132012
+ ensureModuleNode() {
132013
+ const moduleId = this.getModuleNodeId();
132014
+ // If module node already exists, return its ID
132015
+ if (this.nodes.has(moduleId)) {
132016
+ return moduleId;
132017
+ }
132018
+ // Otherwise, create the module node now
132019
+ this.createModuleNode();
132020
+ return moduleId;
132021
+ }
132006
132022
  addEdge(caller, calleeName, line) {
132007
132023
  let resolved;
132008
132024
  // 1. 尝试直接匹配(命名导入:import { foo } from './module')
@@ -133305,14 +133321,15 @@ function buildCalleeTree(nodes, rootNode, visited, currentDepth, maxDepth) {
133305
133321
  const depNode = nodes.get(depId);
133306
133322
  if (!depNode)
133307
133323
  continue;
133324
+ const childDepth = currentDepth + 1;
133308
133325
  const treeNode = {
133309
133326
  id: depNode.id,
133310
133327
  name: depNode.name,
133311
133328
  filePath: depNode.filePath,
133312
133329
  line: depNode.startLine,
133313
133330
  endLine: depNode.endLine,
133314
- depth: currentDepth,
133315
- children: buildCalleeTree(nodes, depNode, visited, currentDepth + 1, maxDepth)
133331
+ depth: childDepth,
133332
+ children: buildCalleeTree(nodes, depNode, visited, childDepth, maxDepth)
133316
133333
  };
133317
133334
  children.push(treeNode);
133318
133335
  }
@@ -133330,14 +133347,15 @@ function buildCallerTree(nodes, targetNodeId, visited, currentDepth, maxDepth) {
133330
133347
  // Find all nodes that depend on the target node
133331
133348
  for (const node of nodes.values()) {
133332
133349
  if (node.dependsOn.has(targetNodeId)) {
133350
+ const childDepth = currentDepth + 1;
133333
133351
  const treeNode = {
133334
133352
  id: node.id,
133335
133353
  name: node.name,
133336
133354
  filePath: node.filePath,
133337
133355
  line: node.startLine,
133338
133356
  endLine: node.endLine,
133339
- depth: currentDepth,
133340
- children: buildCallerTree(nodes, node.id, visited, currentDepth + 1, maxDepth)
133357
+ depth: childDepth,
133358
+ children: buildCallerTree(nodes, node.id, visited, childDepth, maxDepth)
133341
133359
  };
133342
133360
  children.push(treeNode);
133343
133361
  }
@@ -133400,7 +133418,7 @@ function findDirectConnections(matchedNodes, adj) {
133400
133418
  /**
133401
133419
  * BFS to find shortest path between two nodes
133402
133420
  */
133403
- function findShortestPath(adj, startId, endId, maxLength = 10) {
133421
+ function findShortestPath(adj, startId, endId, maxLength) {
133404
133422
  if (startId === endId) {
133405
133423
  return [startId];
133406
133424
  }
@@ -133427,13 +133445,13 @@ function findShortestPath(adj, startId, endId, maxLength = 10) {
133427
133445
  /**
133428
133446
  * Find all chains connecting queried nodes
133429
133447
  */
133430
- function findChains(matchedNodes, adj) {
133448
+ function findChains(matchedNodes, adj, maxDepth) {
133431
133449
  const chains = [];
133432
133450
  const n = matchedNodes.length;
133433
133451
  // Find paths between all pairs
133434
133452
  for (let i = 0; i < n; i++) {
133435
133453
  for (let j = i + 1; j < n; j++) {
133436
- const path = findShortestPath(adj, matchedNodes[i].id, matchedNodes[j].id);
133454
+ const path = findShortestPath(adj, matchedNodes[i].id, matchedNodes[j].id, maxDepth);
133437
133455
  if (path && path.length > 1) {
133438
133456
  chains.push({
133439
133457
  path,
@@ -133450,9 +133468,10 @@ function findChains(matchedNodes, adj) {
133450
133468
  *
133451
133469
  * @param nodes - Node map
133452
133470
  * @param query - Comma-separated function names/patterns
133471
+ * @param maxDepth - Maximum depth for path finding
133453
133472
  * @returns Connection analysis result
133454
133473
  */
133455
- function analyzeConnections(nodes, query) {
133474
+ function analyzeConnections(nodes, query, maxDepth) {
133456
133475
  // Find matching nodes
133457
133476
  const matchedNodes = findMatchingNodes(nodes, query);
133458
133477
  if (matchedNodes.length === 0) {
@@ -133469,7 +133488,7 @@ function analyzeConnections(nodes, query) {
133469
133488
  // Find direct connections
133470
133489
  const directConnections = findDirectConnections(matchedNodes);
133471
133490
  // Find chains
133472
- const chains = findChains(matchedNodes, adj);
133491
+ const chains = findChains(matchedNodes, adj, maxDepth);
133473
133492
  // Collect all involved nodes
133474
133493
  const involvedIds = new Set();
133475
133494
  for (const conn of directConnections) {
@@ -134745,7 +134764,7 @@ async function openGraphViewer(fileSystem) {
134745
134764
  /**
134746
134765
  * Format and display dependency analysis summary
134747
134766
  */
134748
- function displaySummary(result) {
134767
+ function displaySummary(result, asJson = false) {
134749
134768
  const { summary, nodes, relationships, cycles } = result;
134750
134769
  // Maximum number of examples to display for each category
134751
134770
  const MAX_EXAMPLES = 20;
@@ -134780,7 +134799,49 @@ function displaySummary(result) {
134780
134799
  .sort((a, b) => b[1] - a[1])
134781
134800
  .slice(0, MAX_EXAMPLES)
134782
134801
  .filter(([_, count]) => count > 0);
134783
- // Output summary
134802
+ // JSON output mode
134803
+ if (asJson) {
134804
+ const componentTypesObj = {};
134805
+ for (const [type, count] of componentTypes.entries()) {
134806
+ const examples = Array.from(nodes.entries())
134807
+ .filter(([_, node]) => node.componentType === type)
134808
+ .slice(0, MAX_EXAMPLES)
134809
+ .map(([id, _]) => id);
134810
+ componentTypesObj[type] = { count, examples };
134811
+ }
134812
+ const jsonOutput = {
134813
+ summary: {
134814
+ totalFiles: summary.totalFiles,
134815
+ totalNodes: summary.totalNodes,
134816
+ totalRelationships: summary.totalRelationships,
134817
+ languages: summary.languages,
134818
+ cycleCount: cycles.length,
134819
+ },
134820
+ componentTypes: componentTypesObj,
134821
+ topModules: topModules.map(([module, count]) => ({ module, dependencies: count })),
134822
+ relationships: {
134823
+ resolved: {
134824
+ count: resolvedEdges.length,
134825
+ examples: resolvedEdges.slice(0, MAX_EXAMPLES).map(edge => ({
134826
+ caller: edge.caller,
134827
+ callee: edge.callee,
134828
+ callLine: edge.callLine,
134829
+ })),
134830
+ },
134831
+ unresolved: {
134832
+ count: unresolvedEdges.length,
134833
+ examples: unresolvedEdges.slice(0, MAX_EXAMPLES).map(edge => ({
134834
+ caller: edge.caller,
134835
+ callee: edge.callee,
134836
+ callLine: edge.callLine,
134837
+ })),
134838
+ },
134839
+ },
134840
+ };
134841
+ console.log(JSON.stringify(jsonOutput, null, 2));
134842
+ return;
134843
+ }
134844
+ // Text output
134784
134845
  console.log('\nDependency Analysis Summary');
134785
134846
  console.log('==========================');
134786
134847
  console.log(`Files: ${summary.totalFiles}`);
@@ -134832,14 +134893,32 @@ function displaySummary(result) {
134832
134893
  /**
134833
134894
  * Export dependency data to JSON file
134834
134895
  */
134835
- async function exportData(result, outputPath, openInBrowser, fileSystem) {
134896
+ function validateOptions(hasQuery, hasJson, hasViz, hasOpen) {
134897
+ if (hasQuery && hasViz) {
134898
+ console.error('\n❌ Error: --viz cannot be used with --query\n');
134899
+ console.error(' Query results are for quick inspection, not visualization.\n');
134900
+ console.error(' To export full dependency data:\n');
134901
+ console.error(' codebase call --viz graph.json\n');
134902
+ console.error(' To query dependencies:\n');
134903
+ console.error(' codebase call --query "functionName"\n');
134904
+ process.exit(1);
134905
+ }
134906
+ if (hasQuery && hasOpen) {
134907
+ console.error('\n❌ Error: --open cannot be used with --query\n');
134908
+ console.error(' Use --open without --query to visualize the full dependency graph.\n');
134909
+ console.error(' To open the viewer:\n');
134910
+ console.error(' codebase call --open\n');
134911
+ process.exit(1);
134912
+ }
134913
+ }
134914
+ async function exportViz(result, outputPath, openInBrowser, fileSystem) {
134836
134915
  // Generate visualization data
134837
134916
  const viz = generateVisualizationData(result.nodes, result.relationships, result.summary);
134838
134917
  // Resolve output path (support relative paths)
134839
134918
  const resolvedPath = outputPath.startsWith('/') ? outputPath : `${process.cwd()}/${outputPath}`;
134840
134919
  // Write to file
134841
134920
  await promises.writeFile(resolvedPath, JSON.stringify(viz.cytoscape.elements, null, 2), 'utf-8');
134842
- console.log(`\nDependency data exported to: ${resolvedPath}`);
134921
+ console.log(`\n✓ Visualization data exported to: ${resolvedPath}`);
134843
134922
  console.log(` Nodes: ${viz.summary.total_nodes}`);
134844
134923
  console.log(` Edges: ${viz.summary.total_edges}`);
134845
134924
  console.log(` Languages: ${viz.summary.languages.join(', ')}`);
@@ -134889,8 +134968,8 @@ function querySingleFunction(result, query, depth, asJson) {
134889
134968
  /**
134890
134969
  * Query mode - multiple functions (connection analysis)
134891
134970
  */
134892
- function queryMultipleFunctions(result, query, asJson) {
134893
- const analysisResult = analyzeConnections(result.nodes, query);
134971
+ function queryMultipleFunctions(result, query, depth, asJson) {
134972
+ const analysisResult = analyzeConnections(result.nodes, query, depth);
134894
134973
  if (asJson) {
134895
134974
  console.log(JSON.stringify(analysisResult, null, 2));
134896
134975
  }
@@ -134903,30 +134982,48 @@ function queryMultipleFunctions(result, query, asJson) {
134903
134982
  * Query mode handler
134904
134983
  */
134905
134984
  function queryMode(result, query, depthStr, asJson) {
134906
- const depth = parseInt(depthStr, 10) || 10;
134907
134985
  const patterns = query.split(',').map(p => p.trim()).filter(p => p.length > 0);
134908
134986
  if (patterns.length === 0) {
134909
134987
  console.log('\nError: Empty query pattern');
134910
134988
  return;
134911
134989
  }
134912
- // Single pattern or wildcard pattern -> single function query
134913
- // Multiple patterns -> connection analysis
134914
- const hasWildcard = patterns.some(p => p.includes('*') || p.includes('?'));
134915
- if (patterns.length === 1 || hasWildcard) {
134916
- querySingleFunction(result, query, depth, asJson);
134990
+ // Determine depth based on query type
134991
+ let depth;
134992
+ if (depthStr) {
134993
+ // User explicitly provided depth
134994
+ depth = parseInt(depthStr, 10);
134917
134995
  }
134918
134996
  else {
134919
- queryMultipleFunctions(result, query, asJson);
134997
+ // Use different defaults based on query type
134998
+ depth = patterns.length > 1 ? 10 : 3;
134999
+ }
135000
+ // Multiple patterns (comma-separated) -> connection analysis
135001
+ // Single pattern -> single function query with depth
135002
+ if (patterns.length > 1) {
135003
+ queryMultipleFunctions(result, query, depth, asJson);
135004
+ }
135005
+ else {
135006
+ querySingleFunction(result, query, depth, asJson);
134920
135007
  }
134921
135008
  }
134922
135009
  /**
134923
135010
  * Call command handler
134924
135011
  *
134925
- * Provides dependency analysis with multiple output modes:
134926
- * - Summary mode (default): Display statistics overview
134927
- * - Export mode (--output): Export data to JSON file
134928
- * - Query mode (--query): Query specific dependencies
134929
- * - Open mode (--open): Open HTML visualization
135012
+ * Provides dependency analysis with two modes:
135013
+ *
135014
+ * 1. Full Data Mode (no --query):
135015
+ * - Summary mode (default): Display statistics overview
135016
+ * - JSON mode (--json): Display statistics in JSON format
135017
+ * - Export mode (--viz): Export visualization data to file
135018
+ * - Open mode (--open): Open HTML visualization viewer
135019
+ *
135020
+ * 2. Query Mode (with --query):
135021
+ * - Tree format (default): Display dependency tree
135022
+ * - JSON format (--json): Output query results in JSON
135023
+ *
135024
+ * Option constraints:
135025
+ * - --viz/--open cannot be used with --query
135026
+ * - --json works in both summary and query modes
134930
135027
  */
134931
135028
  async function callHandler(targetPath, options) {
134932
135029
  // Initialize logger
@@ -135037,9 +135134,12 @@ async function callHandler(targetPath, options) {
135037
135134
  workspace: fullDeps.workspace,
135038
135135
  };
135039
135136
  // Determine output mode
135040
- const hasOutput = !!options.output;
135137
+ const hasViz = !!options.viz;
135041
135138
  const hasQuery = !!options.query;
135139
+ const hasJson = !!options.json;
135042
135140
  const hasOpen = !!options.open;
135141
+ // Validate option combinations
135142
+ validateOptions(hasQuery, hasJson, hasViz, hasOpen);
135043
135143
  try {
135044
135144
  // Perform analysis
135045
135145
  logger.info('Analyzing dependencies...');
@@ -135048,13 +135148,13 @@ async function callHandler(targetPath, options) {
135048
135148
  cacheBaseDir: options.cache,
135049
135149
  });
135050
135150
  // Mode selection
135051
- if (hasOutput) {
135052
- // Export mode - Task 3
135053
- await exportData(result, options.output, hasOpen, fullDeps.fileSystem);
135054
- }
135055
- else if (hasQuery) {
135151
+ if (hasQuery) {
135056
135152
  // Query mode - Task 4
135057
- queryMode(result, options.query, options.depth || '10', options.json);
135153
+ queryMode(result, options.query, options.depth, hasJson);
135154
+ }
135155
+ else if (hasViz) {
135156
+ // Export mode - Task 3
135157
+ await exportViz(result, options.viz, hasOpen, fullDeps.fileSystem);
135058
135158
  }
135059
135159
  else if (hasOpen) {
135060
135160
  // Open mode - directly open viewer without exporting
@@ -135074,7 +135174,7 @@ async function callHandler(targetPath, options) {
135074
135174
  }
135075
135175
  else {
135076
135176
  // Summary mode (default) - Task 2
135077
- displaySummary(result);
135177
+ displaySummary(result, options.json);
135078
135178
  }
135079
135179
  // Display errors if any
135080
135180
  if (result.errors && result.errors.length > 0) {
@@ -135117,8 +135217,8 @@ function createCallCommand() {
135117
135217
  .option('-p, --path <path>', 'Working directory path', '.')
135118
135218
  .option('-c, --config <path>', 'Configuration file path')
135119
135219
  .option('--demo', 'Use demo workspace')
135120
- .option('--output <file>', 'Export dependency data to JSON file')
135121
- .option('--open', 'Open HTML visualization in browser')
135220
+ .option('--viz <file>', 'Export full dependency data for visualization (cannot use with --query)')
135221
+ .option('--open', 'Open HTML visualization viewer (cannot use with --query)')
135122
135222
  .option('--query <names>', [
135123
135223
  'Query dependencies for specific names',
135124
135224
  '',
@@ -135133,8 +135233,8 @@ function createCallCommand() {
135133
135233
  ' - Multiple patterns (comma-separated): --query="main,helper"',
135134
135234
  ' → Analyzes connections: how "main" connects to "helper"'
135135
135235
  ].join('\n '))
135136
- .option('--depth <number>', 'Query depth for dependency traversal', '10')
135137
- .option('--json', 'Output query results in JSON format')
135236
+ .option('--depth <number>', 'Query depth for dependency traversal (default: 3 for single query, 10 for multi-query)')
135237
+ .option('--json', 'Output in JSON format (works in both summary and query modes)')
135138
135238
  .option('--clear-cache', 'Clear dependency analysis cache')
135139
135239
  .option('--log-level <level>', 'Log level: debug|info|warn|error', 'error')
135140
135240
  .option('--storage <path>', 'Custom storage path')