Skip to main content
Glama

analyze_repository

Parses every TypeScript and JavaScript file in a repository to build an AST cache and return a structural summary of classes, functions, and exports for downstream analysis.

Instructions

Parses every TypeScript and JavaScript file in the current repository via ts-morph and returns a structural summary: total file count, class count, top-level function count, exported symbol count, plus a per-file breakdown. This is the first step in the Veris analysis chain — it builds the AST cache that all downstream tools (export_behavioral_graph, list_workflows, generate_verification_plan, etc.) consume. Call this once at the start of an agent session for any repo.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The handler function for the 'analyze_repository' tool. It calls ensureReport() which creates a RepositoryIntelligenceEngine and invokes engine.analyze(), then returns a text response with file count.
    private handleAnalyzeRepository() {
        const r = this.ensureReport();
        return this.text(`Repository analyzed. Found ${r.files.length} files.`);
    }
  • Tool definition/schema for 'analyze_repository' in the toolDefinitions() method. Defines description, and empty inputSchema (no parameters required).
    { name: "analyze_repository",
      description: "Parses every TypeScript and JavaScript file in the current repository via ts-morph and returns a structural summary: total file count, class count, top-level function count, exported symbol count, plus a per-file breakdown. This is the first step in the Veris analysis chain — it builds the AST cache that all downstream tools (export_behavioral_graph, list_workflows, generate_verification_plan, etc.) consume. Call this once at the start of an agent session for any repo.",
      inputSchema: { type: "object", properties: {}, required: [] } },
  • Registration of the tool in the switch-case handler within setupToolHandlers(). When tool name matches 'analyze_repository', it routes to handleAnalyzeRepository().
    case "analyze_repository": return this.handleAnalyzeRepository();
  • The ensureReport() helper method that lazily creates a RepositoryIntelligenceEngine and calls engine.analyze() to produce the report. This is called by handleAnalyzeRepository().
    private ensureReport(): RepositoryIntelligenceReport {
        if (this.lastReport) return this.lastReport;
        const engine = new RepositoryIntelligenceEngine(this.projectRoot);
        this.lastReport = engine.analyze();
        return this.lastReport;
    }
  • The RepositoryIntelligenceEngine class that performs the actual analysis. Its analyze() method uses ts-morph to parse all TS/JS files and extract classes, functions, imports, and call expressions.
    export class RepositoryIntelligenceEngine {
        private project: Project;
        private security: SecurityBaselineConfig;
    
        constructor(private projectRoot: string, security?: Partial<SecurityBaselineConfig>) {
            this.security = {
                zeroRetentionMode: security?.zeroRetentionMode ?? true,
                airGapped: security?.airGapped ?? true,
                ignoredPaths: security?.ignoredPaths ?? ['node_modules', 'dist', '.git', 'veris-reports', 'bvi-reports', 'coverage', '.next', 'build']
            };
    
            this.project = new Project({
                useInMemoryFileSystem: false,
                // Don't try to type-check; we just want AST. Massive perf win on big repos.
                compilerOptions: { allowJs: true, checkJs: false, noEmit: true, skipLibCheck: true }
            });
    
            const includes = [`${projectRoot}/**/*.ts`, `${projectRoot}/**/*.tsx`, `${projectRoot}/**/*.js`, `${projectRoot}/**/*.jsx`, `${projectRoot}/**/*.mjs`, `${projectRoot}/**/*.cjs`];
            const excludes = this.security.ignoredPaths.map(p => `!${projectRoot}/${p}/**/*`);
            this.project.addSourceFilesAtPaths([...includes, ...excludes]);
        }
    
        public analyze(): RepositoryIntelligenceReport {
            const sourceFiles = this.project.getSourceFiles();
    
            const report: RepositoryIntelligenceReport = {
                projectPath: this.projectRoot,
                files: [],
                dependencyMap: {}
            };
    
            for (const file of sourceFiles) {
                const verisFile = this.extractFileData(file);
                report.files.push(verisFile);
                report.dependencyMap[verisFile.filePath] = verisFile.imports;
            }
    
            return report;
        }
    
        private extractFileData(file: SourceFile): VerisFile {
            const filePath = file.getFilePath();
            const imports = this.extractImports(file);
            const classes = this.extractClasses(file);
            const functions = this.extractFunctions(file, classes);
            return { filePath, classes, functions, imports };
        }
    
        private extractImports(file: SourceFile): string[] {
            const out = new Set<string>();
            // ES imports
            for (const imp of file.getImportDeclarations()) {
                const spec = imp.getModuleSpecifierValue();
                if (spec) out.add(spec);
            }
            // CommonJS require() calls
            try {
                for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) {
                    const callee = call.getExpression();
                    if (callee.getText() !== 'require') continue;
                    const args = call.getArguments();
                    if (args.length === 0) continue;
                    const first = args[0];
                    if (first.getKind() === SyntaxKind.StringLiteral) {
                        const txt = first.getText();
                        // Strip quotes
                        out.add(txt.replace(/^['"`]|['"`]$/g, ''));
                    }
                }
            } catch {
                // resilient
            }
            return Array.from(out);
        }
    
        private extractClasses(file: SourceFile): VerisClass[] {
            const classes: VerisClass[] = file.getClasses().map(cls => ({
                name: cls.getName() || 'AnonymousClass',
                methods: cls.getMethods().map(method => ({
                    name: method.getName(),
                    isExported: cls.isExported(),
                    calls: this.extractCalls(method)
                }))
            }));
    
            // CommonJS prototype assignments: ClassName.prototype.method = function() {}
            // Group by class name, attach methods.
            try {
                const protoAssignments = new Map<string, { methodName: string; node: Node }[]>();
                for (const bin of file.getDescendantsOfKind(SyntaxKind.BinaryExpression)) {
                    if (bin.getOperatorToken().getText() !== '=') continue;
                    const left = bin.getLeft();
                    if (left.getKind() !== SyntaxKind.PropertyAccessExpression) continue;
                    const leftText = left.getText();
                    // Match `Foo.prototype.bar`
                    const m = leftText.match(/^([A-Z][\w$]*)\.prototype\.([\w$]+)$/);
                    if (!m) continue;
                    const [, className, methodName] = m;
                    const right = bin.getRight();
                    if (right.getKind() !== SyntaxKind.FunctionExpression && right.getKind() !== SyntaxKind.ArrowFunction) continue;
                    if (!protoAssignments.has(className)) protoAssignments.set(className, []);
                    protoAssignments.get(className)!.push({ methodName, node: right });
                }
                protoAssignments.forEach((methods, className) => {
                    let cls = classes.find(c => c.name === className);
                    if (!cls) {
                        cls = { name: className, methods: [] };
                        classes.push(cls);
                    }
                    for (const m of methods) {
                        if (cls.methods.find(x => x.name === m.methodName)) continue;
                        cls.methods.push({
                            name: m.methodName,
                            isExported: true,
                            calls: this.extractCalls(m.node as any)
                        });
                    }
                });
            } catch {
                // ignore
            }
    
            return classes;
        }
    
        private extractFunctions(file: SourceFile, classes: VerisClass[]): VerisFunction[] {
            const functions: VerisFunction[] = [];
            const seen = new Set<string>();
            const classNames = new Set(classes.map(c => c.name));
    
            // Function declarations
            for (const fn of file.getFunctions()) {
                const name = fn.getName();
                if (!name) continue;
                if (seen.has(name)) continue;
                seen.add(name);
                functions.push({ name, isExported: fn.isExported(), calls: this.extractCalls(fn) });
            }
    
            // const X = function/() => ...   AND   var X = function/() => ...   AND   let X = ...
            try {
                for (const varDecl of file.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
                    const name = varDecl.getName();
                    if (!name || seen.has(name) || classNames.has(name)) continue;
                    const init = varDecl.getInitializer();
                    if (!init) continue;
                    const k = init.getKind();
                    if (k !== SyntaxKind.FunctionExpression && k !== SyntaxKind.ArrowFunction) continue;
                    // Only top-level (parent.parent.parent should be SourceFile via VariableStatement)
                    seen.add(name);
                    functions.push({ name, isExported: true, calls: this.extractCalls(init as any) });
                }
            } catch {
                // ignore
            }
    
            // module.exports.X = function() {} OR exports.X = function() {}
            try {
                for (const bin of file.getDescendantsOfKind(SyntaxKind.BinaryExpression)) {
                    if (bin.getOperatorToken().getText() !== '=') continue;
                    const left = bin.getLeft();
                    if (left.getKind() !== SyntaxKind.PropertyAccessExpression) continue;
                    const leftText = left.getText();
                    let propName: string | null = null;
                    const m1 = leftText.match(/^module\.exports\.([\w$]+)$/);
                    const m2 = leftText.match(/^exports\.([\w$]+)$/);
                    if (m1) propName = m1[1];
                    else if (m2) propName = m2[1];
                    if (!propName || seen.has(propName) || classNames.has(propName)) continue;
                    const right = bin.getRight();
                    const k = right.getKind();
                    if (k !== SyntaxKind.FunctionExpression && k !== SyntaxKind.ArrowFunction) continue;
                    seen.add(propName);
                    functions.push({ name: propName, isExported: true, calls: this.extractCalls(right as any) });
                }
                // `module.exports = function NAMED() {}` -> create a function with the file's base name
                const baseFnExports = file.getDescendantsOfKind(SyntaxKind.BinaryExpression).filter(b => {
                    if (b.getOperatorToken().getText() !== '=') return false;
                    const lt = b.getLeft().getText();
                    return lt === 'module.exports' || lt === 'exports';
                });
                for (const b of baseFnExports) {
                    const right = b.getRight();
                    const k = right.getKind();
                    if (k !== SyntaxKind.FunctionExpression && k !== SyntaxKind.ArrowFunction) continue;
                    // Use the file's base name (without extension) as the function name
                    const base = file.getBaseNameWithoutExtension();
                    if (seen.has(base) || classNames.has(base)) continue;
                    seen.add(base);
                    functions.push({ name: base, isExported: true, calls: this.extractCalls(right as any) });
                }
            } catch {
                // ignore
            }
    
            return functions;
        }
    
        private extractCalls(node: any): string[] {
            const calls: Set<string> = new Set();
            try {
                const callExprs = node.getDescendantsOfKind(SyntaxKind.CallExpression);
                for (const c of callExprs) {
                    const expr = c.getExpression();
                    const text = expr.getText();
                    const callee = text.split('.').pop();
                    if (callee && /^[A-Za-z_$][\w$]*$/.test(callee)) calls.add(callee);
                }
            } catch {
                // resilient
            }
            return Array.from(calls);
        }
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses the tool's behavior: parsing files, building an AST cache, and returning a summary. Although no annotations exist, the description covers key aspects. Minor gap: does not specify if the cache persists or affects state.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences containing all necessary information without extraneous content. Front-loaded with core action and output, then adds usage guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no parameters and no output schema, the description fully explains return values and its role in the analysis chain. No missing context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

No parameters are defined, so schema coverage is 100%. The description does not need to elaborate on parameters. Baseline for 0 params is 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: parsing TypeScript/JavaScript files and returning a structural summary with specific counts and per-file breakdown. It distinguishes from siblings by positioning itself as the first step in the analysis chain.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to call it ('once at the start of an agent session for any repo') and explains it is the preliminary step for downstream tools, providing clear usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vighriday/Veris'

If you have feedback or need assistance with the MCP directory API, please join our Discord server