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
| Name | Required | Description | Default |
|---|---|---|---|
| instruction | Yes | Detailed description of the feature or bug fix to implement using TDD | |
| sessionId | No | Resume an existing TDD session by ID (from previous tdd return value) | |
| workingDirectory | No | Working directory path | |
| planReference | No | Plan file path or content summary, used as coding context | |
| taskContext | No | Task position and context within the plan (e.g. 'Task 3 of 5: Implement validation logic. Depends on Task 2 auth module.') | |
| testFramework | No | Test framework hint (e.g. 'jest', 'vitest', 'pytest', 'go test') |
Implementation Reference
- src/tools/codex-tdd.ts:54-231 (handler)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, }, }; } } - src/tools/codex-tdd.ts:17-52 (schema)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), }, ], }; } ); } - src/tools/codex-tdd.ts:235-305 (helper)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; }