Skip to content
@aoughwl

aoughwl

an information company; a next-gen self-hosted platform for things

‎‎ ‎ ‎‎ ‎‎ ‎‎ ‎ ‎‎ ‎‎ ‎‎ ‎ ‎‎ ‎ ‎‎ ‎‎‎‎‎ ‎‎ ‎ ‎‎ ‎‎ ‎‎ ‎ ‎‎ ‎‎ ‎‎ ‎ ‎‎ ‎ ‎‎ ‎‎aoughwl

aowl a from-scratch Nimony clone

.nim / .aowl → aowlparse → aowlsem* → aowlhexer* → { aowlc → C · aowljs → JS · aowli → interpret }

A rewrite of the entire Nimony ecosystem

  • parser, semantic checker, lowering
  • interpreter and runtime
  • standard library
  • LSP, MCP, vscode, claude plugin
  • code fix suggestions, formatter, obfuscator
  • and much, much, more...

translates to:

  • C
  • faithful and native JavaScript & TypeScript
  • Python



— all self-hosted, written in itself


Between the frontend stages we use AIF, which is NIF, byte for byte, so any part you find here is compatabile with Nim or Nimony.

The big projects are private for now, but the docs are public and anything private is yours if you just ask — message me on Discord (timbuktu_guy) and I'll add you, no hoops. The playground moves onto the new sem + hexing shortly.

Project Docs
aowl toolchainaowlparse · aowlsem · aowlhexer · aowlc · aowljs · aowli AIF ≡ NIF
aowluprustup for the stack: installs / versions / selects the toolchain (variants · profiles) repo ↗
aowlabi — the stack's shared value-representation / ABI: one canonical per-type layout + marshal matrix, read by aowlc · aowljs · aowli instead of each keeping its own copy docs · repo ↗
aowlcode — Claude Code plugin + MCP server (trace/debug, /land, cheap-applier fan-out) docs
aowllsp — Language Server + VSCode extension docs
aowli-release — public, binary-only distribution of aowli (the aowli source is private); runs a nimony program's typed NIF; prebuilt aowli-interp + aowli-dbg, GitHub Release v0.3.1, hardened (obfnif IR + licence gate + stripped), SHA256 + VirusTotal per binary docs
net stacktcp·net·tls·http·compress·serve·ws·requests — TLS 1.3, HTTP/1.1 · 2 · 3, QUIC + WebTransport, Autobahn WebSocket, single-thread async reactor docs
web / html / css — typed HTML5 + MDN CSS engine + DSL docs
aowljs / aowlts / aowlpy / aowlhl — idiomatic JS/TS/PY backends + shared HL-IR ts · py · hl

‎‎· and more, here




Daily Blog


027 2026-08-02 - Sunday, August 2nd 2026

aowlsem — 29 commits. Compile-time evaluation beyond const initialisers, then bounded by a capability policy. Four sites treated "cannot compute" as a definite answer; three miscompiled silently.

  • when big(): took the wrong branch, no diagnostic — evalCond returns unknown, an unknown elif is not taken, else is unconditional. Three sites: semWhen module-level, semWhen in-proc, whenTakenBody (type bodies — wrong field, so a wrong type). All now run the condition.
  • Enum explicit value kept the auto-increment ordinal unless a bare IntLit: b = v() → 2, nimony 7. foldRawArrayDim handles 1 + 4; c = K * 10 needs ceEvalInt(force = true) — the contains-a-call guard assumes cheaper folding, false here because the prescan precedes constVals.
  • A const inside a proc forked aowlsem until killed. ceDeclaresName matched only top-level (const …), so the copy never stopped, the enclosing proc came in whole still holding it, each child regenerated the evaluator; ceBudget is per-process. Recursive ceDeclaresName restored the stop, --ceDepth: (max 4) backstops.
  • array[sz(), int] emitted (array (i 64) (call sz)). ceTypeNeedsEval keeps a var/let whose own type awaits evaluation out of its evaluator — varT is in ceIsDecl, so it re-entered.
  • Const evaluator had one executor, macro plugins two; --macros:interp|compiled now drives both. Compiled path exposed: std/writenif import only in .p.deps.nif (aowlsem m reads deps, nimony s the body) → undeclared identifier: 'setup'; -o:<nimcache>/<base> collided with nimony's per-module directory.
  • Aggregate consts fold: array[N,int]/array[N,float] unrolled, seq[int] looped to len, all-int objects by field name. Value bound to a let so the initialiser runs once; caller rebuilds (aconstr …)/(oconstr …). Wrong count fails rather than folding a partial.
  • Inferred const type named the bare generic: const S = firstN(3)(at seq (i 64)), the same call under letseq.0.I·.. semConst lacked semLetVar's resolution; the seq materialisation keys off it, so folding was off too.
  • writeNifInt(<a seq>) semchecked clean — nimony: expected: int64 but got: seq. reliable declines any pair with a collection either side; mirror of containerParam added.
  • Earlier: a copied instance resolves an ochoice callee; an assignment spells its ref upcast; a range is iterable whatever its bounds resolved to.
  • Compile-time code runs under aowli's policy. mpRun and ceRunInterp pass --allow: --allow-path:<nifcache>=fs.read,fs.write,fs.meta — a plugin must read its input NIF and write its answer, and gets nothing else. --ctfe-allow:PATH grants one file; exit 77 becomes STOPPED by the compile-time policy; --ctfe-policy:off is the escape hatch.
  • A granted read is recorded, not just permitted: <out>.s.nif.ctfe-reads carries read<TAB>path<TAB>hash<TAB>size, and aowlsem ctfe-check <out.s.nif> exits 0 current / 1 stale (naming the changed file) / 2 no record — an exit code, so nifmake, aowltest and aowlmony need not parse the format.
  • --lens:<out.lens.nif> publishes what the checker resolved. (d …) declarations with position, type and signature; (u …) occurrences naming the symbol they bound; (t …) object/enum member tables with inheritance depth and visibility. Plain NIF.
  • Positions are recorded during checking, not read back. The .s.nif carries one only where a subtree was copied verbatim, so every minted symbol has none; tests/diff.sh canonicalises line info away, so nothing measured that gap. Seams: define, lensAt, lensUse.
  • (u …) carries owner and role, (d …) carries recv, so call edges and UFCS candidates are index filters, not tree walks. owner needed a routine stack semProc now pushes; recv came from Sym.paramTypes.

Gates. diff 685/685 → 701/701, check 400/400 → 401/401. New tests/consteval.sh 18/18 in all.sh: both executors match the oracle and each other, and each actually evaluated — proved by the serialized value file, since the shape folds otherwise make a no-eval run look green. nofp 35/35, diag 175/175, explain 93/93, e2e 6/6. New tests/ctfe.sh 7/7: an ungranted read traps and folds nothing, the same read granted folds, the hash moves when the file does, ctfe-check reads stale on the outdated compile and current on the fresh one, and the digest aowli recorded equals what aowltest --ctfe-hash computes — three copies of FNV-1a/64 with nothing else asserting they agree.

Cost. In-proc when nests to --ceDepth: 1 condition 20s, 4 → 32s, no per-condition branching (ctfe_when_call_multi.nim). whenTakenBody is in the prescan over every module; plain corpus file 1.78s → 1.82s — when defined(…)/x is T fold in evalCond.

Standing. scanFeatures has the same shape but only gates feature scanning. Upstream rejects static:, of v():, generic-routine const calls. selectedWhenBody can't call the evaluator (non-var context) but no longer disagrees with semWhen. New tests/lens.sh 16/16; corpus 705/705 on the uncolored-async branch with the index build.

aowllens — 2 commits. Reads aowlsem's index instead of reconstructing it from the tree.

  • typeat could not answer on an aowlsem artifact — it reads positions off emitted tokens, which aowlsem mints without them. lens.nim parses the sidecar; the occurrence at a position already names its symbol, so shadowing is the checker's answer, not a guess.
  • members takes fields from the index (inheritance already flattened, per-member visibility) and UFCS candidates by recv; calls reads edges from call-role records paired with owner, carrying the call site.
  • Fallback is per-answer, not per-run: no sidecar, or nothing in it for this question, and the existing walk answers exactly as before.

Gates. New tests/lens.sh 5/5, including the negative control — hide the sidecar and 7:29 goes back to {}. difftest.sh 5/5 and newcmds.sh 15/15 unchanged.

Standing. Nothing writes the sidecar in a normal build: nimony's driver invokes nimsem, aowllsp drives nimony check. Two implementations now answer the same questions with no differential between them.

aowlmony — 3 commits. verify diffs native against interpreted off one front end. Its first two findings were both artefacts of which binary ran, not backend defects.

  • aowlmony verify added. On a mismatch it re-runs the interpreted leg under aowli --trace, rebuilds stdout from the write(stdout, …) args, and names the op owning the first divergent byte. Default --native:nimony reuses the binary nimony c already linked at <nimcache>/<mainHash>/<srcStem>.
  • Reported s[2..5]"a" as an aowli defect. It was a stale install. The registry resolves interp to ~/.aowl/bin/aowli-interp (07-26), shadowing a fixed ~/aowli/bin (08-02); same .s.aif, different answer. Every verdict now names both realizers with build dates, and newerBuildThan() prints the newer build plus the AOWLMONY_NIFI= re-run.
  • Exit 1 meant both "backends disagree" and "compile failed", so a shared nimcache_static link race read as a divergence. COMPILE_FAIL_CODE=2 puts it with the could-not-run cases; 1 is now that one claim.
  • locateOp walked ancestor frames only, so a top-level echowrite(stdout, …) recorded at system's line, no user frame above it — had no location. Falls back to the last op run at a line in the entry module, a preceding sibling.

Gates. npm test 25/25 → 41/41, twice clean. The slice case asserted a stale-binary artefact and is gone; the --native:aowlc case asserts the invariant (never exit 1) since aowlc gained multi-module linking mid-session.

Standing. Genuine: 7 div 0 returned 0 and exit 0, fixed in aowli, which now raises division by zero. Native SIGFPEs and loses buffered stdout, so it stays an expected divergence, not a match.

aowltest — new repo, 3 commits. Test results keyed by transitive input hash; an unchanged closure is never re-run, and a compile-time read counts as an input.

  • Key is sha1 of a sorted manifest: dep <path> <sha1> per transitive local import, ext <name> for unresolved stdlib specs, gdep for --dep globals, the command line, --salt. Entry present ⇒ skip.

  • Content-hashed, never mtime. Restoring bytes restores the key, so a branch switch re-hits. --explain diffs a miss against last/<sha1(testpath)> and names the input that moved.

  • isFile as "try open" called every directory a file — glibc fopen() succeeds on directories, so the test root read as one test; 19 of 34 assertions failed at once. std/private/oscommons.fileExists stats; std/files resolves to Nim 2's lib, not nimony's.

  • Import scan is lexicalstd/[a,b], from … import, block and comma continuation, include; no when evaluation, so it over-approximates: costs a re-run, never a wrong skip.

  • A compile-time read is an input no static scan can find. --ctfe-dir:DIR merges the *.ctfe-reads aowlsem wrote into disc/<key> after a run; a later hit on the same static key re-hashes each one first, so a moved schema is a miss with identical source bytes. Off unless asked — a wrong guess at the sidecar location would silently skip a changed test.

Gates. tests/run.sh 35/35 → 41/41 over the cache decision itself: editing lib/base.nim re-runs its two dependents and leaves the third cached at 33.3%; restoring the bytes returns 100%. The six CTFE cases carry the control that matters — without --ctfe-dir the same moved schema is invisible and the run hits, so the re-run is attributable to the record.

aowlrepl — new repo, 4 commits. A nimony REPL on aowli. State persists because the session is one module, re-run from the top on every cell.

  • Imports hoisted, everything else in entry order, then nimony c --nimcache:<dir> and aowli-interp on the main .s.nif — stable across cells because its name hashes the module path. Cold 2.15s, warm 0.19s, run 1ms; only the stdout suffix the previous run did not produce is printed.
  • Completion reads the session's own typed NIF, which the REPL just compiled: aowllens decls per successful compile, aowllens members <recv> memoised — the two queries backing aowllsp. xs.l narrows to len because the NIF says xs is a seq. Raw-mode editor over tcsetattr (std/terminal stops at isatty), ghost text, menu, history.
  • Highlighting and cell-completeness both ran on a hand-rolled scanner; both now use aowlparser tokenize, so echo "a:b", echo '(' and echo 1'u8 are complete and an unterminated literal is not. aowlparser check cannot answer completeness — [] for type, if x > 1:, proc f(): int =, and expression-expected sits in the driver's collectDiags (aowlparser.nim:1188), not the library. Filed.
  • Three silent-wrong-answer defects. compile counted a non-zero nimony exit with unparseable output as success, so the REPL ran the previous session's NIF; snifIsFresh now refuses a NIF older than the module just written. :reset-cache never worked — std/dirs.removeDir is rmdir(2), ENOTEMPTY on a populated nimcache — which wedged a session once that guard fired.

Gates. New tests/run.sh 8/8: 5 golden transcripts, 22 reader verdicts (--analyze), the candidate set (--complete), and the stale-.s.nif guard put back with NIMONY=/bin/true.

Standing. ~/nimony/nimcache_static is shared by every nimony on the machine whatever --nimcache: says, so any concurrent build, test run or LSP deletes static.o mid-link; those processes take no lock, so compile and build.sh take the lock and retry on the signature. Installing to ~/.aowl/bin leaves nothing on PATH — the build now symlinks into ~/.local/bin.

aowli — 11 commits. Interpreted code is now replaceable and compilable while the process runs, and bounded by a capability policy.

  • Hot module swap. tryLoadSym answers from prog.mem before touching a file, so swapHot re-reads the .s.nif and publishes each decl over the same SymIds. Clears callCache and the for/if/case layout caches — those key on a buffer address, which a reallocated buffer reuses.

  • Module-level vars are not re-run, so state survives the code change; a global added by the new version is never initialised. Demoed on a live aowlserve io_uring handler: same pid, same socket, hits keeps counting.

  • Mid-run JIT via aowlc. hybridgen runs nimony c --app:lib — seconds, so startup-only. aowlcjit.nim emits the same uniform shim ABI from the .c.nif plus one gcc -shared -fPIC; --jit:N compiles on the first crossing. Scalar tier, own-module procs; everything else declines to interpret.

  • 7 div 0 returned 0, exit 0 — the divisor reached xint, whose div answers NaN, which mask narrowed to an ordinary 0. isDivByZero raises in both engines, integer only. build.sh now verifies the artifact, not the exit status: a "clean-cache rebuild SUCCEEDED" had left no binary.

  • A native is the only door out of the value model, so nativeCall is a complete capability boundary. iopolicy.nim gates it on an int bitmask (fs.read/fs.write/fs.meta/process/env) plus --allow-path:PREFIX=CAPS; a denial halts through doQuit, so it is not catchable and never returns a substitute value. --audit-reads:FILE records each granted read as path + FNV-1a/64 + size, written by the driver. policyOn() is false until restrictTo, so an unrestricted run is unchanged.

  • The hostOpen backstop demanded fs.read AND fs.write, vetoing a writeFile under a write-only grant that nativeCall had already allowed. capsDeniedOn(need, path) is now the single decision the gate and the hostfd/hostdir backstops share, and the backstop asks for either — its job is catching a syscall that bypassed the gate, not re-deciding it.

Gates. tests/run.sh all 449/449 with the raise in. New demo/hotswap/test.sh 9/9 and demo/hotjit/test.sh 6/6; both carry a negative control (--frozen, --jit off) because the same-answer assertion passes even when nothing happened, and hotjit also asserts hybridNativeCalls > 0. Collatz over 30k inputs: interpreted 3.467s, JIT 0.336s including the mid-run compile, native 0.006s, byte-identical. New tests/policy.sh 11/11, every grant paired with its denial control.

aowlhost — new repo, 4 commits. Runs an aowl module as a plugin under a capability policy. Default grant is nothing.

  • Embeds aowli as a library rather than shelling out: parses the plugin .s.nif, replays imported modules in dependency order, installs the policy before any plugin code runs, and owns its stdout/stderr and exit code. A denied call exits 77.
  • --allow-path:/etc/hostname=fs.read reads that file while a sibling under the same policy is denied and named. plugins/snoop.nim wraps its readFile in try/except and the except arm never runs — the halt is below the language.

Gates. New tests/run.sh 9/9; every denial paired with its granted control, since a trap alone cannot be told from a read that never worked. The write case checks the filesystem afterwards: the absent file is what proves the syscall was never issued.

aowlc — 3 commits. build/run emitted one translation unit; nothing that touched stdout linked.

  • unknown type name 'LongString_0_<system>' on any program calling echo. compileModule stubs missing externs, which covers a function and cannot cover a type. build/run now route through compileProgram over the sibling .c.nif files, entry module last; --single opts out. exec --entry was unaffected, so it read as a codegen bug.
  • --emit-only writes the linked C without running cc — what aowli's JIT consumes before appending shims. test/driver.sh covers build+exec; test/single.sh compiles one TU alone, separating a codegen failure from a missing link step.

web — 6 commits. component gives a tree typed parameters; one lowering engine now backs both surfaces.

  • h1 title rendered <h1><title></title></h1>. A bare ident naming an HTML tag was read as an element, and title/label/footer/data/form/summary/time/code are tags and ordinary parameter names. Only call/cmd forms are elements now.
  • web: and component: share deps/weblower instead of two lowerings that drift; web: gained for/if/while and runtime children. A child's meaning comes from its type — overloaded webAppend(string|HTMLNode|HTML) — and a call is an element iff html's registry knows its name.
  • [placeholder]: lowered to :placeholder — one colon on a pseudo-element selects nothing, so the rule silently never applied. :: for placeholder/before/after/selection/marker/backdrop. [hover]:/[disabled]: get their own class, and the suffix joins the hash so a state and a base block with equal declarations stop colliding.
  • @style with no enclosing element emitted nothing and the page rendered unstyled; it now fails as directiveNeedsAnEnclosingElement. document() adds doctype/charset/viewport/embedded CSS, escapes the title, and emits < as \3c so a declaration cannot close <style> early.

Gates. New tests/run.sh 6/6 (tweb, tcomponent, tsheet, tescape, tdocument, tstates). tescape asserts a component input escaped in both positions it lands in — text child and attribute value — with rawNode the only opt-out.

css — 2 commits. Styling by value, not by property soup.

  • Style is an ordered set of validated declarations merged right-wins by &, so a theme is a proc returning a value and theme & declare("color","red") keeps every property it does not name.
  • Stylesheet maps selector → Style: declaring .btn twice merges rather than shadows, sheet[".btn"] returns a Style reusable per element, selectors go through validateSelector, and useStylesheet installs one process-wide.

web-state — 2 commits, plus 1 in js. Fine-grained DOM binding; the blocker was structural, not scheduling.

  • An effect could not know which node to update. JsProc0/JsProc1 are {.nimcall.} and carry no captured environment, so an effect could only touch globals. toJsWith(p, ctx)_fnToJsCtx, context captured by value — backs effectWith, then web_state/dom's bindText.
  • tests/tdom counts runs per binding, not output: writing count leaves the name binding at 1 run and a no-op write re-runs neither, so a whole-tree re-render fails the test instead of passing it.
  • run.sh hid its failures — every compile error printed as no .c.nif, and a missing .output was a silent skip. Both now report what happened.

Gates. 3/3 (tauto, tdom, treactive).

aowlui — 3 commits. The lab's 3055-line stylesheet and its page, as values, both gated.

  • tools/ingest classifies 429 selectors → 195 components + 64 raw. A selector the model does not explain becomes stRaw verbatim, so the raw count measures the model instead of hiding the gap.
  • Trimming collapsed .a .b and .a.b, ingesting every .owner .part as a modifier class — declaration counts matched exactly, 1739 = 1739, while the design broke. tround compares the declaration multiset both ways: 0 missing, 0 invented.
  • .aowl.nim and the pack compiles. 144 errors were one: imports still named aoughwl/web, so every web: block was semchecked as ordinary code. The genuinely missing field, name = value, webClass, webSlot landed in web.
  • shell.nim ports index.html; tshell reads the kernel script order out of the reference at test time rather than transcribing it, so the gate cannot drift from what it checks.

Gates. 3/3 (tround, tshell, tpage).

Standing. serve does not build here: ~/nimony combined-prs keeps the posix modules under src/lib/posix/ where the transport deps expect src/lib/, and forcing that path pulls a second stdlib (type mismatch: got string but wanted string). Reproduced on an untouched reactor_http.nim, so examples/web_page.nim is committed unverified.


026 2026-08-01 - Saturday, August 1st 2026

aowli — 18 commits. Every defect was a silent wrong answer: plausible output, exit 0, empty stderr.

  • Nested return lost its value to the enclosing case expression (tree-walker only). Outer ret overwrote the result.
  • (expr STMT… VALUE) discarded flReturn/flRaise/flBreak and continued to the trailing value.
  • Cell-backed pointers had no address (cast[uint](addr s[i]) → 0). Stable synthetic addresses added.
  • No conversion op for cast[ptr T] with non-scalar pointee; integer stayed integer and read as 0.
  • Loud-halt not byte-identical across engines; post-quit stdout now dropped at shared sink.
  • Hybrid boundary failed open: aggregates containing pointers crossed by value and segfaulted. Fixed in aowlabi.
  • build.sh always returned 0; now exits non-zero, keeps linker lines, retries once on intermittent errors.
  • Debugger: unmatched breakpoints report nearest executed lines; DAG reported as <cycle>; tuples unreachable by expand.
  • advance()/complete() returned nothing — neither engine could call an imported global as callee. Both now fall back to lazy imported-global slot.
  • VM compiled complete as unconditional opCallNative; now gated on vkCont.
  • setScheduler wrote only engine state, never system’s global. Both engines now update both views.
  • --break-func fired once per statement; now once per invocation.
  • readFile failed on correct paths because aowli launched from temp nimcache. --cwd/AOWLI_CWD tried after cwd-relative open fails.

Gates. run.sh defaulted to 6 of ~45 categories. Now takes all. Real figures: 414/414 → later 434/434 all categories, three-way crosscheck 0 divergences, hybrid 6/6. New lanes: semantics.sh, dbg.sh 15/15, async.sh 9/9. Crosscheck/run defaulted to absolute master paths; now $NIFI_ROOT/bin. On complete-integration: 457 AGREE-PASS / 19 AGREE-FAIL / 0 DIVERGE.

c3b572c does not compile alone (definition lands in next commit). Pair must move together. Both engines agree on every runnable test. master and complete-integration each carry fixes the other lacks; convergence is a merge with behavioural gate. Arena slices 1–5a in; 5b (Cursor interior pointers) remains the blocker.

aowlcode — 44 commits. One question for every tool: what is its verdict actually resting on?

  • nimony exits 0 on rejected arguments; usage banner now treated as failure.
  • explain_failure answered “OK: compiles clean” for failing compiles; verdict now from compile’s own ok.
  • Equivalence tools reported identical/preserved for runs that never ran or both crashed.
  • ddmin/bisect unbounded by time; total budget added, killed runs no longer count as reproducing.
  • Stale server binary answered while signals said healthy; doctor.server_stale + build-wait on launch.
  • aowli binaries already hot-swap; doctor now identifies live build and reports swapped paused sessions.
  • Concurrent nimony builds raced on shared nimcache_static; every invocation now serialises through one flock.

Gates. Differential harness compared tool names only (6 of 26 differed in contract). Now 197 curated + 116 sweep cases, 107 unit, 39 e2e, all 8 hooks smoke-tested.

Messaging. /listen <label> + maildir transport; messages become events. One inbox per repo; /work <repo> drains backlog then works. Heartbeat distinguishes live/wedged/dead; --ping is proof without costing tokens. Chain depth + rate limits break reply loops.

serve — 25 commits. h2spec 95/146 → 146/146: the 95 was concurrency, not protocol.

  • Accept loop ran whole session before accepting again → timeouts in aggregate. Fixed.
  • nghttp2_session_mem_recv return ignored; frames in read tail dropped.
  • close() with unread bytes sent RST instead of GOAWAY; now close_notify → shutdown write → drain.
  • Stream-id reuse never reached on_begin_headers; tracked and terminates with PROTOCOL_ERROR.
  • Reactor gained TLS, timers, stop, outbound client, produced bodies. Idle timeout via CLOCK_MONOTONIC; graceful stop via eventfd.
  • All TLS entry points now take context overload. Streaming bodies via pull producer; static serving gained ETag/304/ranges.
  • WebTransport streams closed out; QUIC shim counts resource overflows.

Gates. h2spec 146/146 h2c and TLS. Streaming e2e: 128 MiB byte-exact at 6 MB peak RSS. Proxy e2e: 12 concurrent 0.5 s upstreams in 0.53 s. One thread serves HTTP/1.1 + 2 + 3 on one port with TLS, timeouts, shutdown, client, streaming.

http — 2 commits. Added parseResponse (mirror of Request). Header count and body size now bounded (128 headers, 64 MiB).

aowlsem — 71 commits. Macros and const initialisers stopped being matched by shape and started being run.

  • Macros were expanded by matching shapes. Now executed: the body is lowered to a plugin module, built, and run per call site with argument trees marshalled in and the expansion read back. Two executors for the same module — --macros:interp semchecks to .s.nif and runs it under aowli, compiled builds a host-native binary with nimony s. tests/macros.sh asserts each matches the nimsem oracle and that the two match each other.
  • A const no fold recognised was emitted unchanged inside its (suf … "i64") wrapper — a call where the wrapper promises a literal. Now evaluated by generating a module from the host's own declarations up to that const and running it; the value returns through std/writenif, not echo, which renders for a human and loses float digits. Covers int, bool, float, string; 64 evaluations per module.
  • The whole transitive import closure was one flat scope, so bindSym("add") in a module importing only std/syncio and std/macros froze std/paths' and std/strutils' add into the choice. Visibility is now a fixpoint over (import (kv <suffix> "<path>") …), seeded from direct imports and extended across export edges.
  • A template selected itself; a module's own declarations, reached back through an import, counted as rivals; the imported-template merge skipped the self-import filter.
  • Untyped imported generics left dirty templates unexpanded; emitInstanceUntyped now semchecks body. Multiple template overloads kept. Hygienic rename of template locals.
  • A generic type argument was substituted only when bare, not recursively — Table[string, int]'s backing seq[(K, V)] kept std/tables' own typevars. Two instances of one generic differing only in hash are no longer a provable clash.
  • Concepts parsed/emitted but never enforced; requirements now checked at instantiation (E0282).
  • Nested generic instances drained at module level (enclosing locals missing); now spliced in place.
  • build.sh installed newest binary, not the one just linked; now asserts artifact newer than sources.
  • typeof(expr) copied as tree; now demands typeOfValue and emits answer. Macro bodies: parameterised stay raw, zero-arg semchecked.
  • A try body is a scope. An imported type's base class is recorded, so an upcast is recognised. User pragma-aliases kept and expanded at the use site. E0410 addr suppression narrowed to object-constructor field values; E0100 for a call-shaped type whose callee names nothing.

Gates. diff.sh 677/677. check 400/400, beat 4/4, nofp 35/35, diag 175/175, explain 93/93, e2e 4/4. defined() byte-exact; compiles()/declared() still open.

025 2026-07-31 - Friday, July 31st 2026

aowlsem passed its 1000th commit — 16 days after the first, 65 landed today, all in the checker. ~32.5k lines of self-hosted Nimony; byte-exact differential corpus 632 → 659, no-false-positive gate 23 → 35.

The productive gate was the broad-module differential, not hand-written probes. tests/moddiff.sh compiles a real project into a fresh nimcache, re-semchecks every module the oracle produced both a .p.nif and a .s.nif for, and canon-diffs each. Feature probes had gone several rounds finding nothing — that seam is saturated; forty real modules found a day's work in one run. 18/40 byte-exact, 0 crashes, the rest quantified in tokens.

The dominant class was when an instance gets minted, three rules:

  • A generic type decl's body keeps (at G …) raw; a generic routine's signature cannot, being shared by every instantiation — proc parseNum[T: SomeInteger](s: openArray[char]) instantiates openArray[char] at decl time.
  • Inside a generic decl, a call over still-abstract operands stays symbolic: t[k] = v in proc addTo[K: Keyable, V](t: var Table[K, V]; …) is the generic symbol, no instance, no haddr. Instantiating there dragged in the operator's whole lifetime-hook cascade — 5922 tokens on a two-line program; std/sets 7530 → 147.
  • typeVarLike, consulted from ~25 places, guessed from spelling — one uppercase letter, so BiTable*[Id, T] had one of two parameters recognised. Every typevar mint is now recorded, spelling kept only as fallback for imported generics. std/bitabs 5863 → 1236.

Commit #1000 fixed the ordering assumption underneath. Type instances emit in request order and a dependency is requested by the body needing it, so HashSet[T] = object; t: Table[T, bool] had its field scanned before Table[TagId, bool] registered hooks: the field read unmanaged, and neither HashSet nor anything holding one got lifetime hooks. Reordering is not the answer — the oracle emits in the same order. nimsem decides off the declaration, so we now do: a generic whose body structurally holds a lifetime pre-registers its instance's hook names at request time. std/optcore 18369 → 16052.

A module-qualified name was unhandled in three emitterstypeOfExpr (no dot case at all), semType, and the case of-value list. One shared test resolves all three: the left of the dot is nothing in scope, which is what an import name is. Relatedly from std/dirs import walkDir is now really selective — loading the whole module registered dirs.getCurrentDir(): Path alongside ospaths2.getCurrentDir(): string.

Smaller parity fixes, each pinned to a real program: a distinct over a primitive keys the base's magic on its own symbol, and !=/>/>= cancel on the operator they derive from; an anonymous routine must never consume a prescan slot, or a lambda in a global initializer takes the next proc's name; proc-type parameters are numbered at the type decl's position; a user-declared lifetime hook suppresses fieldwise synthesis; {.push header: … .} applies to every decl and parameter; ordering two pointers is an address compare; a prefix operator can be a template; a named argument may skip a defaulted parameter; and four absent folds — set consts by name, float consts, (par …) in a when condition (not (defined(cpu16) or defined(cpu8)) was dropping all of std/widestrs, 5009 → 415), and an {.untyped.} template body.

One thing is written down as not reproducible rather than chased. nimsem lists an (ochoice …) in a hash order — system's + comes out 3, 6, 12, 10, 14, 0, …, which is not declaration order, not .s.idx.nif order, and not sorted. We emit the right set, sorted, and the commit says so.

024 2026-07-30 - Thursday, July 30th 2026

aowlcode is now private indefinitely.

aowlcode 1.0 — the tool gate is on by default. aowlcode fronts the Nim and Nimony toolchains for an agent: structured diagnostics, NIF slices and navigation instead of raw compiler output and 40KB single-line artifacts. The tools shipped months ago and were still bypassed for grep -rn + sed -n, so 1.0 inverts the default rather than the documentation.

Aowl mode, default guided. A PreToolUse hook denies Grep, Glob, and Bash segments that are a code search, a source/NIF dump, a tree walk, or a raw nim c / nimony c / nim check. git, test scripts and running a built binary pass; strict denies Bash outright. Each denial carries a redirect table and appends to a ledger /aowl-mode status reports. No state file ⇒ guided; off is a written state, not its absence, and expires on the same 12h TTL, so a stale strict and a stale off both fall back to baseline. Escape hatches: aowlcode-mode commands always pass, AOWLCODE_DEFAULT_MODE moves the baseline, AOWLCODE_NO_MODE_GATE=1 removes the hook, every hook fail-open.

Four tools cover what the gate removes, each replacing a habit whose failure mode is unbounded output — search (excludes generated trees and hidden dirs; .claude/worktrees/ alone multiplied one repo's apparent source count ×10; caps and reports truncation), map (one-call orientation, parsing the build script's actual compiler invocation so a Nimony project with no nimony.cfg marker stops resolving as Nim), changes (git diff as per-file +N -M and hunk headers, ~1% of patch bytes), and run (output middle elided, head 30 / tail 60, so the failing assertion at the tail always survives).

0.8, same cycle, closed three gaps two agents had each hand-rolled in shell: nif_run executes a built .s.nif on aowli with its sibling modules, deriving the install name from the artifact's own stmts header (getting it wrong silently runs the oracle instead of the candidate); bisect runs ddmin over a flag matrix for the minimal reproducing toggle set, catching multi-flag interactions a linear scan misses; nif_diff mode=canon|semantic strips line info and framing and folds generic-instance hashes, replacing a hand-written canon.py. Plus 28 end-to-end checks over the real MCP loop.

aowlsem gained its second half: an optimizer on .s.aif. ~30.5k lines, 917 commits (67 today), corpus 631 modules. Separate command — aowlsem m checks and must match nimsem byte for byte; aowlsem opt rewrites and must only preserve meaning. Those claims cannot share an exit path, so the parity gate is structurally untouched.

Twenty-one passes to a fixpoint (nine sweeps max, tree strictly shrinks): constant folding over the twelve arithmetic/bitwise magics; comparison/not/xor of constants; short-circuit and/or; algebraic identities including operand-discarding ones (legal only once purity can be asked); redundant-conversion drop; constant if/case selection; while false; unreachable statements; dead and write-only locals; unreferenced private procs; constant and copy propagation, where the constant query sees through propagated locals — which is what lets folding and case selection fire at all. Inlining runs first each sweep in three shapes, alpha-renaming the inlined body since .s.aif symbols are module-wide unique. Two invariants cut across: exported means live, and never drop work with effects.

The verification is the substantive part. A dozen hand-written programs will call an optimizer green. A scale gate builds each real program three ways (nimsem's output, ours unoptimized, ours optimized), runs all three on aowli, and demands byte-identical stdout and exit. It caught nine genuine miscompiles the small suite passed — an expression-if deleted outright, an inliner accepting any three-statement proc as one-expression, {.keepOverflowFlag.} making arithmetic observable, inlined generic-instance bodies, statements pasted into expression position — each narrowed by --no:PASS sweep and pinned with a regression program. The gate went 103 → 344 of 609 candidates; one fix (deriving the install name from the artifact header) recovered 145 programs silently recorded as "did not run".

Measured payoff from bench.sh: three nested one-line procs in a hot loop, 360,003 calls → 3, 505ms → 95ms (5.3x); one hot one-liner, 120,003 → 3, 212ms → 77ms (2.8x); a partly-constant loop body, 118ms → 64ms (1.8x). On whole library modules it removes ~1–3% of nodes, and the doc says so — a library is nearly all exported surface.

Anonymous sum types closed — construction and of-pattern matching, including failures that only appear across a module boundary: instantiating an imported generic sum type (Opt[T], Result[T, E]) left pattern bindings holding a field's address rather than its value, and the family's shared tag type was re-declared in every importing module instead of being recognised as foreign by its mangled module segment. That exposed a wider gap worth more than the feature — a local initialised by a call returning a generic application had no type at all. std/opt and std/result check byte-exact both as the defining module and from an importing one.

023 2026-07-29 - Wednesday, July 29th 2026

The playground grew into a real in-browser IDE — still the whole toolchain (parser, checker, interpreter, debugger) compiled to JavaScript, running entirely in the tab.

  • Multi-file projects + explorer. Dockable tree with context menus, multi-select, drag-to-move, preview tabs (single-click italic preview, double-click or edit keeps it), and navigation history on the mouse back/forward buttons (or Alt+←/→).
  • Clone a repo, or share a workspace, from a link. Type owner/repo to clone a public GitHub repo client-side, or hand someone a #clone=owner/repo link. Share packs the entire workspace — every project and file — into one compressed link, not just the active buffer.
  • The aowli debugger, live in the browser. Step through a program on a flame / depth timeline: every statement a cell, call depth stacked into lanes, per-routine colour, zoomable slice and full-run minimap — scrub, reverse-step, jump anywhere, auto-captured on open. (Fixing the current-line highlight traced a neat root cause: echo is a template, so its expansion carries the stdlib's line info, not your call site — the debugger is now file-aware.)
  • Split editors + stdlib browsing. Drag a tab to any edge for side-by-side or stacked. Ctrl-click or F12 on an import opens the real std source. .json / .js / .c / .nif get native highlighting — including a proper NIF grammar — and skip the nimony pipeline, so only .nim / .aowl are checked and run.
  • Latest bundles: obfuscated aowlsem and the aowli interpreter + debugger refreshed.

Every doc page for a runnable library now carries a "▶ Try it live in the Playground" link.

aowlsem spent the day on the half of a checker that never shows up in its output: deciding which programs are wrong. Byte-for-byte agreement on valid programs says nothing about invalid ones, and a checker that quietly accepts a broken program is worse than one emitting a slightly different tree for a good one. The method is a checked-in tool: ten small programs around one theme — arguments, literals, control flow, generics and closures, declarations, numeric types, exceptions — through both checkers, verdicts side by side. Rejecting what the reference accepts is the urgent kind; accepting what it rejects is a missing check; occasionally the reference is wrong.

About thirty checks landed. A sample of what now errors instead of passing silently: a named argument no parameter answers to (with a did-you-mean); for a, b in 0 ..< 3, where the range yields one value per step; assigning to the result of a call; a case on a float; the branches of an if-expression disagreeing on type; a variant constructor setting a field from an unselected branch; a nested proc reading its enclosing local without being a closure; arithmetic on types that have none (true * false, 'z' - 'a', "a" + "a"); indexing with a non-number; mixing signed and unsigned; a set over a non-ordinal; deriving from an object never made a base type; the wrong number of type arguments; a converter not taking exactly one parameter; a pragma that is not one (checked against the whole vocabulary plus your own); and an entire family that had been silently accepted — an undeclared type name in any position: field, parameter, local, parent, generic argument, except filter.

Four false positives came out of the same loop, and mattered more than the gaps. Two methods along one inheritance chain reported as ambiguous — the subtype relation made the signatures look identical. Shadowing a parameter (proc f(a: int) = let a = a + 1), ordinary Nim, reported as redeclaration because parameters share a scope with the body. Both directions of an enum conversion rejected as impossible. A for over an enum range mis-flagged as iterating a non-collection.

That last pair had been hiding: the probe programs also tripped a real error from the reference, so the verdicts "agreed" and the tool said nothing. It now prints why each side rejected, and every new check was confirmed to fire for the same reason rather than by coincidence. Re-running the day's probes through that lens found five more rejected for the wrong reason — three fixed, two written down as open.

Gates: corpus 618 modules, accept/reject agreement 76 → 139, error-message snapshots 64 → 97, and every one of the 71 diagnostic codes has a long-form --explain article. A third case joined the set where aowlsem is right and the reference is not: proc maxOf[T](a, b: T): T = if a > b: a else: b, textbook Nim the reference cannot instantiate.

022 2026-07-28 - Tuesday, July 28th 2026

aowli-release is now private indefinitely.

Debugging a big program under aowli stopped meaning "recompile it every time." Pointing the debugger at aowlsem took minutes per run, but the interpret is ~1 second — the minutes were aowlcode's debug/trace recompiling the whole ~20k-line compiler plus stdlib from scratch with -f, then deleting it. The obvious "persistent cache → incremental rebuild" fix was measured and does not help: a warm no--f rebuild costs the same ~47s. The real fix is to skip the compiler when nothing changed — reuse the built .s.nif, recompile only on an actual source edit. First debug of a session ~47s; every one after ~1 second. (Or hand the tools a prebuilt .s.nif.)

Released aowli v0.3.3 — hybrid-native mode crosses ref/seq-bearing data. Hybrid runs the modules you are not debugging as compiled code while interpreting the one you are; a shared-memory arena lays a live value graph out at native layout, so calls taking ref objects, nested ref graphs and seq[T] fields (including seq-of-object) cross too — the native side reads and mutates the same memory, synced back. Additive and dormant: without the flag, execution is byte-for-byte v0.3.2, and anything not safely marshalable falls back to interpretation.

aowlsem spent the day closing byte-level gaps against the reference's typed output. Method: a small valid program per feature, both checkers, diff token-for-token; every difference is a bug or a recorded lowering choice. ~21.7k lines, 700+ commits, corpus 500/500, accept/reject 10/10, std/system clean.

  • Generic ref object types reached full byte-identity. Container[int](…) constructs a real heap ref (newobj) instead of a value; a generic instance's synthesized lifetime hooks no longer bake in the defining module (the instance is content-addressed already); the object half numbers its type parameter T.1 to the alias's T.0, matching how the reference counts a ref object's two declarations.
  • Value objects that carry methods. An inheritable object with managed fields that also declares methods emitted the full four-hook lifetime form; the reference emits only the user-method vtable, because a type with a real vtable routes its own destruction through it. The trigger is the presence of a user method, not inheritability.
  • Smaller parity fixes. Generic variants resolve named-branch fields; {.borrow.} operators returning a distinct type convert the result back; untyped/typed template parameters are wildcards, so template twice(x: untyped) inlines at the call site; bool case labels emit literal (true)/(false) tags.
  • Reading a variable before it is set is now an error. var x: int; return x is rejected, as by the reference (also discard x, s.add … on an untouched var s: seq). The definite-assignment analysis existed for a single-assignment check; today it started reporting. A branch initializes only when every path does, so a value set in one arm of an if with no else is flagged while an exhaustive case or if/else is accepted. This is what lifts accept/reject to 10/10.
  • var-returning calls as assignment targets. first(c) = 99 writes through the location the call yields; a var-returning proc forwarding another such call emits the bare pointer-to-pointer copy instead of address-of-a-dereference. (Both emission sites were pinned by stepping aowlsem itself under aowli's interactive debugger.)

Earlier the same grind landed lambdas as expressions, cross-scope iterator resolution, custom []/[]=/{}/contains, multi-index x[i, j] read and write (two assertion crashes fixed), cross-module import-resolution fixes, and a relative include resolving straight from its parsed artifact with no source file on disk.

021 2026-07-27 - Monday, July 27th 2026

aowli's debugger can now pause a running program and step through it interactively, instead of only printing breakpoint snapshots after a run finishes. Three additions, all in aowlcode 0.6.13 and documented under Debugging:

  • Interactive stepping (--session). Run a program once and keep it paused between commands: step into a call, step over it, run until the current routine returns, or continue to the next breakpoint. You can set breakpoints while it's paused and inspect the current frame — without re-running the program for each look. In the plugin this is the new debug_session tool.
  • Readable output for big values. A large local — say a compiler's context object full of lookup tables — used to print as thousands of lines. Values are now rendered under a size budget and the rest is elided with a marker, so a frame dump stays readable regardless of how large the values are.
  • Drill into one field. Rather than print a whole value, name the part you want — expand c.currentModule.name, expand xs.3.field — and only that piece is shown. Object fields resolve by name, seq/array elements by index.

Also fixed a build issue where a rebuilt debugger binary could be shadowed by an older copy earlier on the lookup path. The binary now reports its build version (aowli-dbg --version) and installs to one canonical location. Full command reference: aowlcode → Execution.

Released aowli v0.3.2 — two shipped-runtime correctness fixes surfaced by running a real argument parser under the interpreter: s[a..b] / s[a..<b] slices returned only the first element instead of the substring, and a non-string value (a nil/default) could compare == equal to a string. Both fixed; byte-identical to a native compile on the repro, and the differential corpus stays at 77/77. Hardened binaries (obfuscated IR + licence gate + stripped) with SHA256 are on the release page.

aowlsem, the from-scratch semantic checker, keeps closing on full parity with the reference compiler. It is now ~18.6k lines of self-hosted Nimony across 550+ commits, and its byte-exact differential corpus stands at 498/498 modules matching nimony's own typed output, with the entire std/system checking clean (0 diagnostics). Today's work brought generic type instantiation in line with the compiler's own behavior:

  • Generic sum types construct by inference. let d = Some(99) works out Option[int] from the argument, so d.val is an int and d.val == 99 resolves to a single integer comparison instead of a 25-way overload set — the same inference drives annotated conversions (Option[int](x)) and two-parameter sums like Either[int, string].
  • Plain generic value objects infer their instance tooPair(first: 1, second: 2) picks Pair[int] straight from its field values.
  • Generic ref object types instantiate in full. A recursive Tree[T] = ref object variant now emits both halves the compiler expects — the reference alias and its underlying object type — each carrying its own per-instance lifetime hooks (destroy / move / copy), with matching typevar numbering, and its constructors (Branch(…), Leaf(…)) build the concrete instance rather than the generic origin.

020 2026-07-26 - Sunday, July 26th 2026

aowlmcp now speaks the MCP 2026-07-28 spec — the biggest MCP revision since launch, and a clean break with its stateful past. The library serves both protocol versions, negotiated per request, so upgrading a client is never a flag day:

  • Stateless core. No initialize handshake and no Mcp-Session-Id: every request carries its own context in _meta (protocolVersion / clientInfo / clientCapabilities), and a new server/discover RPC advertises capabilities up front (it doubles as the stdio back-compat probe). Any instance can serve any request.
  • Multi-Round-Trip Requests (MRTR). A tool can return an InputRequiredResult to elicit user input mid-call; the client re-issues with inputResponses and the echoed requestState. registerToolMRTR + newInputRequired.
  • Tasks extension. registerTaskTool returns a task handle; tasks/get / tasks/cancel / tasks/update drive it — advertised in server/discover only when a task tool exists.
  • Caching + routing. tools/list gains ttlMs / cacheScope; the Streamable-HTTP Mcp-Method / Mcp-Name routing headers are accepted. Roots / Sampling / Logging are deprecated (aowlmcp never shipped them).

Every new flow is proven over all three transports — stdio, HTTP, and HTTP/3 (QUIC) — because they share one transport-agnostic dispatch core: stdio 27/27, HTTP 15/15 (routing headers + MRTR + Tasks over the wire), HTTP/3 4/4. Docs updated alongside.


Also released aowli v0.3.1 — the interpreter now runs the Nimony semantic checker itself, byte-identical to a native compile. Pointing the interpreter at aowlsem — a real, compiler-grade program — and diffing against native turned up three root-cause bugs; fixing them brought the run to 520/520 tokens identical. This means aowlcode's debug/trace can now be aimed at the compiler's own passes.

  • Fully-initialised pointer values. Constructing an interior ptr left its flat-memory view fields (region/foff/elemBits/base) uninitialised, so a later read saw garbage — a genuine memory-safety class, not a cosmetic gap. Caught with valgrind running under mimalloc's Valgrind-tracking build (mimalloc otherwise bypasses the sanitizer); every pointer construction now initialises all fields.
  • seq append value-copy. s.add x now copies x on the way in — the same =copy envelope semantics v0.3.0 gave assignment — so mutating the appended element never aliases the source.
  • Content-addressed tag dedup. StringView.== is gated so NIF tag interning deduplicates by identity, the way TokenBuf-style content-addressed programs expect.

The debugger got sharper alongside it. --break-func:NAME and file-scoped --break:file.nim:LINE resolve routines through the include chain (a bare line number is ambiguous once modules are merged), and program_args forward to the interpreted program's commandLineParams() — so a breakpoint can land inside semCall while the checker runs on a real input.

A plugin-packaging note. The aowlcode MCP server a session runs isn't the marketplace checkout or the newest cached build — it's the exact version pinned in installed_plugins.json. Ours was stuck several versions back at 0.6.0, which silently dropped any newer argument (like program_args) before it reached the handler. The fix is a one-line pin bump (to 0.6.9) plus a restart; the thing to watch for is a parameter that's present in the source but missing from the live tool's schema.

019 2026-07-25 - Saturday, July 25th 2026

Created aoughwlup and aoughwl
Created aoughwl-code and aoughwl-code-release

Released aowli v0.3.0 — the correctness-complete build. Both engines — the tree-walker behind the public aowli-interp / aowli-dbg, and the internal bytecode VM — now hit zero in-scope divergence across a 423-program differential corpus run against the nimony compiler. The engines agree with each other and with native, program for program.

Value semantics landed. Assigning or binding a value object / tuple / value-array now copies the envelope (refs stay shared) — var x = a; x.a = 999 no longer reaches back and mutates a. This was the last big place aowli's aliasing quietly disagreed with the real =copy.

The last OS-boundary gaps closed. Real host stat / lstat (so fileExists / dirExists are correct), pointer identity in == / !=, cast[int](ptr) round-tripping through flat memory, and VM argv / stdin seeding. Plus a sweep of narrower fixes: float→int conversion, block-expression values, cyclic-import init order, a self-nested-iterator hang, and Table element write-back.

The one boundary the pure value interpreter can't cross by itself is literal-C: {.emit.} and C FFI have no C to run inside the value model. That's exactly what hybrid-native mode absorbs — and today it ran real foreign C for the first time.

Hybrid-native executed real C-FFI. aowli's hybrid mode — interpret most modules, run selected ones as native code — now offloads a header-backed {.importc.} proc to a compiled shim and calls into genuine C: a static inline cadd crossed the boundary, ran natively, and marshaled its result back into the interpreter byte-identical to native. The proc-offload path (pure-nimony procs run native) also picked up a permanent regression lane. This is interpreter-development progress, not yet in the public v0.3.0 binary; the remaining piece is top-level {.emit.} / importc-var in the main module.

Playground refresh. The in-browser playground got a round of work: rebuilt engine bundles (fixing a stale-bundle bug where Bytecode-VM and Native-JS runs showed no output), a unified Pipeline config panel (parser · checker · lowering · engine), aowlsem rebuilt from latest, the two source-pane toolbars merged into one, a clearer footer with an inline engine picker, and curly-brace block mode no longer false-flags a { … } body as "not a Nim block."

018 2026-07-24 - Friday, July 24th 2026

Gave the whole stack one source of truth for how values are laid out: aowlabi. Three places each kept their own copy of how is a string / seq / object / ref actually represented — the interpreter, the C backend, the JS backend — and they had quietly drifted. aowlabi is now the single canonical answer:

  • the size / alignment / field-offset engine — one implementation of the C-struct layout rules, parameterized by pointer size
  • the canonical heap-block spec — string SSO + LongString{fullLen,rc,cap,data}, seq {len,data}, the ARC ref box {rc,data} — as named offset constants, one truth
  • the marshal matrix — which types cross a native boundary by value / by buffer / by fallback, plus the JS representation mapping (fast number vs faithful bigint, char, tuple, and so on)

aowlc, aowljs and aowli all read the same spec now instead of re-deriving it.

aowli grew a real runtime layer. The scattered places where the interpreter crossed from its value world into a faster / foreign executor — host natives, flat memory, syscalls, the miss policy — are one spine now: a provider registry (interpret · host-native · syscall · hybrid-native), a codec (identity · flat C-ABI · JS value), and a policy that is never silently wrong — an unsupported crossing fails loud or falls back to interpret, never a wrong answer.

And the payoff — hybrid execution. aowli can now interpret only the file you care about and run every other module as natively-compiled code at full speed. Debug one file slowly, with full observability, while its libraries run native. aowli auto-generates C-callable shims for the cross-boundary calls, marshals scalars, POD objects / tuples, strings and seqs across using aowlabi's layout, and dispatches at the call site. Every result is byte-identical to the fully-native build; anything that can't be safely marshaled (refs, closures) transparently falls back to the interpreter — so it is faster where it can be and correct everywhere.

aowli --hybrid --interpret:mymod prog     # mymod stays observable, everything else runs native

017 2026-07-23 - Thursday, July 23th 2026

Rebuilt the net stack around a single-threaded async reactor: one OS thread, epoll, passive-proc coroutines, no std async or thread pool.

  • HTTP/1.1: keep-alive, chunked, 300/300 concurrent
  • WebSocket: masking, frame/control validation, fragmentation, incremental UTF-8, close validation, permessage-deflate; 19/19 conformance, 160/160 echo
  • HTTP/3: ngtcp2 + nghttp3 + GnuTLS behind a small pull API; 20 QUIC clients, one thread, ASan/LSan clean
  • RFC 9221 datagrams + WebTransport datagrams over H3; streams remain

Created aowljson: reusable JSON values, error-as-value parsing, serializer, builders, v{"key"}, v.at(i).

Created aowlmcp: transport-independent MCP dispatch over stdio, HTTP, and HTTP/3. Tests: 13/13, 6/6, 4/4. Includes compile diagnostics and NIF outlines through aowlcode.

aowli became an actual runtime: flat memory, casts, copyMem, allocation, unchecked arrays, fd-backed file I/O, env access, ownership hooks, refcounted ref objects, and fail-fast unsupported stdlib calls. It now runs about 92% of compiler-buildable programs, with no known silent wrong-result cases. Remaining: some OS/VM gaps, threads, async.

016 2026-07-22 - Wednesday, July 22th 2026

Released aowli-release v0.1.0 with:

  • aowli-interp: run typed NIF, optional call-tree trace
  • aowli-dbg: batch breakpoints and structured frame dumps
  • stripped binaries, fail-closed licence gate, SHA256, VirusTotal links
  • no source paths or internal proc/type names

Updated aowlcode with trace/debug tools, /land, Haiku appliers, and parallel edit application.

015 2026-07-21 - Tuesday, July 21th 2026

Back to work after a couple of quiet days.

014 2026-07-20 - Monday, July 20th 2026

A quiet day.

Shout-out to a fellow Nim'er's project — 3code, worth a look.

013 2026-07-19 - Sunday, July 19th 2026

More aowlsem — the whole day is a generics push. The semchecker now instantiates and preserves generic constructs end to end: typevar calls and signatures, generic object applications with substituted field types and attached hooks, generic array bounds and range iterators, generic seq index reads, var forwarding through generic params, late-bound generic hook calls, and quoted generic operators. Around it: out parameter type resolution, sink/source normalization, typed pointer comparisons lowered to magics, unchecked-pointer index assignments wrapped, requires pragma expression checking, and threadvar globals emitted. Steady, surgical commits — aowlsem is now past 340 total commits since Tuesday.

012 2026-07-18 - Saturday, July 18th 2026

A major day for aowlsem — 126 commits landing the clean-room semchecker's core. It now passes 397/397 corpus fixtures byte-exact against the nimony oracle, and — the milestone — it does a complete zero-diagnostic traversal of the full std/system: the whole system.nim plus its included std/system/*.nim set, ~6,383 lines, semcheck with 0 errors and 0 log lines. Full-system parity against nimony's own output is down to ~33k canonical diff lines from an earliest baseline of ~62k — a 46.5% reduction, with the first mismatch now a third of the way into the semantic output.

Under that headline: the magic table (arithmetic / comparison / set / pointer magics), varargs[T] params with call-site collection, membership (x in coll) generalized across seq/array/string, seq slicing and s[a..b], for (a, b) in … tuple destructuring, ^k backwards indexing, countdown typevar inference, concept declarations, lifetime-hook attachment, and ptr UncheckedArray[T] indexing. aowlsem also grows diagnostics that go beyond nimony — E0205 self-comparison, E0206 unsigned-compared-to-zero, E0207 empty-loop-range, E0208 tuple-index-out-of-bounds, E0209 shift-amount-out-of-range. (source private for now; docs public, access on request)

We also stood up the whole distribution story — private components, public binaries. The plan is simple: anything that stays source-private, we still ship to everyone — obfuscated, inside a stripped binary.

  • obfuscate was reworked to be IR-only. It operates entirely on the compiler's own NIF/AIF token tree, never on source text, so it inherently can't corrupt runtime data — strings, chars and comments are their own token kinds and are never touched. Its obfnif pass renames every declared symbol to an opaque token (by spelling on parsed NIF, symbol-precise on typed NIF) and weaves in behaviour-preserving control flow, then the result re-feeds the pipeline and behaves identically.
  • aowl-release is the hardening harness — a build-release.sh that wraps each component's own build and layers source obfuscation → a fail-closed licence/version gate → NIF control-flow injection → --strip-all (drops the symbol table decompilers love). The gate refuses to run an expired build; there's no risky client-side kill-switch.
  • Five -release repos now exist as the public homes for the currently-private stages — aowlsem-release, aowli-release, aowlts-release, aowlpy-release, aowlweb-release. Source stays private; the obfuscated, gated, stripped binaries land here shortly so anyone can run the full stack.

And aowluprustup for the aowl/nimony stack. It installs, versions, and selects the pipeline: every slot has interchangeable variants (parser aowlparser|nifler, sem aowlsem|nimsem, hexer aowlhexer|hexer, plus backends and tooling), grouped into one-command profiles (aowl = all ours, nimony = all theirs, hybrid = the driver default), each pinned to a git version with a GitHub update check (it doubles as a nimony version manager). aowlmony then compiles against whatever aowlup has selected — exactly the rustup : cargo split. The -release binaries plug straight into this: aowlup is how you'll pull, pin, and select them, and aowlup +nimony gives one-shot toolchain overrides.

And the playground grew a semantics choice. aowlsem now runs in the browser — you can pick aowl semantics instead of the default nim semantics when type-checking, right in the playground. It's marked experimental: real and checking a substantial slice today, but not the full stdlib or generics yet, so it grows from here. aowlsuggest moved into the playground too — its quick-fix / lint layer now runs client-side over the parser's diagnostics, so fix-its surface as you edit — and aowlparser got another update, with the latest parser bundle now shipping in the playground.

011 2026-07-17 - Friday, July 17th 2026

A heavy day on the front and middle of the pipeline.

aowlparser — reached full 310/310 structural parity with the upstream Nim standard library: the entire stdlib round-trips. Shipped a real check lint mode — grammar-level error detection with fix-its and source-ordered diagnostics: assignment = where == was meant, a for missing its in, identifier-expected on let/const, and more. Fixed three parser hangs (infinite recursion) and hardened the lexer — UTF-8 identifiers, BOM stripping, custom numeric literals (N'big), parenthesized proc literals, term-rewriting template patterns.

aowlsem — a big step toward a true drop-in: an auto-import system that pulls in system and the module's own imports with no manual flags, real include splicing, when not defined(...) folding, definite-assignment that honours noinit/threadvar/importc, typedesc modelled as a type, templates as an overload set, accent-quoted/operator routine names, and the first value-object ARC hook synthesis — the foundation for Table. (source private for now; docs public, access on request)

aowli — the interpreter/VM now reads the shared aowlhl HL-IR layer (hlload / hlclassify / hlwalk) instead of its own tree-walk, and gained dynamic method dispatch with field write-through for ptr/var receivers, closures with nested capture, and UTF-8 add(string, Rune). With this, aowli is feature-complete: it reproduces 100% of the runnable test corpus byte-for-byte against nimony's own compile-and-run, on both engines (tree-walker and bytecode VM).

aowlhl is now the shared high-level IR — one Nim→HL-IR reader that both aowli and aowljs consume, so the interpreter and the JavaScript backend classify and walk the same skeleton. One lowering, many emitters.

The docs site got a ground-up rebuild. Migrated aoughwl.github.io off Jekyll / just-the-docs onto VitePress — it's now a single-page app with instant client-side navigation (no full reloads), a collapsible nested sidebar, a near-black dark theme, local search, and self-hosted fonts (no font or page flash). It deploys through a GitHub Actions workflow instead of Jekyll, and the in-browser playground is preserved byte-identically.

The nav is region-grouped — Overview / Pipeline / Emitters / Tools / Libraries — and every pipeline, emitter, and tool row carries a small right-aligned "↗" straight to that project's GitHub repo. A floating theme toggle sits in the corner, and GitHub · Discord · Support links live in the top bar.

010 2026-07-16 - Thursday, July 16th 2026

Repositioned: aoughwl is a ground-up Nimony toolchain. Wrote the interop contract — AIF ≡ NIF, byte-for-byte, so any Nim/Nimony program behaves identically. Renamed the compiler stages aif* → aowl* (aowlparse / aowlsem / aowlhexer / aowlc / aowljs / aowli / aowlmony) — aif now names the format only — and nim-code → aowl-code. Reworked the docs into two homes — Documentation (terse reference) and Engineering Notes (opinionated writeups) — collapsed the changelog into a single Changes record, and normalized every repo description + topics across the org. aowlsem and aowlhexer stay private for now (docs public, access on request); the playground moves onto the new sem + hexing shortly.

009 2026-07-015 - Wednesday, July 15th 2026

Created aifhexer
Created aif
Created aifmony
Created aifc
Created aifjs
Created aifjs-js
Created aifsem

Updated aifi
Updated aifparser
Updated nimony-playground

008 2026-07-014 - Tuesday, July 14th 2026

Took nifi private.
Updated nifi — 6–10× performance gain.

007 2026-07-013 - Monday, July 13th 2026

Updated nimony-playground
Updated nifparser
Updated nifi

  • Added curly bracket support to the nifparser
  • Finalized nifparser, passing against the full nimony suite- byte identical to niffler
  • Nearly finished nimony-playground, missing small QOL and a final port to the aoughwl ecosystem (I cannot wait)

006 2026-07-012 - Sunday, July 12th 2026

Created nimony-playground
Created nifparser

005 2026-07-11 - Saturday, July 11th 2026

Created nifi, a Nimony NIF Interpreter

004 2026-07-10 - Friday, July 10th 2026

Created aowl-lsp, it's nimony-lsp, but with a universal plugin system. Obtain novel features!
Created vscode-aowl, aoughwl hosted on-machine within VSCode

003 2026-07-09 - Thursday, July 9th, 2026

Finalized the aoughwl core spec.

Noticed a memory bug exists- likely roots as a true Nimony bug which interacts with niflens and my nim-code instances
Created nifrewrite makes NIF rewrites simple

Fixed IC
Updated niflens
Updated nimony-lsp
Our VSCode extension + Nimony LSP is nearly as performant and featurefull as it can be, live diagnostics work phenomenally as you type... more to come here.

002 ‎2026-07-08 - Wednesday, July 8th, 2026

Pushed IC to aoughwl/Nimony: ~1s -> ~10ms
see: ic-parallel-deps, ic-cursor-traversal, ic-warm-daemon, ic-batch-intern

Created niflens, a CLI tool for parsing and viewing NIF
Updated nimony-lsp and nim-code to benefit from niflens — live diagnostics and suggestions now work as you type!

Massively expanded the net stack — now 8 one-concern repos:
  • tlsTLS 1.3 over OpenSSL 3, client + server (SNI, ALPN, verification); pulled into its own repo
  • serveHTTPS, a concurrent worker pool, HTTP/2 (nghttp2: h2c + ALPN h2), chunked request bodies + Expect: 100-continue, opt-in gzip/br compression
  • dual-stack IPv6 across tcp / net — one listener serves v4 + v6
  • new compress repo — one-shot gzip / brotli / zstd codecs
  • ws — a nimony-native WebSocket (RFC 6455), server + client, ws:// and wss://
  • HTTP/3 in requests (useHttp3) via curl-impersonate's bundled ngtcp2
Every layer is tested against real clients — curl --http2, live wss://, TLS 1.3 handshakes.

001 ‎2026-07-07 - Tuesday, July 7th, 2026

Today starts the official aoughwl/nimony fork.
This is now the main place my Nimony work will go:

  • features
  • bug fixes
  • more opinionated, dynamic, and substantial stdlib

We also shipped aoughwl/nimony-lsp and aoughwl/nim-code:

  • nimony-lsp is the language-server side
  • nim-code is a Claude Code plugin and MCP server focused on reducing token usage with Nim and Nimony

Effective indefinitely, the Nimony JavaScript/TypeScript/WASM/Python backend work is private.
For interested parties: the JavaScript and WebAssembly backends are ~complete and remain true to the original vision.
I will gladly and promptly add anyone who wants access, but you will need to reach out to me directly over Discord (timbuktu_guy)


Popular repositories Loading

  1. css css Public

    A complete, MDN-typed CSS engine for nimony: parse a whole stylesheet, then validate every value against its official grammar, every function against its signature, and every selector against Selec…

    Nim 2

  2. nim2-nimony-lsp nim2-nimony-lsp Public

    DEPRECATED — the original Nim 2 nimony-lsp. Superseded by aowllsp (written in Nimony). Complete and fully featured.

    Nim 2 1

  3. html html Public

    Typed HTML5 for nimony: a registry of every HTML5 element and its attributes (void/deprecated/experimental), a document-tree builder, and a correct escaping, void-aware renderer. Pure stdlib, no deps.

    Nim

  4. web web Public

    HTML + validated CSS in one nimony block: a declarative DSL that builds a typed HTML tree and inline-validates every style declaration against the MDN grammar, lowering each style block to a single…

    Nim

  5. js js Public

    JS-value interop for the nimony JavaScript backend — opaque handles to real JS values across the linear-memory boundary.

    JavaScript

  6. aowlhl aowlhl Public

    Shared High-Level IR for the idiomatic aowl backends: sem'd IR to a target-neutral HL-IR that the thin aowlts / aowlpy emitters render. The ~85% common to every idiomatic backend, factored out once.

    Nim

Repositories

Showing 10 of 47 repositories

People

This organization has no public members. You must be a member to see who’s a part of this organization.

Top languages

Loading…

Most used topics

Loading…