Skip to content

Commit 7767fa7

Browse files
authored
feat: enhance walkthrough file discovery and update documentation (#21)
Implemented recursive discovery of walkthrough files, allowing `.walkthrough.json` and `.json` files in `walkthroughs/` directories at any depth.
1 parent c4e45e2 commit 7767fa7

5 files changed

Lines changed: 89 additions & 62 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ The extension uses custom URI schemes for virtual documents:
7575

7676
## Walkthrough files
7777

78-
Walkthroughs are discovered from:
78+
Walkthroughs are discovered recursively from anywhere in the workspace:
7979

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

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

docs/schema.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ This document defines the schema for walkthrough JSON files used by the Virgil e
44

55
## File Naming and Locations
66

7-
Virgil discovers walkthrough files in two locations:
7+
Virgil recursively discovers walkthrough files anywhere in the workspace:
88

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

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

17-
**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.
15+
**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.
1816

1917
## Schema
2018

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

319317
## Markdown Format

src/WalkthroughProvider.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import { execSync } from 'child_process';
5+
import { discoverWalkthroughFiles } from './discovery';
56
import {
67
Walkthrough,
78
WalkthroughStep,
@@ -156,33 +157,7 @@ export class WalkthroughProvider implements vscode.TreeDataProvider<WalkthroughT
156157
}
157158

158159
getAvailableWalkthroughs(): string[] {
159-
const walkthroughFiles: string[] = [];
160-
161-
try {
162-
// Check for .walkthrough.json at root
163-
const rootWalkthroughPath = path.join(this.workspaceRoot, '.walkthrough.json');
164-
if (fs.existsSync(rootWalkthroughPath)) {
165-
walkthroughFiles.push('.walkthrough.json');
166-
}
167-
} catch {
168-
// Ignore errors
169-
}
170-
171-
try {
172-
// Check for all .json files in walkthroughs/ directory
173-
const walkthroughsDir = path.join(this.workspaceRoot, 'walkthroughs');
174-
if (fs.existsSync(walkthroughsDir) && fs.statSync(walkthroughsDir).isDirectory()) {
175-
const files = fs.readdirSync(walkthroughsDir);
176-
const jsonFiles = files.filter((f) => f.endsWith('.json'));
177-
for (const jsonFile of jsonFiles) {
178-
walkthroughFiles.push(path.join('walkthroughs', jsonFile));
179-
}
180-
}
181-
} catch {
182-
// Ignore errors
183-
}
184-
185-
return walkthroughFiles;
160+
return discoverWalkthroughFiles(this.workspaceRoot);
186161
}
187162

188163
getCurrentFile(): string | undefined {

src/discovery.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
/** Directories to skip during recursive walkthrough discovery. */
5+
const SKIP_DIRS = new Set([
6+
'node_modules',
7+
'.git',
8+
'out',
9+
'dist',
10+
'.next',
11+
'.cache',
12+
'build',
13+
'coverage',
14+
'.vscode',
15+
]);
16+
17+
/**
18+
* Recursively discover walkthrough files under `rootDir`.
19+
*
20+
* Matches:
21+
* - Any `.walkthrough.json` file at any depth
22+
* - Any `.json` file inside a directory named `walkthroughs` at any depth
23+
*
24+
* Returns paths relative to `rootDir`, sorted alphabetically.
25+
*/
26+
export function discoverWalkthroughFiles(rootDir: string): string[] {
27+
const results: string[] = [];
28+
29+
function walk(dir: string): void {
30+
let entries: fs.Dirent[];
31+
try {
32+
entries = fs.readdirSync(dir, { withFileTypes: true });
33+
} catch {
34+
return;
35+
}
36+
37+
for (const entry of entries) {
38+
if (entry.isDirectory()) {
39+
if (SKIP_DIRS.has(entry.name)) {
40+
continue;
41+
}
42+
43+
if (entry.name === 'walkthroughs') {
44+
// Collect JSON files directly inside this walkthroughs directory
45+
const walkthroughsDir = path.join(dir, entry.name);
46+
try {
47+
const files = fs.readdirSync(walkthroughsDir);
48+
for (const f of files) {
49+
if (f.endsWith('.json')) {
50+
results.push(path.relative(rootDir, path.join(walkthroughsDir, f)));
51+
}
52+
}
53+
} catch {
54+
// Ignore unreadable directories
55+
}
56+
}
57+
58+
// Continue recursing into all non-skipped directories (including walkthroughs/)
59+
walk(path.join(dir, entry.name));
60+
} else if (entry.name === '.walkthrough.json') {
61+
results.push(path.relative(rootDir, path.join(dir, entry.name)));
62+
}
63+
}
64+
}
65+
66+
walk(rootDir);
67+
results.sort();
68+
return results;
69+
}

src/extension.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
isMarkdownFile,
1010
normalizeLocationPath,
1111
} from './types';
12+
import { discoverWalkthroughFiles } from './discovery';
1213
import { WalkthroughProvider } from './WalkthroughProvider';
1314
import { StepDetailPanel } from './StepDetailPanel';
1415
import { HighlightManager, HighlightColor } from './HighlightManager';
@@ -186,28 +187,12 @@ export function activate(context: vscode.ExtensionContext) {
186187
)
187188
);
188189

189-
// Find walkthrough file (.walkthrough.json at root or any .json in walkthroughs/)
190+
// Find walkthrough file (any .walkthrough.json or walkthroughs/*.json at any depth)
190191
const findWalkthroughFile = (): string | undefined => {
191-
// Check for .walkthrough.json at root
192-
const rootWalkthroughPath = path.join(workspaceRoot, '.walkthrough.json');
193-
if (fs.existsSync(rootWalkthroughPath)) {
194-
return rootWalkthroughPath;
192+
const files = discoverWalkthroughFiles(workspaceRoot);
193+
if (files.length > 0) {
194+
return path.join(workspaceRoot, files[0]);
195195
}
196-
197-
// Check for any .json file in walkthroughs/ directory
198-
const walkthroughsDir = path.join(workspaceRoot, 'walkthroughs');
199-
if (fs.existsSync(walkthroughsDir) && fs.statSync(walkthroughsDir).isDirectory()) {
200-
try {
201-
const files = fs.readdirSync(walkthroughsDir);
202-
const jsonFile = files.find((f) => f.endsWith('.json'));
203-
if (jsonFile) {
204-
return path.join(walkthroughsDir, jsonFile);
205-
}
206-
} catch {
207-
// Ignore errors
208-
}
209-
}
210-
211196
return undefined;
212197
};
213198

@@ -661,10 +646,10 @@ export function activate(context: vscode.ExtensionContext) {
661646
})
662647
);
663648

664-
// Watch for walkthrough file changes (both .walkthrough.json at root and walkthroughs/*.json)
649+
// Watch for walkthrough file changes at any depth
665650
const watcherPatterns = [
666-
new vscode.RelativePattern(workspaceRoot, '.walkthrough.json'),
667-
new vscode.RelativePattern(workspaceRoot, 'walkthroughs/*.json'),
651+
new vscode.RelativePattern(workspaceRoot, '**/.walkthrough.json'),
652+
new vscode.RelativePattern(workspaceRoot, '**/walkthroughs/*.json'),
668653
];
669654

670655
// Create watchers for both patterns

0 commit comments

Comments
 (0)