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