ai newsSa., 31. Okt.

Generative AI and coding agents: understanding, choosing and working well with an AI agent

In just a few years, artificial intelligence has moved from the lab to our pockets, and then into our code editors. It writes, summarizes, translates, explains, and now it acts : it reads a project, edits files, runs tests and proposes fixes. For a developer, an architect or a CIO, the question is

dev.toSamstag 17:14 · 31.10.31 Min. Lesezeit
Bild: dev.to

Posted on Oct 31 • Originally published at abdoulayeba.online

In just a few years, artificial intelligence has moved from the lab to our pockets, and then into our code editors. It writes, summarizes, translates, explains, and now it acts: 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 how to adopt it with method, efficiency and security.

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 agent, 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 Claude Code, Anthropic's coding agent, but the principles apply to most tools.

How should you read this article? 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 complete workshop: the LinkLite mini-project, built from A to Z with Claude Code, all the way to version 1.0.0. A glossary closes the article.

Artificial intelligence 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.

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

This difference is fundamental. In traditional programming, the developer writes the rules. In machine learning, you provide examples and the machine learns the rules.

Figure 1. Traditional programming versus machine learning: in the second case, the rules are what gets produced.

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: massive data, abundant computing power (GPUs) and new architectures of neural networks.

Figure 2. The major milestones of AI, from the first ideas to autonomous agents.

Every AI in production today is so-called “narrow” or specialized AI: it excels in a specific domain (language, images, recommendations…) without consciousness or understanding of the world in the human sense. General AI (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.

These terms overlap and are often wrongly used as synonyms. The best picture is a set of Russian dolls: each notion is a subset of the previous one.

Figure 3. The Russian dolls of AI: an LLM is a generative Deep Learning model applied to language (NLP).

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

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 composes.

Figure 4. Generation is a loop: each token produced is fed back into the context to predict the next one.

Three notions are enough to understand most of an LLM's behavior:

Hallucinations. 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 verify: tests, official documentation, review.

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.

Figure 5. The shift in value: from execution toward expressing needs, steering and deciding.

One phrase sums up the situation well: AI will not replace professionals, but a professional who masters AI will probably replace one who ignores it.

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

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

Golden rule: 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.

In software development, AI has progressed in three stages, each giving the machine a little more autonomy.

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.

Figure 6. The agent can step in at every stage of the software lifecycle, always under the team's control.

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

Analogy: the assistant is an advisor who tells you what to do. The agent is a craftsman who picks up the tools, does the work, checks the result and reports back to you.

Figure 7. Anatomy of an agent: a model, a memory, an action loop and tools, governed by permissions.

At the heart of an agent is a simple loop. Take a concrete request: “The sign-up test is failing, fix it.”

Figure 8. An agentic loop in action: the agent observes, acts, verifies, then reports back.

Key takeaway: an agent's value does not come from the model alone, but from its ability to check its own work (tests, compilation, linting). An agent that can run the tests fixes its own mistakes.

Around the main agent revolves an ecosystem of building blocks with clearly distinct roles. To understand them, picture a company:

Figure 9. The ecosystem of a coding agent: delegation to sub-agents, know-how in skills, MCP connections and checks through hooks.

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

Example of a sub-agent, defined in a simple Markdown file:

--- name: security-reviewer description: Analyzes changes to detect security vulnerabilities (OWASP). Use after any change touching authentication or data. tools: Read, Grep, Glob --- You are an application security expert. For each change: 1. Spot injections, data leaks and missing access controls. 2. Rank each issue by severity (critical, high, medium). 3. Propose a precise fix, without editing the files yourself. Enter fullscreen mode Exit fullscreen mode And a skill, stored in a folder alongside its resources:

--- name: release-notes description: Writes release notes from conventional commits. Use when preparing a release. --- 1. List the commits since the last tag (git log). 2. Group them by type: feat, fix, perf, docs. 3. Write clear, user-oriented notes in English. Enter fullscreen mode Exit fullscreen mode MCP: the universal port The Model Context Protocol (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.

{ "mcpServers": { "gitlab": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-gitlab" ], "env": { "GITLAB_PERSONAL_ACCESS_TOKEN": "${GITLAB_TOKEN}" } } } } Enter fullscreen mode Exit fullscreen mode An MCP server is third-party code that accesses your tools and data. Only install trusted servers, with tokens that have minimal permissions, and never commit a token in plain text: use environment variables.

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

Tip: evaluate two or three tools on the same real task 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.

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.

Figure 10. The AI-driven development cycle: explore, plan, implement, verify, review, commit.

TDD is the agent’s best ally. Tests written first give the agent a verifiable goal: it knows objectively when it is done, and you know the result works.

Prompt engineering 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.

Analogy: a prompt is the brief you give a contractor. A good contractor with a bad brief delivers something well made… that doesn’t match what you need.

Figure 11. The five ingredients of a good prompt.

A vague prompt leaves the agent guessing:

Add pagination to the user list. Enter fullscreen mode Exit fullscreen mode A good prompt frames the work:

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 > service > 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. Enter fullscreen mode Exit fullscreen mode Scope: one task at a time Scope 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: one request = one verifiable goal, ideally one commit.

An LLM has no memory between two conversations. 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.

Analogy: the context window is working memory: what you keep in mind during a meeting. Persistent memory is the logbook you reread when you arrive in the morning.

Figure 12. Two levels of memory: the persistent logbook and the session’s working memory.

An example of a concise, effective CLAUDE.md file:

# Portfolio project: rules for the agent ## Commands - Install: bun install - Tests: bun run test (minimum coverage 95%) - Checks: bun run lint && bun run typecheck ## Architecture - Feature First: each feature in src/features/ <name> / - Mutations only through Server Actions, validated with Zod ## Rules - Never push to main: a branch and a merge request - Commits in English, Conventional Commits format - Never read or modify.env files Enter fullscreen mode Exit fullscreen mode A good memory file is short, factual and up to date. Ten precise rules beat a hundred generic lines: everything in memory takes up room in the context at every session.

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

A custom command is just a Markdown file placed in.claude/commands/. The file.claude/commands/review.md becomes the /review command:

Review the uncommitted changes on the current branch. For each modified file, check: - compliance with the architecture and conventions in CLAUDE.md; - the presence of tests for any new logic; - the absence of any plain-text secret or personal data. Return a list of comments sorted by priority. Enter fullscreen mode Exit fullscreen mode The.claude folder: local, user, organization 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.claude.

Analogy: the project’s.claude folder is the onboarding binder handed to every newcomer: house rules, procedures, useful contacts. The user folder is your personal toolkit, which follows you from project to project.

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 Enter fullscreen mode Exit fullscreen mode Level Scope Example content Versioned? Organization (managed settings) Every workstation in the company Global prohibitions, approved MCP servers Managed by IT Shared project (.claude/, CLAUDE.md ) The whole team on this project Conventions, test commands, common permissions, sub-agents Yes Personal project ( settings.local.json ) You, on this project Permissions specific to your workstation No User ( ~/.claude/ ) You, across all your projects Preferred language, response style, favorite tools No Whatever is versioned is shared with the whole team, and sometimes beyond. No secrets in CLAUDE.md, settings.json or.mcp.json: use environment variables and keep.env files out of the repository.

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 “ how do you allow only what is needed, at the right time? ”

Analogy: you don’t hand a contractor the master key to the entire building. You give them a badge that opens the rooms they need, for the duration of their assignment.

Figure 13. Permission control: what is denied is blocked, what is allowed goes through, everything else requires confirmation.

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

{ "permissions": { "allow": [ "Bash(bun run test:*)", "Bash(bun run lint)", "Bash(git status)", "Bash(git diff:*)" ], "deny": [ "Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)", "Bash(git push --force:*)", "Bash(rm -rf:*)" ] } } Enter fullscreen mode Exit fullscreen mode Personal and sensitive data Minimize: give the agent only the data the task requires. Fictitious test data is better than a production extract. Isolate secrets:.env files denied for reading, tokens with limited rights, regular rotation.

Comply with the GDPR: no real personal data (customers, employees, patients) in the exchanges without a legal basis and a tool approved by the company. Be wary of external content: 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.

Keep a trail: review the diffs, work on branches, keep the Git history so you can roll back. Approach Advantage Risk Verdict Allow everything No interruptions Leaked secrets, destructive commands, unintended actions ❌ Avoid Confirm everything Total control Fatigue: you end up approving everything without reading ⚠️ Counterproductive Targeted, progressive permissions Smooth on the safe, controlled on the sensitive Requires a little configuration ✅ Recommended Best practices for agent-driven coding A checklist to keep at hand for every task you hand to an agent:

The rule that sums it all up: the agent proposes, the human decides. You remain responsible for every line that goes to production.

Theory is not enough: to master 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 Claude Code: from choosing the subject all the way to a tagged version 1.0.0, 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.

What you will build: LinkLite, a URL shortener. You drop in a long address and leave with a short link (a “ticket”) and a click counter. Full source code of the mini-project: https://github.com/abdoulayeba29/linklite

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

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

Setting the scope before 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:

Every choice is driven by the need, not by fashion. That is also what we ask of the agent: justify its choices rather than pile up dependencies.

The code is organized in layers 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 port ), which the infrastructure implements through adapters. The result: you can test all the business logic without a database, and switch databases without touching the business logic.

Lightweight hexagonal architecture: the domain sits at the center, and adapters implement its ports.

// src/domain/link.ts: the port, i.e. what the business logic needs, without saying how export interface LinkRepository { /** Inserts the link; returns false when the slug already exists. */ insert ( link: Link ): Promise < boolean > findBySlug ( slug: string ): Promise < Link | null > /** Atomically increments the click counter; returns the target or null if unknown. */ registerClick ( slug: string ): Promise < string | null > listRecent ( limit: number ): Promise < Link [] > } Enter fullscreen mode Exit fullscreen mode Laying the groundwork: memory, permissions and the Context7 MCP server Before the first line of code, we give the agent what a new colleague would receive on day one: the house rules, the authorized access and up-to-date documentation. Everything is versioned in the repository, and therefore shared by the whole team.

LinkLite's CLAUDE.md and the agent's configuration files (editor view: Monaco rendering, the VS Code engine, of the project's real files).

The settings.json 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 pushing to main are forbidden.

{ "permissions": { "allow": [ "Bash(npm test)", "Bash(npm run test:*)", "Bash(npm run check)", "Bash(git status)", "Bash(git diff:*)", "Bash(git log:*)", "mcp__context7__resolve-library-id", "mcp__context7__query-docs" ], "deny": [ "Read(./.env)", "Read(./.env.*)", "Read(./node_modules/**)", "Read(./coverage/**)", "Bash(git push --force:*)", "Bash(git push origin main:*)", "Bash(rm -rf:*)", "Bash(docker compose down -v:*)" ] } } Enter fullscreen mode Exit fullscreen mode What actually happened during the workshop: the agent wanted to delete old screenshots with rm -f docs/screenshots/*. Claude Code’s guardrail blocked the wildcard deletion, 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.

A language model has a knowledge cutoff: 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.

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

Two equivalent approaches, at project level (recommended, since it is shared with the team):

# Option 1: from the command line, project scope (writes.mcp.json) claude mcp add --transport http --scope project context7 https://mcp.context7.com/mcp # Check the connection from a Claude Code session /mcp Enter fullscreen mode Exit fullscreen mode // Option 2: directly in.mcp.json, at the repository root { "mcpServers": { "context7": { "type": "http", "url": "https://mcp.context7.com/mcp" } } } Enter fullscreen mode Exit fullscreen mode Context7 exposes two tools: resolve-library-id (find a library’s identifier, for example /fastify/fastify ) and query-docs (ask a targeted question about that library). A free API key raises the quotas, but is not required to get started.

The Context7 cycle: the agent resolves the library identifier, queries the documentation, then writes the code.

Context7 does not replace tests. Documentation says what should work; only tests prove what does. In LinkLite, every piece of information that came from Context7 was locked in by a test.

You do not steer an entire project with one giant prompt, but with a series of short, verifiable requests, each ending with an acceptance criterion. They all follow the same template:

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… Enter fullscreen mode Exit fullscreen mode The complete sequence Step Prompt (summary) Mode 1. Scoping “Here is the subject and the scope.

Propose the architecture and the list of files, without writing code.” Plan 2. Foundation “Initialize the project: strict TypeScript, Vitest, ESLint, Prettier, CLAUDE.md and settings.json.” Edit 3. Domain (red) “With the test-writer agent, write the URL and slug validation tests.

They must fail.” Sub-agent 4. Domain (green) “Implement the minimum to make these tests pass, without modifying them.” Edit 5. Use cases “Create LinkService behind the LinkRepository port; in-memory adapter for the tests.” Edit 6.

API + database “Add the Fastify API and the PostgreSQL adapter. use context7” Edit + MCP 7. User interface “/frontend-design A single page for LinkLite… (see below)” Skill 8. End to end “Write the desktop and mobile Playwright journeys, with screenshots. use context7” Edit + MCP 9.

Containers “Multi-stage non-root Dockerfile,.dockerignore, compose with PostgreSQL.” Edit 10. Audit “Run the security-auditor agent on the whole application.” Sub-agent 11. Fixes “Fix every High and Medium finding using TDD, then rerun the audit.” Edit + sub-agent 12.

Delivery “Prepare v1.0.0 with the release-notes skill.” Skill Example: the step 4 prompt 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. Enter fullscreen mode Exit fullscreen mode Why “do not modify the tests”?

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.

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

The TDD cycle, applied to every LinkLite business rule.

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).

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

// vitest.config.ts coverage: { provider: ' v8 ', include: [ ' src ' ], exclude: [ ' src/server.ts ', ' src/infrastructure/migrate-cli.ts ' ], // entry points thresholds: { lines: 90, functions: 90, branches: 90, statements: 90 }, }, Enter fullscreen mode Exit fullscreen mode The anti-SSRF filter (net.BlockList) and the final coverage report: 89 tests, 98.87% of statements covered.

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

Actual Chrome screenshot: the stylesheet was being intercepted by the /:slug route.

The fix followed the TDD cycle, at the scale of a single bug:

A skill is just a folder containing a SKILL.md. 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):

~/.claude/skills/frontend-design/SKILL.md # user scope.claude/skills/frontend-design/SKILL.md # project scope (versioned) Enter fullscreen mode Exit fullscreen mode Claude Code then loads only its description 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 /frontend-design.

/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. Enter fullscreen mode Exit fullscreen mode Note that the prompt describes the need and the constraints, not the style: it is the skill that leads the agent to propose a direction. It settled on the concept of a “link check-room”: you drop off a URL like a piece of luggage and leave with a ticket: kraft paper, ink, signal orange, perforated tickets and a click counter applied like a rubber stamp.

Real Chrome screenshot of the home page: the “counter” form and the drop-off ledger.

Real Chrome screenshot: the ticket generated after creating the /claude-code link.

Real Chrome screenshot: the cloud metadata address 169.254.169.254 is rejected.

Screenshots generated by the Playwright tests: mobile (Pixel 7) in light theme, desktop in dark theme.

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

test ( ' refuse une adresse interne (protection SSRF) ', async ({ page }) => { await page. goto ( ' / ' ) await page. getByLabel ( ' URL à raccourcir ' ). fill ( ' http://169.254.169.254/latest/meta-data ' ) await page. getByRole ( ' button ', { name: ' Raccourcir ' }). click () await expect ( page. getByRole ( ' alert ' )). toContainText ( ' adresses internes sont interdites ' ) await expect ( page. locator ( ' #result ' )). toBeHidden () await capture ( page, ' ssrf ' ) // only captures when SCREENSHOTS=1 }) Enter fullscreen mode Exit fullscreen mode The Playwright journeys and their real run: 10 green tests on desktop and mobile.

A failing test does not always point to the code. The “slug already taken” journey failed at first: it filled in the form a second time before 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.

The tools you get out of the box are a starting point. Real productivity comes from agents and skills tailored to your project: they capture know-how once and for all, and every team member benefits.

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

--- name: security-auditor description: Audite le code de LinkLite pour détecter des failles de sécurité (OWASP Top 10, SSRF, injection SQL, open redirect, secrets). À utiliser après toute modification des routes HTTP, de la validation d'URL, de l'accès base de données ou de la configuration Docker. Ne modifie jamais les fichiers. tools: Read, Grep, Glob model: sonnet --- Tu es un auditeur de sécurité applicative senior. Tu analyses, tu ne corriges pas. ## Méthode 1.

Liste les points d'entrée: routes Fastify, variables d'environnement, Dockerfile, compose. 2. Pour chaque point d'entrée, vérifie: injection SQL, validation des entrées, open redirect / SSRF, XSS, secrets, conteneur.

3. Classe chaque constat: Critique, Élevé, Moyen, Faible. ## Format de réponse Un tableau | Gravité | Fichier:ligne | Problème | Correctif proposé |, trié par gravité. Enter fullscreen mode Exit fullscreen mode The security-auditor sub-agent: read-only tools, a cheaper model, and an enforced method and output format.

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

Each finding was first turned into a red test (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… every IPv4 address: a test that “accepts 8.8.8.8 ” exposed it immediately.

The audit → tests → fixes → re-audit loop, until only low, documented risks remain.

A skill goes further than a reusable prompt: it is a folder 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.

.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 Enter fullscreen mode Exit fullscreen mode The release-notes skill: the steps in SKILL.md, and the real output of its commit collection script.

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

Every file read, every command output, every documentation page takes up space in the context window. 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 working better.

Scope is the first saving. 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.

The critical rules live in the database itself: 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 schema_migrations table.

-- db/migrations/001_create_links.sql CREATE TABLE IF NOT EXISTS links ( slug VARCHAR ( 32 ) PRIMARY KEY CHECK ( slug ~ '^[a-z0-9][a-z0-9-]{2,31}$' ), target_url VARCHAR ( 2048 ) NOT NULL, clicks INTEGER NOT NULL DEFAULT 0 CHECK ( clicks >= 0 ), created_at TIMESTAMPTZ NOT NULL DEFAULT now () ); CREATE INDEX IF NOT EXISTS links_created_at_idx ON links ( created_at DESC ); Enter fullscreen mode Exit fullscreen mode The click counter shows why SQL matters: reading the value, adding 1 and writing it back would lose clicks under concurrent access. A single atomic statement solves the problem, and an integration test fires 20 clicks in parallel to prove it.

async registerClick ( slug: string ): Promise < string | null > { // Single atomic statement: concurrent clicks are never lost const { rows } = await this. pool. query < { target_url: string } > ( ' UPDATE links SET clicks = clicks + 1 WHERE slug = $1 RETURNING target_url ', [ slug ] // parameter: never concatenated, so no SQL injection ) return rows [ 0 ]?. target_url?? null } Enter fullscreen mode Exit fullscreen mode The Dockerfile: multi-stage, lightweight, non-root # 1. Build: development dependencies + TypeScript compilation FROM node:22-alpine AS build WORKDIR /app COPY package.json package-lock.json./ RUN npm ci COPY tsconfig.json./ COPY src./src RUN npm run build # 2. Production dependencies only FROM node:22-alpine AS deps WORKDIR /app COPY package.json package-lock.json./ RUN npm ci --omit = dev && npm cache clean --force # 3.

Final image: no build tools, non-root user FROM node:22-alpine AS runtime ENV NODE_ENV=production PORT=3000 WORKDIR /app COPY --from=deps --chown=node:node /app/node_modules./node_modules COPY --from=build --chown=node:node /app/dist./dist COPY --chown=node:node package.json./ COPY --chown=node:node public./public COPY --chown=node:node db./db USER node EXPOSE 3000 HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \ CMD wget -qO- "http://127.0.0.1:${PORT}/health" > /dev/null || exit 1 CMD ["node", "dist/server.js"] Enter fullscreen mode Exit fullscreen mode The.dockerignore matters as much as the Dockerfile: it keeps secrets (.env ), Git history, tests and the agent's configuration out of the image.

Startup order is guaranteed by conditions: the database must be healthy, then the migrations must succeed.

The docker-compose.yml: healthcheck, migration job, read-only application container and ports bound to 127.0.0.1.

cp.env.example.env # pick a real POSTGRES_PASSWORD docker compose up --build # PostgreSQL 18 → migrations → application open http://localhost:3000 Enter fullscreen mode Exit fullscreen mode Transparency: 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 CI builds the image and starts the whole stack on every change. An honest agent reports what it could not verify; insist on it.

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

LinkLite's history: short-lived branches, conventional commits, then a tagged release on main.

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

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.

The prompt “ Prepare v1.0.0 with the release-notes skill ” ran through the skill's steps:

$ git log --oneline --decorate -3 93a1936 ( HEAD -> main, tag: v1.0.0, release/v1.0.0 ) chore ( release ): v1.0.0 d369e61 ( fix/security ) fix ( security ): address the low-severity findings of the re-audit e735e9b docs: add the MIT license Enter fullscreen mode Exit fullscreen mode The results in numbers Metric Result Unit and integration tests 89, all green Coverage 98.9% of statements, 98.8% of branches (threshold: 90%) End-to-end journeys 10 (desktop + mobile) Security audit 2 High and 4 Medium fixed, residual low risks documented Bugs caught before release Static assets intercepted, SSRF bypasses, 429 turned into 500, IPv4 regression Release v1.0.0 tag, CHANGELOG, multi-stage Docker image, CI pipeline The key takeaway from the workshop: the agent wrote most of the code, but the quality came from the framework around it: 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.

Generative AI has crossed a threshold: having learned to answer, it can now act. 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 clear framework (memory, conventions), a verifiable goal (tests), guardrails (permissions, data security) and human review.

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 /init, enrich the CLAUDE.md, 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.

Originally published at abdoulayeba.online.

Quelle: dev.to (dev.to). Original öffnen