Agent Skills in Claude Code and Visual Studio Code: Setup, Benefits, Risks, and a Step-by-Step Example

Image
 AI coding assistants are useful for one-off questions, but many development tasks are not one-off tasks. A team may follow the same testing procedure before every release, use a specific format for code reviews, or require certain checks whenever a database migration is created. Repeating those requirements in every prompt is inefficient. Putting all of them into a permanent instruction file is not always ideal either, because the assistant may receive irrelevant information for tasks that do not need it. This is the problem that Agent Skills are designed to solve. A skill packages reusable instructions, supporting files, examples, and optional scripts into a folder. Claude Code or GitHub Copilot in Visual Studio Code can discover that folder and load its contents when the current task matches the skill’s purpose. Both environments support the open Agent Skills format, which makes it possible to share some skills across tools. This guide explains what skills are, how they differ ...

How to Review AI-Generated Code Without Being an Expert



An AI coding assistant can produce a working-looking feature in seconds. Reviewing that feature may take considerably longer.

For beginners, this creates an uncomfortable question: How can you review code when you do not yet understand every line?

You do not need to become an expert before reviewing AI-generated code. You need a repeatable process that helps you compare the requested behavior with the actual changes.

A useful review starts with simple questions. Did the AI change only the expected files? Does the feature behave as requested? Were new packages added? Are errors handled? Can you explain the main data flow? Do the tests prove anything useful?

This article applies those questions to the Personal Reading List Tracker.

What You Will Learn

By the end of this guide, you should be able to:

  • review the scope of an AI-generated change;
  • inspect modified and untracked files with Git;
  • compare the implementation with the original prompt;
  • trace a simple user action through the code;
  • identify unnecessary dependencies and complexity;
  • check input validation and error handling;
  • recognize common security and privacy problems;
  • test normal, invalid, and edge-case behavior;
  • ask the AI for explanations without accepting them as proof;
  • decide whether to keep, revise, or reject a change.

Review the Change, Not the AI’s Confidence

AI coding assistants often provide a summary after completing a task.

A summary might say:

Implemented book creation with input validation, rendering, and accessible error handling. The feature is ready to use.

That summary is useful as a starting point.

It is not evidence that the feature works.

The assistant may have:

  • misunderstood the requirement;
  • changed unrelated files;
  • introduced a package;
  • missed an edge case;
  • created invalid HTML;
  • hidden an error;
  • described intended behavior rather than actual behavior.

Your review should rely on:

  • the original task;
  • the Git diff;
  • the current files;
  • observed application behavior;
  • test results.

The AI explains what it believes it did. Git shows what changed.

Begin with the Original Task

Before reading the code, return to the prompt or acceptance criteria.

Suppose the task was:

Implement Add Book form submission in app.js.

Requirements:
- Trim title and author.
- Reject empty and whitespace-only titles.
- Allow an empty author.
- Save id, title, author, status, and createdAt.
- Render the new book immediately.
- Clear title and author after success.

Constraints:
- Modify only app.js.
- Do not add localStorage.
- Do not add dependencies.
- Do not add filtering or editing.

Turn this into a review checklist:

[ ] Only app.js changed.
[ ] Title is trimmed.
[ ] Author is trimmed.
[ ] Empty titles are rejected.
[ ] Whitespace-only titles are rejected.
[ ] Empty author is accepted.
[ ] Every required field exists in the book object.
[ ] The new book renders immediately.
[ ] Form fields clear after success.
[ ] localStorage was not added.
[ ] No dependency was added.
[ ] Filtering and editing were not introduced.

This prevents the review from becoming a general judgment about whether the code “looks good.”

You are comparing the result with a defined task.

Step 1: Check Which Files Changed

Start with:

git status

A focused result may look like:

modified: app.js

That matches the task.

A suspicious result may look like:

modified: app.js
modified: index.html
modified: package.json
modified: package-lock.json
untracked: debug.log

The additional files may have valid explanations, but they require review.

Ask:

  • Why did index.html change?
  • Was a new package installed?
  • What created debug.log?
  • Did the task require any of these changes?

Inspect the list before reading individual lines. File scope is one of the easiest ways to detect prompt drift.

Review Untracked Files

Git reports new untracked files separately.

Examples include:

.env
debug.log
test-output.txt
temporary-data.json

Do not ignore them.

An untracked file may contain:

  • credentials;
  • debugging information;
  • generated output;
  • personal data;
  • a file the AI created accidentally.

Inspect it before deciding whether to keep or delete it.

For example:

cat debug.log

Avoid opening unknown executable files or running unfamiliar scripts merely to inspect them.

Step 2: Read the Git Diff

Run:

git diff

For one file:

git diff app.js

A Git diff shows removed and added lines.

Lines beginning with - were removed. Lines beginning with + were added.

For example:

 form.addEventListener("submit", (event) => {
   event.preventDefault();

-  const title = titleInput.value;
+  const title = titleInput.value.trim();
+  const author = authorInput.value.trim();
+
+  if (!title) {
+    showValidationError("Please enter a book title.");
+    return;
+  }
+
+  const book = {
+    id: crypto.randomUUID(),
+    title,
+    author,
+    status: statusInput.value,
+    createdAt: new Date().toISOString()
+  };
+
+  books.push(book);
+  renderBooks();
 });

You do not need to understand every JavaScript detail immediately.

First compare the added lines with the requirement:

  • .trim() supports whitespace removal.
  • if (!title) rejects an empty result.
  • the book object includes the required fields;
  • books.push(book) adds the object;
  • renderBooks() updates the display.

This is a behavior-oriented review.

Step 3: Check Whether the Scope Matches the Prompt

An AI-generated change may satisfy the task while also changing unrelated code.

Suppose the diff includes:

-const books = [];
+class ReadingListManager {
+  constructor(storageAdapter, analyticsClient) {
+    this.books = [];
+    this.storageAdapter = storageAdapter;
+    this.analyticsClient = analyticsClient;
+  }
+}

This may be sophisticated code, but it is not necessary for basic form submission.

Ask:

  • Did the task require a class?
  • Did it require storage?
  • Did it require analytics?
  • Will this make the next feature easier or harder to understand?
  • Can the same behavior be implemented more simply?

Complexity is not automatically quality.

For a small beginner project, the simplest implementation that meets the requirements is often easier to review and maintain.

Look for Unrequested Features

Common examples include:

  • editing;
  • sorting;
  • confirmation dialogs;
  • animations;
  • theme switching;
  • API integration;
  • account-related code;
  • analytics;
  • additional pages.

Even useful features can be inappropriate when they expand the current task.

Record them as future ideas rather than accepting them automatically.

Step 4: Follow One User Action Through the Code

You do not need to read the entire project at once.

Choose one user action and trace it from beginning to end.

For the Add Book feature:

User enters data
→ User submits the form
→ Submit handler reads the inputs
→ Input is validated
→ Book object is created
→ Book is added to the array
→ List is rendered
→ Form is cleared

Now find each step in the code.

Find the Event Handler

Look for something similar to:

bookForm.addEventListener("submit", handleBookSubmit);

Then find the function:

function handleBookSubmit(event) {
  event.preventDefault();
  // ...
}

Check that the event handler is connected to the correct form.

Find Input Reading

The code may read values like this:

const title = titleInput.value.trim();
const author = authorInput.value.trim();
const status = statusInput.value;

Compare the variable names with the HTML IDs and the project data model.

Find Validation

Look for the condition that stops invalid data:

if (!title) {
  showValidationError("Please enter a book title.");
  return;
}

The return matters because it prevents the rest of the function from creating a book.

Find Object Creation

The object should match the defined data model:

const book = {
  id: crypto.randomUUID(),
  title,
  author,
  status,
  createdAt: new Date().toISOString()
};

Find the State Update

books.push(book);

Find Rendering

renderBooks();

Find Form Reset Behavior

titleInput.value = "";
authorInput.value = "";
titleInput.focus();

Tracing one user action helps you understand the code as a sequence of responsibilities rather than a wall of syntax.

Step 5: Check Names Against the Project Brief

Inconsistent names are a common source of bugs.

The project brief defined these status values:

want-to-read
reading
finished

Check the HTML:

<option value="want-to-read">Want to Read</option>
<option value="reading">Reading</option>
<option value="finished">Finished</option>

Then check the JavaScript.

A mismatch such as this can break filtering later:

const allowedStatuses = ["to-read", "in-progress", "done"];

All parts of the application should use the same internal values.

Also check names such as:

title versus bookTitle
author versus authorName
createdAt versus dateAdded
books versus readingItems

Different names are not always wrong. They increase the chance of confusion when they describe the same concept.

Step 6: Review Input Validation

Input validation answers the question:

What happens when the user provides unexpected data?

For the reading list form, test at least:

  • a normal title;
  • a title with surrounding spaces;
  • an empty title;
  • a whitespace-only title;
  • an empty author;
  • a long title;
  • special characters.

The code should not assume that every input is perfect.

Check Validation Order

Validation should occur before changing the application state.

A risky order is:

books.push(book);

if (!book.title) {
  showError("Title is required.");
}

The invalid book has already been added.

A safer order is:

if (!title) {
  showError("Title is required.");
  return;
}

books.push(book);

Check Whether Validation Matches the Requirement

The requirement says whitespace-only titles are invalid.

This check is insufficient:

if (titleInput.value === "") {
  return;
}

A value containing three spaces is not equal to an empty string.

This is stronger:

const title = titleInput.value.trim();

if (!title) {
  return;
}

Do Not Add Validation Without User Feedback

Rejecting input silently can confuse users.

A validation message should explain what needs correction.

For example:

Please enter a book title.

The message should not expose internal code details.

Step 7: Check Error Handling

Not every feature needs complicated error handling.

It should still fail in an understandable way.

For example, later local storage code may include:

function loadBooks() {
  const savedBooks = localStorage.getItem(STORAGE_KEY);

  if (!savedBooks) {
    return [];
  }

  try {
    return JSON.parse(savedBooks);
  } catch (error) {
    console.warn("Saved reading-list data could not be loaded.");
    return [];
  }
}

Review questions include:

  • What operation can fail?
  • Does failure break the whole page?
  • Does the code recover safely?
  • Does the error message reveal sensitive data?
  • Does the catch block hide a serious problem?

Avoid empty catch blocks:

try {
  return JSON.parse(savedBooks);
} catch (error) {
}

This hides the failure and makes debugging harder.

Also avoid printing entire sensitive objects unnecessarily:

console.error("Failed data:", savedBooks);

The reading list may not be highly sensitive, but logging raw user data is still a poor default.

Step 8: Look for Unnecessary Dependencies

Check:

git diff package.json

and:

git diff package-lock.json

If the project previously had no package files and now contains them, ask why.

A simple feature should not require a large dependency for:

  • trimming strings;
  • generating basic IDs;
  • filtering arrays;
  • storing JSON;
  • formatting simple dates.

Dependencies can be appropriate, but each one adds:

  • installation work;
  • version management;
  • security updates;
  • licensing considerations;
  • build complexity;
  • future maintenance.

For this project, plain browser APIs are sufficient for many core features.

Do not approve a package only because the AI says it is popular or convenient.

Step 9: Look for Security and Privacy Problems

A beginner project can still introduce unsafe patterns.

Check for:

  • hard-coded API keys;
  • passwords;
  • tokens;
  • private URLs;
  • personal information;
  • unsafe HTML insertion;
  • unnecessary external scripts;
  • uncontrolled network requests.

Review HTML Insertion

Suppose the AI renders book titles with:

bookList.innerHTML += `
  <li>
    <h2>${book.title}</h2>
    <p>${book.author}</p>
  </li>
`;

This inserts user-provided text into HTML.

A malicious or accidental string containing HTML could be interpreted by the browser.

A safer pattern for user-provided text is:

const titleElement = document.createElement("h2");
titleElement.textContent = book.title;

textContent treats the value as text rather than markup.

You do not need to become a browser-security expert to ask:

Is user input being inserted as HTML or as plain text?

That question catches an important class of problems.

Check for Secrets

Never accept code like:

const API_KEY = "real-secret-key";

inside a browser application or committed source file.

Client-side JavaScript is visible to users.

The reading list project does not require an API key in its first version.

Step 10: Check Whether Existing Behavior Was Preserved

A new feature can work while breaking an old one.

Suppose book removal now works, but status filtering stops working because removal calls:

renderBooks(books);

instead of respecting the selected filter.

Review previous acceptance criteria, not only the newest feature.

For each change, test:

  • the new behavior;
  • one normal existing behavior;
  • one related edge case.

When adding removal, test:

New behavior:
Remove one selected book.

Existing behavior:
Add a book.

Related behavior:
Remove a book while a filter is active.

This is a basic regression check.

A regression is a previously working behavior that becomes broken after a new change.

Step 11: Run the Application Yourself

Do not rely only on generated test instructions.

Open the application and perform the user workflow.

For the Add Book feature:

  1. Add a normal book.
  2. Confirm it appears once.
  3. Check that the title and author display correctly.
  4. Confirm the selected status is shown.
  5. Submit an empty title.
  6. Submit spaces only.
  7. Leave the author empty.
  8. Add another book.
  9. Confirm the first book remains.
  10. Refresh only if persistence is already part of the project.

Observe both the interface and the browser console.

An application can look correct while reporting JavaScript errors.

Step 12: Review Automated Tests Carefully

AI-generated tests can also be wrong.

A test may:

  • check the implementation rather than behavior;
  • assert a meaningless result;
  • mock away the feature being tested;
  • omit edge cases;
  • pass even when the feature is broken;
  • test a function that is never used.

For example:

test("adds a book", () => {
  expect(true).toBe(true);
});

The test passes, but proves nothing about adding books.

A meaningful test might check:

test("rejects a whitespace-only title", () => {
  expect(validateTitle("   ")).toBe(false);
});

Even then, confirm that the application actually uses validateTitle.

Tests are evidence only when they exercise relevant behavior.

Step 13: Ask the AI to Explain the Change

When a section is unclear, ask focused questions.

For example:

Explain how handleBookSubmit prevents whitespace-only titles. Refer to the exact lines involved and do not suggest changes yet.

Or:

Trace what happens from clicking Add Book to the new item appearing in the list. Name each function in order.

Or:

Why did you add this dependency? Explain what browser feature would be insufficient without it.

These questions can improve understanding.

The explanation still needs verification.

An AI assistant may explain the code it intended to write rather than the code currently present. Compare the answer with the actual file.

Step 14: Use Simple Static Checks

Your project may already include commands for checking code quality.

Examples include:

npm test
npm run lint
npm run format:check

Run only commands documented by the project.

Do not install a large toolchain during a small feature review unless that change is planned separately.

For a plain HTML, CSS, and JavaScript project, you can also check:

  • browser console errors;
  • broken links;
  • invalid element IDs;
  • duplicate IDs;
  • missing script files;
  • obvious syntax errors;
  • keyboard navigation;
  • narrow-screen layout.

Automated tools support review. They do not replace reading and testing.

Step 15: Review the Staged Diff Before Committing

After fixing and testing the change, stage only the intended files.

For example:

git add app.js

Then run:

git diff --staged

This shows exactly what the commit will contain.

Check again:

  • Are only approved files staged?
  • Does every changed line support the task?
  • Are debugging statements still present?
  • Are commented-out experiments included?
  • Are credentials or private values present?
  • Does the commit contain unrelated formatting?

Only then create the commit.

A Beginner-Friendly Review Framework

Use the following framework for every AI-generated feature.

1. Intent

Ask:

  • What was the requested outcome?
  • Which acceptance criteria apply?
  • What was explicitly excluded?

2. Scope

Ask:

  • Which files changed?
  • Were new files created?
  • Were packages or configuration files modified?
  • Did the change expand beyond the prompt?

3. Behavior

Ask:

  • Does the normal user workflow work?
  • What happens with invalid input?
  • What happens with empty data?
  • Does existing behavior still work?

4. Data

Ask:

  • Which values are read?
  • Which values are changed?
  • Does the object match the data model?
  • Is data lost, duplicated, or modified unexpectedly?

5. Complexity

Ask:

  • Is the solution larger than necessary?
  • Were new abstractions introduced?
  • Could a beginner explain the main flow?
  • Was a dependency added without a clear need?

6. Safety

Ask:

  • Are secrets present?
  • Is user input inserted as HTML?
  • Are unexpected network requests made?
  • Is private data logged?

7. Evidence

Ask:

  • What tests were run?
  • Did I observe the behavior myself?
  • Does the Git diff match the summary?
  • Is the staged change ready to become history?

You may remember this as:

Intent
→ Scope
→ Behavior
→ Data
→ Complexity
→ Safety
→ Evidence

Example Review: Add Book Feature

Suppose the AI reports that it implemented the Add Book feature.

Original Requirement

Allow a user to add a book with a required title, optional author, selected status, unique ID, and creation date.

Git Status

modified: app.js

The file scope is correct.

Diff Findings

You observe that:

  • title and author are trimmed;
  • empty titles are rejected;
  • a book object is created;
  • the object is pushed into the array;
  • rendering is called;
  • the form is cleared.

You also notice:

bookList.innerHTML += `<li>${book.title}</li>`;

Because book.title comes from the user, you ask the AI to replace this with DOM creation and textContent.

Manual Tests

You test:

Title: Kindred
Author: Octavia E. Butler
Status: Finished

The book appears.

You then test:

Title: three spaces
Author: empty

The book is rejected.

You add two books and confirm both remain visible.

Decision

The original change is close, but the HTML insertion should be corrected before committing.

This is a successful review.

You did not need to understand every detail of JavaScript. You compared the behavior, traced the data, identified a risky pattern, and requested a focused revision.

When to Accept the Change

A change may be ready to keep when:

  • it matches the requested behavior;
  • the file scope is appropriate;
  • no unexplained dependency was added;
  • the main flow is understandable;
  • invalid input is handled;
  • existing behavior still works;
  • no obvious secret or unsafe pattern is present;
  • tests or manual checks provide evidence;
  • the staged diff contains only intended work.

Understanding does not mean memorizing every syntax rule.

You should be able to explain the change at a practical level.

For example:

The form handler trims the inputs, rejects a blank title, creates a book object, adds it to the in-memory array, rerenders the list, and clears the successful input fields.

That level of understanding is enough for a small feature review.

When to Request a Revision

Ask for a smaller revision when:

  • the feature mostly works but misses an acceptance criterion;
  • one unsafe pattern is present;
  • the code is unnecessarily complex;
  • an unrelated file changed;
  • a package was added without need;
  • naming conflicts with the project brief;
  • the error message is unclear;
  • tests omit an important edge case.

A focused revision prompt might be:

Revise only the rendering code in app.js.

Replace user-provided title and author insertion through innerHTML with DOM elements using textContent.

Do not change the data model, styling, filtering, or form behavior.

After the change, show the exact diff and explain how the rendering behavior remains the same.

When to Reject or Revert the Change

Reject the change when:

  • the scope is much larger than requested;
  • the implementation cannot be explained;
  • the project no longer runs;
  • secrets were introduced;
  • dependencies or architecture changed without approval;
  • the AI repeatedly ignores boundaries;
  • fixing the change would require many more speculative edits;
  • a smaller implementation would be safer.

You can restore a tracked file with:

git restore app.js

Review the diff first because this discards uncommitted changes in that file.

Rejecting generated code is not wasted work.

It tells you that the task, context, or approach needs to be reduced.

Practical Advice from Code Review Work

Experienced reviewers do not begin by trying to prove that every line is perfect.

They begin by understanding the change’s intent and risk.

A one-line text change and an authentication change should not receive the same review depth.

For AI-generated code, useful risk questions include:

  • Does this change handle user input?
  • Does it modify stored data?
  • Does it affect authentication or permissions?
  • Does it introduce a dependency?
  • Does it make a network request?
  • Does it change configuration?
  • Is it difficult to reverse?

The Personal Reading List Tracker is intentionally low risk, but the same review habits scale to larger projects.

The most valuable beginner habit is to stop treating code generation as completion.

Generated code is a proposal until it has been reviewed and verified.

A Reusable Review Checklist

Use this checklist after every AI coding task.

Before Reading the Code

  • Reopen the original prompt.
  • Identify the expected behavior.
  • Identify prohibited changes.
  • Confirm the intended files.

Review the Repository

  • Run git status.
  • Inspect all modified files.
  • Inspect untracked files.
  • Check package and configuration changes.
  • Run git diff.

Review the Implementation

  • Trace the main user action.
  • Compare names with the project brief.
  • Check input validation.
  • Check error handling.
  • Look for unnecessary complexity.
  • Look for unsafe HTML insertion.
  • Check for secrets and private data.

Verify the Result

  • Test normal input.
  • Test invalid input.
  • Test one edge case.
  • Check existing related behavior.
  • Inspect browser or terminal errors.
  • Run existing tests.

Before Committing

  • Stage specific files.
  • Run git diff --staged.
  • Remove debugging leftovers.
  • Confirm the commit has one clear purpose.
  • Commit only after the evidence matches the requirement.

Conclusion

You do not need expert-level knowledge to begin reviewing AI-generated code.

A practical review compares the original task with the actual repository changes. Start with file scope, read the Git diff, trace one user action, check the data model, test invalid input, inspect dependencies, look for unsafe patterns, and verify the result yourself.

The central question is not:

Could I have written every line?

A better question is:

Can I explain what this change does, confirm that it satisfies the requirement, and identify the main risks?

When the answer is no, the code is not ready to commit.

The next article will focus on debugging AI-generated code. We will learn how to reproduce a problem, capture useful evidence, identify the most recent relevant change, test one hypothesis at a time, and avoid making a broken session worse with repeated speculative prompts.

Frequently Asked Questions

Can I ask the AI to review its own code?

Yes, but treat the response as another opinion rather than final proof.

Ask for specific explanations, edge cases, and possible risks, then verify the claims with the Git diff and actual behavior.

What should I do when the code works but I do not understand it?

Do not commit it immediately.

Ask the AI to explain the data flow, simplify unnecessary abstractions, and identify the purpose of each function. Keep the change only when you can explain its main behavior and risks.

Do I need to review generated tests too?

Yes.

AI-generated tests can be incomplete or meaningless. Confirm that they exercise real behavior, include relevant edge cases, and fail when the feature is intentionally broken.

Comments

Popular posts from this blog

Claude Code Multi-Agent Setup for Beginners: Models, Roles, and Your First Project

The Limits and Future of Vibe Coding

How to Test and Deploy Your First Vibe-Coded App