Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ The extension uses custom URI schemes for virtual documents:

## Walkthrough files

Walkthroughs are discovered from:
Walkthroughs are discovered recursively from anywhere in the workspace:

- `.walkthrough.json` at workspace root
- Any `.json` files in `walkthroughs/` directory
- Any `.walkthrough.json` file at any depth
- Any `.json` files inside any `walkthroughs/` directory at any depth

Markdown files can be converted to JSON using the `Virgil: Convert Markdown to Walkthrough` command.

Expand Down
16 changes: 7 additions & 9 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ This document defines the schema for walkthrough JSON files used by the Virgil e

## File Naming and Locations

Virgil discovers walkthrough files in two locations:
Virgil recursively discovers walkthrough files anywhere in the workspace:

1. **Root location**: `.walkthrough.json` at the workspace root
- Example: `.walkthrough.json`
- This is the traditional location for a single walkthrough
1. **`.walkthrough.json` files**: Any file named `.walkthrough.json` at any depth
- Examples: `.walkthrough.json`, `docs/.walkthrough.json`, `packages/frontend/.walkthrough.json`

2. **Walkthroughs directory**: Any `.json` file in the `walkthroughs/` directory at the workspace root
- Examples: `walkthroughs/architecture.json`, `walkthroughs/pr-123.json`, `walkthroughs/onboarding.json`
- This allows organizing multiple walkthroughs in a dedicated directory
2. **`walkthroughs/` directories**: Any `.json` file inside any directory named `walkthroughs/` at any depth
- Examples: `walkthroughs/architecture.json`, `docs/walkthroughs/onboarding.json`, `packages/api/walkthroughs/pr-123.json`

**Note**: Files in the `walkthroughs/` directory do not need the `.walkthrough.json` suffix - any `.json` file is recognized. The extension automatically watches both locations for changes.
**Note**: Files in `walkthroughs/` directories do not need the `.walkthrough.json` suffix any `.json` file is recognized. The extension automatically watches both patterns for changes. Common non-content directories (`node_modules`, `.git`, `out`, `dist`, etc.) are skipped during discovery.

## Schema

Expand Down Expand Up @@ -313,7 +311,7 @@ For PR reviews or comparing changes, use `base_location` with a base reference:
- Use `metadata` for any custom fields (PR numbers, recommendations, tags, etc.)
- The `body` field supports Markdown for rich formatting
- Comments are persisted to the JSON file when added through the extension UI
- Multiple walkthrough files can coexist in a workspace (in `walkthroughs/` directory or as `.walkthrough.json` at root)
- Multiple walkthrough files can coexist in a workspace (in `walkthroughs/` directories or as `.walkthrough.json` files at any depth)
- You can select walkthroughs via the "Select Walkthrough" command, which also allows selecting Markdown files for conversion

## Markdown Format
Expand Down
29 changes: 2 additions & 27 deletions src/WalkthroughProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';
import { discoverWalkthroughFiles } from './discovery';
import {
Walkthrough,
WalkthroughStep,
Expand Down Expand Up @@ -156,33 +157,7 @@ export class WalkthroughProvider implements vscode.TreeDataProvider<WalkthroughT
}

getAvailableWalkthroughs(): string[] {
const walkthroughFiles: string[] = [];

try {
// Check for .walkthrough.json at root
const rootWalkthroughPath = path.join(this.workspaceRoot, '.walkthrough.json');
if (fs.existsSync(rootWalkthroughPath)) {
walkthroughFiles.push('.walkthrough.json');
}
} catch {
// Ignore errors
}

try {
// Check for all .json files in walkthroughs/ directory
const walkthroughsDir = path.join(this.workspaceRoot, 'walkthroughs');
if (fs.existsSync(walkthroughsDir) && fs.statSync(walkthroughsDir).isDirectory()) {
const files = fs.readdirSync(walkthroughsDir);
const jsonFiles = files.filter((f) => f.endsWith('.json'));
for (const jsonFile of jsonFiles) {
walkthroughFiles.push(path.join('walkthroughs', jsonFile));
}
}
} catch {
// Ignore errors
}

return walkthroughFiles;
return discoverWalkthroughFiles(this.workspaceRoot);
}

getCurrentFile(): string | undefined {
Expand Down
69 changes: 69 additions & 0 deletions src/discovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as fs from 'fs';
import * as path from 'path';

/** Directories to skip during recursive walkthrough discovery. */
const SKIP_DIRS = new Set([
'node_modules',
'.git',
'out',
'dist',
'.next',
'.cache',
'build',
'coverage',
'.vscode',
]);

/**
* Recursively discover walkthrough files under `rootDir`.
*
* Matches:
* - Any `.walkthrough.json` file at any depth
* - Any `.json` file inside a directory named `walkthroughs` at any depth
*
* Returns paths relative to `rootDir`, sorted alphabetically.
*/
export function discoverWalkthroughFiles(rootDir: string): string[] {
const results: string[] = [];

function walk(dir: string): void {
let entries: fs.Dirent[];
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return;
}

for (const entry of entries) {
if (entry.isDirectory()) {
if (SKIP_DIRS.has(entry.name)) {
continue;
}

if (entry.name === 'walkthroughs') {
// Collect JSON files directly inside this walkthroughs directory
const walkthroughsDir = path.join(dir, entry.name);
try {
const files = fs.readdirSync(walkthroughsDir);
for (const f of files) {
if (f.endsWith('.json')) {
results.push(path.relative(rootDir, path.join(walkthroughsDir, f)));
}
}
} catch {
// Ignore unreadable directories
}
}

// Continue recursing into all non-skipped directories (including walkthroughs/)
walk(path.join(dir, entry.name));
} else if (entry.name === '.walkthrough.json') {
results.push(path.relative(rootDir, path.join(dir, entry.name)));
}
}
}

walk(rootDir);
results.sort();
return results;
}
31 changes: 8 additions & 23 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isMarkdownFile,
normalizeLocationPath,
} from './types';
import { discoverWalkthroughFiles } from './discovery';
import { WalkthroughProvider } from './WalkthroughProvider';
import { StepDetailPanel } from './StepDetailPanel';
import { HighlightManager, HighlightColor } from './HighlightManager';
Expand Down Expand Up @@ -186,28 +187,12 @@ export function activate(context: vscode.ExtensionContext) {
)
);

// Find walkthrough file (.walkthrough.json at root or any .json in walkthroughs/)
// Find walkthrough file (any .walkthrough.json or walkthroughs/*.json at any depth)
const findWalkthroughFile = (): string | undefined => {
// Check for .walkthrough.json at root
const rootWalkthroughPath = path.join(workspaceRoot, '.walkthrough.json');
if (fs.existsSync(rootWalkthroughPath)) {
return rootWalkthroughPath;
const files = discoverWalkthroughFiles(workspaceRoot);
if (files.length > 0) {
return path.join(workspaceRoot, files[0]);
}

// Check for any .json file in walkthroughs/ directory
const walkthroughsDir = path.join(workspaceRoot, 'walkthroughs');
if (fs.existsSync(walkthroughsDir) && fs.statSync(walkthroughsDir).isDirectory()) {
try {
const files = fs.readdirSync(walkthroughsDir);
const jsonFile = files.find((f) => f.endsWith('.json'));
if (jsonFile) {
return path.join(walkthroughsDir, jsonFile);
}
} catch {
// Ignore errors
}
}

return undefined;
};

Expand Down Expand Up @@ -661,10 +646,10 @@ export function activate(context: vscode.ExtensionContext) {
})
);

// Watch for walkthrough file changes (both .walkthrough.json at root and walkthroughs/*.json)
// Watch for walkthrough file changes at any depth
const watcherPatterns = [
new vscode.RelativePattern(workspaceRoot, '.walkthrough.json'),
new vscode.RelativePattern(workspaceRoot, 'walkthroughs/*.json'),
new vscode.RelativePattern(workspaceRoot, '**/.walkthrough.json'),
new vscode.RelativePattern(workspaceRoot, '**/walkthroughs/*.json'),
];

// Create watchers for both patterns
Expand Down
Loading