[DRAFT] This post is a draft and may change.

Skills, Because Prompts Aren't Enough

Draft
ai agent-skills umbraco alpinejs htmx

I have been using AI coding tools quite a lot lately, and one thing keeps coming back to me: prompts are useful, but they are not enough.

They work well when I need to explain what I want right now. Fix this bug. Refactor this component. Add this feature. But the more I use coding agents in real projects, the more I notice that the hard part is not always the immediate task. It is all the small judgement calls around it.

How do we normally structure this kind of Alpine.js component?

When should htmx own the interaction, and when should Alpine own it?

What does a good Umbraco property label look like?

Which parts of an Umbraco schema are editor-facing text, and which parts are developer contracts that should not be touched?

Those are the things I got tired of explaining over and over again.

So I started writing agent skills.

The problem with niche stacks

AI models are often very good at common patterns. If you ask for a React component, an Express route, or a basic Tailwind layout, there is a lot of training data pointing them in roughly the right direction.

But I spend a lot of time in more niche areas: Umbraco, Alpine.js, htmx, server-rendered interfaces, backoffice customisation, and content editor workflows.

In those areas, the model can still produce code. Often very confidently. But confidence is not the same as judgement.

I have seen agents invent concepts where Umbraco already has something built in. I have seen them reach for too much JavaScript when a small server-rendered interaction would be easier to maintain. I have seen them write Alpine.js that works, but does not match the conventions I want in a shared codebase.

That last part matters. In a team, “it works” is not the only quality bar. The code also needs to be consistent, understandable, and easy for the next person to change.

The first real itch came while I was cleaning up an ongoing project with a large and changing team. I was refactoring Alpine.js code and wanted to check whether things had been built consistently and correctly.

At some point I realised that I did not just want the agent to solve the next task. I wanted it to understand the way I prefer these things to be done.

Writing down judgement

The first skill grew out of that Alpine.js work.

For example, I prefer long-form Alpine directives:

<button
  type="button"
  x-on:click="open = !open"
  x-bind:aria-expanded="open.toString()">
  Toggle
</button>

over shorthand versions like this:

<button
  type="button"
  @click="open = !open"
  :aria-expanded="open.toString()">
  Toggle
</button>

Both can work. This is not about pretending one is always technically superior. It is about making the markup easier to scan for people who do not live in Alpine.js every day.

The same goes for larger Alpine components. I would rather see a focused Alpine.data(...) component than a large inline object living in an x-data attribute.

document.addEventListener('alpine:init', () => {
  Alpine.data('disclosure', () => ({
    open: false,
    toggle() {
      this.open = !this.open;
    },
  }));
});

Again, not because this is clever. Mostly because it is boring in a useful way.

That is what I want from skills. They are not there to make the agent produce more code. Quite the opposite. Many AI models seem very happy to write code, lots of code. I usually want less code. The less code there is, the fewer places there are for mistakes to hide.

htmx, Alpine.js, and clear ownership

The htmx and Alpine.js skills are also about boundaries.

My short version is:

htmx is for communication between the server and the client. Alpine.js is for communication between the client and the user.

That is not a complete architecture guide, but it is a useful rule of thumb.

If something is durable, validated, permissioned, stored in a database, or should survive a refresh, I usually want the server to own it. htmx can ask the server for the next piece of HTML.

If something is temporary UI state, such as a dropdown being open, a modal shell being visible, or a small transition, Alpine.js is often the better fit.

Without that boundary, agents can drift. They may duplicate server state in the browser, fetch JSON where the project already has server-rendered fragments, or turn a small enhancement into a mini application.

That is exactly the kind of drift I want skills to push back against.

Umbraco needs editor judgement

The Umbraco skills came from a similar place, but with a different focus.

Umbraco projects are not only developer projects. They are also editing interfaces. A backoffice can be technically correct and still feel awful to use.

I think many bad Umbraco experiences come from backoffices that were set up in a hurry: unclear document type names, labels that expose developer thinking, missing descriptions, repeated context, or fields where nobody explains what happens if the value is empty.

That is why I wrote the umbraco-friendly-schema skill.

The point is not to rename everything. It is to review the editor-facing parts of a schema:

  • Content type names
  • Property labels
  • Property descriptions
  • Small bits of guidance that remove uncertainty

The aliases are different. They are developer-facing contracts, and in existing projects I am very careful with them. Changing aliases can quickly become a much larger refactor, especially when Deploy, uSync, ModelsBuilder, templates, code, and content all depend on them.

So the skill has a very deliberate rule: improve editor-facing text, but preserve aliases.

A simple example:

Alias: heroBannerTitle
Weak label: Hero banner title
Better label: Heading

If the field already sits inside a Hero group, the label does not need to repeat all of that context. The editor just needs to know what to type.

The UFM skill came from a related cleanup after upgrades, where some labels still used old AngularJS syntax, and some blocks could be much easier to scan.

A block label should help editors identify the content, not just repeat the block type name.

FAQ: {umbValue: heading | fallback:Untitled}

That is much more useful than a list where every item is just called “FAQ Block”. If the label can include the heading, a title, a count, or another stable clue, editors can find the right item faster.

Fallbacks matter too. Editors do not always fill in content exactly the way the developer imagined. A label that works only when every field is filled out is a fragile label.

Skills as shared team memory

In a way, this is all just documentation.

We already have README files, project conventions, checklists, onboarding notes, and AGENTS.md files. Skills are part of the same family. The interesting difference is that a skill is written for an agent to use at the moment it is doing the work.

That makes the structure important.

A broad document saying “write good Alpine.js” is not very useful. A skill that says when to use Alpine, how to structure components, which directive style to prefer, and what to check before editing is much easier for an agent to apply.

The same goes for Umbraco. “Make the backoffice friendly” is a nice ambition, but it is vague. A skill can turn it into concrete checks:

  • Is this text for editors or developers?
  • Is the alias being preserved?
  • Does the description explain where the field appears?
  • Does it say what happens if the field is empty?
  • Does the label help an editor scan a list of similar blocks?

That is the value for me. The skill becomes a small piece of shared memory. It helps the agent work more like the team expects, and it gives the team something concrete to discuss.

Not a replacement for thinking

I do not see these skills as a way to avoid reviewing AI output.

They are more like guardrails. They make it a little less likely that the agent goes off and invents something strange. They remind it of the boring, practical preferences that are easy to forget in a single prompt.

And honestly, some of this is just my taste.

I prefer less code. I prefer explicit Alpine directives. I prefer editor-friendly Umbraco schemas. I prefer using what Umbraco already gives me before inventing another abstraction.

Or, to put it less elegantly: my own farts smell better than yours.

Maybe that is slightly personal. But that is also the point. If I am going to use AI to help me write code, I want to be able to stand behind the result.

Writing these things down helps.

The repository

The skills are available in my skttl/skills repository, and they can be installed with skills.sh.

If you want to try them, the short version is:

npx skills add skttl/skills

But the bigger point is not that everyone should use my exact rules. The more interesting idea is that teams can write down their own judgement and make it available to the agents they work with.

For niche stacks, local conventions, and project-specific preferences, that might be the difference between “the AI wrote some code” and “the AI helped in a way I can actually maintain”.