After Claude edits or writes a .ail file, runs `ailang check` from the project root and adds any errors to what the model reads after the tool result, so it…

Quick start package for using AILANG with Claude Code and OpenAI Codex.
AILANG is a deterministic programming language designed for AI code synthesis and reasoning.
Install the AILANG CLI before installing either agent integration:
curl -fsSL https://ailang.sunholo.com/install.sh | bash
ailang --version
The plugins invoke the local ailang executable, so it must be available on PATH.
# Step 1: Add the marketplace
/plugin marketplace add sunholo-data/ailang_bootstrap
# Step 2: Install the plugin
/plugin install ailang@sunholo-data/ailang_bootstrap
Or clone and add locally:
git clone https://github.com/sunholo-data/ailang_bootstrap
/plugin marketplace add ./ailang_bootstrap
/plugin install ailang@ailang_bootstrap
Codex requires Node.js 18 or newer for the local MCP server:
node --version
codex plugin marketplace add sunholo-data/ailang_bootstrap --ref stable
codex plugin add ailang@ailang-marketplace
codex plugin list
Start a new Codex session after installation. Alternatively, add the marketplace with the CLI, then install AILANG through /plugins or the Codex desktop Plugins directory.
Codex loads the shared skills and five local MCP tools: ailang_prompt, ailang_check, ailang_run, ailang_builtins, and ailang_eval.
Download a platform-specific release from GitHub Releases:
| Platform | Architecture | Asset |
|---|---|---|
| macOS | Apple Silicon | darwin.arm64.ailang-bootstrap.tar.gz |
| macOS | Intel | darwin.x64.ailang-bootstrap.tar.gz |
| Linux | x64 | linux.x64.ailang-bootstrap.tar.gz |
| Windows | x64 | win32.x64.ailang-bootstrap.zip |
Each release includes the AILANG binary pre-bundled.
| Skill | Purpose |
|---|---|
| ailang | Write, run, and develop with AILANG |
| ailang-inbox | Cross-agent messaging with semantic search, deduplication, and GitHub sync |
| Command | Purpose |
|---|---|
/ailang-prompt | Load syntax teaching prompt (do this first!) |
/ailang-run <file> | Run an AILANG program |
/ailang-check <file> | Type-check without running |
/ailang-new <name> [template] | Create new program from template |
/ailang-repl | Start interactive REPL |
/ailang-builtins [search] | List builtin functions |
/ailang-editor <editor> | Install syntax highlighting |
| Hook | Event | Purpose | |||
|---|---|---|---|---|---|
session_start.sh | SessionStart | Inbox check + brain context | |||
brain_session.sh | SessionStart | Inject relevant brain knowledge | |||
git_guard.sh | PreToolUse(Bash) | Block destructive git ops | |||
microrag_context.sh | PreToolUse(Edit\ | Write\ | Read\ | MultiEdit) | JIT μRAG knowledge injection — surfaces AILANG syntax/builtin docs at tool-call time |
microrag_lint.sh | PostToolUse(Edit\ | Write\ | MultiEdit) | First-use builtin nudge for *.ail edits | |
brain_resolution.sh | PostToolUse(Bash) | Capture command resolutions | |||
observatory_hook.sh | All events | OTEL telemetry for AILANG Observatory |
Disable μRAG: export AILANG_MICRORAG_ENABLED=0. Requires ailang and jq on PATH; degrades silently if either is missing.
μRAG brain corpus auto-bootstrap: install.sh runs ailang micro-rag init && ailang micro-rag bootstrap --scope user --no-embed after the binary lands. This populates the brain DB at ~/.ailang/state/brain.db with the ailang-syntax (~50 frames) and ailang-builtins (~280 frames) namespaces using only resources embedded in the binary — no source repo required. Works on Windows / minimal Docker images / anywhere awk and python3 are absent.
Re-run manually after install.sh to upgrade the corpus to embedding-backed retrieval (requires Ollama):
ailang micro-rag bootstrap --scope user # add embeddings on top of SimHash/FTS
ailang micro-rag bootstrap --scope user --reset # full rebuild (use after AILANG version bump)
A live pane that shows what you're writing as AILANG sees it. After every Edit/Write of a .ail file it runs ailang iface and ailang check and draws the module's functions, their types and effect rows (pure or !{IO,FS}), plus any type errors — keeping the last good signatures on screen beside a broken edit. The status line carries a one-line summary.
/plugin install ailang-lens@ailang-marketplace
/reload-plugins
/ail-lens [file.ail] # open the pane, or analyse any file into it
Mods are an early-access Claude Code feature; where they are not enabled the plugin installs but does nothing. Requires ailang on PATH.
1. Local stdio MCP (auto-installed by this plugin) — wraps the local AILANG CLI for actually running/checking code:
ailang_check - Type-check filesailang_run - Run programsailang_prompt - Get teaching prompt (embedded copy)ailang_builtins - List builtinsailang_eval - Evaluate an expression in the non-interactive REPL2. Remote HTTP MCP at mcp.ailang.sunholo.com — version-locked live docs, stdlib, examples, design docs, benchmarks. Available to any agent harness that supports remote MCP (Claude Desktop, Cursor, Cline, Continue, Claude Code).
{
"mcpServers": {
"ailang-docs": {
"url": "https://mcp.ailang.sunholo.com/mcp/",
"transport": "streamable-http"
}
}
}
Tools include prompt_get, stdlib_modules, stdlib_search, examples_for_concept, limitations_list, effects_catalog, and submit_feedback (for filing AILANG bugs from inside an agent session). Use these to avoid stale embedded knowledge — the snapshot is rebuilt and redeployed on every AILANG release.
See docs/guides/agent-mcp for the full tool catalog.
Once installed, you can:
# CRITICAL: Load syntax before writing code
ailang prompt
# Run AILANG code
ailang run --caps IO --entry main program.ail
# Start interactive REPL
ailang repl
# Type-check without running
ailang check program.ail
module myproject/hello
export func main() -> () ! {IO} {
print("Hello, AILANG!")
}
Run with:
ailang run --caps IO --entry main hello.ail
func - NOT fn, function, or deflet x = 1; let y = 2; x + y=> - NOT : or ->print expects string - Use print(show(42)) for numbersailang_bootstrap/
├── .claude-plugin/
│ ├── plugin.json # Claude Code plugin manifest
│ └── marketplace.json # Skills marketplace
├── .codex-plugin/
│ └── plugin.json # Codex plugin manifest
├── .mcp.json # Codex MCP configuration
├── .claude/commands/ # Slash commands
│ ├── ailang-run.md
│ ├── ailang-check.md
│ ├── ailang-new.md
│ └── ...
├── mcp-server/ # MCP server for AILANG tools
│ ├── server.js
│ └── package.json
├── skills/
│ ├── ailang/ # Main AILANG skill
│ │ ├── SKILL.md
│ │ └── resources/
│ └── ailang-inbox/ # Agent messaging skill
│ └── SKILL.md
├── bin/ # AILANG binary (in releases)
└── .github/workflows/
└── release.yml # Platform-specific release builds
MIT License - see LICENSE
hooks/register.ts 86 lines1import type { EngineInterface, Register } from 'claude-code'
2
3// The model's side of the lens: errors go into the tool result's `context`,
4// which the model reads after the result and the person never sees. A passing
5// check adds nothing. Advisory only (M-CLAUDE-CODE-MODS C1/C2).
6
7const MAX_ERRORS = 5
8
9/** Pure: "type error in m (decl 0): ... at [... at m.ail:3:8]: cannot unify ..." → "3:8 cannot unify ..." */
10export function shortError(message: string): string {
11 const where = message.match(/\.ail:(\d+:\d+)/)?.[1]
12 const tail = message.includes(']: ')
13 ? message.slice(message.lastIndexOf(']: ') + 3)
14 : message.replace(/^type error in \S+ \(decl \d+\):\s*/, '')
15 return (where ? `${where} ${tail}` : message).split('\n')[0]!.trim()
16}
17
18/** Pure: the context line for a check report, or undefined when it passed. */
19export function contextFor(rel: string, stdout: string, exitCode: number): string | undefined {
20 const at = stdout.indexOf('{')
21 let report: { passed?: boolean; errors?: { message?: string }[] } | undefined
22 try {
23 report = at < 0 ? undefined : JSON.parse(stdout.slice(at))
24 } catch {
25 report = undefined
26 }
27 if (exitCode === 0 && report?.passed !== false) return undefined
28
29 const errors = (report?.errors ?? []).map(err => shortError(err.message ?? 'error'))
30 if (errors.length === 0) return `ailang check ${rel}: ✗ failed (exit ${exitCode}), no structured errors`
31 const shown = errors.slice(0, MAX_ERRORS).map(err => ` ${err}`)
32 const more = errors.length > MAX_ERRORS ? [` … ${errors.length - MAX_ERRORS} more`] : []
33 return [`ailang check ${rel}: ✗ ${errors.length} error${errors.length === 1 ? '' : 's'}`, ...shown, ...more].join('\n')
34}
35
36async function exists($: EngineInterface, path: string): Promise<boolean> {
37 try {
38 await $.fs.stat(path)
39 return true
40 } catch {
41 return false
42 }
43}
44
45// ailang names a module by its path from the working directory (MOD010), so
46// run it from the project root: the nearest ailang.toml, else .git, else the
47// file's own folder.
48async function projectRoot($: EngineInterface, file: string): Promise<string> {
49 const dirs: string[] = []
50 for (let dir = file.slice(0, file.lastIndexOf('/')); dir; dir = dir.slice(0, dir.lastIndexOf('/'))) {
51 dirs.push(dir)
52 }
53 for (const marker of ['ailang.toml', '.git']) {
54 for (const dir of dirs) {
55 if (await exists($, `${dir}/${marker}`)) return dir
56 }
57 }
58 return dirs[0] ?? '.'
59}
60
61async function check($: EngineInterface, path: string, isFormatting: boolean): Promise<string | undefined> {
62 const file = path.startsWith('/') ? path : `${await $.session.cwd()}/${path}`
63 const cwd = await projectRoot($, file)
64 const rel = file.slice(cwd.length + 1)
65 try {
66 if (isFormatting) await $.process.run(['ailang', 'fmt', '--write', rel], { cwd, timeoutMs: 20_000 })
67 const run = await $.process.run(['ailang', 'check', '--json', '--quiet', rel], { cwd, timeoutMs: 60_000 })
68 return contextFor(rel, run.stdout, run.exitCode)
69 } catch (err) {
70 return `ailang check ${rel}: did not finish (${String(err).slice(0, 80)})`
71 }
72}
73
74export const register: Register = (on, options) => {
75 const isFormatting = options.formatOnEdit === true
76
77 on('tool.call', async ($, e, next) => {
78 const ran = await next(e)
79 const path = e.tool === 'Edit' || e.tool === 'Write' ? e.file_path : undefined
80 if (!path?.endsWith('.ail') || ran.deny !== undefined || ran.isError) return ran
81
82 const line = await check($, path, isFormatting)
83 return line ? { ...ran, context: [...(ran.context ?? []), line] } : ran
84 })
85}
86