Warns when a git add/stash/checkout/restore/reset may sweep dirty files this session did not write (shared checkouts have several agents). A toast for the…

<img src="docs/static/img/ailang-logo.svg" alt="AILANG Logo" width="128" height="128">
<!-- EXAMPLES_STATUS_START -->
Example status <!-- EXAMPLES_STATUS_END -->
Third-party verification. AILANG is written autonomously by AI agents via its own coordinator. The badges above are independent static-analysis and supply-chain scores — not self-reported. The benchmark dashboard publishes the live correctness signal across current benchmark and model cohorts.
AILANG is a purely functional, effect-typed language designed as a deterministic execution substrate for AI-generated code. Every construct has deterministic semantics that can be reflected, verified, and serialized.
Documentation | Examples | Live Demos | Vision | Benchmarks
AILANG is designed to be used by AI coding agents. The AILANG Bootstrap packages the current language guidance, reusable skills, and local MCP tools for Claude Code and OpenAI Codex.
Install the AILANG CLI first:
curl -fsSL https://ailang.sunholo.com/install.sh | bash
ailang --version
The agent integrations call the local ailang executable, so it must be available on PATH.
/plugin marketplace add sunholo-data/ailang_bootstrap
/plugin install ailang@sunholo-data/ailang_bootstrap
AILANG Bootstrap also ships a Codex plugin manifest. Add its stable marketplace:
# Required by the plugin's local MCP server on first launch
node --version
npm --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, after adding the marketplace, launch codex, enter /plugins, and install ailang from the AILANG marketplace. The plugin can also be installed from the Plugins directory in the Codex desktop app. Codex plugins are not currently available in the IDE extension.
Codex reads this repository's AGENTS.md automatically, whether or not the plugin is installed. The plugin adds the reusable AILANG skills and MCP tools; AGENTS.md supplies repository-specific workflows and guardrails.
ailang executableailang_prompt, ailang_check, ailang_run, ailang_builtins, ailang_evalailang promptThe packaging is intentionally native to each host:
| Capability | Claude Code | Codex |
|---|---|---|
| Persistent guidance | CLAUDE.md | AGENTS.md |
| Reusable workflows | Plugin skills | Plugin skills |
| Local AILANG MCP tools | Yes | Yes |
| Host-native commands | Claude slash commands | Skills and MCP tools |
| AILANG Bootstrap hooks | Claude hooks | Not currently packaged |
Once installed, ask the agent to write AILANG code. All agents should run ailang prompt before authoring .ail files; it is the installed CLI's source for current syntax and idioms.
See ailang_bootstrap for details.
The installer detects your OS and architecture automatically. To pin a release:
curl -fsSL https://ailang.sunholo.com/install.sh | VERSION=v0.33.1 bash
# macOS (Apple Silicon)
curl -L https://github.com/sunholo-data/ailang/releases/latest/download/darwin.arm64.ailang.tar.gz | tar -xz
sudo mv ailang /usr/local/bin/
# macOS (Intel)
curl -L https://github.com/sunholo-data/ailang/releases/latest/download/darwin.x64.ailang.tar.gz | tar -xz
sudo mv ailang /usr/local/bin/
# Linux
curl -L https://github.com/sunholo-data/ailang/releases/latest/download/linux.x64.ailang.tar.gz | tar -xz
sudo mv ailang /usr/local/bin/
# From source
git clone https://github.com/sunholo-data/ailang.git
cd ailang && make install
# Verify
ailang --version
For complete setup instructions, see the Getting Started Guide.
module examples/hello_world
import std/io (println)
export func main() -> () ! {IO} {
println("Hello, World!")
}
ailang run --caps IO examples/hello_world.ail
# Output: Hello, World!
ailang repl
λ> 1 + 2
3 :: Int
λ> let double = \x. x * 2 in double(21)
42 :: Int
λ> :type \x. x + x
\x. x + x :: ∀α. Num α ⇒ α → α
λ> :quit
ailang lsp --stdio ships in the binary: diagnostics, hover types, go-to-def, references, document symbols. One-command VS Code install: ailang editor install vscode (guide)Learn more: Why AILANG? | No Loops Design | Go Interop | Stability Promise (1.x)
make install # Build and install
make test # Run all tests
make repl # Start REPL
make lint # Run linter
Guides:
ailang/
├── cmd/ailang/ # CLI
├── internal/ # Compiler (lexer, parser, types, eval, effects)
├── std/ # Standard library (std/io, std/fs, std/json, std/zip, std/xml, etc.)
├── examples/ # Example and reference programs
├── docs/ # Documentation website source
└── design_docs/ # Design documents
Apache 2.0 - See LICENSE
AILANG draws inspiration from Haskell, OCaml, Rust, and Idris/Agda.
For AI agents: Deterministic functional language with Hindley-Milner type inference, algebraic effects, and explicit effect tracking. Read AGENTS.md, then use ailang prompt for current language guidance.
hooks/register.ts 50 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { isSweepingGitOp, parsePorcelain, unownedDirty } from './core'
5
6// A visibility warning, not an authority check (M-CLAUDE-CODE-MODS C1): git
7// itself says what is dirty; this session's Edit/Write calls say what it
8// wrote. Bash writes are unknowable, and the warning says it is a heuristic.
9
10const own = atom({ plugin: 'unowned-dirty', key: 'own' } as const, [])
11
12/** Pure: absolute paths under `top` as repo-relative, as porcelain prints them. */
13export function relativeTo(top: string, paths: readonly string[]): Set<string> {
14 const prefix = top.endsWith('/') ? top : `${top}/`
15 return new Set(paths.filter(p => p.startsWith(prefix)).map(p => p.slice(prefix.length)))
16}
17
18async function sweepWarning($: EngineInterface, command: string): Promise<string | undefined> {
19 const cwd = await $.session.cwd()
20 const top = await $.process.run(['git', 'rev-parse', '--show-toplevel'], { cwd, timeoutMs: 10_000 })
21 if (top.exitCode !== 0) return undefined
22 const status = await $.process.run(['git', 'status', '--porcelain'], { cwd, timeoutMs: 10_000 })
23 if (status.exitCode !== 0) return undefined
24
25 const unowned = unownedDirty(parsePorcelain(status.stdout), relativeTo(top.stdout.trim(), await read($, own)))
26 if (unowned.length === 0) return undefined
27 const named = unowned.slice(0, 5).join(', ') + (unowned.length > 5 ? `, +${unowned.length - 5} more` : '')
28 return `[unowned-dirty heuristic] '${command.trim().slice(0, 60)}' may sweep ${unowned.length} dirty file(s) this session did not write: ${named}. Coordinate before stashing or committing others' in-flight work.`
29}
30
31export const register: Register = on => {
32 on('tool.call', async ($, e, next) => {
33 if (e.tool === 'Edit' || e.tool === 'Write') {
34 const ran = await next(e)
35 if (ran.deny === undefined && !ran.isError) {
36 const path = e.file_path.startsWith('/') ? e.file_path : `${await $.session.cwd()}/${e.file_path}`
37 await update($, own, list => (list.includes(path) ? list : [...list, path]))
38 }
39 return ran
40 }
41 if (e.tool !== 'Bash' || !isSweepingGitOp(e.command)) return next(e)
42
43 // Read the tree before the command changes it; never block it.
44 const warning = await sweepWarning($, e.command).catch(() => undefined)
45 if (warning) $.ui.toast(warning)
46 const ran = await next(e)
47 return warning && ran.deny === undefined ? { ...ran, context: [...(ran.context ?? []), warning] } : ran
48 })
49}
50hooks/core.ts 28 lines1// pi-core: .pi/extensions/unowned-dirty.ts DirtyFile parsePorcelain unownedDirty isSweepingGitOp
2// Generated by scripts/check_claude_mods_drift.sh --write from the pi source
3// above. Do not edit here: change pi, then regenerate.
4
5export interface DirtyFile {
6 status: string; // porcelain XY codes, e.g. " M", "M ", "??"
7 path: string;
8}
9
10/** Pure: parse `git status --porcelain` lines into (status, path). */
11export function parsePorcelain(out: string): DirtyFile[] {
12 return out
13 .split("\n")
14 .filter((l) => l.trim() !== "")
15 .map((l) => ({ status: l.slice(0, 2), path: l.slice(3).trim().replace(/^"|"$/g, "") }));
16}
17
18/** Pure: files dirty in the tree that this session did not write. */
19export function unownedDirty(all: DirtyFile[], ownFiles: Set<string>): string[] {
20 return all.map((f) => f.path).filter((p) => !ownFiles.has(p));
21}
22
23/** Pure: does this bash command perform a sweeping git operation? */
24export function isSweepingGitOp(command: string | undefined): boolean {
25 if (!command) return false;
26 return /\bgit (add|stash|checkout|restore|reset)\b/.test(command);
27}
28types/index.d.ts 9 lines1/** Absolute paths this session wrote through Edit or Write. */
2export type OwnFiles = string[]
3
4declare module 'claude-code' {
5 interface PluginState {
6 'unowned-dirty': { own: OwnFiles }
7 }
8}
9