Runs the repo's CI gates (gofmt on tracked Go roots, make lint, make check-file-sizes when defined) before `git push` / `gh pr create|merge`, and denies the…

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 53 lines1import type { EngineInterface, Register } from 'claude-code'
2
3import { goRoots, isPushCommand, makeTargetDefined, skipRequested } from './core'
4
5// Same gates and same refusals as .pi/extensions/prepush-gate.ts. The escape
6// hatch is read from Claude Code's own launch environment ($.env), so an agent
7// writing `AILANG_SKIP_PREPUSH=1 git push` does not open it. Not a security
8// boundary (M-CLAUDE-CODE-MODS C1): it saves a red CI round-trip, nothing more.
9
10async function gateFailure($: EngineInterface): Promise<string | undefined> {
11 if (skipRequested({ AILANG_SKIP_PREPUSH: await $.env.get('AILANG_SKIP_PREPUSH') })) return undefined
12 const cwd = await $.session.cwd()
13 const run = (argv: string[], timeoutMs: number) => $.process.run(argv, { cwd, timeoutMs })
14
15 // A gate for a toolchain the repo does not use has nothing to check.
16 const tracked = await run(['git', 'ls-files', 'cmd/*.go', 'internal/*.go', 'cmd/**/*.go', 'internal/**/*.go'], 15_000)
17 const roots = goRoots(tracked.stdout)
18 if (roots.length === 0) return undefined
19
20 const gofmt = await run(['gofmt', '-l', ...roots.map(r => `${r}/`)], 30_000)
21 const unformatted = gofmt.stdout.split('\n').filter(l => l.trim() !== '')
22 if (unformatted.length > 0) {
23 return `gofmt: ${unformatted.length} unformatted Go file(s): ${unformatted.slice(0, 3).join(', ')}. Run gofmt -w on them, then push again.`
24 }
25
26 const defined = async (target: string) => {
27 const probe = await run(['make', '-n', target], 15_000)
28 return makeTargetDefined(target, probe.exitCode, `${probe.stderr}\n${probe.stdout}`)
29 }
30 if (await defined('lint')) {
31 const lint = await run(['make', 'lint'], 180_000)
32 if (lint.exitCode !== 0) {
33 const tail = (lint.stderr || lint.stdout).split('\n').filter(Boolean).slice(-6).join('\n')
34 return `prepush gate failed (lint):\n${tail.slice(0, 2000)}\n\nFix locally, then push again.`
35 }
36 }
37 if (await defined('check-file-sizes')) {
38 const sizes = await run(['make', 'check-file-sizes'], 15_000)
39 if (sizes.exitCode !== 0) return `prepush gate failed (file sizes >800 lines):\n${sizes.stdout.slice(-400)}`
40 }
41 return undefined
42}
43
44export const register: Register = on => {
45 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
46 if (!isPushCommand(e.command)) return next(e)
47
48 $.ui.status('prepush gate: running CI gates…')
49 const failure = await gateFailure($).finally(() => $.ui.status(undefined))
50 return failure ? { deny: `${$.plugin.name}: ${failure}` } : next(e)
51 })
52}
53hooks/core.ts 49 lines1// pi-core: .pi/extensions/prepush-gate.ts GateStep gateSteps goRoots makeTargetDefined skipRequested isPushCommand
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 GateStep {
6 name: string;
7 cmd: string;
8 args: string[];
9 timeoutMs: number;
10}
11
12/** Pure: the prepush gate chain for a Go repo (ailang's gates, fast subset). */
13export function gateSteps(): { name: string; cmd: string; args: string[]; timeoutMs: number }[] {
14 return [
15 { name: "gofmt", cmd: "gofmt", args: ["-l", "cmd", "internal"], timeoutMs: 15_000 },
16 { name: "lint", cmd: "make", args: ["lint"], timeoutMs: 180_000 },
17 { name: "file-sizes", cmd: "make", args: ["check-file-sizes"], timeoutMs: 15_000 },
18 ];
19}
20
21/** Pure: the Go source roots (of cmd/, internal/) that `git ls-files` output touches. */
22export function goRoots(lsFilesStdout: string): string[] {
23 const files = lsFilesStdout.split("\n").filter((l) => l.trim() !== "");
24 return ["cmd", "internal"].filter((root) => files.some((f) => f.startsWith(`${root}/`)));
25}
26
27/**
28 * Pure: given `make -n <target>`'s result, does the repo define the target?
29 * Only the two "it isn't there" shapes count as absent; any other failure
30 * means the target exists and is broken, so the gate still runs (and blocks).
31 */
32export function makeTargetDefined(target: string, code: number, output: string): boolean {
33 if (code === 0) return true;
34 if (/No targets specified and no makefile found/i.test(output)) return false;
35 const noRule = new RegExp(`No rule to make target [\`'"‘]${target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"’]`);
36 return !noRule.test(output);
37}
38
39/** Pure: is the operator-level escape hatch set in pi's own environment? */
40export function skipRequested(env: Record<string, string | undefined>): boolean {
41 return env.AILANG_SKIP_PREPUSH === "1";
42}
43
44/** Pure: does this bash command push to a remote? */
45export function isPushCommand(command: string | undefined): boolean {
46 if (!command) return false;
47 return /\bgit push\b/.test(command) || /\bgh pr (create|merge)\b/.test(command);
48}
49