DEV Community

Cover image for I stopped prompting and started building a harness
Valentine Tikhomirov
Valentine Tikhomirov

Posted on AI-assisted

I stopped prompting and started building a harness

For the last few months I've been using AI agents in almost every part of my React Native work. At some point I noticed I was writing the same instructions over and over: how I want projects set up, what a good refactoring looks like, what to check before shipping. Every new session started from zero.

So I started turning those instructions into a harness: a set of skills and agents that encode how I actually work. It's still in alpha, I use it daily on my own projects, and here's how it's built and what I've learned so far.

Why a plugin and not a folder of prompts

My first version was just files copied into ~/.claude. Now the harness is a proper Claude Code plugin in its own git repo. Every skill and agent is called with an rnmh: prefix, so it's clear what comes from the harness and what doesn't.

The biggest change wasn't organizational, though. Once each task had its own dedicated skill, the agent's answers became noticeably more structured, and the results got better with them.

What's inside right now

  • Project bootstrap. Deliberately minimal: it fixes only strict TypeScript and the folder organization I choose, and asks about everything else per project. An opinionated template ages badly; a short interview doesn't.
  • Refactoring skill + agent. Built on the catalog from Martin Fowler's Refactoring (2nd edition), grouped by its chapters. The key decision: it applies refactorings instead of just listing code smells.
  • Cross-project consistency agent. I work on several RN projects in parallel. This agent compares them for duplicated code and naming drift, without assuming there's a shared library.
  • Design-to-code, architecture review, testing and test coverage round out the core set.
  • Diagnostics, release checklist, security review and RN upgrades are the newest additions. They're written, but I haven't battle-tested them on a real project yet, so they're next in line.

First real test

I ran the refactoring agent on my own side project, a headache tracker that's currently in alpha. The home screen had grown into a classic Long Function: one Home() component of ~330 lines with 5 useState, 5 useEffect, 3 async handlers and a three-way JSX branch, all inline.

Before:

function Home() {
  const [openAttack, setOpenAttack] = useState<Attack | null>(null);
  const [recent, setRecent] = useState<Attack[]>([]);
  const [now, setNow] = useState(() => systemClock.now());
  const [loadError, setLoadError] = useState(false);
  const [reduceMotion, setReduceMotion] = useState(false);

  useEffect(() => {
    AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
    const sub = AccessibilityInfo.addEventListener(
      'reduceMotionChanged',
      setReduceMotion,
    );
    return () => sub.remove();
  }, []);
  // ...4 more effects, 3 async handlers, ~150 lines of inline JSX
}
Enter fullscreen mode Exit fullscreen mode

After:

function Home() {
  const reduceMotion = useReduceMotion();
  // ...same effects and handlers, unchanged

  return (
    <View style={styles.middle}>
      {openAttack ? (
        <OngoingAttackCard
          openAttack={openAttack}
          now={now}
          pulseStyle={pulseStyle}
          onSetIntensity={handleSetIntensity}
        />
      ) : recent.length > 0 ? (
        <RecentAttacksList recent={recent} now={now} />
      ) : (
        <Text style={styles.emptyText}>{t('home.noAttacksYet')}</Text>
      )}
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Every change mapped to a named refactoring from the catalog:

  • Extract Component: IntensityPicker, OngoingAttackCard, RecentAttacksList, HomeActions
  • Extract Function into hooks: useReduceMotion, plus useDictation and a generic useKeyboardVisible on the log-input screen
  • Move Function: formatTime() went to the shared formatting module, so it wouldn't be duplicated in two new components
  • Remove Dead Code: an unused style and a dead import it found along the way

Home() went from ~330 to ~150 lines. The total line count actually grew a bit because of new files and imports, and that's fine: the point is that each piece now has one job. Behavior didn't change, since the agent kept refactoring strictly separate from feature work, and tsc and eslint came back clean.

One honest caveat: the agent only ran static checks, not the app, so checking the screens on a simulator was still on me.

What I've learned so far

  1. Narrow skills beat one giant prompt. A skill that does one job with clear steps gives far more consistent output than a long general instruction.
  2. Make it act, not just advise. A reviewer that only lists problems creates homework. One that makes the change and explains it saves time.
  3. Understanding the project is still the weak spot. Agents struggle to read a codebase and actually use that knowledge later. They can miss existing features, so I often have to steer them. Tools like graphify, which gives the agent a map of the codebase, help a lot here, but it's not solved.

What's next

I'm planning to open the harness to other React Native developers. If you're building something similar, or would want a toolkit like this, I'd love to hear what you'd expect from it in the comments.

Top comments (0)