Skip to content

Latest commit

 

History

History
2719 lines (2086 loc) · 105 KB

File metadata and controls

2719 lines (2086 loc) · 105 KB

NTNT Language Implementation Roadmap

This document outlines the implementation plan for NTNT, a programming language designed for AI-driven development. The roadmap prioritizes getting to a working web application quickly while focusing on NTNT's unique differentiators: contracts, AI integration, and intent encoding.

Completed Phases: Phases 1-5 (Core Language, Type System, Modules, Traits, Web) are 100% complete and documented in ROADMAP_COMPLETE.md.


Design Principles

  1. Self-Contained: NTNT has no runtime dependencies on other languages. The interpreter/compiler is written in Rust, but NTNT programs are pure Intent.

  2. AI-First: Features that enable AI development (contracts, intent annotations, structured edits) are core, not afterthoughts.

  3. Production-Ready Web Apps: The goal is building real web applications with safety guarantees.

  4. Lean Standard Library: Include essentials, leave specialized libraries to the community.


Current Status

Completed ✅

  • Lexer with full token support
  • Recursive descent parser
  • Complete AST definitions
  • Tree-walking interpreter
  • Basic type system (Int, Float, String, Bool, Array, Object, Function, Unit) — enforced with gradual typing (Phase 7.1)
  • Full contract system (requires, ensures, old(), result)
  • Struct invariants with automatic checking
  • Built-in math functions (abs, min, max, sqrt, pow, round with optional decimals, etc.)
  • CLI with REPL, run, parse, lex, check commands
  • VS Code extension with syntax highlighting
  • Comprehensive test suite
  • File extension: .tnt
  • Algebraic Data Types with enums
  • Option and Result<T, E> built-ins
  • Pattern matching with match expressions
  • Generic functions and types with trait bounds
  • Type aliases
  • Union types
  • Effect annotations foundation
  • Module system with imports/exports
  • Standard library: std/string, std/math, std/collections, std/env, std/fs, std/path, std/json, std/time, std/crypto, std/url, std/http
  • Traits with default implementations
  • For-in loops and ranges
  • Defer statement
  • Map literals with field access (dot notation)
  • String interpolation and raw strings
  • Template strings ("""...""" with {{}} interpolation, for loops, conditionals)
  • Template elif chains ({{#elif cond}})
  • Template loop metadata (@index, @first, @last, @length, @even, @odd)
  • Template empty blocks ({{#empty}} fallback for empty loops)
  • Template comments ({{! comment }})
  • Template filters ({{expr | filter1 | filter2(arg)}})
  • Map iteration functions (keys(), values(), entries(), has_key())
  • Nested map inference (nested maps don't require map keyword inside map {})
  • Truthy/falsy values (0 is truthy, empty strings/arrays/maps are falsy)
  • CSV parsing (std/csv)
  • ntnt test command for HTTP endpoint testing
  • ntnt docs command for stdlib documentation search
  • ntnt docs --generate for auto-generating reference docs and AI agent guide sync
  • ntnt completions <shell> for shell completions (bash, zsh, fish)
  • Auto-generated documentation (STDLIB_REFERENCE.md, SYNTAX_REFERENCE.md, IAL_REFERENCE.md)
  • External templates with template() function (Mustache-style syntax)
  • Async HTTP server (Axum + Tokio) with bridge to sync interpreter
  • Hot-reload for file-based routes (routes/*.tnt) in async server
  • Hot-reload tracks imported files (lib modules, local imports)
  • Hot-reload for lib modules, middleware, and route directory changes
  • Template cache invalidation (mtime-based hot-reload for compiled templates)
  • NTNT_ENV=production disables hot-reload for better performance
  • Runtime documentation (RUNTIME_REFERENCE.md)
  • Documentation system: // @ntnt doc comments in Rust source, build.rs validation, 267 functions documented
  • 100% documentation coverage enforced at compile time (undocumented function = build error)

Phase 6: Intent-Driven Development (IDD)

Status: Complete ✅

Goal: Make NTNT the first language with native Intent-Driven Development—where human intent becomes executable specification.

See docs/IAL_REFERENCE.md for the Intent Assertion Language reference.

What is IDD?

Intent-Driven Development creates a contract layer between human requirements and AI-generated code. Instead of describing what you want and hoping the AI understands, you write a .intent file that is both:

  • Human-readable requirements - Plain English descriptions anyone can understand
  • Machine-executable tests - Assertions the system verifies automatically
# snowgauge.intent

## Glossary

| Term | Means |
|------|-------|
| a visitor goes to {path} | GET {path} |
| the home page | / |
| the page loads | status 200 |
| they see {text} | body contains {text} |

---

Feature: Site Selection
  id: feature.site_selection

  Scenario: Visitor sees available sites
    When a visitor goes to the home page
    → the page loads
    → they see "Bear Lake"
    → they see "Wild Basin"

6.1 POC Validation (Go/No-Go Checkpoint) ✅

Goal: Prove the concept works before full investment.

  • Intent file parser (YAML-based .intent files)
  • HTTP test runner (start server, make requests, check assertions)
  • Basic assertions (status, body contains, body matches)
  • ntnt intent check command
  • Apply to snowgauge.tnt example
# Target behavior
$ ntnt intent check snowgauge.tnt

Feature: Site Selection
  ✓ GET / returns status 200
  ✓ body contains "Bear Lake"
  ✓ body contains "Wild Basin"

2/2 features passing (5/5 assertions)

Success criteria: Use IDD to develop a new feature in snowgauge. Does it feel useful?

6.2 Core Intent Commands ✅

  • ntnt intent check <file.tnt> - Verify code matches intent
  • ntnt intent init <file.intent> - Generate code scaffolding from intent
  • ntnt intent coverage <file.tnt> - Show which features have implementations
  • ntnt intent diff <file.tnt> - Gap analysis between intent and code

6.2.1 Intent Assertion Language (IAL) Engine ✅

IAL is a term rewriting system where natural language assertions are recursively resolved to executable primitives.

Architecture:

"they see success response"
    ↓ vocabulary lookup
"component.success_response"
    ↓ component expansion
["status 2xx", "body contains 'ok'"]
    ↓ standard term resolution
[Check(InRange, "response.status", 200-299), Check(Contains, "response.body", "ok")]
    ↓ execution
[✓, ✓]

Core Implementation (src/ial/):

  • vocabulary.rs - Pattern matching and term storage
  • resolve.rs - Recursive term → primitive resolution (~30 lines core logic)
  • execute.rs - Primitive execution against Context
  • primitives.rs - Primitive enum (Http, Check) + CheckOp enum
  • standard.rs - Standard vocabulary definitions

Primitives (fixed set - new assertions are vocabulary, not code):

  • Actions: Http, Cli, Sql, ReadFile
  • Checks: Equals, NotEquals, Contains, NotContains, Matches, Exists, NotExists, LessThan, GreaterThan, InRange

High-level API:

pub fn run_assertions(assertions: &[String], vocab: &Vocabulary, port: u16) -> IalResult<Vec<ExecuteResult>>
pub fn run_scenario(method: &str, path: &str, body: Option<&str>, assertions: &[String], vocab: &Vocabulary, port: u16) -> IalResult<(bool, Vec<ExecuteResult>)>

6.3 Code Annotations ✅

  • // @implements: feature.X comment parsing
  • // @supports: constraint.Y for supporting code
  • // @utility, // @internal, // @infrastructure markers
  • Link annotations to intent items
  • Validate IDs exist in intent file
// @implements: feature.site_selection
fn home_handler(req) {
    // This function implements the site selection feature
}

6.4 Expanded Assertions (IAL Standard Vocabulary)

HTTP Assertions (Implemented via IAL)

  • Status code: status: 200, status 2xx, status 4xx
  • Body contains: body contains "text", they see "text"
  • Body negation: body not contains "error", they don't see "text"
  • Regex matching: body matches r"pattern"
  • Header assertions: header "Content-Type" contains "text/html"
  • JSON path: body json "$.users[0].name" equals "Alice"
  • Redirects: redirects to /path
  • Content-type: returns JSON, returns HTML
  • Response timing: responds in under {time}

CLI Assertions (IAL Vocabulary)

  • Exit codes: exits successfully, exits with code {n}
  • Output: output shows {text}, output matches {pattern}
  • Errors: error shows {text}, no error output

File Assertions (IAL Vocabulary)

  • Existence: file {path} exists, file {path} is created
  • Content: file {path} contains {text}
  • Directories: directory {path} exists

Database Assertions (IAL Vocabulary - Definitions ready)

  • Row operations: record is created, record is updated, record is deleted
  • Queries: row exists where {condition}, row count is {n}
  • Database verification: verify_db: with SQL queries (execution pending)
  • State before/after comparison

6.5 Intent Studio

Goal: A collaborative workspace where humans and agents develop intent together.

The .intent format is optimized for machine parsing and testing, but humans deserve a better experience when creating and refining intent. Intent Studio provides a beautiful HTML view that makes intent development feel like a creative collaboration, not a chore.

Phase 1: Basic Studio (MVP) ✅ COMPLETE

  • ntnt intent studio <file.intent> - Start studio server
  • Rich HTML rendering with feature cards and visual hierarchy
  • Auto-refresh via polling (page refreshes every 2 seconds)
  • File watcher detects changes
  • Auto-open browser on launch (with --no-open flag to disable)
  • Beautiful dark theme with stats dashboard
  • Feature icons based on feature name/type
  • Error page with auto-retry when intent file has parse errors
  • Live test execution - tests run against a running app
  • Pass/fail indicators - visual ✓/✗ on every assertion
  • Run Tests button - re-execute tests on demand
  • Default ports - Studio on 3001, app on 8081
  • Native hot-reload - edit .tnt file, changes apply on next request (no restart!)
  • Auto-start app - Studio automatically starts the matching .tnt file

Phase 2: Intent Studio V2 (Mostly Complete)

Design: design-docs/studio-mockup-v2.html

  • Health bar visualization (pass/fail/warning/skip percentages)
  • Filter chips (All, Failing, Warnings, Skipped, Unlinked, Unit Tests)
  • Search across features, scenarios, and assertions
  • Expanded feature cards with scenarios and assertions
  • Unit test section with test data, corpus testing, property checks
  • Invariant bundles display
  • Warning states for not-implemented features
  • Skip states with precondition failure reasons
  • WebSocket-based instant live reload (currently polling at 10s interval)

Phase 3: IAL Explorer ✅ COMPLETE

Design: design-docs/ial_explorer.html

  • Intent file viewer with syntax highlighting
  • Interactive glossary term highlighting
  • Hover popover showing full resolution chain
  • Resolution depth visualization (Level 0 → 1 → 2 → primitive)
  • Sidebar glossary reference panel
  • Link between Studio and Explorer views

Phase 4: Enhanced Studio (Later)

  • Implementation status indicators (linked to @implements annotations)
  • Diff highlighting when intent changes
# Start intent studio (default ports: studio on 3001, app on 8081)
$ ntnt intent studio server.intent

🎨 Intent Studio
  File: server.intent
  URL:  http://127.0.0.1:3001
  App:  http://127.0.0.1:8081
  ✅ Live test execution enabled!

# Custom ports if needed
$ ntnt intent studio server.intent --port 4000 --app-port 9000

Workflow: Human and AI collaborate on intent with live test feedback:

  1. Create or open an existing .intent file (ntnt intent init or edit directly)
  2. Start your app on port 8081 (or use --app-port for custom port)
  3. Start the studio: ntnt intent studio server.intent
  4. Human opens studio in browser (side-by-side with editor)
  5. Tests run automatically—see which assertions pass ✓ or fail ✗
  6. Human and AI collaborate—discussing, adding, removing, refining features
  7. AI updates .intent file, studio refreshes and re-runs tests
  8. Watch tests fail for new features, implement until they pass
  9. All tests green = intent is verified!

6.6 Test Execution for All Program Types

  • HTTP servers (primary focus)
  • CLI applications (run:, exit_code:, stdout:)
  • Library functions (eval:, result:)
  • Database operations (verify_db:, transactions)

6.7 Developer Experience

  • ntnt intent watch - Continuous verification during development
  • Colored output (green/red for pass/fail)
  • Failure details with expected vs actual
  • Intent file line numbers in error messages
  • Parallel test execution

6.8 Intent History & Changelog

  • ntnt intent history <feature> - View feature evolution
  • ntnt intent changelog v1 v2 - Generate release notes from intent diffs
  • ntnt intent archaeology "<term>" - Search intent history
  • Feature history timeline in Intent Studio
  • Removed feature archive - browse features that were removed
  • Shareable URLs for team review

6.9 Advanced Assertions & Behavioral Properties

Behavioral Properties

  • Idempotency: property: idempotent — verifies f(f(x)) == f(x)
  • Determinism: property: deterministic — verifies f(x) == f(x) across calls
  • Round-trip: property: round_trips — verifies g(f(x)) == x
  • Purity: pure: true (same input = same output, no side effects — requires side-effect tracking)
  • Thread safety: parallel: concurrent request testing
  • Sequencing: sequence: state machine transitions
  • No unintended mutations: no_db_writes: true

Side Effect Verification

  • Email sent: email_sent_to:
  • Event published: event_published:
  • Log verification: log_contains:
  • External call verification

Contract Integration

  • contracts: section linking intent to code contracts
  • Precondition violation testing
  • Postcondition verification
  • Invariant checking across test sequences

Resource Constraints

  • Query count: db_query_count <= N
  • Memory bounds: memory_delta < X
  • Connection limits

6.10 Browser & Visual Testing (Future)

  • DOM assertions (element exists, visible, attributes)
  • Browser automation (click, fill, navigate)
  • Visual regression (screenshot comparison)
  • LLM visual verification for subjective qualities

Phase 6 Deliverables:

  • .intent file format and parser
  • ntnt intent check|init|coverage|diff|watch|studio commands
  • @implements annotation system
  • Test execution engine for HTTP servers
  • Intent history and changelog generation
  • Intent Studio with WebSocket hot-reload for collaborative intent development
  • Applied to snowgauge.tnt and other examples

6.11 Modular Intent Files (Future)

  • @include directive for importing features from other .intent files
  • Scoped feature IDs to prevent collisions across modules
  • Module-level constraints that apply to all included features
  • Selective imports: @include "auth.intent" only [feature.login, feature.logout]
# Main application intent file
# Includes modules for large applications

@include "modules/auth.intent"
@include "modules/products.intent"
@include "modules/checkout.intent" only [feature.cart, feature.payment]

## Overview
Full e-commerce platform composed from reusable intent modules.

---

Constraint: Global Rate Limiting
  description: "All API endpoints are rate limited"
  applies_to: [auth.*, products.*, checkout.*]

Note: For most applications, a single .intent file with ## Module: section headers is recommended. The @include directive is for very large projects or organizations that need to share intent modules across multiple applications.


Phase 7: Language Ergonomics & Documentation ← UP NEXT

Status: In Progress

Goal: Address the biggest daily friction points for AI agents and human developers writing NTNT code, and establish the documentation systems that will serve the language long-term. The type system comes first because it's a foundation that makes every subsequent feature stronger.

These features were identified through real-world usage as the highest-impact improvements to the language. The type system is sequenced first because error propagation (?) needs to know return types, closures benefit from type inference, and SQLite needs type mapping. Together, these features transform a typical web handler from ~22 lines of match pyramids to ~6 lines of linear, readable code.

7.1 Type System Enforcement

Priority: Foundation — everything else in this phase builds on real types.

Currently, type annotations are parsed but not enforced. This is the worst of both worlds: syntax noise without safety guarantees. NTNT needs to commit to real types.

Design: Enforced types with aggressive inference.

// Function signatures require types (the contract boundary)
fn add(a: Int, b: Int) -> Int {
    return a + b
}

// Local variables are inferred — no annotation needed
let x = 5              // inferred: Int
let name = "Alice"     // inferred: String
let nums = [1, 2, 3]  // inferred: [Int]

// Explicit annotation optional, useful for documentation
let threshold: Float = 3.14

// Type errors caught at lint/validate time, not runtime
fn greet(name: String) -> String {
    return "Hello, " + name
}
greet(42)  // ✗ Type error: expected String, got Int

Two layers of safety — types + contracts:

// Types catch STRUCTURAL errors (wrong kind of data)
// Contracts catch SEMANTIC errors (right kind, wrong value)
fn divide(a: Int, b: Int) -> Int
    requires b != 0                    // contract: semantic check
    ensures result * b == a            // contract: behavioral check
{
    return a / b    // types guarantee a and b are Int
}

// The contract checker can now verify:
// "ensures result > 0" — result is Int, comparison to Int is valid ✓
// "ensures len(result)" — result is Int, len() expects String/Array ✗

Implementation plan:

  • Type inference engine for local variables (let x = 5Int)

  • Type checking at function call boundaries (argument types match parameter types)

  • Return type verification (function body returns declared type)

  • Type inference for expressions (arithmetic, string concat, comparisons)

  • Generic type resolution (Option<Int>, Result<String, Error>)

  • Union type checking (String | Int accepts either)

  • Array element type inference ([1, 2, 3][Int])

  • Map value type inference (map { "a": 1 }Map<String, Int>)

  • Type errors in ntnt lint and ntnt validate output (not just runtime)

  • Helpful error messages: "expected String, got Int" with line/column

  • Contract expression type-checking (ensures len(result) > 0 — verify result is a type len() accepts)

  • Gradual typing: untyped parameters default to Any (backward compatible)

  • Remove the Effect enum from src/types.rs (7 variants, never checked at runtime)

  • Remove with keyword effect parsing from src/parser.rs (lines 244-258)

  • Remove pure keyword parsing from function signatures

  • Remove TypeExpr::WithEffect variant from AST

  • Remove the two effect-related tests from src/interpreter.rs (test_effects_annotation, test_pure_function)

  • Keep TokenKind::With and TokenKind::Pure as reserved keywords (forward compatibility)

  • Builtin and stdlib function signature registry (~180 functions across 16 modules)

  • Two-pass type checking (collect declarations, then check — enables forward references)

  • Struct field type checking

  • Option/Result pattern binding in match arms

  • ntnt lint --strict warns about untyped function parameters and missing return types

  • Strict lint mode activated by CLI flag, NTNT_STRICT=1 env var, or ntnt.toml config

  • Project config file (ntnt.toml) with [lint] strict = true support

  • Array<T> and Map<K, V> generic annotation resolution (type compatibility with inferred types)

  • Built-in Request and Response struct types for HTTP handlers (field access returns real types)

  • Generic-aware unwrap(): unwrap(Optional<T>)T, unwrap(Result<T, E>)T

  • filter() preserves array element type: filter(Array<T>, pred)Array<T>

  • Fixed missing stdlib signatures: Cache/cache_fetch (std/http), parse_csv (std/csv), parse_datetime (std/time)

  • html()/json()/text()/redirect()/status() return Response instead of Any

  • Collection functions preserve element types: sort(), reverse(), slice(), concat() return Array<T>

  • flatten() unwraps one nesting level: Array<Array<T>>Array<T>

  • push() preserves array type: push(Array<T>, T)Array<T>

  • first(), last(), pop() return element type T from Array<T>

  • Math builtins preserve numeric types: abs(Int)Int, abs(Float)Float

  • min()/max() with float promotion: min(Int, Float)Float

  • clamp() preserves numeric type of first argument

  • keys(Map<K,V>)Array<K>, values(Map<K,V>)Array<V> (type-aware map accessors)

  • get_key(Map<K,V>, key)V (returns map value type instead of Any)

  • entries(Map<K,V>)Array<Array<Any>> (structured return)

  • transform()/.map() callback return type inference: transform(Array<T>, fn(T)->R)Array<R>

  • parse_json() returns Result<Map<String, Any>, String> instead of Any

  • fetch() returns Result<Response, String> instead of Any

  • Match arm struct pattern narrowing: fields bind with struct field types

  • Cross-file import type propagation: import { foo } from "./lib/utils" resolves function signatures

  • Union type soundness: union VALUES require ALL members compatible with target, union TARGETS require ANY member match

  • Union type flattening and deduplication in union_type() computation

  • Block divergence analysis (block_diverges) for otherwise/lambda validation

  • otherwise block divergence warning (must end with return/break/continue)

  • Lambda return type inference via collected_returns (early returns included in union)

  • Flow-sensitive type narrowing after guards (if x == None { return } narrows x to inner type)

  • Narrowing patterns: x == None, x != None, is_some(x), is_none(x), is_ok(x), is_err(x), !cond

  • Guard clause narrowing: diverging then-branch applies false-facts to subsequent code

  • If-expression narrowing: branches get narrowed types from condition

  • Static match exhaustiveness checking for Option, Result, and user-defined enums

  • Map field access returns value type: map.field on Map<K, V> returns V

  • Variadic functions check declared parameter types (not just skip all checking)

  • Variadic functions check minimum argument count

  • Cross-file Pass 2 inference: unannotated exported functions get inferred return types

  • Circular import safety: shared module cache prevents infinite recursion during cross-file analysis

  • Strict mode: string interpolation warning for complex types (Array, Map, Function)

  • Strict mode: Float→Int precision loss warning

  • Cross-file struct/enum propagation (extend import type resolution to include struct and enum definitions)

  • Closure parameter type inference from call context (7.3 closures done; inference from Array<T> context remaining)

  • Heterogeneous map return types: functions returning map { "name": str, "values": arr } need user-defined structs for real types

Backward compatibility: Existing NTNT code continues to work. Untyped function parameters are treated as Any. Adding types is opt-in but encouraged. ntnt lint --strict warns about untyped public function signatures.

7.2 Error Propagation (? Operator)

Priority: Highest friction point — depends on 7.1 to verify return types are Result/Option.

Currently, every fallible operation requires an explicit match:

// Current: verbose error handling
fn handle_request(req) {
    match parse_json(req) {
        Ok(data) => {
            match validate(data) {
                Ok(valid) => {
                    match save_to_db(valid) {
                        Ok(result) => return json(result),
                        Err(e) => return status(500, "DB error: " + str(e))
                    }
                },
                Err(e) => return status(400, "Invalid: " + str(e))
            }
        },
        Err(e) => return status(400, "Parse error: " + str(e))
    }
}

With ? operator:

// Target: concise error propagation
fn handle_request(req: Request) -> Result<Response, Error> {
    let data = parse_json(req)?
    let valid = validate(data)?
    let result = save_to_db(valid)?
    return Ok(json(result))
}

Implementation plan:

  • ? operator on Result<T, E> values — unwrap Ok or early-return Err
  • ? operator on Option<T> values — unwrap Some or early-return None
  • Type checker unwraps Result<T, E>T and Option<T>T for ? expressions
  • Gradual typing: non-Result/Option values pass through unchanged
  • Error type coercion (auto-convert between compatible error types)
  • Type system verifies ? is used in functions that return Result or Option
  • Clear error message when ? is used in a function with wrong return type

7.2.1 Inline Error Handling (otherwise)

Design Doc: plans/syntax-analysis-and-innovation.md § 3.3

Priority: High — can ship independently of ? (no type system dependencies). Immediate ergonomic win for both web handlers and CLI scripts.

The ? operator propagates errors to the caller. otherwise handles errors at the call site — each failure gets its own recovery logic inline. They are complementary: ? for library internals, otherwise for handlers and scripts.

// Web handler — each error gets a specific HTTP response
fn create_user(req) {
    let data = parse_json(req)
        otherwise return status(400, "Invalid JSON: {err}")

    let name = data["name"]
        otherwise return status(400, "Missing field: name")

    let saved = execute(db, "INSERT INTO users (name) VALUES ($1)", [name])
        otherwise return status(500, "Database error: {err}")

    return json(map { "created": true, "name": name }, 201)
}

// CLI script — handle errors, skip bad data, keep going
let content = read_file("data/input.csv")
    otherwise { print("Cannot read input: {err}"); return }

for line in lines {
    let value = float(fields[2])
        otherwise { print("Bad number on line: {line}"); continue }
    // ...
}

Semantics:

  • Works on Result<T, E>: unwraps Ok(v) into the binding, runs the otherwise block on Err(e)
  • Works on Option<T>: unwraps Some(v) into the binding, runs the otherwise block on None
  • err is automatically bound to the error value (or None for Option) inside the otherwise block
  • The otherwise block must diverge: return, continue, break, or panic
  • Single-expression form: otherwise return status(400, "msg") (no braces needed)
  • Block form: otherwise { print("error"); return } (braces for multiple statements)

Implementation plan:

  • Otherwise keyword in lexer
  • Parse otherwise clause on let bindings: let x = expr otherwise { diverging }
  • err auto-binding for the error/None value
  • Single-expression shorthand (no braces for simple cases)
  • Works with Result<T, E> — unwrap Ok or run otherwise on Err
  • Works with Option<T> — unwrap Some or run otherwise on None
  • Verify otherwise block diverges (return/break/continue)
  • Type checker unwraps Result<T, E>T and Option<T>T for let with otherwise
  • Lint integration: warn if otherwise block doesn't diverge

7.3 Anonymous Functions / Closures ✅

Status: Complete (v0.4.0)

Anonymous functions use fn(params) { body } syntax in expression position:

// Inline closures with filter and transform
let doubled = transform(nums, fn(x) { x * 2 })
let evens = filter(nums, fn(x) { x % 2 == 0 })

// Typed params and return type
let multiply = fn(a: Int, b: Int) -> Int { a * b }

// Multi-statement body
let process = fn(item) {
    let cleaned = trim(item)
    return to_lower(cleaned)
}

// Closures capture surrounding variables
let threshold = 10
let above = filter(nums, fn(x) { x > threshold })

// Nested closures (currying)
let make_adder = fn(x) { fn(y) { x + y } }

// Immediate invocation
let result = fn(x) { x + 1 }(5)

Implementation:

  • Anonymous function expression: fn(params) { body }
  • Block body with implicit return (last expression is return value)
  • Multi-statement closures with explicit return
  • Optional type annotations: fn(x: Int) -> Int { ... }
  • Variable capture from enclosing scope (closure semantics)
  • Closures as function arguments (higher-order functions)
  • Nested closures and immediate invocation
  • Lint, type checker, and interpreter support
  • Type inference from call context (parameter types inferred from expected signature)

7.4 SQLite Support (std/db/sqlite)

Priority: High — the most common database for small web apps, requires no external server.

SQLite is the natural database for NTNT's sweet spot: AI-generated web prototypes and small applications. Unlike PostgreSQL, it requires zero setup — just a file path.

import { connect, query, execute } from "std/db/sqlite"

let db = connect("app.db")

// Create tables
execute(db, "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")

// Parameterized queries (safe from injection)
execute(db, "INSERT INTO users (name, email) VALUES (?, ?)", ["Alice", "alice@example.com"])

// Query returns array of maps
let users = query(db, "SELECT * FROM users WHERE name = ?", ["Alice"])
for user in users {
    print("User: " + user["name"])
}

Implementation plan:

  • std/db/sqlite module (Rust rusqlite crate with bundled feature)
  • connect(path) — open or create SQLite database file
  • query(db, sql, params?) — parameterized SELECT queries
  • execute(db, sql, params?) — INSERT/UPDATE/DELETE with param binding
  • close(db) — close database connection
  • Transaction support (begin, commit, rollback)
  • In-memory databases: connect(":memory:")
  • Type mapping: INTEGER↔Int, REAL↔Float, TEXT↔String, BLOB↔Array, NULL↔None

7.5 Pipe Operator (|>)

Priority: Moderate — low implementation effort, eliminates daily friction with nested function calls.

Moved from Future Considerations. The assessment identified nested function calls as a concrete pain point: "the nested function call style gets ugly." With closures (7.3) now in the same phase, pipelines become especially powerful.

// Current: nested, reads inside-out
let result = join(split(trim(to_lower(input)), " "), "-")

// With pipe operator: linear, reads left-to-right
let result = input
    |> to_lower
    |> trim
    |> split(" ")
    |> join("-")

// Powerful with closures (7.3):
let active_emails = users
    |> filter(fn(u) { u.active })
    |> transform(fn(u) { u.email })
    |> join(", ")

Implementation plan:

  • |> operator in lexer (PipeArrow token) and parser (desugars to Expression::Call at parse time — no new AST node needed)
  • x |> f desugars to f(x) — simple rewrite
  • x |> f(a, b) desugars to f(x, a, b) — first-argument insertion
  • Type checking flows through the pipe chain (output type of left = input type of right)
  • Error messages show the failing step in the pipeline, not just the final result

7.6 Better Error Messages

Priority: High — the assessment rated NTNT's error messages as "Basic" and "notably behind languages like Rust or Elm." For an AI-first language, rich errors are essential for self-correction loops.

Currently, errors are terse:

Undefined variable: usr
Type error: expected Int, got String

Target: context-rich, actionable errors:

Error[E001]: Undefined variable `usr`
  --> server.tnt:45:12
   |
45 |     return json(usr)
   |                 ^^^ not found in this scope
   |
   help: did you mean `user`? (defined at line 42)
Error[E012]: Type mismatch in function call
  --> server.tnt:23:18
   |
23 |     let result = add("hello", 5)
   |                      ^^^^^^^ expected Int, got String
   |
   note: function `add` defined at server.tnt:10
         fn add(a: Int, b: Int) -> Int

Implementation plan:

  • Error codes (E001-E012) on all error variants for machine-parseable errors
  • "Did you mean?" suggestions for undefined variables (Levenshtein distance)
  • "Did you mean?" suggestions for undefined functions (Levenshtein distance)
  • Function name included in arity mismatch errors
  • Column info added to ParserError
  • Source code snippets in error output (3-line context for parser errors)
  • Color-coded CLI output (error codes in red, line numbers in blue, suggestions in green, help text in cyan)
  • Type checker forward-scanning cursor (find_line_near) — fixes line number accuracy for all 26 diagnostic sites without AST changes
  • Expression-aware search hints (expr_search_hint) — uses AST structure to build better search needles for line lookup
  • Actionable type hints for conditions (Int → "use != 0"), comparisons (Int vs String → "use int()/str()"), and let bindings (untyped map access)
  • Full AST span tracking on all nodes (line, column, span) — the proper fix; would eliminate heuristic line search entirely and enable exact column-level diagnostics. Requires adding a Span { line, col, offset, len } to every AST node, propagating spans through the parser, and updating the type checker to read spans directly instead of searching source text.
  • Runtime error line numbers via AST span tracking — deferred, depends on full span tracking above
  • "Did you mean?" suggestions for wrong imports (scan stdlib for similar names) — see 7.13
  • Contract violation messages show the contract expression and actual values
  • ntnt lint --format=json structured error output for agent consumption

7.7 Route Pattern Auto-Detection

Priority: Moderate — eliminates the most common "gotcha" in NTNT web development.

Currently, route parameters use {id} syntax, which collides with string interpolation. Forgetting to use raw strings (r"") is a recurring friction point:

// Current: must remember r"" for every route
get(r"/users/{id}", get_user)        // ✅ works
get("/users/{id}", get_user)         // ❌ tries to interpolate {id}

// Target: route functions auto-detect patterns, no r"" needed
get("/users/{id}", get_user)         // ✅ just works
post("/api/orders/{id}/items", add_item)  // ✅ just works

Route registration functions (get, post, put, patch, delete) are already builtins — the compiler knows what they are. Their first argument should automatically suppress string interpolation and treat {...} as route parameter placeholders.

Implementation plan:

  • Route builtin functions treat their path argument as a route pattern (no interpolation)
  • {param} in route patterns is always a route parameter, never interpolation
  • Raw strings (r"") still work for backward compatibility
  • ntnt lint warns if a raw string is used unnecessarily in a route (style hint)
  • Dynamic route registration (rare) uses an explicit API if needed

7.8 Destructuring Assignment

Priority: High — every POST handler parses form data or JSON into individual variables. This is the most repetitive boilerplate in NTNT web code.

// Current: 4 lines to extract fields
let form = parse_form(req)
let name = form["name"]
let email = form["email"]
let age = form["age"]

// With destructuring: 1 line
let { name, email, age } = parse_form(req)

// Works with type annotations
let { name: String, email: String, age: Int } = parse_form(req)

// Nested destructuring
let { user: { name, email }, role } = parse_json(req)?

// Array destructuring
let [first, second, ...rest] = split(line, ",")

// In function parameters
fn create_user({ name, email }: Map) -> User {
    return User { name: name, email: email }
}

Implementation plan:

  • Map destructuring in let bindings: let { key1, key2 } = expr
  • Array destructuring: let [first, second] = expr
  • Rest patterns: let [head, ...tail] = arr, let { name, ...other } = map
  • Nested destructuring: let { user: { name } } = data
  • Destructuring with type annotations
  • Destructuring in function parameters
  • Destructuring in for loops: for { name, email } in users { ... }
  • Type checking: destructured fields are type-inferred from the source expression
  • Map destructuring with rename: let { name: n } = data
  • Map destructuring works with structs: let { name } = user
  • Map destructuring in match expressions

7.9 Default Parameter Values ✅

Priority: Moderate — reduces boilerplate in utility functions and makes APIs more ergonomic.

// Current: caller must always pass all arguments
fn paginate(items, page, per_page) {
    let start = (page - 1) * per_page
    return slice(items, start, start + per_page)
}
paginate(users, 1, 25)  // almost always 1 and 25

// With defaults: optional arguments have sensible fallbacks
fn paginate(items: [Any], page: Int = 1, per_page: Int = 25) -> [Any] {
    let start = (page - 1) * per_page
    return slice(items, start, start + per_page)
}
paginate(users)           // page=1, per_page=25
paginate(users, 3)        // page=3, per_page=25
paginate(users, 2, 50)    // page=2, per_page=50

// Works with web handler helpers
fn respond(data: Map, status_code: Int = 200, content_type: String = "application/json") -> Response {
    return status(status_code, stringify(data))
}

Implementation plan:

  • Default value expressions in function parameter lists: param: Type = expr
  • Default parameters must come after required parameters (parser error if violated)
  • Default expressions evaluated at call time (not definition time)
  • Type inference: default value provides type if annotation is missing
  • ntnt inspect includes default values in function signatures (has_default: true)
  • Works with contracts: requires can reference defaulted parameters

7.10 Guard Clauses (let-else) — Removed

Superseded by otherwise (7.2.1), which provides the same unwrap-or-diverge pattern with better ergonomics: err auto-binding, readable keyword, and works with both Result/Option and non-Result values.

7.11 Intent File Cleanup

Priority: Low — small hygiene task, same spirit as the Effect enum removal.

  • Remove unused Meta: section parsing from intent files (the ## Overview section serves the same purpose)
  • Clean up any other dead parsing paths identified during Phase 7 work

7.12 NTNT Language Documentation (Rust Source → Reference Docs)

Design Doc: plans/documentation_system_design.md

Make the Rust source code the single source of truth for all NTNT language documentation. Replace disconnected TOML files with structured /// @ntnt doc comments placed directly above implementation code. build.rs validates 100% coverage at compile time. Documentation is embedded in the binary — ntnt docs works anywhere with zero setup.

Core Principles:

  1. Impossible to go stalebuild.rs cross-references doc comments against implementations; undocumented elements fail the build
  2. AI-native — Structured data (JSON, embedded) for queries, not just markdown
  3. Self-validating — Every example executes and passes, or CI fails
  4. Multi-level — L0 (signature) → L4 (gotchas/patterns) from one source
  5. Embedded in binary — Like Elixir's bytecode docs; no external files or path configuration needed

Current (TOML-based) — To Be Replaced:

  • TOML files as documentation source (stdlib.toml, syntax.toml, etc.)
  • ntnt docs --generate generates markdown from TOML
  • ntnt docs [query] searches stdlib
  • Pre-commit regeneration

Phase 1: build.rs Scanner + Proof of Concept ✅

Goal: Replace TOML with structured doc comments that live above implementation code.

// @ntnt split
// @module std/string
// @signature split(s: String, delim: String) -> Array<String>
// Splits a string into an array of substrings.
//
// When the delimiter is not found, returns a single-element array
// containing the original string.
// @see_also join, trim, replace
// @since v0.1.0
// @example split("a,b,c", ",") => ["a", "b", "c"] ~ "Basic comma-separated split"
// @example split("no-match", ",") => ["no-match"] ~ "No delimiter found"
module.insert("split".to_string(), Value::NativeFunction {
    name: "split".to_string(),
    func: |args| { /* existing implementation — unchanged */ },
});
  • Write build.rs source scanner — parse // @ntnt blocks, extract fields
  • Add doc comments to std/string module (24 functions) as proof of concept
  • Validate coverage: scanner detects undocumented NativeFunction inserts
  • Generate doc_data.json embedded in binary via include_str!()
  • Auto-discover source files (no hardcoded list — directory scanning)
  • Bidirectional validation: undocumented functions AND orphaned doc blocks fail the build

Phase 2: Complete Source Documentation ✅

  • Add doc comments to all 16 stdlib modules (string, math, collections, http, fs, json, csv, url, path, time, crypto, env, postgres, sqlite, concurrent, http_server)
  • Document global builtins (len, print, str, int, float, type, assert, etc.)
  • Document all 267 functions with full structured metadata (@description, @param, @returns, @example, @see_also, @since, @tags, @error, @gotcha)
  • 100% documentation coverage enforced at compile time
  • Delete TOML documentation files (replaced by source-embedded docs)

Phase 3: Enhanced CLI + Embedded Docs

# Querying (works anywhere — docs are in the binary)
ntnt docs split                       # Full docs for a function
ntnt docs std/string                  # All functions in a module
ntnt docs --examples split            # Just the examples
ntnt docs --search "convert string"   # Full-text search
ntnt docs --related split             # Cross-references
ntnt docs --json split                # JSON output for tooling
ntnt docs --ai-context                # Full dump for AI session start

# Validation
ntnt docs --coverage                  # Documentation completeness report
ntnt docs --test                      # Execute all examples (doctests)
ntnt docs --orphans                   # Docs without implementation
ntnt docs --diff v0.3.7               # What changed since a version

# Generation (publishing — on demand)
ntnt docs --generate                  # Markdown + JSON output
ntnt docs --update-agent-docs         # Regenerate auto-sections in AI_AGENT_GUIDE.md
  • ntnt docs [query] — search and display function docs from embedded data
  • ntnt docs --generate — generate markdown reference docs + AI agent guide sync
  • REPL integration: :doc command
  • ntnt docs --examples — show just examples for a function
  • ntnt docs --search — full-text search across all docs
  • ntnt docs --related — cross-reference via @see_also
  • ntnt docs --json — JSON output for tooling
  • ntnt docs --ai-context — full dump for AI session start
  • ntnt docs --coverage — documentation completeness report
  • ntnt docs --test — execute all doc examples (doctests, see below)
  • ntnt docs --orphans — detect orphaned doc blocks
  • ntnt docs --diff — version diffing between releases

Phase 3.5: Doctests (Execute Documentation Examples)

Design Doc: plans/doctest_design.md

Run the 329 @example directives as automated tests during cargo test. Eval both the example code and expected value as NTNT expressions, compare structurally. ~260 examples in pure modules are testable; I/O modules are skipped. Inspired by Elixir's doctests.

  • Expose embedded doc JSON from lib.rs for integration test access
  • Add Interpreter::import_module_all() for programmatic wildcard imports
  • Write tests/doctest_tests.rs integration test (~150-200 lines)
  • Implement values_equal() recursive structural comparison (no PartialEq on Value)
  • Fix any doc examples that fail (the whole point — catch documentation bugs)
  • Wire into ntnt docs --test CLI command

Phase 4: AI-Native Features + Error Integration

  • Semantic concept index from module membership and @see_also relationships
  • Gotchas per function (non-obvious behaviors)
  • Rich error messages with doc links and suggested fixes
  • ntnt docs --ai-context for efficient AI session start

Success Criteria:

Metric Target
TOML files remaining 0
Documentation coverage 100% (build fails otherwise)
Example pass rate 100% (CI fails otherwise)
Files edited to add a function 1 (the Rust source file)
AI query accuracy 95%+ from structured JSON
Binary size increase < 500 KB (~2-5%)
ntnt docs works without external files Yes — embedded in binary

7.13 Import Error Quality — Collision Warnings + "Did You Mean?"

Priority: Moderate — better error messages for the most common language operation.

Design Doc: plans/bare_imports_design.md

Add collision warnings in lint when the same name is imported from two modules, and wire "Did you mean?" suggestions into import error paths for both wrong module names and wrong export names.

Implementation plan:

  • Import collision warnings in ntnt lint (detect when same name imported from two modules, suggest aliases)
  • "Did you mean?" suggestions for wrong module names (wire existing Levenshtein into import_std_module())
  • "Did you mean?" suggestions for wrong export names (wire into bind_imports(), show available exports)

7.14 If-Expressions (Ternary / Conditional Expressions)

Priority: Moderate — the AST and interpreter already support this; only the parser needs updating.

Currently, NTNT has no way to use if/else as an expression that returns a value. This forces unnecessary mutability:

// Current: must use mut
let mut label = ""
if count > 0 {
    label = "active"
} else {
    label = "inactive"
}

// Target: if-expression returns a value
let label = if count > 0 { "active" } else { "inactive" }

// Works in any expression position
return json(map { "status": if ok { "success" } else { "error" } })

Implementation status: ✅ Complete (v0.3.10)

  • Expression::IfExpr in AST (src/ast.rs)
  • IfExpr evaluation in interpreter (src/interpreter.rs)
  • Parse if in expression position in primary() (src/parser.rs)
  • Type inference: both branches return union type (src/typechecker.rs)
  • Require else branch (no dangling if-expressions)
  • Else-if chains via recursive primary() parsing
  • Integration tests (8 tests covering all patterns)
  • Snowgauge examples updated to use if-expressions

7.15 Regex Capture Groups (capture_pattern)

Priority: Moderate — the existing regex functions (find_pattern, matches_pattern, etc.) only work with the full match. Capture groups are essential for structured text extraction.

// Current: find_pattern returns only the full match, not groups
let match = find_pattern("Bear Lake (1042)", r"([^()]+)\s*\((\d+)\)")
// match = Some("Bear Lake (1042)") — no access to capture groups

// Target: capture_pattern returns an array of captured groups
let groups = capture_pattern("Bear Lake (1042)", r"([^()]+)\s*\((\d+)\)")
// groups = Some(["Bear Lake (1042)", "Bear Lake ", "1042"])
//           ^full match             ^group 1      ^group 2

// Named capture groups (stretch goal)
let groups = capture_pattern(line, r"(?P<name>[^()]+)\s*\((?P<id>\d+)\)")
// groups = Some(map { "0": "Bear Lake (1042)", "name": "Bear Lake ", "id": "1042" })

Why this matters: PHP's preg_match with capture groups is one of the places where PHP is more concise than NTNT for text extraction. The snowgauge example's extract_snotel_name function could be reduced from 6 lines to 2 with capture groups.

Implementation plan:

  • capture_pattern(s: String, pattern: String) -> Option<Array<String>> — returns all capture groups (index 0 = full match, 1+ = groups)
  • capture_all_pattern(s: String, pattern: String) -> Array<Array<String>> — all matches with their capture groups
  • capture_named_pattern(s: String, pattern: String) -> Option<Map<String, String>> — named capture groups as map keys
  • Add // @ntnt doc blocks and update STDLIB_REFERENCE.md

7.16 None/null JSON Serialization ✅

Priority: Low — edge case, but affects data interchange.

None serializes as JSON null in stringify(), and parse_json("null") returns None. Consistent NULL→None mapping across all modules (json, sqlite, postgres, http_server). Previously:

// Current: None values have no JSON representation
let data = map { "name": "Alice", "phone": None }
stringify(data)  // behavior undefined — may skip the key or error

// Target: None serializes to null
stringify(data)  // {"name":"Alice","phone":null}

Implementation plan:

  • Value::None serializes as null in stringify()
  • parse_json() maps JSON null to None
  • Round-trip: parse_json(stringify(map { "x": None })) preserves None
  • Consistent NULL→None across all modules: std/json, std/db/sqlite, std/db/postgres, std/http/server

7.17 Web Application Essentials ✅

Priority: High — these are the last-mile features blocking real web application development.

Added 17 stdlib functions across 3 modules plus 1 global builtin to enable building production web applications:

Password Hashing (std/crypto):

  • hash_password(password, cost?) — bcrypt hashing with configurable cost (default 12)
  • verify_password(password, hash) — verify password against bcrypt hash
  • is_valid_hash(hash) — check if string is a valid bcrypt hash

Cookie Management (std/http/server):

  • set_cookie(name, value, options?) — build Set-Cookie header string
  • get_cookie(req, name) — get single cookie from request
  • get_cookies(req) — get all cookies as map
  • delete_cookie(name, options?) — build cookie deletion header
  • with_cookie(resp, name, value, options?) — add cookie to response
  • Multi-value header support (arrays emit multiple headers with same name)

Structured Logging (std/log — new module):

  • log_debug(message, data?) — debug level logging
  • log_info(message, data?) — info level logging
  • log_warn(message, data?) — warning level logging
  • log_error(message, data?) — error level logging
  • set_log_level(level) — set global log level
  • request_logger() — middleware function for request logging

CORS (global builtin):

  • enable_cors(options?) — configure CORS with origins, methods, headers, credentials
  • Automatic OPTIONS preflight handling
  • CORS headers applied to all responses

File Uploads (std/http/server):

  • parse_multipart(req) — parse multipart/form-data requests
  • save_upload(file_field, path) — save uploaded file to disk

7.18 Security Hardening ✅

Goal: Make NTNT inherently secure by default — no configuration required for safe defaults.

Environment Variables:

Variable Default Description
NTNT_MAX_BODY_SIZE 10MB Maximum request body size (supports KB/MB/GB suffixes)
NTNT_SECURITY_HEADERS true Add security headers to all responses
NTNT_DETAILED_ERRORS dev: true, prod: false Show detailed error messages
NTNT_SSRF_PROTECTION true Block requests to private IPs and cloud metadata
NTNT_ALLOW_LOCALHOST dev: true, prod: false Allow fetch() to localhost
NTNT_ALLOW_PRIVATE_IPS false Allow fetch() to private IP ranges
NTNT_BLOCKED_HOSTS `` Comma-separated list of blocked hostnames

Request Body Limits:

  • Configurable max body size via NTNT_MAX_BODY_SIZE
  • Content-Length header checked before reading
  • Returns 413 Payload Too Large with helpful message

Security Headers (automatic on all responses):

  • X-Content-Type-Options: nosniff — prevent MIME sniffing
  • X-Frame-Options: DENY — prevent clickjacking
  • X-XSS-Protection: 1; mode=block — legacy XSS filter
  • Referrer-Policy: strict-origin-when-cross-origin — control referrer leakage
  • Server header hidden in production mode

Open Redirect Protection:

  • redirect_safe(url, fallback?) — safe redirect that rejects absolute URLs
  • Blocks protocol-relative URLs (//evil.com)
  • Blocks dangerous schemes (javascript:, data:, etc.)

SSRF Protection (fetch, download):

  • Blocks private IP ranges (10.x, 172.16-31.x, 192.168.x)
  • Blocks loopback addresses (127.x, ::1)
  • Blocks cloud metadata endpoints (169.254.169.254, etc.)
  • Blocks link-local addresses
  • DNS resolution validation before request

Path Traversal Protection:

  • Static file serving rejects .. patterns
  • URL-encoded traversal patterns detected and blocked
  • save_upload() validates destination paths
  • Filename sanitization on multipart uploads

Cookie Security (production defaults):

  • Secure: true by default in production
  • SameSite: Lax by default in production
  • HttpOnly: true for session/auth cookies in production
  • Cookie value encoding prevents header injection

Error Message Handling:

  • Production mode returns generic error messages
  • Development mode shows full details
  • Configurable via NTNT_DETAILED_ERRORS

Password Hashing:

  • Minimum bcrypt cost raised to 10 (OWASP compliance)

Phase 7 Deliverables:

  • ✅ Semicolons removed from language (lint warning unnecessary_semicolon, examples cleaned up, return parser updated)
  • ✅ Ghost keywords removed (approve, observe, protocol — no longer reserved)
  • otherwise keyword for inline error handling on Result/Option
  • ✅ Type system with inference and enforcement (gradual typing, strict mode)
  • ✅ Effect enum removed (dead code cleanup)
  • ? operator for Result and Option types
  • ✅ Anonymous functions with closure semantics
  • std/db/sqlite module with full CRUD support
  • ✅ Pipe operator for linear data transformations
  • ✅ Context-rich error messages with suggestions and source snippets
  • ✅ Route pattern auto-detection (no more r"" for route paths)
  • ✅ Destructuring assignment (maps, arrays, nested, in parameters and loops)
  • ✅ Default parameter values (with type inference from defaults, contract support)
  • ✅ NTNT language documentation system (// @ntnt doc comments, build.rs validation, embedded binary docs)
  • ✅ If-expressions (conditional ternary returning a value)
  • ✅ Regex capture groups (capture_pattern, capture_all_pattern, capture_named_pattern)
  • ✅ None/null JSON serialization (consistent NULL→None across json, sqlite, postgres, http_server)
  • ✅ Web application essentials (password hashing, cookies, logging, CORS, file uploads)
  • Guard clauses (let-else) — superseded by otherwise (7.2.1)
  • Intent file Meta section cleanup (7.11 — pending)
  • Import error quality (7.13 — pending)
  • Updated examples using new features

Phase 8: Intent System Maturity

Status: Not Started

Goal: Make Intent-Driven Development a tool that AI agents and humans genuinely rely on — not just for testing, but as the shared plane of understanding and accountability between human and agent.

Phase 6 proved the concept: intent files, the IAL engine, and ntnt intent check work. This phase makes the system something an agent wants to use by fixing the friction points discovered through real-world usage: opaque failures, offline validation, glossary debugging, and the lack of shared decision history.

8.1 Resolution Chain in Failure Output

Priority: Highest — when a test fails, the agent currently has to play detective.

Today, a failure looks like:

FAIL: they see "Welcome"

The agent doesn't know: was the response a 500? Was the body empty? Was "Welcome" misspelled? The IAL engine already has the full resolution chain internally — it just doesn't surface it.

Target output:

FAIL: they see "Welcome"
  Resolved: body contains "Welcome"
  Primitive: Check(Contains, response.body, "Welcome")

  Actual status: 200
  Actual body: "<h1>Welcom to the site</h1>"
  ─────────────────────────
  Closest match: "Welcom" (missing 'e')

Implementation plan:

  • Surface the resolution chain in ntnt intent check failure output (glossary term → standard term → primitive)
  • Show actual HTTP response data on failure (status, body excerpt, headers)
  • Fuzzy match suggestions when body contains fails ("did you mean 'Welcom'?")
  • JSON output mode (--json) for agent consumption of failure details
  • Show resolution chain in Intent Studio failure cards

8.2 Offline Intent Validation (ntnt intent validate)

Priority: High — the collaborative design phase needs fast feedback without starting a server.

During the design phase (drafting features, refining scenarios with the human), there's no way to check if the intent file is well-formed without starting the full server. This makes iteration slow.

$ ntnt intent validate server.intent

✓ 12 features parsed
✓ 8 glossary terms defined
✓ All terms resolve to primitives
⚠ Feature "user.profile" has no scenarios
⚠ Glossary term "admin user" is defined but never used
✗ Scenario "Edit profile" uses undefined term "they are redirected"
  hint: did you mean "redirects to"? (standard term)

11 features valid, 1 error, 2 warnings

Implementation plan:

  • ntnt intent validate <file.intent> — parse and validate without server
  • Check all glossary terms resolve to primitives (no dangling references)
  • Warn on unused glossary terms
  • Warn on features with no scenarios
  • Warn on duplicate feature IDs
  • Validate @implements annotations reference existing feature IDs (cross-check with .tnt file)
  • Suggest corrections for unresolved terms (Levenshtein distance against glossary + standard terms)
  • JSON output mode for agent consumption

8.3 Glossary Inspector (ntnt intent glossary)

Priority: Moderate — the glossary is powerful but opaque. Agents and humans need to see what terms are available and how they resolve.

$ ntnt intent glossary server.intent

Custom Terms (8):
  "they see {text}"          → body contains {text}
  "success response"         → status 200
  "the home page"            → /
  "a logged in user"         → component.authenticated_user
  "a user posts to {path}"   → POST {path}
  ...

Standard Terms (24):
  "status {code}"            → Check(Equals, response.status, {code})
  "body contains {text}"     → Check(Contains, response.body, {text})
  "redirects to {path}"      → Check(Equals, response.headers.location, {path})
  ...

Resolution Trace:
  "they see success response""they see {text}" where text = "success response"
    → body contains "success response"
    → Check(Contains, response.body, "success response")
    ⚠ Note: "success response" is a glossary term, not literal text.
       The assertion checks for the literal string "success response" in the body.
       If you meant status 200, use "→ success response" as its own line.

Implementation plan:

  • ntnt intent glossary <file.intent> — list all custom and standard terms
  • ntnt intent glossary <file.intent> --trace "<term>" — show full resolution chain for a specific term
  • Detect semantic misuse (glossary term used as literal text inside another term)
  • Show which scenarios use each glossary term (reverse lookup)
  • --json output for agent consumption
  • Integration with Intent Studio (glossary panel)

8.4 Feature Status Tracking

Priority: Moderate — makes the intent file a living project document, not just a static test spec.

Feature: User Login
  id: feature.user_login
  status: implemented
  since: v0.3.0

Feature: Password Reset
  id: feature.password_reset
  status: planned

Feature: OAuth Integration
  id: feature.oauth
  status: deprecated
  reason: "Replaced by SAML in v0.4.0"

Behavior:

  • status: planned — scenarios are skipped during intent check (not failed), shown as "planned" in Studio
  • status: implemented — scenarios run normally (default if no status specified)
  • status: deprecated — scenarios still run but shown with deprecation warning in Studio
  • since: — tracks when a feature was introduced (informational, used in changelog generation)

Implementation plan:

  • Parse status: field on Feature blocks (planned | implemented | deprecated)
  • Parse since: field (version string, informational)
  • Parse reason: field for deprecated features
  • ntnt intent check skips planned features with clear "SKIP (planned)" output
  • ntnt intent check shows deprecated warnings
  • Intent Studio renders status badges on feature cards
  • ntnt intent check --include-planned flag to run planned features (expect failures)

8.5 Decision Records

Priority: Moderate — the highest-leverage accountability feature. Records why choices were made, not just what was built.

The intent file currently records what the human and agent agreed to build. But it doesn't record the decisions that shaped those features — why session tokens instead of JWTs, why PostgreSQL instead of SQLite, why this API shape and not another. When an agent returns to a project in a new session, that context is lost.

Feature: User Authentication
  id: feature.user_auth

  Decision: Session tokens over JWTs
    date: 2026-01-15
    context: "MVP needs simple auth. JWTs add complexity (refresh tokens,
             signing keys) without clear benefit at this scale."
    decided_by: human
    alternatives_considered:
      - "JWT with refresh tokens"
      - "OAuth2 with external provider"

  Decision: Bcrypt for password hashing
    date: 2026-01-15
    context: "Industry standard, built into std/crypto."
    decided_by: agent

  Scenario: Successful login
    When a user posts valid credentials to /login
    → success response
    → they see "session_token"

Why this matters for human-agent collaboration:

  • Agent context recovery — when I start a new session, I can read decisions to understand why the code looks the way it does, without asking questions the human already answered
  • Human accountability — decisions have an author. If something breaks because of a design choice, the history shows who made it and why
  • Design archaeologyntnt intent decisions lists all decisions across features, creating a lightweight Architecture Decision Record (ADR) system built into the workflow

Implementation plan:

  • Parse Decision: blocks inside Feature sections
  • Fields: date:, context:, decided_by: (human | agent), alternatives_considered: (optional list)
  • ntnt intent decisions <file.intent> — list all decisions across features
  • ntnt intent decisions <file.intent> --by human — filter by decision maker
  • Intent Studio renders decisions as expandable sections on feature cards
  • Decision records are informational — they don't affect test execution

Deliverables:

  • Resolution chain visibility in all failure output
  • ntnt intent validate for offline structural checking
  • ntnt intent glossary for term inspection and resolution tracing
  • Feature status tracking with skip behavior for planned features
  • Decision records for shared human-agent accountability
  • All new commands support --json output for agent consumption

Phase 9: Package & Module Ecosystem

Status: Not Started

Goal: Let NTNT be extended beyond the standard library. This is the single biggest barrier to adoption — every project eventually needs something the stdlib doesn't have.

This phase delivers the foundation: local packages, git dependencies, and a project manifest. The full registry and publishing infrastructure comes later in Phase 12.2 (Tooling & DX). This is the single feature the assessment identified as "the biggest barrier to adoption."

9.1 Project Manifest (ntnt.toml)

Every NTNT project gets a manifest file that declares metadata and dependencies:

[project]
name = "my-app"
version = "0.1.0"
entry = "server.tnt"

[dependencies]
markdown = { path = "../ntnt-markdown" }        # Local path
email = { git = "https://github.com/user/ntnt-email.git" }  # Git URL

Implementation plan:

  • ntnt.toml parser (TOML format, Rust toml crate)
  • ntnt new <name> — scaffold a new project with ntnt.toml, server.tnt, and directory structure
  • Project metadata: name, version, description, author, license, entry point
  • ntnt run auto-detects ntnt.toml and resolves dependencies before execution
  • ntnt.lock lockfile for reproducible builds

9.2 NTNT-Native Packages

Packages are directories of NTNT code with a ntnt.toml manifest that other projects can import:

ntnt-markdown/
├── ntnt.toml
├── lib.tnt          # Package entry point (exports public API)
├── src/
│   ├── parser.tnt
│   ├── renderer.tnt
│   └── extensions.tnt
└── tests/
    └── markdown_tests.tnt

lib.tnt (package entry point):

// Re-export public API
import { parse } from "./src/parser"
import { render_html, render_text } from "./src/renderer"

export { parse, render_html, render_text }

Consumer usage:

import { parse, render_html } from "markdown"

fn blog_handler(req: Request) -> Response {
    let content = read_file("posts/" + req.params["slug"] + ".md")
    let html_content = render_html(parse(content))
    return html(html_content)
}

Implementation plan:

  • Package resolution: name in ntnt.toml [dependencies] → path or git URL → directory with ntnt.toml
  • Package imports: import { x } from "package-name" resolves to the package's lib.tnt exports
  • Local path dependencies: { path = "../my-package" }
  • Git dependencies: { git = "https://..." } — clone to a cache directory
  • Git ref pinning: { git = "...", tag = "v1.0.0" } or { git = "...", rev = "abc123" }
  • Dependency caching: packages cached in ~/.ntnt/packages/
  • ntnt add <name> --path <path> — add a local dependency
  • ntnt add <name> --git <url> — add a git dependency
  • Circular dependency detection

9.3 Rust Extension Packages (FFI)

For capabilities that can't be written in pure NTNT (system libraries, performance-critical code, bindings to existing ecosystems):

# ntnt-redis/ntnt.toml
[project]
name = "redis"
version = "0.1.0"
type = "native"          # Indicates Rust extension

[native]
crate = "ntnt-redis"     # Rust crate name

Implementation plan:

  • Extension API: Rust trait that native packages implement to expose functions to NTNT
  • Dynamic loading: .so/.dylib/.dll loaded at runtime
  • Type marshaling: Rust types ↔ NTNT Value conversion
  • Standard extension trait with register_functions() method
  • Pre-built extensions for common needs (Redis, email, image processing)
  • ntnt build-ext command for compiling Rust extensions

9.4 Stdlib as Packages

Refactor parts of the standard library to use the same package infrastructure, proving the system works:

  • Extract std/csv as a standalone package (simple, good test case)
  • Extract std/crypto as a standalone package
  • Built-in packages resolve from the interpreter binary (no download needed)
  • Stdlib packages serve as reference implementations for package authors

Deliverables:

  • ntnt.toml project manifest with dependency declaration
  • ntnt new project scaffolding
  • Local path and git URL dependency resolution
  • Package import system (import { x } from "package-name")
  • Rust FFI extension API for native packages
  • Dependency caching and lockfile
  • At least two stdlib modules extracted as proof-of-concept packages

Phase 10: Background Jobs, WebSockets & Real-Time

Status: Not Started

Goal: Production-ready background job system with a declarative Job DSL, pluggable backends, and deep IDD integration — plus WebSocket and SSE support for pushing data to clients. Jobs are first-class language constructs — the Job keyword is syntax, not a library import — with the runtime and queue management provided by std/jobs.

Background jobs are essential for any non-trivial web application: sending emails, processing payments, syncing with external APIs, generating reports. NTNT's job system treats jobs as intentional units of work rather than just functions to execute, aligning with the IDD philosophy. The Job DSL is language-level syntax (like fn or struct), while the Queue runtime lives in std/jobs (like json() lives in std/http/server). See design-docs/background_jobs.md for the full design.

10.1 Job DSL & Core Runtime

Priority: Foundation — the Job declaration syntax and in-memory backend.

/// Sends personalized welcome email to newly registered users
Job SendWelcomeEmail on emails {
    perform(user_id: String) {
        let user = db.find_user(user_id)
        email.send(user.email, "Welcome!", "...")
    }
}

/// Charges customer credit card for completed orders
Job ProcessPayment on payments (retry: 5, timeout: 120s) {
    perform(order_id: String, amount: Float) {
        let order = db.find(order_id)
        stripe.charge(order.customer_id, amount)
    }

    on_failure(error, attempt) {
        alert.notify("Payment failed: {error}")
    }
}

// Enqueue jobs
SendWelcomeEmail.enqueue(map { "user_id": "123" })
ProcessPayment.enqueue_in(3600, map { "order_id": "456", "amount": 29.99 })

Implementation plan:

  • Job declaration syntax in parser (new AST node: JobDeclaration)
  • perform() handler with typed arguments
  • on_failure() hook
  • Job.enqueue(), Job.enqueue_at(), Job.enqueue_in() methods
  • Queue configuration: Queue.configure(map { "backend": "memory" })
  • In-memory backend (zero dependencies, default)
  • Worker loop with retry logic and exponential backoff
  • Priority queues (low, normal, high)
  • Dead letter queue for exhausted retries
  • Job cancellation: Queue.cancel(job_id)
  • Graceful shutdown (drain in-progress jobs on SIGTERM)
  • Job options: retry, timeout, backoff, priority, rate, concurrency, unique, expires, idempotent
  • Doc comment metadata parsing (/// Triggers:, /// Affects:, /// Side effects:)

10.2 Resilience & Production Features

Priority: High — required for any production deployment.

  • Worker heartbeats (detect crashed workers)
  • Visibility timeout (re-enqueue stale jobs after no heartbeat)
  • Rate limiting per job type (e.g., rate: 100/minute)
  • Concurrency limits per job type
  • Job TTL/expiration (expires: 5m — discard stale jobs)
  • Automatic pruning of completed/cancelled jobs
  • Weighted queue processing (prevent starvation of low-priority queues)
  • Queue.work_async() for combined HTTP server + worker mode

10.3 Persistent Backends

Priority: High — in-memory jobs are lost on restart.

import { Queue } from "std/jobs"

// PostgreSQL backend (reliable, ACID, multi-worker)
Queue.configure(map {
    "backend": "postgres",
    "postgres_url": env("DATABASE_URL")
})

// Redis/Valkey backend (high throughput, 10k+ jobs/sec)
Queue.configure(map {
    "backend": "redis",
    "redis_url": env("REDIS_URL")
})
  • PostgreSQL backend with auto-migration (ntnt_jobs table)
  • Distributed locking via SELECT FOR UPDATE SKIP LOCKED
  • Redis/Valkey backend for high-throughput workloads
  • Feature flags to avoid bloating the binary (jobs-postgres, jobs-redis)
  • Separate worker processes for production: Queue.work(map { "queues": ["emails", "payments"], "concurrency": 10 })

10.4 Composition (Chains, Workflows, Batches)

Priority: Moderate — needed for multi-step business processes.

// Sequential chain — each job receives the previous job's result
Chain ProcessOrder {
    ValidateOrder -> ReserveInventory -> ChargePayment -> SendConfirmation
}
ProcessOrder.start(map { "order_id": "123" })

// DAG workflow — fan-out and fan-in
Workflow UserOnboarding {
    CreateAccount -> SendWelcomeEmail
    CreateAccount -> SetupBilling
    [SendWelcomeEmail, SetupBilling] -> ActivateAccount
}

// Batch — parallel with completion callback
let batch = Batch.create(map {
    "on_complete": fn(results) { db.update_total(sum(results)) },
    "on_failure": fn(errors) { alert("Batch failed") }
})
for chunk in data_chunks { batch.add(ProcessChunk, map { "chunk": chunk }) }
batch.run()
  • Chain declaration syntax (sequential job pipelines)
  • Workflow declaration syntax (DAG dependencies with fan-out/fan-in)
  • Batch.create() / batch.add() / batch.run() API
  • Unique jobs / deduplication (unique: args for 1h)
  • Workflow status tracking: Workflow.status(workflow_id)

10.5 WebSocket Support

Priority: High — essential for modern web apps. Live dashboards, chat, notifications, and real-time job status updates all require pushing data to clients.

The assessment identified this as a key missing feature: "there's no way to push data to clients. This limits NTNT to traditional page-based web apps."

import { broadcast, send_to } from "std/ws"

// WebSocket route — handler called per connection
ws("/chat", fn(conn) {
    // Called when a message arrives
    conn.on_message(fn(msg) {
        // Broadcast to all connected clients
        broadcast("/chat", msg)
    })

    conn.on_close(fn() {
        print("Client disconnected")
    })
})

// Send to specific client from anywhere (e.g., from a job)
ws("/jobs/status", fn(conn) {
    // Client subscribes to job updates
    let job_id = conn.params["job_id"]
    conn.on_open(fn() {
        send_to(conn, json(Queue.status(job_id)))
    })
})

// Push from background jobs
Job ProcessPayment on payments {
    perform(order_id: String) {
        // ... process payment ...
        broadcast("/orders/{order_id}", json(map { "status": "paid" }))
    }
}

listen(8080)

Implementation plan:

  • ws(pattern, handler) global builtin for WebSocket routes (mirrors get/post pattern)
  • Connection object: conn.on_message(), conn.on_open(), conn.on_close()
  • send_to(conn, msg) — send to a specific connection
  • broadcast(channel, msg) — send to all connections on a channel
  • std/ws module for additional utilities (rooms, connection tracking)
  • Integration with background jobs — push job status updates to clients
  • Server-Sent Events (SSE) as a simpler alternative: sse(pattern, handler)
  • Connection state management (track connected clients, rooms/channels)
  • Graceful connection cleanup on server shutdown

10.6 IDD Integration & CLI

Priority: Moderate — testable jobs are NTNT's differentiator over Sidekiq/Bull/Oban.

Feature: Welcome Email Job
  id: feature.welcome_email_job
  test:
    - job: SendWelcomeEmail
      args: { "user_id": "123" }
      given:
        - mock db.find_user returns { "id": "123", "email": "test@example.com" }
      assert:
        - status: completed
        - email.send was called with "test@example.com"
  • Job testing in .intent files (job: assertion type)
  • Mock support for job dependencies in IDD scenarios
  • ntnt jobs status — summary of all queues
  • ntnt jobs list [--pending|--failed|--dead] — filter jobs by status
  • ntnt jobs inspect <job-id> — full job details
  • ntnt jobs retry <job-id> — retry a failed/dead job
  • ntnt jobs cancel <job-id> — cancel a pending job
  • ntnt jobs simulate <JobName> --args='...' — dry-run without side effects
  • ntnt jobs replay <job-id> — re-run with exact same inputs for debugging
  • --format=json for agent-consumable output on all commands

10.7 Advanced Features (Future)

  • effect blocks for explicit side-effect declaration (skipped in simulation mode)
  • Job contracts (requires(args) { ... }, ensures(args, result) { ... })
  • Intent verification (verify() hook — did the job achieve its purpose, not just run?)
  • Idempotency static analysis in ntnt lint
  • Natural language queries: ntnt jobs ask "why are emails failing?"
  • AI-powered diagnosis: ntnt jobs diagnose <job-id>
  • Request tracing across job chains: ntnt jobs trace <request-id>

Deliverables:

  • Job, Chain, Workflow language-level declaration syntax
  • std/jobs module with Queue API and worker model
  • In-memory, PostgreSQL, and Redis/Valkey backends
  • Resilience: heartbeats, retries, dead letter queue, rate limiting, graceful shutdown
  • Job composition: chains (sequential), workflows (DAG), batches (parallel)
  • WebSocket and SSE support (ws() builtin, broadcast(), send_to())
  • IDD integration for testing jobs in .intent files
  • ntnt jobs CLI commands for monitoring and management
  • Simulation mode for dry-run execution

Phase 11: Testing Framework

Goal: Comprehensive testing infrastructure complementing Intent-Driven Development.

IDD tests behavior at the feature level. This phase adds unit testing, mocking, and contract-based test generation for fine-grained code verification.

11.1 Unit Test Framework

  • #[test] attribute for test functions
  • Test discovery and runner
  • Parallel test execution
  • assert, assert_eq, assert_ne macros
  • #[should_panic] for expected failures
  • Test filtering and tagging
#[test]
fn test_user_creation() {
    let user = User.new("Alice", "alice@example.com")
    assert_eq(user.name, "Alice")
    assert(user.email.contains("@"))
}

#[test]
#[should_panic(expected: "invariant violated")]
fn test_invalid_email() {
    User.new("Bob", "invalid-email")
}

11.2 Contract-Based Test Generation

  • Auto-generate test cases from contracts
  • Property-based testing with contracts
  • Fuzzing with contract guidance
  • Contract coverage metrics
  • Edge case generation from requires clauses
// Given this contract:
fn divide(a: Int, b: Int) -> Int
    requires b != 0
    ensures result * b == a
{ a / b }

// Auto-generate tests:
// - divide(10, 2) → 5 ✓
// - divide(0, 1) → 0 ✓
// - divide(5, 0) → precondition failure ✓
// - divide(-10, -2) → 5 ✓ (negative handling)

11.3 Mocking & Test Utilities

  • Mock trait implementations
  • HTTP test client (complements IDD HTTP testing)
  • Database test utilities (test transactions, fixtures)
  • Test fixtures and factories
  • Snapshot testing
#[test]
fn test_with_mock_db() {
    let mock_db = MockDatabase.new()
    mock_db.expect_query("SELECT * FROM users").returns([user1, user2])

    let result = get_all_users(mock_db)
    assert_eq(len(result), 2)
}

11.4 Test Integration

  • ntnt test command (runs all tests)
  • ntnt test --unit (unit tests only)
  • ntnt test --intent (IDD tests only)
  • Coverage reports (combined unit + IDD)
  • CI/CD integration patterns
# Run all tests
ntnt test

# Run only unit tests
ntnt test --unit

# Run only IDD feature tests
ntnt test --intent

# Combined coverage report
ntnt test --coverage

Deliverables:

  • #[test] attribute system
  • Contract-based test generation
  • Mocking framework
  • Test runner with filtering
  • Coverage reporting

Phase 12: Tooling & Developer Experience

Goal: World-class developer experience with AI collaboration support.

12.1 Language Server (LSP)

  • Go to definition
  • Find references
  • Hover documentation
  • Code completion
  • Inline diagnostics
  • Code actions (quick fixes)
  • Contract visualization

12.2 Package Registry & Publishing

Note: The package foundation (manifest, local/git dependencies, imports) is built in Phase 9 (Package Ecosystem). This section adds the public registry and publishing infrastructure.

  • Central package registry (hosted service)
  • ntnt publish — publish packages to the registry
  • Semantic versioning enforcement
  • Dependency resolution with version ranges (^1.0, ~2.3)
  • ntnt add <name> — install from registry (in addition to Phase 9's path/git support)
  • Package search: ntnt search <query>
ntnt new my-app
ntnt add http
ntnt add db/postgres --version "^1.0"
ntnt test
ntnt build --release

12.3 Human Approval Mechanisms (From Whitepaper)

  • @requires_approval annotations
  • Approval workflows in IDE
  • Audit trails for approved changes
  • Configurable approval policies
@requires_approval("security")
fn delete_all_users(db: Database) -> Result<Int, DbError> {
    db.execute("DELETE FROM users")
}

@requires_approval("api-change")
pub fn get_user(id: String) -> User {
    // Public API changes require review
}

12.4 Debugger

  • Breakpoints
  • Step debugging
  • Variable inspection
  • Call stack navigation
  • Contract state inspection
  • DAP (Debug Adapter Protocol) support

12.5 User Code Documentation (.tnt Files)

Design Doc: plans/tnt_code_documentation_design.md

Doc comments (///, //!), doctests, and contract-as-documentation for user-written .tnt code. Depends on LSP (12.1) for hover integration.

  • Add DocComment (///) and ModuleDocComment (//!) token types to lexer
  • Attach doc comments to Function, Struct, Enum AST nodes
  • Parse doc comments into structured data (summary, params, examples, metadata annotations)
  • ntnt docs <file> [function] displays formatted output for user code
  • Doctest runner: extract // => examples from doc comments, execute, report pass/fail
  • Contract extraction: requires/ensures/invariant auto-generate documentation sections
  • Include doc comments in ntnt inspect JSON output
  • LSP hover support for user-defined functions

Deliverables:

  • Full LSP server
  • Package registry and publishing
  • Human approval system
  • Debugger
  • User code documentation (doc comments, doctests, contract-as-documentation)

Phase 13: Performance & Compilation

Goal: Production-ready performance through progressive compilation strategies.

Current Architecture

NTNT Source (.tnt)
       ↓
    Lexer (src/lexer.rs)         ✅ Reusable
       ↓
    Parser (src/parser.rs)       ✅ Reusable
       ↓
      AST (src/ast.rs)           ✅ Reusable
       ↓
  Interpreter (src/interpreter.rs)  ← Tree-walking (current, slowest)
       ↓
    Result

Compilation Roadmap

Approach Effort Speedup When
Tree-walking Interpreter ✅ Done Baseline Current
Bytecode VM 2-4 weeks 10-50x Phase 13.1
Native Compilation (Cranelift/LLVM) 2-3 months 100-1000x Phase 13.4

What Can Be Reused

Component Reusable? Notes
Lexer ✅ 100% Tokens don't change
Parser ✅ 100% AST structure stays same
AST ✅ 100% Core data structures
Type System ✅ 100% Expansion for optimization
Interpreter ❌ Replaced Becomes compiler/codegen
Stdlib ⚠️ Partial Need native implementations

13.1 Bytecode VM (First Target)

Goal: 10-50x performance improvement with moderate effort.

  • Design NTNT bytecode format (NBC)
  • Implement bytecode compiler (src/compiler.rs)
  • Implement stack-based VM (src/vm.rs)
  • Bytecode serialization/loading (.tnc files)
  • Debug info preservation for stack traces
  • Keep interpreter for REPL (faster startup)
// Example bytecode instructions
enum OpCode {
    LoadConst(usize),      // Push constant onto stack
    LoadVar(String),       // Push variable value
    StoreVar(String),      // Pop and store to variable
    Add, Sub, Mul, Div,    // Arithmetic
    Eq, Lt, Gt, Le, Ge,    // Comparison
    Call(usize),           // Call function with N args
    Return,                // Return from function
    Jump(usize),           // Unconditional jump
    JumpIfFalse(usize),    // Conditional jump
    MakeArray(usize),      // Create array from N stack values
    MakeMap(usize),        // Create map from N key-value pairs
    GetField(String),      // Map/struct field access
    SetField(String),      // Map/struct field assignment
}

CLI Integration:

ntnt compile app.tnt        # Compile to bytecode (.tnc)
ntnt run app.tnc            # Run bytecode directly
ntnt run app.tnt            # Auto-compile and run (caches .tnc)

13.2 VM Optimizations

  • Constant folding at compile time
  • Dead code elimination
  • Inline caching for method calls
  • Escape analysis for stack allocation
  • Contract elision in release builds (configurable)
  • Hot path detection and optimization

13.3 Memory Management

  • Reference counting with cycle detection
  • Memory pools for hot paths
  • String interning
  • Small string optimization
  • Arena allocators for request handling

13.4 Native Compilation (Future)

Goal: Native machine code for maximum performance (100-1000x faster than interpreter).

Option A: Cranelift Backend (Recommended)

AST → Cranelift IR → Native Machine Code
  • Simpler API than LLVM
  • Good optimization passes
  • Used by rustc (experimental) and Wasmtime
  • Estimated effort: 1-2 months
// Using cranelift crate
use cranelift::prelude::*;
use cranelift_module::Module;

fn compile_function(ast: &Function, module: &mut Module) {
    let mut func = Function::new();
    let mut builder = FunctionBuilder::new(&mut func, &mut ctx);

    // Generate Cranelift IR from AST
    for stmt in &ast.body {
        compile_statement(stmt, &mut builder);
    }
}

Option B: LLVM Backend

AST → LLVM IR → LLVM Optimizer → Native Machine Code
  • Best-in-class optimizations
  • Used by Rust, Swift, Julia, Clang
  • More complex API
  • Estimated effort: 2-3 months
// Using inkwell (LLVM Rust bindings)
use inkwell::context::Context;
use inkwell::builder::Builder;

fn compile_to_llvm(ast: &Module) -> inkwell::module::Module {
    let context = Context::create();
    let module = context.create_module("ntnt");
    let builder = context.create_builder();

    // Generate LLVM IR from AST
}

Option C: Transpile to Rust (Creative Alternative)

AST → Rust Source Code → cargo build → Native Binary
  • Leverage Rust's optimizer for free
  • Easier debugging (human-readable output)
  • Estimated effort: 2-4 weeks
// NTNT source
fn add(a: Int, b: Int) -> Int { a + b }

// Generated Rust
fn add(a: i64, b: i64) -> i64 { a + b }

CLI Integration:

ntnt build app.tnt              # Compile to native binary
ntnt build app.tnt --release    # Optimized build
./app                           # Run native binary directly

13.5 Advanced Static Analysis & Contract Inference

Note: Basic type inference and enforcement are in Phase 7.1 (including contract expression type-checking). This section covers deep analysis that builds on the bytecode compiler, including the full contract inference system.

Contract Inference:

Contract inference warns when you call a function with contracts without satisfying them. Contracts remain completely optional — inference only activates for contracts that someone chose to write. No contracts on your function? No warnings, no obligations.

fn divide(a: Int, b: Int) -> Int
    requires b != 0
{
    return a / b
}

fn compute(x: Int, y: Int) -> Int {
    return divide(x, y)
    //              ^ Warning: `divide` requires `b != 0` but `y` has no such guarantee.
    //                hint: add `requires y != 0` to `compute`, or check before calling.
}
  • Single-level propagation — warn when calling a requires function with an unchecked argument
  • Suggest adding a matching requires clause to the caller
  • Recognize common patterns: if x != 0 { divide(a, x) } satisfies requires x != 0
  • Recognize match arms: Some(v) => use(v) satisfies requires v != None
  • Transitive propagation — propagate contracts through entire call chains (A→B→C)
  • Contract static verification (prove contracts hold using SMT solvers or abstract interpretation)
  • Auto-generate requires clauses from analysis of function body
  • Contract inference across module boundaries

Type Analysis:

  • Flow-sensitive typing (type narrows after null checks) — implemented in Phase 7.1
  • Exhaustive type checking at compile time (match exhaustiveness for Option/Result/enums) — implemented in Phase 7.1
  • Type narrowing in conditionals and match arms — implemented in Phase 7.1
  • Escape analysis for optimization hints

13.6 Advanced Type System Features

  • Associated types in traits
  • Where clauses for complex constraints
  • Contract inheritance (contracts propagate to trait implementations)
  • Liskov Substitution Principle enforcement
  • Contravariant preconditions, covariant postconditions
  • Error context/wrapping: result.context("message")?

13.7 Runtime Library (for Native Compilation)

Native compilation requires re-implementing stdlib in the target:

  • Core runtime (memory, strings, arrays, maps)
  • I/O operations (file system, HTTP)
  • Database drivers (PostgreSQL bindings)
  • Concurrency primitives (threads, channels)

13.8 Advanced Concurrency

Building on Phase 5's channel-based concurrency:

  • spawn(fn) / join(handle) - background task execution
  • parallel([fn1, fn2, ...]) - run multiple functions in parallel
  • select([ch1, ch2, ...]) - wait on multiple channels (Go-style)
  • Async HTTP requests (requires async runtime)

Deliverables:

  • Bytecode compiler and VM (10-50x speedup)
  • Static type checker
  • Advanced type system
  • Optimized memory management
  • Native compilation path (100-1000x speedup)

Phase 14: AI Integration & Structured Edits

Goal: First-class AI development support—NTNT's key differentiator.

14.1 Structured Edits (From Whitepaper)

  • AST-based diff format
  • Semantic-preserving transformations
  • Edit operations: AddFunction, ModifyContract, RenameSymbol, etc.
  • Machine-readable edit format for AI agents
// Instead of text diffs, edits are structured:
Edit {
    type: "ModifyContract",
    target: "fn calculate_shipping",
    add_requires: "dest.len() > 0",
    rationale: "Prevent empty destination strings"
}

14.2 AI Agent SDK

  • Agent communication protocol
  • Context provision API (give AI relevant code context)
  • Suggestion acceptance/rejection tracking
  • Learning from corrections

14.3 Semantic Versioning Enforcement

  • API signature tracking across versions
  • Automatic breaking change detection
  • Semver suggestions based on changes
  • @since and @deprecated annotations
@since("1.2.0")
@deprecated("2.0.0", "Use get_user_by_id instead")
fn get_user(id: String) -> User { }

14.4 Commit Rationale Generation

  • Structured commit metadata
  • Link commits to intents and requirements
  • Auto-generate changelog entries
  • AI-friendly commit format

14.5 AI Agent Optimization

Targeting the specific weaknesses of LLMs: context limits, hallucinations, and safety.

14.5.1 Machine-Readable Diagnostics (--json output)

Enable reliable "Self-Correction Loops" for agents.

  • ntnt check --format=json
  • ntnt lint --format=json
  • Structured errors with remediation suggestions
  • Codes for common agent mistakes (e.g., E023 "Undefined variable")
{
  "file": "server.tnt",
  "line": 45,
  "column": 12,
  "severity": "error",
  "code": "E023",
  "message": "Undefined variable 'usr'",
  "suggestion": {
    "text": "Did you mean 'user'?",
    "replacement": "user",
    "start": 12,
    "end": 15
  }
}

14.5.2 Token-Optimized Context (ntnt describe)

Provide compressed summaries of the codebase to save tokens and reduce distraction.

  • ntnt describe src/ command
  • Extracts: Structs, Signatures, Contracts, Imports
  • Strips: Function bodies, comments (unless doc comments)
  • "Searchable Index" for agents to find correct imports

14.5.3 Native "Simulation Mode" (Safety Nets)

Allow agents to execute code safely without side effects on production data.

  • Global --dry-run flag
  • std/env simulation context check
  • Mocking of side-effecting built-ins (execute, write_file) in simulation mode
// In std/db
pub fn execute(query, params) {
    if (Global.is_simulation) {
        log("WOULD EXECUTE: " + query);
        return Ok(0);
    }
    // ... real execution
}

14.5.4 First-Class todo Keyword (Hole-Driven Development)

Allow agents to partially implement features without blocking compilation.

  • todo keyword (or ???)
  • Syntactically valid but panics at runtime
  • Compiler passes todo blocks
fn complex_logic(user) {
    if (check_auth(user)) {
        todo "Implement retry logic"
    }
}

14.5.5 "Smart Import" Resolution

reduce hallucinated imports by suggesting correct paths.

  • "Smart Linker" in compiler/linter
  • Scans standard library and local modules for missing exports
  • Error message suggests correct path: "Error: json not found in std/http. Did you mean std/http/server?"

Deliverables:

  • Structured edit engine
  • AI agent SDK
  • Semantic versioning tools
  • Commit rationale system

Phase 15: Deployment & Operations

Goal: Production deployment support.

15.1 Build & Distribution

  • Single binary compilation
  • Cross-compilation support
  • Minimal Docker image generation
  • Build profiles (dev, release, test)

15.2 Configuration

  • Environment-based config
  • Config file support (TOML, JSON)
  • Secrets management patterns
  • Validation with contracts

15.3 Observability

  • Structured logging (std/log)
  • Metrics collection (Prometheus format)
  • Distributed tracing (OpenTelemetry compatible)
  • Health check endpoints
  • Contract violation reporting
import { Logger, Metrics } from "std/observe"

let log = Logger.new("api")
let requests = Metrics.counter("http_requests_total")

fn handle_request(req: Request) -> Response {
    requests.inc({ path: req.path, method: req.method })
    log.info("Handling request", { path: req.path })
    // ...
}

15.4 Graceful Lifecycle

  • Signal handling (SIGTERM, SIGINT)
  • Connection draining
  • Shutdown hooks
  • Startup/readiness probes

Deliverables:

  • Binary compilation
  • Docker support
  • Observability stack
  • Graceful lifecycle management

Future Considerations (Post-1.0)

These features are valuable but not essential for the initial release:

Pipeline Operator (|>) → Moved to Phase 7.5

Response Caching (Server-Side)

In-memory caching for HTTP handler responses. Note: For most use cases, CDN caching via HTTP headers (e.g., Cache-Control: s-maxage=N for Cloudflare) is sufficient and preferred. Server-side response caching is only needed for expensive computations that can't be cached at the edge.

  • std/cache module with TTL-based caching
  • cache() middleware for route handlers
  • Cache key generation from request (path, query params)
  • Manual cache API: create_cache, get_cached, set_cached, invalidate

Effect System (Rebuilt)

History: An effect system was partially implemented in Phase 2.4 (syntax parsing only, no enforcement) and removed in Phase 7.1 as dead code. This section describes a proper rebuild that depends on the static analysis infrastructure from Phase 13.

Effect tracking lets the compiler verify that functions only perform the side effects they declare. A pure function can't call an IO function. A function that deletes data requires approval("security"). The compiler enforces this statically — no runtime cost.

fn read_config(path: String) -> String with io {
    return read_file(path)
}

fn add(a: Int, b: Int) -> Int pure {
    return a + b  // compiler error if this called read_file()
}

@requires_approval("destructive")
fn reset_database(db: Database) with io {
    execute(db, "DROP ALL TABLES")
}

Prerequisites:

  • Phase 7.1: Enforced type system (effect checking extends type checking)
  • Phase 13.1+: Bytecode compiler / static analysis passes (effect propagation through call chains)

Implementation:

  • Effect inference (auto-detect effects from function body)
  • Effect propagation (if f calls g with io, then f has io too)
  • Static enforcement (pure functions cannot call io functions)
  • Approval effect integrated with Human Approval Mechanisms (Phase 12.3)
  • Effect polymorphism (generic functions that preserve caller's effects)
  • Contract interaction (contracts on pure functions can be statically verified)

Why wait: A real effect system requires analyzing the full call graph statically. The tree-walking interpreter can't do this — it would need the bytecode compiler's static analysis passes to trace effect propagation across function calls, modules, and generics. Building it before that infrastructure exists would repeat the mistake of Phase 2.4: syntax without enforcement.

Session Types

  • Protocol definitions for typed communication
  • Deadlock prevention at compile time
  • Formal verification of message sequences

Additional Database Drivers

PostgreSQL Enhanced Support (Current):

  • Basic types: INT, BIGINT, FLOAT, DOUBLE, TEXT, VARCHAR, BOOL
  • NUMERIC/DECIMAL (via rust_decimal)
  • DATE, TIME, TIMESTAMP, TIMESTAMPTZ (via chrono)
  • JSON/JSONB
  • UUID
  • Arrays: INT[], TEXT[], FLOAT[], BOOL[]
  • BYTEA (binary data)
  • INTERVAL
  • PostGIS geometry types

Additional Drivers:

  • MySQL/MariaDB
  • SQLite (→ moved to Phase 7.4 as priority item)
  • Redis client

High-Performance HTTP Server ✅ PARTIAL

The HTTP server now uses Axum + Tokio for async request handling:

  • Async runtime (Tokio) for concurrent connections
  • Connection pooling and keep-alive
  • Bridge pattern connecting async handlers to sync interpreter
  • HTTP/2 support with multiplexing
  • Request pipelining
  • Zero-copy response streaming
  • Performance target: 100k+ req/sec on commodity hardware

WebSocket Support → Moved to Phase 10.5

Concurrency Primitives

  • Channels for message passing
  • Structured concurrency (task scopes)
  • Parallel iterators

Implementation Priority Matrix

Phase Focus Business Value Effort
1-5 ✅ Core Language + Web Foundation Complete
6 ✅ Intent-Driven Dev High Complete
7 Ergonomics & Documentation High (Up Next) Medium
8 Intent System Maturity High Medium
9 Package Ecosystem Critical Medium
10 Jobs, WebSockets & Real-Time High Medium
11 Testing Framework High Medium
12 Tooling & DX Very High High
13 Performance High Medium
14 AI Integration Differentiator Medium
15 Deployment High Medium

Milestones

Milestones M1 (Language Complete) and M2 (Web Ready) are complete. See ROADMAP_COMPLETE.md.

M3: Ergonomic Language (End of Phase 7)

  • Enforced type system with inference
  • Error propagation (? operator)
  • Anonymous functions / closures
  • SQLite support
  • Pipe operator for linear data flow
  • Context-rich error messages with suggestions
  • Route pattern auto-detection (no r"" needed)
  • Destructuring, default parameters
  • If-expressions (inline conditional values)
  • Regex capture groups for structured text extraction
  • Two-layer safety: types (structural) + contracts (semantic)
  • NTNT language documentation system (doc comments + build.rs validation, embedded in binary)
  • A typical web handler drops from ~22 lines to ~6

M4: Mature Intent System (End of Phase 8)

  • Resolution chain visibility in failure output
  • Offline intent validation (ntnt intent validate)
  • Glossary inspector (ntnt intent glossary)
  • Feature status tracking (planned/implemented/deprecated)
  • Decision records for human-agent accountability
  • Intent system is a tool agents genuinely rely on

M5: Extensible Language (End of Phase 9)

  • Package manifest (ntnt.toml)
  • Local and git dependencies
  • NTNT-native packages with lib.tnt entry points
  • Rust FFI for native extensions
  • Ecosystem can grow beyond stdlib

M6: Real-Time & Background Processing (End of Phase 10)

  • Job, Chain, Workflow language-level declarations
  • std/jobs with in-memory, PostgreSQL, and Redis backends
  • Resilience: heartbeats, retries, dead letter queue, rate limiting
  • WebSocket and SSE support for real-time client communication
  • Job testing in .intent files
  • ntnt jobs CLI for monitoring and management

M7: Developer Ready (End of Phase 12)

  • Full IDE support (LSP)
  • Package registry and publishing
  • Human approval workflows
  • Comprehensive testing framework (unit + IDD)

M8: Production Ready / 1.0 (End of Phase 15)

  • Performance optimized (bytecode VM, native compilation)
  • AI integration complete (structured edits, agent SDK)
  • Deployment tooling
  • Observability

Success Metrics

  • Time to First App: Hello World web API in < 30 minutes
  • Performance (Bytecode VM): Within 5x of Go for web workloads
  • Performance (Native): Within 2x of Go with Cranelift/LLVM backend
  • Safety: Zero contract violations reach production
  • AI Compatibility: 95%+ of AI-generated code compiles on first try
  • Developer Satisfaction: Tooling comparable to Go/Rust

Example: Complete Web Application

// main.tnt - A complete NTNT web application

import { Server, Request, Response } from "std/http"
import { Database } from "std/db/postgres"
import { Logger } from "std/log"

let log = Logger.new("api")
let db = Database.connect(env("DATABASE_URL"))

struct User {
    id: String,
    name: String,
    email: String
}

impl User {
    invariant self.name.len() > 0
    invariant self.email.contains("@")
}

intent "Retrieve a user by their unique ID" {
    fn get_user(req: Request) -> Response
        requires req.params.id.len() > 0
    {
        match db.query_one("SELECT * FROM users WHERE id = $1", [req.params.id]) {
            Ok(user) => Response.json(user),
            Err(_) => Response.not_found("User not found")
        }
    }
}

intent "Create a new user with validated data" {
    fn create_user(req: Request) -> Response
        requires req.body.name.len() > 0
        requires req.body.email.contains("@")
        ensures result.status == 201 || result.status >= 400
    {
        let user = User {
            id: uuid(),
            name: req.body.name,
            email: req.body.email
        }

        db.insert("users", user)?
        log.info("Created user", { id: user.id })

        Response.created(user)
    }
}

@requires_approval("api-change")
pub fn main() {
    let app = Server.new()
        .get("/users/{id}", get_user)
        .post("/users", create_user)
        .use(logging)
        .use(cors)

    log.info("Starting server on port 8080")
    app.listen(8080)
}

This roadmap is a living document updated as implementation progresses. Last updated: January 2026 (v0.3.8 — Active: Phase 7 Language Ergonomics & Documentation)