From 31b3fca8c1019ce02cfe96ef4ba405f31264f679 Mon Sep 17 00:00:00 2001 From: Eric Alt <13019253+ealt@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:39:50 -0700 Subject: [PATCH] feat: enhance walkthrough file discovery and update documentation - Implemented recursive discovery of walkthrough files, allowing `.walkthrough.json` and `.json` files in `walkthroughs/` directories at any depth. - Updated related documentation in AGENTS.md and schema.md to reflect the new discovery logic. - Refactored WalkthroughProvider and extension.ts to utilize the new discovery function for improved file handling. --- AGENTS.md | 6 ++-- docs/schema.md | 16 ++++----- src/WalkthroughProvider.ts | 29 ++-------------- src/discovery.ts | 69 ++++++++++++++++++++++++++++++++++++++ src/extension.ts | 31 +++++------------ 5 files changed, 89 insertions(+), 62 deletions(-) create mode 100644 src/discovery.ts diff --git a/AGENTS.md b/AGENTS.md index 22f111b..fd4519f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/docs/schema.md b/docs/schema.md index b33d91f..197ffb8 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -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 @@ -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 diff --git a/src/WalkthroughProvider.ts b/src/WalkthroughProvider.ts index 43e0af0..4410078 100644 --- a/src/WalkthroughProvider.ts +++ b/src/WalkthroughProvider.ts @@ -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, @@ -156,33 +157,7 @@ export class WalkthroughProvider implements vscode.TreeDataProvider 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 { diff --git a/src/discovery.ts b/src/discovery.ts new file mode 100644 index 0000000..7766127 --- /dev/null +++ b/src/discovery.ts @@ -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; +} diff --git a/src/extension.ts b/src/extension.ts index 9bb94a7..c84b8b0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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'; @@ -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; }; @@ -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