Commands Scaffolding Feature
November 24, 2025 · View on GitHub
Overview
This document outlines the design and implementation of the commands scaffolding feature for the agent-rules CLI tool. This feature extends the existing template scaffolding system to manage custom commands/prompts for different AI applications.
Requirements Analysis
Core Requirements
- Template Source: Command templates live in
__template__/{language}/_commands/*.command.md - Target Destinations: Each AI app adapter defines its own commands location:
- GitHub Copilot:
.github/prompts/(with.prompt.mdextension) - Other adapters: Not yet supported (returns null)
- GitHub Copilot:
- File Transformation: Adapters can transform command filenames to match their conventions
- User Choice: CLI prompts users whether to include commands (optional step)
- Adapter Customization: Each adapter specifies:
- Target directory for command files
- Optional filename transformation function
Design Principles
- Optional: Users can opt-out of command scaffolding
- Flexible: Allow different target paths and file naming per AI app
- Non-intrusive: Silently skip if
_commands/directory doesn't exist - Consistent: Follow existing adapter pattern architecture (similar to MCP feature)
Architecture Design
1. Enhanced Base Adapter Interface
export interface CommandsConfig {
/** Target directory for command files (relative to project root) */
targetDirectory: string
/** Function to transform command filename to target filename */
fileNameTransform?: (filename: string) => string
}
export interface ScaffoldInstructions {
aiApp: string
codeLanguage: string
codeTopic: string
includeMcp?: boolean
includeCommands?: boolean // New optional field
}
export abstract class BaseAdapter {
// ... existing methods
/**
* Get commands configuration for this AI app
* Returns null if commands are not supported by this adapter
*/
abstract getCommandsConfig(): CommandsConfig | null
/**
* Process commands configuration (optional override)
* Default implementation handles copying command files with transformation
*/
async processCommandsConfiguration(
scaffoldInstructions: ScaffoldInstructions,
resolvedCommandsTemplateDirectory: string,
resolvedTargetDirectory: string
): Promise<void> {
// Default implementation
}
}
2. Commands Template Resolution
// In main.ts
export async function resolveCommandsTemplateDirectory(
scaffoldInstructions: ScaffoldInstructions
): Promise<string | null> {
const { codeLanguage } = scaffoldInstructions
const currentFileDirectory = resolvePackageRootDirectoryForTemplates()
const commandsTemplateDirectory = path.join(
currentFileDirectory,
templateRoot,
codeLanguage,
'_commands'
)
const resolvedCommandsTemplateDirectory = path.resolve(commandsTemplateDirectory)
try {
const templateStats = await fs.stat(resolvedCommandsTemplateDirectory)
if (!templateStats.isDirectory()) {
return null
}
return resolvedCommandsTemplateDirectory
} catch (error) {
// Silently return null if directory doesn't exist
return null
}
}
3. Enhanced Scaffolding Flow
export async function scaffoldAiAppInstructions(
scaffoldInstructions: ScaffoldInstructions
): Promise<void> {
// ... existing instruction scaffolding logic
// ... existing MCP scaffolding logic
// Process commands configuration if requested and supported
if (scaffoldInstructions.includeCommands) {
const commandsConfig = adapter.getCommandsConfig()
if (commandsConfig) {
debug(`Processing commands configuration for ${aiApp}`)
const resolvedCommandsTemplateDirectory = await resolveCommandsTemplateDirectory(
scaffoldInstructions
)
if (resolvedCommandsTemplateDirectory) {
await adapter.processCommandsConfiguration(
scaffoldInstructions,
resolvedCommandsTemplateDirectory,
resolvedTargetDirectory
)
}
}
}
}
4. CLI Integration
// Interactive mode prompt
const includeCommands = await select({
message: 'Include custom commands?',
options: [
{ value: true, label: 'Yes', hint: 'Add custom commands for this AI app' },
{ value: false, label: 'No', hint: 'Skip commands configuration' }
],
initialValue: false
})
// Command line flag
--commands, -c Include custom commands
Implementation Details
Base Adapter Default Implementation
The BaseAdapter class provides a default implementation of processCommandsConfiguration() that:
- Reads all files from the commands template directory
- Filters for files ending with
*.command.md - Applies filename transformation if provided by the adapter
- Copies files to the adapter-specified target directory
- Handles errors gracefully with warnings
async processCommandsConfiguration(
scaffoldInstructions: ScaffoldInstructions,
resolvedCommandsTemplateDirectory: string,
resolvedTargetDirectory: string
): Promise<void> {
const commandsConfig = this.getCommandsConfig()
if (!commandsConfig) return
const targetDirectory = path.resolve(process.cwd(), commandsConfig.targetDirectory)
try {
// Ensure target directory exists
await fs.mkdir(targetDirectory, { recursive: true })
// Read all files from commands template directory
const files = await fs.readdir(resolvedCommandsTemplateDirectory)
// Filter for *.command.md files
const commandFiles = files.filter(file => file.endsWith('.command.md'))
for (const commandFile of commandFiles) {
const sourceFilePath = path.join(resolvedCommandsTemplateDirectory, commandFile)
const stat = await fs.stat(sourceFilePath)
if (stat.isFile()) {
// Apply filename transformation if provided
const targetFileName = commandsConfig.fileNameTransform
? commandsConfig.fileNameTransform(commandFile)
: commandFile
const targetFilePath = path.join(targetDirectory, targetFileName)
// Read and copy file content
const content = await fs.readFile(sourceFilePath, 'utf-8')
await fs.writeFile(targetFilePath, content, 'utf-8')
}
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
console.warn(`Warning: Failed to process commands configuration: ${errorMessage}`)
}
}
GitHub Copilot Adapter Implementation
export class GitHubCopilotAdapter extends BaseAdapter {
getCommandsConfig(): CommandsConfig {
return {
targetDirectory: '.github/prompts',
fileNameTransform: (filename: string) =>
filename.replace('.command.md', '.prompt.md')
}
}
}
Other Adapters
Currently, Cursor, Gemini, and Claude Code adapters return null from getCommandsConfig():
getCommandsConfig(): null {
return null
}
Template Structure
Commands are stored in __template__/{language}/_commands/ with the naming convention *.command.md:
__template__/
nodejs/
_commands/
github-issue-impl.command.md
secure-code/
...
testing/
...
Command Template Format
Command templates use YAML frontmatter to define metadata:
---
tools: ['fetch', 'githubRepo', 'get_pull_request']
agent: 'agent'
name: 'github-issue-impl'
description: 'Implement code based on a GitHub issue'
---
Your goal is to learn the requirements of a GitHub issue...
CLI Usage
Interactive Mode
npx agent-rules
The CLI will prompt:
- Which AI App?
- Which topics?
- Include MCP configuration?
- Include custom commands? ← New prompt
Command Line Flags
# Include commands with GitHub Copilot
npx agent-rules --app github-copilot --topics secure-code --commands
# Short flag
npx agent-rules -a github-copilot -t testing -c
# Combine with MCP
npx agent-rules --app github-copilot --topics testing --mcp --commands
Behavior by Adapter
| Adapter | Commands Support | Target Directory | File Transform |
|---|---|---|---|
| GitHub Copilot | ✅ | .github/prompts/ | .command.md → .prompt.md |
| Cursor | ❌ (future) | N/A | N/A |
| Gemini | ❌ (future) | N/A | N/A |
| Claude Code | ❌ (future) | N/A | N/A |
Error Handling
The feature handles several error scenarios gracefully:
- Missing
_commands/directory: Silently skipped (returns null) - Unsupported adapter: Commands not processed if
getCommandsConfig()returns null - File read/write errors: Warnings logged, processing continues
- Invalid permissions: Warnings logged, file skipped
Testing
The feature includes:
- Unit tests: Verify adapter configurations and file transformations
- Integration tests: End-to-end scaffolding with commands enabled/disabled
- CLI tests: Command line flags and interactive prompts
Future Enhancements
Potential future improvements:
- Additional Adapter Support: Implement commands for Cursor, Gemini, and Claude Code
- Custom Transformations: Allow per-command content transformations
- Command Categories: Support subdirectories within
_commands/ - Command Validation: Validate frontmatter format and required fields
- Command Updates: Smart merging of existing vs new commands
Migration Guide
For existing users, this is a non-breaking change:
- Commands are optional and disabled by default
- No changes to existing instruction or MCP scaffolding
- New
--commandsflag must be explicitly provided
Related Documentation
- MCP Feature Documentation - Similar scaffolding pattern
- Adapter Development - How to extend adapters
- Design Overview - Overall architecture