mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 17:47:16 +00:00
Add comprehensive Conductor plugin implementing Context-Driven Development methodology with tracks, specs, and phased implementation plans. Components: - 5 commands: setup, new-track, implement, status, revert - 1 agent: conductor-validator - 3 skills: context-driven-development, track-management, workflow-patterns - 18 templates for project artifacts Documentation updates: - README.md: Updated counts (68 plugins, 100 agents, 110 skills, 76 tools) - docs/plugins.md: Added Conductor to Workflows section - docs/agents.md: Added conductor-validator agent - docs/agent-skills.md: Added Conductor skills section Also includes Prettier formatting across all project files.
11 KiB
11 KiB
JavaScript Style Guide
Modern JavaScript (ES6+) best practices and conventions.
ES6+ Features
Use Modern Syntax
// Prefer const and let over var
const immutableValue = "fixed";
let mutableValue = "can change";
// Never use var
// var outdated = 'avoid this';
// Template literals over concatenation
const greeting = `Hello, ${name}!`;
// Destructuring
const { id, name, email } = user;
const [first, second, ...rest] = items;
// Spread operator
const merged = { ...defaults, ...options };
const combined = [...array1, ...array2];
// Arrow functions for short callbacks
const doubled = numbers.map((n) => n * 2);
Object Shorthand
// Property shorthand
const name = "John";
const age = 30;
const user = { name, age };
// Method shorthand
const calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
},
};
// Computed property names
const key = "dynamic";
const obj = {
[key]: "value",
[`${key}Method`]() {
return "result";
},
};
Default Parameters and Rest
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
return `${greeting}, ${name}!`;
}
// Rest parameters
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
// Named parameters via destructuring
function createUser({ name, email, role = "user" }) {
return { name, email, role, createdAt: new Date() };
}
Async/Await
Prefer async/await Over Promises
// Bad: Promise chains
function fetchUserPosts(userId) {
return fetch(`/users/${userId}`)
.then((res) => res.json())
.then((user) => fetch(`/posts?userId=${user.id}`))
.then((res) => res.json());
}
// Good: async/await
async function fetchUserPosts(userId) {
const userRes = await fetch(`/users/${userId}`);
const user = await userRes.json();
const postsRes = await fetch(`/posts?userId=${user.id}`);
return postsRes.json();
}
Parallel Execution
// Sequential (slow)
async function loadDataSequentially() {
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
return { users, posts, comments };
}
// Parallel (fast)
async function loadDataParallel() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);
return { users, posts, comments };
}
Error Handling
// try/catch with async/await
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error("Fetch failed:", error.message);
throw error;
}
}
// Error handling utility
async function safeAsync(promise) {
try {
const result = await promise;
return [result, null];
} catch (error) {
return [null, error];
}
}
// Usage
const [data, error] = await safeAsync(fetchData("/api/users"));
if (error) {
handleError(error);
}
Error Handling
Custom Errors
class AppError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.name = "AppError";
this.code = code;
this.statusCode = statusCode;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message, field) {
super(message, "VALIDATION_ERROR", 400);
this.name = "ValidationError";
this.field = field;
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} with id ${id} not found`, "NOT_FOUND", 404);
this.name = "NotFoundError";
this.resource = resource;
this.resourceId = id;
}
}
Error Handling Patterns
// Centralized error handler
function handleError(error) {
if (error instanceof ValidationError) {
showFieldError(error.field, error.message);
} else if (error instanceof NotFoundError) {
showNotFound(error.resource);
} else {
showGenericError("Something went wrong");
reportError(error);
}
}
// Error boundary pattern (for React)
function withErrorBoundary(Component) {
return class extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
reportError(error, info);
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return <Component {...this.props} />;
}
};
}
Module Patterns
ES Modules
// Named exports
export const API_URL = "/api";
export function fetchData(endpoint) {
/* ... */
}
export class ApiClient {
/* ... */
}
// Re-exports
export { User, Post } from "./types.js";
export * as utils from "./utils.js";
// Imports
import { fetchData, API_URL } from "./api.js";
import * as api from "./api.js";
import defaultExport from "./module.js";
Module Organization
// Feature-based organization
// features/user/
// index.js - Public exports
// api.js - API calls
// utils.js - Helper functions
// constants.js - Feature constants
// index.js - Barrel export
export { UserService } from "./service.js";
export { validateUser } from "./utils.js";
export { USER_ROLES } from "./constants.js";
Dependency Injection
// Constructor injection
class UserService {
constructor(apiClient, logger) {
this.api = apiClient;
this.logger = logger;
}
async getUser(id) {
this.logger.info(`Fetching user ${id}`);
return this.api.get(`/users/${id}`);
}
}
// Factory function
function createUserService(config = {}) {
const api = config.apiClient || new ApiClient();
const logger = config.logger || console;
return new UserService(api, logger);
}
Functional Patterns
Pure Functions
// Impure: Modifies external state
let count = 0;
function incrementCount() {
count++;
return count;
}
// Pure: No side effects
function increment(value) {
return value + 1;
}
// Pure: Same input = same output
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
Array Methods
const users = [
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Charlie", active: true },
];
// map - transform
const names = users.map((user) => user.name);
// filter - select
const activeUsers = users.filter((user) => user.active);
// find - first match
const user = users.find((user) => user.id === 2);
// some/every - boolean check
const hasActive = users.some((user) => user.active);
const allActive = users.every((user) => user.active);
// reduce - accumulate
const userMap = users.reduce((map, user) => {
map[user.id] = user;
return map;
}, {});
// Chaining
const activeNames = users
.filter((user) => user.active)
.map((user) => user.name)
.sort();
Composition
// Compose functions
const compose =
(...fns) =>
(x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
const pipe =
(...fns) =>
(x) =>
fns.reduce((acc, fn) => fn(acc), x);
// Usage
const processUser = pipe(validateUser, normalizeUser, enrichUser);
const result = processUser(rawUserData);
Classes
Modern Class Syntax
class User {
// Private fields
#password;
// Static properties
static ROLES = ["admin", "user", "guest"];
constructor(name, email) {
this.name = name;
this.email = email;
this.#password = null;
}
// Getter
get displayName() {
return `${this.name} <${this.email}>`;
}
// Setter
set password(value) {
if (value.length < 8) {
throw new Error("Password too short");
}
this.#password = hashPassword(value);
}
// Instance method
toJSON() {
return { name: this.name, email: this.email };
}
// Static method
static fromJSON(json) {
return new User(json.name, json.email);
}
}
Inheritance
class Entity {
constructor(id) {
this.id = id;
this.createdAt = new Date();
}
equals(other) {
return other instanceof Entity && this.id === other.id;
}
}
class User extends Entity {
constructor(id, name, email) {
super(id);
this.name = name;
this.email = email;
}
toJSON() {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString(),
};
}
}
Common Patterns
Null Safety
// Optional chaining
const city = user?.address?.city;
const firstItem = items?.[0];
const result = obj?.method?.();
// Nullish coalescing
const name = user.name ?? "Anonymous";
const count = value ?? 0;
// Combining both
const displayName = user?.profile?.name ?? "Unknown";
Debounce and Throttle
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
function throttle(fn, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
Memoization
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Usage
const expensiveCalculation = memoize((n) => {
// Complex computation
return fibonacci(n);
});
Best Practices
Avoid Common Pitfalls
// Avoid loose equality
// Bad
if (value == null) {
}
// Good
if (value === null || value === undefined) {
}
if (value == null) {
} // Only acceptable for null/undefined check
// Avoid implicit type coercion
// Bad
if (items.length) {
}
// Good
if (items.length > 0) {
}
// Avoid modifying function arguments
// Bad
function process(options) {
options.processed = true;
return options;
}
// Good
function process(options) {
return { ...options, processed: true };
}
Performance Tips
// Avoid creating functions in loops
// Bad
items.forEach(function (item) {
item.addEventListener("click", function () {});
});
// Good
function handleClick(event) {}
items.forEach((item) => {
item.addEventListener("click", handleClick);
});
// Use appropriate data structures
// For frequent lookups, use Map/Set instead of Array
const userMap = new Map(users.map((u) => [u.id, u]));
const userIds = new Set(users.map((u) => u.id));