Skip to main content
Glama

tdd

Implement code using strict Test-Driven Development methodology by writing tests before production code. Supports iterative TDD cycles with session resume functionality.

Instructions

Invoke Codex CLI to implement code using strict Test-Driven Development (Red-Green-Refactor). Injects TDD methodology template that enforces writing tests before production code. Supports session resume for iterative TDD cycles.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
instructionYesDetailed description of the feature or bug fix to implement using TDD
sessionIdNoResume an existing TDD session by ID (from previous tdd return value)
workingDirectoryNoWorking directory path
planReferenceNoPlan file path or content summary, used as coding context
taskContextNoTask position and context within the plan (e.g. 'Task 3 of 5: Implement validation logic. Depends on Task 2 auth module.')
testFrameworkNoTest framework hint (e.g. 'jest', 'vitest', 'pytest', 'go test')

Implementation Reference

  • The main handler function codexTdd that implements the TDD tool logic. It loads templates, processes context, executes Codex CLI with TDD methodology, manages sessions, and returns structured results with status tracking.
    export async function codexTdd(
      params: CodexTddParams,
      extra?: { signal?: AbortSignal }
    ): Promise<CodexWriteResult> {
      const config = await loadConfig({
        workingDirectory: params.workingDirectory,
      });
      const toolCfg = getToolConfig(config, "tdd");
      const executor = await CodexExecutor.create({
        workingDirectory: params.workingDirectory,
      });
    
      // Load and fill the TDD template
      const template = await loadTddTemplate({
        tddTemplate: config.tddTemplate,
        workingDirectory: params.workingDirectory,
      });
    
      // Build context section: plan reference + task position
      let contextValue = "";
      if (params.planReference) {
        contextValue += params.planReference;
      }
      if (params.taskContext) {
        if (contextValue) contextValue += "\n\n";
        contextValue += `**Current Task Position:** ${params.taskContext}`;
      }
      if (!contextValue) {
        contextValue = "(No plan reference provided)";
      }
    
      // Strip TEST_FRAMEWORK section from template if not provided
      let processedTemplate = template;
      if (!params.testFramework) {
        // Remove "### Test Framework\n\nUse: {TEST_FRAMEWORK}\n" section
        processedTemplate = processedTemplate.replace(
          /### Test Framework\s*\n+Use: \{TEST_FRAMEWORK\}\s*\n*/g,
          ""
        );
      }
    
      const fullInstruction = fillTemplate(processedTemplate, {
        INSTRUCTION: params.instruction,
        PLAN_REFERENCE: contextValue,
        ...(params.testFramework
          ? { TEST_FRAMEWORK: params.testFramework }
          : {}),
      });
    
      try {
        const operationId = `tdd-${crypto.randomUUID()}`;
        progressServer.startOperation(
          operationId,
          "write",
          params.instruction.slice(0, 120)
        );
    
        // Mark session as active before execution (for resume)
        if (params.sessionId) {
          await sessionManager.updateStatus(params.sessionId, "active", {
            workingDirectory: params.workingDirectory,
          });
        }
    
        let result;
        try {
          result = await executor.executeWrite(fullInstruction, {
            sessionId: params.sessionId,
            workingDirectory: params.workingDirectory,
            model: toolCfg.model,
            sandbox: toolCfg.sandbox,
            timeout: toolCfg.timeout,
            onLine: (line) => {
              const event = mapCodexLineToProgressEvent(line, operationId);
              if (event) progressServer.emit(event);
            },
            signal: extra?.signal,
          });
        } finally {
          progressServer.endOperation(operationId, result?.exitCode === 0);
        }
    
        // Parse output
        const parsed = executor.parseOutput(result.stdout);
        if (!params.sessionId && !parsed.sessionId) {
          throw {
            code: CodexErrorCode.CODEX_INVALID_OUTPUT,
            message:
              "Codex CLI did not emit a session_id/thread_id in --json output. Ensure Codex CLI is up to date and that --json output is enabled.",
            recoverable: false,
          } satisfies CodexErrorInfo;
        }
    
        // Determine status
        let status: CodexWriteResult["status"] = "completed";
        if (result.exitCode !== 0) {
          status = "error";
        } else if (
          parsed.filesCreated.length > 0 ||
          parsed.filesModified.length > 0
        ) {
          status = "needs_review";
        }
    
        // Track session
        const sessionId = parsed.sessionId || params.sessionId;
        if (!sessionId) {
          throw {
            code: CodexErrorCode.CODEX_INVALID_OUTPUT,
            message:
              "Missing sessionId (neither parsed from Codex output nor provided via params.sessionId).",
            recoverable: false,
          } satisfies CodexErrorInfo;
        }
        const trackedStatus = result.exitCode !== 0 ? "abandoned" : "completed";
    
        if (params.sessionId) {
          // Resuming existing session
          await sessionManager.markResumed(params.sessionId, {
            workingDirectory: params.workingDirectory,
          });
          await sessionManager.updateStatus(params.sessionId, trackedStatus, {
            workingDirectory: params.workingDirectory,
          });
        } else {
          // New session
          await sessionManager.track(
            {
              sessionId,
              type: "write",
              instruction: params.instruction,
              createdAt: new Date().toISOString(),
              status: trackedStatus,
            },
            { workingDirectory: params.workingDirectory }
          );
        }
    
        return {
          success: result.exitCode === 0,
          sessionId,
          output: {
            summary: parsed.summary,
            filesModified: parsed.filesModified,
            filesCreated: parsed.filesCreated,
          },
          status,
        };
      } catch (error) {
        // Restore session status if it was set to "active" before failure
        if (params.sessionId) {
          try {
            await sessionManager.updateStatus(params.sessionId, "abandoned", {
              workingDirectory: params.workingDirectory,
            });
          } catch {
            /* best effort */
          }
        }
        const errorInfo = error as CodexErrorInfo;
        return {
          success: false,
          sessionId: params.sessionId || "",
          output: {
            summary: "",
            filesModified: [],
            filesCreated: [],
          },
          status: "error",
          error: {
            code: errorInfo.code || CodexErrorCode.UNKNOWN_ERROR,
            message: errorInfo.message || String(error),
            recoverable: errorInfo.recoverable ?? false,
            suggestion: errorInfo.suggestion,
          },
        };
      }
    }
  • CodexTddParamsSchema defines the input validation schema for the TDD tool including instruction, sessionId, workingDirectory, planReference, taskContext, and testFramework parameters.
    export const CodexTddParamsSchema = z.object({
      instruction: z
        .string()
        .describe(
          "Detailed description of the feature or bug fix to implement using TDD"
        ),
      sessionId: z
        .string()
        .uuid()
        .optional()
        .describe("Resume an existing TDD session by ID"),
      workingDirectory: z
        .string()
        .optional()
        .describe("Working directory path"),
      planReference: z
        .string()
        .optional()
        .describe("Plan file path or content summary, used as coding context"),
      taskContext: z
        .string()
        .optional()
        .describe(
          "Task position and context within the plan " +
            "(e.g. 'Task 3 of 5: Implement validation logic. Depends on Task 2 auth module.')"
        ),
      testFramework: z
        .string()
        .optional()
        .describe(
          "Test framework hint (e.g. 'jest', 'vitest', 'pytest', 'go test'). " +
            "Defaults to auto-detect from project."
        ),
    });
    
    export type CodexTddParams = z.infer<typeof CodexTddParamsSchema>;
  • src/index.ts:81-136 (registration)
    Registration of the 'tdd' tool with the MCP server. Lines 82-135 define the tool's description, input parameters, and handler binding that connects the MCP tool to the codexTdd implementation.
    // ─── tdd ───────────────────────────────────────────────────────────
    if (isToolEnabled(config, "tdd")) {
      server.tool(
        "tdd",
        "Invoke Codex CLI to implement code using strict Test-Driven Development (Red-Green-Refactor). " +
          "Injects TDD methodology template that enforces writing tests before production code. " +
          "Supports session resume for iterative TDD cycles.",
        {
          instruction: z
            .string()
            .describe(
              "Detailed description of the feature or bug fix to implement using TDD"
            ),
          sessionId: z
            .string()
            .optional()
            .describe(
              "Resume an existing TDD session by ID (from previous tdd return value)"
            ),
          workingDirectory: z
            .string()
            .optional()
            .describe("Working directory path"),
          planReference: z
            .string()
            .optional()
            .describe(
              "Plan file path or content summary, used as coding context"
            ),
          taskContext: z
            .string()
            .optional()
            .describe(
              "Task position and context within the plan " +
                "(e.g. 'Task 3 of 5: Implement validation logic. Depends on Task 2 auth module.')"
            ),
          testFramework: z
            .string()
            .optional()
            .describe(
              "Test framework hint (e.g. 'jest', 'vitest', 'pytest', 'go test')"
            ),
        },
        async (params, extra) => {
          const result = await codexTdd(params, extra);
          return {
            content: [
              {
                type: "text" as const,
                text: JSON.stringify(result, null, 2),
              },
            ],
          };
        }
      );
    }
  • Helper functions for the TDD tool: loadTddTemplate loads the TDD methodology template from config, bundled files, or hardcoded fallback; fillTemplate substitutes placeholders like {INSTRUCTION}, {PLAN_REFERENCE}, and {TEST_FRAMEWORK}.
    async function loadTddTemplate(options: {
      tddTemplate?: string;
      workingDirectory?: string;
    }): Promise<string> {
      // Priority 1: Config override (file path or inline content)
      if (options.tddTemplate) {
        const templateOverride = options.tddTemplate;
        const candidates: string[] = [];
    
        if (options.workingDirectory) {
          candidates.push(
            path.resolve(options.workingDirectory, templateOverride)
          );
        }
        candidates.push(path.resolve(templateOverride));
    
        for (const candidate of candidates) {
          try {
            if (fs.existsSync(candidate)) {
              return await fs.promises.readFile(candidate, "utf-8");
            }
          } catch {
            // continue
          }
        }
    
        // If it looks like inline template content, use it directly
        if (
          templateOverride.includes("\n") ||
          templateOverride.trimStart().startsWith("#")
        ) {
          return templateOverride;
        }
    
        console.error(
          `codex-dev: tddTemplate override not found: ${templateOverride}`
        );
      }
    
      // Priority 2: Bundled template file
      const moduleDir = path.dirname(fileURLToPath(import.meta.url));
      const templatePath = path.join(
        moduleDir,
        "..",
        "..",
        "templates",
        "tdd-developer.md"
      );
    
      try {
        if (fs.existsSync(templatePath)) {
          return await fs.promises.readFile(templatePath, "utf-8");
        }
      } catch {
        // Fall through to default
      }
    
      // Priority 3: Hardcoded fallback
      return DEFAULT_TDD_TEMPLATE;
    }
    
    function fillTemplate(
      template: string,
      values: Record<string, string>
    ): string {
      let result = template;
      for (const [key, value] of Object.entries(values)) {
        result = result.replace(new RegExp(`\\{${key}\\}`, "g"), () => value);
      }
      return result;
    }

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/FYZAFH/mcp-codex-dev'

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