{"slug": "a-pragmatic-guide-to-ai-assisted-development", "title": "A pragmatic guide to AI-assisted development", "summary": "A developer working in an unfamiliar codebase documented an AI-assisted workflow using Claude Code with Opus 5.5 in the Claude desktop app, in which the agent first gets the application running locally so it can verify its own work. The developer applied the workflow to a real bug in the internal scheduling tool Hub, where a \"Highlighted abilities\" card listed skills without accounting for an employee's interest level — an employee with Mentor-level iOS experience who was \"Very Opposed\" to that work still appeared as a candidate for iOS projects. The developer's steps included loading GitHub issue 7195 into a fresh session, reproducing the bug via a generated /run-hub skill, and asking the model to explain the issue in plain terms before implementing a fix.", "body_md": "I’m currently staffed on a project in a codebase I have no familiarity with, so I’ve leaned on AI-assisted development more than ever. Along the way, I’ve found that writing the code is the easy part. Most of my time goes into understanding the problem, exploring solutions, and verifying the output.\n\nIn this tutorial, I’ll walk through each phase of my approach as I work\non a real issue. Feel free to follow along below, or browse the [skills](https://github.com/stevepolitodesign/skills#how-to-use-these-skills)\nI used.\n\n## \n    [Our base](#our-base)\n  \n\nI’ll be working on our internal scheduling tool, Hub. Hub allows employees to set their experience and interest level for certain skills. These are then rendered on an employee’s profile and help us decide who to staff on new opportunities.\n\nThe issue is that we feature certain skills in a “Highlighted abilities” card, without accounting for the employee’s interest level. So, an employee who has Mentor-level experience with iOS development, but is Very Opposed to that work, would still have the skill listed in the “Highlighted abilities” card. As a result, one employee was picked up as someone who might want to work on an iOS project, which was the last thing he wanted.\n\nFor context, I used Claude Code in the Claude desktop app with Opus 5.5, but the principles and approach will work across tooling and models.\n\n## \n    [Make sure the agent can run the app](#make-sure-the-agent-can-run-the-app)\n  \n\nThe first thing I do as part of my AI-assisted workflow is make sure the\nLLM can [run the application](https://code.claude.com/docs/en/skills#run-and-verify-your-app). If it can’t, it can’t verify\nits work. Plus, it saves me from having to deal with the paper cuts of\nsetting up the application myself. Here’s the prompt I ran:\n\nI have not run this project in a long time. Can you use /run to spin it up? If you find that does not work, use subagents to figure out how to get the app running locally, and use /run-skill-generator to extract that into a skill.\n\nThe LLM probed the application until it was able to spin it up\nsuccessfully, and generated a `/run-hub` skill, which others can use\nnow.\n\n## \n    [Understand the behavior, not the code](#understand-the-behavior-not-the-code)\n  \n\nAlthough the problem was fairly straightforward, I wanted to slow down and make sure I actually understood it before rushing to implement a fix with an LLM.\n\n### \n    [Load the issue in a new session](#load-the-issue-in-a-new-session)\n  \n\nI started by loading the issue into context in a new session. This gives the LLM everything it needs, including comments and screenshots.\n\nLoad https://github.com/thoughtbot/hub/issues/7195 into context, then wait for further instructions.\n\nI find that telling it to wait prevents the LLM from getting ahead of itself and trying to implement a fix.\n\n### \n    [Reproduce the bug locally](#reproduce-the-bug-locally)\n  \n\nNow that the session has the context it needs, the next thing I do is\nsee if I can replicate the issue locally. This is where `/run-hub` comes\nin handy. Without it, I would have had to spin up the app and stumble\nthrough seeding it to replicate the issue.\n\nUsing /run-hub, can you load the screen mentioned in the issue so I can better understand the problem?\n\n### \n    [Don’t be afraid to ask naive questions](#don39t-be-afraid-to-ask-naive-questions)\n  \n\nNow that I could see the issue myself, I took more time to scrutinize it. I could see that beneath the “Highlighted abilities” card was another table that rendered the employee’s skills alongside their interest level.\n\n/eli5 the issue this ticket aims to solve. I’m a little confused because I see that we render iOS as “Very Opposed” on the page.\n\nHere’s the part of the response that made it click for me:\n\nThe “Very Opposed” you saw is in the lower table. The bug is in the top box, which shows the same skill with no interest at all.\n\nSomeone skimming the top box reads “iOS mentor, starred” and never scrolls down to “Very Opposed”.\n\nAfter a quick back-and-forth, it was clear that this was a UI issue. Even though we were showing the employee’s interest level, it was overshadowed by the “Highlighted abilities” card.\n\n### \n    [Trace what actually happens](#trace-what-actually-happens)\n  \n\nNow that I had a better sense of the issue, I wanted to understand how the employee’s highlighted skills affect their eligibility for opportunities. At this point, I wasn’t sure if an employee was automatically selected as a candidate for an opportunity based on their interest level, or if someone from delivery manually selected them based on their highlighted skills.\n\nUsing /trace can you trace the lifecycle of an Opportunity to help figure out how he was selected for the project?\n\nThe answer surprised me. **The app never selected him.** A background\njob matches people to an opportunity, and it already skips anyone who is\nOpposed or Very Opposed to the skill. So, a person most likely picked\nhim after seeing iOS starred on his profile.\n\nThis was an important distinction, because it meant the fix belonged on the profile, not in the matching logic.\n\n### \n    [Let the LLM quiz you](#let-the-llm-quiz-you)\n  \n\nAt this point, I had a firm understanding of the problem, but in an effort to cement that understanding, I used the Socratic method to ensure I could defend it.\n\nUsing /understand, help ensure I understand the problem this ticket aims to solve. I’m not interested in the implementation details, just the behavior.\n\nInstead of explaining the problem to me, it asked me to explain it back, and then poked holes in my answer. Here’s the exchange that changed my mind:\n\n**Me:** He sees his “highlighted” skills in a card at the top of the\npage. The issue is that card does not indicate that he’s opposed to\nskills he’s really good at.\n\n**LLM:** … Say we add a red “Very Opposed” badge next to iOS in that\ncard and change nothing else. Would [his] problem be solved?\n\n**Me:** No, probably not. Instead, we should just remove the skill\nfrom that card.\n\n**LLM:** Right. The badge still leaves iOS starred as one of his top\nskills, and the star is what got him pinged.\n\nI originally figured we could just add the interest level to the card, but that would have missed the point.\n\n### \n    [Slice it into a SPEC](#slice-it-into-a-spec)\n  \n\nNow that I understood the problem, I felt comfortable moving forward\nwith creating a [vertical slice](https://thoughtbot.com/blog/break-apart-your-features-into-full-stack-slices). If you’re familiar with\n[spec-driven development](https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/), this is a lightweight version of that.\n\nLet’s create a SPEC for this using /slice\n\nWhat I like about this is that it’s focused on expected outcomes, not\nimplementation details. But it also provides the LLM (and a human)\nenough context so it knows where to look. This is important because I do\nthe implementation in a separate session to avoid [context\npollution](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents).\n\nBelow is what it generated. I also made sure to add this to the ticket to serve as a central source of truth, and to give other engineers an opportunity to review it.\n\n```\n# Hide opposed skills from Highlighted abilities\n\nThe Highlighted abilities card on a person's skills page leaves out featured\nskills they're Opposed or Very Opposed to. It uses the same interest rule the\napp already applies to featured skills in `Person#public_skills`, and the card\nstays on the page even when that leaves it empty.\n\n## Job story\n\nWhen a teammate scans my skills page to find people for a new project\nI want the skills I've said I'm opposed to kept out of my highlighted strengths\nSo I can keep my level on record as a resource without being pitched onto work I don't want\n\n## Acceptance criteria\n\n- [ ] Given a person is Mentor on a featured skill (iOS) and Very Opposed to it, when anyone views their skills page, then iOS isn't listed under Highlighted abilities\n- [ ] Given a person is Opposed to a featured skill, when anyone views their skills page, then that skill isn't listed under Highlighted abilities\n- [ ] Given a person is Neutral, Interested or Very Interested in a featured skill, when anyone views their skills page, then that skill is still listed under Highlighted abilities\n- [ ] Given a person is Very Opposed to a featured skill, when anyone views their skills page, then the skill still shows in its skill-type table below with its level and \"Very Opposed\"\n\n## Where to look\n\nPaths as of `2cc8f3453`. Where the code was, not what to build.\n\n- `app/presenters/person_abilities_presenter.rb` — `by_featured_skill` is the card's only data source and ignores interest\n- `app/models/ability.rb` — `interested` / `uninterested` scopes already define the Opposed + Very Opposed rule\n- `app/models/person.rb` — `public_skills` (line ~90) applies the same rule to featured skills for the API\n- `app/views/people/skills/index.html.erb` — the card renders unconditionally, so an empty result shows the heading with no rows\n- `spec/presenters/person_abilities_presenter_spec.rb` — existing `#by_featured_skill` spec to extend; `bundle exec rspec spec/presenters/person_abilities_presenter_spec.rb`\n\n## What I cut\n\n- Hiding the card when it's empty. It stays, empty, the way it looks today for anyone with no featured skills.\n- Showing interest in the card, and fixing the legend (the second comment on the issue). Once opposed skills are gone, everything left in the card has positive or neutral interest.\n- The staffing tab's \"Show all people\" still shows opposed people next to real candidates. It's a different screen, so it'd be a separate ticket.\n```\n\nMy favorite part is the “What I cut” section. It makes it clear what\nwe’re **not** doing, which keeps the implementation (and the PR) small.\n\n## \n    [Ship it in a fresh session](#ship-it-in-a-fresh-session)\n  \n\nNow that I have a sound understanding of the problem and a SPEC to guide the LLM, I create a new session to start the implementation phase. This is either the most or least interesting step of the process, depending on how much AI-assisted development you do.\n\n/ship the SPEC outlined in the GitHub issue. During the verification\nphase, use the `/run-hub` skill. Take screenshots for all the cases\nmentioned in the acceptance criteria. Do this with a dynamic workflow.\n\nThe idea is that a [dynamic workflow](https://code.claude.com/docs/en/workflows) will orchestrate\nthe following steps in **separate** sessions. This is important because\nit ensures the session that wrote the code isn’t also the one reviewing\nit. A session reviewing its own code is biased toward it.\n\n1. Identify any [preparatory refactors](https://thoughtbot.com/blog/finding-the-time-to-refactor) .\n2. Implement the solution with a shameless green, [outside-in\nTDD](https://thoughtbot.com/blog/testing-from-the-outsidein) approach.\n3. Review the implementation against multiple axes, including the SPEC.\n4. Verify the implementation against the SPEC.\n5. Explain the change, including the why, risks, and trade-offs.\n\nAfter 54 minutes, the LLM landed on this simple one-line solution.\n\n```\n# app/presenters/person_abilities_presenter.rb\n\n abilities\n   .joins(:skill)\n   .merge(Skill.featured)\n+  .merge(Ability.interested)\n   .sort_by(&method(:skill_level_type_and_name))\n```\n\nThat might seem like a lot of time, but you have to remember that the\nLLM is implementing this with [TDD](https://thoughtbot.com/blog/prevent-the-robocalypse-with-tdd), then doing a thorough\nindependent review, and finally verifying each case of the acceptance\ncriteria while providing screenshots.\n\nDuring this time, I’m not interacting with the session. Instead, I work on something else in parallel.\n\n## \n    [Verify and review yourself](#verify-and-review-yourself)\n  \n\nOnce I came back to the session, I manually verified the work.\n\nCan you spin up the dev server and tell me what page to view so I can review myself.\n\nAdditionally, I reviewed the artifact my\n[`/diff-explainer`](https://github.com/stevepolitodesign/skills#diff-explainer) skill generated, so that I could get\na sense of the **why** behind the change. This allows me to understand\nrisks, trade-offs, and alternative approaches. This is similar to what\nyou would expect from a colleague’s PR description, since you were not\naround for each decision made during implementation.\n\n## \n    [Write the PR description yourself](#write-the-pr-description-yourself)\n  \n\nI’m OK with LLM-generated commit messages, since the LLM holds all the\ncontext. I think of it as an audit log. However, I still think it’s\nimportant to [write the PR description yourself](https://thoughtbot.com/blog/how-to-use-llms-without-driving-your-team-mad). It adds\ncredibility, and also ensures you can explain and defend the spirit of\nthe change. If I do use an LLM to write prose, like the SPEC above, I\nadd a note saying so. In that case, it was “AI-generated, but I vetted\nit and am adding it for posterity.”\n\nMy [`/pr`](https://github.com/stevepolitodesign/skills#pr) skill reads the branch, suggests a title, and gives\nme a brief of what changed, the risks, and the trade-offs. Then it\n**refuses** to write the description for me. Since we [squash-merge our\nPRs](https://thoughtbot.com/blog/don-t-forget-the-silent-step-when-you-squash-and-merge), the description becomes the commit message on `main`, so\nit should be in my words.\n\n**Changed**\n\n- A skill you’re Opposed or Very Opposed to no longer shows in the Highlighted abilities card, even when your level is high (Mentor on iOS, say).\n- It still shows in its skill-type table lower down the page, with its level and “Very Opposed”.\n**Tradeoffs**\n\n- It reuses the\n`Ability.interested` scope but doesn’t share\n`Person#public_skills` itself.\n**Suggested title:** `Hide opposed skills from Highlighted abilities`\n\nNow write the description in your own words.\n\nHere’s what I wrote:\n\nThe spirit of this commit is to remove a skill from the “Highlighted\nabilities” card if the person is “Opposed” or “Very Opposed”. We\n*could* have just updated the card to show the person’s interest\nlevel, but that would defeat the purpose. Why imply an ability is\nhighlighted if the person is opposed to it? Also, that information\nlives on the card below, so removing it from the “Highlighted” section\ndoesn’t remove it completely.\n\nNotice that the reasoning came straight from my `/understand` session.\n\nWhat’s nice about this approach is that the LLM can help proof and verify your message, and take care of cumbersome things like providing screenshots, examples, or linking to GitHub issues.\n\n## \n    [A few more heuristics](#a-few-more-heuristics)\n  \n\nFinally, I want to share some heuristics I follow that have helped.\n\n### \n    [Make the commit as small as possible](#make-the-commit-as-small-as-possible)\n  \n\nMy [`/implement-with-tdd`](https://github.com/stevepolitodesign/skills#implement-with-tdd) skill caps a change at around 200\nlines of code. Keeping the LLM constrained means it’s easier for the LLM\n(or a human) to review the work. Plus, the feedback loop is faster, and\nit uses fewer tokens. It’s the same reason we [work\niteratively](https://thoughtbot.com/blog/working-iteratively).\n\n### \n    [Audit your harness](#audit-your-harness)\n  \n\nIf the LLM did something you didn’t like, correct it. You can improve or\ncreate a skill, adjust your `CLAUDE.md`, or [add a linter](https://thoughtbot.com/blog/enforcing-your-ruby-style-guide-on-ai-generated-code).\n\nAdditionally, consider limiting the number of skills you use. I find\nthat less is more, since they can sometimes conflict. If you’re using\nClaude Code, the [`/doctor`](https://code.claude.com/docs/en/commands) skill can find unused skills and\nwhat they cost in context.\n\n## \n    [Wrapping up](#wrapping-up)\n  \n\nThe LLM wrote one line of code. Most of my time went into understanding the problem, slicing it, and verifying the fix, and I think that’s where it belongs.\n\nNone of this is new. Before LLMs, we still planned before we started coding, we still used TDD, and we still reviewed our colleagues’ PRs without the full context. What’s changed is that an LLM can accelerate each of those steps. What hasn’t changed is that I’m still responsible for what ships.", "url": "https://wpnews.pro/news/a-pragmatic-guide-to-ai-assisted-development", "canonical_source": "https://feed.thoughtbot.com/link/24077/17500399/a-pragmatic-guide-to-ai-assisted-development", "published_at": "2026-10-19 00:00:00+00:00", "updated_at": "2026-10-19 00:49:12.472021+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "developer-tools", "artificial-intelligence"], "entities": ["Claude Code", "Claude desktop app", "Opus 5.5", "Hub", "thoughtbot", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/a-pragmatic-guide-to-ai-assisted-development", "markdown": "https://wpnews.pro/news/a-pragmatic-guide-to-ai-assisted-development.md", "text": "https://wpnews.pro/news/a-pragmatic-guide-to-ai-assisted-development.txt", "jsonld": "https://wpnews.pro/news/a-pragmatic-guide-to-ai-assisted-development.jsonld"}}