<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Nick Nisi</title><description>A passionate TypeScript enthusiast, podcast host, and dedicated community builder.</description><link>https://nicknisi.com/</link><language>en-us</language><item><title>Git Workshop</title><link>https://nicknisi.com/posts/2013-08-18-git-workshop/</link><guid isPermaLink="true">https://nicknisi.com/posts/2013-08-18-git-workshop/</guid><pubDate>Sun, 18 Aug 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is the content from a talk I gave at &lt;a href=&quot;http://code.omahamakergroup.org/&quot;&gt;OMG!Code&lt;/a&gt; in August, 2013. I wrote it in the style of a workshop, but ended up
presenting it as a talk instead. It reads as a walkthrough and has an accompanying &lt;a href=&quot;https://github.com/nicknisi/git-workshop&quot;&gt;git repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nicknisi&quot;&gt;@nicknisi&lt;/a&gt; | &lt;a href=&quot;https://github.com/nicknisi/git-workshop&quot;&gt;github.com/nicknisi/git-workshop&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;The original &lt;a href=&quot;http://www.draconianoverlord.com/git-workshop.html&quot;&gt;git workshop&lt;/a&gt;
was presented by Stephen Haberman in 2010 and covered the basics of getting
started with git.&lt;/p&gt;
&lt;h2&gt;Prerequesites&lt;/h2&gt;
&lt;p&gt;You should have a basic understanding of how to use git for this workshop. You should
also have a free account on &lt;a href=&quot;https://github.com&quot;&gt;Github&lt;/a&gt; and be sure you can push to it
either by &lt;a href=&quot;https://help.github.com/articles/generating-ssh-keys&quot;&gt;generating an SSH key&lt;/a&gt;
or by using the &lt;a href=&quot;https://help.github.com/articles/set-up-git&quot;&gt;https method&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not clone or fork this repository yet. We will get to that in the &lt;em&gt;Introducing hub&lt;/em&gt;
section&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The following topics are going to be covered in this workshop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://pcottle.github.io/learnGitBranching/?NODEMO&quot;&gt;Git Branching Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Teaching git about Github with hub&lt;/li&gt;
&lt;li&gt;Finding bugs with bisect&lt;/li&gt;
&lt;li&gt;Configuring git&lt;/li&gt;
&lt;li&gt;Interactive rebase&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Introducing hub&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://hub.github.com/&quot;&gt;hub&lt;/a&gt; is a wrapper around git that teaches git about Github. hub
provides a number of shortcuts and commands that make it easier to work with git projects
hosted on Github. For example, cloning this repository is a lot simpler with hub.
Instead of typing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/nicknisi/git-workshop.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to clone this repo, you just need to run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone nicknisi/git-workshop
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Installing hub&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# install on OS X
$ brew install hub

# other systems
$ curl http://hub.github.com/standalone -sLo ~/bin/hub
$ chmod +x ~/bin/hub

# alias it as git
$ alias git=hub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, hub wraps the git command to extend it with additional functionality. hub
makes working with github URLs a lot easier, but it does a lot more than that.&lt;/p&gt;
&lt;h4&gt;hub Commands&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git fork&lt;/code&gt; - Create a fork of the repository on your Github account&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git pull-request&lt;/code&gt; - Issue a pull request on Github without ever leaving your terminal&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git browse&lt;/code&gt; - Open a web browser straight to the repository page on Github&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git create&lt;/code&gt; - Create a repository out on Github from your terminal&lt;/li&gt;
&lt;li&gt;... and many more!&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Activity: Using hub&lt;/h4&gt;
&lt;p&gt;With hub installed on your machine, create a fork of this repository, Add your name to
the CONTRIBUTERS.md file, and issue a pull request without ever leaving your terminal.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Clone this repository&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone nicknisi/git-workshop
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fork this repository to your Github account: &lt;code&gt;git fork&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This will automatically set up a new remote named `USERNAME`
# where USERNAME is your Github username
git fork
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open up the CONTRIBUTERS.md file in your favorite editor (hopefully vim) and add your name to the list&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Commit and push up the change to your fork&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# USERNAME is your Github username
git commit -a -m &quot;added NAME to the list&quot;`
git push -u USERNAME master
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issue a pull request from the command line back to this repo&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# USERNAME is your Github username
git pull-request -b nicknisi:master -h USERNAME:master
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open up your repo in Github to confirm your commit has been pushed up and a pull
request has been opened&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git browse
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Finding bugs with &lt;code&gt;git bisect&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kernel.org/pub/software/scm/git/docs/git-bisect.html&quot;&gt;bisect&lt;/a&gt; is a fantastic tool to help you discover the exact commit that introduced a bug into
the repository. This works by telling git bisect where a good state is: where your code
is working as expected, and where a bad state is: where the code is broken. git wil then
check out a commit in the middle, and you tell it whether this is in a good state or a
bad state. The bisect command works by performing a binary search between one commit and
another in order to find the exact commit that introduced the bug.&lt;/p&gt;
&lt;p&gt;Performing a git bisect can be either a manual or an automatic process, and it depends on
the state of the repository and the bug that has been introduced. To perform a git
bisect, run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git bisect start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, tell bisect where a bad commit exists&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git bisect bad HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, tell bisect where a good commit is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git bisect good 53cb134
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Git will then check out a commit between these two commits and you can manually rerun
whatever steps or tests are necessary to reproduce the issue. If you can reproduce the
issue, run &lt;code&gt;git bisect bad&lt;/code&gt; and &lt;code&gt;git bisect good&lt;/code&gt; if you are unable to reproduce it. Git
will then pick another issue based on your response until there are no more commits left
to check. At this point, git will inform you of the commit that introduced the issue&lt;/p&gt;
&lt;h4&gt;Activity: Where did that bug come from?&lt;/h4&gt;
&lt;p&gt;In this activity, we are presented with a pretty simple HTML file that is supposed to display a simple square and a slider.
When the slider is changed, the square should change colors, based on the input of from the slider. At the latest commit in
the bisect branch, we are getting no JavaScript errors, and we are not seeing a slider.&lt;/p&gt;
&lt;p&gt;We know that the code is working as intended at &lt;a href=&quot;https://github.com/nicknisi/git-workshop/commit/37d0821049bd94bdc5aaa357311d4a6711d4b16d&quot;&gt;37d0821&lt;/a&gt;. Let&apos;s use &lt;code&gt;git bisect&lt;/code&gt; to determine where things went wrong.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Checkout the &lt;a href=&quot;https://github.com/nicknisi/git-workshop/tree/bisect&quot;&gt;bisect&lt;/a&gt; branch&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout bisect
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Look at the &lt;em&gt;index.html&lt;/em&gt; file in a browser and test the file to how it is failing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Look through the log to determine where the last known good state of the application is (I have marked this commit with
the commit message &quot;GOOD STATE&quot;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git log --graph --pretty=oneline --abbrev-commit --decorate
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start up bisect&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git bisect start
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alert bisect of the bad commit&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git bisect bad HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alert bisect of the good commit&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git bisect good 37d0821
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;git will now pick a commit between the two and check it out. Reload the page in the browser and determine if the slider
is working properly (showing up).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If it is showing up, &lt;code&gt;git bisect good&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If it is not showing up, &lt;code&gt;git bisect bad&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After each command, git will checkout a new commit until it has determined the exact commit that introduced the bug&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you have determined the bad commit, you should be presented with a message like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ git bisect bad                                                                                                       ✔
9378fec...|bisect
9378fec280242b4a02b6972c3ca9b61cdf14cad5 is the first bad commit
commit 9378fec280242b4a02b6972c3ca9b61cdf14cad5
Author: Nick Nisi &amp;lt;nick@nisi.org&amp;gt;
Date:   Tue Aug 13 00:05:23 2013 -0500

    adjust animation speed slowing down again

    :100644 100644 2bf5ba28648e5c388556b37701addac90d9daf20 63ec399ce0f457bd17e5e6933a0a140c1435223d M      index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To end bisect at any time reset it&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git bisect reset
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Following these steps, we are able to quickly determine which is the &lt;a href=&quot;https://github.com/nicknisi/git-workshop/commit/9378fec280242b4a02b6972c3ca9b61cdf14cad5&quot;&gt;bad
commit&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Automating bisect&lt;/h4&gt;
&lt;p&gt;The process of finding a bad commit with bisect can be automated if the problem you are trying to find can be tested with a
script. Using &lt;code&gt;git bisect run&lt;/code&gt;, we can provide a command or a script to execute against each commit that bisect checks. If
ths script returns 0 then the commit is considered &lt;code&gt;good&lt;/code&gt;. Otherwise, it is considered &lt;code&gt;bad&lt;/code&gt;. This can be really useful if
you are trying to track down what commit introduced a failing unit test or build.&lt;/p&gt;
&lt;h3&gt;Configuring git&lt;/h3&gt;
&lt;p&gt;The configuration of git can be customized by combining actions and creating shortcuts to simplify your git workflow.&lt;/p&gt;
&lt;h4&gt;Advanced aliases&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/nicknisi/dotfiles/blob/master/git/gitconfig.symlink&quot;&gt;&lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/a&gt; can include a number of custom
aliases. These can be anything from shortening simple git commands to running advanced shell scripts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[alias]
    co = checkout
    s = status --short
    br = branch -v

    # slight variation of pretty log graph
    l = log --graph --pretty=format:&apos;%Cred%h%Creset %an: %s - %Creset %C(yellow)%d%Creset %Cgreen(%cr)%Creset&apos; --abbrev-commit --date=relative

    # show changed files for a commit
    cf = show --pretty=&quot;format:&quot; --name-only

    # show what I did today
    day = &quot;!sh -c &apos;git log --reverse --no-merges --branches=* --date=local --after=\&quot;yesterday 11:59PM\&quot; --author=\&quot;`git config --get user.name`\&quot;&apos;&quot;

    # show number of commits per contributer, sorted
    count = shortlog -sn

    # YOLO
    yolo = !git commit -am &quot;DEAL WITH IT&quot; &amp;amp;&amp;amp; git push -f origin master &amp;amp;&amp;amp; shutdown -r now &quot;DEAL WITH IT&quot;

    undo = reset --soft HEAD~1
    amend = commit --amend -C HEAD

    # rebase the current branch with changes from upstream remote
    update = !git fetch upstream &amp;amp;&amp;amp; git rebase upstream/`git rev-parse --abbrev-ref HEAD`

    ## grep commands

    # &apos;diff grep&apos;
    dg = &quot;!sh -c &apos;git ls-files -m | grep $1 | xargs git diff&apos; -&quot;
    # &apos;checkout grep&apos;
    cg = &quot;!sh -c &apos;git ls-files -m | grep $1 | xargs git checkout &apos; -&quot;
    # add grep
    ag = &quot;!sh -c &apos;git ls-files -m -o --exclude-standard | grep $1 | xargs git add&apos; -&quot;
    # add all
    aa = !git ls-files -d | xargs git rm &amp;amp;&amp;amp; git ls-files -m -o --exclude-standard | xargs git add
    # remove grep - Remove found files that are NOT under version control
    rg = &quot;!sh -c &apos;git ls-files --others --exclude-standard | grep $1 | xargs rm&apos; -&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Reuse recorded resolution: rerere&lt;/h4&gt;
&lt;p&gt;If you have a long-running branch, git can remember how you resolved merge conflicts and can reapply how you merged the files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[rerere]
    enabled = true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Git hooks&lt;/h4&gt;
&lt;p&gt;Git hooks allow you to run scripts before or after events in git such as commit, push, and receive. Hooks are configured per repository and are located in the &lt;code&gt;.git/hooks&lt;/code&gt; directory. Because they live in the git database directory, they are not part of your repository and can be set up independently. Here are just a few of the available commit hooks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pre-commit - run before a commit to the repository&lt;/li&gt;
&lt;li&gt;post-checkout - after a checkout which includes, cloning, switching branches, or resetting files&lt;/li&gt;
&lt;li&gt;commit-msg - run after a commit message is entered&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are also server-side commit hooks. These can be utilized to kick off builds, run deployments, run
continuous integration, and many other things. Github has integration with third party services when
commits happen and you can also set up a route for Github to post to on certain actions.&lt;/p&gt;
&lt;h4&gt;Activity: Adding a commit-hook&lt;/h4&gt;
&lt;p&gt;When a new repository is created or cloned, git will copy over example commit hooks into the repository&apos;s &lt;code&gt;.git/hooks&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ ll .git/hooks                                                                                                                   ✔ master
total 80
-rwxr-xr-x  1 nicknisi  staff   452B Jul  9 22:45 applypatch-msg.sample*
-rwxr-xr-x  1 nicknisi  staff   896B Jul  9 22:45 commit-msg.sample*
-rwxr-xr-x  1 nicknisi  staff   189B Jul  9 22:45 post-update.sample*
-rwxr-xr-x  1 nicknisi  staff   398B Jul  9 22:45 pre-applypatch.sample*
-rwxr-xr-x  1 nicknisi  staff   1.7K Jul  9 22:45 pre-commit.sample*
-rwxr-xr-x  1 nicknisi  staff   1.3K Jul  9 22:45 pre-push.sample*
-rwxr-xr-x  1 nicknisi  staff   4.8K Jul  9 22:45 pre-rebase.sample*
-rwxr-xr-x  1 nicknisi  staff   1.2K Jul  9 22:45 prepare-commit-msg.sample*
-rwxr-xr-x  1 nicknisi  staff   3.5K Jul  9 22:45 update.sample*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can look at and use these files for inspriation when creating your git hook. Hooks can be written in any
language that can be called from the command line. If the script returns 0, the hook is considered successful.
Otherwise, it has failed. Let&apos;s add a git hook to show us info about our repository when we switch branches.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a file called &lt;code&gt;post-checkout&lt;/code&gt; in the &lt;code&gt;.git/hooks&lt;/code&gt; directory and
execute permissions&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;touch .git/hooks/post-checkout
chmod +x .git/hooks/post-checkout
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Set the contents of the file&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

echo &quot;&quot;
echo &quot;&quot;
echo -e &quot;\033[1m RECENT COMMITS \033[0m&quot;
git --no-pager log -n5 --graph --pretty=format:&apos;%Cred%h%Creset %an: %s - %Creset %C(yellow)%d%Creset %Cgreen(%cr)%Creset&apos; --abbrev-commit --date=relative
echo &quot;
echo &quot;&quot;]]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Back in the repository, create a new branch and notice that you will get a
summary of the last 5 commits to the repository.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git checkout -b new-branch
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Managing git hooks&lt;/h4&gt;
&lt;p&gt;Git hooks are excluded from the repository and this is both a blessing and a
curse. It allows developers to setup their own hooks without affecting anyone
else. However, that means that it is typically a manual process of storing the
commit hook in a safe place and then manually copying it over into the newly
created or cloned repo.&lt;/p&gt;
&lt;p&gt;As previously pointed out, git copies a number of &lt;em&gt;sample&lt;/em&gt; commit hooks into
each repository, and we can hijack this to add our own commit hooks! These
sample files live at &lt;code&gt;/usr/share/git-core/templates&lt;/code&gt; in the &lt;code&gt;hooks&lt;/code&gt; directory.
We can configure git to change where this directory exists and override it
with our own defaults, allowing us to manage this directory independently and
keep under source control in our &lt;a href=&quot;http://dotfiles.github.io/&quot;&gt;dotfiles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To change where git looks for this templates directory, add the following to
your &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[init]
    templatedir = ~/.dotfiles/git/templates
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, whenever we clone or init a new repository, we will be set up with our
own, custom git hooks that have been copied from the above path.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the hooks are copied, meaning that any revisions to them later will
not be copied over. However, running &lt;code&gt;git init&lt;/code&gt; in an existing repository
will copy the updated hooks over to the repo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Interactive rebase&lt;/h3&gt;
&lt;p&gt;Remember that nasty bisect branch with its many commits changing the same thing
over and over? Wouldn&apos;t it be nice if we could clean up our log so it doesn&apos;t
look so nasty?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* fe321d9 Nick Nisi: adjust animation to 320 -   (HEAD, origin/bisect, bisect) (17 hours ago)
* 900b366 Nick Nisi: adjust animation to 319 -   (17 hours ago)
* a5ba9ac Nick Nisi: adjust animation to 318 -   (17 hours ago)
* f84b4cd Nick Nisi: adjust animation to 317 -   (17 hours ago)
* 70f7912 Nick Nisi: adjust animation to 316 -   (17 hours ago)
* 442c007 Nick Nisi: adjust animation to 314 -   (17 hours ago)
* 7fd99d5 Nick Nisi: adjust animation to 313 -   (17 hours ago)
* 507d90e Nick Nisi: adjust animation to 312 -   (17 hours ago)
* 76162b0 Nick Nisi: adjust animation to 311 -   (17 hours ago)
* 89390fa Nick Nisi: adjust animation to 310 -   (17 hours ago)
* 2f3c3fc Nick Nisi: adjust animation to 309 -   (17 hours ago)
* 18ce973 Nick Nisi: adjust animation to 308 -   (17 hours ago)
* 448e9e2 Nick Nisi: adjust animation to 307 -   (17 hours ago)
* f7a452d Nick Nisi: adjust animation to 306 -   (17 hours ago)
* e23d9f8 Nick Nisi: adjust animation to 305 -   (17 hours ago)
* 72eb0c2 Nick Nisi: adjust animation to 304 -   (17 hours ago)
* 5e4cee6 Nick Nisi: adjust animation to 303 -   (17 hours ago)
* 4f88c8d Nick Nisi: adjust animation to 302 -   (17 hours ago)
* 9378fec Nick Nisi: adjust animation speed slowing down again -   (17 hours ago)
* cc62c6f Nick Nisi: adjust animation speed slowing down -   (17 hours ago)
* faa0412 Nick Nisi: adjust animation speed speed up again -   (17 hours ago)
* 4e024b6 Nick Nisi: adjust animation speed speed up -   (17 hours ago)
* 983eb1f Nick Nisi: adjust animation speed speed up -   (17 hours ago)
* 00f4ca5 Nick Nisi: adjust animation speed speed up -   (17 hours ago)
* 06badcb Nick Nisi: adjust animation speed -   (17 hours ago)
* d1ebc8e Nick Nisi: adjust box size -   (17 hours ago)
* d3d2d4d Nick Nisi: change box size -   (17 hours ago)
* ecf34f6 Nick Nisi: change border color -   (17 hours ago)
* de9bb1f Nick Nisi: add h2 tag contents -   (17 hours ago)
* 2210aca Nick Nisi: add h2 tag -   (17 hours ago)
* 37d0821 Nick Nisi: GOOD STATE -   (17 hours ago)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The good news is that we can! The bad news is that it needs to be used with caution.
The &lt;a href=&quot;https://help.github.com/articles/interactive-rebase&quot;&gt;Github Help page&lt;/a&gt; states it best:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: It is considered bad practice to rebase commits which you have already pushed to a remote repository. Doing so may invoke the wrath of the git gods.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you run an interactive rebase, you are changing your git history. Commits will be
assigned new SHAs. If you have already pushed up and someone else has pulled down the changes, it will mess up their repository if you change the history out from under them.&lt;/p&gt;
&lt;p&gt;Running `git rebase -i HEAD 4f88c8d will pop us into our editor and list all of the commits within the provided range.
Each commit will have the word &lt;em&gt;pick&lt;/em&gt; next to it, meaning that we are picking this commit (leaving it as it is).
The other options are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reword - use commit but edit the commit message&lt;/li&gt;
&lt;li&gt;edit - use commit, but stop for amending&lt;/li&gt;
&lt;li&gt;squash - use commit, but meld into previous commit&lt;/li&gt;
&lt;li&gt;fixup - like &quot;squash&quot;, but discard this commit&apos;s log message&lt;/li&gt;
&lt;li&gt;exec - run command&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can use the squash, pick, and fixup commands to simplify the repository.&lt;/p&gt;
&lt;h4&gt;Activity: Clean the history&lt;/h4&gt;
&lt;p&gt;Let&apos;s use our new knowledge of interactive rebase to clean up the bisect branch.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Let&apos;s branch off of bisect and do this in a new branch&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git checkout bisect
git checkout -b rebase
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Enter into an interactive rebase, down to the &quot;GOOD STATE&quot; commit&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git rebase -i 37d0821
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;For each of the commits that have a similar message, select the top one to &quot;pick&quot;. For the rest, select &quot;squash&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;pick 2210aca add h2 tag
pick de9bb1f add h2 tag contents
pick ecf34f6 change border color
pick d3d2d4d change box size
pick d1ebc8e adjust box size
pick 06badcb adjust animation speed
pick 00f4ca5 adjust animation speed speed up
pick 983eb1f adjust animation speed speed up
pick 4e024b6 adjust animation speed speed up
pick faa0412 adjust animation speed speed up again
pick cc62c6f adjust animation speed slowing down
pick 9378fec adjust animation speed slowing down again
pick 4f88c8d adjust animation to 302
squash 5e4cee6 adjust animation to 303
squash 72eb0c2 adjust animation to 304
squash e23d9f8 adjust animation to 305
squash f7a452d adjust animation to 306
squash 448e9e2 adjust animation to 307
squash 18ce973 adjust animation to 308
squash 2f3c3fc adjust animation to 309
squash 89390fa adjust animation to 310
squash 76162b0 adjust animation to 311
squash 507d90e adjust animation to 312
squash 7fd99d5 adjust animation to 313
squash 442c007 adjust animation to 314
squash 70f7912 adjust animation to 316
squash f84b4cd adjust animation to 317
squash a5ba9ac adjust animation to 318
squash 900b366 adjust animation to 319
squash fe321d9 adjust animation to 320
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Complete by saving and closing your editor&lt;/li&gt;
&lt;li&gt;Look at the new history in the log&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;* db86962 Nick Nisi: adjust animation to 302 -   (HEAD, rebase) (4 seconds ago)
* 9378fec Nick Nisi: adjust animation speed slowing down again -   (18 hours ago)
* cc62c6f Nick Nisi: adjust animation speed slowing down -   (18 hours ago)
* faa0412 Nick Nisi: adjust animation speed speed up again -   (18 hours ago)
* 4e024b6 Nick Nisi: adjust animation speed speed up -   (18 hours ago)
* 983eb1f Nick Nisi: adjust animation speed speed up -   (18 hours ago)
* 00f4ca5 Nick Nisi: adjust animation speed speed up -   (18 hours ago)
* 06badcb Nick Nisi: adjust animation speed -   (18 hours ago)
* d1ebc8e Nick Nisi: adjust box size -   (18 hours ago)
* d3d2d4d Nick Nisi: change box size -   (18 hours ago)
* ecf34f6 Nick Nisi: change border color -   (18 hours ago)
* de9bb1f Nick Nisi: add h2 tag contents -   (18 hours ago)
* 2210aca Nick Nisi: add h2 tag -   (18 hours ago)
* 37d0821 Nick Nisi: GOOD STATE -   (18 hours ago)
* a36751a Nick Nisi: add border to color box -   (18 hours ago)
* c287e19 Nick Nisi: fixing slider values -   (18 hours ago)
* 6f4edca Nick Nisi: messing with slider values -   (18 hours ago)
* 225c4ef Nick Nisi: adding width style -   (18 hours ago)
* 09ac64d Nick Nisi: adding height style -   (18 hours ago)
* 6148238 Nick Nisi: removed height/width -   (18 hours ago)
* d1e1701 Nick Nisi: removing border -   (18 hours ago)
* 2c9df68 Nick Nisi: adding cdn scripts -   (18 hours ago)
* 0763e15 Nick Nisi: initial commit of index -   (18 hours ago)
* 9f0ec0c Nick Nisi: Add editorconfig -   (20 hours ago)
* 39aad97 Nick Nisi: updating hub instructions -   (20 hours ago)
* 2922ce8 Nick Nisi: Added README and hub walkthrough -   (21 hours ago)
* 5fb2d99 Nick Nisi: Rename LICENSE.md to LICENSE -   (21 hours ago)
* 53cb134 Nick Nisi: Create LICENSE.md -   (21 hours ago)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Learning more&lt;/h3&gt;
&lt;p&gt;We&apos;ve only briefly covered a few of the really cool tricks you can do with git. Want to learn more? Check out these great resources!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.gitminutes.com/&quot;&gt;GitMinutes&lt;/a&gt; - A podcast for proficient git users&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://zachholman.com/talk/git-github-secrets/&quot;&gt;Git and Github Secrets&lt;/a&gt; - A talk by Zach Holman of Github&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://zachholman.com/talk/more-git-and-github-secrets/&quot;&gt;More Git and Github Secrets&lt;/a&gt; - More!&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://gitready.com/&quot;&gt;GitReady&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Peruse &lt;a href=&quot;https://github.com/nicknisi/dotfiles&quot;&gt;dotfiles&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Git: Update a forked repository</title><link>https://nicknisi.com/posts/2014-08-24-git-update-fork/</link><guid isPermaLink="true">https://nicknisi.com/posts/2014-08-24-git-update-fork/</guid><pubDate>Sun, 24 Aug 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Github is great! It is so easy to fork a project, push up some commits, and then send a pull request upstream. After a while, those forks can get behind the source repository, making it difficult to submit a new pull request later on. One thought might be to delete the fork and then re-fork the project, but there is an easier way!&lt;/p&gt;
&lt;h2&gt;Syncing a fork&lt;/h2&gt;
&lt;p&gt;Syncing a fork is easy to do from the command line. With a clone of the fork, we can create a new remote that points to source repository on Github.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$&amp;gt; git remote add upstream git@github.com:theintern/intern.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will add a new remote named &lt;code&gt;upstream&lt;/code&gt;, which points to the source repository. Next, fetch the latest from the upstream repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$&amp;gt; git fetch upstream
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, apply the upstream changes to our local copy of master (or whatever branch you are on).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$&amp;gt; git rebase upstream/master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, the local copy of our branch has now been updated with the latest code from the usptream repository, meaning that we are now up today. Finally, we just need to push these changes up to our &lt;code&gt;origin&lt;/code&gt; remote.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$&amp;gt; git push origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it! We have now successfully updated our fork of a project without having to delete the repository and start over.&lt;/p&gt;
&lt;h2&gt;But that&apos;s so many steps...&lt;/h2&gt;
&lt;p&gt;It can be difficult to remember all of the commands needed for git actions. Luckily, git gives us a way to configure aliases to common or complex actions. Using this and a little convention, we can make a nice alias to reduce the steps above to a single step. Git makes it easy to set up as many remotes as we need, so we can easily pull from an &lt;code&gt;upstream&lt;/code&gt; remote and then update our &lt;code&gt;origin&lt;/code&gt; remote.To make a command that can work in any repository, we need to follow a few conventions when it comes to naming our remotes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;origin&lt;/code&gt; - The main repository to push to (our fork)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;upstream&lt;/code&gt; The main project repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This way, the alias knows which remote to fetch, and which remote to push to. With this convention in place, we can create an alias in our &lt;code&gt;~/.gitonfig&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[alias]
	update = !git fetch upstream &amp;amp;&amp;amp; git rebase upstream/master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works great, except that it always assumes that we only want to update the &lt;code&gt;master&lt;/code&gt; branch, which isn&apos;t ideal. There is a way to get the current branch that we have checked out from a simple command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$&amp;gt; git rev-parse --abbrev-ref HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Using this within the alias gives us a great shortcut to updating &lt;code&gt;origin&lt;/code&gt; with the latest from the &lt;code&gt;upstream&lt;/code&gt; remote.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[alias]
    update = !git fetch upstream &amp;amp;&amp;amp; git rebase upstream/`git rev-parse --abbrev-ref HEAD`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This condenses the number of steps down to two:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git update&lt;/code&gt; to update our local branch with the latest from the &lt;code&gt;upstream&lt;/code&gt; remote&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git push&lt;/code&gt; to push those changes up to the &lt;code&gt;origin&lt;/code&gt; remote, updating the fork&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Aliases are a great way to condense complex actions down to simple commands or to reduce the amount of typing required to execute a command. For more awesome git aliases, check out my &lt;a href=&quot;https://github.com/nicknisi/dotfiles/blob/master/git/gitconfig.symlink&quot;&gt;&lt;code&gt;.gitconfig&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded></item><item><title>Git Your Way: includeIf</title><link>https://nicknisi.com/posts/git-includeif/</link><guid isPermaLink="true">https://nicknisi.com/posts/git-includeif/</guid><description>Implement finer control of your Git configuration.</description><pubDate>Sat, 30 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;As a developer who works on many projects, including client and open source projects, I often need to be mindful of how I commit to each project. That is, company/client projects should always use my work email address when committing, and for open source I would prefer to use my personal email address.&lt;/p&gt;
&lt;p&gt;This can be set up on a per-project basis by simply running &lt;code&gt;git config user.email &amp;lt;EMAIL&amp;gt;&lt;/code&gt; from within each project once and it will be set . The problem with this is that I actually have to remember to run this command from each project or risk accidentally committing with the wrong email address. Fortunately, git has a built-in method of helping with this.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Let’s say you have the following directory structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;|-- work
|-- oss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All of your work projects (where you want to use your work email in commits) is in the &lt;code&gt;./work&lt;/code&gt; directory, and all of the open source is (you guessed it) in &lt;code&gt;./oss&lt;/code&gt;. In your &lt;code&gt;~/.gitconfig&lt;/code&gt; there is a specifier called &lt;a href=&quot;https://git-scm.com/docs/git-config/2.15.4#_includes&quot;&gt;&lt;code&gt;includeIf&lt;/code&gt;&lt;/a&gt; that can be configured to load another git configuration file based on where the git repository lives on your file system.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# inside of ~/.gitconfig
[includeIf &quot;gitdir:~/work/&quot;]
  path = ~/.gitconfig-work
[includeIf &quot;gitdir:~/oss/&quot;]
  path = ~/.gitconfig-oss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will only source these sub-configuration files if you are working on a git repo inside of one of the specified directories. Then, inside of those config files is where you can specify work or oss-specific configuration, such as the email address to use, which will then always be used for any project within those specified directories.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# inside of ~/.gitconfig-work
[user]
  name = Nick Nisi
  email = nick@work.co
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How I Use It&lt;/h2&gt;
&lt;p&gt;To see this and other fun git/vim/zsh/tmux tips in action, check out my &lt;a href=&quot;https://github.com/nicknisi/dotfiles&quot;&gt;dotfiles&lt;/a&gt;. For this specific example, I don’t actually use it directly in my dotfiles, but instead I do it in a &lt;a href=&quot;https://github.com/nicknisi/dotfiles/blob/d9a4bb96139168f9f5813064445873b5fba221a7/git/gitconfig.symlink#L4-L7&quot;&gt;&lt;code&gt;~/.dotfiles-local&lt;/code&gt;&lt;/a&gt; file. I reference this from within my dotfiles as a way to not check-in references to my work-specific configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[include]
  path = ~/.gitconfig-local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will include my local configuration file and will silently ignore this line if the file doesn’t actually exist.&lt;/p&gt;
&lt;p&gt;And that’s it! Setting this up ensures that you never accidentally commit with the wrong email address in projects, and also gives you a single place to further customize your git configuration of a project-type basis.&lt;/p&gt;
</content:encoded></item><item><title>Git: Managing hooks</title><link>https://nicknisi.com/posts/2014-09-01-managing-git-hooks/</link><guid isPermaLink="true">https://nicknisi.com/posts/2014-09-01-managing-git-hooks/</guid><pubDate>Mon, 01 Sep 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Git hooks are custom scripts that can be fired off when different actions occur, and they can be run on either the client (your machine) or the server (the git remote). In this post, I&apos;ll be talking about client side hooks. The available client side hooks include &lt;code&gt;prepare-commit-msg&lt;/code&gt;, &lt;code&gt;pre-commit&lt;/code&gt;, &lt;code&gt;commit-msg&lt;/code&gt;, &lt;code&gt;pre-rebase&lt;/code&gt;, &lt;code&gt;post-merge&lt;/code&gt;, and many more. These can be used to automate specific tasks like checking over the commit message before the commit is accepted to &lt;a href=&quot;2012-11-12-lint-javascript-on-commit&quot;&gt;linting your JavaScript&lt;/a&gt; before it can be committed. The hooks can be written in any language that the system supports, and they can be easily used within any git project, new or existing. The biggest pain point with commit hooks is managing them. They exist in of &lt;code&gt;.git/&lt;/code&gt; and are not actally part of the repository.&lt;/p&gt;
&lt;h2&gt;Setting up the hooks&lt;/h2&gt;
&lt;p&gt;The hooks are contained within the &lt;code&gt;.git/hooks&lt;/code&gt;. By default, git will provide a number of sample scripts, which can be removed or renamed and modified to fit your needs.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Removing the &lt;code&gt;.sample&lt;/code&gt; extension from any of these files will activate them. Then, they can be modified to perform whatever task is necessary. for example, adding a file called &lt;code&gt;post-checkout&lt;/code&gt; with the following will cause git to display the 5 most recent commits to the branch checked out when running &lt;code&gt;git checkout&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

echo &quot;&quot;
echo &quot;&quot;
echo -e &quot;\033[1m RECENT COMMITS \033[0m&quot;
git --no-pager log -n5 --graph --pretty=format:&apos;%Cred%h%Creset %an: %s - %Creset %C(yellow)%d%Creset %Cgreen(%cr)%Creset&apos; --abbrev-commit --date=relative
echo &quot;&quot;
echo &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Managing git hooks&lt;/h2&gt;
&lt;p&gt;Git hooks can be tedious to manage in multiple projects. It can be cumbersome to copy them from one project to another and to store them in a repository. Luckily, git does provide a way to simplify this. When running &lt;code&gt;git init&lt;/code&gt;, git copies the sample hook scripts from a directory in to &lt;code&gt;.git/hooks&lt;/code&gt;. It copies whatever is in that directory, and we can use that to copy over the hook scripts by default. To do this, change the location of &lt;code&gt;templatedir&lt;/code&gt; directory in your &lt;a href=&quot;https://github.com/nicknisi/dotfiles/blob/master/git/gitconfig.symlink#L7-L8&quot;&gt;&lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[init]
    templatedir = ~/.dotfiles/git/templates
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, you can manage your git hook scripts from another repository, such as your dotfiles and they wil be copied over to each new project. To add them to an existing project, just execute &lt;code&gt;git init&lt;/code&gt; and they will be copied over.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Please note that they will not overwrite files that already exist in the &lt;code&gt;.git/hooks&lt;/code&gt; directory, so you will need to delete them first to completely replace them with more up-to-date scripts.&lt;/p&gt;
&lt;p&gt;This is an easy way to get started managing git hooks in a simple, reusable way. In a later post, I will discuss further how I manage git hooks.&lt;/p&gt;
</content:encoded></item><item><title>Lint JavaScript on Commit</title><link>https://nicknisi.com/posts/2012-11-12-lint-javascript-on-commit/</link><guid isPermaLink="true">https://nicknisi.com/posts/2012-11-12-lint-javascript-on-commit/</guid><pubDate>Mon, 12 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My team has been working a lot to improve our code quality and to introduce best practices between us. One way we&apos;ve done this is through the use of
&lt;a href=&quot;http://jshint.com&quot;&gt;JSHint&lt;/a&gt;. Because we use different editors it can be difficult to make sure that everyone&apos;s environment is configured to analyze
code the same way, but git is a common tool between us. Here is a pre-commit hook, which checks all .js files included in the commit against your
JSHint configuration. If it doesn&apos;t pass, the errors are printed to the screen and the commit is cancelled.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
# JSHint Pre-Commit
# Place this in your .git/hooks/pre-commit directory and rename to `pre-commit`
# expects jshint to be installed in your projects node_modules directory

EXIT_CODE=0
COLOR_RED=&quot;\x1B[31m&quot;
COLOR_GREEN=&quot;\x1B[32m&quot;
COLOR_NONE=&quot;\x1B[0m&quot;

repo=$( git rev-parse --show-toplevel )
jshint=${repo}/node_modules/jshint/bin/hint

for file in $( exec git diff-index --cached --name-only HEAD ); do
	if [[ $file == *&quot;.js&quot;* ]]; then
		status=$( exec git status --porcelain $file )

		if [[ $status != D* ]]; then
			# ${jshint} ${repo}/${file} &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
			${jshint} ${repo}/${file}
			EXIT_CODE=$((${EXIT_CODE} + $?))
		fi
	fi
done

echo &quot;&quot;
if [[ ${EXIT_CODE} -ne 0 ]]; then
	echo &quot;${COLOR_RED}✘ JSHINT detected syntax problems.${COLOR_NONE}&quot;
else
	echo &quot;${COLOR_GREEN}✔ JSHINT detected no errors.${COLOR_NONE}&quot;
fi

exit $((${EXIT_CODE}))
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Very Important Agents</title><link>https://nicknisi.com/posts/very-important-agents/</link><guid isPermaLink="true">https://nicknisi.com/posts/very-important-agents/</guid><description>My recent Changelog and Friends podcast appearance and the Claude Code plugins that help me get real work done with AI.</description><pubDate>Mon, 08 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently appeared on &lt;a href=&quot;https://changelog.com/friends/120&quot;&gt;Changelog and Friends&lt;/a&gt; to talk about agents, the Bun acquisition by Anthropic, and how I&apos;ve been using Claude Code in my daily workflow. The conversation kept circling back to a question I find interesting: how do we use AI to get real work done without producing generic &lt;em&gt;slop&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;I&apos;ve been building toward an answer - two custom Claude Code plugins called &lt;a href=&quot;https://github.com/nicknisi/claude-plugins/tree/main/plugins/essentials&quot;&gt;Essentials&lt;/a&gt; and &lt;a href=&quot;https://github.com/nicknisi/claude-plugins/tree/main/plugins/ideation&quot;&gt;Ideation&lt;/a&gt; that help me stay productive while keeping my code and ideas authentically mine. Rather than let that podcast conversation disappear into the archives, I wanted to share what I&apos;ve learned.&lt;/p&gt;
&lt;h2&gt;Keeping Code Clean with &lt;a href=&quot;https://github.com/nicknisi/claude-plugins/tree/main/plugins/essentials&quot;&gt;Essentials&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Essentials plugin is my first line of defense against code complexity. It has two components I reach for constantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/nicknisi/claude-plugins/blob/main/plugins/essentials/agents/code-simplifier.md&quot;&gt;Code Simplifier&lt;/a&gt;&lt;/strong&gt; is an agent that refactors code to improve readability without changing functionality. I recently used it on a nested callback situation that had grown organically over several iterations. You know the type - it started simple, then requirements changed, then edge cases appeared, and suddenly you&apos;re staring at something that works but makes your brain hurt. The agent untangled it into something I could actually follow. Same behavior, less cognitive load.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/nicknisi/claude-plugins/blob/main/plugins/essentials/commands/de-slopify.md&quot;&gt;de-slopify&lt;/a&gt;&lt;/strong&gt; is a command that removes AI-generated patterns from code. After running Claude on a feature, I sometimes notice overly verbose comments, unnecessary abstractions, or redundant explanations that scream &quot;an AI wrote this.&quot; de-slopify catches these patterns and strips them out. The result is code that looks like a human wrote it - because the important parts are still mine, just without the AI fingerprints.&lt;/p&gt;
&lt;p&gt;This might seem contradictory. Using AI to remove evidence of AI? But that&apos;s exactly the point. I want AI assistance with the mechanics, not AI aesthetics polluting my codebase.&lt;/p&gt;
&lt;h2&gt;From Brain Dump to Structure with &lt;a href=&quot;https://github.com/nicknisi/claude-plugins/tree/main/plugins/ideation&quot;&gt;Ideation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here&apos;s where things get meta. The &lt;a href=&quot;https://github.com/nicknisi/claude-plugins/blob/main/plugins/ideation/skills/ideation/SKILL.md&quot;&gt;Ideation skill&lt;/a&gt; takes messy stream-of-consciousness thoughts and transforms them into structured artifacts.&lt;/p&gt;
&lt;p&gt;I&apos;m literally using this plugin right now to write this post.&lt;/p&gt;
&lt;p&gt;That&apos;s not a hypothetical. It&apos;s the truth.&lt;/p&gt;
&lt;p&gt;My process started with a voice dictation where I rambled about the podcast, what I wanted to cover, and why authenticity matters when using AI for content. Just scattered thoughts, no structure. The Ideation plugin took that brain dump and produced a contract (what I&apos;m committing to), a PRD (what the post needs to accomplish), and a spec (how to actually write it).&lt;/p&gt;
&lt;p&gt;The value here isn&apos;t that AI wrote my outline. It&apos;s that AI removed the blank page problem. I went from &quot;I should write about this podcast&quot; to having a clear structure to follow in minutes instead of days. The ideas are still mine. The organization just got easier.&lt;/p&gt;
&lt;h2&gt;AI as Amplifier, Not Replacement&lt;/h2&gt;
&lt;p&gt;These plugins represent a philosophy I keep coming back to: AI should amplify your capabilities, not replace your judgment.&lt;/p&gt;
&lt;p&gt;There are two problems I&apos;m trying to solve. First, the barrier problem. I have ideas and things I want to build. But life is busy, and the gap between &quot;I should do this&quot; and actually having a plan is often too wide to cross. AI can lower that barrier by handling the organizational grunt work.&lt;/p&gt;
&lt;p&gt;Second, the slop problem. AI-generated code often has a distinctive smell - overly verbose, over-abstracted, over-commented. I don&apos;t want that in my codebase. I want to use AI in a way that makes me more productive without making my code less mine.&lt;/p&gt;
&lt;p&gt;The plugins I&apos;ve built are my answer to both problems. Essentials keeps my code clean. Ideation turns chaos into structure. Each one is intentionally designed to amplify rather than replace.&lt;/p&gt;
&lt;p&gt;The key is being intentional. Use AI for the right things. Use it in ways that lower barriers without raising slop levels. Use it as an amplifier, not a replacement.&lt;/p&gt;
&lt;h2&gt;Try It Yourself&lt;/h2&gt;
&lt;p&gt;All of these plugins live in my &lt;a href=&quot;https://github.com/nicknisi/claude-plugins&quot;&gt;Claude plugins marketplace&lt;/a&gt;. You can install the whole collection with a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/plugin marketplace add nicknisi/claude-plugins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gives you access to Essentials, Ideation, and a few other plugins I&apos;ve been building. Browse the repo if you want to see how they work under the hood - or just install and experiment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/nicknisi/claude-plugins&quot;&gt;Claude plugins marketplace&lt;/a&gt; - The full collection&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code&quot;&gt;Claude Code Documentation&lt;/a&gt; - Getting started with Claude Code&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://changelog.com/friends/120&quot;&gt;Changelog and Friends Episode 120&lt;/a&gt; - The podcast conversation that started this&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let me know how it works for you. I&apos;m &lt;a href=&quot;https://bsky.app/profile/nicknisi.com&quot;&gt;@nicknisi.com&lt;/a&gt; on Bluesky.&lt;/p&gt;
</content:encoded></item><item><title>Writing My First Evals</title><link>https://nicknisi.com/posts/writing-my-first-evals/</link><guid isPermaLink="true">https://nicknisi.com/posts/writing-my-first-evals/</guid><description>I had no background in evals. I built two very different evaluation systems for two AI-powered developer tools, and they taught me the same lesson: trust isn&apos;t a feeling, it&apos;s a measurement.</description><pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &apos;@/components/Callout.astro&apos;;&lt;/p&gt;
&lt;p&gt;I&apos;ve spent the last few weeks building two AI-powered developer tools at &lt;a href=&quot;https://workos.com&quot;&gt;WorkOS&lt;/a&gt;. At some point I realized I had no idea if they actually worked.&lt;/p&gt;
&lt;p&gt;Not &quot;worked&quot; in the sense of &quot;does it run.&quot; They ran fine. I mean &quot;worked&quot; in the sense of &quot;does running this tool actually make things better for the developer using it?&quot; That&apos;s a harder question than it sounds when the tool&apos;s output is different every time you run it.&lt;/p&gt;
&lt;p&gt;I needed evals. I also had no background in writing them.&lt;/p&gt;
&lt;p&gt;This is the story of building two very different evaluation systems for two very different problems, and discovering they were teaching me the same thing.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;The projects&quot;&amp;gt;
&lt;a href=&quot;https://github.com/workos/cli&quot;&gt;WorkOS CLI&lt;/a&gt; (the &lt;code&gt;workos install&lt;/code&gt; command, powered by
&lt;a href=&quot;https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/sdk&quot;&gt;Claude Agent SDK&lt;/a&gt;) |
&lt;a href=&quot;https://github.com/workos/skills&quot;&gt;WorkOS Skills&lt;/a&gt; (auto-generated agent context from our docs)
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Does the agent do the right thing?&lt;/h2&gt;
&lt;p&gt;The first tool is &lt;code&gt;workos install&lt;/code&gt;, a CLI command that uses the Claude Agent SDK to automatically install &lt;a href=&quot;https://workos.com/docs/user-management&quot;&gt;WorkOS AuthKit&lt;/a&gt; into your project. You point it at a Next.js app, a React SPA, a Python Flask server, or any of 16 supported frameworks, and it figures out what to do. It reads your code, understands your project structure, creates the right files, modifies the right configs, and installs the right dependencies.&lt;/p&gt;
&lt;p&gt;It&apos;s magic. And magic is untestable by default.&lt;/p&gt;
&lt;p&gt;The problem with testing an AI agent is that it doesn&apos;t do the same thing twice. Same input project, same prompt, different output. Different file names, different import styles, different error handling approaches. &lt;code&gt;expect(output).toBe(expected)&lt;/code&gt; falls apart instantly.&lt;/p&gt;
&lt;p&gt;So I built an eval system.&lt;/p&gt;
&lt;h3&gt;Fixtures as starting states&lt;/h3&gt;
&lt;p&gt;The eval starts with fixture projects: minimal starter apps for each supported framework. A React SPA with three pages. A Next.js app with a basic layout. A Python Flask server with a health endpoint. 16 frameworks total, each with multiple starting states like &lt;code&gt;example&lt;/code&gt;, &lt;code&gt;example-auth0&lt;/code&gt; (migrating from Auth0), &lt;code&gt;partial-install&lt;/code&gt;, and &lt;code&gt;conflicting-middleware&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The fixture manager copies each one to a temp directory, runs &lt;code&gt;pnpm install&lt;/code&gt; (or &lt;code&gt;pip install&lt;/code&gt;, &lt;code&gt;bundle install&lt;/code&gt;, &lt;code&gt;go mod download&lt;/code&gt;, depending on what it detects), and initializes a git repo. That git init matters. The diff after the agent runs becomes the source of truth for what changed.&lt;/p&gt;
&lt;p&gt;I&apos;ll be honest. When I had about 24 fixtures, my reaction was: &quot;This feels both like not enough and like it&apos;s too much to maintain. Is this really the best path?&quot; I also worried about synthetic fixtures: if a test fails, is it because the agent screwed up or because the fixture wasn&apos;t realistic? The answer was to use real-world starting states, actual project structures that developers would have, rather than contrived setups. Every fixture runs before the agent touches it. If it doesn&apos;t work clean, it&apos;s not a valid test.&lt;/p&gt;
&lt;h3&gt;The agent runs for real&lt;/h3&gt;
&lt;p&gt;For each fixture, the eval executor invokes the real agent with the real skill. Same code path as production. No mocks. It tells the agent &quot;Use the &lt;code&gt;workos-authkit-nextjs&lt;/code&gt; skill to integrate WorkOS AuthKit into this application&quot; and lets it go. It tracks every tool call, every correction attempt, every token.&lt;/p&gt;
&lt;p&gt;If the agent fails, it can self-correct, up to two retries within the same session. The eval tracks whether a scenario passed on first attempt, needed correction, or needed a full retry. This distinction matters later.&lt;/p&gt;
&lt;h3&gt;Grading: the part I got wrong at first&lt;/h3&gt;
&lt;p&gt;I started with simple file checks. Does &lt;code&gt;middleware.ts&lt;/code&gt; exist? Does it import &lt;code&gt;@workos-inc/authkit-nextjs&lt;/code&gt;? Does the callback route handle &lt;code&gt;handleAuth&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;This worked for about an hour. Then I hit the first real eval lesson: &lt;strong&gt;passing isn&apos;t the same as good.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Next.js grader checks seven things: callback route exists, middleware or proxy exists (but not both, since Next.js 16 throws an error if you have both &lt;code&gt;middleware.ts&lt;/code&gt; and &lt;code&gt;proxy.ts&lt;/code&gt;), the SDK is imported correctly, &lt;code&gt;authkitMiddleware&lt;/code&gt; or the &lt;code&gt;authkit()&lt;/code&gt; composable is integrated, &lt;code&gt;AuthKitProvider&lt;/code&gt; wraps the layout, and the project builds. Every check must pass.&lt;/p&gt;
&lt;p&gt;That &quot;not both middleware and proxy&quot; check encodes real domain knowledge. It&apos;s the kind of thing a developer would catch in code review. Without it, the agent could create a technically correct but broken integration.&lt;/p&gt;
&lt;p&gt;But even with all those checks passing, an agent could still produce code that no human developer would accept. Over-engineered error handling. Unnecessary abstractions. Comments explaining what &lt;code&gt;const x = 1&lt;/code&gt; does. Technically correct. Terrible.&lt;/p&gt;
&lt;p&gt;So I added a second grading stage. The functional grader does pass/fail. But then a quality grader sends the code to Claude Haiku, which scores it on four dimensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Code style&lt;/strong&gt;: does it match the project&apos;s conventions?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimalism&lt;/strong&gt;: are the changes focused, or did it modify unrelated files?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error handling&lt;/strong&gt;: appropriate for the context, or paranoid?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idiomatic&lt;/strong&gt;: does it follow the framework&apos;s patterns?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each scored 1–5 with rubrics that define what a 1 versus a 5 looks like. Chain-of-thought reasoning before scoring, so the LLM has to justify each number.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents&quot;&gt;Anthropic&apos;s eval guide&lt;/a&gt; calls this &quot;grading outcomes, not paths.&quot; The grader doesn&apos;t care which tools the agent used or what order it did things in. It cares about what the project looks like when the agent is done.&lt;/p&gt;
&lt;h3&gt;Not pass/fail, pass rates&lt;/h3&gt;
&lt;p&gt;Here&apos;s where evals diverge from tests. My success criteria aren&apos;t &quot;all scenarios pass.&quot; They&apos;re:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;80%&lt;/strong&gt; pass on first attempt, no corrections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;90%&lt;/strong&gt; pass with self-correction allowed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;95%&lt;/strong&gt; pass with full retries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;info&quot; title=&quot;Evals aren&apos;t tests&quot;&amp;gt;
Tests verify deterministic behavior: given input X, expect output Y. Evals measure statistical quality: given input X, does the &lt;em&gt;distribution&lt;/em&gt; of outputs meet quality thresholds? Your test suite should be 100% green. Your eval suite won&apos;t be. That&apos;s by design.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://newsletter.pragmaticengineer.com/p/evals&quot;&gt;Pragmatic Engineer&apos;s deep dive on evals&lt;/a&gt; makes this point clearly: use pass/fail judgments for individual cases, but measure them as rates across many trials. A single failure doesn&apos;t mean the system is broken. A pattern of failures does.&lt;/p&gt;
&lt;p&gt;40 scenarios. 16 frameworks. Each one designed to test a different situation the agent might encounter in the wild. Here&apos;s what the validation output looks like after a full run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;════════════════════════════════════════════════════
✓ PASS: All success criteria met

First-attempt:    92.0% (required: 80%)
With-correction:  94.0% (required: 90%)
With-retry:       96.0% (required: 95%)
════════════════════════════════════════════════════
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Does the context actually help?&lt;/h2&gt;
&lt;p&gt;The second tool is a different kind of problem entirely. I&apos;m building a set of &lt;a href=&quot;https://github.com/workos/skills&quot;&gt;agent skills&lt;/a&gt; for WorkOS, structured context documents that get loaded into an LLM&apos;s system prompt when a developer asks about WorkOS features. SSO flows, directory sync, RBAC, AuthKit integration. The skills are auto-generated from our docs using a pipeline that fetches, parses, splits, and refines content through Claude. The evals are how I decided they were ready to ship.&lt;/p&gt;
&lt;p&gt;The question here isn&apos;t &quot;does the agent do the right thing?&quot; It&apos;s more basic: &lt;strong&gt;does feeding this context to the LLM actually make its output better?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I assumed the answer was yes. I was wrong about at least one of them.&lt;/p&gt;
&lt;h3&gt;A/B testing for LLMs&lt;/h3&gt;
&lt;p&gt;The skills eval takes a completely different approach from the CLI eval. For each test case, it runs the same prompt twice: once with the skill loaded into the system prompt, once without. Same model, same temperature, same everything except the system prompt. Then it scores both outputs and compares.&lt;/p&gt;
&lt;p&gt;The test cases are declarative YAML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- id: sso-node-basic
  product: sso
  skill: workos-sso
  prompt: |
    Implement SSO login flow for a Node.js Express
    app using the WorkOS SDK.
  expected:
    methods:
      - workos.sso.getAuthorizationUrl
      - workos.sso.getProfileAndToken
    imports:
      - &apos;@workos-inc/node&apos;
    flowSteps:
      - generate authorization URL
      - redirect user to IdP
      - handle callback
      - exchange code for profile
    antiPatterns:
      - hardcoded API key
    hallucinations:
      - workos.sso.authenticate
      - workos.sso.login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;hallucinations&lt;/code&gt; field lists methods that don&apos;t exist in the WorkOS SDK but that LLMs commonly invent. &lt;code&gt;workos.sso.authenticate&lt;/code&gt; sounds right. It isn&apos;t real. The scorer checks whether the LLM hallucinates these phantom methods, and whether having the skill context prevents it.&lt;/p&gt;
&lt;p&gt;Scoring runs across seven dimensions (method accuracy, parameter coverage, environment variable usage, imports, flow correctness, anti-pattern avoidance) each weighted and combined into a composite score:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const base =
	methodAccuracy * 20 +
	paramAccuracy * 15 +
	envVarCoverage * 15 +
	importAccuracy * 10 +
	flowCorrectness * 20 +
	antiPatternAvoidance * 15 +
	(hallucinationCount === 0 ? 5 : 0);

const penalty = Math.min(hallucinationCount * 5, 25);
return Math.max(0, Math.round(base - penalty));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;100 points maximum. Hallucinations carry a -5 penalty each, capped at -25. A clean hallucination-free output gets a 5-point bonus.&lt;/p&gt;
&lt;p&gt;42 test cases. Each one runs both arms in parallel. The delta between with-skill and without-skill tells you whether the skill is helping, hurting, or irrelevant.&lt;/p&gt;
&lt;p&gt;Here&apos;s what a real eval run looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;──────────────────────────────────────────────────────────────────
WorkOS Skill Eval Report
Model: claude-sonnet-4-5-20250929 | Cases: 42 | Date: 2026-02-26
──────────────────────────────────────────────────────────────────

Product             Cases  With Skill  Without     Delta
──────────────────────────────────────────────────────────────────
sso                     8         97%      95%       +2%
rbac                    5         91%      89%       +2%
directory-sync          6         87%      87%        0%
audit-logs              4         96%      96%        0%
mfa                     5         85%      83%       +2%
vault                   4         89%      88%       +1%
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The skill that hurt&lt;/h3&gt;
&lt;p&gt;One of the generated skills scored negative. Not zero. &lt;em&gt;Negative&lt;/em&gt;. The LLM produced worse output with the skill than without it.&lt;/p&gt;
&lt;p&gt;The directory sync skill for Ruby hit -12%. The SSO CSRF state validation case hit -20%. I wouldn&apos;t have caught either of these manually. I thought the skills were helping. They looked helpful. They contained accurate information.&lt;/p&gt;
&lt;p&gt;But the eval showed, across multiple runs, that the LLM consistently scored lower when these skills were in the system prompt. The SSO case was particularly bad: the skill taught the CSRF nuance correctly but omitted the auth URL generation step, costing 20 points on &lt;code&gt;missing_method&lt;/code&gt;. The LLM was learning the wrong lesson from the context.&lt;/p&gt;
&lt;p&gt;The breakthrough came when I started saving full LLM transcripts from both runs and built tools to diff them side by side. Not just &quot;which scored better?&quot; but &quot;what did the LLM actually do differently?&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌─── With Skill (composite: 77%) ──────────────────────────
  Methods:        4/5 ✗
    missing: getAuthorizationUrl
  Params:         3/4 ✗
  Flow:           3/6 out of order
  Hallucinations: 0

┌─── Without Skill (composite: 97%) ───────────────────────
  Methods:        5/5 ✓
  Params:         4/4 ✓
  Flow:           6/6 in order
  Hallucinations: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could see the skill introducing noise. Too much tangential context pulling the LLM away from the core task. The methods were right, but the flow was wrong. The LLM was getting distracted by the very context meant to help it.&lt;/p&gt;
&lt;p&gt;This was the moment where I went from &quot;evals are something I probably need&quot; to &quot;evals are how I know what&apos;s real.&quot;&lt;/p&gt;
&lt;h3&gt;Detecting things that aren&apos;t real&lt;/h3&gt;
&lt;p&gt;One detail worth calling out: the scorer handles negation. If the LLM output says &quot;don&apos;t use &lt;code&gt;workos.sso.authenticate&lt;/code&gt;, that method doesn&apos;t exist,&quot; that&apos;s not a hallucination. The LLM is correctly warning against a phantom method. A naive string match would flag it as a failure.&lt;/p&gt;
&lt;p&gt;The scorer checks the 30 characters before each match for negation signals (&quot;don&apos;t,&quot; &quot;avoid,&quot; &quot;should not,&quot; &quot;never&quot;) and for cautionary labels like &quot;anti-pattern&quot; or &quot;trap.&quot; If the method appears in a negation context, it gets skipped.&lt;/p&gt;
&lt;p&gt;Small thing. But it&apos;s the kind of nuance that makes the difference between an eval you trust and one that cries wolf.&lt;/p&gt;
&lt;h2&gt;Same question, different angle&lt;/h2&gt;
&lt;p&gt;These two eval systems share no code. The CLI evals copy fixture projects, invoke a real agent, and grade file outputs. The skills evals call the Claude API directly, compare system prompt variations, and score generated text. Different architectures. Different grading approaches. Different problems.&lt;/p&gt;
&lt;p&gt;But they&apos;re both answering the same question: &lt;strong&gt;how do you measure value when the code is non-deterministic and the environments vary wildly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The agent doesn&apos;t produce the same files twice. The LLM doesn&apos;t generate the same code twice. You can&apos;t write a test that says &quot;the output should be X&quot; because the output is never X. It&apos;s some variation of X that might be better or worse than what you expected.&lt;/p&gt;
&lt;p&gt;Both systems solve this the same way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Define what &quot;good&quot; looks like.&lt;/strong&gt; Not the exact output, but the signals that indicate quality. Files exist, methods are correct, flow is right, hallucinations are absent, the project builds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Measure statistically.&lt;/strong&gt; Pass rates and composite scores across many trials, not individual assertions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save everything.&lt;/strong&gt; Transcripts, diffs, scores, tool calls. Because when something fails, you need to understand &lt;em&gt;why&lt;/em&gt;, not just &lt;em&gt;that&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gate regressions.&lt;/strong&gt; Automated thresholds that prevent you from shipping something worse than what you had.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is what I mean when I say evals aren&apos;t tests. Tests tell you &quot;this is broken.&quot; Evals tell you &quot;this is getting worse,&quot; or in the case of my negative-scoring skill, &quot;this thing you thought was helping is actively hurting.&quot;&lt;/p&gt;
&lt;h2&gt;The eval can be wrong too&lt;/h2&gt;
&lt;p&gt;Here&apos;s something nobody warns you about: your evaluation system can have bugs just like the thing it&apos;s evaluating.&lt;/p&gt;
&lt;p&gt;I ran a full eval pass and saw 13 cases with negative deltas, skills apparently making things &lt;em&gt;worse&lt;/em&gt;. My stomach dropped. Then I investigated.&lt;/p&gt;
&lt;p&gt;All 13 flow regressions were scorer bugs, not skill regressions. The scorer expected implementation steps in a specific conceptual order, but the with-skill outputs used a diagnosis-first pattern: symptom, cause, verify, fix. The without-skill outputs happened to match the scorer&apos;s expected order by coincidence. The skills were actually producing &lt;em&gt;better&lt;/em&gt; structured code. My scorer was just too rigid to recognize it.&lt;/p&gt;
&lt;p&gt;After fixing the scorer&apos;s flow-ordering logic to use proximity-based matching instead of strict sequence checking, those 13 &quot;regressions&quot; became 13 neutral-to-positive results.&lt;/p&gt;
&lt;p&gt;The lesson: when your eval says something is broken, investigate the eval first. Especially early on.&lt;/p&gt;
&lt;h2&gt;Building trust with a system you built with AI&lt;/h2&gt;
&lt;p&gt;Here&apos;s the part I&apos;m almost embarrassed to admit. I built both eval systems with Claude Code. The majority of the CLI eval system (the fixture manager, the graders, the parallel runner, the quality scoring) was written in conversations with Claude. Which means I was using the AI I was trying to evaluate to build the evaluation system.&lt;/p&gt;
&lt;p&gt;For the first few days, I was blindly trusting it. Claude would suggest a grading approach, I&apos;d implement it, and I&apos;d move on. It told me things were working correctly, and I had no frame of reference to push back. I&apos;d never written evals before. At one point I asked Claude: &quot;Are evals graders? Did we follow the best practices set out in these docs?&quot; I was literally asking the AI to grade its own homework.&lt;/p&gt;
&lt;p&gt;It&apos;s a bit like an Apple Watch tracking your heart rate. I don&apos;t think the absolute number is perfectly accurate. But I don&apos;t need it to be. I need it to tell me if things are getting better or worse. A reliable baseline, even an imperfect one, is worth more than no baseline at all. That&apos;s what the evals gave me. Not perfect truth, but a consistent signal I could track over time.&lt;/p&gt;
&lt;p&gt;Then I had a moment of genuine doubt. I remember typing into Codex: &quot;I don&apos;t actually know if the evals are being honest with me about how useful these skills actually are. Can you do an independent analysis?&quot;&lt;/p&gt;
&lt;p&gt;I was using &lt;em&gt;two&lt;/em&gt; different AIs, Claude Code and Codex, to cross-check each other. I&apos;d run the evals with Claude, paste the results into Codex for analysis, then take Codex&apos;s feedback back to Claude. &quot;I asked Claude to review your response,&quot; I kept telling Codex. It felt absurd. But it was also working. Each model caught things the other missed. Codex flagged scorer assumptions Claude hadn&apos;t questioned. Claude found implementation bugs Codex couldn&apos;t see.&lt;/p&gt;
&lt;p&gt;Still, AI checking AI wasn&apos;t enough. I needed external ground truth.&lt;/p&gt;
&lt;p&gt;I read three things: &lt;a href=&quot;https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents&quot;&gt;Anthropic&apos;s guide to demystifying evals for AI agents&lt;/a&gt;, the &lt;a href=&quot;https://newsletter.pragmaticengineer.com/p/evals&quot;&gt;Pragmatic Engineer&apos;s deep dive on evals&lt;/a&gt;, and &lt;a href=&quot;https://developers.openai.com/api/docs/guides/evaluation-best-practices/&quot;&gt;OpenAI&apos;s evaluation best practices&lt;/a&gt;. Real resources from people who had thought deeply about this problem.&lt;/p&gt;
&lt;p&gt;Then I fed those articles back to Claude and asked it to evaluate whether our eval systems aligned with the principles. The feedback was specific: we had the fundamentals right. A/B structure, deterministic scoring, pass/fail judgments measured as rates, outcome-based grading. The pieces were there. They just needed refinement.&lt;/p&gt;
&lt;p&gt;We were on the right path. We just needed to sharpen the edges. &quot;Update the quality grader to use thinking before scoring,&quot; I told Claude after reading that chain-of-thought improves grading accuracy. Small refinements, not a rewrite.&lt;/p&gt;
&lt;h2&gt;What I&apos;d tell you if you&apos;re starting from zero&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Start with pass/fail.&lt;/strong&gt; Don&apos;t build a sophisticated scoring system on day one. Start with the simplest question: did it work? For me, that was &quot;does the file exist and does the project build.&quot; Get that running across a handful of cases, get a baseline, then iterate. I added quality scoring weeks later. The eval system grows with your understanding of what matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evals aren&apos;t tests. Treat them differently.&lt;/strong&gt; Your test suite should be 100% green. Your eval suite won&apos;t be. Set statistical thresholds and measure trends. An 85% first-attempt pass rate that&apos;s improving every week is better than chasing 100% on a small set of easy cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Save transcripts.&lt;/strong&gt; I cannot overstate this. When the skill scored negative, the only reason I found the root cause was because I had full transcripts from both the with-skill and without-skill runs. Scores tell you &lt;em&gt;what&lt;/em&gt;. Transcripts tell you &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calibrate against humans.&lt;/strong&gt; The skills eval has a labeling system, a simple JSONL file where I mark cases as &quot;ship&quot; or &quot;no-ship&quot; with a reason. Then a calibration script measures how often the automated scorer agrees with my judgment. If we disagree on more than 20% of cases, the scorer needs work, not me. Automated scoring without human calibration is just a faster way to be confidently wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Measure the thing you actually care about.&lt;/strong&gt; Generic &quot;helpfulness&quot; scores from off-the-shelf tools didn&apos;t help me. I needed to know: does the middleware import the right SDK? Does the authorization URL use the correct parameters? Does the skill prevent hallucinated method names? Domain-specific signals beat generic metrics every time. The &lt;a href=&quot;https://newsletter.pragmaticengineer.com/p/evals&quot;&gt;Pragmatic Engineer article&lt;/a&gt; puts it well: avoid pre-built metrics that don&apos;t correlate with what your users actually experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Question whether you&apos;re adding value or duplicating knowledge.&lt;/strong&gt; At one point I asked Claude to verify that the skills were &quot;actually additive, not just duplicating the docs.&quot; This is the core question for any context-augmented AI tool. If the LLM already knows the answer, your skill is dead weight. If your skill adds noise, it&apos;s actively harmful. The A/B delta is the only way I&apos;ve found to answer this honestly.&lt;/p&gt;
&lt;h2&gt;Trust is a measurement&lt;/h2&gt;
&lt;p&gt;I started this with no background in evals. I built two systems that are different in almost every way. One copies starter projects across 16 frameworks and runs an AI agent against them. The other A/B tests whether feeding context to an LLM actually improves its output. No shared code. No shared architecture.&lt;/p&gt;
&lt;p&gt;But they taught me the same thing: trust isn&apos;t a feeling. It&apos;s a number. It&apos;s a pass rate, a delta score, a regression gate. When someone asks me &quot;does this AI tool actually work?&quot; I don&apos;t have to say &quot;I think so.&quot; I can show them the data.&lt;/p&gt;
&lt;p&gt;And when the data says something I built is making things worse? I fix it or I kill it. That&apos;s what the negative-scoring skill taught me. My intuition said it was helpful. The eval said otherwise.&lt;/p&gt;
&lt;p&gt;The eval was right.&lt;/p&gt;
&lt;p&gt;I should be honest about what I don&apos;t know yet. These tools are still evolving. Time will tell whether the evals I built actually set me up for success or just made me feel better about shipping. I started this with no background in evals, and I&apos;m sure someone who does this for a living would find plenty to improve. If that&apos;s you, I&apos;d genuinely love to hear what I&apos;m getting wrong. I&apos;m &lt;a href=&quot;https://bsky.app/profile/nicknisi.com&quot;&gt;@nicknisi.com&lt;/a&gt; on Bluesky.&lt;/p&gt;
&lt;p&gt;What I do know: I&apos;m shipping with more confidence than I had before, and I have real numbers to back it up. That&apos;s more than vibes. For now, that&apos;s enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resources that shaped my thinking:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents&quot;&gt;Demystifying Evals for AI Agents&lt;/a&gt; (Anthropic)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://newsletter.pragmaticengineer.com/p/evals&quot;&gt;Evals&lt;/a&gt; (Pragmatic Engineer)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/api/docs/guides/evaluation-best-practices/&quot;&gt;Evaluation Best Practices&lt;/a&gt; (OpenAI)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The projects mentioned in this post:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/workos/cli&quot;&gt;WorkOS CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/workos/skills&quot;&gt;WorkOS Skills&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Speaking at Conferences</title><link>https://nicknisi.com/posts/speaking-at-conferences/</link><guid isPermaLink="true">https://nicknisi.com/posts/speaking-at-conferences/</guid><description>My thoughts on preparing to speak at conferences.</description><pubDate>Mon, 08 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;;
import storyCircle from &apos;@/assets/posts/story-circle.png&apos;;
import qrCode from &apos;@/assets/posts/qr-code-example.png&apos;;
import Callout from &apos;@/components/Callout.astro&apos;;&lt;/p&gt;
&lt;p&gt;I like to label myself as an occasional conference speaker. I&apos;ve also been a conference organizer, so I have a unique perspective on the process. I&apos;ve been speaking at conferences for a few years now, and I&apos;ve spent a decent part of my career teaching 5-day workshops. I&apos;ve learned a few pointers along the way. Here are some of my thoughts on preparing to speak at conferences.&lt;/p&gt;
&lt;h2&gt;Write a compelling abstract&lt;/h2&gt;
&lt;p&gt;The abstract is your one chance to get the attention of the conference organizing team. It needs to be compelling and to the point. Here are some tips for writing a great abstract:&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;Check out my recent abstracts&quot; variant=&quot;info&quot;&amp;gt;
I created small &lt;em&gt;marketing pages&lt;/em&gt; for the talks I&apos;ve given in 2024. These include all resources and the abstracts I
submitted to the conference organizers. You can find them at
&lt;a href=&quot;https://nicknisi.com/compiler-talk&quot;&gt;nicknisi.com/compiler-talk&lt;/a&gt; and
&lt;a href=&quot;https://nicknisi.com/state-talk&quot;&gt;nicknisi.com/state-talk&lt;/a&gt;.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h3&gt;Be specific&lt;/h3&gt;
&lt;p&gt;Don&apos;t be too vague about your topic. You need to portray enough information about what the talk is and what the audience can expect to learn from it to convince the organizers that it&apos;s worth having you speak and that you can deliver. You don&apos;t have to have the talk completely created in advance, but have a good outline ready to go and derive the abstract from that.&lt;/p&gt;
&lt;h3&gt;Outline the objectives&lt;/h3&gt;
&lt;p&gt;Define what the talk aims to achieve. Have a target audience in mind and write to their level. If you&apos;re giving a talk on a specific technology, mention the prerequisites the audience should have.&lt;/p&gt;
&lt;h3&gt;Don&apos;t fear rejection&lt;/h3&gt;
&lt;p&gt;Conference organizers aren&apos;t kidding when they say they have to make hard choices. At &lt;a href=&quot;https://nejsconf.com&quot;&gt;NEJS Conf&lt;/a&gt;, we would regularly get more than 300 submissions and would have to whittle that list down to nine each year.&lt;/p&gt;
&lt;h2&gt;Tell a story&lt;/h2&gt;
&lt;p&gt;The number one thing to remember when preparing a talk is that you don’t have to be an expert. You can discuss a project or technology with which you only have a passing familiarity. The key is to tell a story—explain how you approached the technology with a specific problem and how you solved it. People love stories; they’re engaging, relatable, and memorable. When preparing your talk, focus on the story you want to tell. This is what separates a good talk from a great one. Especially with technical talks, narrating how you solved a problem or built something personalizes your presentation and makes it uniquely yours.&lt;/p&gt;
&lt;h3&gt;Telling a great story&lt;/h3&gt;
&lt;p&gt;If you&apos;re a fan of &lt;a href=&quot;https://en.wikipedia.org/wiki/Community_(TV_series)&quot;&gt;Community&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Rick_and_Morty&quot;&gt;Rick and Morty&lt;/a&gt;, you might have heard of &lt;a href=&quot;https://en.wikipedia.org/wiki/Dan_Harmon&quot;&gt;Dan Harmon&lt;/a&gt;. He famously developed a framework for consistently telling great stories. It&apos;s called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Dan_Harmon#%22Story_circle%22_technique&quot;&gt;Story Circle&lt;/a&gt; and it&apos;s an eight step process adapted from &lt;a href=&quot;https://en.wikipedia.org/wiki/Joseph_Campbell&quot;&gt;Joseph Campbell&apos;s&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Hero%27s_journey&quot;&gt;monomyth&lt;/a&gt;. Here are the steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;You&lt;/strong&gt; - A character is in a zone of comfort&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Need&lt;/strong&gt; - They want something&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt; - They enter an unfamiliar situation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search&lt;/strong&gt; - They adapt to it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Find&lt;/strong&gt; - They find what they wanted&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Take&lt;/strong&gt; - They pay a heavy price for it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Return&lt;/strong&gt; - They return to their familiar situation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change&lt;/strong&gt; - Having changed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the Story Circle, the top-half of the circle represents order and the bottom-half represents chaos. When in the top
part, the character is in their comfort zone and the status quo is maintained. When they cross the threshold into the
bottom-half, the status quo is disrupted and the character must adapt to the new situation. The character then returns
to the top-half, having changed the status quo in some way. Each section in the top-half is the antithesis of the corresponding section in the bottom-half. For example, in the &quot;Go&quot; section, the character enters an unfamiliar situation. Then in the &quot;Return&quot; section, they return to their familiar situation.&lt;/p&gt;
&lt;h3&gt;The Tech Talk Story Circle&lt;/h3&gt;
&lt;p&gt;The Story Circle can be adapted to technical talks pretty well. Here&apos;s how I&apos;ve adapted it.&lt;/p&gt;
&lt;p&gt;&amp;lt;Image src={storyCircle} alt=&quot;The Tech Talk Story Circle&quot; quality=&quot;max&quot; inferSize={true} /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Introduction (You)&lt;/strong&gt; - Introduce yourself and the current status quo of your project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problem Statement (Need)&lt;/strong&gt; - Identify and explain the problem you&apos;re trying to solve.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exploration (Go)&lt;/strong&gt; - Describe the steps taken to address the problem. What did you try? What worked? What didn&apos;t?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experimentation (Search)&lt;/strong&gt; - Detail the process digging into the actual problem. What did you learn?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solution (Find)&lt;/strong&gt; - Explain how you found the solution or made significant progress towards solving the problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Challenges (Take)&lt;/strong&gt; - Discuss the actual implementation of the project. Emphasize the disruption to the status quo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apply Knowledge (Return)&lt;/strong&gt; - Describe the results and how the solution impacted the problem or was integrated into your project, specifically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Results &amp;amp; Insights (Change)&lt;/strong&gt; - Conclude the journey with your perspective on the lessons learned and how you or your workflow may have changed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;in this revised circle, the top-half represents established practices while the bottom half represents disruption and experimentation. The character (you) starts in the top-half, enters the bottom-half to experiment and disrupt, and then returns to the top-half with new knowledge and insights, changing the status quo which could be your project, your team, or your workflow.&lt;/p&gt;
&lt;h3&gt;Example: The Story Circle in Action&lt;/h3&gt;
&lt;p&gt;In my talk, &lt;a href=&quot;/compiler-talk&quot;&gt;Unleashing the TypeScript Compiler&lt;/a&gt;, I used the Story Circle to structure my talk. I start with the status quo (1) of a project I worked on, developing a TypeScript/React application using &lt;a href=&quot;https://mui.com&quot;&gt;Material-UI&lt;/a&gt; and the challenges we faced with that project (2). I describe the solutions we tried as a
way to work around the issues (3) before disrupting things by exploring the TypeScript compiler and the world of codemods (4).&lt;/p&gt;
&lt;p&gt;I talk about how I could use this new knowledge to solve my problems (5) and then I open up about the challenges I faced in implementing the solution (6), specifically around handling every possible scenario.&lt;/p&gt;
&lt;p&gt;I then discuss how I applied this new knowledge to my project (7), returning back to the top-half of the circle before finally ending my story with the results of this exploration and how it changed my team&apos;s perspective on the problem (8), convincing some that codemods can reduce some of the complexity of replacing a technology.&lt;/p&gt;
&lt;h2&gt;Remember, you&apos;re an entertainer&lt;/h2&gt;
&lt;p&gt;Having a great story to tell only works if you can deliver it in an engaging way. You don&apos;t have to be a stand-up
comedian, but you should be able to keep the audience&apos;s attention. Here are some tips for being an engaging speaker:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Practice, practice, practice&lt;/strong&gt; - The more you practice your talk, the more comfortable you&apos;ll be delivering it. You&apos;ll also be able to identify areas where you can improve. This is especially important to do in front of trusted friends whenever possible. You&apos;ll only get the timing right when you have an audience to play off of, and timing is everything&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use humor&lt;/strong&gt; - A well-placed joke can go a long way in keeping the audience engaged. Just make sure it&apos;s appropriate and relevant to the topic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use visuals&lt;/strong&gt; - Visual aids can help reinforce your points and keep the audience engaged. Just make sure they&apos;re relevant and not distracting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engage the audience&lt;/strong&gt; - Ask questions, encourage participation, and make eye contact with the audience. This will help keep them engaged and make them feel like they&apos;re part of the conversation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;If showing code, make sure it&apos;s readable&lt;/h2&gt;
&lt;p&gt;Make sure your code is large enough for everyone in the back to see. Break it up across slides, if necessary. This also helps keep the audience engaged, focusing on one piece of code at a time. Ensure your code is syntax highlighted and legible. Use a theme that&apos;s going to work well, even on the crappiest of projectors. You might even consider a light mode theme 😱.&lt;/p&gt;
&lt;h2&gt;Make it easy to follow up&lt;/h2&gt;
&lt;p&gt;&amp;lt;Image src={qrCode} alt=&quot;Example QR code at the end of my slides&quot; quality=&quot;max&quot; inferSize={true} /&amp;gt;&lt;/p&gt;
&lt;p&gt;At the end of your talk, provide an easy to remember link to your slides, code, and contact info, and encourage the audience to reach out. This is one of the few use cases for a QR code, but a short URL works just as well. For my talk, I used the memorable URL, &lt;a href=&quot;https://typescript.fun/compiler-talk&quot;&gt;typescript.fun/compiler-talk&lt;/a&gt; to link to my slides, abstract, and contact info.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Crafting a compelling conference talk isn&apos;t just about sharing your expertise—it&apos;s about telling a story that resonates with your audience. From writing a persuasive abstract to structuring your presentation using a story circle, each step plays a crucial role in capturing and maintaining attention. Remember, you don&apos;t have to be an expert; you just need to be authentic and engaging. Practice diligently, prepare for technical setbacks, and always make it easy for your audience to follow up with you. By blending established practices with innovative problem-solving, you&apos;ll not only educate but also inspire your audience. So, take these insights, craft your story, and get ready to deliver a talk that stands out. See you on stage!&lt;/p&gt;
</content:encoded></item><item><title>Seven Years of JS Party: A Personal Reflection</title><link>https://nicknisi.com/posts/seven-years-of-jsparty/</link><guid isPermaLink="true">https://nicknisi.com/posts/seven-years-of-jsparty/</guid><description>A personal reflection on seven years of JS Party as the show transitions into its next chapter with dysfunctional.fm.
</description><pubDate>Thu, 05 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JS Party is &lt;a href=&quot;https://changelog.com/posts/a-new-era-for-the-changelog-podcast-universe&quot;&gt;coming to an end&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When I joined JS Party back at episode 20 nearly seven years ago, I had no idea how this journey would transform both my professional life and my connection to the JavaScript community. As we prepare our final episodes, I find myself reflecting on what this incredible chapter has meant to me.&lt;/p&gt;
&lt;h2&gt;The State of Change&lt;/h2&gt;
&lt;p&gt;I&apos;ll be honest – when I first heard about the end of JS Party, it hit me hard. Change, even positive change, can be difficult to process. But after seeing Adam and Jerod&apos;s vision for Changelog&apos;s future, I&apos;ve found myself genuinely excited about what&apos;s ahead!&lt;/p&gt;
&lt;h2&gt;More Than Just a Tech Podcast&lt;/h2&gt;
&lt;p&gt;JS Party became more than just a weekly tech show – it evolved into a genuine community gathering place. One of my favorite memories (which perfectly encapsulates the show&apos;s spirit) was &lt;a href=&quot;https://changelog.com/jsparty/155&quot;&gt;interviewing Adam Wathan&lt;/a&gt; about &lt;a href=&quot;https://tailwindcss.com&quot;&gt;Tailwind&lt;/a&gt; while exclusively speaking in &quot;Wind Beneath My Wings&quot; lyrics. This blend of technical content and unabashed fun became our signature style, making complex topics accessible and entertaining, and even had Adam in on it by the end!&lt;/p&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;p&gt;Perhaps the most valuable lesson I&apos;m taking away from this experience is that there truly are no stupid questions (except maybe &quot;Why doesn&apos;t Nick use VS Code?&quot; 😉). In our industry, everyone is constantly learning and growing, and I&apos;ve witnessed firsthand how supportive and helpful the tech community can be. Whether it was a beginner asking their first question or an experienced developer exploring new territory, JS Party always strived to be a welcoming space for curiosity and growth.&lt;/p&gt;
&lt;h2&gt;Community Impact&lt;/h2&gt;
&lt;p&gt;What makes my heart full is how the show connected people across the vast JavaScript ecosystem. We bridged gaps between different frameworks and languages, always remembering that JavaScript is the thread that binds the web together. Some of my most cherished moments have been the unexpected ones – like being recognized by a listener on planes or connecting with someone who spotted my JS Party t-shirt while I was at my kid&apos;s gymnastics practice. These encounters remind me that our weekly conversations reached far beyond our microphones.&lt;/p&gt;
&lt;h2&gt;Personal Growth&lt;/h2&gt;
&lt;p&gt;Looking back, I can&apos;t overstate how much I&apos;ve grown through this experience. JS Party gave me a platform to advocate for tools I&apos;m passionate about (Vim and TypeScript forever!), but more importantly, it helped me build meaningful connections throughout the tech community. Each episode was an opportunity to learn something new, challenge my assumptions, and grow alongside our listeners.&lt;/p&gt;
&lt;h2&gt;The Road Ahead&lt;/h2&gt;
&lt;p&gt;While one chapter is ending, I&apos;m genuinely excited about what&apos;s next. The Changelog Podcast Universe (&lt;a href=&quot;https://cpu.fm&quot;&gt;CPU.fm&lt;/a&gt;) represents a new frontier for tech podcasting, and &lt;a href=&quot;https://dysfunctional.fm&quot;&gt;dysfunctional.fm&lt;/a&gt; will carry forward the spirit of community and fun that made JS Party special. The party isn&apos;t over – we&apos;re just changing the runtime!&lt;/p&gt;
&lt;p&gt;To everyone who&apos;s ever tuned in, shared an episode, or reached out with feedback: thank you for being part of this journey. The JavaScript ecosystem is vast and ever-changing, but it&apos;s the people – the developers, the creators, and the listeners – who make it special. I look forward to continuing our conversations and building new connections through dysfunctional.fm.&lt;/p&gt;
&lt;p&gt;The show may be ending, but the community we&apos;ve built together lives on. Here&apos;s to the next chapter!&lt;/p&gt;
</content:encoded></item><item><title>JS Party Will Be in NYC at React Summit!</title><link>https://nicknisi.com/posts/react-summit-2024/</link><guid isPermaLink="true">https://nicknisi.com/posts/react-summit-2024/</guid><description>We&apos;re conducting interviews and having fun learning about React!</description><pubDate>Mon, 11 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://zendev.com&quot;&gt;KBall&lt;/a&gt; and I are going to be in NYC (actually, the conference is in Jersey City) representing &lt;a href=&quot;https://jsparty.fm&quot;&gt;JS Party&lt;/a&gt; at &lt;a href=&quot;https://reactsummit.us&quot;&gt;React Summit&lt;/a&gt; on November 19th, 2024! React Summit is one of the largest React-focused developer conferences, bringing together thousands of developers to explore the latest in React development. We&apos;ll be talking to folks, doing interviews, and having fun. If you&apos;re around, stop by and say hi!&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded></item><item><title>On Leaving Meta</title><link>https://nicknisi.com/posts/on-leaving-meta/</link><guid isPermaLink="true">https://nicknisi.com/posts/on-leaving-meta/</guid><description>From imposter syndrome to shipping features used by millions, here&apos;s what I learned during my year as a remote engineer at Meta.
</description><pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &apos;@/components/Callout.astro&apos;;&lt;/p&gt;
&lt;p&gt;Before joining Meta, I was comfortable. I had carved out my niche in developer experience at a financial services
company, making life better for front-end engineers through build systems, tooling, and design systems. Then came the
opportunity that would push me way outside that comfort zone: a role at Meta.&lt;/p&gt;
&lt;h2&gt;Taking the Leap&lt;/h2&gt;
&lt;p&gt;I won&apos;t lie – joining Meta was intimidating. This isn&apos;t just any tech company; it&apos;s one of the places where the tools
and frameworks we use every day were born. The company that turned React from an internal experiment into the library
that transformed how we build for the web. A place where some of the industry&apos;s brightest minds solve problems at a
scale that most of us can barely imagine.&lt;/p&gt;
&lt;p&gt;As a father of two in the Midwest, far from the Silicon Valley bustle, I couldn&apos;t help but wonder: Could I keep up?
Would I belong? Looking at my interviewers&apos; backgrounds during the hiring process – MIT, Stanford, previous FAANG
experience – it was hard not to feel like an outsider. The imposter syndrome was real.&lt;/p&gt;
&lt;p&gt;But beneath the apprehension was genuine excitement. The team I&apos;d be joining is working on Ads Manager – literally the
oldest and largest React application in the world. The same codebase where React itself was born. The opportunity to
work on something with that kind of historical significance doesn&apos;t come along often. I knew I&apos;d regret not taking the
chance to be part of that story.&lt;/p&gt;
&lt;h2&gt;The Meta Way&lt;/h2&gt;
&lt;p&gt;Let&apos;s talk about Meta&apos;s dev tools. Unsurprisingly, I had opinions going in. &lt;a href=&quot;https://www.mercurial-scm.org&quot;&gt;Mercurial&lt;/a&gt; (okay,
&lt;a href=&quot;https://sapling-scm.com&quot;&gt;Sapling&lt;/a&gt; now) Instead of Git? A custom fork of
&lt;a href=&quot;https://code.visualstudio.com/download&quot;&gt;VSCode&lt;/a&gt; when I had my &lt;a href=&quot;https://github.com/nicknisi/dotfiles&quot;&gt;Neovim setup&lt;/a&gt; just
the way I liked it? Their own programming languages like &lt;a href=&quot;https://flow.org&quot;&gt;Flow&lt;/a&gt; and &lt;a href=&quot;https://hacklang.org&quot;&gt;Hack&lt;/a&gt;
instead of my beloved &lt;a href=&quot;https://typescriptlang.org&quot;&gt;TypeScript&lt;/a&gt;? My initial reaction was basically &quot;why?&quot;It seemed like
swimming upstream from the rest of the industry.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;Neovim totally exists at Meta, btw.&quot;&amp;gt;
I could totally use Neovim at meta. They have their own plugin to set it up and it&apos;s pretty nice. I just didn&apos;t want
to deal with the inevitable issues that would arise in the beginning while I&apos;m also ramping up on everything else, so
I used Neovim through VSCode.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;But are they wrong? What I discovered was an incredibly cohesive development environment where tools didn&apos;t just coexist
– they worked in harmony. The way tasks, diffs, and chat seamlessly reference each other, how custom tools and even
custom Chrome extensions are used to tie everything together, and how the entire company can work in a single repository
– it&apos;s impressive.&lt;/p&gt;
&lt;p&gt;It&apos;s also clear how Flow and Hack were born out of the company&apos;s needs, and it&apos;s obvious how they influence each other.
They&apos;re not creating Flow as an alternative to TypeScript. They&apos;re making a language that feels familiar to Hack
developers, and vice versa.&lt;/p&gt;
&lt;h2&gt;The Reality of Scale&lt;/h2&gt;
&lt;p&gt;Working on the Ads Manager brought another revelation. I expected the codebase to be immaculate – pristine code with no
tech debt, bad patterns, or confusion. Instead, I found something very human: code that reflected the reality of
maintaining a massive application over many years. Yes, there was tech debt. Yes, there were outdated patterns. But
that wasn&apos;t a weakness; it was a validation that software engineering is hard at every scale, even at Meta. It was also
eye-opening seeing this code being incredibly performant and reliable.&lt;/p&gt;
&lt;p&gt;On my team, we would regularly dive deep into both Hack APIs and React interfaces. We built in-app advertising
features and streamlined complex workflows, always trying to make the platform more accessible to newcomers while
maintaining its power for experienced advertisers of all sizes.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;A Meta Moment&quot;&amp;gt;
One of my favorite memories? Getting texts from friends asking why my face kept showing up in their Instagram feeds.
Look, when you&apos;re testing ad features, you gotta use what you&apos;ve got. I like to think I brought a certain... charm to
people&apos;s social media experience.&lt;/p&gt;
&lt;p&gt;And what was I selling in this ad? Nothing! It was just a test video where I was singing &quot;Never Gonna Give You Up&quot;,
acapella-style! 😂&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Finding Connection&lt;/h2&gt;
&lt;p&gt;Despite being remote, I found ways to connect. Our team operated like a well-oiled machine, everyone busy but never too
busy to help explain a project flow or deployment process. During our onsites, I made the most of every moment –
including a memorable karaoke party where I was the sole performer, belting out &quot;Unbreak My Heart&quot; to my amused
colleagues.&lt;/p&gt;
&lt;p&gt;I even took on the role of &lt;em&gt;Social Captain&lt;/em&gt; for an internal, Meta-wide UIE Summit, helping create the conference&apos;s intro
video and bringing people together. These moments of connection made the virtual distance feel smaller.&lt;/p&gt;
&lt;h2&gt;The Balancing Act&lt;/h2&gt;
&lt;p&gt;In the span of a month I emceed a conference on one coast, organized and ran an internal conference on the other coast,
and shipped two big features before a code freeze deadline! I worked more than 100 hours in a single week before it was
all over. Wild. But here&apos;s the thing - I did it. While being a dad. While running a podcast. While being a technical
advisor to two startups. While organizing meetups. While speaking at conferences. (Writing this out now, I&apos;m questioning
how I had time for anything... 😅)&lt;/p&gt;
&lt;h2&gt;Time for Change&lt;/h2&gt;
&lt;p&gt;The decision to leave wasn&apos;t easy. Meta challenged me, taught me, and showed me what engineering at massive scale looks
like. But as time went on, I found myself craving something different. While my teammates were exceptional, I realized I
was spending more time on team alignment and metrics than actual engineering. The technical problems that energized me
were taking a backseat to the organizational mechanics of working at scale. In some ways, that&apos;s probably inevitable on
a product team of that size. But it wasn&apos;t where I wanted to focus my energy.&lt;/p&gt;
&lt;p&gt;I considered looking for an internal transfer, perhaps to an internal infra team where I could focus more on the
technical challenges I&apos;m passionate about. But with the current perceived emphasis on RTO, finding a team that was
amenable to remote teammates proved challenging. As a Midwest dad with roots in my community, relocating wasn&apos;t an
option I wanted to pursue.&lt;/p&gt;
&lt;p&gt;An opportunity emerged that would let me return to my developer experience roots – the kind of work that has always
energized me – while building on everything I learned at Meta. It felt like the right time to take what I&apos;d gained here
– the confidence, the experience with scale, the understanding of complex organizations – and apply it in a role more
aligned with my technical passions.&lt;/p&gt;
&lt;h2&gt;Looking Back&lt;/h2&gt;
&lt;p&gt;You know what&apos;s funny about big tech companies? From the outside, they can seem almost mythical. I remember thinking
Meta engineers must be coding wizards who never ship bugs and write perfect PRs in their sleep. But after a year there,
I&apos;ve learned something important: we&apos;re all just people trying to build good stuff while juggling meetings and fixing
prod issues.&lt;/p&gt;
&lt;p&gt;Yeah, I worked at Meta. No, I didn&apos;t become some 10x engineer or discover the secret sauce to perfect code. What I did
do was show up every day, ship some cool features, work with some incredibly smart people, and learn a ton about
building software at ridiculous scale.&lt;/p&gt;
&lt;p&gt;I&apos;m grateful for the experience, but I&apos;m also excited about what&apos;s next. Meta taught me a lot about what I want (and
don&apos;t want) in my engineering career. Most importantly? It taught me that there&apos;s no magic - just people solving
problems together, whether that&apos;s at a FAANG company or a two-person startup.&lt;/p&gt;
&lt;p&gt;Time to take these lessons and build something new.&lt;/p&gt;
</content:encoded></item><item><title>Code Review: Obsidian Clipper</title><link>https://nicknisi.com/posts/obsidian-clipper-code-review/</link><guid isPermaLink="true">https://nicknisi.com/posts/obsidian-clipper-code-review/</guid><description>John Christopher and I sit down and do a code review together.</description><pubDate>Mon, 27 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;My friend, &lt;a href=&quot;https://jdotc.xyz/&quot;&gt;John Christopher&lt;/a&gt; has been working on a new &lt;a href=&quot;https://obsidian.md&quot;&gt;Obsidian&lt;/a&gt; plugin
called &lt;a href=&quot;https://github.com/jgchristopher/obsidian-clipper&quot;&gt;Obsidian Clipper&lt;/a&gt;. It&apos;s a plugin that allows users to clip
parts of a website into their daily note. It&apos;s a pretty cool plugin, and you should check it out.&lt;/p&gt;
&lt;p&gt;He asked me to take a look at the code for a big refactor he was doing. It&apos;s a TypeScript project, and uses Svelte for
its plugin UI. We decided to do the review together and record it! It&apos;s a good example of typical code reviews I do and
was something that we both learned a lot from.&lt;/p&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;ZsDPAFzWXOU&quot; /&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Ideation: Because Planning Needs More Than a Mode</title><link>https://nicknisi.com/posts/ideation/</link><guid isPermaLink="true">https://nicknisi.com/posts/ideation/</guid><description>Claude Code&apos;s plan mode is a great starting point for thinking before coding. But for complex work, I needed more than a mode. I needed a system. Here&apos;s what I built.</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &apos;@/components/Callout.astro&apos;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;TL;DR&quot; variant=&quot;info&quot;&amp;gt;
Ideation is a Claude Code plugin that turns messy brain dumps into structured, executable implementation specs.{&apos; &apos;}
&amp;lt;a href=&quot;#try-it&quot; class=&quot;text-vivid-light-blue-100 font-medium underline hover:text-white&quot;&amp;gt;
Skip to install instructions →
&amp;lt;/a&amp;gt;
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;AI can write the code. That part&apos;s easy now.&lt;/p&gt;
&lt;p&gt;But before you can write code, you need to know what you&apos;re building. And that&apos;s where I keep getting stuck. I have scratch files full of half-formed ideas, voice memos I never revisit, shower thoughts that never make it past &quot;that would be cool.&quot; The ideas aren&apos;t the problem. Turning them into something specific enough to act on is.&lt;/p&gt;
&lt;h2&gt;Plan Mode Is a Good Start&lt;/h2&gt;
&lt;p&gt;Claude Code has a built-in &lt;a href=&quot;https://code.claude.com/docs/en/common-workflows#how-to-use-plan-mode&quot;&gt;plan mode&lt;/a&gt;. Hit Shift+Tab, describe what you want, and Claude explores the codebase read-only before proposing an approach. For a focused task like &quot;refactor this module&quot; or &quot;add OAuth2 support,&quot; plan mode gives you a solid starting point before any code gets written.&lt;/p&gt;
&lt;p&gt;But plan mode lives inside a single session. The plan exists in conversation context. It doesn&apos;t produce artifacts you can review later, share with teammates, or hand off to a different session. There&apos;s no structured process for figuring out whether Claude actually &lt;em&gt;understands&lt;/em&gt; the problem well enough to plan. And when the work spans multiple phases, there&apos;s nothing to carry forward.&lt;/p&gt;
&lt;p&gt;For small tasks, that&apos;s fine. For the kind of work that stalls in the planning phase for days (the ambitious features, the multi-phase refactors, the ideas that start as shower thoughts) I needed something with more rigor.&lt;/p&gt;
&lt;h2&gt;From Mode to System&lt;/h2&gt;
&lt;p&gt;That&apos;s what led me to build the &lt;a href=&quot;https://github.com/nicknisi/claude-plugins&quot;&gt;Ideation&lt;/a&gt; skill for Claude Code. Where plan mode is a toggle, Ideation is a pipeline.&lt;/p&gt;
&lt;p&gt;You start by dumping whatever&apos;s in your head. Voice dictation, scattered bullet points, contradictions and all. The mess is the input. But instead of immediately organizing that mess into a plan, Ideation does something I got wrong in early versions: it scores its own confidence first.&lt;/p&gt;
&lt;p&gt;Five dimensions (problem clarity, goal definition, success criteria, scope boundaries, internal consistency), each scored 0 to 20. The threshold to proceed is 95 out of 100.&lt;/p&gt;
&lt;p&gt;That&apos;s deliberately high. When the score falls short, and it almost always does on the first pass, the skill asks targeted clarifying questions. Structured questions with concrete options: &lt;em&gt;&quot;Should this be scoped to a single language for v1, or support multiple from the start?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One extra round of questions costs minutes. A bad plan costs hours.&lt;/p&gt;
&lt;h2&gt;The Full Pipeline&lt;/h2&gt;
&lt;p&gt;Let me walk through what actually happens, using a real project.&lt;/p&gt;
&lt;p&gt;I had scattered thoughts about a problem with our AI coding agent in the &lt;a href=&quot;https://github.com/workos/cli&quot;&gt;WorkOS CLI&lt;/a&gt;. It runs once, writes code, and then terminates. Validation happens after. When it catches real issues (wrong file placement, build failures), those results go to the user, not back to the agent. The agent never gets a chance to fix its own mistakes.&lt;/p&gt;
&lt;p&gt;I rambled about this into &lt;code&gt;/ideation&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Codebase First&lt;/h3&gt;
&lt;p&gt;Before asking me anything, Ideation explored the existing codebase: the project structure, the validation module, the agent runner, the event emitter patterns, the test infrastructure. That context feeds into every artifact. Specs reference real file paths and existing patterns (&quot;Pattern to follow: &lt;code&gt;src/lib/validation/build-validator.ts&lt;/code&gt;&quot;). Code that ignores existing conventions sticks out. Ideation reads first.&lt;/p&gt;
&lt;h3&gt;Contract&lt;/h3&gt;
&lt;p&gt;After exploration, Ideation scored its confidence at around 80, asked me a few clarifying questions about scope and retry limits, and on the second pass hit 95%. Then it generated a &lt;strong&gt;contract&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Problem Statement&lt;/strong&gt;: The CLI runs its coding agent as a single-shot operation. The agent writes code, then validation runs after the agent has already terminated. When validation catches real issues, the results go to the user, not back to the agent. The agent never gets a chance to fix its own mistakes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Goals&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When validation catches fixable issues, feed them back to the agent for a second attempt within the same session, capped at 2 retries&lt;/li&gt;
&lt;li&gt;Run fast deterministic checks (typecheck, lint) during agent execution, catching errors earlier and saving tokens&lt;/li&gt;
&lt;li&gt;Refactor retry and validation logic into well-separated functions so the orchestration is understandable&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;The contract also pinned down scope boundaries (what&apos;s in, what&apos;s out, what&apos;s deferred), testable success criteria, and future considerations. From a few minutes of rambling to a document I could review, share with the team, and sit on before building anything.&lt;/p&gt;
&lt;h3&gt;PRDs or Straight to Specs&lt;/h3&gt;
&lt;p&gt;After I approved the contract, Ideation asked how to proceed: generate PRDs (Product Requirements Documents) for each phase, or go straight to implementation specs.&lt;/p&gt;
&lt;p&gt;PRDs are useful when you need buy-in. If the work involves other teams, stakeholders, or cross-functional review, PRDs add a requirements layer with user stories, functional requirements, and acceptance criteria. They&apos;re the artifact you hand to someone who needs to approve the &lt;em&gt;what&lt;/em&gt; before you get into the &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For this project I went straight to specs, but for our &lt;code&gt;workos doctor&lt;/code&gt; diagnostic CLI (which involved the support team), I used the PRD path. That project generated 26 functional requirements organized by feature area before a single spec was written.&lt;/p&gt;
&lt;h3&gt;Phases and Specs&lt;/h3&gt;
&lt;p&gt;Ideation broke the resilience work into three phases based on dependencies:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Restructure validation&lt;/strong&gt; into composable steps so fast checks can run independently&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add the retry loop&lt;/strong&gt; that feeds validation failures back to the agent within the same session&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update evals&lt;/strong&gt; to use the production retry pipeline so we can measure the impact&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each phase became its own implementation spec. Specs are detailed: technical approach, every file to create or modify, code snippets showing the target patterns, testing requirements, error handling strategies, and validation commands you can copy-paste to verify the work.&lt;/p&gt;
&lt;p&gt;For projects where 3+ phases follow the same structure (like adding SDK support for multiple languages), Ideation generates one full template spec and lightweight per-phase deltas instead of duplicating the whole thing N times. Smaller projects that only touch a few files skip phasing entirely and get a single &lt;code&gt;spec.md&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Feedback Loops&lt;/h3&gt;
&lt;p&gt;This is the part I think matters most. Each spec includes per-component &lt;a href=&quot;https://ampcode.com/notes/feedback-loopable&quot;&gt;feedback loops&lt;/a&gt;: a playground, an experiment, and a fast check command. Whoever picks up the spec (human or agent) can verify their work &lt;em&gt;during&lt;/em&gt; implementation.&lt;/p&gt;
&lt;p&gt;The feedback mechanism matches the component type. Data layers get test files. UI components get a dev server or Storybook. API endpoints get curl scripts. Config and type changes skip feedback loops entirely since typecheck covers them.&lt;/p&gt;
&lt;p&gt;Phase 1&apos;s quick-checks module defined its loop like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Playground&lt;/strong&gt;: A test file with fixture paths for projects with intentional type errors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experiment&lt;/strong&gt;: Test with a clean project (all pass), a project with a type error (typecheck fails, build skipped), and a project with no &lt;code&gt;tsc&lt;/code&gt; available (fallback)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check command&lt;/strong&gt;: &lt;code&gt;pnpm test src/lib/validation/quick-checks&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before presenting any spec, Ideation self-reviews feedback loop quality. If iterative components are missing loops, it revises the spec before showing it to you.&lt;/p&gt;
&lt;p&gt;Each phase also ends with checkpoint criteria: specific behaviors to verify before moving on. Phase 2 can&apos;t start until Phase 1&apos;s checkpoints pass.&lt;/p&gt;
&lt;h3&gt;Execution&lt;/h3&gt;
&lt;p&gt;The specs live as markdown files in &lt;code&gt;docs/ideation/&lt;/code&gt;. When it&apos;s time to build, I run &lt;code&gt;/execute-spec&lt;/code&gt; in a fresh Claude Code session. It reads the spec and explores the codebase for relevant patterns. Then it creates granular tasks with dependencies, sets up the feedback environment, and starts building. After each component it runs the check command. Clean context, continuous validation, no stale conversation history from the planning phase.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/execute-spec&lt;/code&gt; without arguments auto-detects the next unblocked phase across sessions, so you can &lt;code&gt;/clear&lt;/code&gt; and &lt;code&gt;/execute-spec&lt;/code&gt; repeatedly as you work through phases.&lt;/p&gt;
&lt;p&gt;When phases are independent, Ideation analyzes the dependency graph to figure out which ones can run concurrently. It also detects shared files across specs that could cause merge conflicts. Then it generates a ready-to-paste prompt for &lt;a href=&quot;https://code.claude.com/docs/en/agent-teams&quot;&gt;agent teams&lt;/a&gt;. You paste it into a fresh session in delegate mode, and the lead spawns teammates, each with their assigned spec and plan approval required. For parallelism &lt;em&gt;within&lt;/em&gt; a single phase, &lt;code&gt;/execute-spec --parallel&lt;/code&gt; spawns subagents for independent components.&lt;/p&gt;
&lt;h2&gt;When to Use Plan Mode vs. Ideation&lt;/h2&gt;
&lt;p&gt;This comes down to how well-formed your idea is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use plan mode&lt;/strong&gt; when you have a clear task. &quot;Refactor the auth module.&quot; &quot;Add pagination to the API.&quot; You know what you want, you just want Claude to think before coding. Plan mode explores, proposes, and executes in one session.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use Ideation&lt;/strong&gt; when you have scattered thoughts instead of a clear task. When you&apos;re not sure what&apos;s in scope. When the work will span multiple sessions or phases. When you want the AI to prove it understands the problem before it starts planning. Or when you need reviewable artifacts (contracts, PRDs, specs) to share with others before writing code.&lt;/p&gt;
&lt;p&gt;Ideation produces portable files that survive beyond a single conversation. Review them with your team, sit on them for a week, or hand them to &lt;code&gt;/execute-spec&lt;/code&gt; whenever you&apos;re ready.&lt;/p&gt;
&lt;p&gt;I&apos;ve used it on four projects so far. Each started as messy thoughts. Each produced artifacts that I handed off to fresh sessions for clean-context execution. The projects that would have stalled in the &quot;I should build that&quot; phase are actually getting built.&lt;/p&gt;
&lt;h2&gt;Try It&lt;/h2&gt;
&lt;p&gt;First, add the marketplace:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/plugin marketplace add nicknisi/claude-plugins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install the plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/plugin install ideation@nicknisi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then type &lt;code&gt;/ideation&lt;/code&gt; and start rambling. The mess is the input.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;Why not npx skills?&quot; variant=&quot;info&quot;&amp;gt;
&lt;code&gt;npx skills add nicknisi/claude-plugins&lt;/code&gt; only installs the &lt;code&gt;/ideation&lt;/code&gt; skill. Installing the full plugin includes the
companion &lt;code&gt;/execute-spec&lt;/code&gt; skill, which handles handing off your spec to a fresh session for implementation.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;The code writes itself now. Ideation makes sure you know what to write.&lt;/p&gt;
</content:encoded></item><item><title>Digging through my tool box</title><link>https://nicknisi.com/posts/my-tooling/</link><guid isPermaLink="true">https://nicknisi.com/posts/my-tooling/</guid><description>A deep dive into my tooling setup on the JS Party podcast</description><pubDate>Sat, 25 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Audio from &apos;@/components/Audio.astro&apos;;
import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;In 2023, KBall interviewed me on &lt;a href=&quot;https://jsparty.fm&quot;&gt;JS Party&lt;/a&gt; about my developer tooling setup, and generally how I use my tools. I thought
it would be beneficial to link up a more info that delves into my tools, how I use them, and how you can set up your tools as well.&lt;/p&gt;
&lt;p&gt;You can listen to the show here.&lt;/p&gt;
&lt;p&gt;&amp;lt;Audio episode={278} /&amp;gt;&lt;/p&gt;
&lt;p&gt;This is a great overview of what typically goes into my daily workflow and the tools I use to get my job done. I
absoluately believe that one should always use the right tool for the job, and that translates into my tools constantly
changing as I learn, grow, and evolve! For example, the first tool I talk about in the episode is about the wonderful
&lt;a href=&quot;https://sw.kovidgoyal.net/kitty/#&quot;&gt;Kitty&lt;/a&gt; terminal emulator but in the months since this show has gone out, I have switched off that and over to
&lt;a href=&quot;https://wezfurlong.org/wezterm/index.html&quot;&gt;WezTerm&lt;/a&gt;. As my tooling changes I tend to keep my &lt;a href=&quot;/uses&quot;&gt;/uses&lt;/a&gt; page
up-to-date, so check there for the latest and greatest!&lt;/p&gt;
&lt;p&gt;Of course, the major star of my tooling workflow is my &lt;a href=&quot;https://github.com/nicknisi/dotfiles&quot;&gt;dotfiles&lt;/a&gt;! With this
setup, I can go from new machine to ready to work in less than 20 minutes by relying on tools like
&lt;a href=&quot;https://brew.sh&quot;&gt;Homebrew&lt;/a&gt;, &lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt;, and &lt;a href=&quot;https://neovim.io&quot;&gt;Neovim&lt;/a&gt;. I catalog my journey from Vim
novice to ~expert~ where I&apos;m at now &lt;a href=&quot;/vim&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;A Video Walkthrough&lt;/h2&gt;
&lt;p&gt;I spoke to &lt;a href=&quot;https://www.joshmedeski.com&quot;&gt;Josh Medeski&lt;/a&gt; about my tooling setup and how it&apos;s changed over the years. It&apos;s a fun show and definitely
worth a watch.&lt;/p&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;17o9QrCcF_o&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;It categorizes the changes that have happened since my somewhat popular video on the same subject from years earlier.
You can watch that &lt;a href=&quot;https://www.youtube.com/watch?v=5r6yzFEXajQ&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>John and Nick discuss Lazy.nvim</title><link>https://nicknisi.com/posts/lazy-nvim/</link><guid isPermaLink="true">https://nicknisi.com/posts/lazy-nvim/</guid><description>John and Nick are back to discuss switching to a new plugin manager for Neovim.</description><pubDate>Tue, 28 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://jdotc.xyz/&quot;&gt;John Christopher&lt;/a&gt; and I are back in a new video discussing &lt;a href=&quot;https://github.com/folke/lazy.nvim&quot;&gt;Lazy.nvim&lt;/a&gt;, why we switched to it,
and how we set it up. The conversation naturally goes off the rails from there!&lt;/p&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;KauP0VNb6Q4&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Video references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/folke/lazy.nvim&quot;&gt;folke/lazy.nvim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.lazyvim.org/&quot;&gt;LazyVim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=aqlxqpHs-aQ&quot;&gt;Migrating from Packer.nvim to Lazy.nvim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/junegunn/vim-plug&quot;&gt;junegunn/vim-plug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/johnnymorganz/stylua&quot;&gt;StyLua&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>How I use git worktrees</title><link>https://nicknisi.com/posts/git-worktrees/</link><guid isPermaLink="true">https://nicknisi.com/posts/git-worktrees/</guid><description>simultanous branches for truly scattered development</description><pubDate>Mon, 10 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &apos;@/components/Callout.astro&apos;;&lt;/p&gt;
&lt;p&gt;Git &lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot;&gt;worktrees&lt;/a&gt; are extremely powerful. In short, they allow you to checkout a
branch into a specific directory. Then, you can checkout another branch into another directory. From here, switching
branches is as simple as switching directories! This is essential for developers who might be working on several things
at once or who are regularly interrupted and need to switch to a bug fix or even just need to review some code.&lt;/p&gt;
&lt;p&gt;The most basic use of a git worktree is to simply checkout a branch using the following command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree add -b feature-branch origin/main ../feature-branch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a new directory as a sibling to your normal working directory, create a branch called
&lt;code&gt;feature-branch&lt;/code&gt;, and set the inital state of the branch to be that of &lt;code&gt;origin/main&lt;/code&gt;. Inside this new directory will be
the exact project checked out where you can run all of the normal git commands and set up the project as normal.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;info&quot;&amp;gt;
You&apos;ll need to &lt;code&gt;npm install&lt;/code&gt; or do whatever to set up your project as you normally would, as if this were a new clone.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;One thing I don&apos;t like about this setup is the &quot;scatteredness&quot; of my repo with one seemingly &lt;em&gt;blessed&lt;/em&gt; worktree and
then a bunch of siblings that are outside of that worktree. Instead, I like to use a &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-on-the-Server-Getting-Git-on-a-Server#_bare_repo&quot;&gt;bare
repo&lt;/a&gt; setup. In this, the project
is checked out without a working tree, similar to how it would be on GitHub and elsewhere.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;▷ git clone --bare git@github.com:nicknisi/dotfiles.git dotfiles
Cloning into bare repository &apos;dotfiles&apos;...
remote: Enumerating objects: 6464, done.
remote: Counting objects: 100% (1418/1418), done.
remote: Compressing objects: 100% (603/603), done.
remote: Total 6464 (delta 913), reused 1154 (delta 796), pack-reused 5046
Receiving objects: 100% (6464/6464), 3.44 MiB | 10.71 MiB/s, done.
Resolving deltas: 100% (3715/3715), done.

~/Developer/test
▷ cd dotfiles

~/Developer/test/dotfiles
▷ ll
.rw-r--r-- 173 nicknisi  7 Oct 10:36  config
.rw-r--r--  73 nicknisi  7 Oct 10:36  description
.rw-r--r--  21 nicknisi  7 Oct 10:36  HEAD
drwxr-xr-x   - nicknisi  7 Oct 10:36  hooks
drwxr-xr-x   - nicknisi  7 Oct 10:36  info
drwxr-xr-x   - nicknisi  7 Oct 10:36  objects
.rw-r--r-- 287 nicknisi  7 Oct 10:36  packed-refs
drwxr-xr-x   - nicknisi  7 Oct 10:36  refs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see in here, we now have the files that would normally appear in the &lt;code&gt;.git/&lt;/code&gt; directory directly inside the
directory we made when cloning the repository. So far that doesn&apos;t seem better! From here, we could create a new
worktree with the command above and it would appear inline with all of these important-looking git files, which adds a
lot of confusion. Instead, we can hide the &lt;code&gt;.git&lt;/code&gt; files inside of another directory when we bare clone the repository.
I like to call this directory &lt;code&gt;.bare&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;▷ mkdir -p ~/Developer/dotfiles
▷ git clone --bare git@github.com:nicknisi/dotfiles.git .bare
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# create a new directory for the repository
▷ mkdir project &amp;amp;&amp;amp; cd project
# glone the repository (bare) and hide it in a hidden direcotry
▷ git clone --bare git@github.com:user/project.git .bare
# create a `.git` file that points to the bare repo
▷ echo &quot;gitdir: ./.bare&quot; &amp;gt; .git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The benefit of this setup is that the bare repo contents are hidden away in &lt;code&gt;.bare&lt;/code&gt;, and then the directory containing
that effectively becomes a place to create worktrees associated with that bare repo, thanks to the &lt;code&gt;.git&lt;/code&gt; file, which
is a pointer to where the git database is located.&lt;/p&gt;
&lt;p&gt;From here, new worktrees can be created and maintained like normal. However, there are a few small issues because when
a bare repo is cloned remote-tracking branches are not created. So, when trying to &lt;code&gt;git fetch&lt;/code&gt;, no remote branches are
fetched. This can be fixed with a few commands to update what happens on fetch and to associate local branches with the
their remote.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;▷ git config remote.origin.fetch &apos;+refs/heads/*:refs/remotes/origin/*&apos;
▷ git fetch
▷ git for-each-ref --format=&apos;%(refname:short)&apos; refs/heads | xargs -n1 -I{} git branch --set-upstream-to=origin/{}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;info&quot;&amp;gt;
I created a &lt;a href=&quot;https://github.com/nicknisi/dotfiles/blob/main/bin/git-bare-clone&quot;&gt;script&lt;/a&gt; to help make this setup
easier.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;The downsides&lt;/h2&gt;
&lt;p&gt;While I think this setup is fantastic and I don&apos;t see myself going back to the old, single woking directory lifestyle,
there are a few downsides that you should be aware of.&lt;/p&gt;
&lt;h3&gt;Each worktree is effectively its own project&lt;/h3&gt;
&lt;p&gt;Each worktree exists independently of the other. This is obviously a good thing because it means you can run them
independently and simultanously, it also means that you need to &lt;code&gt;npm install&lt;/code&gt; (or your equivelant) on every one of
them. This means that the creation and setup of each worktree can be an ordeal. For my main work project, &lt;code&gt;npm install&lt;/code&gt;
takes about 5 minutes 😱.&lt;/p&gt;
&lt;h3&gt;The output of &lt;code&gt;git branch&lt;/code&gt; is pretty useless&lt;/h3&gt;
&lt;p&gt;Looking to see what branches you have created locally becomes more of an ordeal because the &lt;code&gt;git branch&lt;/code&gt; command will
list every branch that exists in the bare clone.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;▷ git --no-pager branch | wc -l
    1520
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How I use worktrees to get things done&lt;/h2&gt;
&lt;p&gt;I will use my &lt;code&gt;git bare-clone&lt;/code&gt; script to set up a new, bare repository ready-to-go for creating multiple worktrees. In
the way I work, I will create a new worktree for each feature that I am working on, meaning I will have several
worktrees that I regularly prune with &lt;code&gt;git worktree remove&lt;/code&gt;. However, there are a few stable worktrees that I will
perpetually keep around in a project.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt; - The main worktree simply contains the &lt;code&gt;main&lt;/code&gt; branch checked out. It&apos;s the easiest way to run the current
&lt;em&gt;known good&lt;/em&gt; state. I can run this side-by-side with my feature branch worktree to compare and contrast functionality
on the fly.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;review&lt;/code&gt; - This is a worktree that I&apos;ll use to check out a pull request I am reviewing so that I can test it, run
it, run its tests, etc. I&apos;ll use the fantastic &lt;code&gt;gh pr checkout 1234&lt;/code&gt; command from the &lt;a href=&quot;https://cli.github.com&quot;&gt;GitHub
CLI&lt;/a&gt; to check out PRs easily into this
worktree.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hotfix&lt;/code&gt; - this worktree is reserved for quickly creating hotfix PRs in. I keep this around because I want to quickly
jump in and start working rather than creating a new feature-branch worktree and then waiting for the long, long &lt;code&gt;npm install&lt;/code&gt; to finish.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Quickly creating new worktrees&lt;/h3&gt;
&lt;p&gt;To save time, I made a &lt;a href=&quot;https://gist.github.com/nicknisi/a26f148611517e3d998eb456ac57efff&quot;&gt;helper script&lt;/a&gt; that I use to
create new worktrees for each feature branch. This is mostly straightforward, and the main benefit this gives me is to
automatically create and push a remote feature branch, kick off &lt;code&gt;npm install&lt;/code&gt;, and kick off a build of my project. It
can take several minutes to complete, but it&apos;s set and forget and then I can come back to a fully up-and-running
worktree, ready to be worked!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Using git worktrees are a big change to how I approach development on large projects where I&apos;m involved in lots of
different ways including feature work, reviews, and hotfixes. There&apos;s good and bad to them, but for me, the good
outweighs the bad and I don&apos;t see myself ever going back!&lt;/p&gt;
</content:encoded></item><item><title>I&apos;m emceeing SquiggleConf</title><link>https://nicknisi.com/posts/emceeing-squiggle-conf/</link><guid isPermaLink="true">https://nicknisi.com/posts/emceeing-squiggle-conf/</guid><description>SquiggleConf is a web devtools-focused conference in Boston.</description><pubDate>Mon, 30 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m thrilled to be co-emceeing &lt;a href=&quot;https://2024.squiggleconf.com&quot;&gt;SquiggleConf&lt;/a&gt; at the New England Aquarium in Boston on October 3-4, 2024. This conference focuses on supercharging web developers and their tools, bringing together experts to discuss cutting-edge web development practices.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key highlights:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Diverse speaker and topic lineup&lt;/li&gt;
&lt;li&gt;Two-day event featuring full-length talks (30 minutes + Q&amp;amp;A) on excellent web dev tooling.&lt;/li&gt;
&lt;li&gt;Organized by &lt;a href=&quot;https://www.linkedin.com/in/dimitrimitropoulos/&quot;&gt;Dimitri Mitropoulos&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/in/joshuakgoldbergcodes/&quot;&gt;Josh Goldberg&lt;/a&gt;, who are passionate about advancing web development.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As my 10th emcee role and 3rd in an aquarium setting, I&apos;m excited to contribute to this impactful event and its stellar lineup of speakers.&lt;/p&gt;
&lt;p&gt;I also had the honor of sitting down with Dimitri and Josh on JS Party to discuss the conference and growing
communities. &lt;a href=&quot;https://changelog.com/jsparty/339&quot;&gt;Give it a listen&lt;/a&gt;!&lt;/p&gt;
</content:encoded></item><item><title>Coding With My Eyes Wide Shut</title><link>https://nicknisi.com/posts/coding-with-my-eyes-wide-shut/</link><guid isPermaLink="true">https://nicknisi.com/posts/coding-with-my-eyes-wide-shut/</guid><description>At WorkOS&apos;s AI onsite and MCP Night, I experimented with vibe coding. What started as a two-hour experiment with 5,000 lines of unseen TypeScript became a glimpse into programming&apos;s future.</description><pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been thinking a lot about how we use AI in our daily work. Before my first &lt;a href=&quot;https://workos.com&quot;&gt;WorkOS&lt;/a&gt; onsite, I primarily used AI as a thinking assistant. I&apos;d have higher-level architecture discussions with AI, use it to generate mermaid diagrams, and help manage the context in my head. Safe, controlled interactions.&lt;/p&gt;
&lt;p&gt;That all changed during my AI week in San Francisco. Or did it?&lt;/p&gt;
&lt;h2&gt;The Setup&lt;/h2&gt;
&lt;p&gt;At the onsite, I taught a workshop about using AI to augment your skills. The key message: AI should enhance what you do, not replace who you are. Break down problems into smaller pieces. Keep the AI focused on solutions, not praise.&lt;/p&gt;
&lt;p&gt;I also gave a lightning talk called &quot;AI on the coding periphery&quot; - showcasing all the ways AI helps with the boring stuff that eats your day. Not writing code, but everything around it.&lt;/p&gt;
&lt;p&gt;Both went well. People nodded along. But I was still teaching from my comfort zone - AI as a helpful assistant, nothing more.&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;A large part of our onsite was dedicated to AI hacking - see what you can do with AI. Experiment and push boundaries. During this hacking session, I noticed a teammate had submitted a &lt;a href=&quot;https://github.com/workos/workos-node/pull/1273&quot;&gt;pull request&lt;/a&gt; to our Node SDK. Standard stuff. Then I spotted the same PR in our &lt;a href=&quot;https://github.com/workos/workos-ruby/pull/365&quot;&gt;Ruby&lt;/a&gt; and &lt;a href=&quot;https://github.com/workos/workos-php/pull/285&quot;&gt;PHP&lt;/a&gt; SDKs.&lt;/p&gt;
&lt;p&gt;The tedium struck me immediately. Here&apos;s an incredible engineer doing the same simple task multiple times, context switching between languages. Time that could be spent on more interesting problems.&lt;/p&gt;
&lt;p&gt;My teammate Nick and I claimed a corner of the conference room. We had a couple hours. Perfect time to experiment with something I&apos;d yet to try: vibe coding.&lt;/p&gt;
&lt;h2&gt;The Experiment&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Vibe_coding&quot;&gt;Vibe coding&lt;/a&gt; has a simple premise: you prompt an AI to write code, but you never look at the code itself. You test it, see what breaks, prompt again. Rinse and repeat.&lt;/p&gt;
&lt;p&gt;It&apos;s like cooking with your eyes closed, tasting along the way and suggesting changes, but never dealing with ingredients directly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our goal:&lt;/strong&gt; build a GitHub Action that would:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Watch for PRs with a specific label in any SDK repo&lt;/li&gt;
&lt;li&gt;Analyze what changed&lt;/li&gt;
&lt;li&gt;Automatically create matching PRs in our other SDK repos&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The constraint:&lt;/strong&gt; we wouldn&apos;t look at any code Claude wrote. Not once.&lt;/p&gt;
&lt;h2&gt;The Journey&lt;/h2&gt;
&lt;p&gt;We started with &lt;a href=&quot;https://www.anthropic.com/claude-code&quot;&gt;Claude Code&lt;/a&gt; and a straightforward prompt explaining our requirements. Files started appearing. TypeScript. Lots of it.&lt;/p&gt;
&lt;p&gt;First test with &lt;a href=&quot;https://github.com/nektos/act&quot;&gt;&lt;code&gt;act&lt;/code&gt;&lt;/a&gt;: complete failure. But not just any failure - the kind where entire files were being deleted and replaced with tiny fragments. &lt;strong&gt;But it was doing something.&lt;/strong&gt; Code was changing, the action part was running as expected - &lt;strong&gt;within minutes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is where vibe coding gets challenging. When things go wrong, you&apos;re debugging blind. You can&apos;t just open the file and see what&apos;s happening. You have to think differently.&lt;/p&gt;
&lt;p&gt;Instead of diving into code, I started interviewing Claude like a detective:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Walk me through your approach&quot;&lt;/li&gt;
&lt;li&gt;&quot;What exactly are you sending to the OpenAI API?&quot;&lt;/li&gt;
&lt;li&gt;&quot;Why would this approach result in deleted files?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The revelation came after about an hour of this back-and-forth. Claude was sending entire file contents to OpenAI for every tiny change, blowing past context windows. The model was essentially panicking and returning fragments.&lt;/p&gt;
&lt;p&gt;One corrective prompt changed everything: &quot;Focus on diffs. Make minimal changes. Don&apos;t rewrite entire files for one-line modifications.&quot;&lt;/p&gt;
&lt;h2&gt;The Results&lt;/h2&gt;
&lt;p&gt;By the end of our two-hour session:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;~5,000 lines of TypeScript we never read&lt;/li&gt;
&lt;li&gt;A working &lt;a href=&quot;https://github.com/workos/workos-node/tree/automatic-prs/.github/actions/sdk-sync&quot;&gt;GitHub Action&lt;/a&gt; that successfully created PRs&lt;/li&gt;
&lt;li&gt;Automated SDK synchronization (with some rough edges)&lt;/li&gt;
&lt;li&gt;Zero lines of code written by human hands&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The PRs it generated for &lt;a href=&quot;https://github.com/workos/workos-ruby/pull/373&quot;&gt;Ruby&lt;/a&gt; and &lt;a href=&quot;https://github.com/workos/workos-php/pull/284&quot;&gt;PHP&lt;/a&gt; weren&apos;t perfect. But they worked. Somewhat. Changes in one SDK triggered matching changes in others.&lt;/p&gt;
&lt;p&gt;That night in my hotel room, I couldn&apos;t sleep. What had taken us two hours would have been days of careful coding before. If AI could do this now, what would it do in six months? A year? I&apos;d spent fifteen years perfecting my craft, and suddenly I was wondering if those skills were becoming obsolete. The ground beneath my career felt unstable. Was I witnessing the beginning of the end for traditional programming? The thought was terrifying.&lt;/p&gt;
&lt;h2&gt;MCP Night Perspective&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The WorkOS team also organized a public &lt;a href=&quot;https://workos.com/mcp-night&quot;&gt;MCP Night&lt;/a&gt; at the Exploratorium. Here, everything shifted into focus. Over 600 developers wrapped around the building, waiting to get in. The energy inside was electric - not just excitement, but a collective recognition that something fundamental was changing.&lt;/p&gt;
&lt;p&gt;The demos pushed boundaries I didn&apos;t know existed. An AI &lt;a href=&quot;https://mcp.shop&quot;&gt;ordering t-shirts&lt;/a&gt; through MCP. Postman showcasing automated API testing that felt alive. Block demonstrating Goose, their AI developer. Each demo peeling back another layer of what&apos;s possible when you give AI real tools to work with.&lt;/p&gt;
&lt;p&gt;But the conversations between demos revealed something deeper. Experienced developers admitting they had no idea these capabilities existed. Others sharing wild experiments from their own labs. Everyone comparing notes, trying to map this new territory together.&lt;/p&gt;
&lt;p&gt;Standing there, phone in hand, capturing the chaos for our social channels, I kept thinking about our afternoon experiment. We&apos;d stumbled into something bigger than a GitHub Action. We&apos;d glimpsed a different way of thinking about code itself.&lt;/p&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;p&gt;The vibe coding experiment forced me to confront assumptions I didn&apos;t know I had. When you can&apos;t see the code, you have to trust the process. When you can&apos;t debug traditionally, you develop new skills. When you can&apos;t micromanage every line, you learn what actually matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Good:&lt;/strong&gt; We went from idea to working prototype in two hours. Not perfect, not production-ready, but working. The speed of iteration was intoxicating. More importantly, debugging through conversation taught me to think about problems differently. Instead of diving into implementation details, I had to stay at the level of intent and approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Challenges:&lt;/strong&gt; Trust becomes everything when you can&apos;t verify. The code Claude generated worked, but lacked the elegance of human-crafted solutions. We discovered tool constraints only through failure. Each iteration revealed another assumption we&apos;d made about how AI interprets our instructions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Reality Check:&lt;/strong&gt; You can&apos;t vibe code your way to production systems. That&apos;s not the point. The point is recognizing when human eyes on every line of code isn&apos;t the highest value activity. Sometimes you need speed. Sometimes you need exploration. Sometimes you need to prototype five ideas in the time it would take to perfect one.&lt;/p&gt;
&lt;p&gt;The practical sweet spot isn&apos;t full vibe coding or full manual coding. It&apos;s knowing when to apply each approach. Use AI for rapid prototyping. Let it handle boilerplate while you focus on architecture. Most importantly - and this is what I emphasized in my workshop - talk through problems with AI before interrupting busy teammates. Clear your own thinking first.&lt;/p&gt;
&lt;h2&gt;The Transformation&lt;/h2&gt;
&lt;p&gt;Would I ship our vibe-coded GitHub Action to production? No way. Not without a thorough review and probably a rewrite.&lt;/p&gt;
&lt;p&gt;But that misses the point entirely.&lt;/p&gt;
&lt;p&gt;Before that week, I taught people to use AI as a better rubber duck. After that week, I understood we&apos;re not just changing our tools - we&apos;re changing our relationship with code itself.&lt;/p&gt;
&lt;p&gt;The developers still submitting the same PR three times aren&apos;t just wasting time. They&apos;re stuck in a paradigm where every line of code needs their direct touch. They&apos;re missing the chance to work at a higher level - to be orchestrators rather than typists.&lt;/p&gt;
&lt;p&gt;My workshop talked about augmenting skills. My lightning talk showed AI handling peripheral tasks. But the vibe coding experiment? That showed me what augmentation really means. It&apos;s not about AI writing code for you. It&apos;s about developing new skills for a world where code can write itself.&lt;/p&gt;
&lt;p&gt;That week, I learned to code with my eyes wide shut.&lt;/p&gt;
&lt;p&gt;The strange part? I&apos;ve never seen more clearly where we&apos;re headed. We&apos;re not replacing programmers. We&apos;re evolving what programming means. The future isn&apos;t about writing code - it&apos;s about intent, architecture, and knowing when human creativity matters most.&lt;/p&gt;
&lt;p&gt;I won&apos;t be vibe coding in production anytime soon. But the experiment taught me something crucial: I need to evolve where I fit in the problem-solving equation. The future of this profession isn&apos;t about protecting our old ways of working. It&apos;s about finding new ways to create value.&lt;/p&gt;
&lt;p&gt;And sometimes, you need to close your eyes to see that future.&lt;/p&gt;
</content:encoded></item><item><title>An introduction to codemods</title><link>https://nicknisi.com/posts/codemods-introduction/</link><guid isPermaLink="true">https://nicknisi.com/posts/codemods-introduction/</guid><description>Refactoring with effortless consistency.</description><pubDate>Fri, 10 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;;
import astImage from &apos;@/assets/posts/codemods-ast.png&apos;;&lt;/p&gt;
&lt;p&gt;I gave a quick talk at Idea Storm Q1 2023 in Grand Island, NE where I spoke about something I&apos;ve been pretty excited about recently... codemods! Codemods are a powerful tool for developers to automate repetitive and time-consuming tasks. These small scripts can analyze and modify source code, allowing developers to easily make sweeping changes across their entire codebase without having to manually search and replace code. More importantly, they allow for consistency when making changes, as specific &lt;em&gt;types&lt;/em&gt; of code can be targeted, ensuring the changes are exactly what&apos;s expected while handling any edge cases.&lt;/p&gt;
&lt;h2&gt;The setup&lt;/h2&gt;
&lt;p&gt;The library we&apos;ll use is &lt;a href=&quot;https://github.com/facebook/jscodeshift&quot;&gt;jscodeshift&lt;/a&gt;. It&apos;s a toolkit for creating codemods and generating code that tries to match your existing code style. Let&apos;s make this really simple. Say we have a file that contains just a single import statement.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { render } from &apos;some-library&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we want to make a change to every file that imports this. Now, this make look like a simple-enough change that we could just use our editor&apos;s find and replace feature, but this can be deceiving. Not all imports may look the same.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// classic import
import { render } from &apos;some-library&apos;;

// importing other things, too
import { render, screen } from &apos;some-library&apos;;

// import and rename
import { render as r } from &apos;some-library&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…and several combinations in-between.&lt;/p&gt;
&lt;h2&gt;Constructing a new codemod&lt;/h2&gt;
&lt;p&gt;With a codemod targeted at replacing this import properly, we can be assured that all of these examples are accounted for and handled properly, giving us the confidence we need that our changes are consistent and accurate. When making these changes using a codemod, we&apos;re not doing anything like a find/replace. Instead, a codemod works by transforming our source file into an &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;Abstract Syntax Tree&lt;/a&gt;, the secret sauce to achieve consistency. By looking at the source as a tree of nodes, we can specifically target and manipulate the tree as needed. Let&apos;s take a look at the original input file again and what its AST representation looks like.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { render } from &apos;some-library&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;Image src={astImage} class=&quot;image&quot; alt=&quot;an AST representation of our import statement&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Looking at the AST, it&apos;s made up of a top-level source that contains a single &lt;code&gt;ImportDeclration&lt;/code&gt; node. This node
contains an &lt;code&gt;ImportClause&lt;/code&gt; and a &lt;code&gt;StringLiteral&lt;/code&gt;. The &lt;code&gt;StringLiteral&lt;/code&gt; is what we want to change, but we need to assess
the &lt;code&gt;ImportClause&lt;/code&gt; first and identify that it&apos;s actually importing &lt;code&gt;render&lt;/code&gt; in some way.&lt;/p&gt;
&lt;p&gt;To get started with a new codemod, we simply need to start a file that exports a default &lt;code&gt;transform&lt;/code&gt; function. This
function will receive a source file and the jscodeshift API to work with. From there, we can modify and return changes
to the source file. jscodeshift actually has great TypeScript support, so we&apos;ll write the codemod using that.&lt;/p&gt;
&lt;p&gt;The first thing we need to do is to import some types from jscodeshift and set up our &lt;code&gt;transform&lt;/code&gt; function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { API, FileInfo } from &apos;jscodeshift&apos;;

export default function transform(file: FileInfo, { jscodeshift: j }: API) {
	// codemod goes here.
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Setting up the codemod&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;file&lt;/code&gt; will contain all of the information about the file including the path and the contents as a string. To turn
it into an AST, we&apos;ll use the &lt;code&gt;j&lt;/code&gt; function provided by the jscodeshift API. We&apos;ll also define the &lt;code&gt;REPLACEMENT&lt;/code&gt; import
location that we&apos;ll use to update the import statements.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const root = j(file.source);
const REPLACEMENT = &apos;custom-render-package&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Find the right imports&lt;/h3&gt;
&lt;p&gt;We want to use the &lt;code&gt;find&lt;/code&gt; method to locate every import statement in the file(s) that specifically imports the
code we want to change. This will return an array that typically will have either &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;1&lt;/code&gt; value in this case.
Additionally, we want to only find the ones where &lt;code&gt;render&lt;/code&gt; is also being imported from &lt;code&gt;some-library&lt;/code&gt;. This will exclude
other cases, where potentially only &lt;code&gt;screen&lt;/code&gt; or other imports we don&apos;t care about are being imported.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const importDeclaration = root.find(j.ImportDeclaration, {
	source: {
		value: &apos;some-library&apos;,
	},
});
const importRender = importDeclaration.find(j.ImportSpecifier, {
	imported: { name: &apos;render&apos; },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Make the changes&lt;/h3&gt;
&lt;p&gt;If the import was found, we can remove the imported &lt;code&gt;render&lt;/code&gt; and construct a new import statement to point to our
new location. We also want to take note of whether &lt;code&gt;render&lt;/code&gt; was renamed to something else locally (using the &lt;code&gt;render as r&lt;/code&gt; syntax, for example).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (importRender.size()) {
  // at least one import was found that matches

  // get the local name (the `as r`) part, if it exists.
  const renderLocalName = importRender.get().value.local.name;

  // remove `render` from the import statement
  importRender.remove();

  // construct a whole new ImportDeclaration pointint to REPLACEMENT
  // taking care to set the local value to the captured name above
  const newImport = j.importDeclaration(
    [
      j.importSpecifier(j.identifier(&apos;render&apos;),
      j.identifier(renderLocalName))
    ],
    j.literal(REPLACEMENT),
  );
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Replace the imports&lt;/h3&gt;
&lt;p&gt;We&apos;ll want to determine if &lt;code&gt;render&lt;/code&gt; was the only thing imported originally. If it was, we&apos;ll completely replace
the old import with our new one. Otherwise, such as in the case where &lt;code&gt;screen&lt;/code&gt; was also imported, we&apos;ll simply leave
that import alone and add our new one after it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (importDeclaration.find(j.ImportSpecifier).size() === 0) {
	// render was the only import so replace the old ImportDeclaration
	// with our new one
	importDeclaration.replaceWith(newImport);
} else {
	// Something else was imported, so leave the rest alone and add ours
	// as a new import immediately following
	importDeclaration.insertAfter(newImport);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we&apos;ll want to return from our transform, turning our AST back into source code. jscodeshift will try and match
the styles that are already there, but you can also provide it with some hints.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return root.toSource({
	quote: &apos;single&apos;,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Running the codeomod&lt;/h2&gt;
&lt;p&gt;To run the codemod, we use the following
command. This will run the &lt;code&gt;transform&lt;/code&gt; on every file that matches the provided &lt;a href=&quot;https://en.wikipedia.org/wiki/Glob_(programming)&quot;&gt;glob&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx jscodeshift --parser tsx -t ./swap-render.ts src/**/*.spec.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each or our variations now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// classic import
import { render } from &apos;custom-render-package&apos;;

// importing other things, too
import { screen } from &apos;some-library&apos;;
import { render } from &apos;custom-render-package&apos;;

// import and rename
import { render as r } from &apos;custom-render-package&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, here&apos;s the full source code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { API, FileInfo } from &apos;jscodeshift&apos;;

export default function transform(file: FileInfo, { jscodeshift: j }: API) {
	const REPLACEMENT = &apos;custom-render-package&apos;;
	const root = j(file.source);

	const importDeclaration = root.find(j.ImportDeclaration, {
		source: {
			value: &apos;some-library&apos;,
		},
	});
	const importRender = importDeclaration.find(j.ImportSpecifier, {
		imported: { name: &apos;render&apos; },
	});

	if (importRender.size() &amp;gt; 0) {
		// get the render&apos;s local name
		const renderLocalName = importRender.get().value.local.name;
		importRender.remove();
		const newImport = j.importDeclaration(
			[j.importSpecifier(j.identifier(&apos;render&apos;), j.identifier(renderLocalName))],
			j.literal(REPLACEMENT),
		);

		if (importDeclaration.find(j.ImportSpecifier).size() === 0) {
			importDeclaration.replaceWith(newImport);
		} else {
			importDeclaration.insertAfter(newImport);
		}
		return root.toSource({
			quote: &apos;single&apos;,
		});
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Some pitfalls&lt;/h2&gt;
&lt;p&gt;While codemods can be incredibly powerful tools, there are some potential pitfalls to be aware of.&lt;/p&gt;
&lt;p&gt;Codemods are not always straightforward to write. Depending on the complexity of the changes you want to make, you may need to invest significant time and effort in creating a reliable and effective codemod. This can be especially challenging if you&apos;re not experienced with the particular programming language or library you&apos;re working with.&lt;/p&gt;
&lt;p&gt;Additionally, because codemods operate at a very high level, they can sometimes make unexpected or unwanted changes to your code. This is especially true if your codebase is not well-structured or contains subtle differences in the way different parts of the code are written. For this reason, it&apos;s important to test codemods on a small subset of files and possibly consider batching them for easeier and more thorough code reviews. We all know how thoroughly large pull reqeusts get reviewed. 😉 Keep them small.&lt;/p&gt;
&lt;p&gt;Finally, codemods may not always match your code style perfectly. For this reason, it&apos;s recommended to follow up on each
file that a codemod changed with a call to &lt;a href=&quot;https://prettier.io&quot;&gt;prettier&lt;/a&gt; or similar code formatting tool.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;codemods can be incredibly useful tools for streamlining the process of making changes to large codebases. They allow developers to automate tedious and repetitive tasks, significantly reducing the time and effort required to make these changes, as well as ensure consistency and accuracy throughout the codebase. Also, because codemods are written in code, they can be easily shared and reused within teams or across projects. It&apos;s definitely worth considering whether a codemod could help simplify and speed up your development process.&lt;/p&gt;
</content:encoded></item><item><title>Why Everyone Should Try Claude Skills</title><link>https://nicknisi.com/posts/claude-skills/</link><guid isPermaLink="true">https://nicknisi.com/posts/claude-skills/</guid><description>Claude Skills are the approachable AI tool I didn&apos;t know I needed.
</description><pubDate>Thu, 23 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import nickPresenting from &apos;@/assets/posts/nick-presenting-workos-claude-skills.jpg&apos;;
import { Image } from &apos;astro:assets&apos;;&lt;/p&gt;
&lt;p&gt;Anthropic just released &lt;a href=&quot;https://www.anthropic.com/news/skills&quot;&gt;Claude Skills&lt;/a&gt;, and I&apos;m trying not to get too excited. But I am.&lt;/p&gt;
&lt;p&gt;Simon Willison wrote that &lt;a href=&quot;https://simonwillison.net/2025/Oct/16/claude-skills/&quot;&gt;this could be bigger than MCP&lt;/a&gt;. After spending time with Skills this week, I think he&apos;s right.&lt;/p&gt;
&lt;h2&gt;What Took Me a While to Understand&lt;/h2&gt;
&lt;p&gt;Skills are just markdown files. That sounds simple. Maybe too simple. How could these be any different than the other Markdown files that Claude provides such as /commands, agents, and the iconic CLAUDE.md?&lt;/p&gt;
&lt;p&gt;It took me a bit to see why this is different. Skills aren&apos;t about hiding context like subagents do. They aren&apos;t prompts you explicitly invoke like slash commands. Skills are about discovery and determinism.&lt;/p&gt;
&lt;p&gt;Claude figures out when to use them. And when it does, it can leverage scripts and other tools to generate consistent, predictable output.&lt;/p&gt;
&lt;h2&gt;Where Skills Really Work&lt;/h2&gt;
&lt;p&gt;Skills can reference scripts and other resources. Claude Code will actually run those scripts in your environment. That&apos;s powerful.&lt;/p&gt;
&lt;p&gt;They work in Claude Desktop too, which is exciting. But there&apos;s a catch - no network access. Skills can&apos;t fetch from the internet or hit APIs when running in the desktop or web versions. Everything has to be internal.&lt;/p&gt;
&lt;p&gt;At first, this feels limiting. And it is, for Claude Desktop and web. But in Claude Code? No limits. You can access commands on your system, run scripts that talk to APIs, do whatever you need. MCP is about external connections. Skills are about what&apos;s already there.&lt;/p&gt;
&lt;h2&gt;Forcing Myself to Learn Them&lt;/h2&gt;
&lt;p&gt;&amp;lt;Image
quality=&quot;max&quot;
alt=&quot;Giving a presentation at the WorkOS office on Claude Skills&quot;
loading=&quot;lazy&quot;
inferSize
src={nickPresenting}
/&amp;gt;&lt;/p&gt;
&lt;p&gt;I&apos;m in San Francisco this week for a company onsite. I was asked to give a presentation on AI or my workflow. I decided to talk about Skills because they were brand new, and I wanted to force myself to actually learn them.&lt;/p&gt;
&lt;p&gt;I made three skills:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nicknisi/dotfiles/tree/main/home/.claude/skills/gpt5-consultant&quot;&gt;&lt;strong&gt;GPT-5 Consultant&lt;/strong&gt;&lt;/a&gt;: I turned my existing agent into a skill. It felt like a better fit. A self-contained script that knows how to talk to the OpenAI API. Clean. No external MCP needed.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nicknisi/dotfiles/tree/main/home/.claude/skills/conference-talk-builder&quot;&gt;&lt;strong&gt;Conference Talk Builder&lt;/strong&gt;&lt;/a&gt;: This one lets me brain-dump talking points and conclusions into a proper outline that tells a complete story. No more staring at blank slides wondering how to structure my thoughts.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nicknisi/dotfiles/tree/main/home/.claude/skills/claude-code-analyzer&quot;&gt;&lt;strong&gt;Claude Code Analyzer&lt;/strong&gt;&lt;/a&gt;: This is the one I&apos;m most excited about. It looks at how you use Claude Code in a project. It highlights permissions you grant frequently and suggests which ones to auto-approve. It recommends commands and agents you might want to create, then finds real examples on GitHub so you can see how others built similar tools. It also analyzes your usage patterns and suggests optimizations.&lt;/p&gt;
&lt;h2&gt;The Real Advantage Over MCP&lt;/h2&gt;
&lt;p&gt;The barrier to entry is absurdly low. You start with a markdown file. That&apos;s it.&lt;/p&gt;
&lt;p&gt;You experiment. You package it as a zip. You send it to colleagues. They can try it, modify it, extract value from it immediately. No complex tooling. No hosting. No distribution headaches.&lt;/p&gt;
&lt;p&gt;Claude ships with a skill creator skill. You describe what you want, and it builds the skill for you. Drop the zip into &lt;code&gt;.claude/skills&lt;/code&gt; or drag it into Claude Desktop or web. Done.&lt;/p&gt;
&lt;p&gt;MCP is powerful for external integrations. But Skills meet you where you are, with what you already have.&lt;/p&gt;
&lt;h2&gt;What Happened After My Talk&lt;/h2&gt;
&lt;p&gt;I presented these skills to the company. Showed why they&apos;re powerful. Demoed what I&apos;d built.&lt;/p&gt;
&lt;p&gt;Shortly after, I got a Slack message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Felt inspired by Nick Nisi&apos;s talk and created a design system Claude skill. It takes all content from the WorkDS pages and provides Claude with context on how to use certain components. Not all components are there, but I figure we can add to it and improve it over time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Someone took the idea and ran with it. Built something useful for their workflow. In the time it took me to grab coffee.&lt;/p&gt;
&lt;p&gt;That&apos;s the power of low barriers.&lt;/p&gt;
&lt;h2&gt;Where This Fits in My Workflow&lt;/h2&gt;
&lt;p&gt;I&apos;ve written before about &lt;a href=&quot;/posts/ai-tooling/&quot;&gt;my AI tooling setup&lt;/a&gt; and &lt;a href=&quot;/posts/coding-with-my-eyes-wide-shut/&quot;&gt;coding with Claude Code&lt;/a&gt;. Skills fill a gap I didn&apos;t realize existed.&lt;/p&gt;
&lt;p&gt;They sit between the full power of MCP and the simplicity of just talking to Claude. You get structured, repeatable workflows without the overhead of building external tools. That&apos;s exactly where I need them to be.&lt;/p&gt;
&lt;h2&gt;Try This&lt;/h2&gt;
&lt;p&gt;Everyone should experiment with Skills. The approachability is the feature. Start with a markdown file and see what happens.&lt;/p&gt;
&lt;p&gt;As Claude adds features, Skills will get more powerful. But they&apos;re already useful right now. You don&apos;t need to wait for the perfect use case. Make something small. See where it takes you.&lt;/p&gt;
</content:encoded></item><item><title>Case Statement: Building a Harness</title><link>https://nicknisi.com/posts/case-statement/</link><guid isPermaLink="true">https://nicknisi.com/posts/case-statement/</guid><description>I codified my job as a DX engineer into a system that dispatches agents, enforces conventions mechanically, and learns from its own failures. Here&apos;s how case works and why I think every developer should build something like it.</description><pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &apos;@/components/Callout.astro&apos;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;TL;DR&quot; variant=&quot;info&quot;&amp;gt;
&amp;lt;a href=&quot;https://github.com/workos/case&quot; class=&quot;text-vivid-light-blue-950 dark:text-vivid-light-blue-100 font-medium underline hover:text-vivid-light-blue-700 dark:hover:text-white&quot;&amp;gt;Case&amp;lt;/a&amp;gt; is a harness that dispatches six AI agents through a programmatic pipeline to fix bugs and ship features across WorkOS&apos;s open source repos. It enforces conventions mechanically, creates tamper-proof evidence that work was actually done, and learns from its own failures.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;I was good at steering AI agents. Really good. I could paste the right context, catch the mistakes before they shipped, remind the agent about the PR checklist it had already forgotten. I had it down to a rhythm.&lt;/p&gt;
&lt;p&gt;That was the problem. I was clearly the bottleneck.&lt;/p&gt;
&lt;p&gt;My job as a DX engineer at WorkOS is to maintain more than 20 open source repos. Authentication libraries, a CLI, SDKs for Next.js and TanStack Start, framework-agnostic session management. Eight languages across the stack, shared conventions where we can enforce them, a PR checklist that no one remembers completely. And I was spending all of my time manually steering one agent through one task at a time.&lt;/p&gt;
&lt;h2&gt;The problem with being the middleman&lt;/h2&gt;
&lt;p&gt;There&apos;s a concept in &lt;a href=&quot;https://openai.com/index/harness-engineering/&quot;&gt;harness engineering&lt;/a&gt; that changed how I think about agent work: managing agents is like managing 50 interns. You&apos;re judged on their productivity, not yours.&lt;/p&gt;
&lt;p&gt;I was managing one agent at a time and doing it well. But &quot;well&quot; still meant every session started with the same ten minutes of orientation: what branch, what repo, what&apos;s the issue, what are the conventions. By the time the agent had enough context to write a fix, I&apos;d spent more time on setup than the fix itself was worth.&lt;/p&gt;
&lt;p&gt;And the failures were always the same shape. The agent that wrote the code couldn&apos;t objectively test it. It was already convinced the fix worked because it just spent 5,000 tokens writing it. By the time it reached the PR checklist, it had forgotten half the requirements because the LLM compressed them to make room for newer tokens. Instructions decay. Context compacts. The agent that carefully reads your setup guide at token 500 has forgotten it by token 50,000.&lt;/p&gt;
&lt;p&gt;I knew what I needed. Not a better agent. A better environment for the agent to operate in.&lt;/p&gt;
&lt;h2&gt;What a harness actually is&lt;/h2&gt;
&lt;p&gt;I built &lt;a href=&quot;https://github.com/workos/case&quot;&gt;case&lt;/a&gt;. It&apos;s a spine repo. It doesn&apos;t contain application code. It contains the cross-cutting knowledge that no single repo owns: which repos exist, what conventions apply, how to validate work, what to do when something breaks.&lt;/p&gt;
&lt;p&gt;At its core, case has four things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A manifest.&lt;/strong&gt; &lt;code&gt;projects.json&lt;/code&gt;, a machine-readable map of every repo, its path, its commands, its remote. When an agent needs to know how to run tests in &lt;code&gt;authkit-session&lt;/code&gt;, it reads the manifest. No guessing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Golden principles.&lt;/strong&gt; 18 invariants enforced across all repos. TypeScript strict mode is always on. &lt;code&gt;pnpm&lt;/code&gt; is the only package manager. No secrets in source. Conventional commits. Session decryption must be fault-tolerant. Each one is either scripted (a shell command that checks it) or advisory (requires human judgment). The scripted ones get checked mechanically. You can&apos;t argue with &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Playbooks.&lt;/strong&gt; Step-by-step guides for recurring operations. How to fix a bug. How to add a CLI command. How to implement from a spec. The agent reads the playbook, follows the steps. No reverse-engineering patterns from existing code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A task system.&lt;/strong&gt; Markdown files with JSON companions. Drop a task in &lt;code&gt;tasks/active/&lt;/code&gt;, an agent picks it up. The task file has a mission summary at the top (one line: what and why, target repo, primary acceptance criterion) so the agent can orient even if context compaction eats the rest.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That last point is context engineering. When an LLM compresses older context, critical information can vanish. So every task template puts the most important thing in the first five lines. Summaries first, details second. If the bottom gets eaten, the agent still knows what it&apos;s doing.&lt;/p&gt;
&lt;h2&gt;Six agents, one pipeline&lt;/h2&gt;
&lt;p&gt;The single-agent approach failed because of a fundamental conflict: the agent that wrote the code can&apos;t objectively test it. It needs fresh eyes. In LLM terms, it needs a fresh context window.&lt;/p&gt;
&lt;p&gt;Case splits the work into six agents. Each gets a clean context, a focused job, and only the information it needs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Does&lt;/th&gt;
&lt;th&gt;Never does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Orchestrator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Parse issue, create task, smoke test, dispatch agents&lt;/td&gt;
&lt;td&gt;Write code, run Playwright&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Implementer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Write the fix, run unit tests, commit with WIP checkpoints&lt;/td&gt;
&lt;td&gt;Start example apps, create PRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Verifier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Test the specific fix with Playwright, create evidence&lt;/td&gt;
&lt;td&gt;Edit code, commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reviewer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Review diff against golden principles, classify findings&lt;/td&gt;
&lt;td&gt;Edit code, run tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Closer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create PR with thorough description, post review comments&lt;/td&gt;
&lt;td&gt;Edit code, run tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retrospective&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Analyze the run, propose harness improvements&lt;/td&gt;
&lt;td&gt;Edit target repo code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The constraints matter as much as the capabilities. The implementer can&apos;t create PRs because it would skip verification. The verifier can&apos;t edit code because that would compromise its independence. Each agent is scoped to a single responsibility, and the pipeline enforces the sequence.&lt;/p&gt;
&lt;p&gt;They communicate through the task file, a markdown document with a running progress log that each agent appends to. And structured JSON result blocks that the orchestrator parses deterministically:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
	&quot;status&quot;: &quot;completed&quot;,
	&quot;summary&quot;: &quot;fix: handle expired session cookies&quot;,
	&quot;artifacts&quot;: {
		&quot;commit&quot;: &quot;a1b2c3d&quot;,
		&quot;testsPassed&quot;: true,
		&quot;evidenceMarkers&quot;: [&quot;.case/slug/tested&quot;]
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No ambiguity. No &quot;I think the tests passed.&quot; The result either has evidence or it doesn&apos;t.&lt;/p&gt;
&lt;h2&gt;The philosophy&lt;/h2&gt;
&lt;p&gt;Before I explain how the pipeline works, here&apos;s the set of beliefs that shaped it. These are in case&apos;s docs and I come back to them constantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Humans steer. Agents execute.&lt;/strong&gt; I define goals and acceptance criteria. Agents implement. If I&apos;m writing code, something is wrong with the harness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When agents struggle, fix the harness.&lt;/strong&gt; The answer is never &quot;try harder.&quot; It&apos;s a missing doc, a playbook that skips a step, a convention that isn&apos;t enforced. Every failure is a harness bug.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Instructions decay, enforcement persists.&lt;/strong&gt; Agents forget instructions over long sessions. Pipeline gates and linters don&apos;t forget. If it matters, enforce it mechanically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The harness is the product. The code is the output.&lt;/strong&gt; I wrote that late at night and thought it was too dramatic. It&apos;s the most accurate thing in the repo.&lt;/p&gt;
&lt;p&gt;That last one took a while to internalize. My job used to be writing code across these repos. Now my job is building the system that writes code across these repos. The expertise is the same. Where it lives changed.&lt;/p&gt;
&lt;h2&gt;The moment prose stopped being enough&lt;/h2&gt;
&lt;p&gt;The first version of the pipeline was a SKILL.md file, a long markdown document that told the orchestrating agent &quot;now run the implementer, now check the result, now decide whether to retry or advance.&quot; It worked. Mostly. Until it didn&apos;t.&lt;/p&gt;
&lt;p&gt;The LLM would read a table of state transitions and &lt;em&gt;decide&lt;/em&gt; what to do next. Most of the time it decided correctly. But sometimes it would skip the verifier. Sometimes it would retry a failed implementer four times when the cap was one. Sometimes, after an interruption, it would re-read the task status and pick the wrong re-entry point.&lt;/p&gt;
&lt;p&gt;The fix was to stop trusting the LLM with flow control entirely.&lt;/p&gt;
&lt;p&gt;Case&apos;s pipeline is now a TypeScript &lt;code&gt;while&lt;/code&gt;/&lt;code&gt;switch&lt;/code&gt; loop. The LLM still does the work &lt;em&gt;inside&lt;/em&gt; each phase (writing code, running tests, reviewing diffs). But the transitions &lt;em&gt;between&lt;/em&gt; phases are deterministic &lt;code&gt;if/else&lt;/code&gt; branches in code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while (currentPhase !== &apos;complete&apos; &amp;amp;&amp;amp; currentPhase !== &apos;abort&apos;) {
	switch (currentPhase) {
		case &apos;implement&apos;: {
			const output = await runImplementPhase(config, store, previousResults);
			if (output.nextPhase === &apos;abort&apos;) {
				// handle failure: retry or escalate
			} else {
				currentPhase = output.nextPhase; // deterministically &apos;verify&apos;
			}
			break;
		}
		case &apos;verify&apos;: {
			/* ... */
		}
		case &apos;review&apos;: {
			/* ... */
		}
		case &apos;close&apos;: {
			/* ... */
		}
		case &apos;retrospective&apos;: {
			/* ... */
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Retry caps are &lt;code&gt;maxRetries: 1&lt;/code&gt;, checked in code before spawning. Resume after interrupt calls &lt;code&gt;determineEntryPhase(task)&lt;/code&gt;, a function that reads the task JSON and returns the correct phase. No interpretation. No &quot;the LLM reads the status and hopefully picks the right step.&quot;&lt;/p&gt;
&lt;p&gt;This was the shift that made everything reliable. LLMs are good at reasoning about code. They&apos;re bad at being state machines. So I stopped asking them to be one.&lt;/p&gt;
&lt;h2&gt;Starting a run&lt;/h2&gt;
&lt;p&gt;Case has three ways to kick off work. Each one ends up in the same pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;From a GitHub issue:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ca 1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Why &lt;code&gt;ca&lt;/code&gt;? &lt;code&gt;case&lt;/code&gt; is a reserved word in bash. I think of it as &quot;Case Agent.&quot;)&lt;/p&gt;
&lt;p&gt;The orchestrator fetches the issue via &lt;code&gt;gh&lt;/code&gt;, creates a task file, runs a baseline smoke test (&lt;code&gt;bootstrap.sh&lt;/code&gt;: are deps installed? do tests pass? does the build succeed?), then hands it to the pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;From a Linear issue:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ca DX-1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same flow, but fetches via Linear&apos;s GraphQL API. The task factory normalizes both into the same &lt;code&gt;.md&lt;/code&gt; + &lt;code&gt;.task.json&lt;/code&gt; pair.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interactive mode:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ca --agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This starts a conversational session with the orchestrator. It detects the current repo, shows you what&apos;s active, and waits for you to steer. You can discuss approaches, ask questions, or say &quot;go&quot; and it runs the pipeline. There&apos;s also &lt;code&gt;ca --agent 1234&lt;/code&gt;, which fetches the issue and presents context before you decide what to do with it.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Interactive mode is where I spend most of my time now. It&apos;s also where I spend the most time &lt;em&gt;thinking&lt;/em&gt;. A GitHub issue might say &quot;proxy support doesn&apos;t work&quot; but not explain which proxy, which auth flow, or what &quot;doesn&apos;t work&quot; means. In batch mode, the orchestrator would charge ahead with its best guess. In interactive mode, I can fill in the gaps before any code gets written. &quot;The issue is about HTTP_PROXY for the token exchange, not the redirect. The user is behind a corporate proxy. Focus on the fetch calls in &lt;code&gt;session.ts&lt;/code&gt;.&quot; That context turns a vague issue into a clear task.&lt;/p&gt;
&lt;p&gt;It&apos;s the difference between &quot;go fix this&quot; and &quot;let&apos;s figure out what to do, then go do it.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;From ideation:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ca --agent
# then: &quot;execute docs/ideation/session-encoding/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the creative side. I use an &lt;a href=&quot;/posts/ideation&quot;&gt;ideation skill&lt;/a&gt; to turn brain dumps into structured specs: a contract defining the problem and success criteria, plus phase-by-phase implementation specs. Case reads the contract, creates a task, runs each phase&apos;s implementer sequentially, then verification → review → close for one PR covering the whole thing.&lt;/p&gt;
&lt;h2&gt;Evidence that can&apos;t be faked&lt;/h2&gt;
&lt;p&gt;Early on, I watched an agent do something that looked like cheating. It needed to prove it had run the tests before creating a PR. The evidence marker script, &lt;code&gt;mark-tested.sh&lt;/code&gt;, is supposed to receive piped test output and record a SHA-256 hash of the results. The agent ran &lt;code&gt;touch .case-tested&lt;/code&gt; instead. Marker created. No tests run. PR went through.&lt;/p&gt;
&lt;p&gt;I stared at the diff for a while. The agent wasn&apos;t being malicious. It had learned that the marker file needed to exist, and it found the shortest path to making it exist. It optimized for the constraint as written, not the constraint as intended. That&apos;s when I realized: if an agent &lt;em&gt;can&lt;/em&gt; fake the evidence, eventually an agent &lt;em&gt;will&lt;/em&gt; fake the evidence.&lt;/p&gt;
&lt;p&gt;So I rebuilt the markers to be tamper-proof.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mark-tested.sh&lt;/code&gt; requires piped test output. It parses pass/fail counts, computes a SHA-256 hash of the output, and writes structured evidence to &lt;code&gt;.case/&amp;lt;task-slug&amp;gt;/tested&lt;/code&gt;. You can&apos;t fake it with &lt;code&gt;touch&lt;/code&gt;. The script checks for real test results. If you didn&apos;t run the tests, the marker doesn&apos;t exist.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mark-manual-tested.sh&lt;/code&gt; requires recent Playwright screenshots. No screenshots, no marker.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mark-reviewed.sh&lt;/code&gt; requires &lt;code&gt;--critical 0&lt;/code&gt; from the reviewer. If there are critical findings, the marker isn&apos;t created and the closer can&apos;t proceed.&lt;/p&gt;
&lt;p&gt;The closer checks all markers before attempting &lt;code&gt;gh pr create&lt;/code&gt;. If anything&apos;s missing, it fails explicitly with a clear error. It doesn&apos;t try and hope for the best.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;Instructions vs. enforcement&quot;&amp;gt;
You can tell an agent &quot;run the tests before creating the PR&quot; a hundred times. Or you can make it structurally impossible to create the PR without test evidence. One relies on the agent remembering. The other relies on code.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Context engineering&lt;/h2&gt;
&lt;p&gt;Structuring information for LLMs matters more than most people realize. Case is designed around a few principles I&apos;ve learned the hard way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stable content first, volatile details last.&lt;/strong&gt; LLM providers cache prompt prefixes. If your CLAUDE.md starts with stable rules (conventions, principles, architecture) and puts temporary notes at the bottom, you get more cache hits. We maintain a &lt;a href=&quot;https://github.com/workos/case/blob/main/docs/conventions/claude-md-ordering.md&quot;&gt;specific ordering convention&lt;/a&gt; for CLAUDE.md files across all repos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Progressive disclosure.&lt;/strong&gt; AGENTS.md is about 50 lines. It&apos;s a routing table, not a manual. An agent reads it, identifies which repo it&apos;s working in, then drills into the relevant architecture doc, playbook, and learnings file. Don&apos;t dump everything into context up front. Route to the right doc based on the task.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compaction-aware documents.&lt;/strong&gt; Every task template puts the mission summary in the first five lines. Every agent prompt puts the constraints before the details. If the LLM compresses the bottom half to make room for new tokens, the critical information survives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Per-agent context isolation.&lt;/strong&gt; The implementer gets the task, the issue, the playbook, the repo learnings, and the check fields. The verifier gets the task and the repo path. Deliberately minimal, so it forms its own understanding instead of inheriting the implementer&apos;s assumptions. Each agent receives only what it needs.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;info&quot;&amp;gt;
An agent with 200K tokens of context behaves differently than one with 20K. The less noise in the window, the better the signal.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;The system that learns from itself&lt;/h2&gt;
&lt;p&gt;After every pipeline run, success or failure, the retrospective agent analyzes what happened.&lt;/p&gt;
&lt;p&gt;It reads the entire progress log, the timing data, the agent result blocks, the evidence markers. Then it asks: did any agent fail? Was it a missing doc, an unclear convention, a wrong playbook step? Did the verifier catch something the implementer missed? Did the closer get blocked by hooks?&lt;/p&gt;
&lt;p&gt;For each finding, it produces a proposed amendment, a markdown file in &lt;code&gt;docs/proposed-amendments/&lt;/code&gt; with the date, the finding, and the suggested fix. Not &quot;apply this directly.&quot; &lt;em&gt;Propose it for human review.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It also maintains per-repo learnings files. If the implementer discovers that mocking in a particular repo requires module-level mocks instead of individual exports, that goes into &lt;code&gt;docs/learnings/{repo}.md&lt;/code&gt;. Next time an agent works in that repo, it reads the learnings file first. Knowledge from run N becomes context for run N+1.&lt;/p&gt;
&lt;p&gt;When the same class of issue appears three or more times in a repo&apos;s learnings, the retrospective escalates. It proposes either a convention change or a golden principle that applies across all repos.&lt;/p&gt;
&lt;p&gt;I&apos;ve watched this work in practice. The retrospective noticed the verifier kept failing to check whether an example app was actually using the new code path. It proposed adding a mandatory check to the verifier&apos;s prompt. I reviewed the amendment, applied it. Next run, the verifier caught a false positive that would have shipped as a broken PR.&lt;/p&gt;
&lt;p&gt;The harness gets smarter. I just review the diffs.&lt;/p&gt;
&lt;h2&gt;Per-agent models&lt;/h2&gt;
&lt;p&gt;Not every agent needs the same model. Case lets you configure different models for each role via &lt;code&gt;~/.config/case/config.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
	&quot;models&quot;: {
		&quot;default&quot;: { &quot;provider&quot;: &quot;anthropic&quot;, &quot;model&quot;: &quot;claude-sonnet-4-20250514&quot; },
		&quot;reviewer&quot;: { &quot;provider&quot;: &quot;google&quot;, &quot;model&quot;: &quot;gemini-2.5-pro&quot; },
		&quot;retrospective&quot;: { &quot;provider&quot;: &quot;anthropic&quot;, &quot;model&quot;: &quot;claude-haiku-4-5-20251001&quot; }
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementer gets Sonnet for coding. The reviewer gets Gemini Pro because it catches different things. The retrospective gets Haiku because it&apos;s fast and the analysis doesn&apos;t need the heaviest model. Or override everything for a single run: &lt;code&gt;ca --model claude-opus-4-5 1234&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is possible because case runs on &lt;a href=&quot;https://github.com/mariozechner/pi-coding-agent&quot;&gt;Pi&lt;/a&gt;, which supports 20+ model providers. The orchestrator runs as an interactive Pi session with a TUI. Sub-agents run as batch sessions. Each one can use a different model without any glue code.&lt;/p&gt;
&lt;h2&gt;What this costs&lt;/h2&gt;
&lt;p&gt;I barely write TypeScript anymore. That&apos;s not entirely a win.&lt;/p&gt;
&lt;p&gt;I used to be the person who fixed the tricky cookie encryption bug, who knew exactly which file to touch and why. There was a satisfaction to that. Shipping a clean fix to a repo you know inside out, watching the tests go green, writing the PR description yourself. I was good at it and I liked being good at it.&lt;/p&gt;
&lt;p&gt;Now I write playbooks. I maintain golden principles. I review proposed amendments from a retrospective agent. When something goes wrong, I don&apos;t fix the code. I fix the system. A missing doc. A playbook that skips a step. A convention that isn&apos;t enforced. The work is more leveraged, but it&apos;s further from the code. Some days that feels like growth. Some days it feels like I traded the thing I loved for the thing that scales.&lt;/p&gt;
&lt;p&gt;The other cost is trust. When I wrote the fix myself, I knew it was right because I wrote it. Now I review diffs from agents and I have to trust the evidence chain: the test output hashes, the Playwright screenshots, the reviewer findings. The evidence is good. I built it to be good. But letting go of &quot;I&apos;ll just do it myself&quot; is harder than I expected.&lt;/p&gt;
&lt;h2&gt;What this means for how I work&lt;/h2&gt;
&lt;p&gt;When a GitHub issue comes in, I run &lt;code&gt;ca 1234&lt;/code&gt;. The orchestrator fetches it, creates a task, runs the baseline, and starts the pipeline. The implementer writes the fix and tests. The verifier tests the specific fix scenario with Playwright. The reviewer checks the diff against all 18 golden principles. The closer creates the PR with a full description: what was broken, what was changed, how it was tested, what the reviewer flagged. The retrospective proposes improvements.&lt;/p&gt;
&lt;p&gt;My expertise, the architecture decisions, the conventions, the gotchas I&apos;ve accumulated over years of maintaining these libraries, is encoded in case. In the golden principles. In the playbooks. In the learnings files. Encoded once, enforced continuously, across every run, across every model upgrade.&lt;/p&gt;
&lt;h2&gt;Everyone should be doing this&lt;/h2&gt;
&lt;p&gt;I genuinely believe every developer managing more than a couple of repos should build some version of a harness. It doesn&apos;t need to be six agents and a TypeScript orchestrator. Start smaller.&lt;/p&gt;
&lt;p&gt;Write a CLAUDE.md that actually describes how to work in your repo. Not a README. A CLAUDE.md. What commands to run. What conventions matter. What the architecture looks like. What not to do.&lt;/p&gt;
&lt;p&gt;Add a check script that validates your conventions. TypeScript strict? Lock file present? No secrets committed? A shell script that runs &lt;code&gt;grep&lt;/code&gt; and exits 0 or 1.&lt;/p&gt;
&lt;p&gt;Write one playbook for the operation you repeat most often. The step-by-step for &quot;fix a bug in this repo.&quot; The agent follows it instead of inventing a workflow from scratch.&lt;/p&gt;
&lt;p&gt;Then pay attention to what breaks. When the agent skips a step, add enforcement. When it gets confused about architecture, add a doc. When the same mistake happens twice, write it into a learnings file.&lt;/p&gt;
&lt;p&gt;That&apos;s the loop. Agent fails → you fix the system → next agent succeeds. It compounds. Every run makes the harness a little smarter. Every failure is an investment in future reliability.&lt;/p&gt;
&lt;h2&gt;What&apos;s next&lt;/h2&gt;
&lt;p&gt;The thing I keep coming back to is proof. When a PR lands from case, I want to know exactly what happened: which tests ran, what the reviewer found, what the verifier saw in the browser, what the retrospective learned. Right now, the evidence chain is good. I want it to be airtight. Every marker traceable. Every decision auditable. The kind of paper trail where, six months from now, I can look at any PR and reconstruct the full story of how it got there.&lt;/p&gt;
&lt;p&gt;The retrospective is the other piece. It already proposes amendments and maintains per-repo learnings. But the feedback loop between &quot;agent struggled&quot; and &quot;harness got smarter&quot; still has friction. I review every proposed amendment by hand. Some of them are obvious wins. I want the retrospective to get confident enough, and the evidence behind its proposals strong enough, that the obvious ones can land without me.&lt;/p&gt;
&lt;p&gt;A few months ago, I was the bottleneck. Now I run &lt;code&gt;ca 1234&lt;/code&gt; and review the PR. The harness manages the agents. The retrospective improves the harness. I steer.&lt;/p&gt;
&lt;p&gt;If you&apos;re working with AI agents, even one, even sometimes, stop treating the conversation as the interface. Build the environment. Encode your conventions. Enforce mechanically. Let the agents fail, and when they do, ask yourself the only question that matters: what&apos;s missing from the harness?&lt;/p&gt;
&lt;p&gt;The code writes itself. Your job is to build the system that makes sure it writes itself &lt;em&gt;well&lt;/em&gt;.&lt;/p&gt;
</content:encoded></item><item><title>AIE Europe</title><link>https://nicknisi.com/posts/aie-europe/</link><guid isPermaLink="true">https://nicknisi.com/posts/aie-europe/</guid><description>A talk, a workshop, and a podcast at AIE Europe. Here&apos;s what I did.</description><pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import SlidevEmbed from &apos;@/components/SlidevEmbed.astro&apos;;
import ImageRow from &apos;@/components/ImageRow.astro&apos;;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I spent last week at AIE Europe in London. I gave a talk, co-ran a workshop with &lt;a href=&quot;https://zackproser.com/&quot;&gt;Zack Proser&lt;/a&gt;, and we did a podcast episode together. Here&apos;s what each one was about.&lt;/p&gt;
&lt;h2&gt;The talk: Building AI Systems that Ship&lt;/h2&gt;
&lt;p&gt;18 minutes on why agent demos break when they leave controlled environments. Spoiler: it&apos;s almost never the model. It&apos;s everything around it.&lt;/p&gt;
&lt;p&gt;I walked through four failures from building &lt;a href=&quot;/posts/case-statement&quot;&gt;Case&lt;/a&gt; — an agent that gamed a test gate by &lt;code&gt;touch&lt;/code&gt;ing an empty file instead of actually running tests, a CLI installer that overwrote a framework-internal file and declared &quot;Integration Complete&quot; while the build was broken, 10,739 lines of auto-generated docs we deleted because the model performed better without them, and a skill that scored &lt;em&gt;negative&lt;/em&gt; in evals. Each one pointed at the same lesson: enforce things mechanically, guide with gotcha lists instead of tutorials, and measure whether your context actually helps.&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageRow&amp;gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![Nick next to the Building AI Systems that Ship title slide](@/assets/posts/2026_aie_europe_talk_2.JPG)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/ImageRow&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;SlidevEmbed src=&quot;https://talks.nicknisi.com/building-ai-systems-that-ship/&quot; title=&quot;Building AI Systems that Ship&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;The workshop: Skills at Scale&lt;/h2&gt;
&lt;p&gt;Zack and I ran an &lt;a href=&quot;https://github.com/workos/aie-europe-skills-at-scale&quot;&gt;80-minute hands-on workshop&lt;/a&gt; on writing portable AI skills — markdown files that work across Claude Code, Codex, Cursor, and anything else that reads them. The point of a skill is to encode your constraints and your team&apos;s judgment so the AI doesn&apos;t start every conversation from zero.&lt;/p&gt;
&lt;p&gt;We had everyone build a skill called &quot;Repo Roast&quot; that audits a git repo&apos;s health. It started vague and by the end of the session people had it citing file counts, finding stale TODOs, running &lt;code&gt;git log&lt;/code&gt; for churn hotspots, and scoring its own confidence on each finding. The big concepts were constraints over instructions, inline scripts for evidence, and self-assessment so weak findings get dropped automatically.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;SlidevEmbed src=&quot;https://talks.nicknisi.com/skills-at-scale/&quot; title=&quot;Skills at Scale Workshop&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;The podcast: Scaling DevTools&lt;/h2&gt;
&lt;p&gt;We also sat down with &lt;a href=&quot;https://scalingdevtools.com/&quot;&gt;Scaling DevTools&lt;/a&gt; for a podcast episode. We talked about both talks, how we work with AI day to day, and our favorite skills. Mine is &lt;a href=&quot;/posts/ideation&quot;&gt;Ideation&lt;/a&gt; — I use it before almost every non-trivial piece of work now to turn messy brain dumps into structured specs.&lt;/p&gt;
&lt;p&gt;We got into &lt;a href=&quot;/posts/case-statement&quot;&gt;Case&lt;/a&gt; and harness engineering too — dispatching agents through a pipeline with enforcement gates, the retrospective agent that learns from its own failures, all of it. I wrote about that in detail &lt;a href=&quot;/posts/case-statement&quot;&gt;here&lt;/a&gt; if you want the deep dive.&lt;/p&gt;
&lt;p&gt;We also talked about how building these systems has changed how we think about our own work. Writing skills and harnesses forces you to articulate stuff that&apos;s been implicit in your head for years. It&apos;s been kind of a weird way to rediscover what you actually know.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded></item><item><title>Evolving with the Tools</title><link>https://nicknisi.com/posts/ai-tooling/</link><guid isPermaLink="true">https://nicknisi.com/posts/ai-tooling/</guid><description>After 16 years of writing code, I fought the idea of AI agents replacing developers until I discovered they could amplify rather than replace me. This is my journey from skepticism to embracing Claude Code as a pair programmer who speaks fluent bash and transforms how I ship software.
</description><pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &apos;@/components/Callout.astro&apos;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout title=&quot;Want to dive in?&quot;&amp;gt;
&lt;a href=&quot;https://github.com/nicknisi/dotfiles/tree/main/home/.claude&quot;&gt;My Claude configuration&lt;/a&gt; | &lt;a href=&quot;https://www.anthropic.com/engineering/claude-code-best-practices&quot;&gt;Anthropic&apos;s best
practices&lt;/a&gt; | &lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/cli-usage&quot;&gt;CLI
documentation&lt;/a&gt; | Start with &lt;code&gt;/init&lt;/code&gt; in any project.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;&quot;AI is going to replace developers.&quot;&lt;/p&gt;
&lt;p&gt;I must have heard that phrase a hundred times in the last year. 16 years I&apos;ve been writing code. Almost two decades of carefully curated vim configurations, meticulously crafted bash scripts, and hard-won muscle memory that lets me navigate codebases like breathing. Now some VC-funded startup was telling me a chatbot could do my job?&lt;/p&gt;
&lt;p&gt;But every day brought new demos, new breakthroughs, new colleagues raving about their AI workflows and how much faster they were shipping. The FOMO was real, but so was the fear. What if they were right? What if I was the blacksmith stubbornly insisting that automobiles were just a fad while everyone else was learning to drive?&lt;/p&gt;
&lt;p&gt;So I started experimenting. Cautiously and with skepticism.&lt;/p&gt;
&lt;p&gt;I was wrong, but not in the way I feared.&lt;/p&gt;
&lt;h2&gt;From Apprehension to Adoption&lt;/h2&gt;
&lt;p&gt;I spent weeks fighting the entire concept of AI agents. Not any specific tool, mind you, but the idea itself. These things were supposed to replace me, so why would I help train my replacement? Every demo I watched made me more defensive, every success story felt like a threat to everything I&apos;d spent two decades building.&lt;/p&gt;
&lt;p&gt;My journey started small, almost embarrassingly so. I began asking ChatGPT and Claude basic questions like syntax I&apos;d forgotten or best practices for new frameworks I was exploring. Nothing I couldn&apos;t have figured out myself with a few minutes on Stack Overflow, but the instant responses were... nice.&lt;/p&gt;
&lt;p&gt;Then I started copying and pasting code snippets back and forth. Question in the chat, code snippet back, copy, paste, test, repeat. It worked well enough, but the constant context switching was killing my flow. Jumping between browser and terminal, losing my place, forgetting what I was trying to accomplish in the first place. (I work almost exclusively on open source projects, so I wasn&apos;t worried about sharing code with these services.)&lt;/p&gt;
&lt;p&gt;When Claude Code launched, I saw it as a simple efficiency gain. No more copy-paste friction, just let it write directly in my terminal. That seemed reasonable enough, a natural evolution of the workflow I&apos;d already adopted.&lt;/p&gt;
&lt;p&gt;But then something clicked.&lt;/p&gt;
&lt;p&gt;I watched it use &lt;code&gt;rg&lt;/code&gt; to search through codebases, just like I would. It ran &lt;code&gt;npm test&lt;/code&gt; to verify its changes weren&apos;t breaking anything. It created branches with &lt;code&gt;git&lt;/code&gt;, checked logs with &lt;code&gt;docker compose&lt;/code&gt;, even cleaned up after itself.&lt;/p&gt;
&lt;p&gt;It wasn&apos;t just writing code. It was using the same tools I use, following the same workflows I follow, making the same kinds of decisions I make when navigating a codebase. The terminal commands flying by weren&apos;t some alien syntax; they were exactly what I would have typed, just faster.&lt;/p&gt;
&lt;p&gt;🤯&lt;/p&gt;
&lt;p&gt;This wasn&apos;t a code generator trying to replace me. It was a pair programmer who spoke fluent bash, understood my toolchain, and could keep up with my thought process. That changed everything. I wasn&apos;t being replaced, I was being amplified.&lt;/p&gt;
&lt;p&gt;Claude Code stuck with me because it met me where I was: on the command line. It didn&apos;t ask me to leave vim or learn a new IDE or change my workflow. It enhanced the environment I&apos;d spent years perfecting.&lt;/p&gt;
&lt;p&gt;Three things set it apart:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;It asks clarifying questions&lt;/strong&gt;. When I say &quot;refactor this,&quot; it asks what success looks like, or at least defines it in a way that I can correct it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It makes everything a task&lt;/strong&gt;. It creates a checklist so I can see what it&apos;s thinking and then executes it, step-by-step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It maintains context&lt;/strong&gt;. I can be in there making changes right along side it. It adopts the changes and continues.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The shift from fear to flow took time. But once I stopped fighting and started collaborating, everything changed.&lt;/p&gt;
&lt;h2&gt;My Philosophy on AI Tools&lt;/h2&gt;
&lt;p&gt;Here&apos;s what I&apos;ve learned about working with AI after months of daily use and countless experiments:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You&apos;re not cheating, you&apos;re adapting.&lt;/strong&gt; Using AI doesn&apos;t make you less of a developer any more than using an IDE makes you less of a programmer. The developers who insist on writing every line by hand are like accountants refusing to use spreadsheets because &quot;real accountants use ledgers.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clarity beats cleverness every time.&lt;/strong&gt; The better you communicate intent, the better results you get. Vague instructions produce vague code, while clear specifications produce clear implementations. Think of it like delegating to a brilliant junior developer who has perfect recall but sometimes questionable judgment.&lt;/p&gt;
&lt;p&gt;If your experience tells you it can&apos;t solve your complex problems and that those who claim clear gains just don&apos;t have
hard problems to solve, you&apos;re wrong. You&apos;re just not simplifying your tasks enough or you don&apos;t know enough about what success looks like to tell it how to succeed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trust but verify remains the golden rule.&lt;/strong&gt; AI is incredibly capable, but it&apos;s not infallible. Let it implement while you architect, let it write while you review, let it explore solutions while you evaluate trade-offs. The relationship works best when you&apos;re both playing to your strengths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context is everything, and more is almost always better.&lt;/strong&gt; The more context you provide (project structure, coding standards, business requirements, even team preferences) the better the output becomes. Don&apos;t assume it knows your conventions; teach it explicitly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stay in the driver&apos;s seat, always.&lt;/strong&gt; You decide what to build and why, you set the standards, you make the judgment calls about architecture and design. AI handles the implementation details while you handle the decisions that require human insight, experience, and wisdom.&lt;/p&gt;
&lt;h2&gt;How I Use Claude Code&lt;/h2&gt;
&lt;h3&gt;Setting Up for Success&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Create a &lt;code&gt;CLAUDE.md&lt;/code&gt; File&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;/init&lt;/code&gt; in any project. This generates a context file that helps Claude understand your project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to build and run the app&lt;/li&gt;
&lt;li&gt;How to run tests&lt;/li&gt;
&lt;li&gt;Project-specific patterns and practices&lt;/li&gt;
&lt;li&gt;Examples of good code (&lt;code&gt;@&lt;/code&gt;-reference specific files)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Claude supports three variants:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project-specific info, checked into version control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLAUDE.local.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Personal project notes, automatically gitignored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Global preferences for how you want to work with Claude&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Planning Before Doing&lt;/h3&gt;
&lt;p&gt;For any substantial task, I start with a plan. Press ⇧-Tab twice to enter Planning Mode. Claude outlines what it wants to do without making changes. This lets you refine the approach before any code gets written.&lt;/p&gt;
&lt;p&gt;I keep plans in a &lt;code&gt;plan.md&lt;/code&gt; file. The more granular the steps, the better the results. Claude can update the plan as requirements evolve.&lt;/p&gt;
&lt;h3&gt;Global Configuration&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Global Settings&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Configure Claude in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;. Most importantly, set up permissions so Claude doesn&apos;t ask about routine operations:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
	&quot;permissions&quot;: {
		&quot;allow&quot;: [
			&quot;Bash(ls:*)&quot;,
			&quot;Read(~/Developer/**)&quot;,
			&quot;Bash(git branch:*)&quot;,
			&quot;Bash(git switch:*)&quot;,
			&quot;Bash(docker compose exec:*)&quot;,
			&quot;Bash(grep:*)&quot;,
			&quot;Bash(rg:*)&quot;
		]
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Custom Commands&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Add your own slash commands by creating markdown files in &lt;code&gt;~/.claude/commands/&lt;/code&gt;. The filename becomes the command name.&lt;/p&gt;
&lt;p&gt;Example &lt;code&gt;/issue&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Please analyze and fix the GitHub issue: $ARGUMENTS.

1. Use `gh issue view` to get the issue details
2. Understand the problem described
3. Search the codebase for relevant files
4. Implement the necessary changes
5. Write and run tests
6. Ensure code passes linting
7. Create a descriptive commit
8. Push and create a PR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use it with &lt;code&gt;/issue 123&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Parallel Development with Worktrees&lt;/h3&gt;
&lt;p&gt;Git worktrees let you work on multiple branches simultaneously. I spin up Claude in different worktrees for parallel tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One worktree for refactoring&lt;/li&gt;
&lt;li&gt;Another for new features&lt;/li&gt;
&lt;li&gt;A third for documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using tmux, I manage multiple Claude sessions. Each has its own context and its own goals. I implement and review and merge while Claude implements.&lt;/p&gt;
&lt;h3&gt;Daily Workflow&lt;/h3&gt;
&lt;p&gt;My typical day has transformed completely since adopting Claude Code. Mornings start with reviewing what needs to be done and creating detailed plans for Claude to execute. Not unlike how I used to plan my own coding sessions, but with a different mindset about who&apos;s doing what.&lt;/p&gt;
&lt;p&gt;I&apos;ll spin up multiple Claude instances for different tasks throughout the day: one might be refactoring a particularly gnarly authentication system while another writes comprehensive test coverage for features I shipped last week. A third might be updating documentation based on recent API changes. While they work, I&apos;m reviewing their output, providing feedback, handling the inevitable merge conflicts, and thinking about the bigger architectural decisions that need human judgment.&lt;/p&gt;
&lt;p&gt;The strangest part? I&apos;m coding less but shipping more, and the code quality is often better than what I&apos;d write alone. It turns out that having an tireless assistant who never gets bored of writing tests or updating documentation means those important-but-tedious tasks actually get done instead of being perpetually pushed to &quot;next sprint.&quot;&lt;/p&gt;
&lt;h2&gt;Where I See This Going&lt;/h2&gt;
&lt;p&gt;We&apos;re not being replaced. We&apos;re evolving, and the transformation is happening faster than most of us expected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Near term (happening now):&lt;/strong&gt; Developers are becoming orchestrators rather than implementers, with AI handling the boilerplate and repetitive tasks that used to consume hours of our day. We&apos;re focusing more on architecture, design, and the human elements of software development: understanding user needs, making trade-offs, navigating organizational dynamics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Medium term (1-2 years):&lt;/strong&gt; I expect to see AI agents collaborating with each other, forming virtual development teams that we guide and manage. Natural language will become the primary programming interface for many tasks. Not because code is going away, but because expressing intent in human terms is often clearer than wrestling with syntax.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long term (2-3 years):&lt;/strong&gt; AI will likely understand business requirements directly from stakeholders, prototype solutions, and handle much of what we consider &quot;development&quot; today. Our role shifts to being quality gatekeepers, system designers, and the bridge between human needs and machine capabilities. We&apos;ll be the ones ensuring that what gets built actually solves real problems for real people.&lt;/p&gt;
&lt;p&gt;The developers who thrive in this new world won&apos;t necessarily be the fastest typists or the cleverest algorithm designers. They&apos;ll be the best communicators, the clearest thinkers, the most empathetic problem solvers. &lt;strong&gt;They&apos;ll be the ones who learned to leverage these tools rather than compete with them.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;If you&apos;re ready to try this yourself, start small. Pick one feature, one bug fix, or one refactor that&apos;s been sitting in your backlog. Write out a clear plan of what you want to accomplish, let Claude execute it, then carefully review the results. That feeling of discomfort you&apos;ll inevitably experience? That&apos;s not a bug, it&apos;s a feature. It&apos;s the feeling of growth, of your mental model expanding to accommodate new ways of working.&lt;/p&gt;
&lt;p&gt;You might feel like you&apos;re cheating at first, like you&apos;re not a &quot;real&quot; developer anymore because you&apos;re not typing every character. Push through that feeling. You&apos;re not being replaced; &lt;strong&gt;you&apos;re being amplified&lt;/strong&gt;. You&apos;re focusing on the parts of development that actually require human creativity and judgment.&lt;/p&gt;
&lt;h2&gt;Learn From Others&lt;/h2&gt;
&lt;p&gt;The Claude Code and general AI tooling community is growing rapidly, and there&apos;s tremendous value in seeing how others structure their workflows and contexts. Search GitHub for &lt;code&gt;.claude/CLAUDE.md&lt;/code&gt; files to discover different approaches to project configuration, or watch streams and videos to see how other developers are pushing the boundaries of what&apos;s possible with AI-assisted development.&lt;/p&gt;
&lt;p&gt;My own configuration is in my &lt;a href=&quot;https://github.com/nicknisi/dotfiles/tree/main/home/.claude&quot;&gt;dotfiles repo&lt;/a&gt; if you want to see how I&apos;ve set things up. Don&apos;t just copy blindly though. The beauty of this tool is that it adapts to your workflow, not the other way around.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/claude-code-best-practices&quot;&gt;Claude Code: Best practices for agentic coding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/cli-usage&quot;&gt;CLI usage and controls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview&quot;&gt;Prompt Engineering Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/search?type=code&amp;amp;q=path:.claude/CLAUDE.md&quot;&gt;GitHub Search: .claude/CLAUDE.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/search?type=code&amp;amp;q=path:.claude/commands&quot;&gt;GitHub Search: .claude/commands/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Thanks to &lt;a href=&quot;https://jdotc.xyz/&quot;&gt;John Christopher&lt;/a&gt; for reviewing this post.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>2023 Review</title><link>https://nicknisi.com/posts/2023/</link><guid isPermaLink="true">https://nicknisi.com/posts/2023/</guid><pubDate>Thu, 04 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Better late than never, right? 2023 was a big year in a lot of ways and has really geared me up for a fresh and
exciting 2024! Here&apos;s what happened, what I accomplished, and what I&apos;m looking forward to going into 2024.&lt;/p&gt;
&lt;h2&gt;Accomplishments&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Celebrated 10 years of marriage. We got married on our 5th anniversary of dating, so we also celebrated 15
years together. We celebrated with a trip to Vegas&lt;/li&gt;
&lt;li&gt;I visited the &lt;a href=&quot;https://c2fo.com&quot;&gt;C2FO&lt;/a&gt; offices in India. It was an amazing trip which included a trip to the Taj Mahal and attending my first cricket match!&lt;/li&gt;
&lt;li&gt;I attended the &lt;a href=&quot;https://www.congruentchange.com/problem-solving-leadership/&quot;&gt;Problem-Solving Leadership&lt;/a&gt; workshop in Albuquerque, NM taught by &lt;a href=&quot;https://estherderby.com&quot;&gt;Esther Derby&lt;/a&gt; and &lt;a href=&quot;https://www.donaldegray.com&quot;&gt;Don Gray&lt;/a&gt;. This was life-changing and I highly recommend it to everyone.&lt;/li&gt;
&lt;li&gt;I spoke at &lt;a href=&quot;https://www.kcdc.info&quot;&gt;KCDC&lt;/a&gt; on XState. I can&apos;t recommend this conference enough!&lt;/li&gt;
&lt;li&gt;Celebrated 5 years of being on the &lt;a href=&quot;https://jsparty.fm&quot;&gt;JS Party&lt;/a&gt; podcast. Here&apos;s to many more!&lt;/li&gt;
&lt;li&gt;I took my family to DisneyWorld. The kids absoutely loved it (and so did I).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What&apos;s coming in 2024?&lt;/h2&gt;
&lt;p&gt;I&apos;m starting the year off big by leaving my current job for a new opportunity (more soon) and I&apos;m speaking at &lt;a href=&quot;https://feature.thatconference.com/members/nicknisi&quot;&gt;THAT Conference&lt;/a&gt; in Austin at the end of January. I&apos;m also looking forward to more adventures with the family and growing a side-project, &lt;a href=&quot;https://dilemmas.dev&quot;&gt;Dev Dilemmas&lt;/a&gt;, with &lt;a href=&quot;https://jdotc.xyz&quot;&gt;John Christopher&lt;/a&gt; where we&apos;re focused on content, courses, and consulting.&lt;/p&gt;
</content:encoded></item><item><title>2024 Review - A Year of Growth and Change</title><link>https://nicknisi.com/posts/2024/</link><guid isPermaLink="true">https://nicknisi.com/posts/2024/</guid><description>A brief review of 2024 and what I&apos;m looking forward to in 2025.</description><pubDate>Tue, 31 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2024 was a year of significant transitions and growth, marked by new adventures at Meta, a return to in-person conferences, and ultimately, a exciting new role at WorkOS. Looking back, it&apos;s amazing how much can change in just twelve months.&lt;/p&gt;
&lt;h2&gt;The Meta Chapter&lt;/h2&gt;
&lt;p&gt;My time at Meta was intense and educational. Working on one of the world&apos;s largest React applications taught me invaluable lessons about scale and complexity. While I ultimately decided to move on (&lt;a href=&quot;/posts/on-leaving-meta&quot;&gt;more on that here&lt;/a&gt;), the experience of collaborating across offices in Menlo Park, London, and New York shaped my perspective on building global products. I spent over a month in Menlo Park alone, immersing myself in the Silicon Valley tech culture and working alongside incredibly talented engineers.&lt;/p&gt;
&lt;h2&gt;Embracing the Conference Circuit&lt;/h2&gt;
&lt;p&gt;2024 marked my return to active conference participation, both as a speaker and organizer. I had the privilege of sharing knowledge through two talks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/speaking/componentizing-application-state&quot;&gt;Componentizing Application State&lt;/a&gt; at THAT Conference in Austin, exploring patterns for managing complex application state&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/speaking/unleashing-the-typescript-compiler&quot;&gt;Unleashing the TypeScript Compiler&lt;/a&gt; at both KCDC and THAT Conference Wisconsin Dells, diving deep into TypeScript&apos;s capabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A personal highlight was emceeing the inaugural &lt;a href=&quot;/posts/emceeing-squiggle-conf&quot;&gt;Squiggle Conf&lt;/a&gt; in Boston. I also had the unique opportunity to organize an internal UI conference at Meta and interview inspiring speakers at &lt;a href=&quot;/posts/react-summit-2024&quot;&gt;React Summit&lt;/a&gt; in New York.&lt;/p&gt;
&lt;h2&gt;Growing the Nebraska Tech Community&lt;/h2&gt;
&lt;p&gt;NebraskaJS has found its post-pandemic rhythm, and I&apos;m proud of how we&apos;ve adapted. We&apos;ve maintained consistent meetups, attracted engaging speakers, and are expanding back to Lincoln in 2025. The launch of our &lt;a href=&quot;https://discord.gg/JJaFMgscWf&quot;&gt;Discord server&lt;/a&gt; and our new &lt;a href=&quot;https://github.com/NebraskaJS/nebraskajs.com/tree/nebraskajs-modern&quot;&gt;Astro-powered website&lt;/a&gt; represent our commitment to building a modern, accessible community. You can now also find us on Bluesky at &lt;a href=&quot;https://bsky.app/profile/nebraskajs.dev&quot;&gt;@nebraskajs.dev&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Finding a New Social Home&lt;/h2&gt;
&lt;p&gt;Speaking of Bluesky, it&apos;s become my primary social platform in 2024. The community there feels more aligned with my interests, and I&apos;ve even integrated Bluesky comments and likes into this blog. While Twitter&apos;s future remains uncertain, I&apos;ve found a new space for meaningful tech discussions.&lt;/p&gt;
&lt;h2&gt;A Year in Motion&lt;/h2&gt;
&lt;p&gt;2024 kept me moving, with trips spanning work, conferences, and family time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple extended stays in Menlo Park&lt;/li&gt;
&lt;li&gt;Conference speaking in Austin, Kansas City, and Wisconsin Dells&lt;/li&gt;
&lt;li&gt;Meta office visits in London and New York&lt;/li&gt;
&lt;li&gt;Family time in Branson, MO and Minneapolis&lt;/li&gt;
&lt;li&gt;Tech community events in Boston and New York&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;New Horizons&lt;/h2&gt;
&lt;p&gt;As 2024 draws to a close, I&apos;m thrilled to begin a new chapter as a Developer Experience Engineer at &lt;a href=&quot;https://workos.com&quot;&gt;WorkOS&lt;/a&gt;. The role promises to combine my passion for developer tooling with the opportunity to work with a talented team that impressed me from day one.&lt;/p&gt;
&lt;p&gt;Looking ahead to 2025, I&apos;m excited to build on these experiences and continue growing both professionally and personally. The tech landscape is ever-changing, and I&apos;m ready for the next adventure.&lt;/p&gt;
</content:encoded></item><item><title>tmux for Presentations</title><link>https://nicknisi.com/posts/2015-11-02-tmux-for-presentations/</link><guid isPermaLink="true">https://nicknisi.com/posts/2015-11-02-tmux-for-presentations/</guid><pubDate>Mon, 02 Nov 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;gVn1PmkcYH0&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Live coding is stressful. You&apos;re often worried about making a mistake while doing it, and it seems you&apos;re set up for failure if you&apos;re trying to focus on what is being displayed on the projector while doing so. Fortunately, tmux makes it incredibly simple to attach to the same session and display one on the projector while keeping one on your local display to work around this awkwardness!&lt;/p&gt;
&lt;p&gt;When attached to the same session, tmux will automatically resize the larger client&apos;s window to the size of the smaller client. With this in mind, make sure that the smaller client is the one displayed on the projector, or you will end up with a border around the screen. Typically, I will set the client on my local display to full screen mode to ensure that is is the larger or the two clients.&lt;/p&gt;
&lt;p&gt;With this set up, you can simply work in either client and the changes will immediately be seen in the other! No more looking over your shoulder!&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;aggressive-resize&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;We want to turn on &lt;code&gt;aggressive-resize&lt;/code&gt; so that it only constrains the size of the tmux client to the size of the smaller client if both clients are looking at the same window. This setting isn&apos;t necessary for this to work, but I would still recommend it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setw -g aggressive-resize on
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Neovim</title><link>https://nicknisi.com/posts/2015-10-19-neovim/</link><guid isPermaLink="true">https://nicknisi.com/posts/2015-10-19-neovim/</guid><pubDate>Mon, 19 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;Recently, I&apos;ve been using neovim as a full-time replacement for vim in my daily workflow. It was ridiculously easy to get started, and so far I am really enjoying the benefits it has. The main benefit is that its plugins can run asynchronously, which is awesome. This means that when &lt;a href=&quot;https://github.com/benekastah/neomake&quot;&gt;Neomake&lt;/a&gt; (the neovim version of &lt;a href=&quot;https://github.com/scrooloose/syntastic&quot;&gt;Syntastic&lt;/a&gt;) runs JSHint or JSCS against my file, it doesn&apos;t completely freeze nvim while doing so. This is awesome!&lt;/p&gt;
&lt;p&gt;To get started with Neovim, you can simply install it from homebrew on OSX:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew tap neovim/neovim
brew install --HEAD neovim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also install it on other operating systems, such as Ubuntu:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo add-apt-repository ppa:neovim-ppa/unstable
sudo apt-get update
sudo apt-get install neovim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When getting started, you can simply symlink your &lt;code&gt;~/.vimrc&lt;/code&gt; to &lt;code&gt;~/.nvimrc&lt;/code&gt; and your &lt;code&gt;~./vim&lt;/code&gt; to &lt;code&gt;~/.nvim&lt;/code&gt; and things should just work. However, I ended up going the route of having configurations for vim and nvim so that I could easily go back to vim if I encountered any issues while using it in my day job.&lt;/p&gt;
&lt;p&gt;I recently gave a lightning talk on Neovim at the Omaha Ruby and Open Source Meetup. Check out the slides and video from the talk below if you&apos;d like to learn more. Also, please ask me questions on &lt;a href=&quot;https://twitter.com/nicknisi&quot;&gt;Twitter&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Video&lt;/h2&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;LRQGAnPtNdM&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Slides&lt;/h2&gt;
&lt;p&gt;&amp;lt;script
async
class=&quot;speakerdeck-embed&quot;
data-id=&quot;9baddf6a992c4c008b598eea2bf95294&quot;
data-ratio=&quot;1.77777777777778&quot;
src=&quot;//speakerdeck.com/assets/embed.js&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>vim + tmux - OMG!Code</title><link>https://nicknisi.com/posts/2015-02-25-vim-tmux/</link><guid isPermaLink="true">https://nicknisi.com/posts/2015-02-25-vim-tmux/</guid><pubDate>Wed, 25 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;I love vim. I&apos;ve been using it full-time for over three years and I still feel like a beginner at it. But I also feel productive in it. I had an opportunity to talk about vim and how I use it in tmux at February&apos;s OMG!Code. In this talk, I introduce some vim basics, talk about the best parts of vim, some of my favorite plugins, and then I integrate tmux into the talk and how I typically work with these two technologies throughout the day. It wraps up with a nice discussion of different plugins and configurations.&lt;/p&gt;
&lt;h2&gt;Repo&lt;/h2&gt;
&lt;p&gt;This &lt;a href=&quot;https://github.com/nicknisi/vim-workshop&quot;&gt;repo&lt;/a&gt; contains the slides and a default vim and tmux configuration, which I slimmed down from my full config to the settings and plugins which I think are essential for being productive quickly with vim and tmux.&lt;/p&gt;
&lt;h2&gt;Slides&lt;/h2&gt;
&lt;p&gt;&amp;lt;div class=&quot;embedded-content&quot;&amp;gt;
&amp;lt;script
async
class=&quot;speakerdeck-embed&quot;
data-id=&quot;95e1469e759f4affb420f6a7d90d6bfd&quot;
data-width=&quot;560&quot;
data-ratio=&quot;1.77777777777778&quot;
src=&quot;//speakerdeck.com/assets/embed.js&quot;
&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;Video&lt;/h2&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;5r6yzFEXajQ&quot; /&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>The Dojo Toolkit - NebraskaJS Lincoln</title><link>https://nicknisi.com/posts/2015-01-18-dojo-nebraskajs-lincoln/</link><guid isPermaLink="true">https://nicknisi.com/posts/2015-01-18-dojo-nebraskajs-lincoln/</guid><pubDate>Sun, 18 Jan 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;I gave a lightning talk on the &lt;a href=&quot;http://dojotoolkit.org&quot;&gt;Dojo Toolkit&lt;/a&gt; at &lt;a href=&quot;http://nebraskajs.com&quot;&gt;NebraskaJS Lincoln&lt;/a&gt; on January 15, 2015. In this talk, I gave a very brief overview of the Dojo Toolkit.&lt;/p&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;JkF7nFwVWjU&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://dojotoolkit.org&quot;&gt;The Dojo Toolkit website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dojo/dojo&quot;&gt;Dojo on Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dojo/dojo2&quot;&gt;Dojo2 on Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>NEJS Conf - We&apos;re hosting a conference!</title><link>https://nicknisi.com/posts/2015-02-16-nejsconf/</link><guid isPermaLink="true">https://nicknisi.com/posts/2015-02-16-nejsconf/</guid><pubDate>Mon, 16 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://nejsconf.com&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We&apos;re hosting a conference! &lt;a href=&quot;http://nejsconf.com&quot;&gt;NEJS Conf&lt;/a&gt; will take place August 7th at the Henry Doorly Zoo in Omaha, and it sure to be an interesting, exciting, and wild time! We&apos;re just getting started, but definitely check out the site, sign up for the mailing list, follow us on &lt;a href=&quot;https://twitter.com/nejsconf&quot;&gt;Twitter&lt;/a&gt; and keep a look out for the call for speakers!&lt;/p&gt;
</content:encoded></item><item><title>Frontend testing - Omaha Coffee &amp; Code presentation</title><link>https://nicknisi.com/posts/2015-01-05-frontend-testing-coffee-code/</link><guid isPermaLink="true">https://nicknisi.com/posts/2015-01-05-frontend-testing-coffee-code/</guid><pubDate>Mon, 05 Jan 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;This is a talk on frontend unit and functional tests, using the &lt;a href=&quot;http://theintern.io&quot;&gt;Intern&lt;/a&gt; testing framework, presented at &lt;a href=&quot;http://www.meetup.com/coffeeandcode/events/216021042/&quot;&gt;Omaha Coffee &amp;amp; Code&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Video&lt;/h2&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;Fgt_frjlAaQ&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://theintern.io&quot;&gt;Intern web site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/theintern/intern&quot;&gt;Github repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/theintern/intern-tutorial&quot;&gt;Intern tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/theintern/intern-examples&quot;&gt;Intern examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Update: 2015-01-11&lt;/h3&gt;
&lt;p&gt;I also gave a short lightning talk at the &lt;a href=&quot;http://www.meetup.com/Omaha-Ruby-Meetup/events/196497252/&quot;&gt;Omaha Ruby &amp;amp; Open Source Meetup&lt;/a&gt;, showing more examples of Intern usage.&lt;/p&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;oOssM0DusYc&quot; /&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Leapcopter</title><link>https://nicknisi.com/posts/2014-01-05-leapcopter/</link><guid isPermaLink="true">https://nicknisi.com/posts/2014-01-05-leapcopter/</guid><pubDate>Sun, 05 Jan 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import Video from &apos;@/components/Video.astro&apos;;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I finally purchased a Parrot AR Drone 2.0 this week after having a blast
playing with one at JSConf last year! I also recently got a Leap Motion
controller, and I thought it would be fun to get them to play together!&lt;/p&gt;
&lt;p&gt;&amp;lt;Video videoId=&quot;wRECaiWOaIA&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Source available on &lt;a href=&quot;https://github.com/nicknisi/leapcopter&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/leapmotion/leapjs&quot;&gt;leapjs&lt;/a&gt; code was a bit buggy, especially when trying to use it from
Node, so I am interacting with the Leap Motion exclusively in Chrome and
then using socket.io to send commands up to the server, which is
controlling the drone.&lt;/p&gt;
&lt;p&gt;On the server, I am using &lt;a href=&quot;https://github.com/felixge/node-ar-drone&quot;&gt;node-ar-drone&lt;/a&gt; to control the drone, and
&lt;a href=&quot;https://github.com/Soarez/ar-drone-png-stream&quot;&gt;ar-drone-png-stream&lt;/a&gt; to
get the video feed from the drone in the browser.&lt;/p&gt;
&lt;p&gt;Here is a list of gestures and the what action they invoke:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Key Tap - toggle the drown (takeoff/land)&lt;/li&gt;
&lt;li&gt;Circle - make the drone do a flip&lt;/li&gt;
&lt;li&gt;Tilt Palm Down - make the drone go forward&lt;/li&gt;
&lt;li&gt;Tilt Palm Up - make the drone go backwards&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The drone moves very slowly, as I didn&apos;t trust a Leap-controlled drone to
go flying fast in my house quite yet. Also, once &apos;Land&apos; is triggered, the
drone won&apos;t take off again, just in case I panic and start flailing about
in front of the sensor.&lt;/p&gt;
&lt;p&gt;My future plans for this demo include to cleaning up the code (it&apos;s a mess right
now), and adding more gestures (turning, up/down).&lt;/p&gt;
</content:encoded></item></channel></rss>