Skip to content

Add mcp-ts-introspect: TypeScript package introspection tool#18

Open
genesiscz wants to merge 1 commit intomasterfrom
feat/mcp-ts-introspect
Open

Add mcp-ts-introspect: TypeScript package introspection tool#18
genesiscz wants to merge 1 commit intomasterfrom
feat/mcp-ts-introspect

Conversation

@genesiscz
Copy link
Owner

@genesiscz genesiscz commented Feb 9, 2026

Summary

  • New mcp-ts-introspect tool for introspecting TypeScript packages (exports, types, structure)
  • Includes CLI interface, MCP server mode, caching, and package resolver
  • Simplifies README.md (removes outdated tool listing)

Test plan

  • Verify tools mcp-ts-introspect --help works
  • Test introspection on a sample package
  • Verify MCP server mode starts correctly

Summary by CodeRabbit

Release Notes

  • New Features

    • Added TypeScript introspection tool with three modes: package inspection, source analysis, and project analysis.
    • Integrated caching with 7-day TTL to improve performance.
    • Support for filtering results by search term and limiting output size.
    • Multiple output formats (stdout, clipboard, file) with JSON and human-readable options.
    • MCP server mode for AI integration.
  • Documentation

    • Added comprehensive README with CLI usage examples and feature overview.
  • Chores

    • Updated TypeScript compiler options for enhanced compatibility.

Copilot AI review requested due to automatic review settings February 9, 2026 03:31
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete TypeScript introspection tool (mcp-ts-introspect) with CLI and MCP server capabilities. The implementation includes modules for package resolution, export extraction, caching with TTL, and introspection across three modes (package, source, project). A comprehensive README documents the CLI usage, features, and MCP configuration. Configuration updates enable module compatibility.

Changes

Cohort / File(s) Summary
Core Introspection Logic
src/mcp-ts-introspect/introspect.ts, src/mcp-ts-introspect/exportExtractor.ts, src/mcp-ts-introspect/packageResolver.ts
Implements three introspection functions (introspectPackage, introspectSource, introspectProject), export extraction via AST traversal, and multi-strategy package resolution with support for npm, pnpm, and custom layouts. Includes timeout enforcement and deduplication logic.
Infrastructure & Utilities
src/mcp-ts-introspect/cache.ts, src/mcp-ts-introspect/types.ts, src/mcp-ts-introspect/utils.ts
Provides TTL-based caching (7-day default), shared type definitions (ExportInfo, IntrospectOptions, PackageLocation, CacheEntry), and filtering/limiting utility for export results.
User-Facing Interfaces
src/mcp-ts-introspect/index.ts, src/mcp-ts-introspect/mcp-server.ts
Implements interactive CLI with Enquirer prompts, multi-mode input handling, flexible output formatting (stdout/clipboard/file), and MCP server with Zod-validated tools for remote introspection.
Documentation & Configuration
src/mcp-ts-introspect/README.md, tsconfig.json
Comprehensive README covering CLI usage, introspection modes, options, examples, and MCP server setup. TypeScript config additions (esModuleInterop, downlevelIteration) for compatibility.

Sequence Diagram

sequenceDiagram
    participant User as User/CLI
    participant Index as CLI Handler<br/>(index.ts)
    participant Resolver as PackageResolver<br/>(packageResolver.ts)
    participant Introspect as Introspection<br/>(introspect.ts)
    participant Extractor as ExportExtractor<br/>(exportExtractor.ts)
    participant Cache as Cache<br/>(cache.ts)
    participant Output as Output Formatter<br/>(utils.ts)
    
    User->>Index: CLI args (mode, package/source/path)
    Index->>Index: Interactive prompts for missing inputs
    Index->>Introspect: introspectPackage/Source/Project(...)
    
    alt Package Mode
        Introspect->>Resolver: findPackageJsonAndDir()
        Resolver-->>Introspect: PackageLocation
        Introspect->>Resolver: findDeclarationFiles()
        Resolver-->>Introspect: .d.ts file paths
        Introspect->>Cache: loadCache()
        Cache-->>Introspect: cached exports or null
    end
    
    alt Cache Miss or Non-Package
        Introspect->>Extractor: extractExports() per file
        Extractor-->>Introspect: ExportInfo[]
        Introspect->>Cache: saveCache()
    end
    
    Introspect->>Output: filterExports(searchTerm, limit)
    Output-->>Introspect: filtered ExportInfo[]
    Introspect-->>Index: ExportInfo[]
    
    Index->>Index: formatExports (JSON/human-readable)
    Index->>User: stdout/clipboard/file output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main change: adding a new TypeScript package introspection tool named mcp-ts-introspect, which is the primary focus of this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mcp-ts-introspect

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @genesiscz, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new capability to the GenesisTools suite: a TypeScript introspection tool. This tool empowers developers and AI assistants to deeply understand TypeScript codebases and packages by programmatically extracting detailed information about their exports, types, and structure. The addition of this tool is accompanied by a necessary cleanup and simplification of the project's main documentation, ensuring clarity and relevance.

Highlights

  • New TypeScript Introspection Tool: Introduced mcp-ts-introspect, a powerful new tool for analyzing TypeScript packages, source code, and projects to extract export information.
  • Dual Operation Modes: The new tool supports both a command-line interface (CLI) for direct use and an MCP (Model Context Protocol) server mode for integration with AI assistants.
  • Comprehensive Introspection Features: The tool can extract function, class, type, and constant exports, including full TypeScript type signatures and JSDoc comments, with support for regex-based filtering and result limiting.
  • Robust Package Resolution and Caching: It includes advanced package resolution capabilities (supporting npm, yarn, pnpm) and file-based caching with a 7-day TTL to speed up repeated lookups.
  • README.md Simplification: The main README.md has been significantly streamlined by removing outdated tool listings and sections, focusing on the core offerings.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • README.md
    • Removed the 'DeepWiki' badge and the entire 'Claude Code Plugin' section.
    • Updated and streamlined the 'Table of Contents' and 'Available Tools' sections, removing several older tools and adding the new 'MCP TS Introspect' and 'TS AI Indexer'.
    • Reordered the 'Git Commit' tool to be feat(readme): CLI markdown rendering with Mermaid support #11 in the list.
    • Cleaned up markdown formatting for lists and tables, removing redundant hyphens.
    • Removed 'Useful notes' sections related to MCP Global Installation and Python Package Management Tips.
  • src/mcp-ts-introspect/README.md
    • Added new documentation for the mcp-ts-introspect tool, detailing its purpose, usage, introspection modes (package, source, project), CLI options, examples, features, and MCP server configuration.
  • src/mcp-ts-introspect/cache.ts
    • Added a new file implementing caching logic for introspection results, including a 7-day time-to-live (TTL) and file-based storage.
  • src/mcp-ts-introspect/exportExtractor.ts
    • Added a new file containing the core logic for extracting TypeScript exports (functions, classes, types, constants), their type signatures, and JSDoc descriptions using ts-morph.
  • src/mcp-ts-introspect/index.ts
    • Added the main entry point for the mcp-ts-introspect CLI tool, handling argument parsing, interactive prompts, calling introspection functions, formatting output, and managing output destinations (stdout, clipboard, file). It also orchestrates the MCP server startup.
  • src/mcp-ts-introspect/introspect.ts
    • Added a new file containing the primary introspection logic for packages, source code, and projects. It integrates ts-morph for parsing, utilizes the caching mechanism, and includes package resolution capabilities with a timeout for operations.
  • src/mcp-ts-introspect/mcp-server.ts
    • Added a new file implementing the MCP server functionality, defining introspect-package, introspect-source, and introspect-project tools for AI assistants with Zod schema validation for inputs.
  • src/mcp-ts-introspect/packageResolver.ts
    • Added a new file responsible for locating TypeScript packages and their declaration files (.d.ts) using various strategies, including require.resolve, node_modules scanning, .pnpm directory checks, and package.json fields.
  • src/mcp-ts-introspect/types.ts
    • Added a new file defining TypeScript interfaces for the data structures used by the introspection tool, such as ExportInfo, IntrospectOptions, PackageLocation, and CacheEntry.
  • src/mcp-ts-introspect/utils.ts
    • Added a new file providing utility functions, specifically filterExports for applying search terms and result limits to the extracted export information.
  • tsconfig.json
    • Added esModuleInterop: true and downlevelIteration: true to the compiler options for improved compatibility.
Activity
  • The pull request introduces a new feature and updates documentation. No specific reviewer comments or interactions are provided in the context.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new and powerful TypeScript introspection tool, mcp-ts-introspect, which is a great addition to the GenesisTools suite. The implementation is robust, covering CLI, MCP server mode, caching, and various package manager quirks. The accompanying documentation is thorough. I've also noticed a significant cleanup of the main README.md, which improves its focus and readability. My review includes a few suggestions for code refinement, a bug fix for deduplication logic, and some minor documentation consistency improvements. Overall, this is a high-quality contribution.

Comment on lines +98 to +100
const uniqueExports = Array.from(
new Map(allExports.map(exp => [exp.name, exp])).values()
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current deduplication logic for package exports only considers the export name. This could lead to incorrect results if a package exports multiple items with the same name from different entry points (e.g., pkg/a and pkg/b). To ensure correctness, deduplication should be based on both the name and the type signature, similar to how it's done in introspectProject.

Suggested change
const uniqueExports = Array.from(
new Map(allExports.map(exp => [exp.name, exp])).values()
);
const uniqueExports = Array.from(
new Map(allExports.map(exp => [`${exp.name}:${exp.typeSignature}`, exp])).values()
);

README.md Outdated
- `--all, -a` - All uncommitted files (default)
- `--commits, -c NUM` - Files from last NUM commits
- `--staged, -s` - Only staged files
- `--unstaged, -u` - Only unstaged files
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line has trailing whitespace, which should be removed for cleaner formatting.

Suggested change
- `--unstaged, -u` - Only unstaged files
- `--unstaged, -u` - Only unstaged files

README.md Outdated
---

### 6. 🔄 Watch
### 6. 🔄 Watch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This heading has a trailing space. It should be removed for consistency and clean formatting.

Suggested change
### 6. 🔄 Watch
### 6. 🔄 Watch

README.md Outdated

<div align="center">

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line contains only whitespace. It can be made an empty line to avoid potential rendering issues and improve readability.

Comment on lines +112 to +113
"command": "tools",
"args": ["mcp-ts-introspect", "--mcp"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The MCP configuration example here uses "command": "tools", which might not work reliably with some clients like Claude Desktop. The main README.md correctly notes that a full, absolute path to the command is often required. It would be beneficial to update this example to reflect that best practice for a better user experience.

Suggested change
"command": "tools",
"args": ["mcp-ts-introspect", "--mcp"]
"command": "/path/to/GenesisTools/tools",
"args": ["mcp-ts-introspect", "--mcp"]

Comment on lines +82 to +110
function getDescription(node: Node): string | null {
// Try to get JSDoc comments
if (!('getJsDocs' in node)) {
return null;
}

const jsDocs = (node as any).getJsDocs();

for (const jsDoc of jsDocs) {
const description = jsDoc.getDescription();
if (description) {
return description.trim();
}
}

// Check parent node for JSDoc (useful for variable declarations)
const parent = node.getParent();
if (parent && 'getJsDocs' in parent) {
const parentJsDocs = (parent as any).getJsDocs();
for (const jsDoc of parentJsDocs) {
const description = jsDoc.getDescription();
if (description) {
return description.trim();
}
}
}

return null;
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation uses the in operator and any casts to check for and access getJsDocs. A more idiomatic and type-safe approach with ts-morph is to use the Node.isJSDocableNode() type guard. This avoids any and makes the code cleaner and more maintainable.

function getDescription(node: Node): string | null {
    // Try to get JSDoc comments from the node itself
    if (Node.isJSDocableNode(node)) {
        for (const jsDoc of node.getJsDocs()) {
            const description = jsDoc.getDescription();
            if (description) {
                return description.trim();
            }
        }
    }

    // Check parent node for JSDoc (useful for variable declarations)
    const parent = node.getParent();
    if (parent && Node.isJSDocableNode(parent)) {
        for (const jsDoc of parent.getJsDocs()) {
            const description = jsDoc.getDescription();
            if (description) {
                return description.trim();
            }
        }
    }

    return null;
}

Comment on lines +260 to +261
searchPaths: Array.isArray(argv.searchPaths) ? argv.searchPaths :
argv.searchPaths ? [argv.searchPaths] : [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for normalizing the searchPaths argument is a bit complex. It can be simplified using Array.prototype.flat(), which gracefully handles cases where the argument is a single string, an array, or undefined.

            searchPaths: (argv.searchPaths ? [argv.searchPaths] : []).flat(),

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new mcp-ts-introspect tool to introspect TypeScript exports from npm packages, source strings, or whole projects, and exposes the same functionality via an MCP server. It also updates TypeScript compiler settings needed for CommonJS interop and refreshes documentation to include the new tool while removing outdated sections.

Changes:

  • Add mcp-ts-introspect CLI + interactive mode with package/source/project introspection.
  • Add MCP server mode exposing introspect-package, introspect-source, and introspect-project tools.
  • Add caching + package resolution + export extraction utilities; update docs and tsconfig.json for interop.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
tsconfig.json Enables esModuleInterop and downlevelIteration for compatibility with new deps/import patterns.
src/mcp-ts-introspect/utils.ts Adds export filtering (regex/string fallback) and result limiting.
src/mcp-ts-introspect/types.ts Introduces shared types for export data, options, and cache entries.
src/mcp-ts-introspect/packageResolver.ts Implements package discovery and .d.ts file location strategies (node_modules/pnpm/exports scanning).
src/mcp-ts-introspect/mcp-server.ts Adds MCP server exposing introspection operations as tools over stdio transport.
src/mcp-ts-introspect/introspect.ts Implements package/source/project introspection using ts-morph with caching and timeouts.
src/mcp-ts-introspect/index.ts Adds CLI entrypoint: argument parsing, interactive prompts, output handling, and MCP mode.
src/mcp-ts-introspect/exportExtractor.ts Extracts exported symbols and shapes them into ExportInfo records.
src/mcp-ts-introspect/cache.ts Adds filesystem cache read/write with TTL semantics.
src/mcp-ts-introspect/README.md Documents usage, options, examples, and MCP integration for the new tool.
README.md Updates tool listing/docs and removes/condenses outdated sections while adding MCP TS Introspect.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +47 to +52
for (const entry of entries) {
if (entry.includes(packageName)) {
const packagePath = join(pnpmPath, entry, "node_modules", packageName);
const packageJsonPath = join(packagePath, "package.json");

if (existsSync(packageJsonPath)) {
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PNPM store folder names don’t include / for scoped packages (they use +, e.g. @types+node@...). Checking entry.includes(packageName) will fail for scoped packages like @types/node, so pnpm-installed scoped packages won’t be found. Consider matching against a normalized form (e.g., packageName.replace("/", "+")) and/or parsing the pnpm entry format more precisely to avoid false positives.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +80
function getExportKind(node: Node): ExportInfo["kind"] | null {
if (Node.isTypeAliasDeclaration(node)) {
return "type";
} else if (Node.isFunctionDeclaration(node)) {
return "function";
} else if (Node.isClassDeclaration(node)) {
return "class";
} else if (Node.isVariableDeclaration(node)) {
return "const";
} else if (Node.isExportSpecifier(node)) {
// For re-exports, check the original declaration
const symbol = node.getSymbol();
if (symbol) {
const valueDeclaration = symbol.getValueDeclaration();
if (valueDeclaration) {
return getExportKind(valueDeclaration);
}
}
}

// Default to const for other types
return "const";
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getExportKind() defaults to returning "const" for any unhandled declaration kinds. This will misclassify common exports from .d.ts such as interface, enum, and namespace as "const". Add explicit handling for these node kinds (and update ExportInfo["kind"] accordingly), or map them to a more accurate category (e.g., treat interfaces as "type").

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +19
const timeout = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs);
});

return Promise.race([promise, timeout]);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withTimeout() creates a timer but never clears it when the main promise resolves. With many calls this can accumulate pending timers unnecessarily. Track the timer handle and clearTimeout() once either branch settles (e.g., in a finally around await Promise.race(...)).

Suggested change
const timeout = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs);
});
return Promise.race([promise, timeout]);
let timeoutId: NodeJS.Timeout | undefined;
const timeout = new Promise<never>((_, reject) => {
timeoutId = setTimeout(
() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)),
timeoutMs
);
});
try {
return await Promise.race([promise, timeout]);
} finally {
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +106
if (request.params.name === "introspect-project") {
try {
const args = IntrospectProjectSchema.parse(request.params.arguments);
const options: IntrospectOptions = {
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request.params.arguments may be undefined (especially for tools with no required params like introspect-project). Passing undefined to z.object(...).parse() will throw a ZodError even though {} would be valid. Consider parsing request.params.arguments ?? {} for schemas where arguments are optional.

Copilot uses AI. Check for mistakes.
}

async function getOutputDestination(argv: Args): Promise<string> {
if (argv.output) return argv.output;
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI help says --output accepts file, but getOutputDestination() returns argv.output verbatim. Passing --output file will write to a file literally named file instead of prompting for a filename (or erroring). Either change the help to document that --output should be a filename, or explicitly treat output === "file" as “prompt for filename” (or add a separate --output-file).

Suggested change
if (argv.output) return argv.output;
if (argv.output) {
if (argv.output === "file") {
try {
const fileResponse = await prompter.prompt({
type: "input",
name: "filename",
message: "Enter output filename:",
initial: "exports.json"
}) as { filename: string };
return fileResponse.filename;
} catch (error: any) {
if (error.message === 'canceled') {
logger.info("\nOperation cancelled by user.");
process.exit(0);
}
throw error;
}
}
return argv.output;
}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,24 @@
export interface ExportInfo {
name: string;
kind: "function" | "class" | "type" | "const";
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExportInfo.kind doesn’t include categories like interface, enum, or namespace, but the extractor will encounter these in .d.ts files. This forces misclassification (currently defaulting to "const"). Expand the union (or document a deliberate mapping, e.g. interface→"type") to keep the output semantically accurate.

Suggested change
kind: "function" | "class" | "type" | "const";
kind: "function" | "class" | "type" | "const" | "interface" | "enum" | "namespace";

Copilot uses AI. Check for mistakes.
Comment on lines +163 to +166
// Generate cache key from project path
const cacheKey = crypto.createHash('md5').update(projectPath).digest('hex');

// Check cache first
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cacheKey is computed from the initial projectPath before you potentially normalize it to the parent directory when tsconfig.json isn’t found. This can cause the same project to be cached under different keys depending on which subdirectory the user points at (and can also cache results under a key that no longer matches the actual analyzed project). Compute the cache key after resolving the final projectPath/tsconfigPath.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +99
const uniqueExports = Array.from(
new Map(allExports.map(exp => [exp.name, exp])).values()
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deduping package exports by name alone can drop valid distinct exports (overloads, namespaces merged with functions, re-exports with different type signatures, etc.). Consider using a compound key (e.g., name + kind + typeSignature) similar to the project-mode dedupe logic, or keep an array per name.

Suggested change
const uniqueExports = Array.from(
new Map(allExports.map(exp => [exp.name, exp])).values()
const buildExportDedupKey = (exp: ExportInfo): string => {
// Use a compound key so that distinct exports with the same name
// (e.g. overloads, merged declarations) are not collapsed.
const e: any = exp;
const kind = e.kind ?? "";
const typeSig = e.typeSignature ?? e.type ?? "";
const ns = e.namespace ?? e.moduleName ?? "";
return `${exp.name}::${kind}::${typeSig}::${ns}`;
};
const uniqueExports = Array.from(
new Map(allExports.map(exp => [buildExportDedupKey(exp), exp])).values()

Copilot uses AI. Check for mistakes.
Comment on lines +259 to +266
const options: IntrospectOptions = {
searchPaths: Array.isArray(argv.searchPaths) ? argv.searchPaths :
argv.searchPaths ? [argv.searchPaths] : [],
searchTerm: argv.searchTerm,
cache: argv.cache,
cacheDir: argv.cacheDir,
limit: argv.limit
};
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--search-paths from the help text won’t be picked up here because minimist won’t camelCase --search-paths into searchPaths by default (it will populate argv["search-paths"]). As written, user-supplied search paths will be ignored. Consider normalizing the flag name (e.g., read argv["search-paths"], add an alias, or switch to a parser that supports camelCase).

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,212 @@
import { join, dirname, basename } from "node:path";
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import basename.

Suggested change
import { join, dirname, basename } from "node:path";
import { join, dirname } from "node:path";

Copilot uses AI. Check for mistakes.
genesiscz added a commit that referenced this pull request Feb 13, 2026
- Fix critical stale cleanup scope bug (Thread #4/#11): project-scoped
  listing no longer deletes other projects' cache entries
- Preserve matchSnippet when merging search results (Thread #19)
- Fix rg-only results getting 0 slots when meta returns 20 (Thread #13)
- Use rmSync with force flag instead of repetitive try/catch (Thread #1)
- Add try/catch fallback for METADATA_VERSION (Thread #3/#6)
- Add -- and -F flags to ripgrep commands (Thread #5/#17)
- Reuse extractTextFromMessage in rgExtractSnippet (Thread #2)
- Add 10MB file size cap + early exit in extraction (Thread #7/#18)
- Add error handling for ripgrep functions (Thread #10/#16)
genesiscz added a commit that referenced this pull request Feb 15, 2026
- Fix critical stale cleanup scope bug (Thread #4/#11): project-scoped
  listing no longer deletes other projects' cache entries
- Preserve matchSnippet when merging search results (Thread #19)
- Fix rg-only results getting 0 slots when meta returns 20 (Thread #13)
- Use rmSync with force flag instead of repetitive try/catch (Thread #1)
- Add try/catch fallback for METADATA_VERSION (Thread #3/#6)
- Add -- and -F flags to ripgrep commands (Thread #5/#17)
- Reuse extractTextFromMessage in rgExtractSnippet (Thread #2)
- Add 10MB file size cap + early exit in extraction (Thread #7/#18)
- Add error handling for ripgrep functions (Thread #10/#16)
genesiscz added a commit that referenced this pull request Feb 15, 2026
#25)

* fix(claude-history): full metadata extraction, rg search, auto-reindex

- Read entire JSONL files for metadata (was limited to 50 lines/64KB)
- Store full firstPrompt (was truncated to 120 chars)
- Index ALL user messages into allUserText field (capped 5000 chars)
- Summary/custom-title: latest wins (captures tail of file)
- Add ripgrep full-content search fallback (rgSearchFiles/rgExtractSnippet)
- Auto-reindex via MD5 hash of lib.ts+cache.ts (METADATA_VERSION)
- Clean up stale cache entries for deleted session files
- Rename DB from stats-cache.db to index.db
- Show match snippets in claude-resume session picker
- Search firstPrompt in matchByIdOrName

* feat(claude-resume): show conversation excerpts in session picker

Adds a second line below each session name showing additional
context: summary (if name is from title), first prompt (if name
is from summary), or match snippet (if from search).

* fix(claude-resume): run metadata + rg search in parallel, merge results

Previously rg only ran as fallback when metadata returned 0 results.
Now both phases run concurrently via Promise.all. Results are deduped
and merged: metadata matches first (ranked by relevance), then rg-only
matches with snippets. Catches content in assistant messages and user
text past the 5000-char metadata cap.

* fix(claude-history): resolve dashed project names, add nerdy stats

- extractProjectName: progressively resolve encoded dir against
  filesystem to recover dashed names (col-fe was showing as "fe")
- Cache resolved project names to avoid repeated filesystem lookups
- Show detailed stats in session picker: session count, project count,
  scope, indexed/stale/reindex status

* feat(claude-resume): nerdy search diagnostics

Show detailed breakdown of where results came from:
- Index matches: count + what was searched (name/branch/project/prompt)
- Content search: meta hits, rg total hits, overlap, rg-only unique hits

* fix(claude-history): address PR #25 review comments

- Fix critical stale cleanup scope bug (Thread #4/#11): project-scoped
  listing no longer deletes other projects' cache entries
- Preserve matchSnippet when merging search results (Thread #19)
- Fix rg-only results getting 0 slots when meta returns 20 (Thread #13)
- Use rmSync with force flag instead of repetitive try/catch (Thread #1)
- Add try/catch fallback for METADATA_VERSION (Thread #3/#6)
- Add -- and -F flags to ripgrep commands (Thread #5/#17)
- Reuse extractTextFromMessage in rgExtractSnippet (Thread #2)
- Add 10MB file size cap + early exit in extraction (Thread #7/#18)
- Add error handling for ripgrep functions (Thread #10/#16)
* fix(claude-history): add cwd to early-exit check, use null for empty allUserText

- Include cwd in early-exit condition to avoid skipping cwd extraction
- Return null instead of "" for allUserText when no user text found
- Anonymize path examples in comments
genesiscz added a commit that referenced this pull request Feb 17, 2026
- Replace duplicate stripAnsi with import from utils/string (#18)
- Fix wrapToWidth ANSI-aware truncation (#1/#3/#22)
- Add await to recursive executeTool call (#9/#20)
- Guard cursor when filtered list is empty (#5/#19)
- Use basename() instead of split("/").pop() (#10/#16)
- Validate --width NaN input (#14)
- Re-display watch message after screen clear (#15)
genesiscz added a commit that referenced this pull request Feb 18, 2026
- Replace duplicate stripAnsi with import from utils/string (#18)
- Fix wrapToWidth ANSI-aware truncation (#1/#3/#22)
- Add await to recursive executeTool call (#9/#20)
- Guard cursor when filtered list is empty (#5/#19)
- Use basename() instead of split("/").pop() (#10/#16)
- Validate --width NaN input (#14)
- Re-display watch message after screen clear (#15)
genesiscz added a commit that referenced this pull request Feb 18, 2026
* fix: clean error formatting and fuzzy match for tools entry point

* feat(tools): add discovery, introspection, and search-select modules

- discovery.ts: scans src/ for tools, extracts descriptions from README.md
- introspect.ts: runs --help on tools and parses Commander output into structured data
- search-select.ts: single-select search prompt with onHighlight callback, modeled on search-multiselect

* feat(tools): interactive browser with search, README preview, and subcommand explorer

* feat(markdown): add render options and CLI flags (--watch, --width, --theme, --no-color)

* fix: improve fuzzy matching to handle trailing dashes and segment matching

"tools cli-" now correctly matches "markdown-cli" by stripping trailing
dashes and checking dash-separated segments.

* feat(markdown-cli): add demo subcommand with interactive template gallery

* Revert "feat(markdown-cli): add demo subcommand with interactive template gallery"

This reverts commit f5c84db.

* feat(markdown): implement theme palette system (dark/light/minimal)

Wire up the --theme flag to actual color palettes that change
mermaid blocks, table borders, and alert colors per theme.

* fix: address PR #28 code review feedback

- Replace duplicate stripAnsi with import from utils/string (#18)
- Fix wrapToWidth ANSI-aware truncation (#1/#3/#22)
- Add await to recursive executeTool call (#9/#20)
- Guard cursor when filtered list is empty (#5/#19)
- Use basename() instead of split("/").pop() (#10/#16)
- Validate --width NaN input (#14)
- Re-display watch message after screen clear (#15)

* fix(markdown-cli/tools): address PR #28 review feedback

- Remove stray n8n.json (unrelated to PR scope)
- fix(markdown-cli): reject invalid --theme values via Commander .choices() (Thread #23)
- fix(tools): guard discoverTools against missing srcDir with existsSync check (Thread #24)
- fix(markdown): use display width for emoji/wide chars in wrapToWidth (Thread #25)
genesiscz added a commit that referenced this pull request Feb 19, 2026
- Use Promise.allSettled in showConfig to handle stale tokens (#14)
- Clamp pct in renderBar to prevent RangeError (#17)
- Clear firedThresholds on period reset in watch mode (#18)
- Wrap entire getKeychainCredentials in try/catch for non-macOS (#19)
- Escape sound parameter in AppleScript notifications (#20)
- Fix import.meta.dir off-by-one in update tool (#21)
- Use isCancelled() in withCancel() for light-mode cancel (#22)
- Remove 100% cap on projected usage
genesiscz added a commit that referenced this pull request Feb 19, 2026
- Use Promise.allSettled in showConfig to handle stale tokens (#14)
- Clamp pct in renderBar to prevent RangeError (#17)
- Clear firedThresholds on period reset in watch mode (#18)
- Wrap entire getKeychainCredentials in try/catch for non-macOS (#19)
- Escape sound parameter in AppleScript notifications (#20)
- Fix import.meta.dir off-by-one in update tool (#21)
- Use isCancelled() in withCancel() for light-mode cancel (#22)
- Remove 100% cap on projected usage
@genesiscz genesiscz force-pushed the feat/mcp-ts-introspect branch from 06d4f64 to a3be48c Compare February 23, 2026 21:58
@genesiscz genesiscz force-pushed the feat/mcp-ts-introspect branch from a3be48c to 74a8971 Compare March 10, 2026 00:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mcp-ts-introspect/cache.ts`:
- Around line 16-24: Validate the parsed JSON before trusting the cast to
CacheEntry: after reading cacheFile via Bun.file(cacheFile).json(), check that
the result is a non-null object, has a numeric timestamp and an array exports
(e.g., typeof cacheData === 'object' && cacheData !== null && typeof
cacheData.timestamp === 'number' && Array.isArray(cacheData.exports')); if
validation fails, log a warning with logger (including key) and return null
instead of proceeding to compute age or access cacheData.exports; keep using
CACHE_TTL and the existing try/catch so corrupted or tampered files are safely
ignored.

In `@src/mcp-ts-introspect/exportExtractor.ts`:
- Around line 58-80: getExportKind is missing handling for exported interfaces
so InterfaceDeclaration nodes fall through to the default "const"; update
getExportKind to check for Node.isInterfaceDeclaration(node) and return "type"
(similar to the TypeAlias case), and also ensure the re-export branch (inside
Node.isExportSpecifier) will recurse correctly when symbol.getValueDeclaration()
returns an InterfaceDeclaration so the added check covers those paths as well.
- Around line 5-19: The function extractExports is declared async but contains
no awaits; remove the async keyword and make its signature synchronous (export
function extractExports(sourceFile: SourceFile): ExportInfo[]) and return the
exports array directly; then update callers that rely on a Promise (notably
usages in introspect.ts that call withTimeout(extractExports(...))) to either
wrap the result with Promise.resolve(extractExports(...)) or call
withTimeoutSync/adjust withTimeout to accept synchronous results so the call
sites remain correct.
- Around line 34-35: The code creates an unnecessary intermediate variable and a
redundant cast: remove the local const declaration and the `as Node` cast by
using the declaration directly from `symbol.getDeclarations()` where needed
(e.g., replace uses of `declaration`/`node` with the first element of
`declarations` or assign it once without a cast). Update references in
exportExtractor.ts around the `declarations[0]` usage (the block that currently
declares `const declaration = declarations[0]; const node = declaration as
Node;`) to use the declaration value directly with its inferred Node subtype.
- Line 1: The project imports ts-morph (see import of type Symbol as
MorphSymbol, Node, SourceFile in exportExtractor.ts) but ts-morph is missing
from package.json; add ts-morph to dependencies (e.g., run npm install --save
ts-morph or add an appropriate version entry under "dependencies") so CI can
resolve the import and build succeeds.

In `@src/mcp-ts-introspect/index.ts`:
- Around line 1-4: Replace the Enquirer and minimist imports/usages in
src/mcp-ts-introspect/index.ts with the recommended libraries: import commander
(use the Program API / program.option and program.command to parse arguments and
subcommands instead of minimist) and import `@clack/prompts` (use functions like
text, select, confirm from `@clack/prompts` in place of Enquirer prompts); update
any code that calls Enquirer.prompt(...) and any direct minimist argv parsing to
use commander.command/option and its parsed values, and replace Enquirer prompt
calls with the equivalent `@clack/prompts` functions, leaving clipboardy and chalk
imports as-is.
- Line 1: Replace use of chalk with picocolors across this module: remove the
"import chalk from 'chalk'" and import the functions you need from 'picocolors'
(e.g., import { red, green, yellow, bold } from 'picocolors'), then update all
places that call chalk.* (refer to usages of the imported symbol "chalk") to
call the corresponding picocolors functions (e.g., chalk.red -> red, chalk.bold
-> bold) so terminal coloring uses picocolors APIs instead of chalk.
- Around line 3-4: Replace the two imports of Enquirer and minimist with the
project's approved libraries: use commander for CLI argument parsing and
`@clack/prompts` for interactive prompts; specifically remove references to
Enquirer and minimist in src/mcp-ts-introspect/index.ts, import and use
commander.Command (or the top-level commander API) where minimist was used to
parse argv, and replace Enquirer prompt calls with the appropriate
`@clack/prompts` functions (e.g., select/text/confirm) so the same prompt flow and
option names are preserved; ensure you update any variable names that referenced
Enquirer/minimist (e.g., Enquirer, minimist, parsed args) to the new commander
and `@clack/prompts` identifiers.

In `@src/mcp-ts-introspect/introspect.ts`:
- Around line 88-89: introspectPackage currently deduplicates exports using only
export.name (uniqueExports in introspectPackage) while introspectProject uses a
composite key of name:typeSignature, causing inconsistent behavior for
overloaded or same-named exports; update introspectPackage's deduplication to
use the same composite key as introspectProject (e.g.,
`${exp.name}:${exp.typeSignature}` or equivalent field) when building the Map so
both functions produce consistent uniqueExports results and preserve distinct
overloads or module-specific exports.
- Around line 14-20: The withTimeout function leaves the timeout timer active
even after the input promise settles; modify withTimeout so the setTimeout
returns a timer id (e.g., timerId) and ensure you call clearTimeout(timerId)
when the original promise resolves or rejects (or when the race settles) to
avoid keeping the timer active—update the Promise.race logic in withTimeout to
clear the timeout in both success and failure paths referencing withTimeout and
the timeout timer id.
- Around line 154-165: The code reassigns the function parameter projectPath
when falling back to the parent directory; instead, keep the original parameter
immutable by introducing a new variable (e.g., resolvedProjectPath or
projectRoot) and use that for subsequent path logic: compute tsconfigPath from
resolvedProjectPath, check existsSync, compute parentPath as needed, set
resolvedProjectPath = parentPath when falling back, and leave the original
projectPath untouched; update references in this block (tsconfigPath, parentPath
checks) to use the new variable so behavior is identical but the parameter is
not reassigned.

In `@src/mcp-ts-introspect/mcp-server.ts`:
- Around line 47-138: The three handlers inside server.setRequestHandler
("introspect-package", "introspect-source", "introspect-project") duplicate
identical ZodError catch logic; extract a small helper (e.g.,
parseOrThrow(schema, value) or formatZodError(error) used by
parseWithValidation) that calls schema.parse, catches z.ZodError and throws a
new Error with the formatted message, then replace the direct calls to
IntrospectPackageSchema.parse, IntrospectSourceSchema.parse and
IntrospectProjectSchema.parse with the helper; ensure the helper references
z.ZodError for type checking and is used in each try block so the catch blocks
can be removed or simplified.

In `@src/mcp-ts-introspect/packageResolver.ts`:
- Around line 46-62: The fuzzy match using entry.includes(packageName) can
return wrong packages; change the check in the loop that examines entries to a
precise match: compare entry exactly to packageName or to known pnpm-stored
variants (e.g., entry.startsWith(packageName + "@") and
entry.startsWith(packageName.replace('/', '+') + "@") or
entry.startsWith(packageName.replace('/', '+') + "+") as needed) so only the
intended package (or package@version/store-encoded scoped name) is selected;
update the check around the entry variable (in the loop in packageResolver.ts
where packageName is used) to use these stricter comparisons before constructing
packagePath and packageJsonPath.

In `@src/mcp-ts-introspect/types.ts`:
- Around line 1-6: The ExportInfo.kind union is too narrow and can miscategorize
exports; update the ExportInfo interface (symbol: ExportInfo, property: kind) to
include other possible export kinds such as "interface", "enum", "namespace",
and "variable" (and any other project-specific variants you expect) so the
extractor can represent those exports accurately; adjust any downstream
switch/handling logic that discriminates on ExportInfo.kind to handle the new
values or provide a default case.

In `@src/mcp-ts-introspect/utils.ts`:
- Around line 9-16: The code constructs a RegExp from external searchTerm and
runs regex.test during filtering (see new RegExp(searchTerm, "i"),
regex.test(...) and the filtered = exports.filter(...) usage), which is
vulnerable to ReDoS; validate searchTerm with safe-regex before compiling and
executing: import safe-regex, check safeRegex(searchTerm) (and also enforce a
reasonable max length), and if validation fails either escape the input (use a
safe escaping fallback) or reject/skip regex path and fall back to a simple
substring case-insensitive match for name/typeSignature/description so
regex.test is never called on unsafe patterns. Ensure these checks occur before
new RegExp(...) and before any regex.test(...) calls.

In `@tsconfig.json`:
- Around line 34-36: The tsconfig currently sets "downlevelIteration": true
while the "target" is "ESNext"; remove the "downlevelIteration" setting (or add
a comment) because it has no effect when targeting ESNext—locate the
"downlevelIteration" entry in tsconfig.json and either delete it or replace it
with a short comment explaining why it remains (e.g., kept for older-target
compatibility) so intent is clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc6862aa-a4ad-479e-bdd7-a496ef72627f

📥 Commits

Reviewing files that changed from the base of the PR and between f982ace and 74a8971.

📒 Files selected for processing (10)
  • src/mcp-ts-introspect/README.md
  • src/mcp-ts-introspect/cache.ts
  • src/mcp-ts-introspect/exportExtractor.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/mcp-server.ts
  • src/mcp-ts-introspect/packageResolver.ts
  • src/mcp-ts-introspect/types.ts
  • src/mcp-ts-introspect/utils.ts
  • tsconfig.json
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Use bun run to execute TypeScript files directly without a build step
Place general-purpose helper functions in src/utils/ instead of tool directories
Never add // src/path/to/file.ts as the first line of files
Don't add comments that restate what the code already says (e.g., obvious descriptions of function calls)
Always use block form with braces for if statements, even for early returns; never use one-line if statements
Add an empty line before if statements unless the preceding line is a variable declaration used by that if
Add an empty line after closing } unless followed by else, catch, finally, or another }
No as any type assertions; use proper type narrowing, type guards, or explicit interfaces instead
Use discriminant checks (e.g., entity.className === "User") when working with union types instead of type assertions
Prefer error: err over error: err instanceof Error ? err.message : String(err) when the error field accepts unknown type

Files:

  • src/mcp-ts-introspect/utils.ts
  • src/mcp-ts-introspect/cache.ts
  • src/mcp-ts-introspect/types.ts
  • src/mcp-ts-introspect/mcp-server.ts
  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/exportExtractor.ts
  • src/mcp-ts-introspect/packageResolver.ts
src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.ts: Use @clack/prompts for interactive user prompts (preferred over @inquirer/prompts)
Use Bun.spawn() for executing external commands and always check exit codes with meaningful error messages
Use Node.js path module for cross-platform path handling and resolve relative paths to absolute using resolve()
Use Bun's native file APIs (Bun.write()) for better performance instead of other file writing methods
Use centralized logging with pino logger outputting to /logs/ directory organized by date
Handle user cancellation from prompts gracefully
Provide sensible defaults and suggestions in user prompts
Strip ANSI codes from chalk colored output for non-TTY environments

Files:

  • src/mcp-ts-introspect/utils.ts
  • src/mcp-ts-introspect/cache.ts
  • src/mcp-ts-introspect/types.ts
  • src/mcp-ts-introspect/mcp-server.ts
  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/exportExtractor.ts
  • src/mcp-ts-introspect/packageResolver.ts
src/**/index.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/index.ts: Use commander for parsing command-line arguments with subcommands and options
Support multiple output destinations (file, clipboard, stdout) and use clipboardy for clipboard operations
Tool discovery checks for directories with index.ts or index.tsx (tool name = directory name) and standalone .ts or .tsx files (tool name = filename without extension)
Respect --silent and --verbose flags in tool output handling

Files:

  • src/mcp-ts-introspect/index.ts
🧠 Learnings (19)
📓 Common learnings
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 82
File: src/claude/lib/usage/dashboard-config.ts:24-25
Timestamp: 2026-03-09T13:39:43.059Z
Learning: GenesisTools (genesiscz/GenesisTools) is a personal CLI tool, not a multi-tenant service. Do not suggest data migration paths or backward-compatibility shims for simple default value changes in configuration files (e.g., dashboard-config.ts). The TUI provides interactive controls (e.g., the `i` key to cycle poll intervals at runtime), so users can adjust settings without needing automated migrations. Avoid overengineering suggestions in this context.
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 81
File: src/github/commands/get.ts:209-212
Timestamp: 2026-03-09T13:14:03.722Z
Learning: In the GenesisTools repo (genesiscz/GenesisTools), there is no CI formatter (e.g., Prettier/Biome) configured or enforced. Do not flag formatting mismatches based on static analysis tool annotations (e.g., GitHub Actions CI formatter warnings) in this repository, as they are not representative of actual enforced formatting rules. TypeScript files under src/ should be reviewed for logical correctness and consistency with existing code patterns instead.
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/index.ts : Tool discovery checks for directories with `index.ts` or `index.tsx` (tool name = directory name) and standalone `.ts` or `.tsx` files (tool name = filename without extension)
📚 Learning: 2026-02-20T00:52:27.023Z
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 31
File: src/ask/utils/helpers.ts:3-3
Timestamp: 2026-02-20T00:52:27.023Z
Learning: In all TypeScript source files under src, prefer using picocolors for colored terminal output in new code. Picocolors is smaller and faster than chalk, so adopt it for CLI output coloring and avoid adding chalk in new code paths unless there is a compelling compatibility reason.

Applied to files:

  • src/mcp-ts-introspect/utils.ts
  • src/mcp-ts-introspect/cache.ts
  • src/mcp-ts-introspect/types.ts
  • src/mcp-ts-introspect/mcp-server.ts
  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/exportExtractor.ts
  • src/mcp-ts-introspect/packageResolver.ts
📚 Learning: 2026-02-24T15:32:37.494Z
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 54
File: src/github/lib/output.ts:109-113
Timestamp: 2026-02-24T15:32:37.494Z
Learning: In TypeScript files under src/, do not require a leading blank line before an if statement that is the first statement inside a function body (immediately after the function signature). The blank line rule should only apply to if statements that come after other statements within the function body. Apply this guideline consistently across TS files in src to reduce unnecessary vertical whitespace and keep concise function bodies.

Applied to files:

  • src/mcp-ts-introspect/utils.ts
  • src/mcp-ts-introspect/cache.ts
  • src/mcp-ts-introspect/types.ts
  • src/mcp-ts-introspect/mcp-server.ts
  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/exportExtractor.ts
  • src/mcp-ts-introspect/packageResolver.ts
📚 Learning: 2026-03-09T13:13:58.786Z
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 81
File: src/github/commands/get.ts:209-212
Timestamp: 2026-03-09T13:13:58.786Z
Learning: In the GenesisTools repo (genesiscz/GenesisTools), do not treat CI formatter warnings as enforceable formatting rules for TypeScript files under src/. Focus reviews on logical correctness and consistency with existing code patterns. For files under src (e.g., src/github/commands/get.ts), prioritize code structure, readability, naming, correctness, and adherence to project conventions over automated formatting warnings from CI tools.

Applied to files:

  • src/mcp-ts-introspect/utils.ts
  • src/mcp-ts-introspect/cache.ts
  • src/mcp-ts-introspect/types.ts
  • src/mcp-ts-introspect/mcp-server.ts
  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/exportExtractor.ts
  • src/mcp-ts-introspect/packageResolver.ts
📚 Learning: 2026-02-24T15:32:44.925Z
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 54
File: src/github/lib/review-output.ts:18-20
Timestamp: 2026-02-24T15:32:44.925Z
Learning: In TypeScript files, do not require a blank line between the opening brace of a function and the first statement if the first statement is the if statement immediately after the signature. The blank-line rule applies to separating an if from unrelated preceding code within the same block, not to spacing after the function opening brace. Apply this rule to all TS functions across the codebase.

Applied to files:

  • src/mcp-ts-introspect/utils.ts
  • src/mcp-ts-introspect/cache.ts
  • src/mcp-ts-introspect/types.ts
  • src/mcp-ts-introspect/mcp-server.ts
  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/exportExtractor.ts
  • src/mcp-ts-introspect/packageResolver.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/*.ts : Provide sensible defaults and suggestions in user prompts

Applied to files:

  • src/mcp-ts-introspect/README.md
  • src/mcp-ts-introspect/index.ts
📚 Learning: 2026-03-09T13:39:43.059Z
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 82
File: src/claude/lib/usage/dashboard-config.ts:24-25
Timestamp: 2026-03-09T13:39:43.059Z
Learning: GenesisTools (genesiscz/GenesisTools) is a personal CLI tool, not a multi-tenant service. Do not suggest data migration paths or backward-compatibility shims for simple default value changes in configuration files (e.g., dashboard-config.ts). The TUI provides interactive controls (e.g., the `i` key to cycle poll intervals at runtime), so users can adjust settings without needing automated migrations. Avoid overengineering suggestions in this context.

Applied to files:

  • src/mcp-ts-introspect/README.md
📚 Learning: 2026-02-20T00:52:23.917Z
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 31
File: .claude/commands/release.md:50-55
Timestamp: 2026-02-20T00:52:23.917Z
Learning: The GenesisTools repository uses "master" as its default branch, not "main". Commands and scripts targeting the default branch should reference "master" explicitly.

Applied to files:

  • src/mcp-ts-introspect/README.md
📚 Learning: 2026-02-25T23:00:07.620Z
Learnt from: genesiscz
Repo: genesiscz/GenesisTools PR: 56
File: plugins/genesis-tools/commands/github-pr.md:131-131
Timestamp: 2026-02-25T23:00:07.620Z
Learning: Adopt the style: use lowercase 'markdown' (not 'Markdown') in the GenesisTools documentation. Apply this consistently across all Markdown files in the repository (any .md file), including generated docs and READMEs.

Applied to files:

  • src/mcp-ts-introspect/README.md
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to **/*.ts : Use discriminant checks (e.g., `entity.className === "User"`) when working with union types instead of type assertions

Applied to files:

  • src/mcp-ts-introspect/types.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to **/*.ts : No `as any` type assertions; use proper type narrowing, type guards, or explicit interfaces instead

Applied to files:

  • src/mcp-ts-introspect/types.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/index.ts : Tool discovery checks for directories with `index.ts` or `index.tsx` (tool name = directory name) and standalone `.ts` or `.tsx` files (tool name = filename without extension)

Applied to files:

  • src/mcp-ts-introspect/introspect.ts
  • src/mcp-ts-introspect/index.ts
  • src/mcp-ts-introspect/packageResolver.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/*.ts : Use `clack/prompts` for interactive user prompts (preferred over `inquirer/prompts`)

Applied to files:

  • src/mcp-ts-introspect/index.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/index.ts : Use `commander` for parsing command-line arguments with subcommands and options

Applied to files:

  • src/mcp-ts-introspect/index.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/index.ts : Support multiple output destinations (file, clipboard, stdout) and use `clipboardy` for clipboard operations

Applied to files:

  • src/mcp-ts-introspect/index.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/index.ts : Respect `--silent` and `--verbose` flags in tool output handling

Applied to files:

  • src/mcp-ts-introspect/index.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: The `tools` executable shows an interactive tool selector using inquirer/prompts when run without arguments

Applied to files:

  • src/mcp-ts-introspect/index.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to **/*.ts : Don't add comments that restate what the code already says (e.g., obvious descriptions of function calls)

Applied to files:

  • src/mcp-ts-introspect/exportExtractor.ts
📚 Learning: 2026-02-20T01:35:08.233Z
Learnt from: CR
Repo: genesiscz/GenesisTools PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-20T01:35:08.233Z
Learning: Applies to src/**/*.ts : Use Node.js `path` module for cross-platform path handling and resolve relative paths to absolute using `resolve()`

Applied to files:

  • src/mcp-ts-introspect/packageResolver.ts
🧬 Code graph analysis (6)
src/mcp-ts-introspect/utils.ts (1)
src/mcp-ts-introspect/types.ts (1)
  • ExportInfo (1-6)
src/mcp-ts-introspect/cache.ts (1)
src/mcp-ts-introspect/types.ts (2)
  • ExportInfo (1-6)
  • CacheEntry (21-24)
src/mcp-ts-introspect/mcp-server.ts (2)
src/mcp-ts-introspect/types.ts (1)
  • IntrospectOptions (8-14)
src/mcp-ts-introspect/introspect.ts (3)
  • introspectPackage (22-101)
  • introspectSource (103-134)
  • introspectProject (136-211)
src/mcp-ts-introspect/introspect.ts (5)
src/mcp-ts-introspect/types.ts (2)
  • IntrospectOptions (8-14)
  • ExportInfo (1-6)
src/mcp-ts-introspect/cache.ts (2)
  • loadCache (9-32)
  • saveCache (34-53)
src/mcp-ts-introspect/utils.ts (1)
  • filterExports (4-38)
src/mcp-ts-introspect/packageResolver.ts (2)
  • findPackageJsonAndDir (7-86)
  • findDeclarationFiles (101-155)
src/mcp-ts-introspect/exportExtractor.ts (1)
  • extractExports (5-19)
src/mcp-ts-introspect/index.ts (4)
src/ask/types/config.ts (1)
  • Args (30-32)
src/mcp-ts-introspect/types.ts (2)
  • ExportInfo (1-6)
  • IntrospectOptions (8-14)
src/mcp-ts-introspect/mcp-server.ts (1)
  • startMcpServer (33-244)
src/mcp-ts-introspect/introspect.ts (3)
  • introspectPackage (22-101)
  • introspectSource (103-134)
  • introspectProject (136-211)
src/mcp-ts-introspect/exportExtractor.ts (1)
src/mcp-ts-introspect/types.ts (1)
  • ExportInfo (1-6)
🪛 ast-grep (0.41.0)
src/mcp-ts-introspect/utils.ts

[warning] 9-9: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(searchTerm, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🪛 GitHub Actions: CI
src/mcp-ts-introspect/introspect.ts

[error] 4-4: Cannot find module 'ts-morph' or its corresponding type declarations.

src/mcp-ts-introspect/index.ts

[error] 3-3: Cannot find module 'enquirer' or its corresponding type declarations.


[error] 4-4: Cannot find a declaration file for module 'minimist'. '/home/runner/work/GenesisTools/GenesisTools/node_modules/minimist/index.js' implicitly has an 'any' type. Try npm i --save-dev @types/minimist`` if it exists or add a new declaration (.d.ts) file containing declare module 'minimist';

src/mcp-ts-introspect/exportExtractor.ts

[error] 1-1: Cannot find module 'ts-morph' or its corresponding type declarations.

🔇 Additional comments (12)
src/mcp-ts-introspect/README.md (1)

1-150: LGTM!

The README is comprehensive and well-structured, covering CLI usage, all three introspection modes, MCP server configuration, and available tools. The examples are practical and the documentation aligns with the implemented features.

src/mcp-ts-introspect/types.ts (1)

8-24: LGTM!

The IntrospectOptions, PackageLocation, and CacheEntry interfaces are well-defined with appropriate optional fields and clear semantics.

src/mcp-ts-introspect/cache.ts (1)

34-52: LGTM!

The saveCache function correctly ensures the directory exists, handles errors gracefully without throwing, and logs outcomes appropriately. Using Bun.write aligns with the coding guidelines.

src/mcp-ts-introspect/packageResolver.ts (2)

101-155: LGTM!

The findDeclarationFiles function has well-ordered fallback strategies (types/typings field → exports field → index.d.ts → main.d.ts → full scan) and handles errors gracefully.


187-214: LGTM!

The findAllDeclarationFiles function correctly implements depth-limited recursive scanning, excludes hidden directories and node_modules, and handles directory read errors gracefully.

src/mcp-ts-introspect/mcp-server.ts (1)

140-237: LGTM!

The ListToolsRequestSchema handler correctly exposes the available tools with their input schemas. The schemas are well-documented with descriptions and appropriate defaults.

src/mcp-ts-introspect/index.ts (3)

238-255: LGTM for argument handling structure.

The minimist configuration properly handles aliases, boolean flags, string options, and defaults. The structure is well-organized.


268-324: LGTM for mode execution logic.

The switch-case structure for handling different introspection modes is clean, with proper async/await usage and verbose logging support.


329-341: LGTM for output handling.

Correctly supports all three output destinations (clipboard, stdout, file) using appropriate libraries (clipboardy, Bun.write), and provides user feedback for non-stdout outputs.

src/mcp-ts-introspect/exportExtractor.ts (1)

82-110: LGTM!

The defensive checks for getJsDocs availability and the parent fallback for variable declarations are well-designed. The as unknown as pattern is acceptable here given the runtime check guards it.

src/mcp-ts-introspect/introspect.ts (2)

103-134: LGTM!

The in-memory project setup for source code introspection is clean and appropriately skips caching since the source is ephemeral.


183-194: LGTM!

Good resilience pattern: catching per-file errors and continuing with other files ensures partial results are still returned when individual files fail.

Comment on lines +16 to +24
try {
const cacheData = (await Bun.file(cacheFile).json()) as CacheEntry;

// Check if cache is still valid
const age = Date.now() - cacheData.timestamp;
if (age > CACHE_TTL) {
logger.info(`Cache for ${key} is expired (${Math.floor(age / 1000 / 60 / 60)} hours old)`);
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add validation for cache data structure.

The as CacheEntry assertion trusts that the JSON file contains valid data. Corrupted or tampered cache files could cause runtime errors when accessing cacheData.exports or cacheData.timestamp.

🛡️ Proposed fix with validation
     try {
-        const cacheData = (await Bun.file(cacheFile).json()) as CacheEntry;
+        const cacheData = await Bun.file(cacheFile).json();
+
+        // Validate cache structure
+        if (!cacheData || typeof cacheData.timestamp !== "number" || !Array.isArray(cacheData.exports)) {
+            logger.warn(`Invalid cache structure for ${key}`);
+            return null;
+        }

         // Check if cache is still valid
         const age = Date.now() - cacheData.timestamp;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const cacheData = (await Bun.file(cacheFile).json()) as CacheEntry;
// Check if cache is still valid
const age = Date.now() - cacheData.timestamp;
if (age > CACHE_TTL) {
logger.info(`Cache for ${key} is expired (${Math.floor(age / 1000 / 60 / 60)} hours old)`);
return null;
}
try {
const cacheData = await Bun.file(cacheFile).json();
// Validate cache structure
if (!cacheData || typeof cacheData.timestamp !== "number" || !Array.isArray(cacheData.exports)) {
logger.warn(`Invalid cache structure for ${key}`);
return null;
}
// Check if cache is still valid
const age = Date.now() - cacheData.timestamp;
if (age > CACHE_TTL) {
logger.info(`Cache for ${key} is expired (${Math.floor(age / 1000 / 60 / 60)} hours old)`);
return null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/cache.ts` around lines 16 - 24, Validate the parsed
JSON before trusting the cast to CacheEntry: after reading cacheFile via
Bun.file(cacheFile).json(), check that the result is a non-null object, has a
numeric timestamp and an array exports (e.g., typeof cacheData === 'object' &&
cacheData !== null && typeof cacheData.timestamp === 'number' &&
Array.isArray(cacheData.exports')); if validation fails, log a warning with
logger (including key) and return null instead of proceeding to compute age or
access cacheData.exports; keep using CACHE_TTL and the existing try/catch so
corrupted or tampered files are safely ignored.

@@ -0,0 +1,110 @@
import { type Symbol as MorphSymbol, Node, type SourceFile } from "ts-morph";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if ts-morph is declared in package.json
echo "=== Checking package.json for ts-morph dependency ==="
cat package.json | jq '.dependencies["ts-morph"] // .devDependencies["ts-morph"] // "NOT FOUND"'

Repository: genesiscz/GenesisTools

Length of output: 131


Add ts-morph to package.json dependencies.

The file imports ts-morph but the package is missing from package.json, causing CI failures. Add it to your dependencies.

🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Cannot find module 'ts-morph' or its corresponding type declarations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/exportExtractor.ts` at line 1, The project imports
ts-morph (see import of type Symbol as MorphSymbol, Node, SourceFile in
exportExtractor.ts) but ts-morph is missing from package.json; add ts-morph to
dependencies (e.g., run npm install --save ts-morph or add an appropriate
version entry under "dependencies") so CI can resolve the import and build
succeeds.

Comment on lines +5 to +19
export async function extractExports(sourceFile: SourceFile): Promise<ExportInfo[]> {
const exports: ExportInfo[] = [];
const exportedSymbols = sourceFile.getExportSymbols();

logger.info(`Extracting exports from ${sourceFile.getFilePath()}, found ${exportedSymbols.length} export symbols`);

for (const symbol of exportedSymbols) {
const exportInfo = processSymbol(symbol);
if (exportInfo) {
exports.push(exportInfo);
}
}

return exports;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

async keyword is unnecessary.

extractExports contains no await expressions and could be a regular synchronous function. The async keyword adds minor overhead by wrapping the return in a Promise.

♻️ Proposed refactor
-export async function extractExports(sourceFile: SourceFile): Promise<ExportInfo[]> {
+export function extractExports(sourceFile: SourceFile): ExportInfo[] {

Note: If you change this, callers in introspect.ts that use withTimeout(extractExports(...)) would need adjustment since withTimeout expects a Promise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/exportExtractor.ts` around lines 5 - 19, The function
extractExports is declared async but contains no awaits; remove the async
keyword and make its signature synchronous (export function
extractExports(sourceFile: SourceFile): ExportInfo[]) and return the exports
array directly; then update callers that rely on a Promise (notably usages in
introspect.ts that call withTimeout(extractExports(...))) to either wrap the
result with Promise.resolve(extractExports(...)) or call withTimeoutSync/adjust
withTimeout to accept synchronous results so the call sites remain correct.

Comment on lines +34 to +35
const declaration = declarations[0];
const node = declaration as Node;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Redundant type assertion.

declarations[0] from symbol.getDeclarations() already returns a Node subtype. The intermediate declaration variable and the cast to Node are unnecessary.

♻️ Proposed simplification
     const declaration = declarations[0];
-    const node = declaration as Node;

     // Get type signature
-    const type = symbol.getTypeAtLocation(node);
-    const typeSignature = type.getText(node);
+    const type = symbol.getTypeAtLocation(declaration);
+    const typeSignature = type.getText(declaration);

     // Get JSDoc description
-    const description = getDescription(node);
+    const description = getDescription(declaration);

     // Determine kind
-    const kind = getExportKind(node);
+    const kind = getExportKind(declaration);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const declaration = declarations[0];
const node = declaration as Node;
const declaration = declarations[0];
// Get type signature
const type = symbol.getTypeAtLocation(declaration);
const typeSignature = type.getText(declaration);
// Get JSDoc description
const description = getDescription(declaration);
// Determine kind
const kind = getExportKind(declaration);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/exportExtractor.ts` around lines 34 - 35, The code
creates an unnecessary intermediate variable and a redundant cast: remove the
local const declaration and the `as Node` cast by using the declaration directly
from `symbol.getDeclarations()` where needed (e.g., replace uses of
`declaration`/`node` with the first element of `declarations` or assign it once
without a cast). Update references in exportExtractor.ts around the
`declarations[0]` usage (the block that currently declares `const declaration =
declarations[0]; const node = declaration as Node;`) to use the declaration
value directly with its inferred Node subtype.

Comment on lines +58 to +80
function getExportKind(node: Node): ExportInfo["kind"] | null {
if (Node.isTypeAliasDeclaration(node)) {
return "type";
} else if (Node.isFunctionDeclaration(node)) {
return "function";
} else if (Node.isClassDeclaration(node)) {
return "class";
} else if (Node.isVariableDeclaration(node)) {
return "const";
} else if (Node.isExportSpecifier(node)) {
// For re-exports, check the original declaration
const symbol = node.getSymbol();
if (symbol) {
const valueDeclaration = symbol.getValueDeclaration();
if (valueDeclaration) {
return getExportKind(valueDeclaration);
}
}
}

// Default to const for other types
return "const";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing InterfaceDeclaration handling.

Exported interfaces will fall through to the default "const" return, which is incorrect. Consider adding a case for Node.isInterfaceDeclaration(node) returning "type".

🐛 Proposed fix
 function getExportKind(node: Node): ExportInfo["kind"] | null {
     if (Node.isTypeAliasDeclaration(node)) {
         return "type";
+    } else if (Node.isInterfaceDeclaration(node)) {
+        return "type";
     } else if (Node.isFunctionDeclaration(node)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getExportKind(node: Node): ExportInfo["kind"] | null {
if (Node.isTypeAliasDeclaration(node)) {
return "type";
} else if (Node.isFunctionDeclaration(node)) {
return "function";
} else if (Node.isClassDeclaration(node)) {
return "class";
} else if (Node.isVariableDeclaration(node)) {
return "const";
} else if (Node.isExportSpecifier(node)) {
// For re-exports, check the original declaration
const symbol = node.getSymbol();
if (symbol) {
const valueDeclaration = symbol.getValueDeclaration();
if (valueDeclaration) {
return getExportKind(valueDeclaration);
}
}
}
// Default to const for other types
return "const";
}
function getExportKind(node: Node): ExportInfo["kind"] | null {
if (Node.isTypeAliasDeclaration(node)) {
return "type";
} else if (Node.isInterfaceDeclaration(node)) {
return "type";
} else if (Node.isFunctionDeclaration(node)) {
return "function";
} else if (Node.isClassDeclaration(node)) {
return "class";
} else if (Node.isVariableDeclaration(node)) {
return "const";
} else if (Node.isExportSpecifier(node)) {
// For re-exports, check the original declaration
const symbol = node.getSymbol();
if (symbol) {
const valueDeclaration = symbol.getValueDeclaration();
if (valueDeclaration) {
return getExportKind(valueDeclaration);
}
}
}
// Default to const for other types
return "const";
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/exportExtractor.ts` around lines 58 - 80, getExportKind
is missing handling for exported interfaces so InterfaceDeclaration nodes fall
through to the default "const"; update getExportKind to check for
Node.isInterfaceDeclaration(node) and return "type" (similar to the TypeAlias
case), and also ensure the re-export branch (inside Node.isExportSpecifier) will
recurse correctly when symbol.getValueDeclaration() returns an
InterfaceDeclaration so the added check covers those paths as well.

Comment on lines +47 to +138
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "introspect-package") {
try {
const args = IntrospectPackageSchema.parse(request.params.arguments);
const options: IntrospectOptions = {
searchPaths: args.searchPaths,
searchTerm: args.searchTerm,
cache: args.cache,
cacheDir: args.cacheDir,
limit: args.limit,
};

const exports = await introspectPackage(args.packageName, options);

return {
content: [
{
type: "text",
text: JSON.stringify(exports, null, 2),
},
],
};
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors.map((e) => `${e.path}: ${e.message}`).join(", ")}`
);
}
throw error;
}
}

if (request.params.name === "introspect-source") {
try {
const args = IntrospectSourceSchema.parse(request.params.arguments);
const options: IntrospectOptions = {
searchTerm: args.searchTerm,
limit: args.limit,
};

const exports = await introspectSource(args.sourceCode, options);

return {
content: [
{
type: "text",
text: JSON.stringify(exports, null, 2),
},
],
};
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors.map((e) => `${e.path}: ${e.message}`).join(", ")}`
);
}
throw error;
}
}

if (request.params.name === "introspect-project") {
try {
const args = IntrospectProjectSchema.parse(request.params.arguments);
const options: IntrospectOptions = {
searchTerm: args.searchTerm,
cache: args.cache,
cacheDir: args.cacheDir,
limit: args.limit,
};

const exports = await introspectProject(args.projectPath, options);

return {
content: [
{
type: "text",
text: JSON.stringify(exports, null, 2),
},
],
};
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors.map((e) => `${e.path}: ${e.message}`).join(", ")}`
);
}
throw error;
}
}

throw new Error(`Unknown tool: ${request.params.name}`);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting common error handling logic.

The try/catch blocks for all three tool handlers have identical ZodError handling. This could be extracted to reduce duplication.

♻️ Proposed refactor
+function handleToolError(error: unknown): never {
+    if (error instanceof z.ZodError) {
+        throw new Error(
+            `Invalid arguments: ${error.errors.map((e) => `${e.path}: ${e.message}`).join(", ")}`
+        );
+    }
+    throw error;
+}
+
 server.setRequestHandler(CallToolRequestSchema, async (request) => {
     if (request.params.name === "introspect-package") {
         try {
             // ... existing code ...
         } catch (error) {
-            if (error instanceof z.ZodError) {
-                throw new Error(
-                    `Invalid arguments: ${error.errors.map((e) => `${e.path}: ${e.message}`).join(", ")}`
-                );
-            }
-            throw error;
+            handleToolError(error);
         }
     }
     // ... similar for other handlers
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/mcp-server.ts` around lines 47 - 138, The three
handlers inside server.setRequestHandler ("introspect-package",
"introspect-source", "introspect-project") duplicate identical ZodError catch
logic; extract a small helper (e.g., parseOrThrow(schema, value) or
formatZodError(error) used by parseWithValidation) that calls schema.parse,
catches z.ZodError and throws a new Error with the formatted message, then
replace the direct calls to IntrospectPackageSchema.parse,
IntrospectSourceSchema.parse and IntrospectProjectSchema.parse with the helper;
ensure the helper references z.ZodError for type checking and is used in each
try block so the catch blocks can be removed or simplified.

Comment on lines +46 to +62
const entries = await readdir(pnpmPath);
for (const entry of entries) {
if (entry.includes(packageName)) {
const packagePath = join(pnpmPath, entry, "node_modules", packageName);
const packageJsonPath = join(packagePath, "package.json");

if (existsSync(packageJsonPath)) {
// Resolve symlinks for pnpm
const realPackagePath = realpathSync(packagePath);
logger.info(`Found package in pnpm at: ${realPackagePath}`);
return {
packageJsonPath: join(realPackagePath, "package.json"),
packageDir: realPackagePath,
};
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fuzzy pnpm directory matching may return wrong package.

The entry.includes(packageName) check on line 48 could match unintended packages. For example, searching for typescript would also match typescript-eslint, typescript-transform, etc.

Consider using a more precise match pattern.

🔧 Proposed fix for precise matching
                 for (const entry of entries) {
-                    if (entry.includes(packageName)) {
+                    // pnpm directory format: package-name@version or `@scope`+package-name@version
+                    const escapedName = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+                    const pnpmPattern = new RegExp(`^(@[^+]+\\+)?${escapedName.replace('/', '\\+')}@`);
+                    if (pnpmPattern.test(entry)) {
                         const packagePath = join(pnpmPath, entry, "node_modules", packageName);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const entries = await readdir(pnpmPath);
for (const entry of entries) {
if (entry.includes(packageName)) {
const packagePath = join(pnpmPath, entry, "node_modules", packageName);
const packageJsonPath = join(packagePath, "package.json");
if (existsSync(packageJsonPath)) {
// Resolve symlinks for pnpm
const realPackagePath = realpathSync(packagePath);
logger.info(`Found package in pnpm at: ${realPackagePath}`);
return {
packageJsonPath: join(realPackagePath, "package.json"),
packageDir: realPackagePath,
};
}
}
}
const entries = await readdir(pnpmPath);
for (const entry of entries) {
// pnpm directory format: package-name@version or `@scope`+package-name@version
const escapedName = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const pnpmPattern = new RegExp(`^(@[^+]+\\+)?${escapedName.replace('/', '\\+')}@`);
if (pnpmPattern.test(entry)) {
const packagePath = join(pnpmPath, entry, "node_modules", packageName);
const packageJsonPath = join(packagePath, "package.json");
if (existsSync(packageJsonPath)) {
// Resolve symlinks for pnpm
const realPackagePath = realpathSync(packagePath);
logger.info(`Found package in pnpm at: ${realPackagePath}`);
return {
packageJsonPath: join(realPackagePath, "package.json"),
packageDir: realPackagePath,
};
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/packageResolver.ts` around lines 46 - 62, The fuzzy
match using entry.includes(packageName) can return wrong packages; change the
check in the loop that examines entries to a precise match: compare entry
exactly to packageName or to known pnpm-stored variants (e.g.,
entry.startsWith(packageName + "@") and
entry.startsWith(packageName.replace('/', '+') + "@") or
entry.startsWith(packageName.replace('/', '+') + "+") as needed) so only the
intended package (or package@version/store-encoded scoped name) is selected;
update the check around the entry variable (in the loop in packageResolver.ts
where packageName is used) to use these stricter comparisons before constructing
packagePath and packageJsonPath.

Comment on lines +1 to +6
export interface ExportInfo {
name: string;
kind: "function" | "class" | "type" | "const";
typeSignature: string;
description: string | null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider expanding the kind union type.

The current kind values cover common cases, but TypeScript exports can also include "interface", "enum", "namespace", and "variable". If the export extractor encounters these, they may be miscategorized.

💡 Potential expansion
 export interface ExportInfo {
     name: string;
-    kind: "function" | "class" | "type" | "const";
+    kind: "function" | "class" | "type" | "const" | "interface" | "enum" | "namespace" | "variable";
     typeSignature: string;
     description: string | null;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface ExportInfo {
name: string;
kind: "function" | "class" | "type" | "const";
typeSignature: string;
description: string | null;
}
export interface ExportInfo {
name: string;
kind: "function" | "class" | "type" | "const" | "interface" | "enum" | "namespace" | "variable";
typeSignature: string;
description: string | null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/types.ts` around lines 1 - 6, The ExportInfo.kind union
is too narrow and can miscategorize exports; update the ExportInfo interface
(symbol: ExportInfo, property: kind) to include other possible export kinds such
as "interface", "enum", "namespace", and "variable" (and any other
project-specific variants you expect) so the extractor can represent those
exports accurately; adjust any downstream switch/handling logic that
discriminates on ExportInfo.kind to handle the new values or provide a default
case.

Comment on lines +9 to +16
try {
const regex = new RegExp(searchTerm, "i");
filtered = exports.filter(
(exp) =>
regex.test(exp.name) ||
regex.test(exp.typeSignature) ||
(exp.description && regex.test(exp.description))
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the safe-regex npm package and how to use it for ReDoS prevention?

💡 Result:

safe-regex is an npm package that heuristically flags regular expressions that might exhibit catastrophic backtracking (ReDoS). It does this by analyzing the pattern (not running it) and, per its README, “limits the star height to 1”; it returns a simple boolean “safe/unsafe” result. It also warns there can be false positives and false negatives. [1]

Install

npm i safe-regex

[1]

Use in code (boolean “is this pattern safe?”)

const safe = require('safe-regex');

// string pattern
console.log(safe('(x+x+)+y'));          // false (likely vulnerable)
console.log(safe('(beep|boop)*'));      // true

// RegExp object also works
console.log(safe(/\blocation\s*:[^:\n]+\b(Oakland|San Francisco)\b/)); // true

These examples mirror the package’s README. [1]

Tuning strictness with opts.limit

safe(re, opts) accepts:

  • opts.limit: “maximum number of allowed repetitions in the entire regex” (default 25). [1]
const ok = safe('(a+){10}', { limit: 5 }); // stricter => more likely to return false

[1]

How this helps with ReDoS prevention (practical pattern)

Use safe-regex as a gate before compiling/using regexes that can be influenced by untrusted input (user-provided patterns, or developer patterns applied to untrusted text):

function compileUserRegex(pattern, flags='') {
  if (!safe(pattern)) throw new Error('Rejected potentially unsafe regex');
  return new RegExp(pattern, flags);
}

[1]

Important limitations / stronger defenses

  • Because safe-regex can miss issues, consider additional defenses like input-length limits and/or switching to a non-backtracking engine such as RE2 (via node-re2), which is commonly used specifically to mitigate ReDoS risk. [1][2]

Sources: safe-regex README on npm (description, API, examples, opts.limit, limitations) [1]; RE2/node-re2 rationale for ReDoS protection [2].


🏁 Script executed:

# Find and read the utils.ts file
fd -e ts -p "*utils.ts" | head -20

Repository: genesiscz/GenesisTools

Length of output: 354


🏁 Script executed:

# Check package.json for current dependencies
cat package.json | head -50

Repository: genesiscz/GenesisTools

Length of output: 2081


🏁 Script executed:

# Find utils.ts with proper glob syntax
fd --glob "**/utils.ts" 2>/dev/null | grep -i "mcp-ts-introspect"

Repository: genesiscz/GenesisTools

Length of output: 96


🏁 Script executed:

# Read full package.json and check for safe-regex
cat package.json

Repository: genesiscz/GenesisTools

Length of output: 5137


🏁 Script executed:

cat -n src/mcp-ts-introspect/utils.ts

Repository: genesiscz/GenesisTools

Length of output: 1772


🏁 Script executed:

# Search for filterExports usage to understand MCP exposure
rg "filterExports" src/mcp-ts-introspect/ -A 2 -B 2

Repository: genesiscz/GenesisTools

Length of output: 2616


🏁 Script executed:

# Check the main MCP server implementation
fd --glob "**/mcp-ts-introspect/*.ts" | head -20

Repository: genesiscz/GenesisTools

Length of output: 48


🏁 Script executed:

# Find MCP server implementation
fd --glob "**/mcp-ts-introspect/*.ts" -x wc -l

Repository: genesiscz/GenesisTools

Length of output: 48


🏁 Script executed:

# Look for MCP tools/handlers to see if searchTerm is exposed externally
rg "searchTerm" src/mcp-ts-introspect/ -B 5 -A 2

Repository: genesiscz/GenesisTools

Length of output: 16324


ReDoS vulnerability with externally-provided regex patterns in MCP server.

The MCP server explicitly exposes searchTerm as user input across three tools (introspect-package, introspect-source, introspect-project), where it is used directly in new RegExp(searchTerm, "i"). The current try-catch fallback only catches syntax errors during regex construction—it does not protect against Regular Expression Denial of Service (ReDoS) attacks, which occur during pattern matching (the .test() calls) with valid but pathological patterns.

Add regex validation before compilation using safe-regex:

+import safeRegex from "safe-regex";
+
 export function filterExports(exports: ExportInfo[], searchTerm?: string, limit?: number): ExportInfo[] {
     let filtered = exports;

     // Apply search filter
     if (searchTerm) {
         try {
+            // Validate pattern to prevent catastrophic backtracking
+            if (!safeRegex(searchTerm)) {
+                logger.warn(`Potentially unsafe regex pattern detected, falling back to string match`);
+                throw new Error("Unsafe regex");
+            }
             const regex = new RegExp(searchTerm, "i");

Note: safe-regex uses heuristics and can have false positives/negatives. Consider additional defenses like input length limits or a timeout wrapper around regex operations if further hardening is needed.

🧰 Tools
🪛 ast-grep (0.41.0)

[warning] 9-9: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(searchTerm, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp-ts-introspect/utils.ts` around lines 9 - 16, The code constructs a
RegExp from external searchTerm and runs regex.test during filtering (see new
RegExp(searchTerm, "i"), regex.test(...) and the filtered = exports.filter(...)
usage), which is vulnerable to ReDoS; validate searchTerm with safe-regex before
compiling and executing: import safe-regex, check safeRegex(searchTerm) (and
also enforce a reasonable max length), and if validation fails either escape the
input (use a safe escaping fallback) or reject/skip regex path and fall back to
a simple substring case-insensitive match for name/typeSignature/description so
regex.test is never called on unsafe patterns. Ensure these checks occur before
new RegExp(...) and before any regex.test(...) calls.

Comment on lines +34 to +36
// Required for compatibility
"esModuleInterop": true,
"downlevelIteration": true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

downlevelIteration may be unnecessary with ESNext target.

The downlevelIteration option is used when targeting older JavaScript versions (ES5/ES3) to support accurate iteration semantics. Since the project targets ESNext (line 12), this option has no effect—the compiler won't downlevel iteration constructs.

Consider removing it unless you plan to change the target, or clarify the specific compatibility need in the comment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tsconfig.json` around lines 34 - 36, The tsconfig currently sets
"downlevelIteration": true while the "target" is "ESNext"; remove the
"downlevelIteration" setting (or add a comment) because it has no effect when
targeting ESNext—locate the "downlevelIteration" entry in tsconfig.json and
either delete it or replace it with a short comment explaining why it remains
(e.g., kept for older-target compatibility) so intent is clear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants