Agent Skills in Claude Code and Visual Studio Code: Setup, Benefits, Risks, and a Step-by-Step Example
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.
By the end of this guide, you should be able to:
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:
Your review should rely on:
The AI explains what it believes it did. Git shows what changed.
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.
Start with:
git statusA focused result may look like:
modified: app.jsThat matches the task.
A suspicious result may look like:
modified: app.js
modified: index.html
modified: package.json
modified: package-lock.json
untracked: debug.logThe additional files may have valid explanations, but they require review.
Ask:
index.html change?debug.log?Inspect the list before reading individual lines. File scope is one of the easiest ways to detect prompt drift.
Git reports new untracked files separately.
Examples include:
.env
debug.log
test-output.txt
temporary-data.jsonDo not ignore them.
An untracked file may contain:
Inspect it before deciding whether to keep or delete it.
For example:
cat debug.logAvoid opening unknown executable files or running unfamiliar scripts merely to inspect them.
Run:
git diffFor one file:
git diff app.jsA 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.books.push(book) adds the object;renderBooks() updates the display.This is a behavior-oriented review.
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:
Complexity is not automatically quality.
For a small beginner project, the simplest implementation that meets the requirements is often easier to review and maintain.
Common examples include:
Even useful features can be inappropriate when they expand the current task.
Record them as future ideas rather than accepting them automatically.
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 clearedNow find each step in the code.
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.
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.
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.
The object should match the defined data model:
const book = {
id: crypto.randomUUID(),
title,
author,
status,
createdAt: new Date().toISOString()
};books.push(book);renderBooks();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.
Inconsistent names are a common source of bugs.
The project brief defined these status values:
want-to-read
reading
finishedCheck 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 readingItemsDifferent names are not always wrong. They increase the chance of confusion when they describe the same concept.
Input validation answers the question:
What happens when the user provides unexpected data?
For the reading list form, test at least:
The code should not assume that every input is perfect.
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);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;
}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.
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:
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.
Check:
git diff package.jsonand:
git diff package-lock.jsonIf the project previously had no package files and now contains them, ask why.
A simple feature should not require a large dependency for:
Dependencies can be appropriate, but each one adds:
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.
A beginner project can still introduce unsafe patterns.
Check for:
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.
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.
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:
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.
Do not rely only on generated test instructions.
Open the application and perform the user workflow.
For the Add Book feature:
Observe both the interface and the browser console.
An application can look correct while reporting JavaScript errors.
AI-generated tests can also be wrong.
A test may:
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.
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.
Your project may already include commands for checking code quality.
Examples include:
npm test
npm run lint
npm run format:checkRun 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:
Automated tools support review. They do not replace reading and testing.
After fixing and testing the change, stage only the intended files.
For example:
git add app.jsThen run:
git diff --stagedThis shows exactly what the commit will contain.
Check again:
Only then create the commit.
Use the following framework for every AI-generated feature.
Ask:
Ask:
Ask:
Ask:
Ask:
Ask:
Ask:
You may remember this as:
Intent
→ Scope
→ Behavior
→ Data
→ Complexity
→ Safety
→ EvidenceSuppose the AI reports that it implemented the Add Book feature.
Allow a user to add a book with a required title, optional author, selected status, unique ID, and creation date.modified: app.jsThe file scope is correct.
You observe that:
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.
You test:
Title: Kindred
Author: Octavia E. Butler
Status: FinishedThe book appears.
You then test:
Title: three spaces
Author: emptyThe book is rejected.
You add two books and confirm both remain visible.
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.
A change may be ready to keep when:
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.
Ask for a smaller revision when:
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.Reject the change when:
You can restore a tracked file with:
git restore app.jsReview 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.
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:
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.
Use this checklist after every AI coding task.
git status.git diff.git diff --staged.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.
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.
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.
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
Post a Comment