AI Workflow
Complete tutorial for the AI-to-AI feedback loop: use Claude Code or Cursor to write code, scan with Radar, get structured output, paste into AI for fixes, and pass the gate.
AI Workflow
Technical Debt Radar is designed to work as a feedback loop between AI coding tools and production standards. You write code with Claude Code, Cursor, or Copilot. Radar scans it. You feed the structured report back to the AI tool. The AI fixes the violations. You push clean code that passes the gate.
This workflow is the fastest way to ship production-quality code with AI assistance.
The AI-to-AI Feedback Loop
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1. Write code with Claude Code / Cursor / Copilot │
│ │ │
│ ▼ │
│ 2. radar scan --format ai-prompt │
│ │ │
│ ▼ │
│ 3. Structured report (optimized for AI consumption) │
│ │ │
│ ▼ │
│ 4. Paste report into AI tool │
│ │ │
│ ▼ │
│ 5. AI fixes violations with full context │
│ │ │
│ ▼ │
│ 6. radar scan --format ai-prompt (verify fixes) │
│ │ │
│ ▼ │
│ 7. Clean code → Push → Gate passes │
│ │
└─────────────────────────────────────────────────────────────┘
Step-by-Step Tutorial
Step 1: Write Code with an AI Tool
Use Claude Code, Cursor, or any AI coding assistant to build a new feature. For this example, we are adding an order export endpoint to a NestJS application:
// src/orders/controllers/order-export.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import fs from 'fs';
const prisma = new PrismaClient();
@Controller('orders')
export class OrderExportController {
@Get('export')
async exportOrders(@Query('format') format: string) {
const orders = await prisma.order.findMany({
include: { items: true, customer: true },
});
const template = fs.readFileSync('./templates/export.html', 'utf-8');
for (const order of orders) {
order.invoice = await prisma.invoice.findFirst({
where: { orderId: order.id },
});
}
sendExportEmail(orders, format);
return { count: orders.length, format };
}
}
This code works. It compiles. It passes unit tests. But it has five violations that Radar will catch.
Step 2: Run Radar with AI-Prompt Format
radar scan src/orders/controllers/order-export.controller.ts --format ai-prompt
Step 3: Read the Structured Report
The ai-prompt format outputs a structured report optimized for AI consumption:
# Technical Debt Radar — Violation Report
# File: src/orders/controllers/order-export.controller.ts
# Score: +29 | Gate: BLOCKED (threshold: 15)
## Violations (5 found)
### [ARCH-001] Architecture Violation (Critical, +5 points)
- Rule: api -> __db__ (controllers must not import database directly)
- Line 2: `import { PrismaClient } from '@prisma/client'`
- Fix: Inject a repository or service instead of using PrismaClient directly.
The controller should depend on an application-layer service that
abstracts database access.
### [PERF-001] Unbounded Query on XL Table (Critical, +8 points)
- Pattern: `prisma.order.findMany()` without `take` or pagination
- Line 8: `const orders = await prisma.order.findMany({ ... })`
- Table: orders (declared as L — 100K-1M rows)
- Fix: Add `take` and `skip` parameters for pagination. Add a `where`
clause to narrow results. Return a paginated response with total count.
### [PERF-002] N+1 Query Pattern (Critical, +8 points)
- Pattern: Database query inside a loop
- Lines 13-16: `for (const order of orders) { order.invoice = await prisma.invoice.findFirst(...) }`
- Fix: Use Prisma `include` to eager-load invoices in the initial query,
or use a single `findMany` with an `in` filter on order IDs.
### [RUNTIME-001] Synchronous File Read in Handler (Critical, +5 points)
- Pattern: `fs.readFileSync` inside HTTP handler
- Line 11: `const template = fs.readFileSync('./templates/export.html', 'utf-8')`
- Fix: Replace with `await fs.promises.readFile(...)`. Synchronous file
reads block the Node.js event loop and cause request timeouts under load.
### [RELIAB-001] Fire-and-Forget Promise (Warning, +3 points)
- Pattern: Async function called without `await` or `.catch()`
- Line 18: `sendExportEmail(orders, format)`
- Fix: Add `await` inside a try/catch block, or add `.catch()` to handle
errors. Unhandled promise rejections crash the Node.js process.
## Fix Priority
1. ARCH-001 — Move database access to a service/repository (+5 credit)
2. PERF-001 — Add pagination to order query (+8 credit)
3. PERF-002 — Replace N+1 with eager loading (+8 credit)
4. RUNTIME-001 — Use async file read (+5 credit)
5. RELIAB-001 — Await or catch the email promise (+3 credit)
## Context
- Architecture: DDD (domain → infrastructure denied)
- Framework: NestJS (handler scope detected via @Get decorator)
- ORM: Prisma (findMany, findFirst patterns detected)
- Data volumes: orders=L, invoices=M
Step 4: Paste the Report into Your AI Tool
Copy the entire output and paste it into Claude Code, Cursor, or your preferred AI assistant:
"Here is a Technical Debt Radar report for my order export controller. Please fix all five violations while preserving the same functionality. Follow DDD architecture --- the controller should use a service, and database access should be in the infrastructure layer."
Step 5: AI Fixes the Violations
The AI tool produces the fixed code. Because the Radar report includes exact line numbers, violation types, and fix instructions, the AI generates precise corrections:
// src/orders/controllers/order-export.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { ExportOrdersUseCase } from '../use-cases/export-orders.use-case';
@Controller('orders')
export class OrderExportController {
constructor(private readonly exportOrders: ExportOrdersUseCase) {}
@Get('export')
async exportOrders(@Query('format') format: string, @Query('page') page = 0) {
return this.exportOrders.execute({ format, page });
}
}
// src/orders/use-cases/export-orders.use-case.ts
import { Injectable } from '@nestjs/common';
import { OrderRepository } from '../domain/order.repository';
import { ExportService } from '../domain/export.service';
import { EmailService } from '../../shared/contracts/email.contracts';
@Injectable()
export class ExportOrdersUseCase {
constructor(
private readonly orderRepo: OrderRepository,
private readonly exportService: ExportService,
private readonly emailService: EmailService,
private readonly logger: Logger,
) {}
async execute(params: { format: string; page: number }) {
try {
const orders = await this.orderRepo.findWithInvoices({
page: params.page,
limit: 50,
});
const template = await this.exportService.loadTemplate('export');
try {
await this.emailService.sendExportEmail(orders.data, params.format);
} catch (error) {
this.logger.error('Failed to send export email', { error });
}
return { count: orders.data.length, total: orders.total, format: params.format };
} catch (error) {
this.logger.error('Export failed', { params, error });
throw error;
}
}
}
// src/orders/infra/prisma-order.repository.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../shared/prisma.service';
import { OrderRepository, PaginatedOrders } from '../domain/order.repository';
@Injectable()
export class PrismaOrderRepository implements OrderRepository {
constructor(private readonly prisma: PrismaService) {}
async findWithInvoices(params: { page: number; limit: number }): Promise<PaginatedOrders> {
const [data, total] = await Promise.all([
this.prisma.order.findMany({
include: { items: true, customer: true, invoice: true },
take: params.limit,
skip: params.page * params.limit,
orderBy: { createdAt: 'desc' },
}),
this.prisma.order.count(),
]);
return { data: data.map(OrderMapper.toDomain), total };
}
}
Every violation is resolved:
- ARCH-001 --- Controller no longer imports
PrismaClient. Database access is in the infrastructure layer. - PERF-001 --- Query uses
takeandskipfor pagination. - PERF-002 --- N+1 replaced with Prisma
include(eager loading). - RUNTIME-001 --- Template loading is now async (in the export service).
- RELIAB-001 --- Email sending is awaited with error handling.
Step 6: Rescan to Verify
$ radar scan src/orders/ --format ai-prompt
# Technical Debt Radar — Violation Report
# Path: src/orders/
# Score: 0 | Gate: PASS (threshold: 15)
## Violations (0 found)
No violations detected. All checks passed.
Step 7: Push and Pass the Gate
git add -A
git commit -m "feat: add order export endpoint with proper DDD architecture"
git push origin feature/order-export
The CI pipeline runs Radar. The PR comment shows:
## Technical Debt Radar Report
Gate: PASS | Score: 0 (threshold: 15)
No violations detected. All checks passed.
Why This Workflow Works
AI coding tools generate syntactically correct code without awareness of three things:
-
Your architecture rules. Claude Code does not know that your domain layer cannot import from infrastructure. It does not know which modules can communicate. It generates the most direct solution, which often violates boundaries.
-
Your data volumes. Cursor does not know your
orderstable has 500K rows. It generatesfindMany()without pagination because it works on the 10-row development database. -
The Node.js event loop. Copilot does not think about concurrency.
fs.readFileSyncis shorter to type than the async alternative, so it generates the sync version inside a request handler.
Radar fills this gap. The ai-prompt format is specifically designed to give AI tools the context they need:
- Exact violation type and location so the AI knows what to fix.
- Fix instructions so the AI knows the correct pattern to use.
- Architecture context so the AI understands the rules it must follow.
- Data volume context so the AI generates paginated queries.
The result: AI writes code, Radar catches what it misses, AI fixes the issues, and you ship clean code. The entire loop takes minutes, not hours.
Using radar fix Instead
For an even tighter loop, use radar fix to skip the manual copy-paste step:
# Scan and fix in one command
radar fix src/orders/
# Radar scans, sends violations + context to Claude API,
# generates fixes, and presents them for your approval
The radar fix command handles the entire loop automatically: scan, generate fixes, present diffs, and apply with your confirmation. It requires an Anthropic API key and a Solo plan or higher.
Tips for Better AI Fixes
Be specific in your prompt. When pasting the Radar report into an AI tool, add context about your architecture:
"Fix these violations. We use DDD with NestJS. Domain layer must have zero infrastructure imports. All database access goes through repository interfaces."
Fix one file at a time. AI tools produce better fixes when focused on a single file rather than a batch of 10 files.
Rescan after every fix round. Some fixes can introduce new violations (e.g., moving code to a new file that is in the wrong layer). Always rescan.
Use --format ai-prompt consistently. The ai-prompt format is optimized for AI tools. The github and markdown formats are optimized for human reading and may not provide the structured context that AI tools need.