A newcomer opening a repository for the first time faces a folder tree and a README that went stale a year ago. A code tour is a way to hand them a route instead: a short, ordered series of stops, each tied to a place in the code, each with a few sentences of explanation shown right beside it, much like a guided museum route. The route lives in the repository as a small JSON file, so it is reviewed, versioned and shared like any other file. The catch is that a stop points at a place, and code moves. This guide explains what the file contains, how to write stops that keep landing where you meant, what makes a stop worth reading, and a short check you can run in CI that fails the moment a stop points at nothing.

What a CodeTour is

The tool in question is an extension for Visual Studio Code. The marketplace page describes it as an extension for recording and playing back guided tours of codebases directly inside the editor (Visual Studio Marketplace). The README adds the intended use: a tour is a series of interactive steps, each associated with a directory, or a file and line, with a description of that code, so a developer can clone a repository and start learning it without a contributing guide or help from a colleague (CodeTour README).

You can record a tour from inside the editor, clicking the comment bar on a line and typing the description in Markdown. But under the recorder is a plain file. The README says the tour is written as JSON into the .tours directory of the workspace, that the file is simple enough to edit by hand, and that .vscode/tours and .github/tours are accepted too, along with some single well-known file names such as main.tour (CodeTour README). Subfolders inside .tours are discovered as well. That matters for anyone generating tours from a script or a model: you do not need the recorder, you need a valid file in the right place.

The shape of the file

The published JSON schema (a JSON format description) marks two top-level fields as required: title and steps (CodeTour schema). Optional top-level fields include description, ref, isPrimary, nextTour and when. Inside steps, the only field the schema requires on every step is description. The location fields are all optional:

  • file: a path measured from the top folder of the workspace;
  • directory: a folder path, also relative to the root (the README notes it wins when both directory and file are set);
  • line: the line number, where the first line is 1;
  • pattern: a regular expression, used only when line is not set;
  • selection: a start and end, each with a line and a character, for highlighting a span;
  • title: an optional name for the step in the tree view.

A tiny valid tour looks like this:

{
  "title": "How a request travels",
  "steps": [
    {
      "directory": "src/routes",
      "description": "Every URL is declared in this folder. Start here when you look for who handles a path."
    },
    {
      "file": "src/server.ts",
      "pattern": "app\\.listen",
      "description": "The process starts listening here. Everything above this line is wiring."
    }
  ]
}

A step that names neither a file nor a directory is allowed too. The README calls it a content step, a step with a title and Markdown text but no location, handy as an introduction (CodeTour README).

Writing stops that survive edits

A line number is the most fragile address in a codebase. Add an import near the head of a file and every stop below it is one line off; delete a block and a stop may point past the end. Three habits keep a tour alive.

Prefer a pattern to a line where the format allows it. The schema describes pattern as a regular expression that associates the step with line content instead of position, and says it is only considered when line is not set (CodeTour schema). A pattern such as a function name or a distinctive call follows the code when it moves. Keep it specific: a pattern that matches in several places is a pattern whose landing spot you have not verified, and this guide did not test what the extension does with several matches. Use line when there is no stable text to match, or when you want to point at a blank spot or a region.

Keep the tour short. Every stop is a promise that must stay true, so a tour with many stops decays faster than one with a handful. A newcomer also reads a short tour to the end. Aim for the route a first-week contributor really needs: the entry point, the request or job path, where data is read and written, where configuration lives, how tests run. Leave the long tail to a README or to other tours.

Choose how tightly to pin it. When you record a tour, the extension asks for a git ref to attach it to, and the README describes the trade-off. A tour tied to a commit or a tag cannot drift, because it replays that exact state, but the files are read-only unless the reader is on that commit. A tour with no ref walks through whatever the reader has checked out, so it stays editable but can drift (CodeTour README). For onboarding on a living branch, no ref plus patterns plus a drift check, described below, is usually the practical mix. For a tutorial that must never change, a tag fits.

You can also tell the extension which tour a newcomer should see first. isPrimary marks one tour as the primary one, and the README says a title beginning 1 - or #1 - is treated as primary automatically, while numbered titles in sequence get next-tour navigation, or you can name the follow-up with nextTour (CodeTour README).

What makes a stop useful to a newcomer

A stop is not a code comment. The code is already on screen, so repeating what it says wastes the step. What the newcomer lacks is the reason, the neighbours and the trap.

  • Say why it is here. The cheapest value is a sentence on why this file exists and what would be missing without it.
  • Say what to look at next. Step descriptions are Markdown, and the README documents links to workspace files and references to other steps by number, such as [#2] (CodeTour README). Use them so the tour reads as a story, not a list of locations.
  • Name the trap. The thing a new person gets wrong is rarely visible in the code: an environment variable that must be set, a generated file nobody edits by hand, a test that needs a service running.
  • One idea per stop. Two ideas in one description split attention, and a stop that needs a long essay probably wants to be two stops or a paragraph in the README.
  • Order by the reader's questions. Where does it start, what does it touch, where does it end, how do I run it.

Write in the language your team reads. The format does not care, and a tour in the newcomer's own language is read more carefully than a perfect one in a language they struggle with.

The check that can fail

The README suggests running a task in CI to detect tour drift as pull requests land, and names two ready-made options, one for GitHub Actions and one for Azure Pipelines (CodeTour README). If you want a check of your own, or one that works in any pipeline, the core of it is small: every step that names a file must name a file that exists, and its line must lie inside that file.

import { readFileSync, existsSync } from 'node:fs';
import { globSync } from 'node:fs';

let bad = 0;
for (const tour of globSync('.tours/**/*.tour')) {
  const data = JSON.parse(readFileSync(tour, 'utf8'));
  data.steps.forEach((step, i) => {
    if (!step.file) return;
    if (!existsSync(step.file)) { console.log(`${tour} step ${i + 1}: missing ${step.file}`); bad++; return; }
    if (typeof step.line === 'number') {
      const lines = readFileSync(step.file, 'utf8').split('\n').length;
      if (step.line < 1 || step.line > lines) { console.log(`${tour} step ${i + 1}: line ${step.line} past the ${lines} lines of ${step.file}`); bad++; }
    }
  });
}
process.exitCode = bad ? 1 : 0;

Do not trust it before you have seen it fail. Take a tour that passes, then make three breaks one at a time: rename a file a step points at, change one line to a number far beyond the file length, and delete the block a pattern should match. The first two must turn the script red. The third will not, because the script above does not evaluate patterns, and that is worth knowing: extend the check to test that each pattern matches at least one line of its file, or accept that pattern steps are checked only by a person opening the tour. A check that goes green on the third break is telling you about its own limits, not about the tour.

One more detail: a trailing newline makes a naive split count one extra empty line, so a step pointing at that phantom line passes the script but lands on nothing in the editor. If you care about that edge, count lines the way your editor does.

When this does not apply

Tour files for one Visual Studio Code extension are what this text covers, drawn from the extension's README, its JSON schema and its marketplace page, all read on 30 October 2026. The extension may change its format or defaults, and the schema file is the authority on field names. Nothing here was tested inside the editor: how a pattern with several matches behaves, or how strictly a step pointing past the end of a file is reported, was not tried. Tours are an aid for people using that editor; they do nothing for readers on other tools, and a plain README, an architecture note or a recorded walkthrough may fit your team better (more notes on keeping code and its explanations honest are in our building series). The CI sketch is one possible shape, not part of the extension, and it checks only that places exist, not that the explanation next to them is still true. That second kind of drift needs a human who reads the tour when the code changes.

One tool for the file itself

For the mechanical part, our codetour-file skill takes a walkthrough described in words together with your files and how many lines each has, and returns one tour file, nothing around it. Paths stay relative and spelled as you listed them, and every line number is compared with the length of its file. If a stop names an unlisted file, or a line beyond the end of its file, the skill drops that stop and records it in the tour's description, so the editor never opens a step that leads nowhere. The explanations come from your request: the skill does not see the code, so it adds no statements on how the code behaves.

Read this post as Markdown: /blog/codetour-onboarding-walkthrough.md · Atom feed.