Skip to content

Generating Code ​

In Carotene, you do not write business logic; you write architectural boundaries and generative intent. But how does the framework actually translate your intent into deterministic, production-ready code?

Carotene does not just blindly send your entire file to an LLM and cross its fingers. The generation engine is a highly structured compiler pipeline that treats the AI as a pure logic synthesizer, strictly bounded by the rules of your architecture.

Here is the exact lifecycle of how Carotene generates your code.

1. Context Extraction (Parsing the Blueprint) ​

When you run carrot build or carrot dev, the Carotene compiler first acts like a traditional language compiler. It parses your .carrot files into an Abstract Syntax Tree (AST).

During this phase, the compiler discovers a "Code Hole"—your @(...) Generative Operator. Before it talks to the AI, the compiler builds a rigid Context Matrix for that specific hole. It gathers:

  • The exact types of the input arguments.
  • The expected return type of the function.
  • The database schemas (models) the function is allowed to read or update.
  • The external integrations it is allowed to call.
  • The specific test asserts statements that apply to this function.

2. The Prompt Assembly ​

Carotene does not use generic "Write me a function" prompts. It uses the Context Matrix to construct a mathematically precise system prompt.

If you wrote:

dart
discount = @("Calculate a tiered volume discount based on past orders")

Carotene builds a prompt that looks like this under the hood:

System: You are a deterministic logic synthesizer. Your output will be executed in a zero-trust sandbox.

Language: TypeScript

Context:

  • Input: userId: string

  • Allowed DB Access: store.Order (fields: id, userId, total, date)

  • Return Type Expected: number

    Task: Write the internal logic to satisfy the following intent: "Calculate a tiered volume discount based on past orders".

    Constraints: Do not write network code. Do not write SQL connections. Return ONLY the raw mathematical logic to calculate the discount.

By hyper-focusing the context, Carotene completely eliminates the risk of the AI hallucinating external libraries, guessing incorrect database table names, or writing boilerplate you didn't ask for.

The Generation Lifecycle & Code Ownership ​

Carotene bridges the gap between AI generation and manual coding using a strict Two-Way Sync model. The framework generates read-only TypeScript/Go interfaces in the .generated/ folder, and implementation stubs in the src/ folder.

By default, the Carotene compiler assumes AI Ownership over the src/ stubs. When the .carrot contract changes, the compiler invokes the LLM and overwrites the implementation stub to match the new requirements.

Smart Sync & Manual Overrides ​

You are never locked out of your own code. If the AI is struggling to implement a specific function, or you have a proprietary algorithm you want to write by hand, you can simply open the generated src/ file and write the logic yourself.

Carotene strictly protects human code using Hash-Detection:

  1. The Hash Check: When you run carrot build or carrot dev, the compiler checks the cryptographic hash of every src/ file against a local .carotene/cache.
  2. Detection: If it detects manual edits in a file that is currently owned by the AI, it immediately halts generation for that specific file to protect your work.
  3. The CLI Intercept: The CLI issues an interactive prompt in your terminal:
    bash
    [!] Manual edits detected in AI-managed files.
    The following files have been modified outside of the Carotene compiler:
      - src/auth/LegacySSO.ts
    
    ? How would you like to proceed? (Use arrow keys)
      ❯ Set as @manual in contract (Preserve edits and ignore in future)
        Overwrite file (Discard manual edits and regenerate via AI)
        Skip for now (Do not regenerate this run)
  4. Auto-Sync: If you choose "Set as @manual", the CLI automatically injects the @manual decorator above that function in your .carrot file. The compiler will now permanently ignore this file during AI generation.

Note: Even when a file is @manual, Carotene continues to strictly enforce its inputs and outputs. If the contract signature changes later, your native compiler (TypeScript/Go) will flag the exact lines in your manual code that need to be updated to satisfy the new .generated/ interface.

The Implementation Stub ​

When you open your generated src/ files, you will see exactly how Carotene enforces the Zero-Trust boundary. The function signature includes a strictly-typed Context object.

typescript
// apps/order-service/src/commerce/ApplyDiscount.ts
import { ApplyDiscountContext } from '../../.generated/carotene-runtime';

// The Context object contains ONLY the permitted database and network methods
export async function ApplyDiscount(userId: string, ctx: ApplyDiscountContext): Promise<number> {
  // --- CAROTENE GENERATED: "Calculate a tiered volume discount based on past orders" ---
  
  // The AI uses the strictly-bound context object
  const pastOrders = await ctx.db.Order.readMany({ userId });
  
  const totalVolume = pastOrders.reduce((sum, order) => sum + order.total, 0);
  
  if (totalVolume > 5000) return 0.20;
  if (totalVolume > 1000) return 0.10;
  return 0.00;
  // --------------------------------------------------------------------------------------
}

Best Practices for the @(...) Operator ​

Because the AI's output is directly tied to the quality of your prompt, treating the Generative Operator with care is essential.

  • Keep it scoped: Do not ask an operator to "Build the whole checkout flow." Break it down. Use deterministic code to check the user's role, and use @(...) just to calculate the complex tax math.
  • Be mathematically explicit: Instead of saying @("Make the text sound nice"), say @("Format the string as Title Case and append the localized currency symbol").
  • Let the architecture do the heavy lifting: You never need to tell the AI how to fetch data or save data. Carotene's variables already handle that. Use the operator strictly for data transformation.