aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
321 lines (283 loc) • 7.91 kB
text/typescript
/**
* Command generation and deployment
*/
import { mkdir, writeFile, readFile, access } from 'fs/promises';
import { join, dirname } from 'path';
import { constants } from 'fs';
import {
CommandOptions,
GeneratedCommand,
CommandDeploymentResult,
ValidationResult,
ValidationIssue,
CursorCommandSpec,
} from './types.js';
import { generateCommandContent } from './templates.js';
import { getCommandsDirectory, getFileExtension } from '../platform-paths.js';
/**
* Validate command name (kebab-case)
*/
function validateCommandName(name: string): string | null {
if (!name || name.trim() === '') {
return 'Command name cannot be empty';
}
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
return 'Command name must be kebab-case (lowercase letters, numbers, hyphens only, must start with letter)';
}
if (name.startsWith('-') || name.endsWith('-')) {
return 'Command name cannot start or end with a hyphen';
}
if (name.includes('--')) {
return 'Command name cannot contain consecutive hyphens';
}
return null;
}
/**
* Validate command options
*/
export function validateCommand(command: GeneratedCommand): ValidationResult {
const issues: ValidationIssue[] = [];
// Validate name
const nameError = validateCommandName(command.name);
if (nameError) {
issues.push({
type: 'error',
field: 'name',
message: nameError,
suggestion: 'Use lowercase letters, numbers, and hyphens only (e.g., my-command)',
});
}
// Validate content
if (!command.content || command.content.trim() === '') {
issues.push({
type: 'error',
field: 'content',
message: 'Command content cannot be empty',
});
}
// Validate platform
const validPlatforms = ['claude', 'codex', 'copilot', 'cursor', 'factory', 'generic', 'windsurf'];
if (!validPlatforms.includes(command.platform)) {
issues.push({
type: 'error',
field: 'platform',
message: `Invalid platform: ${command.platform}`,
suggestion: `Must be one of: ${validPlatforms.join(', ')}`,
});
}
// Warn if content doesn't include standard options
if (!command.content.includes('--interactive') && !command.content.includes('--guidance')) {
issues.push({
type: 'warning',
field: 'content',
message: 'Command should support --interactive and --guidance options',
suggestion: 'Add these standard options to command arguments',
});
}
return {
valid: issues.filter(i => i.type === 'error').length === 0,
issues,
command,
};
}
/**
* Transform command content for Cursor platform (JSON format)
*/
function transformForCursor(
name: string,
description: string,
options: CommandOptions
): string {
const spec: CursorCommandSpec = {
name,
description,
arguments: (options.args || []).map(arg => ({
name: arg.name,
description: arg.description,
required: arg.required,
type: 'string',
default: arg.default,
})),
options: [
...(options.options || []).map(opt => ({
name: opt.name,
description: opt.description,
type: opt.type,
default: opt.default,
})),
{
name: 'interactive',
description: 'Enable interactive mode with guided questions',
type: 'boolean',
default: false,
},
{
name: 'guidance',
description: 'Provide strategic guidance for execution',
type: 'string',
},
],
execution: {
steps: [
'Validate inputs',
'Process request',
'Generate output',
'Report status',
],
},
};
return JSON.stringify(spec, null, 2);
}
/**
* Generate a command from options
*/
export async function generateCommand(options: CommandOptions): Promise<GeneratedCommand> {
// Validate command name
const nameError = validateCommandName(options.name);
if (nameError) {
throw new Error(nameError);
}
// Default template
const template = options.template || 'utility';
// Determine target directory
const targetDir = options.targetDir || getCommandsDirectory(options.platform, options.projectPath);
// Get file extension
const ext = getFileExtension(options.platform);
// Build file path
const filePath = join(targetDir, `${options.name}${ext}`);
// Generate content based on platform
let content: string;
if (options.platform === 'cursor') {
// Cursor uses JSON format
content = transformForCursor(options.name, options.description, options);
} else {
// Other platforms use markdown
content = generateCommandContent(
template,
options.name,
options.description,
options.args,
options.options,
options.guidance
);
}
return {
name: options.name,
path: filePath,
content,
platform: options.platform,
template,
};
}
/**
* Check if file exists
*/
async function fileExists(path: string): Promise<boolean> {
try {
await access(path, constants.F_OK);
return true;
} catch {
return false;
}
}
/**
* Backup existing file
*/
async function backupFile(path: string): Promise<string> {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
const backupPath = `${path}.backup-${timestamp}`;
const content = await readFile(path, 'utf-8');
await writeFile(backupPath, content, 'utf-8');
return backupPath;
}
/**
* Deploy a generated command to the filesystem
*/
export async function deployCommand(
command: GeneratedCommand,
options: { force?: boolean; backup?: boolean } = {}
): Promise<CommandDeploymentResult> {
try {
// Validate command first
const validation = validateCommand(command);
if (!validation.valid) {
const errors = validation.issues.filter(i => i.type === 'error').map(i => i.message);
return {
command,
success: false,
error: `Validation failed: ${errors.join('; ')}`,
};
}
// Check if file exists
const exists = await fileExists(command.path);
// Handle existing file
if (exists && !options.force) {
return {
command,
success: false,
error: `File already exists: ${command.path}. Use force option to overwrite.`,
};
}
// Backup if requested and file exists
let backed_up = false;
if (exists && options.backup) {
await backupFile(command.path);
backed_up = true;
}
// Ensure directory exists
const dir = dirname(command.path);
await mkdir(dir, { recursive: true });
// Write command file
await writeFile(command.path, command.content, 'utf-8');
return {
command,
success: true,
backed_up,
};
} catch (error) {
return {
command,
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Generate and deploy a command in one step
*/
export async function generateAndDeploy(
options: CommandOptions,
deployOptions: { force?: boolean; backup?: boolean } = {}
): Promise<CommandDeploymentResult> {
const command = await generateCommand(options);
// If dry run, don't deploy
if (options.dryRun) {
return {
command,
success: true,
error: undefined,
};
}
return deployCommand(command, deployOptions);
}
/**
* List existing commands in a project
*/
export async function listCommands(
platform: string,
projectPath: string
): Promise<string[]> {
const commandsDir = getCommandsDirectory(platform as any, projectPath);
const ext = getFileExtension(platform as any);
try {
const { readdir } = await import('fs/promises');
const files = await readdir(commandsDir);
return files
.filter(f => f.endsWith(ext))
.map(f => f.replace(ext, ''))
.sort();
} catch {
// Directory doesn't exist or is empty
return [];
}
}