<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abdoulaye BA</title>
    <description>The latest articles on DEV Community by Abdoulaye BA (@asin).</description>
    <link>https://dev.to/asin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4219111%2Ffec5f6d9-b584-48b2-ba3a-a3f60a125730.png</url>
      <title>DEV Community: Abdoulaye BA</title>
      <link>https://dev.to/asin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asin"/>
    <language>en</language>
    <item>
      <title>Generative AI and coding agents: understanding, choosing and working well with an AI agent</title>
      <dc:creator>Abdoulaye BA</dc:creator>
      <pubDate>Sat, 31 Oct 2026 16:14:41 +0000</pubDate>
      <link>https://dev.to/asin/generative-ai-and-coding-agents-understanding-choosing-and-working-well-with-an-ai-agent-1mfm</link>
      <guid>https://dev.to/asin/generative-ai-and-coding-agents-understanding-choosing-and-working-well-with-an-ai-agent-1mfm</guid>
      <description>&lt;p&gt;&lt;strong&gt;In just a few years, artificial intelligence has moved from the lab to our pockets, and then into our code editors.&lt;/strong&gt; It writes, summarizes, translates, explains, and now it &lt;em&gt;acts&lt;/em&gt;: it reads a project, edits files, runs tests and proposes fixes. For a developer, an architect or a CIO, the question is no longer whether to pay attention, but &lt;strong&gt;how to adopt it with method, efficiency and security&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article offers a complete journey, from the general to the concrete: what AI really is, how a language model works, what sets an assistant apart from an &lt;strong&gt;agent&lt;/strong&gt;, how to choose and steer a coding agent, how to write a good prompt, manage memory, configure permissions and protect data. The examples are based on &lt;strong&gt;Claude Code&lt;/strong&gt;, Anthropic's coding agent, but the principles apply to most tools.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How should you read this article?&lt;/strong&gt; The first sections lay the foundations and are accessible to everyone. The next ones describe agents and their ecosystem, then a practical guide for technical teams. The second half is a &lt;strong&gt;complete workshop&lt;/strong&gt;: the &lt;em&gt;LinkLite&lt;/em&gt; mini-project, built from A to Z with Claude Code, all the way to version 1.0.0. A glossary closes the article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;In this article&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understanding artificial intelligence&lt;/li&gt;
&lt;li&gt;The major families: Machine Learning, Deep Learning, NLP, LLMs and generative AI&lt;/li&gt;
&lt;li&gt;AI today: a market upheaval&lt;/li&gt;
&lt;li&gt;AI as an everyday assistant&lt;/li&gt;
&lt;li&gt;From assistant to agent: AI in IT and the information system&lt;/li&gt;
&lt;li&gt;What is an AI agent?&lt;/li&gt;
&lt;li&gt;Agents, sub-agents, skills, MCP, hooks and commands&lt;/li&gt;
&lt;li&gt;Choosing your AI agent&lt;/li&gt;
&lt;li&gt;AI-driven development&lt;/li&gt;
&lt;li&gt;Prompt engineering: phrasing your request well&lt;/li&gt;
&lt;li&gt;AI memory: why and how to remember&lt;/li&gt;
&lt;li&gt;Useful commands: the Claude Code example&lt;/li&gt;
&lt;li&gt;The .claude folder: local, user, organization&lt;/li&gt;
&lt;li&gt;Permissions and data security&lt;/li&gt;
&lt;li&gt;Best practices for agent-driven coding&lt;/li&gt;
&lt;li&gt;Hands-on workshop: LinkLite, a mini-project from start to finish with Claude Code&lt;/li&gt;
&lt;li&gt;Laying the groundwork: memory, permissions and the Context7 MCP server&lt;/li&gt;
&lt;li&gt;The workshop prompts: from scoping to delivery&lt;/li&gt;
&lt;li&gt;TDD with an agent: red, green, refactor&lt;/li&gt;
&lt;li&gt;Designing the interface with the frontend-design skill&lt;/li&gt;
&lt;li&gt;Building your own agents and skills&lt;/li&gt;
&lt;li&gt;Saving tokens and holding the scope&lt;/li&gt;
&lt;li&gt;PostgreSQL and Docker: a deploy-ready application&lt;/li&gt;
&lt;li&gt;Git, code quality and shipping v1.0.0&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;Glossary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding artificial intelligence
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A simple definition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Artificial intelligence&lt;/strong&gt; refers to the set of techniques that enable a machine to perform tasks we usually associate with human intelligence: recognizing an image, understanding a sentence, translating a text, making a decision, solving a problem or generating content.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: a traditional program is a &lt;strong&gt;cooking recipe&lt;/strong&gt; written line by line by a human. An AI is an &lt;strong&gt;apprentice cook&lt;/strong&gt; who is shown thousands of successful dishes until they work out the rules on their own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This difference is fundamental. In traditional programming, the developer writes the rules. In machine learning, you provide &lt;strong&gt;examples&lt;/strong&gt; and the machine &lt;strong&gt;learns the rules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff2okoutwt1swxhx3ffgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff2okoutwt1swxhx3ffgf.png" alt="Traditional programming versus machine learning: in the second case, the rules are what gets produced." width="800" height="1374"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1. Traditional programming versus machine learning: in the second case, the rules are what gets produced.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A brief history
&lt;/h3&gt;

&lt;p&gt;AI was not born with conversational assistants. It went through several waves of enthusiasm and disillusionment (the “AI winters”) before the current acceleration, made possible by three ingredients coming together: &lt;strong&gt;massive data&lt;/strong&gt;, &lt;strong&gt;abundant computing power&lt;/strong&gt; (GPUs) and &lt;strong&gt;new architectures&lt;/strong&gt; of neural networks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9zmxs46gm4dtsgpf5t5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9zmxs46gm4dtsgpf5t5x.png" alt="The major milestones of AI, from the first ideas to autonomous agents." width="796" height="69"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2. The major milestones of AI, from the first ideas to autonomous agents.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Narrow AI, general AI
&lt;/h3&gt;

&lt;p&gt;Every AI in production today is so-called &lt;strong&gt;“narrow”&lt;/strong&gt; or specialized AI: it excels in a specific domain (language, images, recommendations…) without consciousness or understanding of the world in the human sense. &lt;strong&gt;General AI&lt;/strong&gt; (able to reason about any subject the way a human does) remains a research goal and a subject of debate. Keeping this distinction in mind avoids two symmetrical mistakes: underestimating today's tools, or crediting them with capabilities they do not have.&lt;/p&gt;
&lt;h2&gt;
  
  
  The major families: Machine Learning, Deep Learning, NLP, LLMs and generative AI
&lt;/h2&gt;

&lt;p&gt;These terms overlap and are often wrongly used as synonyms. The best picture is a set of &lt;strong&gt;Russian dolls&lt;/strong&gt;: each notion is a subset of the previous one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpfyfgt3itvzjm8k7i9wn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpfyfgt3itvzjm8k7i9wn.png" alt="The Russian dolls of AI: an LLM is a generative Deep Learning model applied to language (NLP)." width="720" height="1272"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3. The Russian dolls of AI: an LLM is a generative Deep Learning model applied to language (NLP).&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Notion&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Concrete example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Machine Learning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Algorithms that learn patterns from data.&lt;/td&gt;
&lt;td&gt;Bank fraud detection, credit scoring, sales forecasting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deep Learning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Machine Learning based on neural networks with many layers.&lt;/td&gt;
&lt;td&gt;Medical image recognition, speech recognition.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NLP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Natural language processing (text and speech).&lt;/td&gt;
&lt;td&gt;Translation, sentiment analysis, extracting information from a résumé.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generative AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Models able to produce new content: text, images, audio, code.&lt;/td&gt;
&lt;td&gt;Generating an illustration, a summary, a draft letter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Large language model trained on huge corpora of text and code.&lt;/td&gt;
&lt;td&gt;Conversational assistants, coding agents.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  How does an LLM work?
&lt;/h3&gt;

&lt;p&gt;An LLM does not “look up” an answer in a database: it &lt;strong&gt;predicts, word after word&lt;/strong&gt; (more precisely, &lt;strong&gt;token after token&lt;/strong&gt;), the most plausible continuation of a text. A token is a fragment of a word: “development” may be split into several tokens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: it is your phone's autocomplete, but trained on a gigantic library and able to take entire pages of context into account. It does not recite: it &lt;strong&gt;composes&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx0zpgdun7ti1jhkkplmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx0zpgdun7ti1jhkkplmk.png" alt="Generation is a loop: each token produced is fed back into the context to predict the next one." width="799" height="105"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4. Generation is a loop: each token produced is fed back into the context to predict the next one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three notions are enough to understand most of an LLM's behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The context window&lt;/strong&gt;: the amount of text the model “sees” at any given moment (your request, the conversation history, the files it has read). Beyond that, information is no longer taken into account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The knowledge cutoff date&lt;/strong&gt;: the model only knows what was in its training data. Hence the value of feeding it up-to-date documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature&lt;/strong&gt;: a setting that controls the amount of randomness. Low, responses are stable and predictable; high, they are more creative.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Hallucinations.&lt;/strong&gt; Because it produces the most plausible text, an LLM can confidently state something false: an API function that does not exist, an invented reference, a wrong figure. The answer is not to distrust it wholesale, but to &lt;strong&gt;verify&lt;/strong&gt;: tests, official documentation, review.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  AI today: a market upheaval
&lt;/h2&gt;

&lt;p&gt;The arrival of mainstream conversational assistants triggered adoption at an unprecedented pace. Within a few months, millions of people had built AI into their daily work, and companies had to respond on several fronts at once.&lt;/p&gt;
&lt;h3&gt;
  
  
  What actually changes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Productivity&lt;/strong&gt;: writing, summarizing, document analysis and code production are all accelerated, sometimes dramatically on repetitive tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jobs&lt;/strong&gt;: few jobs disappear overnight, but many &lt;strong&gt;are transformed&lt;/strong&gt;. Value shifts from execution toward design, verification and decision-making.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sought-after skills&lt;/strong&gt;: knowing how to express a need, evaluate a result, understand the limits of the tools and use them safely is becoming as important as technical mastery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recruitment&lt;/strong&gt;: job postings increasingly require proficiency with AI tools, and applications themselves are often screened and analyzed automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulation&lt;/strong&gt;: in Europe, the AI Act is gradually regulating uses according to their level of risk, alongside the GDPR for personal data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F54362b2y1th23z0hhqv2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F54362b2y1th23z0hhqv2.png" alt="The shift in value: from execution toward expressing needs, steering and deciding." width="800" height="420"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 5. The shift in value: from execution toward expressing needs, steering and deciding.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One phrase sums up the situation well: &lt;strong&gt;AI will not replace professionals, but a professional who masters AI will probably replace one who ignores it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  AI as an everyday assistant
&lt;/h2&gt;

&lt;p&gt;Even before talking about code, generative AI is first and foremost a &lt;strong&gt;versatile assistant&lt;/strong&gt;. Used well, it saves a considerable amount of time on tasks everyone faces.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Example request&lt;/th&gt;
&lt;th&gt;Watch out for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write&lt;/td&gt;
&lt;td&gt;“Write a friendly follow-up email about an unpaid invoice.”&lt;/td&gt;
&lt;td&gt;Check the tone and the facts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Summarize&lt;/td&gt;
&lt;td&gt;“Summarize this 12-page meeting report into 5 action items.”&lt;/td&gt;
&lt;td&gt;Make sure no key point is missing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Translate&lt;/td&gt;
&lt;td&gt;“Translate this paragraph into professional English.”&lt;/td&gt;
&lt;td&gt;Watch the domain-specific vocabulary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain&lt;/td&gt;
&lt;td&gt;“Explain the principles of the GDPR to me as if I were a beginner.”&lt;/td&gt;
&lt;td&gt;Cross-check with an official source.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Organize&lt;/td&gt;
&lt;td&gt;“Propose a 4-week training plan.”&lt;/td&gt;
&lt;td&gt;Adapt it to the real context.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analyze&lt;/td&gt;
&lt;td&gt;“What are the risks in this service contract?”&lt;/td&gt;
&lt;td&gt;It does not replace a lawyer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: the AI assistant is a &lt;strong&gt;very fast, well-read colleague available at any hour&lt;/strong&gt;, but one who has just joined the company. They know nothing about your context, your clients or your internal rules, unless you explain them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golden rule:&lt;/strong&gt; never paste personal data, secrets (passwords, API keys) or confidential company information into a consumer assistant, unless your organization has approved the tool for that use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  From assistant to agent: AI in IT and the information system
&lt;/h2&gt;

&lt;p&gt;In software development, AI has progressed in three stages, each giving the machine a little more autonomy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Chatbot&lt;/th&gt;
&lt;th&gt;Copilot (autocomplete)&lt;/th&gt;
&lt;th&gt;Coding agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Role&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Answers questions&lt;/td&gt;
&lt;td&gt;Suggests the next lines of code in the editor&lt;/td&gt;
&lt;td&gt;Carries out a task end to end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Whatever you paste&lt;/td&gt;
&lt;td&gt;The open file&lt;/td&gt;
&lt;td&gt;The whole project, the tools, the terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Actions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Inserts text&lt;/td&gt;
&lt;td&gt;Reads, writes, runs, tests, fixes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Autonomy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High, bounded by permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analogy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A manual that answers back&lt;/td&gt;
&lt;td&gt;A desk neighbor whispering hints&lt;/td&gt;
&lt;td&gt;A junior developer you delegate to&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The coding agent fits into the entire software lifecycle: understanding existing code, design, implementation, testing, documentation, code review, migration, and even operations (log analysis, automation scripts). Within an information system (IS), it becomes a lever for modernizing legacy applications, documenting an application portfolio or industrializing repetitive tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr0qd8hohw1aikk55swy0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr0qd8hohw1aikk55swy0.png" alt="The agent can step in at every stage of the software lifecycle, always under the team's control." width="800" height="83"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 6. The agent can step in at every stage of the software lifecycle, always under the team's control.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What is an AI agent?
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Definition
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;AI agent&lt;/strong&gt; is a system that combines a language model with the ability to &lt;strong&gt;act&lt;/strong&gt; on its environment through &lt;strong&gt;tools&lt;/strong&gt;, and that chains these actions &lt;strong&gt;in a loop&lt;/strong&gt; until it reaches a goal. Where an assistant answers once, an agent &lt;strong&gt;observes, decides, acts, verifies&lt;/strong&gt;, then starts again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: the assistant is an &lt;strong&gt;advisor&lt;/strong&gt; who tells you what to do. The agent is a &lt;strong&gt;craftsman&lt;/strong&gt; who picks up the tools, does the work, checks the result and reports back to you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  The components of an agent
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flwlpkq8cb4a65aj6zkfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flwlpkq8cb4a65aj6zkfx.png" alt="Anatomy of an agent: a model, a memory, an action loop and tools, governed by permissions." width="799" height="521"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 7. Anatomy of an agent: a model, a memory, an action loop and tools, governed by permissions.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The agentic loop
&lt;/h3&gt;

&lt;p&gt;At the heart of an agent is a simple loop. Take a concrete request: &lt;em&gt;“The sign-up test is failing, fix it.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Far933g2hp9ummmpr8rqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Far933g2hp9ummmpr8rqw.png" alt="An agentic loop in action: the agent observes, acts, verifies, then reports back." width="800" height="532"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 8. An agentic loop in action: the agent observes, acts, verifies, then reports back.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; an agent's value does not come from the model alone, but from its ability to &lt;strong&gt;check its own work&lt;/strong&gt; (tests, compilation, linting). An agent that can run the tests fixes its own mistakes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Agents, sub-agents, skills, MCP, hooks and commands
&lt;/h2&gt;

&lt;p&gt;Around the main agent revolves an ecosystem of building blocks with clearly distinct roles. To understand them, picture a &lt;strong&gt;company&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Building block&lt;/th&gt;
&lt;th&gt;Analogy&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The project manager&lt;/td&gt;
&lt;td&gt;Understands the goal, plans, acts and coordinates.&lt;/td&gt;
&lt;td&gt;Always: it is your main point of contact.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sub-agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An expert you hand a case file to&lt;/td&gt;
&lt;td&gt;A specialized agent with its own context, tools and instructions; it returns a concise result.&lt;/td&gt;
&lt;td&gt;Focused, large-scale tasks: security review, exploring a big codebase, writing tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A procedure sheet&lt;/td&gt;
&lt;td&gt;Packaged know-how (instructions, scripts, templates) loaded only when it is useful.&lt;/td&gt;
&lt;td&gt;Recurring procedures: generating a report, following a style guide, deploying.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The universal USB-C port&lt;/td&gt;
&lt;td&gt;A standard protocol for connecting the agent to external tools and data.&lt;/td&gt;
&lt;td&gt;Accessing Jira, GitLab, a database, documentation, a browser.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The automatic security check at the building entrance&lt;/td&gt;
&lt;td&gt;A script triggered automatically at a specific moment (before or after an action).&lt;/td&gt;
&lt;td&gt;Formatting after every edit, blocking a dangerous command.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A desktop shortcut&lt;/td&gt;
&lt;td&gt;A pre-recorded instruction launched with &lt;code&gt;/nom&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Repeated actions: code review, creating a component, writing a commit message.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb31cbev2qyfjg1hq2krk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb31cbev2qyfjg1hq2krk.png" alt="The ecosystem of a coding agent: delegation to sub-agents, know-how in skills, MCP connections and checks through hooks." width="800" height="835"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 9. The ecosystem of a coding agent: delegation to sub-agents, know-how in skills, MCP connections and checks through hooks.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Sub-agent or skill: the key difference
&lt;/h3&gt;

&lt;p&gt;The two are often confused. A &lt;strong&gt;sub-agent&lt;/strong&gt; is a &lt;em&gt;someone&lt;/em&gt;: it works in a &lt;strong&gt;separate context&lt;/strong&gt;, which keeps the main conversation uncluttered, then returns a summary. A &lt;strong&gt;skill&lt;/strong&gt; is a piece of &lt;em&gt;know-how&lt;/em&gt;: it does not work on its own; it enriches the agent that loads it, at the moment the agent needs it.&lt;/p&gt;

&lt;p&gt;Example of a sub-agent, defined in a simple Markdown file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-reviewer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Analyzes changes to detect security vulnerabilities (OWASP). Use after any change touching authentication or data.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are an application security expert. For each change:
&lt;span class="p"&gt;1.&lt;/span&gt; Spot injections, data leaks and missing access controls.
&lt;span class="p"&gt;2.&lt;/span&gt; Rank each issue by severity (critical, high, medium).
&lt;span class="p"&gt;3.&lt;/span&gt; Propose a precise fix, without editing the files yourself.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a skill, stored in a folder alongside its resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release-notes&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Writes release notes from conventional commits. Use when preparing a release.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="p"&gt;1.&lt;/span&gt; List the commits since the last tag (git log).
&lt;span class="p"&gt;2.&lt;/span&gt; Group them by type: feat, fix, perf, docs.
&lt;span class="p"&gt;3.&lt;/span&gt; Write clear, user-oriented notes in English.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MCP: the universal port
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Model Context Protocol&lt;/strong&gt; (MCP) is an open standard that lets any compatible agent connect to any compatible tool. Without it, every agent would need a dedicated integration for every tool; with it, a connector written once works everywhere, exactly like a USB-C cable that charges a phone, a laptop or headphones alike.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gitlab"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-gitlab"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"GITLAB_PERSONAL_ACCESS_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${GITLAB_TOKEN}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;An MCP server is third-party code that accesses your tools and data. Only install &lt;strong&gt;trusted&lt;/strong&gt; servers, with tokens that have &lt;strong&gt;minimal&lt;/strong&gt; permissions, and never commit a token in plain text: use environment variables.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Choosing your AI agent
&lt;/h2&gt;

&lt;p&gt;Today there are several families of tools: &lt;strong&gt;editor-integrated assistants&lt;/strong&gt; (IDE extensions), &lt;strong&gt;command-line agents&lt;/strong&gt; that work in the terminal, and &lt;strong&gt;cloud agents&lt;/strong&gt; that handle tasks in the background on a repository. The right choice depends less on hype than on objective criteria.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Questions to ask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model quality&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does it reason well about complex code? Does it follow instructions? How does it perform on your languages?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does it work in your IDE, your terminal, your CI/CD? Does it support MCP?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can it understand an entire project? Does it maintain a project memory?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Are permissions fine-grained? Can you review every change before it is applied?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Where is the data processed? Is it used for training? What contractual guarantees are there?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sub-agents, skills, hooks, custom commands?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Subscription or pay-as-you-go? Is the cost predictable for the team?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is it compatible with the company’s security policy, the GDPR, the AI Act?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; evaluate two or three tools on the &lt;strong&gt;same real task&lt;/strong&gt; from your project (fixing a bug, adding a tested feature) rather than on demos. Measure the result, the review time required and the quality of the tests produced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  AI-driven development
&lt;/h2&gt;

&lt;p&gt;Working with an agent is not about telling it “build the app”. The teams that get the best results follow a disciplined cycle in which humans stay in charge of the decisions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpno1p7z06q2vewy3bdb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpno1p7z06q2vewy3bdb7.png" alt="The AI-driven development cycle: explore, plan, implement, verify, review, commit." width="796" height="66"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 10. The AI-driven development cycle: explore, plan, implement, verify, review, commit.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A concrete example: adding a filter to a list
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Explore&lt;/strong&gt;: “Read the orders module and explain how the list is loaded. Don’t change anything.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt;: “Propose a plan to add a server-side status filter, listing the files affected and the tests you intend to write.” Then you review and adjust the plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement&lt;/strong&gt;: “Apply step 1 of the plan and write the tests first.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify&lt;/strong&gt;: the agent runs the tests and keeps fixing until they are green.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review&lt;/strong&gt;: you read the diff as you would a colleague’s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit&lt;/strong&gt;: “Create a commit in Conventional Commits format.”&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TDD is the agent’s best ally.&lt;/strong&gt; Tests written first give the agent a verifiable goal: it knows objectively when it is done, and you know the result works.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Prompt engineering: phrasing your request well
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prompt engineering&lt;/strong&gt; is the art of phrasing a request to get the best result from a model. With an agent, the stakes are even higher: a vague prompt produces vague work, across many files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: a prompt is the &lt;strong&gt;brief you give a contractor&lt;/strong&gt;. A good contractor with a bad brief delivers something well made… that doesn’t match what you need.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Anatomy of a good prompt
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fihg5raymwsn46z418swe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fihg5raymwsn46z418swe.png" alt="The five ingredients of a good prompt." width="800" height="93"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 11. The five ingredients of a good prompt.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Bad prompt, good prompt
&lt;/h3&gt;

&lt;p&gt;A vague prompt leaves the agent guessing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add pagination to the user list.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good prompt frames the work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context: Next.js application, module src/features/users.
The user list loads every record, which slows the page down.

Goal: add server-side pagination (20 items per page).

Constraints:
- follow the existing architecture (repository &amp;gt; service &amp;gt; action);
- do not change the database schema;
- no new dependencies.

Success criteria:
- service unit tests for the first page, an intermediate page and the last page;
- all tests and the lint pass.

Start by proposing a plan, without modifying any file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scope: one task at a time
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope&lt;/strong&gt; is the number one success factor. A request that mixes a refactor, a new feature and bug fixes produces a result that is hard to review and test. Break it down: &lt;strong&gt;one request = one verifiable goal&lt;/strong&gt;, ideally one commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where should you write your prompt?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;What for&lt;/th&gt;
&lt;th&gt;Lifetime&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The conversation&lt;/td&gt;
&lt;td&gt;The request at hand&lt;/td&gt;
&lt;td&gt;The session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The &lt;code&gt;CLAUDE.md&lt;/code&gt; file&lt;/td&gt;
&lt;td&gt;The project’s permanent rules (conventions, commands, architecture)&lt;/td&gt;
&lt;td&gt;As long as the project exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A ticket or a specification&lt;/td&gt;
&lt;td&gt;A detailed feature, shared with the team&lt;/td&gt;
&lt;td&gt;For the duration of the development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A custom command&lt;/td&gt;
&lt;td&gt;A request repeated identically&lt;/td&gt;
&lt;td&gt;Reusable at will&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Techniques that make a difference
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ask for a plan first&lt;/strong&gt;, and approve it before any change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give an example&lt;/strong&gt; of the expected result (the style of an existing test, a reference component).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Point to the relevant files&lt;/strong&gt; instead of letting the agent search for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invite the agent to ask questions&lt;/strong&gt; when information is missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Require verification&lt;/strong&gt;: “run the tests and show me the result”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct early&lt;/strong&gt;: interrupt as soon as a direction looks wrong, rather than letting the agent dig itself deeper.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AI memory: why and how to remember
&lt;/h2&gt;

&lt;p&gt;An LLM has &lt;strong&gt;no memory between two conversations&lt;/strong&gt;. Every new session starts from scratch. Without memory, you would have to re-explain the project conventions, the test commands, the architecture decisions… every single time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: the context window is &lt;strong&gt;working memory&lt;/strong&gt;: what you keep in mind during a meeting. Persistent memory is the &lt;strong&gt;logbook&lt;/strong&gt; you reread when you arrive in the morning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpsc7pnrrxpbxpmkn5sa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpsc7pnrrxpbxpmkn5sa.png" alt="Two levels of memory: the persistent logbook and the session’s working memory." width="800" height="1055"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 12. Two levels of memory: the persistent logbook and the session’s working memory.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What should you store in memory?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commands&lt;/strong&gt;: how to run the tests, the build, the lint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conventions&lt;/strong&gt;: architecture, naming, code style, commit format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decisions&lt;/strong&gt;: technical choices and the reasons behind them (“we don’t use an ORM here because…”).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pitfalls&lt;/strong&gt;: what has caused problems before and the right way to do it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example of a concise, effective &lt;code&gt;CLAUDE.md&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Portfolio project: rules for the agent&lt;/span&gt;

&lt;span class="gu"&gt;## Commands&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Install: bun install
&lt;span class="p"&gt;-&lt;/span&gt; Tests: bun run test (minimum coverage 95%)
&lt;span class="p"&gt;-&lt;/span&gt; Checks: bun run lint &amp;amp;&amp;amp; bun run typecheck

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Feature First: each feature in src/features/&lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;/
&lt;span class="p"&gt;-&lt;/span&gt; Mutations only through Server Actions, validated with Zod

&lt;span class="gu"&gt;## Rules&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Never push to main: a branch and a merge request
&lt;span class="p"&gt;-&lt;/span&gt; Commits in English, Conventional Commits format
&lt;span class="p"&gt;-&lt;/span&gt; Never read or modify .env files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A good memory file is &lt;strong&gt;short, factual and up to date&lt;/strong&gt;. Ten precise rules beat a hundred generic lines: everything in memory takes up room in the context at every session.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Useful commands: the Claude Code example
&lt;/h2&gt;

&lt;p&gt;In Claude Code, commands starting with &lt;code&gt;/&lt;/code&gt; control the session itself. Here are the most useful ones day to day (the full list is available with &lt;code&gt;/help&lt;/code&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyzes the project and generates a first &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;The first time you open a project.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opens and edits the memory files.&lt;/td&gt;
&lt;td&gt;To add a rule or a decision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/clear&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clears the conversation context.&lt;/td&gt;
&lt;td&gt;When switching tasks, to start fresh.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/compact&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Summarizes the conversation to free up context.&lt;/td&gt;
&lt;td&gt;During a long session on the same task.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switches models.&lt;/td&gt;
&lt;td&gt;A more powerful model for a complex task.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/permissions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Views and sets permissions.&lt;/td&gt;
&lt;td&gt;To allow a safe command or deny one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/agents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates and manages sub-agents.&lt;/td&gt;
&lt;td&gt;To delegate a specialty.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows the connected MCP servers.&lt;/td&gt;
&lt;td&gt;To check a connection to an external tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/hooks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Configures hooks.&lt;/td&gt;
&lt;td&gt;To automate a check.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/resume&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resumes a previous conversation.&lt;/td&gt;
&lt;td&gt;To continue an interrupted task.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opens the settings.&lt;/td&gt;
&lt;td&gt;To customize the tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lists all available commands.&lt;/td&gt;
&lt;td&gt;When in doubt.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Shortcuts and custom commands
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@&lt;/code&gt; followed by a path: references a specific file in the request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!&lt;/code&gt; at the start of a line: runs a shell command directly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Esc&lt;/code&gt;: interrupts the agent so you can redirect it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Shift + Tab&lt;/code&gt;: cycles through modes (including &lt;strong&gt;plan mode&lt;/strong&gt;, where the agent proposes without modifying anything).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A custom command is just a Markdown file placed in &lt;code&gt;.claude/commands/&lt;/code&gt;. The file &lt;code&gt;.claude/commands/review.md&lt;/code&gt; becomes the &lt;code&gt;/review&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Review the uncommitted changes on the current branch.
For each modified file, check:
&lt;span class="p"&gt;-&lt;/span&gt; compliance with the architecture and conventions in CLAUDE.md;
&lt;span class="p"&gt;-&lt;/span&gt; the presence of tests for any new logic;
&lt;span class="p"&gt;-&lt;/span&gt; the absence of any plain-text secret or personal data.
Return a list of comments sorted by priority.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The .claude folder: local, user, organization
&lt;/h2&gt;

&lt;p&gt;An agent’s configuration is spread across several levels, from the most personal to the most collective. In Claude Code, it lives mainly in folders named &lt;code&gt;.claude&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: the project’s &lt;code&gt;.claude&lt;/code&gt; folder is the &lt;strong&gt;onboarding binder&lt;/strong&gt; handed to every newcomer: house rules, procedures, useful contacts. The user folder is your &lt;strong&gt;personal toolkit&lt;/strong&gt;, which follows you from project to project.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mon-projet/
├── CLAUDE.md                  # project memory, shared (versioned)
├── .mcp.json                  # project MCP servers (versioned, no secrets)
└── .claude/
    ├── settings.json          # shared permissions and hooks (versioned)
    ├── settings.local.json    # personal settings (not versioned)
    ├── commands/              # custom commands: /review, /release…
    ├── agents/                # project sub-agents
    └── skills/                # project skills

~/.claude/                     # user level, specific to your machine
├── CLAUDE.md                  # personal preferences, across all projects
├── settings.json              # global personal settings
├── commands/                  # your personal commands
└── agents/                    # your personal sub-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Example content&lt;/th&gt;
&lt;th&gt;Versioned?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Organization&lt;/strong&gt; (managed settings)&lt;/td&gt;
&lt;td&gt;Every workstation in the company&lt;/td&gt;
&lt;td&gt;Global prohibitions, approved MCP servers&lt;/td&gt;
&lt;td&gt;Managed by IT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Shared project&lt;/strong&gt; (&lt;code&gt;.claude/&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The whole team on this project&lt;/td&gt;
&lt;td&gt;Conventions, test commands, common permissions, sub-agents&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Personal project&lt;/strong&gt; (&lt;code&gt;settings.local.json&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;You, on this project&lt;/td&gt;
&lt;td&gt;Permissions specific to your workstation&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;User&lt;/strong&gt; (&lt;code&gt;~/.claude/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;You, across all your projects&lt;/td&gt;
&lt;td&gt;Preferred language, response style, favorite tools&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Whatever is versioned is shared with the whole team, and sometimes beyond. &lt;strong&gt;No secrets&lt;/strong&gt; in &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;settings.json&lt;/code&gt; or &lt;code&gt;.mcp.json&lt;/code&gt;: use environment variables and keep &lt;code&gt;.env&lt;/code&gt; files out of the repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Permissions and data security
&lt;/h2&gt;

&lt;p&gt;An agent that can read files and run commands has real power over your machine and your code. So the question is not “should everything be allowed?” (the answer is clearly no) but “&lt;strong&gt;how do you allow only what is needed, at the right time?&lt;/strong&gt;”&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Analogy: you don’t hand a contractor the master key to the entire building. You give them a &lt;strong&gt;badge&lt;/strong&gt; that opens the rooms they need, for the duration of their assignment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The principle of least privilege
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rp5rtxfqopwufylgbuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rp5rtxfqopwufylgbuz.png" alt="Permission control: what is denied is blocked, what is allowed goes through, everything else requires confirmation." width="799" height="162"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 13. Permission control: what is denied is blocked, what is allowed goes through, everything else requires confirmation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In practice, you allow &lt;strong&gt;safe, frequent&lt;/strong&gt; actions up front (running tests, reading code), you explicitly &lt;strong&gt;deny&lt;/strong&gt; anything that touches secrets or irreversible operations, and you leave everything else subject to confirmation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(bun run test:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(bun run lint)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git status)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git diff:*)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(./secrets/**)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push --force:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(rm -rf:*)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Personal and sensitive data
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimize&lt;/strong&gt;: give the agent only the data the task requires. Fictitious test data is better than a production extract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate secrets&lt;/strong&gt;: &lt;code&gt;.env&lt;/code&gt; files denied for reading, tokens with limited rights, regular rotation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comply with the GDPR&lt;/strong&gt;: no real personal data (customers, employees, patients) in the exchanges without a legal basis and a tool approved by the company.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be wary of external content&lt;/strong&gt;: a web page, a ticket or a file can contain malicious instructions (“prompt injection”). A careful agent treats such content as data, never as orders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a trail&lt;/strong&gt;: review the diffs, work on branches, keep the Git history so you can roll back.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Advantage&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Allow everything&lt;/td&gt;
&lt;td&gt;No interruptions&lt;/td&gt;
&lt;td&gt;Leaked secrets, destructive commands, unintended actions&lt;/td&gt;
&lt;td&gt;❌ Avoid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confirm everything&lt;/td&gt;
&lt;td&gt;Total control&lt;/td&gt;
&lt;td&gt;Fatigue: you end up approving everything without reading&lt;/td&gt;
&lt;td&gt;⚠️ Counterproductive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targeted, progressive permissions&lt;/td&gt;
&lt;td&gt;Smooth on the safe, controlled on the sensitive&lt;/td&gt;
&lt;td&gt;Requires a little configuration&lt;/td&gt;
&lt;td&gt;✅ Recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Best practices for agent-driven coding
&lt;/h2&gt;

&lt;p&gt;A checklist to keep at hand for every task you hand to an agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task is scoped to one clear, verifiable goal.&lt;/li&gt;
&lt;li&gt;The agent works on a dedicated branch, never directly on the main branch.&lt;/li&gt;
&lt;li&gt;A plan was proposed and approved before any changes.&lt;/li&gt;
&lt;li&gt;Tests describe the expected behavior, ideally written first.&lt;/li&gt;
&lt;li&gt;Tests, lint and build pass before the task is considered done.&lt;/li&gt;
&lt;li&gt;A human has reviewed the diff, as they would a colleague’s.&lt;/li&gt;
&lt;li&gt;No secrets or personal data appear in the code or the commits.&lt;/li&gt;
&lt;li&gt;The CLAUDE.md file has been updated if a new rule has emerged.&lt;/li&gt;
&lt;li&gt;The commit is atomic, with an explicit message.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The most common pitfalls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blind trust&lt;/strong&gt;: accepting code that “looks right” without testing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Giant requests&lt;/strong&gt;: a single prompt for an entire feature, impossible to review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polluted context&lt;/strong&gt;: an overly long session mixing several topics; better to &lt;code&gt;/clear&lt;/code&gt; between tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invisible debt&lt;/strong&gt;: generated code that works but that nobody on the team understands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill loss&lt;/strong&gt;: delegating without understanding. The agent should remain an accelerator, not a crutch.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The rule that sums it all up:&lt;/strong&gt; the agent proposes, the human decides. You remain responsible for every line that goes to production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hands-on workshop: LinkLite, a mini-project from start to finish with Claude Code
&lt;/h2&gt;

&lt;p&gt;Theory is not enough: to &lt;strong&gt;master&lt;/strong&gt; a coding agent, you need to have watched it work on a real project, with its successes, its mistakes and its guardrails. This part walks through a complete mini-project built with &lt;strong&gt;Claude Code&lt;/strong&gt;: from choosing the subject all the way to a &lt;strong&gt;tagged version 1.0.0&lt;/strong&gt;, tested, audited and ready to deploy. Each step picks up a concept covered earlier (prompts, memory, sub-agents, skills, MCP, permissions) and puts it into practice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What you will build:&lt;/strong&gt; &lt;em&gt;LinkLite&lt;/em&gt;, a URL shortener. You drop in a long address and leave with a short link (a “ticket”) and a click counter.&lt;br&gt;&lt;br&gt;
Full source code of the mini-project: &lt;a href="https://github.com/abdoulayeba29/linklite" rel="noopener noreferrer"&gt;https://github.com/abdoulayeba29/linklite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you will practice:&lt;/strong&gt; prompt engineering, project memory (&lt;code&gt;CLAUDE.md&lt;/code&gt;), permissions, the &lt;strong&gt;Context7&lt;/strong&gt; MCP server, the &lt;strong&gt;frontend-design&lt;/strong&gt; skill, custom agents and skills, TDD, integration and end-to-end tests, PostgreSQL, Docker, Git and delivery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why this subject?
&lt;/h3&gt;

&lt;p&gt;A good workshop subject is &lt;strong&gt;small but complete&lt;/strong&gt;: it should touch every layer of a real application without overwhelming the reader. The URL shortener ticks every box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A clear business domain&lt;/strong&gt;: rules that are simple to state and to test (slug format, whether a URL is allowed or not).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real database&lt;/strong&gt;: uniqueness, constraints, and a click counter that must stay accurate even under concurrent access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An API and a user interface&lt;/strong&gt;: enough to practice design and end-to-end testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real security stakes&lt;/strong&gt;: a shortener can be used to disguise an internal address (SSRF) or for spam, which makes it ideal for putting an audit agent to the test.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The scope: what is in, what is out
&lt;/h3&gt;

&lt;p&gt;Setting the scope &lt;em&gt;before&lt;/em&gt; writing code is the best protection against drift, ours as well as the agent’s, which tends to “enrich” a vague request. The scope was written down in black and white, then carried into the first prompt:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;In v1.0.0&lt;/th&gt;
&lt;th&gt;Out of scope (deliberately)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shorten a public &lt;code&gt;http&lt;/code&gt;/&lt;code&gt;https&lt;/code&gt; URL&lt;/td&gt;
&lt;td&gt;User accounts and authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random or custom slug (&lt;code&gt;/mon-lien&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Link expiration, QR codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;302 redirect and accurate click counter&lt;/td&gt;
&lt;td&gt;Detailed statistics (country, browser…)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List of the 20 most recent links&lt;/td&gt;
&lt;td&gt;Preview of the target page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Light/dark theme, responsive, accessible&lt;/td&gt;
&lt;td&gt;Admin interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security: anti-SSRF, rate limiting, CSP&lt;/td&gt;
&lt;td&gt;Cloud deployment (v1 is “ready to deploy”)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Technical choices and the reasoning behind them
&lt;/h3&gt;

&lt;p&gt;Every choice is driven by the need, not by fashion. That is also what we ask of the agent: &lt;strong&gt;justify&lt;/strong&gt; its choices rather than pile up dependencies.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; (strict) on Node.js 22&lt;/td&gt;
&lt;td&gt;Strong typing: the agent and the compiler correct each other.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP server&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fastify 5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lightweight, fast, with built-in JSON Schema input validation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PostgreSQL 18&lt;/strong&gt; (SQL)&lt;/td&gt;
&lt;td&gt;Declarative constraints, transactions, atomic updates: exactly what a reliable counter requires.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data access&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pg&lt;/code&gt; driver + parameterized SQL&lt;/td&gt;
&lt;td&gt;Three queries: an ORM would be heavier than the problem (KISS).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Vitest&lt;/strong&gt; + &lt;strong&gt;Playwright&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Fast unit/integration tests; real user journeys in a real browser.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User interface&lt;/td&gt;
&lt;td&gt;Framework-free HTML/CSS/JS&lt;/td&gt;
&lt;td&gt;One page, no front-end build: the frontend-design skill takes care of the look and feel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Docker&lt;/strong&gt; + docker compose&lt;/td&gt;
&lt;td&gt;The same environment everywhere, database included.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The architecture: hexagonal, the lightweight version
&lt;/h3&gt;

&lt;p&gt;The code is organized in &lt;strong&gt;layers&lt;/strong&gt; whose dependencies all point toward the center. The domain (the business rules) knows nothing about Fastify or PostgreSQL: it describes what it needs as an interface (a &lt;strong&gt;port&lt;/strong&gt;), which the infrastructure implements through &lt;strong&gt;adapters&lt;/strong&gt;. The result: you can test all the business logic without a database, and switch databases without touching the business logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbe5j4cber7njqyo0ewu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbe5j4cber7njqyo0ewu.png" alt="Lightweight hexagonal architecture: the domain sits at the center, and adapters implement its ports." width="799" height="312"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Lightweight hexagonal architecture: the domain sits at the center, and adapters implement its ports.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/domain/link.ts: the port, i.e. what the business logic needs, without saying how&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LinkRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** Inserts the link; returns false when the slug already exists. */&lt;/span&gt;
  &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;findBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Atomically increments the click counter; returns the target or null if unknown. */&lt;/span&gt;
  &lt;span class="nf"&gt;registerClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;listRecent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Laying the groundwork: memory, permissions and the Context7 MCP server
&lt;/h2&gt;

&lt;p&gt;Before the first line of code, we give the agent what a new colleague would receive on day one: the &lt;strong&gt;house rules&lt;/strong&gt;, the &lt;strong&gt;authorized access&lt;/strong&gt; and &lt;strong&gt;up-to-date documentation&lt;/strong&gt;. Everything is versioned in the repository, and therefore shared by the whole team.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Concept from the article&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Commands, architecture, coding rules, Git, security&lt;/td&gt;
&lt;td&gt;Project memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.claude/settings.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allowed commands, forbidden reads and actions&lt;/td&gt;
&lt;td&gt;Permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.mcp.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Context7 MCP server&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.claude/agents/*.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;security-auditor&lt;/code&gt; and &lt;code&gt;test-writer&lt;/code&gt; sub-agents&lt;/td&gt;
&lt;td&gt;Sub-agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.claude/skills/release-notes/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Release know-how (notes, CHANGELOG, tag)&lt;/td&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.claude/commands/review.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/review&lt;/code&gt; command before merging&lt;/td&gt;
&lt;td&gt;Custom commands&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4yw6oydd7lxjts15biy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4yw6oydd7lxjts15biy.webp" alt="LinkLite's CLAUDE.md file open in the editor, with the .claude folder tree in the terminal" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;LinkLite's CLAUDE.md and the agent's configuration files (editor view: Monaco rendering, the VS Code engine, of the project's real files).&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Permissions: allow what is safe, forbid what is dangerous
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;settings.json&lt;/code&gt; file avoids two pitfalls: approving the same harmless command a hundred times, and letting a destructive command slip through out of fatigue. Tests, linting and reading the Git history are allowed; reading secrets, recursive deletion and &lt;em&gt;pushing&lt;/em&gt; to &lt;code&gt;main&lt;/code&gt; are forbidden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm test)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm run test:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm run check)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git status)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git diff:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git log:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"mcp__context7__resolve-library-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp__context7__query-docs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(./node_modules/**)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Read(./coverage/**)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push --force:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push origin main:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(rm -rf:*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(docker compose down -v:*)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What actually happened during the workshop:&lt;/strong&gt; the agent wanted to delete old screenshots with &lt;code&gt;rm -f docs/screenshots/*&lt;/code&gt;. Claude Code’s guardrail &lt;strong&gt;blocked the wildcard deletion&lt;/strong&gt;, and the agent cleanly worked around the need (the files were being rewritten anyway). That is exactly what permissions are for: making the costly mistake impossible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Context7: up-to-date documentation, right in the context
&lt;/h3&gt;

&lt;p&gt;A language model has a &lt;strong&gt;knowledge cutoff&lt;/strong&gt;: it knows libraries as they were when it was trained. But APIs move fast: a renamed option, a changed default, a major version that breaks everything. The classic result: plausible code… that does not compile, or worse, compiles but misbehaves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context7&lt;/strong&gt; is an MCP server that solves this problem: it indexes the official documentation and examples of thousands of libraries, &lt;strong&gt;version by version&lt;/strong&gt;, and serves them to the agent on demand. Instead of “remembering”, the agent &lt;strong&gt;looks it up&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Context7
&lt;/h3&gt;

&lt;p&gt;Two equivalent approaches, at project level (recommended, since it is shared with the team):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option 1: from the command line, project scope (writes .mcp.json)&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http &lt;span class="nt"&gt;--scope&lt;/span&gt; project context7 https://mcp.context7.com/mcp

&lt;span class="c"&gt;# Check the connection from a Claude Code session&lt;/span&gt;
/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Option&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;directly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.mcp.json,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;repository&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.context7.com/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Context7 exposes two tools: &lt;code&gt;resolve-library-id&lt;/code&gt; (find a library’s identifier, for example &lt;code&gt;/fastify/fastify&lt;/code&gt;) and &lt;code&gt;query-docs&lt;/code&gt; (ask a targeted question about that library). A free API key raises the quotas, but is not required to get started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futed7p3rxks96efyacvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futed7p3rxks96efyacvf.png" alt="The Context7 cycle: the agent resolves the library identifier, queries the documentation, then writes the code." width="800" height="435"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Context7 cycle: the agent resolves the library identifier, queries the documentation, then writes the code.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Using it day to day
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The habit:&lt;/strong&gt; add “&lt;em&gt;use context7&lt;/em&gt;” at the end of any prompt that involves a library. You can also put it in &lt;code&gt;CLAUDE.md&lt;/code&gt; so it happens every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precise questions:&lt;/strong&gt; “&lt;code&gt;webServer&lt;/code&gt; options in &lt;code&gt;playwright.config&lt;/code&gt;” uses far fewer tokens than “Playwright documentation”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-approved tools:&lt;/strong&gt; both Context7 tools are in the &lt;code&gt;allow&lt;/code&gt; list of &lt;code&gt;settings.json&lt;/code&gt;, so they do not ask for confirmation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What Context7 actually changed in LinkLite
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Question asked&lt;/th&gt;
&lt;th&gt;What the up-to-date docs prevented&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vitest 4&lt;/td&gt;
&lt;td&gt;v8 coverage and threshold configuration&lt;/td&gt;
&lt;td&gt;A configuration written for the API of the installed version (v4), not an earlier one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;webServer&lt;/code&gt;, mobile projects, dark theme&lt;/td&gt;
&lt;td&gt;A test server started by hand: Playwright starts the application and waits for it on its own.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@fastify/rate-limit&lt;/td&gt;
&lt;td&gt;Per-route limit, handling the 429 response&lt;/td&gt;
&lt;td&gt;A 429 turned into a 500 by the global error handler: the docs flagged it, and a test locks it in.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fastify 5&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;trustProxy&lt;/code&gt; behind a reverse proxy&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;The most telling case:&lt;/strong&gt; trust based on a “hop count” no longer works (it now fails closed to prevent IP spoofing). Code written “from memory” would have been wrong.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Context7 does not replace tests.&lt;/strong&gt; Documentation says what &lt;em&gt;should&lt;/em&gt; work; only tests prove what does. In LinkLite, every piece of information that came from Context7 was locked in by a test.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The workshop prompts: from scoping to delivery
&lt;/h2&gt;

&lt;p&gt;You do not steer an entire project with one giant prompt, but with a &lt;strong&gt;series of short, verifiable requests&lt;/strong&gt;, each ending with an acceptance criterion. They all follow the same template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTEXT     : where things stand, files involved (@src/domain/...)
GOAL        : a single observable outcome
CONSTRAINTS : CLAUDE.md rules, what NOT to do
VERIFICATION: the command that proves it is done (tests, lint, screenshot)
FORMAT      : plan first / diff / table…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The complete sequence
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Prompt (summary)&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Scoping&lt;/td&gt;
&lt;td&gt;“Here is the subject and the scope. Propose the architecture and the list of files, without writing code.”&lt;/td&gt;
&lt;td&gt;Plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Foundation&lt;/td&gt;
&lt;td&gt;“Initialize the project: strict TypeScript, Vitest, ESLint, Prettier, CLAUDE.md and settings.json.”&lt;/td&gt;
&lt;td&gt;Edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Domain (red)&lt;/td&gt;
&lt;td&gt;“With the test-writer agent, write the URL and slug validation tests. They must fail.”&lt;/td&gt;
&lt;td&gt;Sub-agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Domain (green)&lt;/td&gt;
&lt;td&gt;“Implement the minimum to make these tests pass, without modifying them.”&lt;/td&gt;
&lt;td&gt;Edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Use cases&lt;/td&gt;
&lt;td&gt;“Create LinkService behind the LinkRepository port; in-memory adapter for the tests.”&lt;/td&gt;
&lt;td&gt;Edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. API + database&lt;/td&gt;
&lt;td&gt;“Add the Fastify API and the PostgreSQL adapter. use context7”&lt;/td&gt;
&lt;td&gt;Edit + MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7. User interface&lt;/td&gt;
&lt;td&gt;“/frontend-design A single page for LinkLite… (see below)”&lt;/td&gt;
&lt;td&gt;Skill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8. End to end&lt;/td&gt;
&lt;td&gt;“Write the desktop and mobile Playwright journeys, with screenshots. use context7”&lt;/td&gt;
&lt;td&gt;Edit + MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9. Containers&lt;/td&gt;
&lt;td&gt;“Multi-stage non-root Dockerfile, .dockerignore, compose with PostgreSQL.”&lt;/td&gt;
&lt;td&gt;Edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10. Audit&lt;/td&gt;
&lt;td&gt;“Run the security-auditor agent on the whole application.”&lt;/td&gt;
&lt;td&gt;Sub-agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11. Fixes&lt;/td&gt;
&lt;td&gt;“Fix every High and Medium finding using TDD, then rerun the audit.”&lt;/td&gt;
&lt;td&gt;Edit + sub-agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12. Delivery&lt;/td&gt;
&lt;td&gt;“Prepare v1.0.0 with the release-notes skill.”&lt;/td&gt;
&lt;td&gt;Skill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Example: the step 4 prompt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context: the tests in tests/unit/target-url.test.ts are failing (red step).
Goal: implement parseTargetUrl in src/domain/target-url.ts.
Constraints:
  - do NOT modify the tests;
  - no external dependencies: only node:net and the URL API;
  - follow the CLAUDE.md architecture (the domain imports nothing else).
Verification: npm test must pass, then npm run check.
Format: show me the diff, then the test output.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why “do not modify the tests”?&lt;/strong&gt; Faced with a red test, an agent may be tempted to adjust the test rather than the code. Saying it explicitly closes that door: the test is the specification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TDD with an agent: red, green, refactor
&lt;/h2&gt;

&lt;p&gt;Test-driven development is an agent’s best ally: it turns a vague intention into an &lt;strong&gt;objective criterion&lt;/strong&gt;. The agent no longer has to guess what “correct” means: it reads it in the tests, and it knows when it is done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F524heh77ic7sdzspwqdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F524heh77ic7sdzspwqdp.png" alt="The TDD cycle, applied to every LinkLite business rule." width="799" height="220"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The TDD cycle, applied to every LinkLite business rule.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The LinkLite test pyramid
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What is tested&lt;/th&gt;
&lt;th&gt;Volume&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit&lt;/td&gt;
&lt;td&gt;Vitest&lt;/td&gt;
&lt;td&gt;Domain and use cases, with no database or network (in-memory adapter)&lt;/td&gt;
&lt;td&gt;≈ 60 tests, a few milliseconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration&lt;/td&gt;
&lt;td&gt;Vitest + real PostgreSQL&lt;/td&gt;
&lt;td&gt;SQL, constraints, 20 concurrent clicks, SQL injection, migrations; API via &lt;code&gt;app.inject()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;≈ 25 tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End to end&lt;/td&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;Real user journeys in Chrome, desktop and mobile&lt;/td&gt;
&lt;td&gt;10 journeys&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flumsmiijsb4n6x54lyit.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flumsmiijsb4n6x54lyit.webp" alt="The target-url.test.ts test file in the editor, with Vitest’s green output in the terminal" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The target URL tests, including the bypasses found by the audit, and their actual run (editor view: Monaco rendering of the real files and output).&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Coverage as a guardrail
&lt;/h3&gt;

&lt;p&gt;Coverage does not prove the code is correct, but it reveals what has &lt;strong&gt;never been executed&lt;/strong&gt;. The threshold is blocking: below 90%, the command fails, both locally and in continuous integration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vitest.config.ts&lt;/span&gt;
&lt;span class="nx"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/server.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/infrastructure/migrate-cli.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// entry points&lt;/span&gt;
  &lt;span class="nx"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2u1y6q92b1pdjd3wvh8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2u1y6q92b1pdjd3wvh8.webp" alt="Anti-SSRF filter code in the editor and a 98.87% coverage summary in the terminal" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The anti-SSRF filter (net.BlockList) and the final coverage report: 89 tests, 98.87% of statements covered.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A real bug, caught thanks to the process
&lt;/h3&gt;

&lt;p&gt;First time opening the interface in the browser: the page loads… with no styling at all. The diagnosis is quick: the &lt;code&gt;GET /:slug&lt;/code&gt; route, meant to redirect a short link, was also catching &lt;code&gt;/styles.css&lt;/code&gt; and &lt;code&gt;/app.js&lt;/code&gt;, mistaking them for unknown slugs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffti8ver65ad5hmtgwbqx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffti8ver65ad5hmtgwbqx.webp" alt="LinkLite page without styles: only raw text is shown" width="800" height="430"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Actual Chrome screenshot: the stylesheet was being intercepted by the /:slug route.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The fix followed the TDD cycle, at the scale of a single bug:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Red&lt;/strong&gt;: an integration test requires &lt;code&gt;/assets/styles.css&lt;/code&gt; to respond &lt;code&gt;200&lt;/code&gt; with &lt;code&gt;text/css&lt;/code&gt;: it fails (302).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green&lt;/strong&gt;: static files are served under the &lt;code&gt;/assets/&lt;/code&gt; prefix, which is already a slug &lt;strong&gt;reserved&lt;/strong&gt; by the domain: no collision possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrail&lt;/strong&gt;: the test stays in the suite: this bug cannot come back without breaking continuous integration.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/assets/styles.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/text&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;css/&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/assets/app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/javascript/&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;])(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sert la ressource statique %s sans la confondre avec un slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Designing the interface with the frontend-design skill
&lt;/h2&gt;

&lt;p&gt;Left to its own devices, a model produces “average” interfaces: the same font, the same purple gradient, the same rounded cards. That makes sense: it reproduces the average of what it has seen. The &lt;strong&gt;frontend-design&lt;/strong&gt; skill, published by Anthropic, corrects this bias: it forces the agent to &lt;strong&gt;commit to a bold art direction&lt;/strong&gt; before writing a single line of CSS.&lt;/p&gt;
&lt;h3&gt;
  
  
  What the skill contains
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A mandatory thinking phase&lt;/strong&gt;: who the interface is for, what tone (brutalist, editorial, retro, luxurious…), what technical constraint, and above all &lt;em&gt;which detail will make it memorable&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precise guidelines&lt;/strong&gt;: distinctive typography (no Inter or Arial), a dominant palette with a sharp accent, targeted animations, texture and depth rather than flat fills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit don'ts&lt;/strong&gt;: generic “AI-generated” aesthetics.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Installing and invoking it
&lt;/h3&gt;

&lt;p&gt;A skill is just a folder containing a &lt;code&gt;SKILL.md&lt;/code&gt;. This one is available in Anthropic's public skills repository; you copy it either at user level (all projects) or at project level (shared with the team):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude/skills/frontend-design/SKILL.md     # user scope
.claude/skills/frontend-design/SKILL.md       # project scope (versioned)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code then loads only its &lt;strong&gt;description&lt;/strong&gt; at startup; the full content enters the context only when the skill is used: automatically if the request is about an interface, or explicitly with &lt;code&gt;/frontend-design&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The prompt used for LinkLite
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/frontend-design Single-page interface for LinkLite, a URL shortener:
form (URL + optional custom slug), result with a copy button,
list of the 20 most recent links with a click counter, a message when a link
is not found (?missing=slug). Vanilla HTML/CSS/JS with no framework, served by
Fastify, strict CSP (no inline script, Google Fonts styles allowed),
accessible, responsive, light and dark mode.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the prompt describes the &lt;strong&gt;need and the constraints&lt;/strong&gt;, not the style: it is the skill that leads the agent to propose a direction. It settled on the concept of a &lt;strong&gt;“link check-room”&lt;/strong&gt;: you drop off a URL like a piece of luggage and leave with a &lt;strong&gt;ticket&lt;/strong&gt;: kraft paper, ink, signal orange, perforated tickets and a click counter applied like a rubber stamp.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Design decision&lt;/th&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typefaces with character&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Bricolage Grotesque&lt;/em&gt; for headings, &lt;em&gt;IBM Plex Mono&lt;/em&gt; for body text (a “printed ticket” register).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Texture&lt;/td&gt;
&lt;td&gt;Lined kraft background, SVG paper grain, hard offset shadows.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion&lt;/td&gt;
&lt;td&gt;Staggered reveal of the ledger; the new ticket “drops in”; &lt;code&gt;prefers-reduced-motion&lt;/code&gt; is respected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dark theme&lt;/td&gt;
&lt;td&gt;CSS variables redefined under &lt;code&gt;prefers-color-scheme: dark&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;innerHTML&lt;/code&gt;: all data goes through &lt;code&gt;textContent&lt;/code&gt; (no XSS), and an external script that complies with the CSP.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8woz859t79t81iqm6mp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8woz859t79t81iqm6mp.webp" alt="LinkLite home page: large heading, form and ticket ledger" width="800" height="430"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Real Chrome screenshot of the home page: the “counter” form and the drop-off ledger.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1x573lbhjahbcdao4sqt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1x573lbhjahbcdao4sqt.webp" alt="Result ticket showing the short link localhost:3100/claude-code and a Copy button" width="800" height="430"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Real Chrome screenshot: the ticket generated after creating the /claude-code link.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpfdxlm0zjosghjhlpcx.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpfdxlm0zjosghjhlpcx.webp" alt="Error message: invalid target URL, internal addresses are not allowed" width="800" height="151"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Real Chrome screenshot: the cloud metadata address 169.254.169.254 is rejected.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz155qv8ujhk1pstmurc5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz155qv8ujhk1pstmurc5.webp" alt="LinkLite on mobile in light theme and on desktop in dark theme" width="800" height="419"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshots generated by the Playwright tests: mobile (Pixel 7) in light theme, desktop in dark theme.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  End-to-end tests also produce the demo
&lt;/h3&gt;

&lt;p&gt;The Playwright journeys run against the &lt;strong&gt;real stack&lt;/strong&gt; (Fastify + PostgreSQL), on desktop and mobile. With &lt;code&gt;SCREENSHOTS=1&lt;/code&gt;, they also record the documentation screenshots along the way: the demo can never fall behind the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refuse une adresse interne (protection SSRF)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;URL à raccourcir&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://169.254.169.254/latest/meta-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Raccourcir&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toContainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adresses internes sont interdites&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeHidden&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ssrf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// only captures when SCREENSHOTS=1&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotiywz8saf7pk7bfs175.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotiywz8saf7pk7bfs175.webp" alt="Playwright test file in the editor and ten passing journeys in the terminal" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Playwright journeys and their real run: 10 green tests on desktop and mobile.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A failing test does not always point to the code.&lt;/strong&gt; The “slug already taken” journey failed at first: it filled in the form a second time &lt;em&gt;before&lt;/em&gt; the first response had reset it. The fix belonged in the test (wait for the first ticket), not in the application. Reading the error before “fixing” anything remains a human reflex you should demand from the agent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Building your own agents and skills
&lt;/h2&gt;

&lt;p&gt;The tools you get out of the box are a starting point. Real productivity comes from &lt;strong&gt;agents and skills tailored to your project&lt;/strong&gt;: they capture know-how once and for all, and every team member benefits.&lt;/p&gt;
&lt;h3&gt;
  
  
  A security audit sub-agent
&lt;/h3&gt;

&lt;p&gt;A sub-agent is a Markdown file with a YAML header: a name, a description (which says &lt;em&gt;when&lt;/em&gt; to use it), the allowed tools and a model. This one is deliberately &lt;strong&gt;read-only&lt;/strong&gt;: an auditor analyzes, it does not fix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security-auditor&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Audite le code de LinkLite pour détecter des failles de sécurité&lt;/span&gt;
  &lt;span class="s"&gt;(OWASP Top 10, SSRF, injection SQL, open redirect, secrets). À utiliser après&lt;/span&gt;
  &lt;span class="s"&gt;toute modification des routes HTTP, de la validation d'URL, de l'accès base de&lt;/span&gt;
  &lt;span class="s"&gt;données ou de la configuration Docker. Ne modifie jamais les fichiers.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Tu es un auditeur de sécurité applicative senior. Tu analyses, tu ne corriges pas.

&lt;span class="gu"&gt;## Méthode&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Liste les points d'entrée : routes Fastify, variables d'environnement, Dockerfile, compose.
&lt;span class="p"&gt;2.&lt;/span&gt; Pour chaque point d'entrée, vérifie : injection SQL, validation des entrées,
   open redirect / SSRF, XSS, secrets, conteneur.
&lt;span class="p"&gt;3.&lt;/span&gt; Classe chaque constat : Critique, Élevé, Moyen, Faible.

&lt;span class="gu"&gt;## Format de réponse&lt;/span&gt;
Un tableau | Gravité | Fichier:ligne | Problème | Correctif proposé |, trié par gravité.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feauqt0i0mszjfjkrem61.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feauqt0i0mszjfjkrem61.webp" alt="The security-auditor agent definition open in the editor" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The security-auditor sub-agent: read-only tools, a cheaper model, and an enforced method and output format.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The audit in real conditions
&lt;/h3&gt;

&lt;p&gt;Run against the “finished” application (62 green tests, 97% coverage), the auditor still found plenty to do. It even &lt;strong&gt;verified its hypotheses by running the code&lt;/strong&gt; instead of speculating:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Filter bypass via IPv6: &lt;code&gt;http://[::ffff:127.0.0.1]/&lt;/code&gt; (IPv4-“mapped”) and NAT64 were accepted.&lt;/td&gt;
&lt;td&gt;Filter rewritten with &lt;code&gt;net.BlockList&lt;/code&gt; and the full list of non-public ranges.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A trailing dot was enough: &lt;code&gt;http://localhost./&lt;/code&gt; slipped past the checks.&lt;/td&gt;
&lt;td&gt;Hostname normalized before validation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Reserved ranges accepted (CGNAT, including Alibaba Cloud's metadata address).&lt;/td&gt;
&lt;td&gt;Ranges added and tested.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Hostnames without a domain (&lt;code&gt;http://db:5432/&lt;/code&gt;): an internal Docker service.&lt;/td&gt;
&lt;td&gt;Single-label hostnames rejected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;No rate limiting: link spam and inflated counters.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@fastify/rate-limit&lt;/code&gt;: 120 req/min, 10 creations/min per IP.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;No request size limit, incomplete CSP, connection pool without timeouts.&lt;/td&gt;
&lt;td&gt;Body capped at 4 KB, &lt;code&gt;object-src 'none'&lt;/code&gt;, PostgreSQL timeouts.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each finding was first turned into a &lt;strong&gt;red test&lt;/strong&gt; (twelve at once for the bypasses), then fixed. A second pass by the auditor confirmed the fixes and flagged a few residual low-severity cases, which were fixed in turn. It also caught that a newly added range was mistakenly blocking… &lt;em&gt;every&lt;/em&gt; IPv4 address: a test that “accepts &lt;code&gt;8.8.8.8&lt;/code&gt;” exposed it immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmeqck9698ykd8aco1ybv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmeqck9698ykd8aco1ybv.png" alt="The audit → tests → fixes → re-audit loop, until only low, documented risks remain." width="800" height="152"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The audit → tests → fixes → re-audit loop, until only low, documented risks remain.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A release skill, with references and a script
&lt;/h3&gt;

&lt;p&gt;A skill goes further than a reusable prompt: it is a &lt;strong&gt;folder&lt;/strong&gt; that can bundle reference documents and scripts. The agent reads the references only when it needs them (this is “progressive disclosure”) and runs the scripts without copying them into its context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.claude/skills/release-notes/
├── SKILL.md                      # when to use it + the steps
├── references/
│   ├── commit-types.md           # commit type → release notes section
│   └── release-template.md       # exact release notes template
└── scripts/
    └── collect-commits.sh        # commits since the last tag, grouped by type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8xuubrjblfjjwowpqny.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8xuubrjblfjjwowpqny.webp" alt="The release-notes skill's SKILL.md file and the collect-commits script output in the terminal" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The release-notes skill: the steps in SKILL.md, and the real output of its commit collection script.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A command for code review
&lt;/h3&gt;

&lt;p&gt;Finally, a custom &lt;code&gt;/review&lt;/code&gt; command (file &lt;code&gt;.claude/commands/review.md&lt;/code&gt;) reviews the current branch against &lt;code&gt;main&lt;/code&gt;: architecture compliance, SOLID and DRY principles, presence of tests, no secrets. It ends with either “Ready to merge” or the list of blocking issues.&lt;/p&gt;
&lt;h2&gt;
  
  
  Saving tokens and holding the scope
&lt;/h2&gt;

&lt;p&gt;Every file read, every command output, every documentation page takes up space in the &lt;strong&gt;context window&lt;/strong&gt;. A cluttered context costs more, slows the agent down and degrades the quality of its answers: it “forgets” the instructions given at the start. Saving tokens therefore also means &lt;strong&gt;working better&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;How LinkLite applies it&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delegate to sub-agents&lt;/td&gt;
&lt;td&gt;Both audits ran in a sub-agent (≈ 57,000 then 69,000 working tokens); only the final table came back.&lt;/td&gt;
&lt;td&gt;The main context stays focused on the code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The right model for the task&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;model: sonnet&lt;/code&gt; for the auditor and the test writer.&lt;/td&gt;
&lt;td&gt;Cheaper for well-scoped work.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Block useless reads&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;coverage&lt;/code&gt;, &lt;code&gt;dist&lt;/code&gt; under &lt;code&gt;deny&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;No thousands of lines read by mistake.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-demand skills&lt;/td&gt;
&lt;td&gt;Only the description is loaded; the content, when it is needed.&lt;/td&gt;
&lt;td&gt;Rich know-how with no standing cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targeted Context7 queries&lt;/td&gt;
&lt;td&gt;One precise question per library.&lt;/td&gt;
&lt;td&gt;A few excerpts instead of an entire documentation set.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filtered output&lt;/td&gt;
&lt;td&gt;`&lt;/td&gt;
&lt;td&gt;tail -5&lt;code&gt;, &lt;/code&gt;grep` on errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A concise &lt;code&gt;CLAUDE.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;One page: commands, architecture, rules.&lt;/td&gt;
&lt;td&gt;Loaded in every session: every line counts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/clear&lt;/code&gt; and &lt;code&gt;/compact&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Between two independent steps.&lt;/td&gt;
&lt;td&gt;Start again from a clean context.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Scope is the first saving.&lt;/strong&gt; All the “extra” ideas (analytics, QR codes, accounts) went onto the out-of-scope list rather than into the code. A feature nobody asked for costs tokens to write, to test, to audit… and to maintain.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  PostgreSQL and Docker: a deploy-ready application
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Why PostgreSQL, and hand-written SQL
&lt;/h3&gt;

&lt;p&gt;The critical rules live &lt;strong&gt;in the database itself&lt;/strong&gt;: even if a bug slipped past application-level validation, PostgreSQL would reject invalid data. Migrations are plain, versioned SQL files, applied inside a transaction and tracked in a &lt;code&gt;schema_migrations&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- db/migrations/001_create_links.sql&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;slug&lt;/span&gt;        &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s1"&gt;'^[a-z0-9][a-z0-9-]{2,31}$'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;target_url&lt;/span&gt;  &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;clicks&lt;/span&gt;      &lt;span class="nb"&gt;INTEGER&lt;/span&gt;       &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clicks&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;  &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;   &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;links_created_at_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The click counter shows why SQL matters: reading the value, adding 1 and writing it back would lose clicks under concurrent access. A &lt;strong&gt;single atomic statement&lt;/strong&gt; solves the problem, and an integration test fires 20 clicks in parallel to prove it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;registerClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Single atomic statement: concurrent clicks are never lost&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UPDATE links SET clicks = clicks + 1 WHERE slug = $1 RETURNING target_url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// parameter: never concatenated, so no SQL injection&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;target_url&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Dockerfile: multi-stage, lightweight, non-root
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Build: development dependencies + TypeScript compilation&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# 2. Production dependencies only&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# 3. Final image: no build tools, non-root user&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production PORT=3000&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps --chown=node:node /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build --chown=node:node /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node package.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node public ./public&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node db ./db&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=15s --timeout=3s --start-period=10s --retries=3 \&lt;/span&gt;
  CMD wget -qO- "http://127.0.0.1:${PORT}/health" &amp;gt; /dev/null || exit 1
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.dockerignore&lt;/code&gt; matters as much as the Dockerfile: it keeps secrets (&lt;code&gt;.env&lt;/code&gt;), Git history, tests and the agent's configuration out of the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker compose: the database, the migrations, then the application
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2uy31ptjqq4a3aahpzlk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2uy31ptjqq4a3aahpzlk.png" alt="Startup order is guaranteed by conditions: the database must be healthy, then the migrations must succeed." width="799" height="155"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Startup order is guaranteed by conditions: the database must be healthy, then the migrations must succeed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ksr0dbxvhjmtp7qbw2w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ksr0dbxvhjmtp7qbw2w.webp" alt="The docker-compose.yml file open in the editor" width="800" height="478"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The docker-compose.yml: healthcheck, migration job, read-only application container and ports bound to 127.0.0.1.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env          &lt;span class="c"&gt;# pick a real POSTGRES_PASSWORD&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;     &lt;span class="c"&gt;# PostgreSQL 18 → migrations → application&lt;/span&gt;
open http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Transparency:&lt;/strong&gt; Docker was not installed on the workshop machine, so the image could not be built locally. To avoid shipping an unverified file, two safeguards were used: the exact layout of the image (compiled code + production dependencies only) was reproduced and tested outside a container (migration, redirect, static assets), and the &lt;strong&gt;CI builds the image and starts the whole stack&lt;/strong&gt; on every change. An honest agent reports what it could not verify; insist on it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Git, code quality and shipping v1.0.0
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One branch per intent, main always stable
&lt;/h3&gt;

&lt;p&gt;No commit was made directly on &lt;code&gt;main&lt;/code&gt; after initialization: each step lives on its own branch, and &lt;code&gt;main&lt;/code&gt; only moves forward when everything is green. The history tells the story of the project, in order:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffnxm702ua5b8wwtu5gi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffnxm702ua5b8wwtu5gi.png" alt="LinkLite's history: short-lived branches, conventional commits, then a tagged release on main." width="799" height="621"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;LinkLite's history: short-lived branches, conventional commits, then a tagged release on main.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Messages follow &lt;strong&gt;Conventional Commits&lt;/strong&gt; (&lt;code&gt;feat&lt;/code&gt;, &lt;code&gt;fix&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;docs&lt;/code&gt;, &lt;code&gt;chore&lt;/code&gt;), in English. This is not just a cosmetic convention: it is what lets the &lt;code&gt;release-notes&lt;/code&gt; skill generate release notes automatically. Here, with no remote repository, &lt;code&gt;main&lt;/code&gt; was only ever advanced by &lt;em&gt;fast-forward&lt;/em&gt; (&lt;code&gt;git merge --ff-only&lt;/code&gt;), which keeps the history linear; in a team, this step goes through a reviewed merge request and a protected &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean Code, SOLID and DRY: concrete examples
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;Where to see it in LinkLite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;S&lt;/strong&gt;: single responsibility&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;parseTargetUrl&lt;/code&gt; validates, &lt;code&gt;LinkService&lt;/code&gt; orchestrates, &lt;code&gt;PostgresLinkRepository&lt;/code&gt; stores, and the routes translate HTTP ↔ domain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;O&lt;/strong&gt;: open/closed&lt;/td&gt;
&lt;td&gt;A new storage backend (Redis…) = a new adapter, with no change to the use case.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;L&lt;/strong&gt;: substitution&lt;/td&gt;
&lt;td&gt;The in-memory and PostgreSQL adapters are interchangeable: the same behavior suite applies to both.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;I&lt;/strong&gt;: focused interfaces&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;LinkRepository&lt;/code&gt; port contains only the four operations the domain needs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;D&lt;/strong&gt;: dependency inversion&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LinkService&lt;/code&gt; receives its repository, its clock and its slug generator: everything is injectable, and therefore testable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DRY&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A single &lt;code&gt;positiveInteger&lt;/code&gt; function validates every numeric configuration variable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KISS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No ORM for three queries, no front-end framework for a single page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fail fast&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;loadConfig&lt;/code&gt; refuses to start without &lt;code&gt;DATABASE_URL&lt;/code&gt; or with an invalid value.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Continuous integration
&lt;/h3&gt;

&lt;p&gt;The pipeline replays everything that was done locally, on a fresh machine: types, lint, formatting, tests with coverage against a PostgreSQL service, dependency audit, Playwright journeys, then building the Docker image and starting the full stack. A branch with a red pipeline does not get merged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shipping, with the release-notes skill
&lt;/h3&gt;

&lt;p&gt;The prompt “&lt;em&gt;Prepare v1.0.0 with the release-notes skill&lt;/em&gt;” ran through the skill's steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collected the commits since the start of the project, grouped by type (the skill's script).&lt;/li&gt;
&lt;li&gt;Wrote user-oriented notes following the reference template (the effect, not the implementation).&lt;/li&gt;
&lt;li&gt;Added the &lt;code&gt;[1.0.0]&lt;/code&gt; section at the top of &lt;code&gt;CHANGELOG.md&lt;/code&gt; and bumped &lt;code&gt;package.json&lt;/code&gt; to 1.0.0.&lt;/li&gt;
&lt;li&gt;Blocking checks: &lt;code&gt;npm run check&lt;/code&gt;, coverage (98.9%) and 10 green end-to-end journeys.&lt;/li&gt;
&lt;li&gt;Commit &lt;code&gt;chore(release): v1.0.0&lt;/code&gt; on a &lt;code&gt;release/v1.0.0&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;Advanced &lt;code&gt;main&lt;/code&gt; by &lt;em&gt;fast-forward&lt;/em&gt;, then placed an annotated &lt;code&gt;v1.0.0&lt;/code&gt; tag on &lt;code&gt;main&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--decorate&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt;
93a1936 &lt;span class="o"&gt;(&lt;/span&gt;HEAD -&amp;gt; main, tag: v1.0.0, release/v1.0.0&lt;span class="o"&gt;)&lt;/span&gt; chore&lt;span class="o"&gt;(&lt;/span&gt;release&lt;span class="o"&gt;)&lt;/span&gt;: v1.0.0
d369e61 &lt;span class="o"&gt;(&lt;/span&gt;fix/security&lt;span class="o"&gt;)&lt;/span&gt; fix&lt;span class="o"&gt;(&lt;/span&gt;security&lt;span class="o"&gt;)&lt;/span&gt;: address the low-severity findings of the re-audit
e735e9b docs: add the MIT license
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The results in numbers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit and integration tests&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;89&lt;/strong&gt;, all green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;98.9%&lt;/strong&gt; of statements, &lt;strong&gt;98.8%&lt;/strong&gt; of branches (threshold: 90%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end journeys&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;10&lt;/strong&gt; (desktop + mobile)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security audit&lt;/td&gt;
&lt;td&gt;2 High and 4 Medium fixed, residual low risks documented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bugs caught before release&lt;/td&gt;
&lt;td&gt;Static assets intercepted, SSRF bypasses, 429 turned into 500, IPv4 regression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;v1.0.0&lt;/code&gt; tag, CHANGELOG, multi-stage Docker image, CI pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The key takeaway from the workshop: the agent wrote most of the code, but &lt;strong&gt;the quality came from the framework around it&lt;/strong&gt;: a written scope, a project memory, tests first, up-to-date documentation via Context7, a design guided by a skill, an independent auditor and a tooled release process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Generative AI has crossed a threshold: having learned to &lt;strong&gt;answer&lt;/strong&gt;, it can now &lt;strong&gt;act&lt;/strong&gt;. For development teams and IT departments, coding agents are a major opportunity, provided they are treated for what they are: very fast collaborators who need a &lt;strong&gt;clear framework&lt;/strong&gt; (memory, conventions), a &lt;strong&gt;verifiable goal&lt;/strong&gt; (tests), &lt;strong&gt;guardrails&lt;/strong&gt; (permissions, data security) and &lt;strong&gt;human review&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The LinkLite workshop showed it in practice: a well-framed agent delivers a tested, audited and containerized application in a few steps, and the mistakes it makes are caught by the tests, the auditor and the permissions rather than by users. The best way to start is simple: pick a real but limited task, run &lt;code&gt;/init&lt;/code&gt;, enrich the &lt;code&gt;CLAUDE.md&lt;/code&gt;, configure a few cautious permissions, then follow the cycle of explore, plan, implement, verify, review. It is through methodical practice that AI becomes a true skills multiplier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Glossary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A system that combines a language model, tools and an action loop to achieve a goal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An MCP server that provides the agent with up-to-date, versioned library documentation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context (window)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The amount of text the model can take into account at any given moment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hallucination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;False information confidently produced by a model.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A script triggered automatically before or after an agent action.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Large Language Model.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Model Context Protocol: an open standard for connecting an agent to tools and data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Port / adapter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hexagonal architecture: an interface defined by the domain (port) and an interchangeable technical implementation (adapter).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An instruction or request addressed to the model.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Packaged know-how (instructions, scripts) loaded by the agent when it is useful.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sub-agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A specialized agent, with its own context, to which the main agent delegates a task.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSRF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server-Side Request Forgery: abusing a server to reach internal addresses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TDD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Test-Driven Development: writing the failing test before the code that makes it pass.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Token&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A fragment of text, the basic unit processed by a language model.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://abdoulayeba.online/en/articles/ia-generative-agents-de-codage" rel="noopener noreferrer"&gt;abdoulayeba.online&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>tdd</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
