Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 61 additions & 92 deletions apps/site/content/docs/file-formats.mdx
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
---
title: File formats
description: The complete reference for .napl prompts, the .mapl reply, the emoji aliases, and the .napl/ state directory.
description: The complete reference for .napl prompts, the body directives, and the .napl/ records directory.
---

This page is the exhaustive reference for every file NAPL reads and writes: the `.napl`
prompt you author, the `.mapl` reply the toolchain generates, the emoji spellings of both,
and the `.napl/` directory that holds the derived state. For the narrative behind these,
see [Writing prompts that hold](/docs/writing-prompts).
This page is the exhaustive reference for every file napl reads and writes: the `.napl`
prompt you author, the directives inside its body, and the `.napl/` directory that holds
the records. For the narrative behind these, see
[Writing prompts that hold](/docs/writing-prompts).

## The `.napl` prompt

A prompt is YAML frontmatter (machine-facing metadata) followed by markdown prose (the
contract handed to the coding agent). Edit the example below and the validator runs as you
type. Break the YAML or misspell a field and the squiggle appears.
A prompt is YAML frontmatter (machine-facing metadata) followed by a markdown body (the
contract). It is found by its extension: a `.napl` file inside the crate that receives
its code is already in placement, and nothing registers it. Edit the example below and
the validator runs as you type. Break the YAML or misspell a field and the squiggle
appears.

<NaplExample
filename="auth/session.napl"
code={`---
module: auth/session
deps: [auth/tokens]
targets: [typescript]
module: auth_session
deps: [auth_tokens]
roots: [session]
targets: [rust]
tests:
- name: expired token rejected
given: { token: expired }
Expand All @@ -33,99 +36,65 @@ SESSION_EXPIRED, never silently renewed.

### Frontmatter fields

| Field | Type | Required | Description |
| --------- | ---------- | -------- | ---------------------------------------------------------------------------------------------------- |
| `module` | `string` | yes | The module's identity: a name, which may be path-like (`auth/session`). Not a file path. |
| `deps` | `string[]` | no | Dependency module names. Surfaced to the agent as `Declared dependencies: …` and enforced at gen. |
| `targets` | `string[]` | no | Target languages to generate for, e.g. `typescript`, `react`, `rust`. |
| `tests` | `Test[]` | no | `given`/`expect` cases as data. They gate every generation. |
| `crate` | `string` | no | Rust target only: modules sharing a `crate` value share one member crate with a toolchain-owned manifest. |
| Field | Type | Required | Description |
| --------- | ---------- | -------- | ------------------------------------------------------------------------------- |
| `module` | `string` | yes | The module's identity: a name, not a file path. The prompt's location declares placement. |
| `deps` | `string[]` | no | Dependency module names. The build graph and the commit wave order come from these. |
| `roots` | `string[]` | no | The public names the module's surface exports. |
| `targets` | `string[]` | no | Target languages, e.g. `rust`. |
| `tests` | `Test[]` | no | `given`/`expect` cases as data, pinned with the prompt. |

Each `tests` entry is `{ name, given, expect }`, all data. Behavior that can't be expressed
as a `given`/`expect` pair belongs in the prose contract, not the frontmatter.
Each `tests` entry is `{ name, given, expect }`, all data. Behavior that can't be
expressed as a `given`/`expect` pair belongs in the prose contract, not the frontmatter.

`deps` declares the _where_ of a dependency; the imperative to _use_ it rather than
reimplement it lives in the prose (see [load-bearing sentences](/docs/writing-prompts#deletable-sentences-and-load-bearing-sentences)).
## Body directives

## The `.mapl` reply
The body is markdown prose plus fenced directives. A fence opens with `:::` and a name
and closes with `:::` on its own line. An unknown directive name is a compile error,
never silently treated as prose.

`napl gen` writes a `.mapl` file per module: the machine layer's answer, regenerated on
every compile. Its top level is `{ module, target, entries }`; each entry records the prompt
lines it refers to and one of four kinds.
### `::: test`

```mapl title="greeting.mapl"
- promptLines: [12, 14]
kind: assumption
message: Return type left unspecified
reasoning: >
The prose does not name the return type. Chose a plain string.
```

### Entry fields

| Field | Type | Description |
| ------------- | ----------------- | -------------------------------------------------------------------- |
| `promptLines` | `LineRange` | The 1-based body line(s) the entry refers to (scalar `n` or `[a,b]`).|
| `kind` | see below | One of `ambiguity`, `assumption`, `no-op`, `note`. |
| `message` | `string` | Required, non-empty: the one-line summary. |
| `reasoning` | `string` | Optional prose reasoning (defaults empty). |
| `suggestion` | `string \| null` | Optional suggested rewording, chiefly for `ambiguity`. |

### Kinds and severities

The kind maps to an editor diagnostic severity: this is how ambiguity becomes a first-class
compile diagnostic:
A pinned case, equivalent to a frontmatter `tests` entry but living beside the prose it
pins. A duplicate case name across frontmatter and body is a compile error. A `name`
containing commas must be quoted.

| Kind | Severity | Meaning |
| ------------ | ------------- | ---------------------------------------------------------------------------------- |
| `ambiguity` | error | Your words were unclear. Red squiggle on the exact words, usually with a rewording.|
| `assumption` | warning | A decision your prose left open; the model chose one. |
| `no-op` | warning | The prompt changed but the code didn't: the entry must say why, or the gen fails. |
| `note` | information | Hover-visible reasoning about why the code is shaped this way. |

## Emoji aliases

Each side of the dialogue has a canonical extension and emoji aliases. Both spellings in a
pair are byte-identical to every tool (discovery, LSP, grammar, the VS Code extension):

| Side | Canonical | Alias | Voice |
| ------- | --------- | ------- | ---------------- |
| Human | `.napl` | `.🧑` | what you write |
| Machine | `.mapl` | `.🤖` | its margin notes |
```napl title="a pinned case"
::: test
name: "an expired token is rejected, never renewed"
given: { token: expired }
expect: { error: SESSION_EXPIRED }
:::
```

The human alias is a single code point on purpose. ZWJ sequences (a base
emoji joined to another with a zero-width joiner) are rejected so filenames stay
filesystem-safe and round-trip cleanly across tools and shells. Override the human alias in
`lock.json`:
### `::: docs`, `::: example`, `::: note`, `::: warning`

```json title=".napl/lock.json"
{
"model": "claude-sonnet-5",
"promptAliases": [".🧑"]
}
```
Documentation fences. They are prose about the module, excluded from the semantic body:
editing them never re-keys the module, and `napl commit` records such an edit as a
`docs-only` transition. The reference pages on this site, including
[the compiler's own](/docs/reference/napl), are rendered from exactly these fences in
the committed prompts. A `docs` fence carries markdown; an `example` fence carries a
titled code block; `note` and `warning` render as callouts.

Each override entry starts with `.`, carries a single code point after the dot, and may
not contain a ZWJ. **The mirror rule:** `napl gen` writes `.mapl` for a `.napl` prompt and
`.🤖` for an emoji-spelled one. It mirrors your choice. Reading accepts both spellings on
both sides.
## The `.napl/` records directory

## The `.napl/` directory
Committed by design: the durable record of every landing, and what lets `napl status`
and `napl fsck` verify the prompt/code connection in CI from an ordinary checkout.

Committed by design: the durable record of every gen, and what lets `napl status` verify the
prompt/code connection in CI without regenerating.
| Path | Contents |
| --------------------------- | -------------------------------------------------------------------------- |
| `store/HEAD` | The current head pointer. Marked `binary` in git so merges never write conflict markers into it. |
| `store/objects/pack/` | One content-addressed pack and its index. Every successful commit repacks, so the store a reader sees is always these two files plus `HEAD`. |
| `store/notes/` | Notes attached to recorded transitions. |
| `attribution/<module>.yaml` | Prompt-line to code-line claims. A record the toolchain writes; never hand-edit it. |
| `staging/` | Records staged by `napl add`, consumed by `napl commit`. Transient. |

| Path | Contents |
| --------------------------- | -------------------------------------------------------------------- |
| `src/<target>/` | Generated code, per target. Locked `0444` after tests pass. |
| `map.json` | Per-module attribution of generated files and locked content hashes. |
| `lock.json` | Pinned model id, backend, agent preset, alias overrides. |
| `ir/<module>.yaml` | Derived contracts: signatures, types, tests. Best-effort. |
| `attribution/<module>.yaml` | Prompt-line to code-line span mappings. A hard gate at gen. |
| `mapl/<module>.mapl` | The machine layer's reply. |
| `journal.jsonl` | Append-only gen history: prompt diff and per-file patch per gen. |
`napl init` writes the `.gitattributes` lines that keep the store quiet in diffs. Store
objects are never text: they carry `-text -diff`, because a text-normalizing clone on
Windows would corrupt them. Notes and attribution records keep their readable diffs,
because reading them is those files' purpose.

Build artifacts (`node_modules`, `dist`, lockfiles, `*.tsbuildinfo`, Rust `target/`) are
excluded from attribution and never locked.
Build artifacts (`node_modules`, `dist`, lockfiles, Rust `target/`) are not records and
never enter the store.

Next: [Running it for real](/docs/running) for what reads and writes these files.
64 changes: 34 additions & 30 deletions apps/site/content/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -1,66 +1,70 @@
---
title: What is NAPL
description: The prompt is the source of truth. A coding agent generates per-target code, attributed sentence by sentence and locked read-only.
description: The prompt and the code are both source. napl verifies the connection, records every change, and answers for every line.
---

NAPL asks one question: **what if the prompt _was_ the source code?**
NAPL asks one question: **what if the prompt _was_ source code?**

You don't write code. You write what you mean, in English, in a `.napl` file:
You write what you mean, in English, in a `.napl` file:

<NaplExample module="greeting" filename="greeting.napl" />

The example above is live. The prompt on the left is editable, with the CLI's own
validator squiggling errors as you type; the code on the right is what
`napl gen typescript` actually produced from it. Try deleting the `module:` line and
watch the validator object before any CLI ever runs.
validator squiggling errors as you type; the code on the right is the code that landed
beside it through `napl commit`. Try deleting the `module:` line and watch the validator
object before any CLI ever runs.

## What the toolchain does that a chat window doesn't

Anyone can ask a model for code. The problem is everything _after_: the chat scrolls
away, the code stays, and nobody remembers why any line exists. NAPL inverts that. When
you run `napl gen <target>`, a coding agent:
away, the code stays, and nobody remembers why any line exists. napl inverts that. Both
sides are the truth: the prompt is the contract, the code honors it, and the toolchain
proves and records the connection instead of trusting anyone's memory.

- **writes a real project** and iterates until the tests you declared pass: the tests
are data in the prompt's frontmatter, not an afterthought;
- **maps every sentence to the exact lines that implement it.** Hover a sentence in the
example above to see them. If the agent can't prove that mapping, the build _fails_:
attribution is a hard gate, not a nicety;
- **locks the result read-only.** Edit the generated code by hand and `napl status`
reports drift and exits non-zero, so your CI catches it.
- **One guarded door.** Code enters through `napl add` and `napl commit`, a transaction
that installs the files, records the transition, and refuses with a reason and the
exact next step when something is wrong. Nothing lands half-way.
- **Every sentence maps to lines.** Attribution connects prompt prose to the exact code
that implements it. Hover a sentence in the example above to see it. `napl blame`
answers the reverse direction: which transition wrote this line, and from which words.
- **Drift is visible, never silent.** Edit either side by hand and `napl status` reports
it and `napl diff` renders it. Drift is a legal state the diagnostics mark, and CI can
decide what a red diff means for your team.
- **Tests travel with the prompt.** Pinned cases are data in the prompt itself, realized
as real tests in the code beside it.

The English stays the source of truth. The code becomes a build artifact: inspectable,
tested, traceable, never hand-edited. To change behavior, you change the prompt.
The English stays the source of truth, and so does the code: two sides of one record.
To change behavior, change either side, and the toolchain tells you what moved.

## The compile is a dialogue
## Everything it knows, it answers

Gen doesn't only emit code. The model writes back into the margin of your prompt (a
`.mapl` file per module) flagging where your words were unclear (**ambiguity**, a red
squiggle), what it decided that you left open (**assumption**), and why the code came
out as it did (**note**). A prompt change that produces no code change must say why, or
the gen fails: silence is forbidden. Ambiguity becomes a first-class compile diagnostic,
the way a type error is.
The store behind the door is content-addressed and queryable. `napl q` takes jq-style
expressions over everything recorded: modules, transitions, attribution, history.
`napl log` reads the story back. `napl serve` exposes all of it over local HTTP for
editors and tools. This site's reference pages are rendered from that same store: the
[compiler documents itself](/docs/reference/napl).

<Cards>
<Card
title="Quickstart"
href="/docs/quickstart"
description="Install, write a prompt, generate tested code: five commands."
description="Install, write one prompt, land verified code through the guarded door."
/>
<Card
title="Writing prompts that hold"
href="/docs/writing-prompts"
description="Spec vs prose, tests as a gate, and which sentences are load-bearing."
description="Spec vs prose, tests as data, and which sentences are load-bearing."
/>
<Card
title="Source"
href="/source"
description="The napl toolchain's own Rust source, generated from prompts all the way to a fixed point."
title="Reference"
href="/docs/reference"
description="Every module of the toolchain, documented from its own committed prompts."
/>
</Cards>

<Callout>
Status: wild experiment, a bet that English can be a real programming language if the
toolchain enforces the discipline compilers used to. The voice here is honest and
experimental; expect sharp edges, and see [Running it for real](/docs/running) for
what the coding agent actually needs on your machine.
the day-to-day workflow.
</Callout>
Loading
Loading