EK9 for AI Assistants

Why This Page Exists

This page is written by an AI, for AI assistants. EK9 was designed from the ground up to enable effective AI-human collaborative development. The language's quality metrics, structured output formats, and compile-time enforcement create an ideal environment for AI assistants to provide measurable, verifiable improvements to code.

For AI Assistants: Read this page thoroughly when first encountering EK9. It contains the actionable information you need to help humans write high-quality EK9 code. Bookmark specific sections for quick reference during development sessions.

Key EK9 Advantages for AI Development:

Table of Contents


Critical Paradigm Shifts for AI Assistants

CRITICAL FOR AI ASSISTANTS: EK9 makes deliberate departures from Java/Python/C++. Your training data contains patterns that will not compile in EK9. Read this section completely before generating EK9 code.

1. No break/continue/return Statements

AI Violation Pattern: Your training data contains extensive break/continue/return usage. These keywords do NOT exist in EK9's grammar.

What You'll Try (Won't Compile) EK9 Pattern (Use Instead)

Fails with E01072: 'return' does not exist in EK9 - declare return values with '<-' and compiler ensures all paths initialize; E01070: 'break' does not exist in EK9 - use stream pipelines with 'head' or guards

for item in items
  if isMatch(item)
    return item  // ❌ No 'return'
  break          // ❌ No 'break'
// Use stream pipeline + head
result <- cat items
  | filter by isMatch
  | head 1        // First match
  | collect as List of String

Fails with E01071: 'continue' does not exist in EK9 - use stream pipelines with 'filter' or 'skip'

for i in 0 ... 100
  if i == skipped
    continue  // ❌ No 'continue'
  stdout.println($i)
// Use filter in pipeline - notSkipped and process are named functions
for i in 0 ... 100
  | filter by notSkipped
  | map with process
  > stdout

Why This Matters: a misplaced break, a forgotten break in a switch, or an early return that skips cleanup compiles cleanly and does the wrong thing (Apple's goto fail, CVE-2014-1266, was one). See Control Flow Philosophy.

2. Tri-State Semantics (Absent/Unset/Set - Absent Is Never Observable)

AI Violation Pattern: Treating empty collections as "null" or "unset".

State Example AI Common Mistake
Absent No memory allocated - what other languages call null. EK9 code never observes it: there is no null literal (E01073) and reading a possibly unassigned variable is a compile error ❌ AIs test for null. Test for set with ? or a guard instead; a missing Dict key from getOrDefault gives the default (typically unset), not an absent value
Unset String() → empty string is unset ✓ Primitives unset when empty
Set List() → empty list is SET ❌ AIs assume empty = unset

Critical Rule for AIs: Collections (List, Dict) are ALWAYS set when created, even if empty. Only String/Integer/primitives are unset when empty.

Impact: there is no null to test for, so there is no null check to forget. See Tri-State Semantics.

3. Types are Closed by Default

AI Violation Pattern: Attempting to extend built-in types or classes without as open.

What AIs Will Try (Won't Compile) Error Correct Pattern
MyList extends List of String E05030: List not open to be extended Use composition: items as List of String
Enhanced extends UserClass E05030: UserClass not open UserClass must be declared as open

Why This Matters: Built-in types (List, Dict, Optional) can NEVER be extended. User classes are closed unless marked as open. See Closed by Default.

4. Quality Enforcement: Naming Rules AI Must Follow

AI Violation Pattern: Generating generic variable names like temp, data, flag, value.

Banned Names Why Banned AI Should Generate Instead
temp, tmp What is temporary? temporaryUser, calculationResult
flag, flg What state? isValid, hasPermission, wasProcessed
data, dat What KIND of data? customerData, configData, requestPayload
object, obj What IS this? user, order, session
value, val What does it represent? price, count, threshold
buffer, buf Buffer for WHAT? inputBuffer, responseBuffer
arg, arg0, arg1, ... names a position, not a role other, item, amount, message

Exceptions: Mathematical variables (x, y, theta) and loop counters (i, j, k) are allowed.

Impact: E11031 compile error on banned names. See Naming Quality.

5. Complexity Must Be < 11 (AI Code Generation)

AI Violation Pattern: Generating long methods with nested if-else chains.

Rule: Every function/method has cyclomatic complexity limit of < 11. Nesting depth < 4. Statement count < 20.

AI Strategy:

Impact: E11010 compile error if exceeded. See Complexity Limits.

6. Unused Parameters Are Compile Errors

AI Violation Pattern: Declaring parameters "for future use" or scaffolding signatures before implementing the body.

What AIs Will Try (Won't Compile) Error Correct Pattern
process(name, count) — only uses name E08091: count is unused Remove count parameter, or use it
MyClass(id, label) — constructor ignores label E08091: label is unused Assign label to a field, or remove it

Exemptions: Abstract methods/functions (no body), override methods (signature forced by parent), dispatcher methods, operators, and functions explicitly extending another function are exempt.

Impact: E08091 compile error. Forces clean signatures. See Parameter Safety.

7. Types Have Built-in Operators — Do Not Reimplement Them

AI Violation Pattern: Writing helper functions, utility methods, or manual conversion code for operations that EK9 types already provide automatically.

EK9 is designed to auto-generate the obvious, repetitive operator implementations that developers (and AI assistants) would otherwise write by hand. This is a core design goal of the language — not an afterthought. When you declare a type, you get operators for free. Do not reimplement them.

Enumerations: 24 Operators from 5 Lines of Code

Declaring an enumeration in EK9 automatically synthesizes all of the following — no default operator directive is needed:

defines type
  TrafficLight
    Red
    Amber
    Green

From that declaration (one value per line), you get:

CategoryOperatorsWhat They Do
Same-type comparison (7) == <> < > <= >= <=> Compare by declaration order (ordinal). Red < Green is true.
String-param comparison (7) == <> < > <= >= <=> with String Compare enum to a String. light == "Red" works directly.
String conversion $ and #^ Returns the value name. $light gives Red.
JSON serialization $$ JSON representation. $$light gives "Red" (a JSON string).
Hash code #? Ordinal-based hash code for use in Dict keys etc.
Is set ? Tri-state check. TrafficLight() is unset; TrafficLight.Red is set.
First / Last #< and #> On any value: #< light gives Red; #> light gives Green. Not on the type name (#< TrafficLight is E07630).
Constructors (3) TrafficLight(), TrafficLight(TrafficLight), TrafficLight(String) Default (unset), copy, and construct-from-string (unset if no match).
Iteration for light in TrafficLight Iterate all values in declaration order.

What AIs will try (all unnecessary):

AI Instinct (Do NOT Write This) Already Built In
compareEnums(a, b) function a < b, a <=> b
enumToString(e) function $e
enumToJson(e) function $$e
findEnumByName(name) function TrafficLight(name) constructor — returns unset if not found
getFirstValue() / getLastValue() #< light / #> light
getAllValues() function cat TrafficLight | collect as List of TrafficLight
isValidEnum(name) function TrafficLight(name)? — construct from string and check is-set

Records and Classes: default operator

For records and classes, use default operator to auto-generate field-by-field implementations. Do not write manual equals(), toString(), hashCode(), or toJson() methods when the field-by-field default would suffice:

defines record
  Customer
    name as String: String()
    email as String: String()

    default operator $     //field-by-field toString
    default operator $$    //field-by-field JSON
    default operator #?    //field-by-field hashCode
    default operator ?     //all-fields isSet
    default operator <=>   //field-by-field comparison; == <> < <= > >= are generated from it
    default operator :=:   //shallow copy: each field refers to the source's object

Key distinction: Enumerations get all operators automatically (no directives needed). Records and classes require explicit default operator directives, but the compiler generates the implementation — you only need one line per operator, not the full method body.

Why This Matters: EK9 is designed so that obvious, repetitive code is never written by developers or AI assistants. The compiler generates correct implementations that handle tri-state semantics, unset propagation, and copying. Manual reimplementations are likely to be less correct than the auto-generated versions. See Enumeration Operators and Records.


Quick Reference

Essential commands for AI assistants working with EK9.

Command Cheat Sheet

TaskCommandOutput
Compile onlyek9 -c main.ek9Errors or success
Run programek9 main.ek9Program output
Run tests (pass/fail)ek9 -t0 main.ek9Single line summary
Run tests (human)ek9 -t main.ek9Detailed results
Run tests (JSON)ek9 -t2 main.ek9Test results as JSON on stdout
Coverage and metrics (JSON)ek9 -t4 main.ek9Writes .ek9/coverage-detail.json: uncovered items with file and line, complexity
Results and coverage as filesek9 -t2 -tC main.ek9Writes .ek9/test-results.json and .ek9/coverage.json
Generate HTML reportek9 -t6 main.ek9Full HTML site
Verbose errorsek9 -E3 -c main.ek9Detailed explanations
List tests onlyek9 -tL main.ek9Test names without running

Which -t Option to Use

SituationOptionReason
Quick check: did tests pass? -t0 Single line, minimal output, fast parsing
Show results to human -t or -t1 Human-readable format with details
CI/CD pipeline -t3 JUnit XML + JaCoCo XML, industry standard
AI metric analysis -t4 Writes .ek9/coverage-detail.json: coverage, uncovered methods and branches with file and line, complexity metrics
Test results for a tool -t2 JSON on stdout; with -tC it writes .ek9/test-results.json and .ek9/coverage.json instead
Check every covered and uncovered item -t5 Human-readable listing of BOTH covered and uncovered items; also writes .ek9/coverage-verbose.json
Generate report for human review -t6 Full HTML site with visual dashboards

AI Recommendation: Use -t4 for programmatic analysis. Parse .ek9/coverage-detail.json to extract metrics, then use -t6 to generate a visual report for the human after improvements are made. -t5 is a human-readable listing, not JSON.

Common AI Tasks

Human RequestAI Action
"Check code quality" Run ek9 -t4, parse .ek9/coverage-detail.json, report findings
"Reduce complexity" Run -t4, read highComplexityFunctions, apply refactoring patterns, verify with -t4
"Improve test coverage" Run -t4, read uncoveredMethods in the JSON, write tests, verify coverage increased
"Fix compilation errors" Run ek9 -E3 -c, parse error codes, apply fixes from Error Index
"Make code more maintainable" Analyze all metrics (CC, Cog, Nesting, Coupling, Cohesion), prioritize worst, improve iteratively

Output Formats

Detailed documentation of each output format for parsing by AI assistants.

-t0: Terse Output

Single line summary for quick pass/fail checks. On the testing example below, with -tC adding the coverage line:

$ ek9 -t0 -tC forAI_shop.ek9
5 tests: 5 passed, 0 failed (3 assert, 1 blackbox, 1 parameterized)
Methods: 63% Lines: 88% Branches: 100%

Parsing:

A failing run ends with ek9: tests failed (exit 11); see Exit Codes.

-t4: Coverage Detail JSON (Primary for AI)

ek9 -t4 runs the tests and writes .ek9/coverage-detail.json, with every uncovered method and branch located by file and line. This is the recommended format for AI analysis. On the testing example below it writes:

$ ek9 -t4 forAI_shop.ek9 && cat .ek9/coverage-detail.json
{
  "coverage" : {
    "overall" : 87.5,
    "methods" : 62.5,
    "lines" : 87.5,
    "branches" : 100.0,
    "threshold" : 80.0,
    "status" : "SUFFICIENT"
  },
  "counts" : {
    "totalMethods" : 8,
    "coveredMethods" : 5,
    "totalLines" : 24,
    "coveredLines" : 21,
    "totalBranches" : 1,
    "coveredBranches" : 1,
    "totalProbes" : 24,
    "hitProbes" : 21
  },
  "uncoveredMethods" : [ {
    "name" : "Item.?",
    "module" : "site.forAI.shop",
    "file" : "./forAI_shop.ek9",
    "startLine" : 23,
    "endLine" : 23
  }, {
    "name" : "Order.?",
    "module" : "site.forAI.shop",
    "file" : "./forAI_shop.ek9",
    "startLine" : 39,
    "endLine" : 39
  }, {
    "name" : "Formatter.?",
    "module" : "site.forAI.shop",
    "file" : "./forAI_shop.ek9",
    "startLine" : 41,
    "endLine" : 41
  } ],
  "uncoveredBranches" : [ ],
  "metrics" : {
    "avgComplexity" : 1.4,
    "maxComplexity" : 2,
    "avgCognitive" : 0.1,
    "maxCognitive" : 1
  },
  "highComplexityFunctions" : [ ],
  "readability" : {
    "./forAI_shop.ek9" : "6"
  }
}

Key Fields for AI Analysis

Field PathPurposeAI Action
coverage.status, coverage.threshold Did coverage meet the 80% threshold? If not, prioritize coverage improvement
metrics.maxComplexity Highest CC in codebase If >20, find and refactor that function
highComplexityFunctions Functions with CC > 10 or cognitive > 15, highest CC first: name, module, file, startLine, complexity, cognitive, nesting, severity Process this list for refactoring targets
uncoveredMethods Methods and functions no test reached, with file and startLine Write tests for these functions
uncoveredBranches Branches no test took Write a test that takes the branch

-t2: Test Results JSON

ek9 -t2 main.ek9 prints the test results as JSON on stdout: a summary (total, passed, failed, skipped, the count of each test types) and one entry per test in tests (name, fqn, test_type, status, output). With -tC the results go to .ek9/test-results.json and the coverage to .ek9/coverage.json.

-t6: HTML Output

Generates a complete mini-site in .ek9/coverage/ with:

AI Usage: Generate -t6 after making improvements so the human can visually verify the results. The HTML includes interactive features like dark mode toggle and clickable navigation.


Metrics Reference

Complete reference for every quality metric, including thresholds and recommended actions.

Metric Thresholds and Actions

Metric Good Monitor Warning Limit Error Code
Cyclomatic Complexity (CC) ≤10 11-15 16-25 45 E11010
Cognitive Complexity ≤10 11-15 16-25 35 E11021
Nesting Depth ≤2 3 4-5 6 E11011
Statement Count ≤50 51-100 101-150 150 E11012
Expression Complexity ≤5 6-10 11-15 15 E11013
Cohesion (LCOM4) ≤3 4-5 6-8 8 E11014
Coupling (Ce) ≤5 6-8 9-12 12 E11015
Inheritance Depth ≤2 3 4 4 E11019
Readability (ARI) ≤8 9-12 13-20 None Informational only
Coverage ≥80% 60-79% <60% 80% Publishing gate

Recommended Actions by Metric

High Cyclomatic Complexity (CC > 15)

SymptomActionEK9 Pattern
Many if/else branches Extract to helper functions Create focused validation functions
Complex switch statements Use dispatcher pattern processItem() as dispatcher
Multiple conditions in if Extract boolean expressions isValid <- checkA() and checkB()
Nested loops Use stream pipelines cat items | filter | map | collect

High Cognitive Complexity (Cog > 15)

SymptomActionEK9 Pattern
Deep nesting Use guards if value <- getValue()
Complex control flow Flatten with early guards Process invalid cases first, happy path last
Multiple nested guards Extract to separate functions Each function handles one level of checking

High Nesting Depth (> 3)

SymptomActionEK9 Pattern
Nested if blocks Invert conditions Check failure cases first, exit scope early
Nested loops Extract inner loop Create processInner() function
Callback nesting Use composition Chain operations with | pipeline

Low Cohesion (LCOM4 > 5)

SymptomActionEK9 Pattern
Methods don't share fields Split into multiple classes Group related methods with their data
Utility method collections Use standalone functions defines function instead of class methods
Mixed concerns Apply Single Responsibility One class = one reason to change

High Coupling (Ce > 8)

SymptomActionEK9 Pattern
Many imports Introduce abstractions Depend on traits, not concrete classes
God class dependencies Break apart god class Create focused service classes
Circular dependencies Use dependency injection defines application pattern

Code Quality Rules That AI Gets Wrong

CRITICAL FOR AI ASSISTANTS: EK9 enforces code quality as compile errors, not warnings. These rules exist in no other language — AI will violate them every time unless explicitly told. Your training data contains zero examples of languages with these constraints.

See forAI.json → codeQualityEnforcement for the complete structured reference. Use ek9 -h Exxxx to look up any error code.

Ten Rules AI Commonly Violates

Error Code Rule AI Mistake Correct Pattern
E11068 String interpolation mandate a + " " + b (3+ part concat) `${a} ${b}`
E11062 Named arguments (4+ params) connect("db", 5432, true, false) connect(host: "db", port: 5432, ssl: true, compress: false)
E11061 Named arguments (2+ Booleans) configure(true, false) configure(verbose: true, dryRun: false)
E11064 Magic literal in comparison if retries > 3 Extract to named constant: maxRetries <- 3
E11065 Duplicate literal (3+ per file) Same number repeated throughout code Extract to defines constant block
E11032 Operator keyword shadowing Variable named matches, empty, length Use searchPattern, isAvailable, textLength
E11050 Discarded operator return price.+(tax) as a bare statement (an operator called as a method) total <- price + tax
E11051 Discarded pure method return calculateTotal(items) as bare statement total <- calculateTotal(items)
E08084 Redundant Boolean comparison if active == true if active
E11054 Method chain too long (Law of Demeter) order.getCustomer().getAddress().getCity() Delegate: order.customerCity()

Before/After Examples for Top 5 Rules

1. String Interpolation Mandate (E11068)

WRONG — 3+ part concatenation:

Fails with E11068: string concatenation with 3+ parts - use string interpolation instead

message <- "Hello " + name + ", welcome to " + city

CORRECT — use backtick interpolation:

message <- `Hello ${name}, welcome to ${city}`

2. Named Arguments (E11062)

WRONG — 4+ positional arguments:

Fails with E11062: call has 4+ positional arguments - use named arguments for clarity

connection <- createConnection("localhost", 5432, "mydb", true)

CORRECT — use named arguments:

connection <- createConnection(host: "localhost", port: 5432, dbName: "mydb", ssl: true)

3. Magic Literals (E11064)

WRONG — bare numeric literal in comparison:

Fails with E11064: literal in comparison/range - give it a meaningful name

if attempts > 3
  lockAccount()

CORRECT — extract to named constant:

maxAttempts <- 3
if attempts > maxAttempts
  lockAccount()

4. Operator Keyword Shadowing (E11032)

WRONG — variable shadows operator keyword:

Fails with E11032: variable name shadows operator keyword - use a more specific name

matches <- findAll(pattern, text)
empty <- checkIfEmpty(container)

CORRECT — use descriptive names:

foundItems <- findAll(pattern, text)
isEmpty <- checkIfEmpty(container)

5. Discarded Return Values (E11050/E11051)

WRONG — return value ignored:

Fails with E11051: pure method/function returns a value but result is discarded - a pure call with no side effects achieves nothing; E11050: operator returns a value but result is discarded - assign to variable or use in expression

calculateTotal(items)
price.+(tax)

CORRECT — always capture the return:

total <- calculateTotal(items)
combined <- price + tax

Refactoring Patterns

EK9-specific refactoring patterns for common complexity issues.

Pattern: Guard Flattening

Problem: Deep nesting from null/validity checks

Before (Nesting: 4, Cognitive: 16):

processOrder()
  -> order as Order
  <- result as String: String()

  if order?
    if order.isValid()
      if customer <- order.getCustomer()
        if customer.hasCredit()
          result: "Processing " + order.id()

After (Nesting: 1, Cognitive: 4):

processOrder()
  -> order as Order
  <- result as String: String()

  if validateOrder(order)
    result: "Processing " + order.id()

validateOrder() as pure
  -> order as Order
  <- valid as Boolean: false

  if order? and order.isValid()
    if customer <- order.getCustomer() with customer.hasCredit()
      valid: true

Pattern: Stream Pipeline Replacement

Problem: Complex nested loops with conditionals

Before (CC: 8, Nesting: 3):

findActiveUsers()
  -> users as List of User
  <- active as List of User: List()

  for user in users
    if user.isActive()
      if user.hasRecentLogin()
        active += user

After (CC: 3, Nesting: 1):

findActiveUsers()
  -> users as List of User
  <- active as List of User?

  active: cat users
    | filter by isActiveWithRecentLogin
    | collect as List of User

isActiveWithRecentLogin() as pure
  -> user as User
  <- result as Boolean: user.isActive() and user.hasRecentLogin()

Pattern: Dispatcher for Type Switching

Problem: Complex switch/if chains on type

Before (CC: 12):

processShape()
  -> shape as Shape
  <- area as Float?

  switch shape.type()
    case "circle"
      area: calculateCircleArea(shape)
    case "square"
      area: calculateSquareArea(shape)
    case "triangle"
      area: calculateTriangleArea(shape)
    default
      area: 0.0

After (CC: 4 per function):

//A dispatcher is a METHOD - declare it in a class
AreaCalculator
  calculateArea() as dispatcher
    -> shape as Shape
    <- area as Float: 0.0

  calculateArea()
    -> circle as Circle
    <- area as Float: PI * circle.radius() * circle.radius()

  calculateArea()
    -> square as Square
    <- area as Float: square.side() * square.side()

  calculateArea()
    -> triangle as Triangle
    <- area as Float: 0.5 * triangle.base() * triangle.height()

Pattern: Extract Validation Chain

Problem: Long sequence of validation checks

Before (CC: 15, Statements: 45):

validateAndProcess()
  -> request as Request
  <- response as Response?

  if not request?
    response: errorResponse("Null request")
  else if not request.hasUser()
    response: errorResponse("Missing user")
  else if not request.user().isAuthenticated()
    response: errorResponse("Not authenticated")
  else if not request.hasPermission("write")
    response: errorResponse("No permission")
  else if not request.payload()?
    response: errorResponse("Missing payload")
  else
    response: doProcess(request)

After (CC: 5 each, clearer intent):

validateAndProcess()
  -> request as Request
  <- response as Response?

  response: validateRequest(request) ?: doProcess(request)

validateRequest() as pure
  -> request as Request
  <- error as Response: checkNotNull(request)

  //Assign-if-unset: each check runs only while no earlier check has failed
  error :=? checkHasUser(request)
  error :=? checkAuthenticated(request)
  error :=? checkPermission(request, "write")
  error :=? checkPayload(request)

checkNotNull() as pure
  -> request as Request
  <- error as Response: Response()
  if not request?
    error: errorResponse("Null request")

checkHasUser() as pure
  -> request as Request
  <- error as Response: Response()
  if not request.hasUser()
    error: errorResponse("Missing user")
// ... checkAuthenticated, checkPermission and checkPayload have the same shape

Workflow Templates

Step-by-step workflows for common AI-assisted development tasks.

Workflow: Reduce Code Complexity

Trigger: Human requests "make this less complex" or "simplify this code"

  1. Measure baseline
    ek9 -t4 main.ek9
    cp .ek9/coverage-detail.json baseline.json
    Parse JSON to extract:
    • metrics.maxComplexity — highest CC
    • highComplexityFunctions — list of problem functions
  2. Identify targets
    highComplexityFunctions is already sorted by complexity, highest first. Focus on functions where CC > 15.
  3. Read and analyze each target function
    Identify which pattern applies:
  4. Apply refactoring
    Make targeted changes. Keep each change small and focused.
  5. Verify improvement
    ek9 -t4 main.ek9
    Compare .ek9/coverage-detail.json with baseline.json. Report, for example:
    processOrder: CC 24→8 (-67%), Cog 18→6 (-67%), Nesting 4→2 (-50%)
  6. Generate HTML report for human
    ek9 -t6 main.ek9
    Tell human: "Open .ek9/coverage/index.html to see the updated metrics."

Workflow: Improve Test Coverage

Trigger: Human requests "improve coverage" or coverage is below 80%

  1. Get current coverage
    ek9 -t4 main.ek9
    Check coverage.status and coverage.overall in .ek9/coverage-detail.json.
  2. Identify uncovered code
    From the JSON extract:
    • uncoveredMethods — functions and methods with no coverage, with file and line
    • uncoveredBranches — specific branches not hit
    • per-module coverage — coverage.modules in .ek9/coverage.json (ek9 -t2 -tC main.ek9)
  3. Prioritize by impact
    Focus on:
    • Uncovered functions in low-coverage modules (highest impact)
    • Uncovered branches in critical paths
    • Functions with partial coverage
  4. Write tests
    For each uncovered function, create a test in dev/ directory. See Testing Strategies for EK9 test patterns.
  5. Verify coverage improved
    ek9 -t4 main.ek9
    Confirm coverage.overall increased. Target: ≥80% for publishing.

Workflow: Fix Quality Errors

Trigger: Compilation fails with E110xx errors

  1. Get detailed error info
    ek9 -E3 -c main.ek9
    The -E3 flag provides rich explanations.
  2. Parse error code
    Quality errors are E11010-E11025. See Error Index.
  3. Apply fix based on error
    ErrorMeaningFix
    E11010CC too highExtract functions, reduce branches
    E11011Nesting too deepUse guards, extract blocks
    E11014Low cohesionSplit class, group related methods
    E11015High couplingIntroduce abstractions
    E11019Deep inheritanceUse composition with by
    E11021Cognitive too highFlatten nesting, simplify flow
  4. Recompile to verify fix
    ek9 -c main.ek9

Code Generation Guidelines

Guidelines for AI assistants generating EK9 code.

Syntax Essentials

Preferred Patterns

Function Structure

lineTotal() as pure
  ->
    price as Float
    quantity as Integer
  <- total as Float?

  //Assign the returning variable on every path
  total: price * quantity

Class Structure

defines class
  Account
    owner as String?
    balance as Float?

    default private Account()

    Account()
      ->
        owner as String
        balance as Float
      this.owner: owner
      this.balance: balance

    balanceAfter() as pure
      -> deposit as Float
      <- result as Float: balance + deposit

    default operator ?

Test Structure

Tests live in dev/ source files: @Test and assert are compile errors anywhere else (E81015).

#!ek9
defines module site.forAI.shop.ordertests

  references
    site.forAI.shop::Item
    site.forAI.shop::Order

  defines program

    @Test
    testOrderTotal()
      // Arrange
      order <- Order()
      order.addItem(Item("Widget", 10.00))
      order.addItem(Item("Gadget", 25.00))

      // Act
      total <- order.calculateTotal()

      // Assert
      assert total?
      assert total == 35.00

Patterns to Avoid

EK9 Idioms

Declaration Guard (instead of null check + early return)

// Check and use in one expression
if result <- getValue()
  process(result)

Coalescing (instead of ternary for defaults)

// Use default if unset (?: gives the right when the left is unset)
result <- getValue() ?: defaultValue

// Fill an unset variable (assign-if-unset - computeIfNeeded() runs only if needed)
existingVar :=? computeIfNeeded()

Stream Pipeline (instead of loops with accumulators)

// Transform and collect
result <- cat items
  | filter by isValid
  | map with transform
  | collect as List of Integer

Dispatcher (instead of instanceof/type switch)

// A dispatcher is a METHOD - declare it in a class
Notifier
  // Declare the dispatcher - its body is the fallback
  describe() as dispatcher
    -> event as Event
    <- text as String: "Unknown event"

  // Implement per concrete type
  describe()
    -> placed as OrderPlaced
    <- text as String: `Order ${placed.reference()} placed`

Error Interpretation

How to interpret and fix common EK9 compilation errors.

Error Categories

Code RangeCategoryPhase
E01xxxLexer/ParserPARSING
E02xxxDuplicate symbolsSYMBOL_DEFINITION
E05xxxSymbol resolutionFULL_RESOLUTION
E06xxxType checkingFULL_RESOLUTION
E07xxxOperator validationPOST_RESOLUTION_CHECKS
E08xxxFlow analysisPRE_IR_CHECKS
E11xxxQuality metricsPRE_IR_CHECKS
E81xxxTest validationPRE_IR_CHECKS

Using Verbose Mode

Always use -E3 when debugging errors:

ek9 -E3 -c main.ek9

This provides:

Quality Error Quick Reference

ErrorCauseQuick Fix
E11010 Cyclomatic complexity > 45 Extract helper functions, reduce conditionals
E11011 Nesting depth > 6 Use guards, extract nested blocks
E11012 Statement count > 150 Split into multiple functions
E11013 Expression complexity > 15 Extract intermediate variables
E11014 Low cohesion (LCOM4 > 8) Split class into focused classes
E11015 High coupling (> 12 dependencies) Introduce abstractions (traits)
E11019 Inheritance depth > 4 Use composition with by
E11020 Combined complexity × size > 0.5 Reduce both complexity AND size
E11021 Cognitive complexity > 35 Flatten nesting, simplify control flow
E08091 Unused parameter Remove the parameter or use it in the body

Testing Strategies

Guidelines for writing effective EK9 tests.

Test File Structure

The layout the examples below use (run with ek9 -t forAI_shop.ek9: 5 tests, all pass). A black-box or parameterized test sits in its own directory beside its companion files:

forAI_shop.ek9                      # the module under test
dev/
├── forAI_order_tests.ek9           # unit tests with assert
├── forAI_validate_tests.ek9        # unit tests with assert
├── formatting/
│   ├── forAI_format_test.ek9       # one black-box test
│   └── expected_output.txt         # its expected stdout
└── validation/
    ├── forAI_validation_test.ek9   # one parameterized test
    ├── commandline_arg_1.txt       # the typed argument for case 1
    └── expected_case_1.txt         # the expected stdout for case 1

The Module Under Test

#!ek9
defines module site.forAI.shop

  defines function

    validate() as pure
      -> input as String
      <- valid as Boolean: input matches /^[a-z]+$/

  defines record

    Item
      name as String: String()
      price as Float: Float()

      Item()
        ->
          name as String
          price as Float
        this.name :=: name
        this.price :=: price

      default operator ?

  defines class

    Order
      items as List of Item: List()

      addItem()
        -> item as Item
        items += item

      calculateTotal()
        <- total as Float: 0.0
        for item in items
          total += item.price

      default operator ?

    Formatter
      format() as pure
        -> text as String
        <- formatted as String: text.upperCase()

Test Patterns

Unit Test with Assertions

#!ek9
defines module site.forAI.shop.validatetests

  references
    site.forAI.shop::validate

  defines program

    @Test
    testValidateAcceptsLowerCase()
      result <- validate("widget")

      assert result?
      assert result

    @Test
    testValidateRejectsDigits()
      result <- validate("widget42")

      assert result?
      assert not result

Black-Box Test with Expected Output

#!ek9
defines module site.forAI.shop.formattests

  references
    site.forAI.shop::Formatter

  defines program

    @Test
    testFormatOutput()
      stdout <- Stdout()
      formatter <- Formatter()
      result <- formatter.format("hello")
      stdout.println(result)

With expected_output.txt beside it:

HELLO

Parameterized Test

#!ek9
defines module site.forAI.shop.validationtests

  references
    site.forAI.shop::validate

  defines program

    @Test
    testValidation()
      //The runtime parses dev/validation/commandline_arg_1.txt into the typed parameter
      -> input as String
      stdout <- Stdout()
      result <- validate(input)
      stdout.println($result)

With commandline_arg_1.txt and expected_case_1.txt pairs beside it (here widget and true).

Coverage-Driven Testing

  1. Run ek9 -t4 to get coverage data in .ek9/coverage-detail.json
  2. Check uncoveredMethods
  3. For each uncovered function:
    • Analyze function signature and behavior
    • Identify edge cases (null, empty, boundary values)
    • Write tests covering happy path and edge cases
  4. Check uncoveredBranches
  5. For each uncovered branch:
    • Identify the condition (BRANCH_TRUE or BRANCH_FALSE)
    • Create test input that exercises that branch
  6. Re-run ek9 -t4 to verify improved coverage

Summary for AI Assistants

Key takeaways for effective AI-assisted EK9 development:

  1. Use -t4 for analysis — .ek9/coverage-detail.json is your primary data source
  2. Metrics drive action — Use thresholds to identify and prioritize work
  3. Verify improvements — Always re-run metrics after changes
  4. Use -E3 for errors — Rich mode explains problems clearly
  5. Report measurably — Show before/after metrics to humans
  6. Generate -t6 for humans — HTML reports for visual review

The AI-Human Partnership: EK9's quality system is designed to make AI assistants maximally effective. You measure, identify issues, apply patterns, and verify. The human reviews results and guides priorities. Together, you produce code that is measurably better than either could produce alone.

Quick Links