Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
Filter by Categories
About Article
AI Tools
Analyze Data
Archive
Best Practices
Better Outputs
Blog
Code Optimization
Code Quality
Command Line
Course
Daily tips
Dashboard
Data Analysis & Manipulation
Data Engineer
Data Visualization
DataFrame
Delta Lake
DevOps
DuckDB
Environment Management
Feature Engineer
Git
Jupyter Notebook
LLM
LLM Tools
Machine Learning
Machine Learning & AI
Machine Learning Tools
Manage Data
MLOps
Natural Language Processing
Newsletter Archive
NumPy
Pandas
Polars
PySpark
Python Helpers
Python Tips
Python Utilities
Scrape Data
SQL
Testing
Time Series
Tools
Visualization
Visualization & Reporting
Workflow & Automation
Workflow Automation

Make Your Coding Agent Answers Easier to Scan with i-have-adhd

Make Your Coding Agent Answers Easier to Scan with i-have-adhd

Table of Contents

Introduction

Have you ever received an answer from your coding agent that was correct but hard to scan?

This happens to me often in Claude Code. The answer may be useful, but the important parts are buried in long paragraphs, so I spend extra time figuring out what Claude Code is doing, why it chose that path, and what I should do next.

This matters because if I cannot quickly see the agent’s reasoning and next action, I am more likely to miss a suggestion or change that does not fit what I asked for.

The i-have-adhd plugin claims to solve this by making coding-agent answers more scannable and action-first.

The claim is appealing, but I wanted evidence. So I ran a small A/B test to compare Claude Code with the plugin off and on.

This article walks through the experiment and the results so you can decide whether the plugin is worth installing.

💻 Get the Code: Get the runner, prompts, raw outputs, and metrics from the GitHub companion folder.

Stay Current with CodeCut

Easy-to-digest articles on Python, AI, and open-source tools. Delivered twice a week.

What Is the i-have-adhd Plugin?

i-have-adhd is a coding-agent plugin that makes responses easier to scan and act on. It does this by nudging the agent to:

RuleWhy it matters
Put the next action firstThe reader can see the recommended action before reading the explanation.
Use numbered steps for multi-step answersThe order of work is clear.
Limit long lists to the five most useful itemsThe reader has fewer options to sort through.
Skip unnecessary contextThe answer stays focused on the decision or action.
End with one concrete follow-upThe reader knows what to do next without asking another question.

If you need machine-readable structure instead of human-scannable answers, my structured LLM output tools comparison covers schema-based approaches.

Success conditions for using the plugin

I did not want to measure length alone. For the plugin to be worth using, it had to pass these checks:

  • Is the answer easier to scan?
  • Is the important information easier to find?
  • Is useful detail still included?
  • Does a detailed prompt still get a detailed answer?

Setup

Here is the test environment:

ItemValue
Claude Code2.1.251
ModelClaude Opus 5
Operating systemmacOS 25.6
MachineApple M5 Pro, 64 GB
Plugini-have-adhd v0.2.0

For installation instructions for the plugin, refer to the setup section in the GitHub README.

How I Tested It

Controlled Settings

I compared Claude Code with the plugin off and on. To keep the comparison fair, every run used:

  • Same model
  • Same prompt
  • Same default output style

Each prompt was run five times in each condition.

Run Command

Each run used the same command to remove avoidable sources of variation:

  • claude -p "$prompt" runs a fresh, single-turn Claude Code response.
  • --permission-mode acceptEdits prevents permission handling from changing between runs.
  • outputStyle: "default" keeps the baseline response style fixed.
  • learning-output-style@claude-plugins-official: false disables a second output-style plugin that could change the answer format.
claude -p "$prompt" \
  --permission-mode acceptEdits \
  --settings '{"outputStyle":"default",
               "enabledPlugins":{"learning-output-style@claude-plugins-official":false}}' \
  < /dev/null

With those settings fixed, the only thing I changed was whether the plugin was on or off.

Plugin Switch

For this Claude Code experiment, I controlled the plugin with one marker file: ~/.claude/.i-have-adhd-always.

  • Creating the file turned the plugin on.
  • Removing the file turned the plugin off.
# Plugin on: a SessionStart hook reads this sentinel and injects the ruleset
touch ~/.claude/.i-have-adhd-always

# Plugin off: same model, same prompt, no ruleset
rm ~/.claude/.i-have-adhd-always

The metrics

To make the comparison repeatable, I measured each answer with simple text-based metrics instead of relying only on manual reading.

Here are the metrics I will use:

MetricWhat it tells me
Prose word countHow much explanation the reader has to read, excluding code blocks.
Non-blank linesHow much vertical space the answer takes on screen.
Total list itemsHow many choices the reader has to scan before deciding what matters.
Ends with a next actionWhether the answer gives the reader a concrete thing to do next.

For prose word count, I stripped fenced code blocks first then counted the words.

prose = strip_code_blocks(answer)
prose_word_count = len(prose.split())

The plugin's answer to Prompt 3 has 422 words in total. The two prose lines are counted, while the fenced code block between them is stripped, leaving 308 prose words.

For non-blank lines, I split each answer by line breaks, removed empty lines, and counted the remaining lines.

nonblank_lines = sum(bool(line.strip()) for line in answer.splitlines())

Six lines from the plugin's answer to Prompt 1. Three carry content and are marked counted; the three blank lines between them are marked stripped.

For total list items, I counted Markdown bullet lines and numbered-step lines separately, then added them.

bullet_lines = count_lines(answer, r"^\s*[-*]\s")
numbered_lines = count_lines(answer, r"^\s*\d+\.")
total_list_items = bullet_lines + numbered_lines

Five lines from the default answer to Prompt 1. Three bullet lines and two numbered lines are labelled separately, then added: 3 bullets plus 2 numbered equals 5 list items.

For the final-action check, I looked at the last two non-blank lines and marked the answer as actionable if one of them started with Next or **Next:**:

ends_with_next_action = starts_with_next(last_two_nonblank_lines(answer))

Lines from the plugin's answer to Prompt 1. Everything above the closing line is marked not read, and the boxed final line, which starts with Next, marks the answer as ending with a next action.

Together, these metrics tell me whether the answer is shorter and easier to scan.

The full experiment source is in run_ab.sh.

The three prompts

I tested the plugin on three prompts: two where the agent had to choose among several possible causes, and one where I explicitly asked for a detailed explanation.

Prompts 1 and 2 are diagnostic. They check whether the plugin makes an answer easier to scan when the question has several plausible causes:

Prompt 1: My model accuracy dropped after I retrained it on the new data. What should I look at?
Prompt 2: Our nightly ETL job started taking 3 hours instead of 40 minutes. Where should I start looking?

Prompt 3 asks for depth. It checks whether the plugin still gives enough detail when the reader explicitly asks for an in-depth explanation:

Prompt 3: Explain the difference between .apply() and vectorized operations in pandas, and when .apply() is actually the right choice. I want enough detail to decide.

Test Results

Prompt 1: Model accuracy dropped after retraining

The first prompt was:

My model accuracy dropped after I retrained it on the new data. What should I look at?

Here are the median results across five runs:

MetricDefaultWith the plugin
Prose words323170
Non-blank lines198
List items135
Runs ending with a next action0 of 55 of 5

Default answer

The default answer looked like this. I shortened the answer with .... See the full default answer for the complete response:

Here are the key things to investigate, roughly in order of likelihood:

## Data Issues (check first)
- **Data drift** — Compare distributions of features and target between old and new data. Shifted distributions are the most common cause.
- **Label quality** — Check for labeling errors, inconsistent annotation guidelines, or changed label definitions in the new data.
...

## Preprocessing / Pipeline Issues
- **Feature engineering changes** — Were any transformations, scalers, or encoders refit on the new data? A scaler fit on different distributions can hurt.
...

## Training Issues
- **Train/test split** — Are you evaluating on the same test set, or a new one? A harder test set looks like a worse model.
...

## Quick Diagnostic Steps

1. **Evaluate the old model on the new test data** — if it also drops, the data shifted, not the training.
...

The single most informative step is #1: test your old model on the new data. That tells you immediately whether the problem is the data or the training.

Want me to help you write code to diagnose any of these?

Plugin answer

The plugin answer:

Check these in order:

1. **Data drift** — Compare distributions of key features between old and new data. Plot histograms or run a KS test. If the new data looks different, that's your likely cause.

2. **Label quality** — Spot-check labels in the new data for errors, inconsistencies, or a shifted class distribution. Even a small % of mislabeled examples can tank accuracy.

3. **Train/test leakage lost** — If the old model accidentally had leakage that inflated metrics, retraining on clean data will look like a drop. Verify your old eval was legit.

4. **Class imbalance shift** — Compare class ratios old vs new. A shift from 50/50 to 90/10 changes what "accuracy" means. Check per-class precision/recall, not just overall accuracy.

5. **Hyperparameters/preprocessing mismatch** — Confirm the same feature engineering, scaling, and hyperparameters were used. A missing normalization step or changed default can silently break things.

**Next:** Run `old_data.describe()` vs `new_data.describe()` side by side and check for columns with large mean/std shifts. That's the fastest way to spot data drift.

Nothing essential disappeared. Data drift, label quality, leakage, class imbalance, and preprocessing still appear.

But the plugin answer is much easier to scan for the following reasons:

  • Condensed list: the answer reduced many bullets across multiple sections into five focused checks, making it easier to act on.
  • Clear order: the five checks were ranked, so the user knew which one to try first.
  • Concrete next step: the answer closed with one action the reader could take immediately.

Prompt 2: ETL job slowed from 40 minutes to 3 hours

The second prompt was:

Our nightly ETL job started taking 3 hours instead of 40 minutes.
Where should I start looking?

Here are the median results:

MetricDefaultWith the plugin
Prose words257222
Non-blank lines2617
List items135
Runs ending with a next action0 of 55 of 5

Default answer

The full default answer listed five broad headings, each with several bullets:

Here's a systematic approach, ordered by most common culprits:

## 1. Data Volume Changes
...

## 2. Database / Query Performance
...

## 3. Resource Contention
...

## 4. Code / Config Changes
...

## 5. Infrastructure Changes
...

## Where to start concretely
...

Near the end, it gave the highest-value action:

The single highest-value action: **find which step in the pipeline got slower**. Going from 40min to 3hr is a ~4.5x slowdown — that's usually one or two steps, not everything uniformly slower. Once you isolate the step, the root cause is usually obvious.

That is useful advice, but it comes after a long list of possible causes, making it easier to miss and slower to act on.

Plugin answer

The full plugin answer started with an action:

**Run this first** to find what changed recently:

```bash
# Check git log for ETL-related changes around when slowdown started
git log --since="1 week ago" --oneline -- '**/etl*' '**/pipeline*' '**/transform*'
```

Then it listed exactly five ranked causes:

## 5 most likely causes, ranked

1. **Data volume spike** — Check row counts for source tables...

2. **Missing or dropped index** — A schema migration may have dropped an index...

3. **New upstream schema / join explosion** — A changed join key or added NULL values can silently turn an inner join...

4. **Resource contention** — Another job now overlaps the ETL window...

5. **Code change in transform logic** — A recent commit added ...

And then the quick diagnostic steps:

## Quickest diagnostic

Add timing around each ETL phase (extract, transform, load) if you don't already have it. The phase that grew from minutes to hours tells you exactly where to dig.

```python
# Minimal example
import time
for phase in [extract, transform, load]:
    start = time.time()
    phase()
    print(f"{phase.__name__}: {time.time() - start:.1f}s")
```

Finally, it gave the highest-value action:

**Next step:** Check whether this is a data problem or a code problem — compare today's source row counts against last week's. If counts are similar, focus on indexes and recent code changes.

Even though the length is similar, the output is more readable because of the following:

  • Fewer items: thirteen list items became five, reducing the amount of information the user had to compare.
  • Ranked checks: the plugin ordered the causes, so the user could start with the most likely checks first.
  • Action first: a runnable command appeared before the explanation, making the answer useful even when skimmed.

Prompt 3: pandas .apply() vs vectorized operations

The third prompt was:

Explain the difference between .apply() and vectorized operations in pandas,
and when .apply() is actually the right choice. I want enough detail to decide.

This prompt tested whether the plugin still gives enough explanation when the user explicitly asks for depth.

MetricDefaultWith the plugin
Prose words426359
Non-blank lines6150
List items58
Bullet lines03
Numbered lines05
Runs ending with a next action0 of 55 of 5

The plugin answer is 16% shorter, but the table alone cannot tell us whether useful explanation was lost. To check that, we need to look at how both answers were organized.

Default answer

The full default answer opened with the concept, then moved through performance, use cases, decision rules, and traps:

## `.apply()` vs Vectorized Operations in Pandas

### Vectorized operations
Vectorized operations run in compiled C/NumPy under the hood...

### What `.apply()` actually does
`.apply()` is a loop in disguise...

### Performance hierarchy (fastest to slowest)

| Approach | Relative speed | Notes |
|---|---|---|
| NumPy / pandas vectorized ops | **1x** (baseline) | `.str`, `.dt`, arithmetic, `np.where`, `pd.cut` |
...

### When `.apply()` is the right choice

**1. Complex logic that genuinely can't be vectorized**
...

### Decision rule
Ask yourself: ...

### Common traps
...

Plugin answer

The full plugin answer covered the same core ideas, but reorganized the sections, trimmed some extra material, and kept the final action explicit:

## Vectorized ops vs `.apply()` in pandas
Vectorized operations run in compiled C/NumPy...

### Speed difference
Vectorized arithmetic: 1x baseline
Row-wise `.apply()`: 50-200x slower

### Why vectorized is faster
1. No per-element Python overhead
2. Cache-friendly memory access
3. SIMD / CPU optimizations

### When to use vectorized (most of the time)
Arithmetic, conditionals, string ops, datetime ops, aggregations...

### When `.apply()` is actually the right choice
1. Complex row logic that can't be expressed as array ops
2. GroupBy with custom aggregation
3. Small DataFrames where speed doesn't matter
4. Operating on non-numeric/irregular data
5. Prototype/exploration

Next: pick one `.apply()` call in your code and check if it fits the vectorized alternatives above.

What changed in the plugin answer:

  • The comparison appears upfront, so the reader sees the core difference before reading examples.
  • It removes extra background and keeps the answer tied to the main question: which approach should I use, and when?
  • Extra material is trimmed. The default answer includes common traps, while the plugin answer leaves that out to keep the answer focused.
  • It ends with a specific next step, so the user knows how to apply the advice to their own code.

The answer became shorter, but not shallow. It still kept the core explanation needed to understand the concept.

Should You Install It?

After this test, I decided to keep the plugin installed because it makes Claude Code answers easier for me to scan and act on.

There are two things to keep in mind.

First, the rules may become less visible after a long coding session with many files, tool outputs, and previous turns. When that happens, a short reminder or a fresh session may help.

Second, there is a small token cost because the plugin adds its rules to the session context. But the rules are short, so the cost should be small.

Run the Test Yourself

The experiment files are available in the GitHub companion folder.

Run the batch:

git clone https://github.com/khuyentran1401/codecut-blog.git
cd codecut-blog/i-have-adhd-plugin-output-ab
bash run_ab.sh

The script runs the full A/B test:

  • Runs all prompts with the plugin on.
  • Runs the same prompts with the plugin off.
  • Saves the raw answers in transcripts/.

Stay Current with CodeCut

Easy-to-digest articles on Python, AI, and open-source tools. Delivered twice a week.

References

  • i-have-adhd evals/rubric.md (ayghri, 2026): the evaluation rubric weights correctness, autonomy, actionability, safety, and concision.
  • i-have-adhd evals/cases.jsonl (ayghri, 2026): the maintainers’ case taxonomy that separates action-oriented cases from cases where brevity can remove needed detail.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top

Work with Khuyen Tran

Work with Khuyen Tran