Alain Lilic

How I work

I work with Claude Code. My time goes into requirements, specs, review, tests and operations. This page shows the tools and rules I use for that, with real excerpts and measured numbers.

Spec first, then the plan, then the code

Before any code exists, I write a spec: context, goal, decisions with reasons, and what is deliberately left out. That becomes an implementation plan with small steps to tick off, each with a command and an expected result. Only then does building start, step by step. I split larger projects into phases, and each phase gets its own spec. My global rules file adds four rules after Karpathy: think first, keep it simple, change only what is needed, work towards a verifiable goal. The numbers below come from a Payload project on 7 Jul 2026.

93
lines of spec, the first commit in the repo
1061
lines of implementation plan with tick-off steps
25 min
from spec to first code commit

My own hooks: rules that do not get forgotten

A rule in a text file is a request. A hook is a boundary. All my texts follow style rules, for example forbidden characters such as em and en dashes. The rule was in several project notes, and still on 14 Sep 2026 such dashes were on several live sites. Since then my own PreToolUse hook checks every file write, every shell command and every MCP call before it runs. If it finds a forbidden character, it blocks the call and sends the agent the location with a suggested replacement. The agent fixes it and sends again.

hooks/keine-gedankenstriche.py (shortened)Scroll sidewayspython
MUSTER = re.compile(r"[...]|&[mn]dash;|&#821[12];|&#x201[34];", re.IGNORECASE)

def main():
    try:
        daten = json.load(sys.stdin)
    except Exception:
        return 0
    name = daten.get("tool_name") or ""
    ein = daten.get("tool_input") or {}
    if name == "Write":
        kandidaten = [ein.get("content") or ""]
    # ... Edit, MultiEdit, NotebookEdit und Bash analog
    elif name.startswith("mcp__"):
        kandidaten = list(texte(ein))
    else:
        return 0

    funde = []
    for text in kandidaten:
        for m in MUSTER.finditer(text):
            funde.append(text[max(0, m.start() - 40):m.end() + 25].replace("\n", " "))
    if not funde:
        return 0
    # ... Begründung mit Fundstellen nach stderr
    return 2
My own PreToolUse hook, shortened. Exit code 2 blocks the call, and the text on stderr goes back to the agent as the reason. The character class is left out here; the file contains the characters themselves.

Skills with measured evals

A skill is a reusable instruction set that my coding agent loads for matching tasks. I treat skills like code: the same test tasks run with and without the skill, grading scripts check every output against fixed criteria, and the result is stored as a benchmark file in the eval folder. That shows me whether a skill helps and what it costs. Not every skill wins clearly: in its first round ui-master was only slightly ahead and used 57% more tokens on average. The test sets are small, three to six tasks per round. They do not replace judgement, but they stop me from imagining an improvement.

24% / 94%
a research skill: pass rate without / with the skill, 3 tasks (03/2026)
89% / 94%
ui-master: without / with the skill, 4 tasks (05/2026)
+57%
more tokens per run with ui-master in the same round
37 / 37
ui-master: reworked version, old version 35 / 37, 6 tasks (08/2026)

A wiki as long-term memory

A coding agent forgets almost everything between sessions. So my agents maintain a Markdown wiki based on Andrej Karpathy's LLM wiki pattern. Every project has a short profile page, larger pieces of work end in a synthesis with decisions and findings, and every session ends with an entry in the daily journal, dead ends included. A Bash script regenerates the catalogue of my skills, agents, plugins, MCP servers and hooks from the file system. Agents read the index first and then only the pages they need. That saves tokens and keeps the context small.

577
Markdown pages in the wiki (18 Oct 2026)
317
of them generated catalogue pages
53
daily journal entries since 20 Jul 2026
35
syntheses after larger pieces of work

Multi-agent workflows with clear roles

I split larger tasks across several agents working in parallel. Each has a role: scouts only read, builders write code, reviewers look specifically for errors in facts, security and UI, and a fix round follows. At the end I look at the results myself, and only what I approve goes to production. This portfolio is an example: on 18 Oct 2026 a workflow of five parallel read-only agents took stock of job ads, my projects and the state of the domain. On the same day four workflows with ten agents each worked on my agent platform. Before the deploy, the test suite was green.

5
parallel read-only agents for this portfolio's inventory
392 s
runtime of that inventory
4 x 10
agents on my agent platform the same day

First the image, then the claim

Until 1 Aug 2026 I checked a website with WebGL animations through measured values. The numbers were right while the page was visibly broken on phones. Since then the rule is: every claim about appearance needs an image a human can look at. For that there is a test rig built on Playwright: a real browser with WebGL, five device profiles, comparison against reference images. The same applies to logic with tests. I only say something works once the tests pass and I have seen the result.

pruefstand.mjs (shortened)Scroll sidewaysjavascript
/* Prüfstand: echter Browser, echte Geräteprofile, echte Bilder MIT WebGL-Ebene.
 *
 * Warum es diese Datei gibt: bis 2026-08-01 wurde die Choreografie über
 * `[...].state()` und Helligkeitsmittelwerte aus einem Canvas-Puffer
 * geprüft. Beides sind Stellvertreter. Sie haben bestätigt, dass Zahlen
 * stimmen, während die Seite auf dem Telefon sichtbar kaputt war: Körper auf
 * dem Fließtext, zerrissene Karten-Kompositionen, ein grauer Kasten um die
 * Schlusszeile. Kein Messwert hat davon etwas gemeldet.
 *
 * Regel, die daraus folgt: JEDE Behauptung über das Aussehen braucht ein Bild,
 * das ein Mensch anschauen kann. `state()` ist danach nützlich, um zu erklären
 * WARUM etwas so aussieht, nie, um zu behaupten, dass es gut aussieht.
 */
// ...
const PROFILE = {
  iphone: { ...devices["iPhone 13"] },
  pixel: { ...devices["Pixel 5"] },
  ipad: { ...devices["iPad (gen 7)"] },
  klein: { viewport: { width: 1000, height: 700 }, deviceScaleFactor: 1 },
  desktop: { viewport: { width: 1465, height: 900 }, deviceScaleFactor: 1 }
};
Header and device profiles of the test rig, shortened. One dash in the original is replaced by a comma here, and one object name is left out.
5
device profiles: iPhone 13, Pixel 5, iPad, 1000 px, 1465 px
45
images per profile and run

Reverse engineering, clean-room only

On 18 Oct 2026 I set up REA, a reverse engineering tool that my coding agent uses as an MCP server. On top of that there is Ghidra, whose arm64 decompiler I compiled myself because the official release does not include one. I cannot show a project where I used it yet. I wrote down the limits before the first use: only to understand public behaviour, only publicly reachable pages and open-source demos, no bypassing logins, no third-party code, assets or branding, no test messages into the live chats of my projects. REA is meant to produce a feature description, and the build that follows uses my own code.