version: '1.0'
template: shared
description: Shared rules and patterns for all Agiflow templates
rules:
- pattern: import-standards
description: Import organization and standards
must_do:
- rule: Use clean import paths without file extensions
codeExample: |
// ✅ GOOD - No file extensions
import { UserService } from '../services';
import { myTool } from '../tools/MyTool';
import type { Config } from '../types';
// ❌ BAD - File extensions in imports
import { UserService } from '../services/index.js';
import { myTool } from '../tools/MyTool.js';
import type { Config } from '../types.ts';
- rule: Use barrel imports without /index suffix
codeExample: |
// ✅ GOOD - Clean barrel imports
import { UserService } from '../services';
import { myTool } from '../tools';
import type { Config } from '../types';
// ❌ BAD - Explicit /index in path
import { UserService } from '../services/index';
import { myTool } from '../tools/index';
- rule: Import from barrel exports, not individual files (except @modelcontextprotocol/sdk)
codeExample: |
// ✅ GOOD - Import from barrel
import { UserService, OrderService } from '../services';
import { MyTool } from '../tools';
// ✅ OK - @modelcontextprotocol/sdk requires .js extension
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
// ❌ BAD - Direct file imports
import { UserService } from '../services/UserService';
import { OrderService } from '../services/OrderService';
should_do:
- rule: Use static imports at the top of the file instead of inline dynamic imports
codeExample: |
// ✅ GOOD
import { listScaffoldMethods } from '../hooks/geminiCli/listScaffoldMethods';
const hooks = await listScaffoldMethods();
// ⚠️ AVOID - Unnecessary dynamic import
const hooks = await import('../hooks/geminiCli/listScaffoldMethods');
// ✅ OK - Dynamic import when necessary (lazy loading, conditional)
if (condition) {
const module = await import('./heavy-module');
}
must_not_do:
- rule: Never use .js or .ts extensions in import paths
codeExample: |
// ❌ BAD - File extensions
import { UserService } from '../services/index.js';
import { myTool } from '../tools/MyTool.js';
import { config } from './config.ts';
// ✅ GOOD - No extensions
import { UserService } from '../services';
import { myTool } from '../tools/MyTool';
import { config } from './config';
- rule: Never use inline require() statements - use static imports
codeExample: |
// ❌ BAD - Inline require
const { listScaffoldMethods } = require('../hooks/geminiCli/listScaffoldMethods');
const hooks = require('./hooks');
// ✅ GOOD - Static import
import { listScaffoldMethods } from '../hooks/geminiCli/listScaffoldMethods';
import hooks from './hooks';
- pattern: export-standards
description: Strict export standards enforced across all TypeScript/JavaScript files
must_do:
- rule: Use direct exports only - export declarations at point of definition
codeExample: |
// ✅ GOOD - Direct exports
export class UserService {
getUser() { /* implementation */ }
}
export function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
export const API_ENDPOINTS = {
users: '/api/users',
orders: '/api/orders'
} as const;
export type User = {
id: string;
email: string;
};
- rule: Use barrel exports from collocated files via index.ts
codeExample: |
// ✅ GOOD - Barrel exports in index.ts
export { UserService } from './userService';
export { OrderService } from './orderService';
export { PaymentService } from './paymentService';
export type { User, Order, Payment } from './types';
must_not_do:
- rule: Never use default exports
codeExample: |
// ❌ BAD - Default exports
export default class UserService {}
export default function calculateTotal() {}
export default { users: '/api/users' };
- rule: Never use named exports (export statements)
codeExample: |
// ❌ BAD - Named exports
class UserService {}
function calculateTotal() {}
const API_ENDPOINTS = {};
export { UserService, calculateTotal, API_ENDPOINTS };
- rule: Never use namespace exports
codeExample: |
// ❌ BAD - Namespace exports
export * as UserUtils from './userUtils';
export * from './services';
- pattern: error-handling
description: Consistent error handling patterns across all code
must_do:
- rule: Always use try-catch blocks for async operations
codeExample: |
// ✅ GOOD
async function fetchUser(id: string) {
try {
const response = await fetch(`/api/users/${id}`);
return await response.json();
} catch (error) {
throw new Error(`Failed to fetch user: ${error.message}`);
}
}
- rule: Throw descriptive error messages with context
codeExample: |
// ✅ GOOD
if (!userId) {
throw new Error('User ID is required for fetching user data');
}
- rule: Handle errors at appropriate abstraction levels
codeExample: |
// ✅ GOOD - Handle in service, propagate meaningful errors
async function createOrder(data: OrderData) {
try {
return await orderService.create(data);
} catch (error) {
if (error instanceof ValidationError) {
throw new BadRequestError('Invalid order data', error.details);
}
throw new ServerError('Failed to create order', error);
}
}
must_not_do:
- rule: Never use empty catch blocks
codeExample: |
// ❌ BAD
try {
await dangerousOperation();
} catch (e) {
// Silent failure!
}
- rule: Never catch errors without proper handling or re-throwing
codeExample: |
// ❌ BAD
try {
await operation();
} catch (error) {
console.log('Something went wrong'); // Only logging, not handling
}
- rule: Never expose sensitive error details to clients
codeExample: |
// ❌ BAD
catch (error) {
return { error: error.stack }; // Exposing stack trace
}
- pattern: naming-conventions
description: Consistent naming conventions across all code
must_do:
- rule: Use PascalCase for classes, interfaces, types, and enums
codeExample: |
// ✅ GOOD
class UserService {}
interface UserData {}
type OrderStatus = 'pending' | 'completed';
enum PaymentMethod { CREDIT_CARD, PAYPAL }
- rule: Use camelCase for variables, functions, and methods
codeExample: |
// ✅ GOOD
const userName = 'John';
function getUserById(id: string) {}
const handleSubmit = () => {};
- rule: Use UPPER_SNAKE_CASE for constants
codeExample: |
// ✅ GOOD
const MAX_RETRY_ATTEMPTS = 3;
const API_BASE_URL = 'https://api.example.com';
- rule: Use camelCase for file names
codeExample: |
// ✅ GOOD
userService.ts
paymentProcessor.ts
orderValidation.ts
must_not_do:
- rule: Never mix naming conventions
codeExample: |
// ❌ BAD
class user_service {} // Wrong: should be PascalCase
const UserName = 'John'; // Wrong: should be camelCase
function GetUserById() {} // Wrong: should be camelCase
- rule: Never use abbreviations unless widely recognized
codeExample: |
// ❌ BAD
const usrNm = 'John';
function getUsrByID() {}
// ✅ GOOD
const userName = 'John';
function getUserById() {}
- pattern: constants-usage
description: Avoid magic strings and hardcoded values - use constants for maintainability
must_do:
- rule: Define constants in a dedicated constants file or folder
codeExample: |
// ✅ GOOD - Constants in dedicated file
// src/constants/index.ts
export const CONFIG_FILENAME = 'config.yaml';
export const CONFIG_FILENAME_HIDDEN = '.config.yaml';
export const CONFIG_FILENAMES = [CONFIG_FILENAME_HIDDEN, CONFIG_FILENAME] as const;
export const DEFAULT_TIMEOUT = 30000;
export const MAX_RETRIES = 3;
- rule: Import and use constants instead of inline magic strings
codeExample: |
// ✅ GOOD - Using constants
import { CONFIG_FILENAME, DEFAULT_TIMEOUT } from '../constants';
const configPath = path.join(dir, CONFIG_FILENAME);
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
// ❌ BAD - Magic strings inline
const configPath = path.join(dir, 'config.yaml');
const timeout = options.timeout ?? 30000;
- rule: Use constants for repeated string literals (filenames, keys, messages)
codeExample: |
// ✅ GOOD - Constants for filenames
export const ARCHITECT_FILENAME = 'architect.yaml';
export const RULES_FILENAME = 'RULES.yaml';
export const SCAFFOLD_FILENAME = 'scaffold.yaml';
// Then use everywhere
if (file.endsWith(ARCHITECT_FILENAME)) { }
const rulesPath = path.join(dir, RULES_FILENAME);
- rule: Group related constants together
codeExample: |
// ✅ GOOD - Related constants grouped
// File patterns
export const ARCHITECT_FILENAME = 'architect.yaml';
export const ARCHITECT_FILENAME_HIDDEN = '.architect.yaml';
export const ARCHITECT_FILENAMES = [ARCHITECT_FILENAME_HIDDEN, ARCHITECT_FILENAME] as const;
// Timeouts
export const DEFAULT_TIMEOUT_MS = 30000;
export const MAX_TIMEOUT_MS = 600000;
// Limits
export const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
export const MAX_RESULTS = 100;
must_not_do:
- rule: Never use magic strings - inline string literals that have special meaning
codeExample: |
// ❌ BAD - Magic strings
if (filename === 'architect.yaml') { }
const path = join(dir, 'RULES.yaml');
await fs.readFile('config.json');
// ✅ GOOD - Use constants
import { ARCHITECT_FILENAME, RULES_FILENAME, CONFIG_FILENAME } from '../constants';
if (filename === ARCHITECT_FILENAME) { }
const path = join(dir, RULES_FILENAME);
await fs.readFile(CONFIG_FILENAME);
- rule: Never use magic numbers - inline numeric literals with special meaning
codeExample: |
// ❌ BAD - Magic numbers
setTimeout(callback, 30000);
if (retries > 3) { }
const maxSize = 10485760;
// ✅ GOOD - Named constants
import { DEFAULT_TIMEOUT_MS, MAX_RETRIES, MAX_FILE_SIZE_BYTES } from '../constants';
setTimeout(callback, DEFAULT_TIMEOUT_MS);
if (retries > MAX_RETRIES) { }
const maxSize = MAX_FILE_SIZE_BYTES;
- rule: Never duplicate the same literal value across multiple files
codeExample: |
// ❌ BAD - Same string in multiple files
// file1.ts
const config = path.join(dir, 'architect.yaml');
// file2.ts
if (name.endsWith('architect.yaml')) { }
// file3.ts
await fs.access(path.join(root, 'architect.yaml'));
// ✅ GOOD - Single source of truth
// constants/index.ts
export const ARCHITECT_FILENAME = 'architect.yaml';
// All files import from constants
import { ARCHITECT_FILENAME } from '../constants';
- pattern: type-safety
description: Strong type safety standards for TypeScript code
must_do:
- rule: Always provide explicit return types for functions
codeExample: |
// ✅ GOOD
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
- rule: Define interfaces for object shapes
codeExample: |
// ✅ GOOD
interface User {
id: string;
email: string;
name: string;
}
function createUser(data: User): User {
return { ...data };
}
- rule: Use type guards for runtime type checking
codeExample: |
// ✅ GOOD
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'email' in value
);
}
must_not_do:
- rule: Never use 'any' type unless absolutely necessary
codeExample: |
// ❌ BAD
function processData(data: any) {
return data.someProperty;
}
// ✅ GOOD
function processData(data: UserData) {
return data.email;
}
- rule: Never use type assertions to bypass type checking
codeExample: |
// ❌ BAD
const user = response as User; // Unsafe
// ✅ GOOD
if (isUser(response)) {
const user = response; // Type-safe
}
- rule: Never ignore TypeScript errors with @ts-ignore
codeExample: |
// ❌ BAD
// @ts-ignore
const value = dangerousOperation();
- pattern: documentation-standards
description: Code documentation standards
must_do:
- rule: Add JSDoc comments for all public APIs
codeExample: |
// ✅ GOOD
/**
* Calculates the total price of items in the cart
* @param items - Array of items to calculate total for
* @returns The total price as a number
*/
export function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
- rule: Document complex logic with inline comments
codeExample: |
// ✅ GOOD
// Apply discount based on user tier (10% for premium, 5% for standard)
const discount = user.tier === 'premium' ? 0.1 : 0.05;
const finalPrice = basePrice * (1 - discount);
- rule: Keep README.md updated with setup and usage instructions
should_do:
- rule: Add examples in JSDoc for complex functions
- rule: Document edge cases and assumptions
- rule: Keep comments up-to-date with code changes
must_not_do:
- rule: Never leave commented-out code
codeExample: |
// ❌ BAD
function processOrder() {
// const oldImplementation = doSomething();
// return oldImplementation;
return newImplementation();
}
- rule: Never use obvious comments that don't add value
codeExample: |
// ❌ BAD
// Increment counter by 1
counter++;
// ✅ GOOD (no comment needed for obvious code)
counter++;
- pattern: async-await-patterns
description: Best practices for asynchronous code
must_do:
- rule: Always use async/await over raw promises
codeExample: |
// ✅ GOOD
async function fetchUserData(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
- rule: Handle promise rejections with try-catch
codeExample: |
// ✅ GOOD
async function saveUser(user: User): Promise<void> {
try {
await database.save(user);
} catch (error) {
throw new Error(`Failed to save user: ${error.message}`);
}
}
- rule: Use Promise.all for parallel operations
codeExample: |
// ✅ GOOD
async function fetchMultipleUsers(ids: string[]): Promise<User[]> {
const promises = ids.map(id => fetchUser(id));
return Promise.all(promises);
}
must_not_do:
- rule: Never mix async/await with .then/.catch chains
codeExample: |
// ❌ BAD
async function badExample() {
await operation()
.then(result => process(result))
.catch(error => handle(error));
}
// ✅ GOOD
async function goodExample() {
try {
const result = await operation();
return process(result);
} catch (error) {
handle(error);
}
}
- rule: Never forget to await async functions
codeExample: |
// ❌ BAD
async function badExample() {
saveUser(user); // Missing await!
return;
}
// ✅ GOOD
async function goodExample() {
await saveUser(user);
return;
}
- pattern: test-pattern
description: Test pattern description
- pattern: duplicate-pattern
description: Test pattern description