Claude Code learns Common Lisp

Claude Code generated the great majority of this website.

Claude Code is a program that runs in your command line whereby you can type prompts and it will generate code according to your goals.

My current goals include learning Common Lisp because I want to better understand a rather intimidating book called Let Over Lambda by Doug Hoyte. Ironically that book is about lisp macros, which is a way to use code to generate code but not the same way Claude Code generates code.

Along the way I've learned some tips and tricks for Claude Code that I'll share here.

Have a workflow

Claude Code truly is an eager assistant and will attempt to fulfill your request all at once even if it's 100s of lines of code! No sane developer would work this way. To temper this excitement, we have a workflow to break feature development into reasonable steps. Our current workflow is:

  1. Write hurl tests for the feature
  2. Write database tests for the feature
  3. Implement the database code until the tests pass
  4. Implement the server code until the tests pass
  5. Reflect on any difficulties and amend the CLAUDE.md
  6. Commit your work

Given my current interest in hypermedia systems we start at the application level. My initial prompt is a discussion about the feature including specific reference to URLs and entities we will be dealing with. I love hurl files because they are a self validating specification of a feature. Having this specification (which I can edit if required) allows Claude Code also to have the context to generate the database tests. The same is true for both steps of the implementation. Having this layered approach where we descend through the application stack writing tests, then ascend writing the implementation, makes it much more difficult for Claude Code to hallucinate. Yes, maybe Claude Code will hallucinate a method in one step, but it's quickly going to be caught in the next step. The reflection step is helpful because Claude Code does get stuck from time to time. It's important to identify these pain points and provide tools to get unstuck in future. The CLAUDE.md file is read by Claude Code at the start of each session so it's a good place to put learned wisdom.

The tools for the job

I expect my Claude Code's favourite tool is

$ tests/hurl/run.sh quick

Which runs all the hurl scripts and prints "Success" if they all pass and prints "Failure" otherwise.

There are two other operating modes:

  • normal which lists all scripts and their success
  • verbose which expects a specific script to be named and will print verbose output to aid debugging.

There are similar scripts for the database tests.

The local version of the project is always running as a service, so we have a script to reload: scripts/reload.sh. We have similar tools for checking recent logs, running compilations etc.

This suite of tools means for the most part I don't need to babysit Claude Code anymore. It will get a reasonable implementation of the feature I describe in a single context.

Vendor your dependencies

No doubt your project will take on dependencies overtime. It's priceless to make the code of those dependencies available to Claude Code in a vendor directory. For example I'm using the Hunchentoot web server and cl-dbi for database integration. The code for both of those (and all their dependencies) are in the vendor directory of our repository. I remember once, Claude Code was desperately trying to guess how to get the last-inserted-id from sqlite using cl-dbi. You can type prompts while Claude Code is working and I just suggested that it look in the vendor/cl-dbi directory and it quickly found the appropriate method. This is also helpful for research because Claude Code can help determine if a given dependency contains certain functionality.

Keep documentation in the repository

Common Lisp and some of the tools I've mentioned aren't widely used. This means the libraries and tools might not be well represented in the training set for Claude Code. It's therefore important to have at least a README for each tool or dependency in a docs folder for Claude Code to reference. For example at the moment we have htmx.md, hurl.md, overtype.md, simple-css.md and so on.

Claude Code can't count parenthesis

You've probably heard the one about LLMs being bad at counting the number of Rs in the strawberry. Well Claude Code is even worse at counting parenthesis in Common Lisp. This is difficult for any human also. Common Lisp developers will use their text editors to enforce parenthetical hygiene. Here's some example lisp

(hunchentoot:define-easy-handler (willow-glossary :uri "/glossary") ()
  (setf (hunchentoot:content-type*) "text/html")
  (let ((user (require-login)))
    (when user
      (render-willow-page "Glossary" (htm-glossary-content)))))

Gun to your head, are the parenthesis balanced? As far as I can tell Claude Code just guesses, especially when editing large forms. We have a tool to only report syntax errors and Claude Code is encouraged to consult the git-diff when running into parenthesis errors but it's probably still not perfect.

In summary

To get the most out of your Claude Code assistant:

  • break down feature development into manageable steps
  • have a suite of scripts to assist development
  • vendor your dependencies
  • provide documentation
  • be supportive and help find solutions to common blockers

So... just be a good senior software engineer?