Intent-Driven Development (IDD)
Intent-Driven Development (IDD) is a methodology where human requirements become executable specifications. You write what your software should do in natural language, the system verifies that your code actually does it.
At the heart of IDD is the Intent Assertion Language (IAL), a term rewriting engine that translates natural language assertions into executable tests.
More Than Acceptance Testing
IDD isn't just BDD or spec-driven development with different syntax. It spans from unit tests to integration tests to acceptance tests, all in one unified language. Here's a unit test example:
# math.intent
Feature: Math Utilities
id: feature.math_utils
description: "Core mathematical functions"
Scenario: Calculate discount correctly
When calculating discount
→ result matches expected
Scenario: Discount is deterministic
When calculating discount
→ discount is predictable
---
## Glossary Extensions (Unit Testing)
| Term | Means |
|------|-------|
| calculating discount | call: calculate_discount({price}, {percent}), source: lib/math.tnt, input: test_data.discount_examples |
| result matches expected | result is {expected} |
| discount is predictable | property: deterministic |
---
## Test Data
Test Cases: Discount Examples
id: test_data.discount_examples
| price | percent | expected |
| 100 | 10 | 90 |
| 50 | 25 | 37.5 |
| 200 | 0 | 200 |
This tests a pure function directly, with multiple input/output pairs and property checks. The call: keyword invokes the function, input: specifies the test data table, and property: deterministic verifies the function always returns the same result for the same inputs.
The same .intent file can mix unit tests with HTTP scenarios:
Scenario: Apply discount via API
When POST /api/cart/discount with {"percent": 10}
→ status 200
→ body contains "total"
One file, one language, all levels of testing.
The Core Idea
Instead of writing requirements in a document and tests in code, you write both in one place: the .intent file. This file is both human-readable documentation and machine-executable specification.
# server.intent
## Glossary
| Term | Means |
|------|-------|
| page loads successfully | status 200, returns HTML |
| returns HTML | header "Content-Type" contains "text/html" |
| they see "{text}" | body contains "{text}" |
---
Feature: User Greeting
id: feature.greeting
description: "Display a personalized greeting"
Scenario: Greet by name
When a user visits /?name=Alice
→ page loads successfully
→ they see "Hello, Alice"
Scenario: Default greeting
When a user visits /
→ page loads successfully
→ they see "Hello, World"
The Glossary: Your Domain Language
The glossary is where the magic happens. You define terms in natural language, and IAL resolves them into executable checks:
| Term | Means |
|------|-------|
| page loads successfully | status 200, returns HTML |
| returns HTML | header "Content-Type" contains "text/html" |
| they see "{text}" | body contains "{text}" |
When you write → page loads successfully in a scenario, IAL expands it:
page loads successfully→status 200, returns HTMLreturns HTML→header "Content-Type" contains "text/html"- Finally resolves to primitives:
Check(Equals, response.status, 200)andCheck(Contains, response.headers.content-type, "text/html")
Terms can reference other terms, building a hierarchy from natural language down to executable checks. This is term rewriting, the core mechanism of IAL.
Built-in Standard Terms
IAL includes a vocabulary of standard terms you can use without defining them in your glossary:
| Term | Meaning |
|---|---|
status 200 |
HTTP status equals 200 |
status 2xx |
Any success status (200-299) |
body contains "{text}" |
Response body includes text |
body not contains "{text}" |
Response body excludes text |
header "{name}" contains "{value}" |
Header check |
content-type is json |
JSON response shorthand |
response time < 500ms |
Performance assertion |
code is valid |
Passes lint checks |
Your glossary terms extend and build upon these standard terms, creating a domain-specific language for your project.
Linking Code to Intent
NTNT links your implementation to your intent file using @implements annotations:
// server.tnt
import { html } from "std/http/server"
// @implements: feature.greeting
fn home(req) {
let name = req.query_params["name"] ?? "World"
return html("<h1>Hello, {name}!</h1>")
}
get("/", home)
listen(8080)
The annotation // @implements: feature.greeting tells NTNT that this function implements the feature.greeting requirement from your intent file.
The Verification Workflow
Run ntnt intent check server.tnt and NTNT:
- Finds
server.intent(linked by filename) - Starts your server
- Executes each scenario's HTTP requests
- Resolves assertions through glossary → standard terms → primitives
- Reports pass/fail for every check
$ ntnt intent check server.tnt
🎯 Intent Check: server.intent
Feature: User Greeting
✓ Greet by name (2 checks)
✓ Default greeting (2 checks)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ All scenarios passed (4 checks)
Intent Commands
NTNT provides several commands for working with intent files:
# Verify implementation matches intent
ntnt intent check server.tnt
# Show which features have implementations
ntnt intent coverage server.tnt
# Generate code scaffolding from intent
ntnt intent init server.intent -o server.tnt
# Visual development with live tests
ntnt intent studio server.intent
Intent Studio: Visual Development
For the best experience, use Intent Studio:
ntnt intent studio server.intent
This opens a visual interface at http://127.0.0.1:3001 that shows:
- Health bar: Overall pass/fail percentage at a glance
- Feature cards: Expandable views of each feature and its scenarios
- Live test results: Pass/fail indicators update as you code
- Resolution chains: See how assertions expand from glossary to primitives
- Native hot-reload: Edit your code, changes apply on next request
Your app runs on port 8081 while Studio runs on port 3001. Edit your .tnt file, refresh your browser, and see tests re-run instantly.
IAL Primitives
At the bottom of every resolution chain are primitives, the actual executable operations:
- Http: Execute HTTP request, capture response
- Cli: Execute CLI command, capture output
- CodeQuality: Run lint/validation checks
- FunctionCall: Call an NTNT function (unit testing)
- PropertyCheck: Verify function properties (deterministic, idempotent)
- Check: Universal assertion (equals, contains, matches, etc.)
You never write primitives directly. You write natural language in your glossary and scenarios; IAL resolves them to primitives automatically.
Why This Matters
Traditional development has a gap between requirements and code. Requirements live in documents that become outdated. Tests verify behavior but don't explain intent. Code comments try to bridge the gap but are often ignored.
IDD closes this gap:
- Requirements are executable: They're not just documentation, they're tests
- Code is traceable: Every function links back to its requirement via
@implements - Changes are verifiable: Modify code, run check, know if you broke anything
- Natural language is preserved: Humans read "page loads successfully", machines execute the checks
- AI agents have clear targets: Tell an agent to implement
feature.greetingand it knows exactly what success looks like
Design by Contract Integration
IDD works alongside NTNT's contract system. While intent files define feature-level requirements, contracts define function-level specifications:
fn withdraw(amount: Int) -> Int
requires amount > 0
requires amount <= self.balance
ensures result >= 0
{
self.balance = self.balance - amount
return self.balance
}
In HTTP routes, contract violations automatically return proper error responses: failed preconditions return 400 Bad Request, failed postconditions return 500 Internal Server Error.
Getting Started
Ready to try IDD? Here's the workflow:
- Create a
.intentfile describing what you want to build - Define your domain language in the Glossary
- Write features with scenarios and assertions
- Generate scaffolding:
ntnt intent init app.intent -o app.tnt - Implement with
@implementsannotations - Verify:
ntnt intent check app.tnt - Iterate with Intent Studio:
ntnt intent studio app.intent
Check out the Learn page for installation instructions and a complete walkthrough.
