cd /news/developer-tools/go-pure-locking-in-quality-using-nat… · home topics developer-tools article
[ARTICLE · art-80875] src=pub.towardsai.net ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Go Pure: Locking In Quality Using Native Claude Code Features

Nick, a developer using Anthropic's Claude Code, refactored his AI-assisted development workflow by converting prose-based coding standards into deterministic ESLint lint rules, reducing reliance on probabilistic AI for code quality and improving cost efficiency. The move eliminated 17 session files, a post-compaction hook, and a 400-line skills index, replacing them with file/folder-aware rules that catch errors like timezone bugs at zero context cost. Anthropic's Memory system and path-scoped rule loading in Claude Code enabled the shift, with custom plugins such as 'clear-mornings' enforcing rules like 'prefer-datecore-over-new-date' as blocking errors.

read10 min views1 publishedJul 30, 2026

Heya! My name is Nick and I love writing Web apps! With the advent of AI tooling in late 2025 hinting at greater things to come. I wanted to realize the benefits AI could offer but deep down I wanted to begin to understand it.

In 2026 it feels like there is a rapidly evolving tech space around context engineering. When Anthropic releases more powerful models they usually release new tooling that can improve the software development workflow experience.

Looking back on the articles I have written here on Medium I see where I needed to work around the limitations that existed at the time. Working with a 200kb context level is not a constraint I operate on daily now and some of the tooling I built around that limit no longer needs to exist.

The feature capabilities that Anthropic built into the Memory system are fantastic! I have moved virtually all of the prose based coding standards that lived in markdown and converted them to lint and file type based rules.

The goal was to utilize AI only where it’s needed but default to deterministic tools and improve cost efficiency. By using less AI for code quality the whole development cycle has less friction not to mention greater speed with faster lint tools.

The sole motivation and goal while reworking my Claude Code infrastructure was as follows.

Every rule lives at the lowest layer that can catch it deterministically. Prose is the layer of last resort.

Everything I had built at the time was a rational patch for a real 2025 weakness. We were coding with lossy compaction, no persistent memory, no path-scoped rule and prose as the only enforcement mechanism.

This is what the extra layers looked like before the refactor.

— 17 Session Files: markdown dumps for stringing multi-day sessions together

— Coding Standards: every rule, pattern and testing recipe, in prose, that every new session read. This fired as a hook and would thrash other Claude sessions.

— Post-compaction Hook: re-inject “context essentials” file every time compact fired. Because compaction used to eat the rules.

— A SPARC config, pattern templates, a VIOLATIONS.md ledger and a 400 line hand maintained skills index.

Every piece made sense at the time but every piece also paid a tax on every session.

Prose are anything Claude has to read and remember

A lint error is not probabilistic. It fires the same way every time, for every bot or human at zero context cost.

We took all of the mechanical coding-standards direction out of markdown and made it ESLint errors that we possibly could. For most things this is a straightforward swap but for more complex issues you will have to get more creative.

That’s no problem though because Claude Code provides file/folder aware rules that only apply while editing those files. If you want your AI to not get lazy and skip important steps you need this system. We found history in the session files of many production bugs in a timezone sensitive app. We turned these into rules and it became a custom plugin (shown below).

js// eslint.config.mjs{ files: ['src/**/*.{ts,tsx}'], plugins: { 'clear-mornings': customRulesPlugin }, rules: { 'clear-mornings/prefer-datecore-over-new-date': 'error', 'clear-mornings/require-timezone-in-date-operations': 'error', 'clear-mornings/no-manual-date-concatenation': 'error', },}Now when Claude writes any new Date() in a component the result isn’t a “please remember not to do this”. It’s a red squiggle in the IDE and a blocked commit.

Same treatment for useEffect in client side components. We also prevented Claude from working around the system with ignore statements.

The removed session files were a great source of defects we encounter repeatedly and their remediation. These were converted straight into lint rules where possible. If you have been recording these in the memory system instead those can be a good pool to pull from.

Simply ask Claude “hey what are the commonly encountered bugs in this system and can they be converted to a deterministic system like lint”. Claude can pull from memory or if you have git CLI your repo history holds the sordid details.

We kept exactly one edit time hook, its header shows you the lesson that shaped it.

bash# History: this hook used to whole-file-grep every edit for ~15 patterns# and "block" with exit 1. Two problems:# 1. exit 1 is NON-BLOCKING for PostToolUse — it never actually blocked.# Proof: 48 data-testid, 5 @ts-*, and other "blocked" patterns# accumulated anyway.# 2. Whole-file grep fired on legacy code elsewhere in a file you barely# touched → false blocks + token thrash across parallel sessions.Two hard learned specifics there.

First, hook exit codes matter. For postToolUse exit 1 is advisory where as exit 2 is blocking. We had months of imaginary enforcement before noticing violations piling up.

Second, be diff aware. The hook now inspects only the content the current edit introduced ('new_string' / 'content') — never the file on disk. So legacy code and parallel sessions can’t trip it (full hook below).

#!/bin/bash# Edit-time standards hook — NARROW + DIFF-AWARE## Covers ONLY what your linter structurally cannot:#   - If ESLint ignores test files, suppressions and testid queries in tests#     are invisible to lint — this hook is their only gate.#   - Rules with legacy violations (where a lint `error` would block CI on#     old code) get a DIFF-AWARE check that blocks only NEW additions.## DIFF-AWARE: inspect ONLY the content this edit introduced (Edit.new_string /# Write.content), never the file on disk — legacy code and parallel-session# edits elsewhere in the file can't trip it.## EXIT CODES (PostToolUse contract):#   2 = block + feed message back to Claude (real enforcement)#   0 = allow (optional advisory on stderr)#   NOTE: exit 1 is NON-BLOCKING for PostToolUse. Enforcement via exit 1#   is imaginary.set -o pipefailINPUT=$(cat)TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')# Only Write/Edit with a file path.if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then exit 0; fiif [[ -z "$FILE_PATH" ]]; then exit 0; fi# Only code files.case "$FILE_PATH" in  *.ts|*.tsx|*.js|*.jsx) ;;  *) exit 0 ;;esac# ---- Diff-aware content: ONLY what this edit introduced ----NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty')if [[ -z "$NEW_CONTENT" ]]; then exit 0; fiis_test_file=falsecase "$FILE_PATH" in  *test*|*spec*|*__tests__*) is_test_file=true ;;esacBLOCKS=()WARNINGS=()# BLOCK: data-testid and *ByTestId queries — NEW additions only.# Accessible queries (getByRole/getByText/getByLabelText) required instead.if printf '%s' "$NEW_CONTENT" | grep -qE 'data-testid|(get|query|find)(All)?ByTestId'; then  BLOCKS+=("data-testid / *ByTestId is banned — use semantic HTML + accessible queries (getByRole, getByText, getByLabelText).")fi# BLOCK: type suppressions inside TEST files (non-test src is covered by# ESLint's ban-ts-comment; tests are lint-ignored, so this is their gate).if $is_test_file && printf '%s' "$NEW_CONTENT" | grep -qE '@ts-ignore|@ts-expect-error|@ts-nocheck'; then  BLOCKS+=("@ts-ignore / @ts-expect-error / @ts-nocheck — fix the underlying type, never suppress it.")fi# WARN: newly-skipped tests. Advisory — fix or remove, don't leave disabled.if $is_test_file && printf '%s' "$NEW_CONTENT" | grep -qE '\b(describe|it|test)\.skip\b|\bx(describe|it|test)\b'; then  WARNINGS+=("Skipped test added — fix or remove it rather than leaving it disabled.")fiif [[ ${#BLOCKS[@]} -gt 0 ]]; then  {    echo ""    echo "STANDARDS VIOLATION (new code): $FILE_PATH"    echo "========================================"    for b in "${BLOCKS[@]}"; do echo "  [BLOCK] $b"; done    for w in "${WARNINGS[@]}"; do echo "  [WARN]  $w"; done    echo ""  } >&2  exit 2fiif [[ ${#WARNINGS[@]} -gt 0 ]]; then  {    echo ""    echo "Standards check: $FILE_PATH"    echo "----------------------------------------"    for w in "${WARNINGS[@]}"; do echo "  - $w"; done    echo ""  } >&2fiexit 0

Once the code is complete and it’s time to push there is a more strict set of code quality checks. These all run on pre-commit. All of these are commit checks and are nice to have done for you when wrapping up a long-running session.

Our final CLAUDE.md shrank to 81 lines and holds what prose is actually for: judgment calls no linter can express.

Of those things that remained in CLAUDE.md we questioned does this apply on every turn or only when specific files are open?

CLAUDE.md contains topics that apply in every session. Ours contains constraints like:

— Load the domain skill before grepping

— database writes and commit’s need human approval

— no use of memoization since we use React compiler

— a handful of architectural invariants and pointers

For constraints that apply only to specific files (or directories) we can use rules. A rules file is plain markdown with one frontmatter field.

yaml---paths: - 'src/server/trpc/**/*.ts'---We applied rules covering the following topics:

— SSR Authentication

— async server component stacking

— tRPC security and procedure lifecycle

— i18n policy

and it means I can push far more code more quickly

The old system was built to protect the output quality and did that but also ended up causing real friction when I had multiple Claude sessions running. When any of the secondary sessions made a change on disk that caused a hook to run and check all of the uncommitted code, Claude would often git stash code from another session then have to stitch it all back together after realizing its mistake.

The new system offers the flexibility to weave in other sessions while a long-running task finishes in the main thread. I can finally do this without stomping on any of the other work and it means I can push far more code more quickly.

All coding and research sessions start with a plan. For one thing, it makes sense to start Claude off in a read-only state. When you start in/plan Claude will never write to files until after gaining approval.

One of the biggest changes to our system was to remove session files. The replacement is Claude plans. These documents had the same purpose: to document decisions and the current state of a feature.

By writing a plan document into the native directory ./claude/plans (or as you see here the docs directory) Claude will know to reference this doc on a new session if it comes up in conversation.

Take a look at the start of this session after exiting plan mode. Claude is poised and ready to take off! Using plan mode is a great way to have Claude researching and getting prepped up for a project while another Claude session(s) is running. Just let Claude sit after the plan is written, then even if the power goes out you still have that plan file, the result of all the research and planning. Once the other session(s) completes commit/push and let your new plan rip!

Now after Claude gets to around 55% context and is at a stopping point I can straight up drop the session, start a new one and simply say “pick up phase 3 of …” and Claude knows exactly what to do!

Even with the 1m context I often still run over the 75% mark where Claude needs a renewal or complete abandonment in favor of a new session.

The now native built in memory and plans features allow for a consistent and reliable source of truth when it comes to state of a project, the decisions made while working on it and the original vision it started from.

I researched compaction and found that the process actually uses tokens. It’s packaging up the accumulated context and sending it off to the server for summarization. Instead I have been using the /clear command near the end of my usable context.

Because we start with a plan and can easily pickup from the plan. Holding around a bunch of superfluous context isn’t advantageous and costs more tokens over the life of your task.

My workflow in 2026 has gone entirely native. I always start with a plan using the /plan command or the Shift + Tab mode toggle. That will write a plan to session and you can extend this to write to disk.

Ensuring code quality with deterministic tools is faster, cheaper and doesn’t make a mistake. Speeding up the development cycle and resulting in less thrash while running multiple sessions.

Thanks for reading and please indulge me! How do you facilitate multi-session workflows? I mean both multiple Claude sessions to work on a single task as well as multiple simultaneous sessions? How have you kept your code clean using hooks or other native features? Please tell me in the comments!

https://code.claude.com/docs/en/common-workflowshttps://code.claude.com/docs/en/memoryhttps://code.claude.com/docs/en/agent-sdk/hooks

Go Pure: Locking In Quality Using Native Claude Code Features was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.

── more in #developer-tools 4 stories · sorted by recency
── more on @nick 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/go-pure-locking-in-q…] indexed:0 read:10min 2026-07-30 ·