Skip to main content
This example demonstrates building a code assistant that can analyze codebases, read files, and provide intelligent coding help using PromptSmith with Mastra.

What You’ll Build

A code assistant that can:
  • Read and analyze source files
  • Search for code patterns
  • Provide code suggestions and explanations
  • Follow coding best practices

Prerequisites

npm install promptsmith-ts @mastra/core zod
Set up your API key (example with Anthropic):
export ANTHROPIC_API_KEY="your-api-key-here"

Complete Example

import { Agent } from "@mastra/core/agent";
import { createPromptBuilder } from "promptsmith-ts/builder";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";

// Tool: Read a file from the filesystem
async function readFile(filePath: string) {
  try {
    const absolutePath = path.resolve(filePath);
    const content = await fs.readFile(absolutePath, "utf-8");
    const lines = content.split("\n");
    
    return {
      path: filePath,
      content,
      lineCount: lines.length,
      size: content.length,
    };
  } catch (error) {
    return {
      error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
    };
  }
}

// Tool: Search for code patterns
async function searchCode(directory: string, pattern: string) {
  try {
    // In production, use proper glob/search library
    const files = await fs.readdir(directory);
    const results = [];
    
    for (const file of files) {
      if (file.endsWith(".ts") || file.endsWith(".js")) {
        const filePath = path.join(directory, file);
        const content = await fs.readFile(filePath, "utf-8");
        
        if (content.includes(pattern)) {
          const lines = content.split("\n");
          const matchingLines = lines
            .map((line, index) => ({ line, number: index + 1 }))
            .filter(({ line }) => line.includes(pattern));
          
          results.push({
            file,
            path: filePath,
            matches: matchingLines.length,
            lines: matchingLines.slice(0, 5), // First 5 matches
          });
        }
      }
    }
    
    return results;
  } catch (error) {
    return {
      error: `Search failed: ${error instanceof Error ? error.message : String(error)}`,
    };
  }
}

// Tool: Analyze code structure
async function analyzeFile(filePath: string) {
  try {
    const content = await fs.readFile(filePath, "utf-8");
    const lines = content.split("\n");
    
    // Simple analysis (in production, use proper AST parser)
    const functions = lines.filter(line => 
      line.includes("function ") || line.includes("const ") && line.includes(" => ")
    );
    const imports = lines.filter(line => line.trim().startsWith("import "));
    const exports = lines.filter(line => line.includes("export "));
    
    return {
      filePath,
      totalLines: lines.length,
      functions: functions.length,
      imports: imports.length,
      exports: exports.length,
      blankLines: lines.filter(line => line.trim() === "").length,
    };
  } catch (error) {
    return {
      error: `Analysis failed: ${error instanceof Error ? error.message : String(error)}`,
    };
  }
}

async function main() {
  // Build the code assistant
  const codeAssistant = createPromptBuilder()
    .withIdentity(
      "You are an expert programming assistant specializing in TypeScript and JavaScript"
    )
    .withCapabilities([
      "Read and analyze source code files",
      "Search for code patterns and functions",
      "Explain code functionality and best practices",
      "Suggest improvements and refactoring opportunities",
      "Help debug and troubleshoot issues",
    ])
    .withTool({
      name: "read_file",
      description: "Read the contents of a source code file",
      schema: z.object({
        filePath: z.string().describe("Path to the file to read"),
      }),
      execute: async ({ filePath }) => {
        return await readFile(filePath);
      },
    })
    .withTool({
      name: "search_code",
      description: "Search for code patterns in a directory",
      schema: z.object({
        directory: z.string().describe("Directory to search in"),
        pattern: z.string().describe("Code pattern to search for"),
      }),
      execute: async ({ directory, pattern }) => {
        return await searchCode(directory, pattern);
      },
    })
    .withTool({
      name: "analyze_file",
      description: "Analyze the structure and metrics of a code file",
      schema: z.object({
        filePath: z.string().describe("Path to the file to analyze"),
      }),
      execute: async ({ filePath }) => {
        return await analyzeFile(filePath);
      },
    })
    .withConstraint("must", [
      "Always read files before making suggestions about their content",
      "Provide specific line numbers when referencing code",
      "Explain the reasoning behind code suggestions",
      "Follow TypeScript and JavaScript best practices",
    ])
    .withConstraint("must_not", [
      "Suggest code changes without understanding the full context",
      "Make assumptions about file contents without reading them",
      "Recommend deprecated or insecure patterns",
    ])
    .withGuardrails()
    .withTone("Clear, educational, and constructive");

  // Export to Mastra format
  const { instructions, tools } = codeAssistant.toMastra();

  // Create Mastra agent
  const agent = new Agent({
    name: "code-assistant",
    instructions,
    model: "anthropic/claude-3-5-sonnet",
    tools,
  });

  // Use the assistant
  const response = await agent.generate([
    {
      role: "user",
      content: "Can you analyze the main.ts file and suggest any improvements?",
    },
  ]);

  console.log("Code Assistant Response:");
  console.log(response.text);
}

main().catch(console.error);

Step-by-Step Walkthrough

1

Define File Operation Tools

Create tools for reading files, searching code, and analyzing structure:
async function readFile(filePath: string) {
  const content = await fs.readFile(filePath, "utf-8");
  return {
    path: filePath,
    content,
    lineCount: content.split("\n").length,
  };
}
These tools provide the assistant with access to your codebase. In production, add proper security checks and path validation.
2

Create the Code Assistant Builder

Set up the assistant’s identity and expertise:
const codeAssistant = createPromptBuilder()
  .withIdentity(
    "You are an expert programming assistant specializing in TypeScript and JavaScript"
  )
The identity establishes the assistant’s area of expertise.
3

Add Code Analysis Tools

Add tools for file operations and code analysis:
.withTool({
  name: "read_file",
  description: "Read the contents of a source code file",
  schema: z.object({
    filePath: z.string().describe("Path to the file to read"),
  }),
  execute: async ({ filePath }) => await readFile(filePath),
})
Each tool is typed with Zod for parameter validation.
4

Set Coding Best Practices

Define constraints that ensure quality suggestions:
.withConstraint("must", [
  "Always read files before making suggestions",
  "Provide specific line numbers when referencing code",
  "Explain the reasoning behind suggestions",
])
These constraints ensure the assistant follows best practices.
5

Export to Mastra

Convert the PromptSmith configuration to Mastra format:
const { instructions, tools } = codeAssistant.toMastra();

const agent = new Agent({
  name: "code-assistant",
  instructions,
  model: "anthropic/claude-3-5-sonnet",
  tools,
});
The .toMastra() method handles all the conversion automatically.
6

Use the Assistant

Generate responses using the Mastra agent:
const response = await agent.generate([
  {
    role: "user",
    content: "Can you analyze the main.ts file?",
  },
]);
The assistant will use its tools to read and analyze the file.

Expected Output

When you run this example, you’ll see output similar to:
Code Assistant Response:
I've analyzed the main.ts file. Here's what I found:

File Statistics:
- Total lines: 156
- Functions: 8
- Imports: 12
- Exports: 5

Suggestions for improvement:

1. Lines 23-45: The `processData` function is quite long (22 lines). Consider
   breaking it into smaller, more focused functions for better maintainability.

2. Line 67: The error handling could be more specific. Instead of catching all
   errors, consider handling specific error types differently.

3. Lines 89-92: This code is duplicated from lines 34-37. Consider extracting
   it into a shared helper function.

Would you like me to show you specific refactoring examples for any of these?

Key Concepts

File System Tools - This example shows how to give your agent access to the file system safely using tools with proper error handling and validation.
Mastra Integration - Using .toMastra() automatically converts PromptSmith tools to Mastra’s format, eliminating duplication and ensuring compatibility.
Context-Aware Analysis - The constraints ensure the assistant always reads files before making suggestions, providing context-aware recommendations.

Security Considerations

When building code assistants with file access:
  • Validate and sanitize all file paths
  • Restrict access to specific directories
  • Never execute code without explicit user confirmation
  • Log all file operations for audit trails
  • Consider rate limiting to prevent abuse

Customization Ideas

  • Add a write_file tool for code modifications
  • Include a run_tests tool for testing suggestions
  • Add git integration for viewing diffs and history
  • Implement AST parsing for deeper code analysis
  • Add linting and formatting tools

Next Steps

Data Analysis Agent

Learn how to build agents with conditional logic

Mastra Integration

Deep dive into using PromptSmith with Mastra

Build docs developers (and LLMs) love