SLOPSHOPPER

ailang-check-on-edit

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…

newguardprocess
A shopper browsing a rack in a slop shop
README

AILANG Bootstrap

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.

Installation

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.

Claude Code

# 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

OpenAI Codex

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.

Manual Installation

Download a platform-specific release from GitHub Releases:

PlatformArchitectureAsset
macOSApple Silicondarwin.arm64.ailang-bootstrap.tar.gz
macOSInteldarwin.x64.ailang-bootstrap.tar.gz
Linuxx64linux.x64.ailang-bootstrap.tar.gz
Windowsx64win32.x64.ailang-bootstrap.zip

Each release includes the AILANG binary pre-bundled.

What's Included

Skills

SkillPurpose
ailangWrite, run, and develop with AILANG
ailang-inboxCross-agent messaging with semantic search, deduplication, and GitHub sync

Slash Commands (Claude Code)

CommandPurpose
/ailang-promptLoad 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-replStart interactive REPL
/ailang-builtins [search]List builtin functions
/ailang-editor <editor>Install syntax highlighting

Hooks (Claude Code)

HookEventPurpose
session_start.shSessionStartInbox check + brain context
brain_session.shSessionStartInject relevant brain knowledge
git_guard.shPreToolUse(Bash)Block destructive git ops
microrag_context.shPreToolUse(Edit\Write\Read\MultiEdit)JIT μRAG knowledge injection — surfaces AILANG syntax/builtin docs at tool-call time
microrag_lint.shPostToolUse(Edit\Write\MultiEdit)First-use builtin nudge for *.ail edits
brain_resolution.shPostToolUse(Bash)Capture command resolutions
observatory_hook.shAll eventsOTEL 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)

AILANG lens (Claude Code mod, optional)

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.

MCP Servers (TWO complementary servers)

1. Local stdio MCP (auto-installed by this plugin) — wraps the local AILANG CLI for actually running/checking code:

  • ailang_check - Type-check files
  • ailang_run - Run programs
  • ailang_prompt - Get teaching prompt (embedded copy)
  • ailang_builtins - List builtins
  • ailang_eval - Evaluate an expression in the non-interactive REPL

2. 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.

Quick Start

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

Example AILANG Program

module myproject/hello

export func main() -> () ! {IO} {
  print("Hello, AILANG!")
}

Run with:

ailang run --caps IO --entry main hello.ail

Key Syntax Rules

  1. Use func - NOT fn, function, or def
  2. Semicolons between statements - let x = 1; let y = 2; x + y
  3. Pattern matching uses => - NOT : or ->
  4. No loops - Use recursion instead
  5. print expects string - Use print(show(42)) for numbers

Documentation

Repository Structure

ailang_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

License

MIT License - see LICENSE

Source 1 files
hooks/register.ts 86 lines
1import 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