ucn 3.7.12 → 3.7.13

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.
@@ -92,7 +92,7 @@ ucn deadcode --exclude=test # Skip test files (most useful)
92
92
  | Quick project overview | `ucn toc` | Every file with function/class counts and line counts |
93
93
  | Find by glob pattern | `ucn find "handle*"` | Locate definitions matching a glob (supports * and ?) |
94
94
  | Text search with context | `ucn search term --context=3` | Like grep -C 3, shows surrounding lines |
95
- | Regex search | `ucn search '\d+' --regex` | Search with regex patterns (alternation, character classes, etc.) |
95
+ | Regex search (default) | `ucn search '\d+'` | Search supports regex by default (alternation, character classes, etc.) |
96
96
  | Text search filtered | `ucn search term --exclude=test` | Search only in matching files |
97
97
  | Finding all usages (not just calls) | `ucn usages <name>` | Groups into: definitions, calls, imports, type references |
98
98
  | Finding sibling/related functions | `ucn related <name>` | Name-based + structural matching (same file, shared deps). Not semantic — best for parse/format pairs |
@@ -127,7 +127,7 @@ ucn [target] <command> [name] [--flags]
127
127
  | `--staged` | Analyze staged changes (diff-impact) |
128
128
  | `--no-cache` | Force re-index after editing files |
129
129
  | `--context=N` | Lines of surrounding context in `usages`/`search` output |
130
- | `--regex` | Use search term as a regex pattern |
130
+ | `--no-regex` | Force plain text search (regex is default) |
131
131
  | `--functions` | Show per-function line counts in `stats` (complexity audit) |
132
132
 
133
133
  ## Workflow Integration
@@ -1 +1 @@
1
- ghu_Jo1ldIUK4d6zLMSctdSV2Jx8UkoXa50x6epI
1
+ ghu_OKOjTMtrqEazgazx9Dj0l0z6irgt8R3KXKET
@@ -1 +1 @@
1
- {"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzE5NjQ2MjMsIm5iZiI6MTc3MTk2NDMyMywiaWF0IjoxNzcxOTY0MzIzLCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6Im1sZW9jYSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJwdWJsaXNoIiwicmVzb3VyY2UiOiJpby5naXRodWIubWxlb2NhLyoifV19.weq7KLw5cAAB7qZpxtU45meX0nLUU1vTiHlzFB5Ecf9OTFtKM63-JSwYBXrr410pdt-7CgW6pV6aGruoIpiIBw","expires_at":1771964623}
1
+ {"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzIwMDg1ODksIm5iZiI6MTc3MjAwODI4OSwiaWF0IjoxNzcyMDA4Mjg5LCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6Im1sZW9jYSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJwdWJsaXNoIiwicmVzb3VyY2UiOiJpby5naXRodWIubWxlb2NhLyoifV19.VANcz6OV-uLS8b-UAAEu1aX4HRO4FcTO2c3Ub9r4_8dfRjh5dGQlHddmnOLUBBTf8UwWu0Ap2m4PCQSUvcvfAg","expires_at":1772008589}
package/cli/index.js CHANGED
@@ -83,8 +83,8 @@ const flags = {
83
83
  // Diff-impact options
84
84
  base: args.find(a => a.startsWith('--base='))?.split('=')[1] || null,
85
85
  staged: args.includes('--staged'),
86
- // Regex search mode
87
- regex: args.includes('--regex'),
86
+ // Regex search mode (default: ON; --no-regex to force plain text)
87
+ regex: args.includes('--no-regex') ? false : undefined,
88
88
  // Stats: per-function line counts
89
89
  functions: args.includes('--functions')
90
90
  };
@@ -106,7 +106,7 @@ const knownFlags = new Set([
106
106
  '--depth', '--direction', '--add-param', '--remove-param', '--rename-to',
107
107
  '--default', '--top', '--no-follow-symlinks',
108
108
  '--base', '--staged',
109
- '--regex', '--functions'
109
+ '--regex', '--no-regex', '--functions'
110
110
  ]);
111
111
 
112
112
  // Handle help flag
@@ -1514,7 +1514,13 @@ function findInGlobFiles(files, name) {
1514
1514
 
1515
1515
  function searchGlobFiles(files, term) {
1516
1516
  const results = [];
1517
- const regex = flags.regex ? new RegExp(term, flags.caseSensitive ? '' : 'i') : new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
1517
+ const useRegex = flags.regex !== false; // Default: regex ON
1518
+ let regex;
1519
+ if (useRegex) {
1520
+ try { regex = new RegExp(term, flags.caseSensitive ? '' : 'i'); } catch (e) { regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i'); }
1521
+ } else {
1522
+ regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
1523
+ }
1518
1524
 
1519
1525
  for (const file of files) {
1520
1526
  try {
@@ -1565,7 +1571,13 @@ function searchGlobFiles(files, term) {
1565
1571
  // ============================================================================
1566
1572
 
1567
1573
  function searchFile(filePath, lines, term) {
1568
- const regex = flags.regex ? new RegExp(term, flags.caseSensitive ? '' : 'i') : new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
1574
+ const useRegex = flags.regex !== false;
1575
+ let regex;
1576
+ if (useRegex) {
1577
+ try { regex = new RegExp(term, flags.caseSensitive ? '' : 'i'); } catch (e) { regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i'); }
1578
+ } else {
1579
+ regex = new RegExp(escapeRegExp(term), flags.caseSensitive ? '' : 'i');
1580
+ }
1569
1581
  const matches = [];
1570
1582
 
1571
1583
  lines.forEach((line, idx) => {
@@ -1694,7 +1706,7 @@ FIND CODE
1694
1706
  find <name> Find symbol definitions (supports glob: find "handle*")
1695
1707
  usages <name> All usages grouped: definitions, calls, imports, references
1696
1708
  toc Table of contents (compact; --detailed lists all symbols)
1697
- search <term> Text search (--context=N, --exclude=, --in=, --regex)
1709
+ search <term> Text search (regex default, --context=N, --exclude=, --in=)
1698
1710
  tests <name> Find test files for a function
1699
1711
 
1700
1712
  ═══════════════════════════════════════════════════════════════════════════════
@@ -1747,7 +1759,7 @@ Common Flags:
1747
1759
  --include-methods Include method calls (obj.fn) in caller/callee analysis
1748
1760
  --include-uncertain Include ambiguous/uncertain matches
1749
1761
  --include-exported Include exported symbols in deadcode
1750
- --regex Use search term as a regex pattern
1762
+ --no-regex Force plain text search (regex is default)
1751
1763
  --functions Show per-function line counts (stats command)
1752
1764
  --include-decorated Include decorated/annotated symbols in deadcode
1753
1765
  --exact Exact name match only (find)
@@ -1908,7 +1920,7 @@ function parseInteractiveFlags(tokens) {
1908
1920
  base: tokens.find(a => a.startsWith('--base='))?.split('=')[1] || null,
1909
1921
  staged: tokens.includes('--staged'),
1910
1922
  maxLines: parseInt(tokens.find(a => a.startsWith('--max-lines='))?.split('=')[1] || '0') || null,
1911
- regex: tokens.includes('--regex'),
1923
+ regex: tokens.includes('--no-regex') ? false : undefined,
1912
1924
  functions: tokens.includes('--functions'),
1913
1925
  };
1914
1926
  }
package/core/project.js CHANGED
@@ -4442,7 +4442,18 @@ class ProjectIndex {
4442
4442
  let filesScanned = 0;
4443
4443
  let filesSkipped = 0;
4444
4444
  const regexFlags = options.caseSensitive ? 'g' : 'gi';
4445
- const regex = options.regex ? new RegExp(term, regexFlags) : new RegExp(escapeRegExp(term), regexFlags);
4445
+ const useRegex = options.regex !== false; // Default: regex ON
4446
+ let regex;
4447
+ if (useRegex) {
4448
+ try {
4449
+ regex = new RegExp(term, regexFlags);
4450
+ } catch (e) {
4451
+ // Invalid regex — fall back to plain text
4452
+ regex = new RegExp(escapeRegExp(term), regexFlags);
4453
+ }
4454
+ } else {
4455
+ regex = new RegExp(escapeRegExp(term), regexFlags);
4456
+ }
4446
4457
 
4447
4458
  for (const [filePath, fileEntry] of this.files) {
4448
4459
  // Apply exclude/in filters
@@ -4465,7 +4476,7 @@ class ProjectIndex {
4465
4476
  try {
4466
4477
  const parser = getParser(language);
4467
4478
  const { findMatchesWithASTFilter } = require('../languages/utils');
4468
- const astMatches = findMatchesWithASTFilter(content, term, parser, { codeOnly: true, regex: options.regex });
4479
+ const astMatches = findMatchesWithASTFilter(content, term, parser, { codeOnly: true, regex: useRegex });
4469
4480
 
4470
4481
  for (const m of astMatches) {
4471
4482
  const match = {
@@ -417,8 +417,13 @@ function findMatchesWithASTFilter(content, term, parser, options = {}) {
417
417
  const lines = content.split('\n');
418
418
  const matches = [];
419
419
 
420
- // Create search pattern use raw regex when regex mode is enabled
421
- const regex = options.regex ? new RegExp(term, 'gi') : new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
420
+ // Default: regex mode ON. Use raw pattern unless regex=false.
421
+ let regex;
422
+ if (options.regex !== false) {
423
+ try { regex = new RegExp(term, 'gi'); } catch (e) { regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); }
424
+ } else {
425
+ regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
426
+ }
422
427
 
423
428
  lines.forEach((line, idx) => {
424
429
  const lineNum = idx + 1;
package/mcp/server.js CHANGED
@@ -196,7 +196,7 @@ FINDING CODE:
196
196
  - find <name>: Locate definitions ranked by usage count. Supports glob patterns (e.g. find "handle*" or "_update*"). Use when you know the name but not the file.
197
197
  - usages <name>: See every usage organized by type: definitions, calls, imports, references. Complete picture of how something is used. Use code_only=true to skip comments/strings.
198
198
  - toc: Get a quick overview of a project you haven't seen before — file counts, line counts, function/class counts, entry points. Use detailed=true for full symbol listing.
199
- - search <term>: Text search (like grep, respects .gitignore). Supports context=N for surrounding lines, exclude/in for file filtering. Case-insensitive by default; set case_sensitive=true for exact case. Set regex=true to use the term as a regex pattern (e.g. "\\d+" or "foo|bar").
199
+ - search <term>: Text search (like grep, respects .gitignore). Supports regex by default (e.g. "\\d+" or "foo|bar"). Supports context=N for surrounding lines, exclude/in for file filtering. Case-insensitive by default; set case_sensitive=true for exact case. Invalid regex auto-falls back to plain text.
200
200
  - tests <name>: Find test files covering a function, test case names, and how it's called in tests. Use before modifying or to find test patterns to follow.
201
201
  - deadcode: Find dead code: functions/classes with zero callers. Use during cleanup to identify safely deletable code. Excludes exported, decorated, and test symbols by default — use include_exported/include_decorated/include_tests to expand.
202
202
 
@@ -256,8 +256,8 @@ server.registerTool(
256
256
  calls_only: z.boolean().optional().describe('Only direct calls and test-case matches (tests command)'),
257
257
  max_lines: z.number().optional().describe('Max source lines for class (large classes show summary by default)'),
258
258
  direction: z.enum(['imports', 'importers', 'both']).optional().describe('Graph direction: imports (what this file uses), importers (who uses this file), both (default: both)'),
259
- term: z.string().optional().describe('Search term (plain text by default; set regex=true to use as regex pattern)'),
260
- regex: z.boolean().optional().describe('Treat search term as a regex pattern (default: false, plain text)'),
259
+ term: z.string().optional().describe('Search term (regex by default; set regex=false to force plain text)'),
260
+ regex: z.boolean().optional().describe('Treat search term as a regex pattern (default: true). Set false to force plain text escaping.'),
261
261
  functions: z.boolean().optional().describe('Include per-function line counts in stats output, sorted by size (complexity audit)'),
262
262
  add_param: z.string().optional().describe('Parameter name to add (plan command)'),
263
263
  remove_param: z.string().optional().describe('Parameter name to remove (plan command)'),
@@ -434,7 +434,7 @@ server.registerTool(
434
434
  caseSensitive: case_sensitive || false,
435
435
  exclude: searchExclude,
436
436
  in: inPath || undefined,
437
- regex: regex || false
437
+ regex: regex
438
438
  });
439
439
  return toolResult(output.formatSearch(result, term));
440
440
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.7.12",
3
+ "version": "3.7.13",
4
4
  "mcpName": "io.github.mleoca/ucn",
5
5
  "description": "Universal Code Navigator — AST-based call graph analysis for AI agents. Find callers, trace impact, detect dead code across JS/TS, Python, Go, Rust, Java, and HTML. CLI, MCP server, and agent skill.",
6
6
  "main": "index.js",