mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 09:37:15 +00:00
fix(conductor): move plugin to plugins/ directory for proper discovery
Conductor plugin was at root level instead of plugins/ directory, causing slash commands to not be recognized by Claude Code.
This commit is contained in:
600
plugins/conductor/templates/code_styleguides/csharp.md
Normal file
600
plugins/conductor/templates/code_styleguides/csharp.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# C# Style Guide
|
||||
|
||||
C# conventions and best practices for .NET development.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### General Rules
|
||||
|
||||
```csharp
|
||||
// PascalCase for public members, types, namespaces
|
||||
public class UserService { }
|
||||
public void ProcessOrder() { }
|
||||
public string FirstName { get; set; }
|
||||
|
||||
// camelCase for private fields, parameters, locals
|
||||
private readonly ILogger _logger;
|
||||
private int _itemCount;
|
||||
public void DoWork(string inputValue) { }
|
||||
|
||||
// Prefix interfaces with I
|
||||
public interface IUserRepository { }
|
||||
public interface INotificationService { }
|
||||
|
||||
// Suffix async methods with Async
|
||||
public async Task<User> GetUserAsync(int id) { }
|
||||
public async Task ProcessOrderAsync(Order order) { }
|
||||
|
||||
// Constants: PascalCase (not SCREAMING_CASE)
|
||||
public const int MaxRetryCount = 3;
|
||||
public const string DefaultCurrency = "USD";
|
||||
```
|
||||
|
||||
### Field and Property Naming
|
||||
|
||||
```csharp
|
||||
public class Order
|
||||
{
|
||||
// Private fields: underscore prefix + camelCase
|
||||
private readonly IOrderRepository _repository;
|
||||
private int _itemCount;
|
||||
|
||||
// Public properties: PascalCase
|
||||
public int Id { get; set; }
|
||||
public string CustomerName { get; set; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
|
||||
// Boolean properties: Is/Has/Can prefix
|
||||
public bool IsActive { get; set; }
|
||||
public bool HasDiscount { get; set; }
|
||||
public bool CanEdit { get; }
|
||||
}
|
||||
```
|
||||
|
||||
## Async/Await Patterns
|
||||
|
||||
### Basic Async Usage
|
||||
|
||||
```csharp
|
||||
// Always use async/await for I/O operations
|
||||
public async Task<User> GetUserAsync(int id)
|
||||
{
|
||||
var user = await _repository.FindAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
throw new NotFoundException($"User {id} not found");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
// Don't block on async code
|
||||
// Bad
|
||||
var user = GetUserAsync(id).Result;
|
||||
|
||||
// Good
|
||||
var user = await GetUserAsync(id);
|
||||
```
|
||||
|
||||
### Async Best Practices
|
||||
|
||||
```csharp
|
||||
// Use ConfigureAwait(false) in library code
|
||||
public async Task<Data> FetchDataAsync()
|
||||
{
|
||||
var response = await _httpClient.GetAsync(url)
|
||||
.ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<Data>()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Avoid async void except for event handlers
|
||||
// Bad
|
||||
public async void ProcessOrder() { }
|
||||
|
||||
// Good
|
||||
public async Task ProcessOrderAsync() { }
|
||||
|
||||
// Event handler exception
|
||||
private async void Button_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessOrderAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleError(ex);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Parallel Async Operations
|
||||
|
||||
```csharp
|
||||
// Execute independent operations in parallel
|
||||
public async Task<DashboardData> LoadDashboardAsync()
|
||||
{
|
||||
var usersTask = _userService.GetActiveUsersAsync();
|
||||
var ordersTask = _orderService.GetRecentOrdersAsync();
|
||||
var statsTask = _statsService.GetDailyStatsAsync();
|
||||
|
||||
await Task.WhenAll(usersTask, ordersTask, statsTask);
|
||||
|
||||
return new DashboardData
|
||||
{
|
||||
Users = await usersTask,
|
||||
Orders = await ordersTask,
|
||||
Stats = await statsTask
|
||||
};
|
||||
}
|
||||
|
||||
// Use SemaphoreSlim for throttling
|
||||
public async Task ProcessItemsAsync(IEnumerable<Item> items)
|
||||
{
|
||||
using var semaphore = new SemaphoreSlim(10); // Max 10 concurrent
|
||||
|
||||
var tasks = items.Select(async item =>
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
await ProcessItemAsync(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
```
|
||||
|
||||
## LINQ
|
||||
|
||||
### Query Syntax vs Method Syntax
|
||||
|
||||
```csharp
|
||||
// Method syntax (preferred for simple queries)
|
||||
var activeUsers = users
|
||||
.Where(u => u.IsActive)
|
||||
.OrderBy(u => u.Name)
|
||||
.ToList();
|
||||
|
||||
// Query syntax (for complex queries with joins)
|
||||
var orderSummary =
|
||||
from order in orders
|
||||
join customer in customers on order.CustomerId equals customer.Id
|
||||
where order.Total > 100
|
||||
group order by customer.Name into g
|
||||
select new { Customer = g.Key, Total = g.Sum(o => o.Total) };
|
||||
```
|
||||
|
||||
### LINQ Best Practices
|
||||
|
||||
```csharp
|
||||
// Use appropriate methods
|
||||
var hasItems = items.Any(); // Not: items.Count() > 0
|
||||
var firstOrDefault = items.FirstOrDefault(); // Not: items.First()
|
||||
var count = items.Count; // Property, not Count()
|
||||
|
||||
// Avoid multiple enumerations
|
||||
// Bad
|
||||
if (items.Any())
|
||||
{
|
||||
foreach (var item in items) { }
|
||||
}
|
||||
|
||||
// Good
|
||||
var itemList = items.ToList();
|
||||
if (itemList.Count > 0)
|
||||
{
|
||||
foreach (var item in itemList) { }
|
||||
}
|
||||
|
||||
// Project early to reduce memory
|
||||
var names = users
|
||||
.Where(u => u.IsActive)
|
||||
.Select(u => u.Name) // Select only what you need
|
||||
.ToList();
|
||||
```
|
||||
|
||||
### Common LINQ Operations
|
||||
|
||||
```csharp
|
||||
// Filtering
|
||||
var adults = people.Where(p => p.Age >= 18);
|
||||
|
||||
// Transformation
|
||||
var names = people.Select(p => $"{p.FirstName} {p.LastName}");
|
||||
|
||||
// Aggregation
|
||||
var total = orders.Sum(o => o.Amount);
|
||||
var average = scores.Average();
|
||||
var max = values.Max();
|
||||
|
||||
// Grouping
|
||||
var byDepartment = employees
|
||||
.GroupBy(e => e.Department)
|
||||
.Select(g => new { Department = g.Key, Count = g.Count() });
|
||||
|
||||
// Joining
|
||||
var result = orders
|
||||
.Join(customers,
|
||||
o => o.CustomerId,
|
||||
c => c.Id,
|
||||
(o, c) => new { Order = o, Customer = c });
|
||||
|
||||
// Flattening
|
||||
var allOrders = customers.SelectMany(c => c.Orders);
|
||||
```
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
### Service Registration
|
||||
|
||||
```csharp
|
||||
// In Program.cs or Startup.cs
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Transient: new instance each time
|
||||
services.AddTransient<IEmailService, EmailService>();
|
||||
|
||||
// Scoped: one instance per request
|
||||
services.AddScoped<IUserRepository, UserRepository>();
|
||||
|
||||
// Singleton: one instance for app lifetime
|
||||
services.AddSingleton<ICacheService, MemoryCacheService>();
|
||||
|
||||
// Factory registration
|
||||
services.AddScoped<IDbConnection>(sp =>
|
||||
{
|
||||
var config = sp.GetRequiredService<IConfiguration>();
|
||||
return new SqlConnection(config.GetConnectionString("Default"));
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Constructor Injection
|
||||
|
||||
```csharp
|
||||
public class OrderService : IOrderService
|
||||
{
|
||||
private readonly IOrderRepository _repository;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
private readonly IEmailService _emailService;
|
||||
|
||||
public OrderService(
|
||||
IOrderRepository repository,
|
||||
ILogger<OrderService> logger,
|
||||
IEmailService emailService)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
|
||||
}
|
||||
|
||||
public async Task<Order> CreateOrderAsync(OrderRequest request)
|
||||
{
|
||||
_logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
|
||||
|
||||
var order = new Order(request);
|
||||
await _repository.SaveAsync(order);
|
||||
await _emailService.SendOrderConfirmationAsync(order);
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Options Pattern
|
||||
|
||||
```csharp
|
||||
// Configuration class
|
||||
public class EmailSettings
|
||||
{
|
||||
public string SmtpServer { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string FromAddress { get; set; }
|
||||
}
|
||||
|
||||
// Registration
|
||||
services.Configure<EmailSettings>(
|
||||
configuration.GetSection("Email"));
|
||||
|
||||
// Usage
|
||||
public class EmailService
|
||||
{
|
||||
private readonly EmailSettings _settings;
|
||||
|
||||
public EmailService(IOptions<EmailSettings> options)
|
||||
{
|
||||
_settings = options.Value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### xUnit Basics
|
||||
|
||||
```csharp
|
||||
public class CalculatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_TwoPositiveNumbers_ReturnsSum()
|
||||
{
|
||||
// Arrange
|
||||
var calculator = new Calculator();
|
||||
|
||||
// Act
|
||||
var result = calculator.Add(2, 3);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(5, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, 1, 2)]
|
||||
[InlineData(0, 0, 0)]
|
||||
[InlineData(-1, 1, 0)]
|
||||
public void Add_VariousNumbers_ReturnsCorrectSum(int a, int b, int expected)
|
||||
{
|
||||
var calculator = new Calculator();
|
||||
Assert.Equal(expected, calculator.Add(a, b));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mocking with Moq
|
||||
|
||||
```csharp
|
||||
public class OrderServiceTests
|
||||
{
|
||||
private readonly Mock<IOrderRepository> _mockRepository;
|
||||
private readonly Mock<ILogger<OrderService>> _mockLogger;
|
||||
private readonly OrderService _service;
|
||||
|
||||
public OrderServiceTests()
|
||||
{
|
||||
_mockRepository = new Mock<IOrderRepository>();
|
||||
_mockLogger = new Mock<ILogger<OrderService>>();
|
||||
_service = new OrderService(_mockRepository.Object, _mockLogger.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOrderAsync_ExistingOrder_ReturnsOrder()
|
||||
{
|
||||
// Arrange
|
||||
var expectedOrder = new Order { Id = 1, Total = 100m };
|
||||
_mockRepository
|
||||
.Setup(r => r.FindAsync(1))
|
||||
.ReturnsAsync(expectedOrder);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetOrderAsync(1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedOrder.Id, result.Id);
|
||||
_mockRepository.Verify(r => r.FindAsync(1), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOrderAsync_NonExistingOrder_ThrowsNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
_mockRepository
|
||||
.Setup(r => r.FindAsync(999))
|
||||
.ReturnsAsync((Order)null);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => _service.GetOrderAsync(999));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
```csharp
|
||||
public class ApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public ApiIntegrationTests(WebApplicationFactory<Program> factory)
|
||||
{
|
||||
_client = factory.CreateClient();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUsers_ReturnsSuccessAndCorrectContentType()
|
||||
{
|
||||
// Act
|
||||
var response = await _client.GetAsync("/api/users");
|
||||
|
||||
// Assert
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("application/json; charset=utf-8",
|
||||
response.Content.Headers.ContentType.ToString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Null Handling
|
||||
|
||||
```csharp
|
||||
// Null-conditional operators
|
||||
var length = customer?.Address?.Street?.Length;
|
||||
var name = user?.Name ?? "Unknown";
|
||||
|
||||
// Null-coalescing assignment
|
||||
list ??= new List<Item>();
|
||||
|
||||
// Pattern matching for null checks
|
||||
if (user is not null)
|
||||
{
|
||||
ProcessUser(user);
|
||||
}
|
||||
|
||||
// Guard clauses
|
||||
public void ProcessOrder(Order order)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(order);
|
||||
|
||||
if (order.Items.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Order must have items", nameof(order));
|
||||
}
|
||||
|
||||
// Process...
|
||||
}
|
||||
```
|
||||
|
||||
### Records and Init-Only Properties
|
||||
|
||||
```csharp
|
||||
// Record for immutable data
|
||||
public record User(int Id, string Name, string Email);
|
||||
|
||||
// Record with additional members
|
||||
public record Order
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string CustomerName { get; init; }
|
||||
public decimal Total { get; init; }
|
||||
|
||||
public bool IsHighValue => Total > 1000;
|
||||
}
|
||||
|
||||
// Record mutation via with expression
|
||||
var updatedUser = user with { Name = "New Name" };
|
||||
```
|
||||
|
||||
### Pattern Matching
|
||||
|
||||
```csharp
|
||||
// Type patterns
|
||||
public decimal CalculateDiscount(object customer) => customer switch
|
||||
{
|
||||
PremiumCustomer p => p.PurchaseTotal * 0.2m,
|
||||
RegularCustomer r when r.YearsActive > 5 => r.PurchaseTotal * 0.1m,
|
||||
RegularCustomer r => r.PurchaseTotal * 0.05m,
|
||||
null => 0m,
|
||||
_ => throw new ArgumentException("Unknown customer type")
|
||||
};
|
||||
|
||||
// Property patterns
|
||||
public string GetShippingOption(Order order) => order switch
|
||||
{
|
||||
{ Total: > 100, IsPriority: true } => "Express",
|
||||
{ Total: > 100 } => "Standard",
|
||||
{ IsPriority: true } => "Priority",
|
||||
_ => "Economy"
|
||||
};
|
||||
|
||||
// List patterns (C# 11)
|
||||
public bool IsValidSequence(int[] numbers) => numbers switch
|
||||
{
|
||||
[1, 2, 3] => true,
|
||||
[1, .., 3] => true,
|
||||
[_, _, ..] => numbers.Length >= 2,
|
||||
_ => false
|
||||
};
|
||||
```
|
||||
|
||||
### Disposable Pattern
|
||||
|
||||
```csharp
|
||||
public class ResourceManager : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly FileStream _stream;
|
||||
|
||||
public ResourceManager(string path)
|
||||
{
|
||||
_stream = File.OpenRead(path);
|
||||
}
|
||||
|
||||
public void DoWork()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
// Work with _stream
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Using statement
|
||||
using var manager = new ResourceManager("file.txt");
|
||||
manager.DoWork();
|
||||
```
|
||||
|
||||
## Code Organization
|
||||
|
||||
### File Structure
|
||||
|
||||
```csharp
|
||||
// One type per file (generally)
|
||||
// Filename matches type name: UserService.cs
|
||||
|
||||
// Order of members
|
||||
public class UserService
|
||||
{
|
||||
// 1. Constants
|
||||
private const int MaxRetries = 3;
|
||||
|
||||
// 2. Static fields
|
||||
private static readonly object _lock = new();
|
||||
|
||||
// 3. Instance fields
|
||||
private readonly IUserRepository _repository;
|
||||
|
||||
// 4. Constructors
|
||||
public UserService(IUserRepository repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
// 5. Properties
|
||||
public int TotalUsers { get; private set; }
|
||||
|
||||
// 6. Public methods
|
||||
public async Task<User> GetUserAsync(int id) { }
|
||||
|
||||
// 7. Private methods
|
||||
private void ValidateUser(User user) { }
|
||||
}
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
Solution/
|
||||
├── src/
|
||||
│ ├── MyApp.Api/ # Web API project
|
||||
│ ├── MyApp.Core/ # Domain/business logic
|
||||
│ ├── MyApp.Infrastructure/ # Data access, external services
|
||||
│ └── MyApp.Shared/ # Shared utilities
|
||||
├── tests/
|
||||
│ ├── MyApp.UnitTests/
|
||||
│ └── MyApp.IntegrationTests/
|
||||
└── MyApp.sln
|
||||
```
|
||||
668
plugins/conductor/templates/code_styleguides/dart.md
Normal file
668
plugins/conductor/templates/code_styleguides/dart.md
Normal file
@@ -0,0 +1,668 @@
|
||||
# Dart/Flutter Style Guide
|
||||
|
||||
Dart language conventions and Flutter-specific patterns.
|
||||
|
||||
## Null Safety
|
||||
|
||||
### Enable Sound Null Safety
|
||||
|
||||
```dart
|
||||
// pubspec.yaml
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
// All types are non-nullable by default
|
||||
String name = 'John'; // Cannot be null
|
||||
String? nickname; // Can be null
|
||||
|
||||
// Late initialization
|
||||
late final Database database;
|
||||
```
|
||||
|
||||
### Null-Aware Operators
|
||||
|
||||
```dart
|
||||
// Null-aware access
|
||||
final length = user?.name?.length;
|
||||
|
||||
// Null-aware assignment
|
||||
nickname ??= 'Anonymous';
|
||||
|
||||
// Null assertion (use sparingly)
|
||||
final definitelyNotNull = maybeNull!;
|
||||
|
||||
// Null-aware cascade
|
||||
user
|
||||
?..name = 'John'
|
||||
..email = 'john@example.com';
|
||||
|
||||
// Null coalescing
|
||||
final displayName = user.nickname ?? user.name ?? 'Unknown';
|
||||
```
|
||||
|
||||
### Null Handling Patterns
|
||||
|
||||
```dart
|
||||
// Guard clause with null check
|
||||
void processUser(User? user) {
|
||||
if (user == null) {
|
||||
throw ArgumentError('User cannot be null');
|
||||
}
|
||||
// user is promoted to non-nullable here
|
||||
print(user.name);
|
||||
}
|
||||
|
||||
// Pattern matching (Dart 3)
|
||||
void handleResult(Result? result) {
|
||||
switch (result) {
|
||||
case Success(data: final data):
|
||||
handleSuccess(data);
|
||||
case Error(message: final message):
|
||||
handleError(message);
|
||||
case null:
|
||||
handleNull();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Async/Await
|
||||
|
||||
### Future Basics
|
||||
|
||||
```dart
|
||||
// Async function
|
||||
Future<User> fetchUser(int id) async {
|
||||
final response = await http.get(Uri.parse('/users/$id'));
|
||||
if (response.statusCode != 200) {
|
||||
throw HttpException('Failed to fetch user');
|
||||
}
|
||||
return User.fromJson(jsonDecode(response.body));
|
||||
}
|
||||
|
||||
// Error handling
|
||||
Future<User?> safeFetchUser(int id) async {
|
||||
try {
|
||||
return await fetchUser(id);
|
||||
} on HttpException catch (e) {
|
||||
logger.error('HTTP error: ${e.message}');
|
||||
return null;
|
||||
} catch (e) {
|
||||
logger.error('Unexpected error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Parallel Execution
|
||||
|
||||
```dart
|
||||
// Wait for all futures
|
||||
Future<Dashboard> loadDashboard() async {
|
||||
final results = await Future.wait([
|
||||
fetchUsers(),
|
||||
fetchOrders(),
|
||||
fetchStats(),
|
||||
]);
|
||||
|
||||
return Dashboard(
|
||||
users: results[0] as List<User>,
|
||||
orders: results[1] as List<Order>,
|
||||
stats: results[2] as Stats,
|
||||
);
|
||||
}
|
||||
|
||||
// With typed results
|
||||
Future<(List<User>, List<Order>)> loadData() async {
|
||||
final (users, orders) = await (
|
||||
fetchUsers(),
|
||||
fetchOrders(),
|
||||
).wait;
|
||||
return (users, orders);
|
||||
}
|
||||
```
|
||||
|
||||
### Streams
|
||||
|
||||
```dart
|
||||
// Stream creation
|
||||
Stream<int> countStream(int max) async* {
|
||||
for (var i = 0; i < max; i++) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
yield i;
|
||||
}
|
||||
}
|
||||
|
||||
// Stream transformation
|
||||
Stream<String> userNames(Stream<User> users) {
|
||||
return users.map((user) => user.name);
|
||||
}
|
||||
|
||||
// Stream consumption
|
||||
void listenToUsers() {
|
||||
userStream.listen(
|
||||
(user) => print('New user: ${user.name}'),
|
||||
onError: (error) => print('Error: $error'),
|
||||
onDone: () => print('Stream closed'),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Widgets
|
||||
|
||||
### Stateless Widgets
|
||||
|
||||
```dart
|
||||
class UserCard extends StatelessWidget {
|
||||
const UserCard({
|
||||
super.key,
|
||||
required this.user,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final User user;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: NetworkImage(user.avatarUrl),
|
||||
),
|
||||
title: Text(user.name),
|
||||
subtitle: Text(user.email),
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stateful Widgets
|
||||
|
||||
```dart
|
||||
class Counter extends StatefulWidget {
|
||||
const Counter({super.key, this.initialValue = 0});
|
||||
|
||||
final int initialValue;
|
||||
|
||||
@override
|
||||
State<Counter> createState() => _CounterState();
|
||||
}
|
||||
|
||||
class _CounterState extends State<Counter> {
|
||||
late int _count;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_count = widget.initialValue;
|
||||
}
|
||||
|
||||
void _increment() {
|
||||
setState(() {
|
||||
_count++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text('Count: $_count'),
|
||||
ElevatedButton(
|
||||
onPressed: _increment,
|
||||
child: const Text('Increment'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Widget Best Practices
|
||||
|
||||
```dart
|
||||
// Use const constructors
|
||||
class MyWidget extends StatelessWidget {
|
||||
const MyWidget({super.key}); // const constructor
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
children: [
|
||||
Text('Hello'), // const widget
|
||||
SizedBox(height: 8), // const widget
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract widgets for reusability
|
||||
class PrimaryButton extends StatelessWidget {
|
||||
const PrimaryButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
this.isLoading = false,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ElevatedButton(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: Text(label),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Provider Pattern
|
||||
|
||||
```dart
|
||||
// Model with ChangeNotifier
|
||||
class CartModel extends ChangeNotifier {
|
||||
final List<Item> _items = [];
|
||||
|
||||
List<Item> get items => List.unmodifiable(_items);
|
||||
|
||||
double get totalPrice => _items.fold(0, (sum, item) => sum + item.price);
|
||||
|
||||
void addItem(Item item) {
|
||||
_items.add(item);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void removeItem(Item item) {
|
||||
_items.remove(item);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Provider setup
|
||||
void main() {
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => CartModel(),
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Consuming provider
|
||||
class CartPage extends StatelessWidget {
|
||||
const CartPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<CartModel>(
|
||||
builder: (context, cart, child) {
|
||||
return ListView.builder(
|
||||
itemCount: cart.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Text(cart.items[index].name),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Riverpod Pattern
|
||||
|
||||
```dart
|
||||
// Provider definition
|
||||
final userProvider = FutureProvider<User>((ref) async {
|
||||
final repository = ref.read(userRepositoryProvider);
|
||||
return repository.fetchCurrentUser();
|
||||
});
|
||||
|
||||
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
|
||||
return CounterNotifier();
|
||||
});
|
||||
|
||||
class CounterNotifier extends StateNotifier<int> {
|
||||
CounterNotifier() : super(0);
|
||||
|
||||
void increment() => state++;
|
||||
void decrement() => state--;
|
||||
}
|
||||
|
||||
// Consumer widget
|
||||
class UserProfile extends ConsumerWidget {
|
||||
const UserProfile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final userAsync = ref.watch(userProvider);
|
||||
|
||||
return userAsync.when(
|
||||
data: (user) => Text('Hello, ${user.name}'),
|
||||
loading: () => const CircularProgressIndicator(),
|
||||
error: (error, stack) => Text('Error: $error'),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### BLoC Pattern
|
||||
|
||||
```dart
|
||||
// Events
|
||||
abstract class CounterEvent {}
|
||||
class IncrementEvent extends CounterEvent {}
|
||||
class DecrementEvent extends CounterEvent {}
|
||||
|
||||
// State
|
||||
class CounterState {
|
||||
final int count;
|
||||
const CounterState(this.count);
|
||||
}
|
||||
|
||||
// BLoC
|
||||
class CounterBloc extends Bloc<CounterEvent, CounterState> {
|
||||
CounterBloc() : super(const CounterState(0)) {
|
||||
on<IncrementEvent>((event, emit) {
|
||||
emit(CounterState(state.count + 1));
|
||||
});
|
||||
|
||||
on<DecrementEvent>((event, emit) {
|
||||
emit(CounterState(state.count - 1));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
class CounterPage extends StatelessWidget {
|
||||
const CounterPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CounterBloc, CounterState>(
|
||||
builder: (context, state) {
|
||||
return Text('Count: ${state.count}');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```dart
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('Calculator', () {
|
||||
late Calculator calculator;
|
||||
|
||||
setUp(() {
|
||||
calculator = Calculator();
|
||||
});
|
||||
|
||||
test('adds two positive numbers', () {
|
||||
expect(calculator.add(2, 3), equals(5));
|
||||
});
|
||||
|
||||
test('handles negative numbers', () {
|
||||
expect(calculator.add(-1, 1), equals(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Widget Tests
|
||||
|
||||
```dart
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments', (WidgetTester tester) async {
|
||||
// Build widget
|
||||
await tester.pumpWidget(const MaterialApp(home: Counter()));
|
||||
|
||||
// Verify initial state
|
||||
expect(find.text('Count: 0'), findsOneWidget);
|
||||
|
||||
// Tap increment button
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify incremented state
|
||||
expect(find.text('Count: 1'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows loading indicator', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: UserProfile(isLoading: true),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Mocking
|
||||
|
||||
```dart
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
@GenerateMocks([UserRepository])
|
||||
void main() {
|
||||
late MockUserRepository mockRepository;
|
||||
late UserService service;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockUserRepository();
|
||||
service = UserService(mockRepository);
|
||||
});
|
||||
|
||||
test('fetches user by id', () async {
|
||||
final user = User(id: 1, name: 'John');
|
||||
when(mockRepository.findById(1)).thenAnswer((_) async => user);
|
||||
|
||||
final result = await service.getUser(1);
|
||||
|
||||
expect(result, equals(user));
|
||||
verify(mockRepository.findById(1)).called(1);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Factory Constructors
|
||||
|
||||
```dart
|
||||
class User {
|
||||
final int id;
|
||||
final String name;
|
||||
final String email;
|
||||
|
||||
const User({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.email,
|
||||
});
|
||||
|
||||
// Factory from JSON
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return User(
|
||||
id: json['id'] as int,
|
||||
name: json['name'] as String,
|
||||
email: json['email'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
// Factory for default user
|
||||
factory User.guest() {
|
||||
return const User(
|
||||
id: 0,
|
||||
name: 'Guest',
|
||||
email: 'guest@example.com',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'email': email,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Extension Methods
|
||||
|
||||
```dart
|
||||
extension StringExtensions on String {
|
||||
String capitalize() {
|
||||
if (isEmpty) return this;
|
||||
return '${this[0].toUpperCase()}${substring(1)}';
|
||||
}
|
||||
|
||||
bool get isValidEmail {
|
||||
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
|
||||
}
|
||||
}
|
||||
|
||||
extension DateTimeExtensions on DateTime {
|
||||
String get formatted => '${day.toString().padLeft(2, '0')}/'
|
||||
'${month.toString().padLeft(2, '0')}/$year';
|
||||
|
||||
bool get isToday {
|
||||
final now = DateTime.now();
|
||||
return year == now.year && month == now.month && day == now.day;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
final name = 'john'.capitalize(); // 'John'
|
||||
final isValid = 'test@example.com'.isValidEmail; // true
|
||||
```
|
||||
|
||||
### Sealed Classes (Dart 3)
|
||||
|
||||
```dart
|
||||
sealed class Result<T> {}
|
||||
|
||||
class Success<T> extends Result<T> {
|
||||
final T data;
|
||||
Success(this.data);
|
||||
}
|
||||
|
||||
class Error<T> extends Result<T> {
|
||||
final String message;
|
||||
Error(this.message);
|
||||
}
|
||||
|
||||
class Loading<T> extends Result<T> {}
|
||||
|
||||
// Usage with exhaustive pattern matching
|
||||
Widget buildResult(Result<User> result) {
|
||||
return switch (result) {
|
||||
Success(data: final user) => Text(user.name),
|
||||
Error(message: final msg) => Text('Error: $msg'),
|
||||
Loading() => const CircularProgressIndicator(),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Freezed for Immutable Data
|
||||
|
||||
```dart
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'user.freezed.dart';
|
||||
part 'user.g.dart';
|
||||
|
||||
@freezed
|
||||
class User with _$User {
|
||||
const factory User({
|
||||
required int id,
|
||||
required String name,
|
||||
required String email,
|
||||
@Default(false) bool isActive,
|
||||
}) = _User;
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
||||
}
|
||||
|
||||
// Usage
|
||||
final user = User(id: 1, name: 'John', email: 'john@example.com');
|
||||
final updatedUser = user.copyWith(name: 'Jane');
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Feature-Based Organization
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart
|
||||
├── app.dart
|
||||
├── core/
|
||||
│ ├── constants/
|
||||
│ ├── extensions/
|
||||
│ ├── utils/
|
||||
│ └── widgets/
|
||||
├── features/
|
||||
│ ├── auth/
|
||||
│ │ ├── data/
|
||||
│ │ ├── domain/
|
||||
│ │ └── presentation/
|
||||
│ ├── home/
|
||||
│ │ ├── data/
|
||||
│ │ ├── domain/
|
||||
│ │ └── presentation/
|
||||
│ └── profile/
|
||||
└── shared/
|
||||
├── models/
|
||||
├── services/
|
||||
└── widgets/
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
```dart
|
||||
// Files: snake_case
|
||||
// user_repository.dart
|
||||
// home_screen.dart
|
||||
|
||||
// Classes: PascalCase
|
||||
class UserRepository {}
|
||||
class HomeScreen extends StatelessWidget {}
|
||||
|
||||
// Variables and functions: camelCase
|
||||
final userName = 'John';
|
||||
void fetchUserData() {}
|
||||
|
||||
// Constants: camelCase or SCREAMING_SNAKE_CASE
|
||||
const defaultPadding = 16.0;
|
||||
const API_BASE_URL = 'https://api.example.com';
|
||||
|
||||
// Private: underscore prefix
|
||||
class _HomeScreenState extends State<HomeScreen> {}
|
||||
final _internalCache = <String, dynamic>{};
|
||||
```
|
||||
235
plugins/conductor/templates/code_styleguides/general.md
Normal file
235
plugins/conductor/templates/code_styleguides/general.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# General Code Style Guide
|
||||
|
||||
Universal coding principles that apply across all languages and frameworks.
|
||||
|
||||
## Readability
|
||||
|
||||
### Code is Read More Than Written
|
||||
|
||||
- Write code for humans first, computers second
|
||||
- Favor clarity over cleverness
|
||||
- If code needs a comment to explain what it does, consider rewriting it
|
||||
|
||||
### Formatting
|
||||
|
||||
- Consistent indentation (use project standard)
|
||||
- Reasonable line length (80-120 characters)
|
||||
- Logical grouping with whitespace
|
||||
- One statement per line
|
||||
|
||||
### Structure
|
||||
|
||||
- Keep functions/methods short (ideally < 20 lines)
|
||||
- One level of abstraction per function
|
||||
- Early returns to reduce nesting
|
||||
- Group related code together
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### General Principles
|
||||
|
||||
- Names should reveal intent
|
||||
- Avoid abbreviations (except universally understood ones)
|
||||
- Be consistent within codebase
|
||||
- Length proportional to scope
|
||||
|
||||
### Variables
|
||||
|
||||
```
|
||||
# Bad
|
||||
d = 86400 # What is this?
|
||||
temp = getUserData() # Temp what?
|
||||
|
||||
# Good
|
||||
secondsPerDay = 86400
|
||||
userData = getUserData()
|
||||
```
|
||||
|
||||
### Functions/Methods
|
||||
|
||||
- Use verbs for actions: `calculateTotal()`, `validateInput()`
|
||||
- Use `is/has/can` for booleans: `isValid()`, `hasPermission()`
|
||||
- Be specific: `sendEmailNotification()` not `send()`
|
||||
|
||||
### Constants
|
||||
|
||||
- Use SCREAMING_SNAKE_CASE or language convention
|
||||
- Group related constants
|
||||
- Document magic numbers
|
||||
|
||||
### Classes/Types
|
||||
|
||||
- Use nouns: `User`, `OrderProcessor`, `ValidationResult`
|
||||
- Avoid generic names: `Manager`, `Handler`, `Data`
|
||||
|
||||
## Comments
|
||||
|
||||
### When to Comment
|
||||
|
||||
- WHY, not WHAT (code shows what, comments explain why)
|
||||
- Complex algorithms or business logic
|
||||
- Non-obvious workarounds with references
|
||||
- Public API documentation
|
||||
|
||||
### When NOT to Comment
|
||||
|
||||
- Obvious code
|
||||
- Commented-out code (delete it)
|
||||
- Change history (use git)
|
||||
- TODOs without tickets (create tickets instead)
|
||||
|
||||
### Comment Quality
|
||||
|
||||
```
|
||||
# Bad
|
||||
i += 1 # Increment i
|
||||
|
||||
# Good
|
||||
# Retry limit based on SLA requirements (see JIRA-1234)
|
||||
maxRetries = 3
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Principles
|
||||
|
||||
- Fail fast and explicitly
|
||||
- Handle errors at appropriate level
|
||||
- Preserve error context
|
||||
- Log for debugging, throw for callers
|
||||
|
||||
### Patterns
|
||||
|
||||
```
|
||||
# Bad: Silent failure
|
||||
try:
|
||||
result = riskyOperation()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Good: Explicit handling
|
||||
try:
|
||||
result = riskyOperation()
|
||||
except SpecificError as e:
|
||||
logger.error(f"Operation failed: {e}")
|
||||
raise OperationFailed("Unable to complete operation") from e
|
||||
```
|
||||
|
||||
### Error Messages
|
||||
|
||||
- Be specific about what failed
|
||||
- Include relevant context
|
||||
- Suggest remediation when possible
|
||||
- Avoid exposing internal details to users
|
||||
|
||||
## Functions and Methods
|
||||
|
||||
### Single Responsibility
|
||||
|
||||
- One function = one task
|
||||
- If you need "and" to describe it, split it
|
||||
- Extract helper functions for clarity
|
||||
|
||||
### Parameters
|
||||
|
||||
- Limit parameters (ideally ≤ 3)
|
||||
- Use objects/structs for many parameters
|
||||
- Avoid boolean parameters (use named options)
|
||||
- Order: required first, optional last
|
||||
|
||||
### Return Values
|
||||
|
||||
- Return early for edge cases
|
||||
- Consistent return types
|
||||
- Avoid returning null/nil when possible
|
||||
- Consider Result/Option types for failures
|
||||
|
||||
## Code Organization
|
||||
|
||||
### File Structure
|
||||
|
||||
- One primary concept per file
|
||||
- Related helpers in same file or nearby
|
||||
- Consistent file naming
|
||||
- Logical directory structure
|
||||
|
||||
### Import/Dependency Order
|
||||
|
||||
1. Standard library
|
||||
2. External dependencies
|
||||
3. Internal dependencies
|
||||
4. Local/relative imports
|
||||
|
||||
### Coupling and Cohesion
|
||||
|
||||
- High cohesion within modules
|
||||
- Low coupling between modules
|
||||
- Depend on abstractions, not implementations
|
||||
- Avoid circular dependencies
|
||||
|
||||
## Testing Considerations
|
||||
|
||||
### Testable Code
|
||||
|
||||
- Pure functions where possible
|
||||
- Dependency injection
|
||||
- Avoid global state
|
||||
- Small, focused functions
|
||||
|
||||
### Test Naming
|
||||
|
||||
```
|
||||
# Describe behavior, not implementation
|
||||
test_user_can_login_with_valid_credentials()
|
||||
test_order_total_includes_tax_and_shipping()
|
||||
```
|
||||
|
||||
## Security Basics
|
||||
|
||||
### Input Validation
|
||||
|
||||
- Validate all external input
|
||||
- Sanitize before use
|
||||
- Whitelist over blacklist
|
||||
- Fail closed (deny by default)
|
||||
|
||||
### Secrets
|
||||
|
||||
- Never hardcode secrets
|
||||
- Use environment variables or secret managers
|
||||
- Don't log sensitive data
|
||||
- Rotate credentials regularly
|
||||
|
||||
### Data Handling
|
||||
|
||||
- Minimize data collection
|
||||
- Encrypt sensitive data
|
||||
- Secure data in transit and at rest
|
||||
- Follow principle of least privilege
|
||||
|
||||
## Performance Mindset
|
||||
|
||||
### Premature Optimization
|
||||
|
||||
- Make it work, then make it fast
|
||||
- Measure before optimizing
|
||||
- Optimize bottlenecks, not everything
|
||||
- Document performance-critical code
|
||||
|
||||
### Common Pitfalls
|
||||
|
||||
- N+1 queries
|
||||
- Unnecessary allocations in loops
|
||||
- Missing indexes
|
||||
- Synchronous operations that could be async
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
- [ ] Does it work correctly?
|
||||
- [ ] Is it readable and maintainable?
|
||||
- [ ] Are edge cases handled?
|
||||
- [ ] Is error handling appropriate?
|
||||
- [ ] Are there security concerns?
|
||||
- [ ] Is it tested adequately?
|
||||
- [ ] Does it follow project conventions?
|
||||
- [ ] Is there unnecessary complexity?
|
||||
562
plugins/conductor/templates/code_styleguides/go.md
Normal file
562
plugins/conductor/templates/code_styleguides/go.md
Normal file
@@ -0,0 +1,562 @@
|
||||
# Go Style Guide
|
||||
|
||||
Go idioms and conventions for clean, maintainable code.
|
||||
|
||||
## gofmt and Standard Formatting
|
||||
|
||||
### Always Use gofmt
|
||||
|
||||
```bash
|
||||
# Format a single file
|
||||
gofmt -w file.go
|
||||
|
||||
# Format entire project
|
||||
gofmt -w .
|
||||
|
||||
# Use goimports for imports management
|
||||
goimports -w .
|
||||
```
|
||||
|
||||
### Formatting Rules (Enforced by gofmt)
|
||||
|
||||
- Tabs for indentation
|
||||
- No trailing whitespace
|
||||
- Consistent brace placement
|
||||
- Standardized spacing
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Explicit Error Checking
|
||||
|
||||
```go
|
||||
// Always check errors explicitly
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening file %s: %w", filename, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Don't ignore errors with _
|
||||
// Bad
|
||||
data, _ := json.Marshal(obj)
|
||||
|
||||
// Good
|
||||
data, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling object: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Error Wrapping
|
||||
|
||||
```go
|
||||
// Use %w to wrap errors for unwrapping later
|
||||
func processFile(path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading file %s: %w", path, err)
|
||||
}
|
||||
|
||||
if err := validate(data); err != nil {
|
||||
return fmt.Errorf("validating data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check wrapped errors
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// Handle file not found
|
||||
}
|
||||
|
||||
var validationErr *ValidationError
|
||||
if errors.As(err, &validationErr) {
|
||||
// Handle validation error
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Error Types
|
||||
|
||||
```go
|
||||
// Sentinel errors for expected conditions
|
||||
var (
|
||||
ErrNotFound = errors.New("resource not found")
|
||||
ErrUnauthorized = errors.New("unauthorized access")
|
||||
ErrInvalidInput = errors.New("invalid input")
|
||||
)
|
||||
|
||||
// Custom error type with additional context
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
|
||||
}
|
||||
|
||||
// Error constructor
|
||||
func NewValidationError(field, message string) error {
|
||||
return &ValidationError{Field: field, Message: message}
|
||||
}
|
||||
```
|
||||
|
||||
## Interfaces
|
||||
|
||||
### Small, Focused Interfaces
|
||||
|
||||
```go
|
||||
// Good: Single-method interface
|
||||
type Reader interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
type Writer interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// Compose interfaces
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
// Bad: Large interfaces
|
||||
type Repository interface {
|
||||
Find(id string) (*User, error)
|
||||
FindAll() ([]*User, error)
|
||||
Create(user *User) error
|
||||
Update(user *User) error
|
||||
Delete(id string) error
|
||||
FindByEmail(email string) (*User, error)
|
||||
// Too many methods - hard to implement and test
|
||||
}
|
||||
```
|
||||
|
||||
### Accept Interfaces, Return Structs
|
||||
|
||||
```go
|
||||
// Good: Accept interface, return concrete type
|
||||
func NewUserService(repo UserRepository) *UserService {
|
||||
return &UserService{repo: repo}
|
||||
}
|
||||
|
||||
// Interface defined by consumer
|
||||
type UserRepository interface {
|
||||
Find(ctx context.Context, id string) (*User, error)
|
||||
Save(ctx context.Context, user *User) error
|
||||
}
|
||||
|
||||
// Concrete implementation
|
||||
type PostgresUserRepo struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *PostgresUserRepo) Find(ctx context.Context, id string) (*User, error) {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Interface Naming
|
||||
|
||||
```go
|
||||
// Single-method interfaces: method name + "er"
|
||||
type Reader interface { Read(p []byte) (n int, err error) }
|
||||
type Writer interface { Write(p []byte) (n int, err error) }
|
||||
type Closer interface { Close() error }
|
||||
type Stringer interface { String() string }
|
||||
|
||||
// Multi-method interfaces: descriptive name
|
||||
type UserStore interface {
|
||||
Get(ctx context.Context, id string) (*User, error)
|
||||
Put(ctx context.Context, user *User) error
|
||||
}
|
||||
```
|
||||
|
||||
## Package Structure
|
||||
|
||||
### Standard Layout
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── cmd/
|
||||
│ └── myapp/
|
||||
│ └── main.go # Application entry point
|
||||
├── internal/
|
||||
│ ├── auth/
|
||||
│ │ ├── auth.go
|
||||
│ │ └── auth_test.go
|
||||
│ ├── user/
|
||||
│ │ ├── user.go
|
||||
│ │ ├── repository.go
|
||||
│ │ └── service.go
|
||||
│ └── config/
|
||||
│ └── config.go
|
||||
├── pkg/ # Public packages (optional)
|
||||
│ └── api/
|
||||
│ └── client.go
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Package Guidelines
|
||||
|
||||
```go
|
||||
// Package names: short, lowercase, no underscores
|
||||
package user // Good
|
||||
package userService // Bad
|
||||
package user_service // Bad
|
||||
|
||||
// Package comment at top of primary file
|
||||
// Package user provides user management functionality.
|
||||
package user
|
||||
|
||||
// Group imports: stdlib, external, internal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"myproject/internal/config"
|
||||
)
|
||||
```
|
||||
|
||||
### Internal Packages
|
||||
|
||||
```go
|
||||
// internal/ packages cannot be imported from outside the module
|
||||
// Use for implementation details you don't want to expose
|
||||
|
||||
// myproject/internal/cache/cache.go
|
||||
package cache
|
||||
|
||||
// This can only be imported by code in myproject/
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test File Organization
|
||||
|
||||
```go
|
||||
// user_test.go - same package
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserValidation(t *testing.T) {
|
||||
// Test implementation details
|
||||
}
|
||||
|
||||
// user_integration_test.go - external test package
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"myproject/internal/user"
|
||||
)
|
||||
|
||||
func TestUserService(t *testing.T) {
|
||||
// Test public API
|
||||
}
|
||||
```
|
||||
|
||||
### Table-Driven Tests
|
||||
|
||||
```go
|
||||
func TestAdd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b int
|
||||
expected int
|
||||
}{
|
||||
{"positive numbers", 2, 3, 5},
|
||||
{"negative numbers", -1, -1, -2},
|
||||
{"mixed numbers", -1, 5, 4},
|
||||
{"zeros", 0, 0, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Add(tt.a, tt.b)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Add(%d, %d) = %d; want %d",
|
||||
tt.a, tt.b, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Helpers
|
||||
|
||||
```go
|
||||
// Helper functions should call t.Helper()
|
||||
func newTestUser(t *testing.T) *User {
|
||||
t.Helper()
|
||||
return &User{
|
||||
ID: uuid.New().String(),
|
||||
Name: "Test User",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
}
|
||||
|
||||
func assertNoError(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual[T comparable](t *testing.T, got, want T) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mocking with Interfaces
|
||||
|
||||
```go
|
||||
// Define interface for dependency
|
||||
type UserRepository interface {
|
||||
Find(ctx context.Context, id string) (*User, error)
|
||||
Save(ctx context.Context, user *User) error
|
||||
}
|
||||
|
||||
// Mock implementation for testing
|
||||
type mockUserRepo struct {
|
||||
users map[string]*User
|
||||
}
|
||||
|
||||
func newMockUserRepo() *mockUserRepo {
|
||||
return &mockUserRepo{users: make(map[string]*User)}
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) Find(ctx context.Context, id string) (*User, error) {
|
||||
user, ok := m.users[id]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) Save(ctx context.Context, user *User) error {
|
||||
m.users[user.ID] = user
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test using mock
|
||||
func TestUserService_GetUser(t *testing.T) {
|
||||
repo := newMockUserRepo()
|
||||
repo.users["123"] = &User{ID: "123", Name: "Test"}
|
||||
|
||||
service := NewUserService(repo)
|
||||
user, err := service.GetUser(context.Background(), "123")
|
||||
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, user.Name, "Test")
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Options Pattern
|
||||
|
||||
```go
|
||||
// Option function type
|
||||
type ServerOption func(*Server)
|
||||
|
||||
// Option functions
|
||||
func WithPort(port int) ServerOption {
|
||||
return func(s *Server) {
|
||||
s.port = port
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) ServerOption {
|
||||
return func(s *Server) {
|
||||
s.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogger(logger *slog.Logger) ServerOption {
|
||||
return func(s *Server) {
|
||||
s.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor using options
|
||||
func NewServer(opts ...ServerOption) *Server {
|
||||
s := &Server{
|
||||
port: 8080, // defaults
|
||||
timeout: 30 * time.Second,
|
||||
logger: slog.Default(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Usage
|
||||
server := NewServer(
|
||||
WithPort(9000),
|
||||
WithTimeout(time.Minute),
|
||||
)
|
||||
```
|
||||
|
||||
### Context Usage
|
||||
|
||||
```go
|
||||
// Always pass context as first parameter
|
||||
func (s *Service) ProcessRequest(ctx context.Context, req *Request) (*Response, error) {
|
||||
// Check for cancellation
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Use context for timeouts
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result, err := s.repo.Find(ctx, req.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("finding item: %w", err)
|
||||
}
|
||||
|
||||
return &Response{Data: result}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Defer for Cleanup
|
||||
|
||||
```go
|
||||
func processFile(path string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close() // Always executed on return
|
||||
|
||||
// Process file...
|
||||
return nil
|
||||
}
|
||||
|
||||
// Multiple defers execute in LIFO order
|
||||
func transaction(db *sql.DB) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback() // Safe: no-op if committed
|
||||
|
||||
// Do work...
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
```
|
||||
|
||||
### Concurrency Patterns
|
||||
|
||||
```go
|
||||
// Worker pool
|
||||
func processItems(items []Item, workers int) []Result {
|
||||
jobs := make(chan Item, len(items))
|
||||
results := make(chan Result, len(items))
|
||||
|
||||
// Start workers
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for item := range jobs {
|
||||
results <- process(item)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Send jobs
|
||||
for _, item := range items {
|
||||
jobs <- item
|
||||
}
|
||||
close(jobs)
|
||||
|
||||
// Wait and collect
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
var out []Result
|
||||
for r := range results {
|
||||
out = append(out, r)
|
||||
}
|
||||
return out
|
||||
}
|
||||
```
|
||||
|
||||
## Code Quality
|
||||
|
||||
### Linting with golangci-lint
|
||||
|
||||
```yaml
|
||||
# .golangci.yml
|
||||
linters:
|
||||
enable:
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- unused
|
||||
- gosimple
|
||||
- gocritic
|
||||
- gofmt
|
||||
- goimports
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
```
|
||||
|
||||
### Common Commands
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
go fmt ./...
|
||||
|
||||
# Run linter
|
||||
golangci-lint run
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
|
||||
# Run tests with coverage
|
||||
go test -coverprofile=coverage.out ./...
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
# Check for race conditions
|
||||
go test -race ./...
|
||||
|
||||
# Build
|
||||
go build ./...
|
||||
```
|
||||
618
plugins/conductor/templates/code_styleguides/html-css.md
Normal file
618
plugins/conductor/templates/code_styleguides/html-css.md
Normal file
@@ -0,0 +1,618 @@
|
||||
# HTML & CSS Style Guide
|
||||
|
||||
Web standards for semantic markup, maintainable styling, and accessibility.
|
||||
|
||||
## Semantic HTML
|
||||
|
||||
### Document Structure
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Page description for SEO" />
|
||||
<title>Page Title | Site Name</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav aria-label="Main navigation">
|
||||
<!-- Navigation -->
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<!-- Primary content -->
|
||||
</article>
|
||||
<aside>
|
||||
<!-- Supplementary content -->
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<!-- Footer content -->
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Semantic Elements
|
||||
|
||||
```html
|
||||
<!-- Use appropriate semantic elements -->
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav aria-label="Main navigation">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/about">About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Article with header and footer -->
|
||||
<article>
|
||||
<header>
|
||||
<h1>Article Title</h1>
|
||||
<time datetime="2024-01-15">January 15, 2024</time>
|
||||
</header>
|
||||
|
||||
<p>Article content...</p>
|
||||
|
||||
<footer>
|
||||
<p>Written by <address>Author Name</address></p>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- Sections with headings -->
|
||||
<section aria-labelledby="features-heading">
|
||||
<h2 id="features-heading">Features</h2>
|
||||
<p>Section content...</p>
|
||||
</section>
|
||||
|
||||
<!-- Figures with captions -->
|
||||
<figure>
|
||||
<img src="chart.png" alt="Sales data showing 20% growth">
|
||||
<figcaption>Q4 2024 Sales Performance</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- Definition lists -->
|
||||
<dl>
|
||||
<dt>HTML</dt>
|
||||
<dd>HyperText Markup Language</dd>
|
||||
<dt>CSS</dt>
|
||||
<dd>Cascading Style Sheets</dd>
|
||||
</dl>
|
||||
```
|
||||
|
||||
### Form Elements
|
||||
|
||||
```html
|
||||
<form action="/submit" method="POST">
|
||||
<!-- Text input with label -->
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
autocomplete="email"
|
||||
aria-describedby="email-hint"
|
||||
/>
|
||||
<span id="email-hint" class="hint">We'll never share your email.</span>
|
||||
</div>
|
||||
|
||||
<!-- Select with label -->
|
||||
<div class="form-group">
|
||||
<label for="country">Country</label>
|
||||
<select id="country" name="country" required>
|
||||
<option value="">Select a country</option>
|
||||
<option value="us">United States</option>
|
||||
<option value="uk">United Kingdom</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Radio group with fieldset -->
|
||||
<fieldset>
|
||||
<legend>Preferred Contact Method</legend>
|
||||
<div>
|
||||
<input type="radio" id="contact-email" name="contact" value="email" />
|
||||
<label for="contact-email">Email</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="contact-phone" name="contact" value="phone" />
|
||||
<label for="contact-phone">Phone</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Submit button -->
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## BEM Naming Convention
|
||||
|
||||
### Block, Element, Modifier
|
||||
|
||||
```css
|
||||
/* Block: Standalone component */
|
||||
.card {
|
||||
}
|
||||
|
||||
/* Element: Part of block (double underscore) */
|
||||
.card__header {
|
||||
}
|
||||
.card__body {
|
||||
}
|
||||
.card__footer {
|
||||
}
|
||||
|
||||
/* Modifier: Variation (double hyphen) */
|
||||
.card--featured {
|
||||
}
|
||||
.card--compact {
|
||||
}
|
||||
.card__header--centered {
|
||||
}
|
||||
```
|
||||
|
||||
### BEM Examples
|
||||
|
||||
```html
|
||||
<!-- Card component -->
|
||||
<article class="card card--featured">
|
||||
<header class="card__header">
|
||||
<h2 class="card__title">Card Title</h2>
|
||||
</header>
|
||||
<div class="card__body">
|
||||
<p class="card__text">Card content goes here.</p>
|
||||
</div>
|
||||
<footer class="card__footer">
|
||||
<button class="card__button card__button--primary">Action</button>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- Navigation component -->
|
||||
<nav class="nav nav--horizontal">
|
||||
<ul class="nav__list">
|
||||
<li class="nav__item nav__item--active">
|
||||
<a class="nav__link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav__item">
|
||||
<a class="nav__link" href="/about">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
```
|
||||
|
||||
### BEM Best Practices
|
||||
|
||||
```css
|
||||
/* Avoid deep nesting */
|
||||
/* Bad */
|
||||
.card__header__title__icon {
|
||||
}
|
||||
|
||||
/* Good - flatten structure */
|
||||
.card__title-icon {
|
||||
}
|
||||
|
||||
/* Avoid styling elements without class */
|
||||
/* Bad */
|
||||
.card h2 {
|
||||
}
|
||||
|
||||
/* Good */
|
||||
.card__title {
|
||||
}
|
||||
|
||||
/* Modifiers extend base styles */
|
||||
.button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.button--large {
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
background: blue;
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
### ARIA Attributes
|
||||
|
||||
```html
|
||||
<!-- Live regions for dynamic content -->
|
||||
<div aria-live="polite" aria-atomic="true">Status updates appear here</div>
|
||||
|
||||
<!-- Landmarks -->
|
||||
<nav aria-label="Main navigation"></nav>
|
||||
<nav aria-label="Footer navigation"></nav>
|
||||
|
||||
<!-- Current page in navigation -->
|
||||
<a href="/about" aria-current="page">About</a>
|
||||
|
||||
<!-- Expanded/collapsed state -->
|
||||
<button aria-expanded="false" aria-controls="menu">Toggle Menu</button>
|
||||
<div id="menu" hidden>Menu content</div>
|
||||
|
||||
<!-- Disabled vs aria-disabled -->
|
||||
<button disabled>Can't click (removed from tab order)</button>
|
||||
<button aria-disabled="true">Can't click (stays in tab order)</button>
|
||||
|
||||
<!-- Loading states -->
|
||||
<button aria-busy="true">
|
||||
<span aria-hidden="true">Loading...</span>
|
||||
<span class="visually-hidden">Please wait</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
```html
|
||||
<!-- Skip link -->
|
||||
<a href="#main-content" class="skip-link">Skip to main content</a>
|
||||
|
||||
<!-- Focusable elements should be obvious -->
|
||||
<style>
|
||||
:focus-visible {
|
||||
outline: 2px solid blue;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Tabindex usage -->
|
||||
<!-- tabindex="0": Add to tab order -->
|
||||
<div tabindex="0" role="button">Custom button</div>
|
||||
|
||||
<!-- tabindex="-1": Programmatically focusable only -->
|
||||
<div id="modal" tabindex="-1">Modal content</div>
|
||||
|
||||
<!-- Never use tabindex > 0 -->
|
||||
```
|
||||
|
||||
### Screen Reader Support
|
||||
|
||||
```css
|
||||
/* Visually hidden but accessible */
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Hide from screen readers */
|
||||
[aria-hidden="true"] {
|
||||
/* Decorative content */
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Icon buttons need accessible names -->
|
||||
<button aria-label="Close dialog">
|
||||
<svg aria-hidden="true"><!-- icon --></svg>
|
||||
</button>
|
||||
|
||||
<!-- Decorative images -->
|
||||
<img src="decoration.png" alt="" role="presentation" />
|
||||
|
||||
<!-- Informative images -->
|
||||
<img src="chart.png" alt="Sales increased 20% in Q4 2024" />
|
||||
|
||||
<!-- Complex images -->
|
||||
<figure>
|
||||
<img
|
||||
src="flowchart.png"
|
||||
alt="User registration process"
|
||||
aria-describedby="flowchart-desc"
|
||||
/>
|
||||
<figcaption id="flowchart-desc">
|
||||
Step 1: Enter email. Step 2: Verify email. Step 3: Create password.
|
||||
</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
## Responsive Design
|
||||
|
||||
### Mobile-First Approach
|
||||
|
||||
```css
|
||||
/* Base styles for mobile */
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Tablet and up */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop and up */
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
padding: 32px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Flexible Units
|
||||
|
||||
```css
|
||||
/* Use relative units */
|
||||
body {
|
||||
font-size: 16px; /* Base size */
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem; /* Relative to root */
|
||||
margin-bottom: 1em; /* Relative to element */
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 75ch; /* Character width for readability */
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Fluid typography */
|
||||
h1 {
|
||||
font-size: clamp(1.5rem, 4vw, 3rem);
|
||||
}
|
||||
|
||||
/* Fluid spacing */
|
||||
.section {
|
||||
padding: clamp(2rem, 5vw, 4rem);
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Images
|
||||
|
||||
```html
|
||||
<!-- Responsive image with srcset -->
|
||||
<img
|
||||
src="image-800.jpg"
|
||||
srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
|
||||
sizes="(max-width: 600px) 100vw, 50vw"
|
||||
alt="Description"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<!-- Art direction with picture -->
|
||||
<picture>
|
||||
<source media="(min-width: 1024px)" srcset="hero-desktop.jpg" />
|
||||
<source media="(min-width: 768px)" srcset="hero-tablet.jpg" />
|
||||
<img src="hero-mobile.jpg" alt="Hero image" />
|
||||
</picture>
|
||||
```
|
||||
|
||||
## CSS Best Practices
|
||||
|
||||
### Custom Properties (CSS Variables)
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-primary: #0066cc;
|
||||
--color-primary-dark: #004c99;
|
||||
--color-secondary: #6c757d;
|
||||
--color-success: #28a745;
|
||||
--color-error: #dc3545;
|
||||
|
||||
/* Typography */
|
||||
--font-family-base: system-ui, sans-serif;
|
||||
--font-family-mono: ui-monospace, monospace;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.25rem;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 0.25rem;
|
||||
--spacing-sm: 0.5rem;
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 1.5rem;
|
||||
--spacing-xl: 2rem;
|
||||
|
||||
/* Borders */
|
||||
--border-radius: 4px;
|
||||
--border-color: #dee2e6;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-primary: #4da6ff;
|
||||
--color-background: #1a1a1a;
|
||||
--color-text: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
/* Usage */
|
||||
.button {
|
||||
background: var(--color-primary);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
```
|
||||
|
||||
### Modern Layout
|
||||
|
||||
```css
|
||||
/* Flexbox for 1D layouts */
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Grid for 2D layouts */
|
||||
.page-layout {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sidebar main"
|
||||
"footer footer";
|
||||
grid-template-columns: 250px 1fr;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
}
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
}
|
||||
.main {
|
||||
grid-area: main;
|
||||
}
|
||||
.footer {
|
||||
grid-area: footer;
|
||||
}
|
||||
|
||||
/* Auto-fit grid */
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
```css
|
||||
/* Avoid expensive properties in animations */
|
||||
/* Bad - triggers layout */
|
||||
.animate-bad {
|
||||
animation: move 1s;
|
||||
}
|
||||
@keyframes move {
|
||||
to {
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Good - uses transform */
|
||||
.animate-good {
|
||||
animation: move-optimized 1s;
|
||||
}
|
||||
@keyframes move-optimized {
|
||||
to {
|
||||
transform: translate(100px, 100px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Use will-change sparingly */
|
||||
.will-animate {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* Contain for layout isolation */
|
||||
.card {
|
||||
contain: layout style;
|
||||
}
|
||||
|
||||
/* Content-visibility for off-screen content */
|
||||
.below-fold {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 500px;
|
||||
}
|
||||
```
|
||||
|
||||
## HTML Best Practices
|
||||
|
||||
### Validation and Attributes
|
||||
|
||||
```html
|
||||
<!-- Use proper input types -->
|
||||
<input type="email" autocomplete="email" />
|
||||
<input type="tel" autocomplete="tel" />
|
||||
<input type="url" />
|
||||
<input type="number" min="0" max="100" step="1" />
|
||||
<input type="date" min="2024-01-01" />
|
||||
|
||||
<!-- Required and validation -->
|
||||
<input type="text" required minlength="2" maxlength="50" pattern="[A-Za-z]+" />
|
||||
|
||||
<!-- Autocomplete for better UX -->
|
||||
<input type="text" name="name" autocomplete="name" />
|
||||
<input type="text" name="address" autocomplete="street-address" />
|
||||
<input type="text" name="cc-number" autocomplete="cc-number" />
|
||||
```
|
||||
|
||||
### Performance Attributes
|
||||
|
||||
```html
|
||||
<!-- Lazy loading -->
|
||||
<img src="image.jpg" loading="lazy" alt="Description" />
|
||||
<iframe src="video.html" loading="lazy"></iframe>
|
||||
|
||||
<!-- Preload critical resources -->
|
||||
<link rel="preload" href="critical.css" as="style" />
|
||||
<link rel="preload" href="hero.jpg" as="image" />
|
||||
<link rel="preload" href="font.woff2" as="font" crossorigin />
|
||||
|
||||
<!-- Preconnect to origins -->
|
||||
<link rel="preconnect" href="https://api.example.com" />
|
||||
<link rel="dns-prefetch" href="https://analytics.example.com" />
|
||||
|
||||
<!-- Async/defer scripts -->
|
||||
<script src="analytics.js" async></script>
|
||||
<script src="app.js" defer></script>
|
||||
```
|
||||
|
||||
### Microdata and SEO
|
||||
|
||||
```html
|
||||
<!-- Schema.org markup -->
|
||||
<article itemscope itemtype="https://schema.org/Article">
|
||||
<h1 itemprop="headline">Article Title</h1>
|
||||
<time itemprop="datePublished" datetime="2024-01-15"> January 15, 2024 </time>
|
||||
<div itemprop="author" itemscope itemtype="https://schema.org/Person">
|
||||
<span itemprop="name">Author Name</span>
|
||||
</div>
|
||||
<div itemprop="articleBody">Article content...</div>
|
||||
</article>
|
||||
|
||||
<!-- Open Graph for social sharing -->
|
||||
<meta property="og:title" content="Page Title" />
|
||||
<meta property="og:description" content="Page description" />
|
||||
<meta property="og:image" content="https://example.com/image.jpg" />
|
||||
<meta property="og:url" content="https://example.com/page" />
|
||||
```
|
||||
569
plugins/conductor/templates/code_styleguides/javascript.md
Normal file
569
plugins/conductor/templates/code_styleguides/javascript.md
Normal file
@@ -0,0 +1,569 @@
|
||||
# JavaScript Style Guide
|
||||
|
||||
Modern JavaScript (ES6+) best practices and conventions.
|
||||
|
||||
## ES6+ Features
|
||||
|
||||
### Use Modern Syntax
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```javascript
|
||||
// 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));
|
||||
```
|
||||
566
plugins/conductor/templates/code_styleguides/python.md
Normal file
566
plugins/conductor/templates/code_styleguides/python.md
Normal file
@@ -0,0 +1,566 @@
|
||||
# Python Style Guide
|
||||
|
||||
Python conventions following PEP 8 and modern best practices.
|
||||
|
||||
## PEP 8 Fundamentals
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
```python
|
||||
# Variables and functions: snake_case
|
||||
user_name = "John"
|
||||
def calculate_total(items):
|
||||
pass
|
||||
|
||||
# Constants: SCREAMING_SNAKE_CASE
|
||||
MAX_CONNECTIONS = 100
|
||||
DEFAULT_TIMEOUT = 30
|
||||
|
||||
# Classes: PascalCase
|
||||
class UserAccount:
|
||||
pass
|
||||
|
||||
# Private: single underscore prefix
|
||||
class User:
|
||||
def __init__(self):
|
||||
self._internal_state = {}
|
||||
|
||||
# Name mangling: double underscore prefix
|
||||
class Base:
|
||||
def __init__(self):
|
||||
self.__private = "truly private"
|
||||
|
||||
# Module-level "private": single underscore
|
||||
_module_cache = {}
|
||||
```
|
||||
|
||||
### Indentation and Line Length
|
||||
|
||||
```python
|
||||
# 4 spaces per indentation level
|
||||
def function():
|
||||
if condition:
|
||||
do_something()
|
||||
|
||||
# Line length: 88 characters (Black) or 79 (PEP 8)
|
||||
# Break long lines appropriately
|
||||
result = some_function(
|
||||
argument_one,
|
||||
argument_two,
|
||||
argument_three,
|
||||
)
|
||||
|
||||
# Implicit line continuation in brackets
|
||||
users = [
|
||||
"alice",
|
||||
"bob",
|
||||
"charlie",
|
||||
]
|
||||
```
|
||||
|
||||
### Imports
|
||||
|
||||
```python
|
||||
# Standard library
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
|
||||
# Third-party
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Local application
|
||||
from myapp.models import User
|
||||
from myapp.utils import format_date
|
||||
|
||||
# Avoid wildcard imports
|
||||
# Bad: from module import *
|
||||
# Good: from module import specific_item
|
||||
```
|
||||
|
||||
## Type Hints
|
||||
|
||||
### Basic Type Annotations
|
||||
|
||||
```python
|
||||
from typing import Optional, List, Dict, Tuple, Union, Any
|
||||
|
||||
# Variables
|
||||
name: str = "John"
|
||||
age: int = 30
|
||||
active: bool = True
|
||||
scores: List[int] = [90, 85, 92]
|
||||
|
||||
# Functions
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
def find_user(user_id: int) -> Optional[User]:
|
||||
"""Returns User or None if not found."""
|
||||
pass
|
||||
|
||||
def process_items(items: List[str]) -> Dict[str, int]:
|
||||
"""Returns count of each item."""
|
||||
pass
|
||||
```
|
||||
|
||||
### Advanced Type Hints
|
||||
|
||||
```python
|
||||
from typing import (
|
||||
TypeVar, Generic, Protocol, Callable,
|
||||
Literal, TypedDict, Final
|
||||
)
|
||||
|
||||
# TypeVar for generics
|
||||
T = TypeVar('T')
|
||||
def first(items: List[T]) -> Optional[T]:
|
||||
return items[0] if items else None
|
||||
|
||||
# Protocol for structural typing
|
||||
class Renderable(Protocol):
|
||||
def render(self) -> str: ...
|
||||
|
||||
def display(obj: Renderable) -> None:
|
||||
print(obj.render())
|
||||
|
||||
# Literal for specific values
|
||||
Status = Literal["pending", "active", "completed"]
|
||||
|
||||
def set_status(status: Status) -> None:
|
||||
pass
|
||||
|
||||
# TypedDict for dictionary shapes
|
||||
class UserDict(TypedDict):
|
||||
id: int
|
||||
name: str
|
||||
email: Optional[str]
|
||||
|
||||
# Final for constants
|
||||
MAX_SIZE: Final = 100
|
||||
```
|
||||
|
||||
### Type Hints in Classes
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, Self
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
id: int
|
||||
name: str
|
||||
email: str
|
||||
active: bool = True
|
||||
|
||||
# Class variable
|
||||
_instances: ClassVar[Dict[int, 'User']] = {}
|
||||
|
||||
def deactivate(self) -> Self:
|
||||
self.active = False
|
||||
return self
|
||||
|
||||
class Builder:
|
||||
def __init__(self) -> None:
|
||||
self._value: str = ""
|
||||
|
||||
def append(self, text: str) -> Self:
|
||||
self._value += text
|
||||
return self
|
||||
```
|
||||
|
||||
## Docstrings
|
||||
|
||||
### Function Docstrings
|
||||
|
||||
```python
|
||||
def calculate_discount(
|
||||
price: float,
|
||||
discount_percent: float,
|
||||
min_price: float = 0.0
|
||||
) -> float:
|
||||
"""Calculate the discounted price.
|
||||
|
||||
Args:
|
||||
price: Original price of the item.
|
||||
discount_percent: Discount percentage (0-100).
|
||||
min_price: Minimum price floor. Defaults to 0.0.
|
||||
|
||||
Returns:
|
||||
The discounted price, not less than min_price.
|
||||
|
||||
Raises:
|
||||
ValueError: If discount_percent is not between 0 and 100.
|
||||
|
||||
Example:
|
||||
>>> calculate_discount(100.0, 20.0)
|
||||
80.0
|
||||
"""
|
||||
if not 0 <= discount_percent <= 100:
|
||||
raise ValueError("Discount must be between 0 and 100")
|
||||
|
||||
discounted = price * (1 - discount_percent / 100)
|
||||
return max(discounted, min_price)
|
||||
```
|
||||
|
||||
### Class Docstrings
|
||||
|
||||
```python
|
||||
class UserService:
|
||||
"""Service for managing user operations.
|
||||
|
||||
This service handles user CRUD operations and authentication.
|
||||
It requires a database connection and optional cache.
|
||||
|
||||
Attributes:
|
||||
db: Database connection instance.
|
||||
cache: Optional cache for user lookups.
|
||||
|
||||
Example:
|
||||
>>> service = UserService(db_connection)
|
||||
>>> user = service.get_user(123)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: DatabaseConnection,
|
||||
cache: Optional[Cache] = None
|
||||
) -> None:
|
||||
"""Initialize the UserService.
|
||||
|
||||
Args:
|
||||
db: Active database connection.
|
||||
cache: Optional cache instance for performance.
|
||||
"""
|
||||
self.db = db
|
||||
self.cache = cache
|
||||
```
|
||||
|
||||
## Virtual Environments
|
||||
|
||||
### Setup Commands
|
||||
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python -m venv .venv
|
||||
|
||||
# Activate (Unix/macOS)
|
||||
source .venv/bin/activate
|
||||
|
||||
# Activate (Windows)
|
||||
.venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Freeze dependencies
|
||||
pip freeze > requirements.txt
|
||||
```
|
||||
|
||||
### Modern Tools
|
||||
|
||||
```bash
|
||||
# Using uv (recommended)
|
||||
uv venv
|
||||
uv pip install -r requirements.txt
|
||||
|
||||
# Using poetry
|
||||
poetry init
|
||||
poetry add requests
|
||||
poetry install
|
||||
|
||||
# Using pipenv
|
||||
pipenv install
|
||||
pipenv install requests
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── .venv/ # Virtual environment (gitignored)
|
||||
├── src/
|
||||
│ └── myapp/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── utils.py
|
||||
├── tests/
|
||||
│ ├── __init__.py
|
||||
│ └── test_main.py
|
||||
├── pyproject.toml # Modern project config
|
||||
├── requirements.txt # Pinned dependencies
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### pytest Basics
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from myapp.calculator import add, divide
|
||||
|
||||
def test_add_positive_numbers():
|
||||
assert add(2, 3) == 5
|
||||
|
||||
def test_add_negative_numbers():
|
||||
assert add(-1, -1) == -2
|
||||
|
||||
def test_divide_by_zero_raises():
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
divide(10, 0)
|
||||
|
||||
# Parametrized tests
|
||||
@pytest.mark.parametrize("a,b,expected", [
|
||||
(1, 1, 2),
|
||||
(0, 0, 0),
|
||||
(-1, 1, 0),
|
||||
])
|
||||
def test_add_parametrized(a, b, expected):
|
||||
assert add(a, b) == expected
|
||||
```
|
||||
|
||||
### Fixtures
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from myapp.database import Database
|
||||
from myapp.models import User
|
||||
|
||||
@pytest.fixture
|
||||
def db():
|
||||
"""Provide a clean database for each test."""
|
||||
database = Database(":memory:")
|
||||
database.create_tables()
|
||||
yield database
|
||||
database.close()
|
||||
|
||||
@pytest.fixture
|
||||
def sample_user(db):
|
||||
"""Create a sample user in the database."""
|
||||
user = User(name="Test User", email="test@example.com")
|
||||
db.save(user)
|
||||
return user
|
||||
|
||||
def test_user_creation(db, sample_user):
|
||||
found = db.find_user(sample_user.id)
|
||||
assert found.name == "Test User"
|
||||
```
|
||||
|
||||
### Mocking
|
||||
|
||||
```python
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
import pytest
|
||||
|
||||
def test_api_client_with_mock():
|
||||
# Create mock
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {"id": 1, "name": "Test"}
|
||||
mock_response.status_code = 200
|
||||
|
||||
with patch('requests.get', return_value=mock_response) as mock_get:
|
||||
result = fetch_user(1)
|
||||
|
||||
mock_get.assert_called_once_with('/users/1')
|
||||
assert result['name'] == "Test"
|
||||
|
||||
@patch('myapp.service.external_api')
|
||||
def test_with_patch_decorator(mock_api):
|
||||
mock_api.get_data.return_value = {"status": "ok"}
|
||||
result = process_data()
|
||||
assert result["status"] == "ok"
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exception Patterns
|
||||
|
||||
```python
|
||||
# Define custom exceptions
|
||||
class AppError(Exception):
|
||||
"""Base exception for application errors."""
|
||||
pass
|
||||
|
||||
class ValidationError(AppError):
|
||||
"""Raised when validation fails."""
|
||||
def __init__(self, field: str, message: str):
|
||||
self.field = field
|
||||
self.message = message
|
||||
super().__init__(f"{field}: {message}")
|
||||
|
||||
class NotFoundError(AppError):
|
||||
"""Raised when a resource is not found."""
|
||||
def __init__(self, resource: str, identifier: Any):
|
||||
self.resource = resource
|
||||
self.identifier = identifier
|
||||
super().__init__(f"{resource} '{identifier}' not found")
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
```python
|
||||
def get_user(user_id: int) -> User:
|
||||
try:
|
||||
user = db.find_user(user_id)
|
||||
if user is None:
|
||||
raise NotFoundError("User", user_id)
|
||||
return user
|
||||
except DatabaseError as e:
|
||||
logger.error(f"Database error: {e}")
|
||||
raise AppError("Unable to fetch user") from e
|
||||
|
||||
# Context managers for cleanup
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def database_transaction(db):
|
||||
try:
|
||||
yield db
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Dataclasses
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
id: int
|
||||
name: str
|
||||
email: str
|
||||
active: bool = True
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
tags: List[str] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
self.email = self.email.lower()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Point:
|
||||
"""Immutable point."""
|
||||
x: float
|
||||
y: float
|
||||
|
||||
def distance_to(self, other: 'Point') -> float:
|
||||
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
|
||||
```
|
||||
|
||||
### Context Managers
|
||||
|
||||
```python
|
||||
from contextlib import contextmanager
|
||||
from typing import Generator
|
||||
|
||||
@contextmanager
|
||||
def timer(name: str) -> Generator[None, None, None]:
|
||||
"""Time a block of code."""
|
||||
import time
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
elapsed = time.perf_counter() - start
|
||||
print(f"{name}: {elapsed:.3f}s")
|
||||
|
||||
# Usage
|
||||
with timer("data processing"):
|
||||
process_large_dataset()
|
||||
|
||||
# Class-based context manager
|
||||
class DatabaseConnection:
|
||||
def __init__(self, connection_string: str):
|
||||
self.connection_string = connection_string
|
||||
self.connection = None
|
||||
|
||||
def __enter__(self):
|
||||
self.connection = connect(self.connection_string)
|
||||
return self.connection
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
return False # Don't suppress exceptions
|
||||
```
|
||||
|
||||
### Decorators
|
||||
|
||||
```python
|
||||
from functools import wraps
|
||||
from typing import Callable, TypeVar, ParamSpec
|
||||
import time
|
||||
|
||||
P = ParamSpec('P')
|
||||
R = TypeVar('R')
|
||||
|
||||
def retry(max_attempts: int = 3, delay: float = 1.0):
|
||||
"""Retry decorator with exponential backoff."""
|
||||
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(func)
|
||||
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
last_exception = None
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
last_exception = e
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(delay * (2 ** attempt))
|
||||
raise last_exception
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@retry(max_attempts=3, delay=0.5)
|
||||
def fetch_data(url: str) -> dict:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
```
|
||||
|
||||
## Code Quality Tools
|
||||
|
||||
### Ruff Configuration
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # Pyflakes
|
||||
"I", # isort
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
ignore = ["E501"] # Line too long (handled by formatter)
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["myapp"]
|
||||
```
|
||||
|
||||
### Type Checking with mypy
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
strict = true
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
ignore_missing_imports = true
|
||||
```
|
||||
451
plugins/conductor/templates/code_styleguides/typescript.md
Normal file
451
plugins/conductor/templates/code_styleguides/typescript.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# TypeScript Style Guide
|
||||
|
||||
TypeScript-specific conventions and best practices for type-safe development.
|
||||
|
||||
## Strict Mode
|
||||
|
||||
### Enable Strict Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
- Catches errors at compile time
|
||||
- Better IDE support and autocomplete
|
||||
- Self-documenting code
|
||||
- Easier refactoring
|
||||
|
||||
## Type Safety
|
||||
|
||||
### Avoid `any`
|
||||
|
||||
```typescript
|
||||
// Bad
|
||||
function processData(data: any): any {
|
||||
return data.value;
|
||||
}
|
||||
|
||||
// Good
|
||||
interface DataItem {
|
||||
value: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
function processData(data: DataItem): string {
|
||||
return data.value;
|
||||
}
|
||||
```
|
||||
|
||||
### Use `unknown` for Unknown Types
|
||||
|
||||
```typescript
|
||||
// When type is truly unknown
|
||||
function parseJSON(json: string): unknown {
|
||||
return JSON.parse(json);
|
||||
}
|
||||
|
||||
// Then narrow with type guards
|
||||
function isUser(obj: unknown): obj is User {
|
||||
return (
|
||||
typeof obj === "object" && obj !== null && "id" in obj && "name" in obj
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Prefer Explicit Types
|
||||
|
||||
```typescript
|
||||
// Bad: Implicit any
|
||||
const items = [];
|
||||
|
||||
// Good: Explicit type
|
||||
const items: Item[] = [];
|
||||
|
||||
// Also good: Type inference when obvious
|
||||
const count = 0; // number inferred
|
||||
const name = "John"; // string inferred
|
||||
```
|
||||
|
||||
## Interfaces vs Types
|
||||
|
||||
### Use Interfaces for Object Shapes
|
||||
|
||||
```typescript
|
||||
// Preferred for objects
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
// Interfaces can be extended
|
||||
interface AdminUser extends User {
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
// Interfaces can be augmented (declaration merging)
|
||||
interface User {
|
||||
avatar?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Use Types for Unions, Primitives, and Computed Types
|
||||
|
||||
```typescript
|
||||
// Union types
|
||||
type Status = "pending" | "active" | "completed";
|
||||
|
||||
// Primitive aliases
|
||||
type UserId = string;
|
||||
|
||||
// Computed/mapped types
|
||||
type Readonly<T> = {
|
||||
readonly [P in keyof T]: T[P];
|
||||
};
|
||||
|
||||
// Tuple types
|
||||
type Coordinate = [number, number];
|
||||
```
|
||||
|
||||
### Decision Guide
|
||||
|
||||
| Use Case | Recommendation |
|
||||
| ----------------------- | -------------- |
|
||||
| Object shape | `interface` |
|
||||
| Union type | `type` |
|
||||
| Function signature | `type` |
|
||||
| Class implementation | `interface` |
|
||||
| Mapped/conditional type | `type` |
|
||||
| Library public API | `interface` |
|
||||
|
||||
## Async Patterns
|
||||
|
||||
### Prefer async/await
|
||||
|
||||
```typescript
|
||||
// Bad: Callback hell
|
||||
function fetchUserData(id: string, callback: (user: User) => void) {
|
||||
fetch(`/users/${id}`)
|
||||
.then((res) => res.json())
|
||||
.then((user) => callback(user));
|
||||
}
|
||||
|
||||
// Good: async/await
|
||||
async function fetchUserData(id: string): Promise<User> {
|
||||
const response = await fetch(`/users/${id}`);
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling in Async Code
|
||||
|
||||
```typescript
|
||||
// Explicit error handling
|
||||
async function fetchUser(id: string): Promise<User> {
|
||||
try {
|
||||
const response = await fetch(`/users/${id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(`Failed to fetch user: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) {
|
||||
throw error;
|
||||
}
|
||||
throw new NetworkError("Network request failed", { cause: error });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Promise Types
|
||||
|
||||
```typescript
|
||||
// Return type annotation for clarity
|
||||
async function loadData(): Promise<Data[]> {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Use Promise.all for parallel operations
|
||||
async function loadAllData(): Promise<[Users, Posts]> {
|
||||
return Promise.all([fetchUsers(), fetchPosts()]);
|
||||
}
|
||||
```
|
||||
|
||||
## Module Structure
|
||||
|
||||
### File Organization
|
||||
|
||||
```
|
||||
src/
|
||||
├── types/ # Shared type definitions
|
||||
│ ├── user.ts
|
||||
│ └── api.ts
|
||||
├── utils/ # Pure utility functions
|
||||
│ ├── validation.ts
|
||||
│ └── formatting.ts
|
||||
├── services/ # Business logic
|
||||
│ ├── userService.ts
|
||||
│ └── authService.ts
|
||||
├── components/ # UI components (if applicable)
|
||||
└── index.ts # Public API exports
|
||||
```
|
||||
|
||||
### Export Patterns
|
||||
|
||||
```typescript
|
||||
// Named exports (preferred)
|
||||
export interface User { ... }
|
||||
export function createUser(data: UserInput): User { ... }
|
||||
export const DEFAULT_USER: User = { ... };
|
||||
|
||||
// Re-exports for public API
|
||||
// index.ts
|
||||
export { User, createUser } from './user';
|
||||
export { type Config } from './config';
|
||||
|
||||
// Avoid default exports (harder to refactor)
|
||||
// Bad
|
||||
export default class UserService { ... }
|
||||
|
||||
// Good
|
||||
export class UserService { ... }
|
||||
```
|
||||
|
||||
### Import Organization
|
||||
|
||||
```typescript
|
||||
// 1. External dependencies
|
||||
import { useState, useEffect } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
// 2. Internal absolute imports
|
||||
import { ApiClient } from "@/services/api";
|
||||
import { User } from "@/types";
|
||||
|
||||
// 3. Relative imports
|
||||
import { formatDate } from "./utils";
|
||||
import { UserCard } from "./UserCard";
|
||||
```
|
||||
|
||||
## Utility Types
|
||||
|
||||
### Built-in Utility Types
|
||||
|
||||
```typescript
|
||||
// Partial - all properties optional
|
||||
type UpdateUser = Partial<User>;
|
||||
|
||||
// Required - all properties required
|
||||
type CompleteUser = Required<User>;
|
||||
|
||||
// Pick - select properties
|
||||
type UserPreview = Pick<User, "id" | "name">;
|
||||
|
||||
// Omit - exclude properties
|
||||
type UserWithoutPassword = Omit<User, "password">;
|
||||
|
||||
// Record - dictionary type
|
||||
type UserRoles = Record<string, Role>;
|
||||
|
||||
// ReturnType - extract return type
|
||||
type ApiResponse = ReturnType<typeof fetchData>;
|
||||
|
||||
// Parameters - extract parameter types
|
||||
type FetchParams = Parameters<typeof fetch>;
|
||||
```
|
||||
|
||||
### Custom Utility Types
|
||||
|
||||
```typescript
|
||||
// Make specific properties optional
|
||||
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||
|
||||
// Make specific properties required
|
||||
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
||||
|
||||
// Deep readonly
|
||||
type DeepReadonly<T> = {
|
||||
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
|
||||
};
|
||||
```
|
||||
|
||||
## Enums and Constants
|
||||
|
||||
### Prefer const Objects Over Enums
|
||||
|
||||
```typescript
|
||||
// Enums have runtime overhead
|
||||
enum Status {
|
||||
Pending = "pending",
|
||||
Active = "active",
|
||||
}
|
||||
|
||||
// Prefer const objects
|
||||
const Status = {
|
||||
Pending: "pending",
|
||||
Active: "active",
|
||||
} as const;
|
||||
|
||||
type Status = (typeof Status)[keyof typeof Status];
|
||||
```
|
||||
|
||||
### When to Use Enums
|
||||
|
||||
```typescript
|
||||
// Numeric enums for bit flags
|
||||
enum Permissions {
|
||||
None = 0,
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
Execute = 1 << 2,
|
||||
All = Read | Write | Execute,
|
||||
}
|
||||
```
|
||||
|
||||
## Generics
|
||||
|
||||
### Basic Generic Usage
|
||||
|
||||
```typescript
|
||||
// Generic function
|
||||
function first<T>(items: T[]): T | undefined {
|
||||
return items[0];
|
||||
}
|
||||
|
||||
// Generic interface
|
||||
interface Repository<T> {
|
||||
find(id: string): Promise<T | null>;
|
||||
save(item: T): Promise<T>;
|
||||
delete(id: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### Constraining Generics
|
||||
|
||||
```typescript
|
||||
// Constrain to objects with id
|
||||
function findById<T extends { id: string }>(
|
||||
items: T[],
|
||||
id: string,
|
||||
): T | undefined {
|
||||
return items.find((item) => item.id === id);
|
||||
}
|
||||
|
||||
// Multiple constraints
|
||||
function merge<T extends object, U extends object>(a: T, b: U): T & U {
|
||||
return { ...a, ...b };
|
||||
}
|
||||
```
|
||||
|
||||
## Error Types
|
||||
|
||||
### Custom Error Classes
|
||||
|
||||
```typescript
|
||||
class AppError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string,
|
||||
public readonly statusCode: number = 500,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "AppError";
|
||||
}
|
||||
}
|
||||
|
||||
class ValidationError extends AppError {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly field: string,
|
||||
) {
|
||||
super(message, "VALIDATION_ERROR", 400);
|
||||
this.name = "ValidationError";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Type Guards for Errors
|
||||
|
||||
```typescript
|
||||
function isAppError(error: unknown): error is AppError {
|
||||
return error instanceof AppError;
|
||||
}
|
||||
|
||||
function handleError(error: unknown): void {
|
||||
if (isAppError(error)) {
|
||||
console.error(`[${error.code}] ${error.message}`);
|
||||
} else if (error instanceof Error) {
|
||||
console.error(`Unexpected error: ${error.message}`);
|
||||
} else {
|
||||
console.error("Unknown error occurred");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Types
|
||||
|
||||
### Type Testing
|
||||
|
||||
```typescript
|
||||
// Use type assertions for compile-time checks
|
||||
type Assert<T, U extends T> = U;
|
||||
|
||||
// Test that types work as expected
|
||||
type _TestUserHasId = Assert<{ id: string }, User>;
|
||||
|
||||
// Expect error (compile-time check)
|
||||
// @ts-expect-error - User should require id
|
||||
const invalidUser: User = { name: "John" };
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Builder Pattern
|
||||
|
||||
```typescript
|
||||
class QueryBuilder<T> {
|
||||
private filters: Array<(item: T) => boolean> = [];
|
||||
|
||||
where(predicate: (item: T) => boolean): this {
|
||||
this.filters.push(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
execute(items: T[]): T[] {
|
||||
return items.filter((item) => this.filters.every((filter) => filter(item)));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Result Type
|
||||
|
||||
```typescript
|
||||
type Result<T, E = Error> =
|
||||
| { success: true; data: T }
|
||||
| { success: false; error: E };
|
||||
|
||||
function divide(a: number, b: number): Result<number> {
|
||||
if (b === 0) {
|
||||
return { success: false, error: new Error("Division by zero") };
|
||||
}
|
||||
return { success: true, data: a / b };
|
||||
}
|
||||
```
|
||||
90
plugins/conductor/templates/index.md
Normal file
90
plugins/conductor/templates/index.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Conductor Hub
|
||||
|
||||
## Project: {{PROJECT_NAME}}
|
||||
|
||||
Central navigation for all Conductor artifacts and development tracks.
|
||||
|
||||
## Quick Links
|
||||
|
||||
### Core Documents
|
||||
|
||||
| Document | Description | Status |
|
||||
| --------------------------------------------- | -------------------------- | ---------- |
|
||||
| [Product Vision](./product.md) | Product overview and goals | {{STATUS}} |
|
||||
| [Product Guidelines](./product-guidelines.md) | Voice, tone, and standards | {{STATUS}} |
|
||||
| [Tech Stack](./tech-stack.md) | Technology decisions | {{STATUS}} |
|
||||
| [Workflow](./workflow.md) | Development process | {{STATUS}} |
|
||||
|
||||
### Track Management
|
||||
|
||||
| Document | Description |
|
||||
| ------------------------------- | ---------------------- |
|
||||
| [Track Registry](./tracks.md) | All development tracks |
|
||||
| [Active Tracks](#active-tracks) | Currently in progress |
|
||||
|
||||
### Style Guides
|
||||
|
||||
| Guide | Language/Domain |
|
||||
| ---------------------------------------------- | ------------------------- |
|
||||
| [General](./code_styleguides/general.md) | Universal principles |
|
||||
| [TypeScript](./code_styleguides/typescript.md) | TypeScript conventions |
|
||||
| [JavaScript](./code_styleguides/javascript.md) | JavaScript best practices |
|
||||
| [Python](./code_styleguides/python.md) | Python standards |
|
||||
| [Go](./code_styleguides/go.md) | Go idioms |
|
||||
| [C#](./code_styleguides/csharp.md) | C# conventions |
|
||||
| [Dart](./code_styleguides/dart.md) | Dart/Flutter patterns |
|
||||
| [HTML/CSS](./code_styleguides/html-css.md) | Web standards |
|
||||
|
||||
## Active Tracks
|
||||
|
||||
| Track | Status | Priority | Spec | Plan |
|
||||
| -------------- | ---------- | ------------ | ------------------------------------- | ------------------------------------- |
|
||||
| {{TRACK_NAME}} | {{STATUS}} | {{PRIORITY}} | [spec](./tracks/{{TRACK_ID}}/spec.md) | [plan](./tracks/{{TRACK_ID}}/plan.md) |
|
||||
|
||||
## Recent Activity
|
||||
|
||||
| Date | Track | Action |
|
||||
| -------- | --------- | ---------- |
|
||||
| {{DATE}} | {{TRACK}} | {{ACTION}} |
|
||||
|
||||
## Project Status
|
||||
|
||||
**Current Phase:** {{CURRENT_PHASE}}
|
||||
**Overall Progress:** {{PROGRESS_PERCENTAGE}}%
|
||||
|
||||
### Milestone Tracker
|
||||
|
||||
| Milestone | Target Date | Status |
|
||||
| --------------- | ----------- | ------------ |
|
||||
| {{MILESTONE_1}} | {{DATE_1}} | {{STATUS_1}} |
|
||||
| {{MILESTONE_2}} | {{DATE_2}} | {{STATUS_2}} |
|
||||
| {{MILESTONE_3}} | {{DATE_3}} | {{STATUS_3}} |
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Review [Product Vision](./product.md) for project context
|
||||
2. Check [Tech Stack](./tech-stack.md) for technology decisions
|
||||
3. Read [Workflow](./workflow.md) for development process
|
||||
4. Find your track in [Track Registry](./tracks.md)
|
||||
5. Follow track spec and plan
|
||||
|
||||
## Commands Reference
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
{{SETUP_COMMAND}}
|
||||
|
||||
# Development
|
||||
{{DEV_COMMAND}}
|
||||
|
||||
# Testing
|
||||
{{TEST_COMMAND}}
|
||||
|
||||
# Build
|
||||
{{BUILD_COMMAND}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** {{LAST_UPDATED}}
|
||||
**Maintained By:** {{MAINTAINER}}
|
||||
196
plugins/conductor/templates/product-guidelines.md
Normal file
196
plugins/conductor/templates/product-guidelines.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# Product Guidelines
|
||||
|
||||
## Voice & Tone
|
||||
|
||||
### Brand Voice
|
||||
|
||||
{{BRAND_VOICE_DESCRIPTION}}
|
||||
|
||||
### Voice Attributes
|
||||
|
||||
- **{{ATTRIBUTE_1}}:** {{ATTRIBUTE_1_DESCRIPTION}}
|
||||
- **{{ATTRIBUTE_2}}:** {{ATTRIBUTE_2_DESCRIPTION}}
|
||||
- **{{ATTRIBUTE_3}}:** {{ATTRIBUTE_3_DESCRIPTION}}
|
||||
|
||||
### Tone Variations by Context
|
||||
|
||||
| Context | Tone | Example |
|
||||
| -------------- | -------------------- | ----------------------- |
|
||||
| Success states | {{SUCCESS_TONE}} | {{SUCCESS_EXAMPLE}} |
|
||||
| Error states | {{ERROR_TONE}} | {{ERROR_EXAMPLE}} |
|
||||
| Onboarding | {{ONBOARDING_TONE}} | {{ONBOARDING_EXAMPLE}} |
|
||||
| Empty states | {{EMPTY_STATE_TONE}} | {{EMPTY_STATE_EXAMPLE}} |
|
||||
|
||||
### Words We Use
|
||||
|
||||
- {{PREFERRED_WORD_1}}
|
||||
- {{PREFERRED_WORD_2}}
|
||||
- {{PREFERRED_WORD_3}}
|
||||
|
||||
### Words We Avoid
|
||||
|
||||
- {{AVOIDED_WORD_1}}
|
||||
- {{AVOIDED_WORD_2}}
|
||||
- {{AVOIDED_WORD_3}}
|
||||
|
||||
## Messaging Guidelines
|
||||
|
||||
### Core Messages
|
||||
|
||||
**Primary Message:**
|
||||
|
||||
> {{PRIMARY_MESSAGE}}
|
||||
|
||||
**Supporting Messages:**
|
||||
|
||||
1. {{SUPPORTING_MESSAGE_1}}
|
||||
2. {{SUPPORTING_MESSAGE_2}}
|
||||
3. {{SUPPORTING_MESSAGE_3}}
|
||||
|
||||
### Message Hierarchy
|
||||
|
||||
1. **Must Communicate:** {{MUST_COMMUNICATE}}
|
||||
2. **Should Communicate:** {{SHOULD_COMMUNICATE}}
|
||||
3. **Could Communicate:** {{COULD_COMMUNICATE}}
|
||||
|
||||
### Audience-Specific Messaging
|
||||
|
||||
| Audience | Key Message | Proof Points |
|
||||
| -------------- | ------------- | ------------ |
|
||||
| {{AUDIENCE_1}} | {{MESSAGE_1}} | {{PROOF_1}} |
|
||||
| {{AUDIENCE_2}} | {{MESSAGE_2}} | {{PROOF_2}} |
|
||||
|
||||
## Design Principles
|
||||
|
||||
### Principle 1: {{PRINCIPLE_1_NAME}}
|
||||
|
||||
{{PRINCIPLE_1_DESCRIPTION}}
|
||||
|
||||
**Do:**
|
||||
|
||||
- {{PRINCIPLE_1_DO_1}}
|
||||
- {{PRINCIPLE_1_DO_2}}
|
||||
|
||||
**Don't:**
|
||||
|
||||
- {{PRINCIPLE_1_DONT_1}}
|
||||
- {{PRINCIPLE_1_DONT_2}}
|
||||
|
||||
### Principle 2: {{PRINCIPLE_2_NAME}}
|
||||
|
||||
{{PRINCIPLE_2_DESCRIPTION}}
|
||||
|
||||
**Do:**
|
||||
|
||||
- {{PRINCIPLE_2_DO_1}}
|
||||
- {{PRINCIPLE_2_DO_2}}
|
||||
|
||||
**Don't:**
|
||||
|
||||
- {{PRINCIPLE_2_DONT_1}}
|
||||
- {{PRINCIPLE_2_DONT_2}}
|
||||
|
||||
### Principle 3: {{PRINCIPLE_3_NAME}}
|
||||
|
||||
{{PRINCIPLE_3_DESCRIPTION}}
|
||||
|
||||
**Do:**
|
||||
|
||||
- {{PRINCIPLE_3_DO_1}}
|
||||
- {{PRINCIPLE_3_DO_2}}
|
||||
|
||||
**Don't:**
|
||||
|
||||
- {{PRINCIPLE_3_DONT_1}}
|
||||
- {{PRINCIPLE_3_DONT_2}}
|
||||
|
||||
## Accessibility Standards
|
||||
|
||||
### Compliance Target
|
||||
|
||||
{{ACCESSIBILITY_STANDARD}} (e.g., WCAG 2.1 AA)
|
||||
|
||||
### Core Requirements
|
||||
|
||||
#### Perceivable
|
||||
|
||||
- All images have meaningful alt text
|
||||
- Color is not the only means of conveying information
|
||||
- Text has minimum contrast ratio of 4.5:1
|
||||
- Content is readable at 200% zoom
|
||||
|
||||
#### Operable
|
||||
|
||||
- All functionality available via keyboard
|
||||
- No content flashes more than 3 times per second
|
||||
- Skip navigation links provided
|
||||
- Focus indicators clearly visible
|
||||
|
||||
#### Understandable
|
||||
|
||||
- Language is clear and simple
|
||||
- Navigation is consistent
|
||||
- Error messages are descriptive and helpful
|
||||
- Labels and instructions are clear
|
||||
|
||||
#### Robust
|
||||
|
||||
- Valid HTML markup
|
||||
- ARIA labels used appropriately
|
||||
- Compatible with assistive technologies
|
||||
- Progressive enhancement approach
|
||||
|
||||
### Testing Requirements
|
||||
|
||||
- Screen reader testing with {{SCREEN_READER}}
|
||||
- Keyboard-only navigation testing
|
||||
- Color contrast verification
|
||||
- Automated accessibility scans
|
||||
|
||||
## Error Handling Philosophy
|
||||
|
||||
### Error Prevention
|
||||
|
||||
- Validate input early and often
|
||||
- Provide clear constraints and requirements upfront
|
||||
- Use inline validation where appropriate
|
||||
- Confirm destructive actions
|
||||
|
||||
### Error Communication
|
||||
|
||||
#### Principles
|
||||
|
||||
1. **Be specific:** Tell users exactly what went wrong
|
||||
2. **Be helpful:** Explain how to fix the problem
|
||||
3. **Be human:** Use friendly, non-technical language
|
||||
4. **Be timely:** Show errors as soon as they're detected
|
||||
|
||||
#### Error Message Structure
|
||||
|
||||
```
|
||||
[What happened] + [Why it happened (if relevant)] + [How to fix it]
|
||||
```
|
||||
|
||||
#### Examples
|
||||
|
||||
| Bad | Good |
|
||||
| --------------- | ---------------------------------------------------- |
|
||||
| "Invalid input" | "Email address must include @ symbol" |
|
||||
| "Error 500" | "We couldn't save your changes. Please try again." |
|
||||
| "Failed" | "Unable to connect. Check your internet connection." |
|
||||
|
||||
### Error States
|
||||
|
||||
| Severity | Visual Treatment | User Action Required |
|
||||
| -------- | ---------------------- | -------------------- |
|
||||
| Info | {{INFO_TREATMENT}} | Optional |
|
||||
| Warning | {{WARNING_TREATMENT}} | Recommended |
|
||||
| Error | {{ERROR_TREATMENT}} | Required |
|
||||
| Critical | {{CRITICAL_TREATMENT}} | Immediate |
|
||||
|
||||
### Recovery Patterns
|
||||
|
||||
- Auto-save user progress where possible
|
||||
- Provide clear "try again" actions
|
||||
- Offer alternative paths when primary fails
|
||||
- Preserve user input on errors
|
||||
102
plugins/conductor/templates/product.md
Normal file
102
plugins/conductor/templates/product.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Product Vision
|
||||
|
||||
## Product Overview
|
||||
|
||||
**Name:** {{PRODUCT_NAME}}
|
||||
|
||||
**Tagline:** {{ONE_LINE_DESCRIPTION}}
|
||||
|
||||
**Description:**
|
||||
{{DETAILED_DESCRIPTION}}
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### The Problem
|
||||
|
||||
{{PROBLEM_DESCRIPTION}}
|
||||
|
||||
### Current Solutions
|
||||
|
||||
{{EXISTING_SOLUTIONS}}
|
||||
|
||||
### Why They Fall Short
|
||||
|
||||
{{SOLUTION_GAPS}}
|
||||
|
||||
## Target Users
|
||||
|
||||
### Primary Users
|
||||
|
||||
{{PRIMARY_USER_PERSONA}}
|
||||
|
||||
- **Who:** {{USER_DESCRIPTION}}
|
||||
- **Goals:** {{USER_GOALS}}
|
||||
- **Pain Points:** {{USER_PAIN_POINTS}}
|
||||
- **Technical Proficiency:** {{TECHNICAL_LEVEL}}
|
||||
|
||||
### Secondary Users
|
||||
|
||||
{{SECONDARY_USER_PERSONA}}
|
||||
|
||||
- **Who:** {{USER_DESCRIPTION}}
|
||||
- **Goals:** {{USER_GOALS}}
|
||||
- **Relationship to Primary:** {{RELATIONSHIP}}
|
||||
|
||||
## Core Value Proposition
|
||||
|
||||
### Key Benefits
|
||||
|
||||
1. {{BENEFIT_1}}
|
||||
2. {{BENEFIT_2}}
|
||||
3. {{BENEFIT_3}}
|
||||
|
||||
### Differentiators
|
||||
|
||||
- {{DIFFERENTIATOR_1}}
|
||||
- {{DIFFERENTIATOR_2}}
|
||||
|
||||
### Value Statement
|
||||
|
||||
> {{VALUE_STATEMENT}}
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Key Performance Indicators
|
||||
|
||||
| Metric | Target | Measurement Method |
|
||||
| ------------ | ------------ | ------------------ |
|
||||
| {{METRIC_1}} | {{TARGET_1}} | {{METHOD_1}} |
|
||||
| {{METRIC_2}} | {{TARGET_2}} | {{METHOD_2}} |
|
||||
| {{METRIC_3}} | {{TARGET_3}} | {{METHOD_3}} |
|
||||
|
||||
### North Star Metric
|
||||
|
||||
{{NORTH_STAR_METRIC}}
|
||||
|
||||
### Leading Indicators
|
||||
|
||||
- {{LEADING_INDICATOR_1}}
|
||||
- {{LEADING_INDICATOR_2}}
|
||||
|
||||
### Lagging Indicators
|
||||
|
||||
- {{LAGGING_INDICATOR_1}}
|
||||
- {{LAGGING_INDICATOR_2}}
|
||||
|
||||
## Out of Scope
|
||||
|
||||
### Explicitly Not Included
|
||||
|
||||
- {{OUT_OF_SCOPE_1}}
|
||||
- {{OUT_OF_SCOPE_2}}
|
||||
- {{OUT_OF_SCOPE_3}}
|
||||
|
||||
### Future Considerations
|
||||
|
||||
- {{FUTURE_CONSIDERATION_1}}
|
||||
- {{FUTURE_CONSIDERATION_2}}
|
||||
|
||||
### Non-Goals
|
||||
|
||||
- {{NON_GOAL_1}}
|
||||
- {{NON_GOAL_2}}
|
||||
204
plugins/conductor/templates/tech-stack.md
Normal file
204
plugins/conductor/templates/tech-stack.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Technology Stack
|
||||
|
||||
## Frontend
|
||||
|
||||
### Framework
|
||||
|
||||
**Choice:** {{FRONTEND_FRAMEWORK}}
|
||||
**Version:** {{FRONTEND_VERSION}}
|
||||
|
||||
**Rationale:**
|
||||
{{FRONTEND_RATIONALE}}
|
||||
|
||||
### State Management
|
||||
|
||||
**Choice:** {{STATE_MANAGEMENT}}
|
||||
**Version:** {{STATE_VERSION}}
|
||||
|
||||
**Rationale:**
|
||||
{{STATE_RATIONALE}}
|
||||
|
||||
### Styling
|
||||
|
||||
**Choice:** {{STYLING_SOLUTION}}
|
||||
**Version:** {{STYLING_VERSION}}
|
||||
|
||||
**Rationale:**
|
||||
{{STYLING_RATIONALE}}
|
||||
|
||||
### Additional Frontend Libraries
|
||||
|
||||
| Library | Purpose | Version |
|
||||
| ------------ | -------------------- | -------------------- |
|
||||
| {{FE_LIB_1}} | {{FE_LIB_1_PURPOSE}} | {{FE_LIB_1_VERSION}} |
|
||||
| {{FE_LIB_2}} | {{FE_LIB_2_PURPOSE}} | {{FE_LIB_2_VERSION}} |
|
||||
| {{FE_LIB_3}} | {{FE_LIB_3_PURPOSE}} | {{FE_LIB_3_VERSION}} |
|
||||
|
||||
## Backend
|
||||
|
||||
### Language
|
||||
|
||||
**Choice:** {{BACKEND_LANGUAGE}}
|
||||
**Version:** {{BACKEND_LANGUAGE_VERSION}}
|
||||
|
||||
**Rationale:**
|
||||
{{BACKEND_LANGUAGE_RATIONALE}}
|
||||
|
||||
### Framework
|
||||
|
||||
**Choice:** {{BACKEND_FRAMEWORK}}
|
||||
**Version:** {{BACKEND_FRAMEWORK_VERSION}}
|
||||
|
||||
**Rationale:**
|
||||
{{BACKEND_FRAMEWORK_RATIONALE}}
|
||||
|
||||
### Database
|
||||
|
||||
#### Primary Database
|
||||
|
||||
**Choice:** {{PRIMARY_DATABASE}}
|
||||
**Version:** {{PRIMARY_DB_VERSION}}
|
||||
|
||||
**Rationale:**
|
||||
{{PRIMARY_DB_RATIONALE}}
|
||||
|
||||
#### Secondary Database (if applicable)
|
||||
|
||||
**Choice:** {{SECONDARY_DATABASE}}
|
||||
**Purpose:** {{SECONDARY_DB_PURPOSE}}
|
||||
|
||||
### Additional Backend Libraries
|
||||
|
||||
| Library | Purpose | Version |
|
||||
| ------------ | -------------------- | -------------------- |
|
||||
| {{BE_LIB_1}} | {{BE_LIB_1_PURPOSE}} | {{BE_LIB_1_VERSION}} |
|
||||
| {{BE_LIB_2}} | {{BE_LIB_2_PURPOSE}} | {{BE_LIB_2_VERSION}} |
|
||||
| {{BE_LIB_3}} | {{BE_LIB_3_PURPOSE}} | {{BE_LIB_3_VERSION}} |
|
||||
|
||||
## Infrastructure
|
||||
|
||||
### Hosting
|
||||
|
||||
**Provider:** {{HOSTING_PROVIDER}}
|
||||
**Environment:** {{HOSTING_ENVIRONMENT}}
|
||||
|
||||
**Services Used:**
|
||||
|
||||
- {{HOSTING_SERVICE_1}}
|
||||
- {{HOSTING_SERVICE_2}}
|
||||
- {{HOSTING_SERVICE_3}}
|
||||
|
||||
### CI/CD
|
||||
|
||||
**Platform:** {{CICD_PLATFORM}}
|
||||
|
||||
**Pipeline Stages:**
|
||||
|
||||
1. {{PIPELINE_STAGE_1}}
|
||||
2. {{PIPELINE_STAGE_2}}
|
||||
3. {{PIPELINE_STAGE_3}}
|
||||
4. {{PIPELINE_STAGE_4}}
|
||||
|
||||
### Monitoring
|
||||
|
||||
**APM:** {{APM_TOOL}}
|
||||
**Logging:** {{LOGGING_TOOL}}
|
||||
**Alerting:** {{ALERTING_TOOL}}
|
||||
|
||||
### Additional Infrastructure
|
||||
|
||||
| Service | Purpose | Provider |
|
||||
| ----------- | ------------------- | -------------------- |
|
||||
| {{INFRA_1}} | {{INFRA_1_PURPOSE}} | {{INFRA_1_PROVIDER}} |
|
||||
| {{INFRA_2}} | {{INFRA_2_PURPOSE}} | {{INFRA_2_PROVIDER}} |
|
||||
|
||||
## Development Tools
|
||||
|
||||
### Package Manager
|
||||
|
||||
**Choice:** {{PACKAGE_MANAGER}}
|
||||
**Version:** {{PACKAGE_MANAGER_VERSION}}
|
||||
|
||||
### Testing
|
||||
|
||||
| Type | Tool | Coverage Target |
|
||||
| ----------- | ------------------------- | ------------------------- |
|
||||
| Unit | {{UNIT_TEST_TOOL}} | {{UNIT_COVERAGE}}% |
|
||||
| Integration | {{INTEGRATION_TEST_TOOL}} | {{INTEGRATION_COVERAGE}}% |
|
||||
| E2E | {{E2E_TEST_TOOL}} | Critical paths |
|
||||
|
||||
### Linting & Formatting
|
||||
|
||||
**Linter:** {{LINTER}}
|
||||
**Formatter:** {{FORMATTER}}
|
||||
**Config:** {{LINT_CONFIG}}
|
||||
|
||||
### Additional Dev Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
| -------------- | ---------------------- |
|
||||
| {{DEV_TOOL_1}} | {{DEV_TOOL_1_PURPOSE}} |
|
||||
| {{DEV_TOOL_2}} | {{DEV_TOOL_2_PURPOSE}} |
|
||||
| {{DEV_TOOL_3}} | {{DEV_TOOL_3_PURPOSE}} |
|
||||
|
||||
## Decision Log
|
||||
|
||||
### {{DECISION_1_TITLE}}
|
||||
|
||||
**Date:** {{DECISION_1_DATE}}
|
||||
**Status:** {{DECISION_1_STATUS}}
|
||||
|
||||
**Context:**
|
||||
{{DECISION_1_CONTEXT}}
|
||||
|
||||
**Decision:**
|
||||
{{DECISION_1_DECISION}}
|
||||
|
||||
**Consequences:**
|
||||
|
||||
- {{DECISION_1_CONSEQUENCE_1}}
|
||||
- {{DECISION_1_CONSEQUENCE_2}}
|
||||
|
||||
---
|
||||
|
||||
### {{DECISION_2_TITLE}}
|
||||
|
||||
**Date:** {{DECISION_2_DATE}}
|
||||
**Status:** {{DECISION_2_STATUS}}
|
||||
|
||||
**Context:**
|
||||
{{DECISION_2_CONTEXT}}
|
||||
|
||||
**Decision:**
|
||||
{{DECISION_2_DECISION}}
|
||||
|
||||
**Consequences:**
|
||||
|
||||
- {{DECISION_2_CONSEQUENCE_1}}
|
||||
- {{DECISION_2_CONSEQUENCE_2}}
|
||||
|
||||
---
|
||||
|
||||
### {{DECISION_3_TITLE}}
|
||||
|
||||
**Date:** {{DECISION_3_DATE}}
|
||||
**Status:** {{DECISION_3_STATUS}}
|
||||
|
||||
**Context:**
|
||||
{{DECISION_3_CONTEXT}}
|
||||
|
||||
**Decision:**
|
||||
{{DECISION_3_DECISION}}
|
||||
|
||||
**Consequences:**
|
||||
|
||||
- {{DECISION_3_CONSEQUENCE_1}}
|
||||
- {{DECISION_3_CONSEQUENCE_2}}
|
||||
|
||||
## Version Compatibility Matrix
|
||||
|
||||
| Component | Min Version | Max Version | Notes |
|
||||
| --------------- | ----------- | ----------- | ----------- |
|
||||
| {{COMPONENT_1}} | {{MIN_1}} | {{MAX_1}} | {{NOTES_1}} |
|
||||
| {{COMPONENT_2}} | {{MIN_2}} | {{MAX_2}} | {{NOTES_2}} |
|
||||
| {{COMPONENT_3}} | {{MIN_3}} | {{MAX_3}} | {{NOTES_3}} |
|
||||
10
plugins/conductor/templates/track-metadata.json
Normal file
10
plugins/conductor/templates/track-metadata.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "",
|
||||
"type": "feature|bug|chore|refactor",
|
||||
"status": "pending|in_progress|completed",
|
||||
"created_at": "",
|
||||
"updated_at": "",
|
||||
"description": "",
|
||||
"spec_path": "",
|
||||
"plan_path": ""
|
||||
}
|
||||
198
plugins/conductor/templates/track-plan.md
Normal file
198
plugins/conductor/templates/track-plan.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Implementation Plan: {{TRACK_NAME}}
|
||||
|
||||
## Overview
|
||||
|
||||
**Track ID:** {{TRACK_ID}}
|
||||
**Spec:** [spec.md](./spec.md)
|
||||
**Estimated Effort:** {{EFFORT_ESTIMATE}}
|
||||
**Target Completion:** {{TARGET_DATE}}
|
||||
|
||||
## Progress Summary
|
||||
|
||||
| Phase | Status | Progress |
|
||||
| ------------------------- | ---------- | ------------- |
|
||||
| Phase 1: {{PHASE_1_NAME}} | {{STATUS}} | {{PROGRESS}}% |
|
||||
| Phase 2: {{PHASE_2_NAME}} | {{STATUS}} | {{PROGRESS}}% |
|
||||
| Phase 3: {{PHASE_3_NAME}} | {{STATUS}} | {{PROGRESS}}% |
|
||||
| Phase 4: {{PHASE_4_NAME}} | {{STATUS}} | {{PROGRESS}}% |
|
||||
|
||||
## Phase 1: {{PHASE_1_NAME}}
|
||||
|
||||
**Objective:** {{PHASE_1_OBJECTIVE}}
|
||||
**Estimated Duration:** {{PHASE_1_DURATION}}
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **1.1 {{TASK_1_1_TITLE}}**
|
||||
- [ ] {{SUBTASK_1_1_1}}
|
||||
- [ ] {{SUBTASK_1_1_2}}
|
||||
- [ ] {{SUBTASK_1_1_3}}
|
||||
|
||||
- [ ] **1.2 {{TASK_1_2_TITLE}}**
|
||||
- [ ] {{SUBTASK_1_2_1}}
|
||||
- [ ] {{SUBTASK_1_2_2}}
|
||||
|
||||
- [ ] **1.3 {{TASK_1_3_TITLE}}**
|
||||
- [ ] {{SUBTASK_1_3_1}}
|
||||
- [ ] {{SUBTASK_1_3_2}}
|
||||
|
||||
### Verification
|
||||
|
||||
- [ ] All Phase 1 tests passing
|
||||
- [ ] Code coverage meets threshold
|
||||
- [ ] Code review approved
|
||||
|
||||
### Checkpoint
|
||||
|
||||
```
|
||||
Commit: [track-id] checkpoint: phase 1 complete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: {{PHASE_2_NAME}}
|
||||
|
||||
**Objective:** {{PHASE_2_OBJECTIVE}}
|
||||
**Estimated Duration:** {{PHASE_2_DURATION}}
|
||||
**Dependencies:** Phase 1 complete
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **2.1 {{TASK_2_1_TITLE}}**
|
||||
- [ ] {{SUBTASK_2_1_1}}
|
||||
- [ ] {{SUBTASK_2_1_2}}
|
||||
- [ ] {{SUBTASK_2_1_3}}
|
||||
|
||||
- [ ] **2.2 {{TASK_2_2_TITLE}}**
|
||||
- [ ] {{SUBTASK_2_2_1}}
|
||||
- [ ] {{SUBTASK_2_2_2}}
|
||||
|
||||
- [ ] **2.3 {{TASK_2_3_TITLE}}**
|
||||
- [ ] {{SUBTASK_2_3_1}}
|
||||
- [ ] {{SUBTASK_2_3_2}}
|
||||
|
||||
### Verification
|
||||
|
||||
- [ ] All Phase 2 tests passing
|
||||
- [ ] Integration tests passing
|
||||
- [ ] Code review approved
|
||||
|
||||
### Checkpoint
|
||||
|
||||
```
|
||||
Commit: [track-id] checkpoint: phase 2 complete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: {{PHASE_3_NAME}}
|
||||
|
||||
**Objective:** {{PHASE_3_OBJECTIVE}}
|
||||
**Estimated Duration:** {{PHASE_3_DURATION}}
|
||||
**Dependencies:** Phase 2 complete
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **3.1 {{TASK_3_1_TITLE}}**
|
||||
- [ ] {{SUBTASK_3_1_1}}
|
||||
- [ ] {{SUBTASK_3_1_2}}
|
||||
|
||||
- [ ] **3.2 {{TASK_3_2_TITLE}}**
|
||||
- [ ] {{SUBTASK_3_2_1}}
|
||||
- [ ] {{SUBTASK_3_2_2}}
|
||||
|
||||
- [ ] **3.3 {{TASK_3_3_TITLE}}**
|
||||
- [ ] {{SUBTASK_3_3_1}}
|
||||
- [ ] {{SUBTASK_3_3_2}}
|
||||
|
||||
### Verification
|
||||
|
||||
- [ ] All Phase 3 tests passing
|
||||
- [ ] End-to-end tests passing
|
||||
- [ ] Code review approved
|
||||
|
||||
### Checkpoint
|
||||
|
||||
```
|
||||
Commit: [track-id] checkpoint: phase 3 complete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: {{PHASE_4_NAME}}
|
||||
|
||||
**Objective:** {{PHASE_4_OBJECTIVE}}
|
||||
**Estimated Duration:** {{PHASE_4_DURATION}}
|
||||
**Dependencies:** Phase 3 complete
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] **4.1 {{TASK_4_1_TITLE}}**
|
||||
- [ ] {{SUBTASK_4_1_1}}
|
||||
- [ ] {{SUBTASK_4_1_2}}
|
||||
|
||||
- [ ] **4.2 {{TASK_4_2_TITLE}}**
|
||||
- [ ] {{SUBTASK_4_2_1}}
|
||||
- [ ] {{SUBTASK_4_2_2}}
|
||||
|
||||
- [ ] **4.3 {{TASK_4_3_TITLE}}**
|
||||
- [ ] {{SUBTASK_4_3_1}}
|
||||
- [ ] {{SUBTASK_4_3_2}}
|
||||
|
||||
### Verification
|
||||
|
||||
- [ ] All tests passing
|
||||
- [ ] Coverage ≥ 80%
|
||||
- [ ] Performance benchmarks met
|
||||
- [ ] Documentation complete
|
||||
- [ ] Code review approved
|
||||
|
||||
### Checkpoint
|
||||
|
||||
```
|
||||
Commit: [track-id] checkpoint: phase 4 complete (track done)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final Verification
|
||||
|
||||
### Quality Gates
|
||||
|
||||
- [ ] All unit tests passing
|
||||
- [ ] All integration tests passing
|
||||
- [ ] All E2E tests passing
|
||||
- [ ] Code coverage ≥ 80%
|
||||
- [ ] No critical linting errors
|
||||
- [ ] Security scan passed
|
||||
- [ ] Performance requirements met
|
||||
- [ ] Accessibility requirements met
|
||||
|
||||
### Documentation
|
||||
|
||||
- [ ] API documentation updated
|
||||
- [ ] README updated (if applicable)
|
||||
- [ ] Changelog entry added
|
||||
|
||||
### Deployment
|
||||
|
||||
- [ ] Staging deployment successful
|
||||
- [ ] Smoke tests passed
|
||||
- [ ] Production deployment approved
|
||||
|
||||
---
|
||||
|
||||
## Deviations Log
|
||||
|
||||
| Date | Task | Deviation | Reason | Resolution |
|
||||
| -------- | -------- | ------------- | ---------- | -------------- |
|
||||
| {{DATE}} | {{TASK}} | {{DEVIATION}} | {{REASON}} | {{RESOLUTION}} |
|
||||
|
||||
## Notes
|
||||
|
||||
{{IMPLEMENTATION_NOTES}}
|
||||
|
||||
---
|
||||
|
||||
**Plan Created:** {{CREATED_DATE}}
|
||||
**Last Updated:** {{UPDATED_DATE}}
|
||||
169
plugins/conductor/templates/track-spec.md
Normal file
169
plugins/conductor/templates/track-spec.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Track Specification: {{TRACK_NAME}}
|
||||
|
||||
## Overview
|
||||
|
||||
**Track ID:** {{TRACK_ID}}
|
||||
**Type:** {{TRACK_TYPE}} (feature | bug | chore | refactor)
|
||||
**Priority:** {{PRIORITY}} (critical | high | medium | low)
|
||||
**Created:** {{CREATED_DATE}}
|
||||
**Author:** {{AUTHOR}}
|
||||
|
||||
### Description
|
||||
|
||||
{{TRACK_DESCRIPTION}}
|
||||
|
||||
### Background
|
||||
|
||||
{{BACKGROUND_CONTEXT}}
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### FR-1: {{REQUIREMENT_1_TITLE}}
|
||||
|
||||
{{REQUIREMENT_1_DESCRIPTION}}
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] {{FR1_CRITERIA_1}}
|
||||
- [ ] {{FR1_CRITERIA_2}}
|
||||
- [ ] {{FR1_CRITERIA_3}}
|
||||
|
||||
### FR-2: {{REQUIREMENT_2_TITLE}}
|
||||
|
||||
{{REQUIREMENT_2_DESCRIPTION}}
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] {{FR2_CRITERIA_1}}
|
||||
- [ ] {{FR2_CRITERIA_2}}
|
||||
- [ ] {{FR2_CRITERIA_3}}
|
||||
|
||||
### FR-3: {{REQUIREMENT_3_TITLE}}
|
||||
|
||||
{{REQUIREMENT_3_DESCRIPTION}}
|
||||
|
||||
**Acceptance Criteria:**
|
||||
|
||||
- [ ] {{FR3_CRITERIA_1}}
|
||||
- [ ] {{FR3_CRITERIA_2}}
|
||||
- [ ] {{FR3_CRITERIA_3}}
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### Performance
|
||||
|
||||
- {{PERFORMANCE_REQUIREMENT_1}}
|
||||
- {{PERFORMANCE_REQUIREMENT_2}}
|
||||
|
||||
### Security
|
||||
|
||||
- {{SECURITY_REQUIREMENT_1}}
|
||||
- {{SECURITY_REQUIREMENT_2}}
|
||||
|
||||
### Scalability
|
||||
|
||||
- {{SCALABILITY_REQUIREMENT_1}}
|
||||
|
||||
### Accessibility
|
||||
|
||||
- {{ACCESSIBILITY_REQUIREMENT_1}}
|
||||
|
||||
### Compatibility
|
||||
|
||||
- {{COMPATIBILITY_REQUIREMENT_1}}
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### Must Have (P0)
|
||||
|
||||
- [ ] {{P0_CRITERIA_1}}
|
||||
- [ ] {{P0_CRITERIA_2}}
|
||||
- [ ] {{P0_CRITERIA_3}}
|
||||
|
||||
### Should Have (P1)
|
||||
|
||||
- [ ] {{P1_CRITERIA_1}}
|
||||
- [ ] {{P1_CRITERIA_2}}
|
||||
|
||||
### Nice to Have (P2)
|
||||
|
||||
- [ ] {{P2_CRITERIA_1}}
|
||||
- [ ] {{P2_CRITERIA_2}}
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- {{IN_SCOPE_1}}
|
||||
- {{IN_SCOPE_2}}
|
||||
- {{IN_SCOPE_3}}
|
||||
- {{IN_SCOPE_4}}
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- {{OUT_OF_SCOPE_1}}
|
||||
- {{OUT_OF_SCOPE_2}}
|
||||
- {{OUT_OF_SCOPE_3}}
|
||||
|
||||
### Future Considerations
|
||||
|
||||
- {{FUTURE_1}}
|
||||
- {{FUTURE_2}}
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Upstream Dependencies
|
||||
|
||||
| Dependency | Type | Status | Notes |
|
||||
| ---------- | ---------- | ------------ | ----------- |
|
||||
| {{DEP_1}} | {{TYPE_1}} | {{STATUS_1}} | {{NOTES_1}} |
|
||||
| {{DEP_2}} | {{TYPE_2}} | {{STATUS_2}} | {{NOTES_2}} |
|
||||
|
||||
### Downstream Impacts
|
||||
|
||||
| Component | Impact | Mitigation |
|
||||
| --------------- | ------------ | ---------------- |
|
||||
| {{COMPONENT_1}} | {{IMPACT_1}} | {{MITIGATION_1}} |
|
||||
| {{COMPONENT_2}} | {{IMPACT_2}} | {{MITIGATION_2}} |
|
||||
|
||||
### External Dependencies
|
||||
|
||||
- {{EXTERNAL_DEP_1}}
|
||||
- {{EXTERNAL_DEP_2}}
|
||||
|
||||
## Risks
|
||||
|
||||
### Technical Risks
|
||||
|
||||
| Risk | Probability | Impact | Mitigation |
|
||||
| --------------- | ----------- | ------------ | ---------------- |
|
||||
| {{TECH_RISK_1}} | {{PROB_1}} | {{IMPACT_1}} | {{MITIGATION_1}} |
|
||||
| {{TECH_RISK_2}} | {{PROB_2}} | {{IMPACT_2}} | {{MITIGATION_2}} |
|
||||
|
||||
### Business Risks
|
||||
|
||||
| Risk | Probability | Impact | Mitigation |
|
||||
| -------------- | ----------- | ------------ | ---------------- |
|
||||
| {{BIZ_RISK_1}} | {{PROB_1}} | {{IMPACT_1}} | {{MITIGATION_1}} |
|
||||
|
||||
### Unknowns
|
||||
|
||||
- {{UNKNOWN_1}}
|
||||
- {{UNKNOWN_2}}
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] {{QUESTION_1}}
|
||||
- [ ] {{QUESTION_2}}
|
||||
- [ ] {{QUESTION_3}}
|
||||
|
||||
## References
|
||||
|
||||
- {{REFERENCE_1}}
|
||||
- {{REFERENCE_2}}
|
||||
- {{REFERENCE_3}}
|
||||
|
||||
---
|
||||
|
||||
**Approved By:** {{APPROVER}}
|
||||
**Approval Date:** {{APPROVAL_DATE}}
|
||||
53
plugins/conductor/templates/tracks.md
Normal file
53
plugins/conductor/templates/tracks.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Track Registry
|
||||
|
||||
This file maintains the registry of all development tracks for the project. Each track represents a distinct body of work with its own spec and implementation plan.
|
||||
|
||||
## Status Legend
|
||||
|
||||
| Symbol | Status | Description |
|
||||
| ------ | ----------- | ------------------------- |
|
||||
| `[ ]` | Pending | Not yet started |
|
||||
| `[~]` | In Progress | Currently being worked on |
|
||||
| `[x]` | Completed | Finished and verified |
|
||||
|
||||
## Active Tracks
|
||||
|
||||
### [ ] {{TRACK_ID}}: {{TRACK_NAME}}
|
||||
|
||||
**Description:** {{TRACK_DESCRIPTION}}
|
||||
**Priority:** {{PRIORITY}}
|
||||
**Folder:** [./tracks/{{TRACK_ID}}/](./tracks/{{TRACK_ID}}/)
|
||||
|
||||
---
|
||||
|
||||
### [ ] {{TRACK_ID}}: {{TRACK_NAME}}
|
||||
|
||||
**Description:** {{TRACK_DESCRIPTION}}
|
||||
**Priority:** {{PRIORITY}}
|
||||
**Folder:** [./tracks/{{TRACK_ID}}/](./tracks/{{TRACK_ID}}/)
|
||||
|
||||
---
|
||||
|
||||
## Completed Tracks
|
||||
|
||||
<!-- Move completed tracks here -->
|
||||
|
||||
---
|
||||
|
||||
## Track Creation Checklist
|
||||
|
||||
When creating a new track:
|
||||
|
||||
1. [ ] Add entry to this registry
|
||||
2. [ ] Create track folder: `./tracks/{{track-id}}/`
|
||||
3. [ ] Create spec.md from template
|
||||
4. [ ] Create plan.md from template
|
||||
5. [ ] Create metadata.json from template
|
||||
6. [ ] Update index.md with new track reference
|
||||
|
||||
## Notes
|
||||
|
||||
- Track IDs should be lowercase with hyphens (e.g., `user-auth`, `api-v2`)
|
||||
- Keep descriptions concise (one line)
|
||||
- Prioritize tracks as: critical, high, medium, low
|
||||
- Archive completed tracks quarterly
|
||||
192
plugins/conductor/templates/workflow.md
Normal file
192
plugins/conductor/templates/workflow.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Development Workflow
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **plan.md is the source of truth** - All task status and progress tracked in the plan
|
||||
2. **Test-Driven Development** - Red → Green → Refactor cycle with 80% coverage target
|
||||
3. **CI/CD Compatibility** - All changes must pass automated pipelines before merge
|
||||
4. **Incremental Progress** - Small, verifiable commits with clear purpose
|
||||
|
||||
## Task Lifecycle
|
||||
|
||||
### Step 1: Task Selection
|
||||
|
||||
- Review plan.md for next pending task
|
||||
- Verify dependencies are complete
|
||||
- Confirm understanding of acceptance criteria
|
||||
|
||||
### Step 2: Progress Marking
|
||||
|
||||
- Update task status in plan.md from `[ ]` to `[~]`
|
||||
- Note start time if tracking velocity
|
||||
|
||||
### Step 3: Red Phase (Write Failing Tests)
|
||||
|
||||
- Write test(s) that define expected behavior
|
||||
- Verify test fails for the right reason
|
||||
- Keep tests focused and minimal
|
||||
|
||||
### Step 4: Green Phase (Make Tests Pass)
|
||||
|
||||
- Write minimum code to pass tests
|
||||
- Avoid premature optimization
|
||||
- Focus on correctness over elegance
|
||||
|
||||
### Step 5: Refactor Phase
|
||||
|
||||
- Improve code structure without changing behavior
|
||||
- Apply relevant style guide conventions
|
||||
- Remove duplication and clarify intent
|
||||
|
||||
### Step 6: Coverage Verification
|
||||
|
||||
- Run coverage report
|
||||
- Ensure new code meets 80% threshold
|
||||
- Add edge case tests if coverage gaps exist
|
||||
|
||||
### Step 7: Deviation Documentation
|
||||
|
||||
- If implementation differs from spec, document why
|
||||
- Update spec if change is permanent
|
||||
- Flag for review if uncertain
|
||||
|
||||
### Step 8: Code Commit
|
||||
|
||||
- Stage related changes only
|
||||
- Write clear commit message referencing task
|
||||
- Format: `[track-id] task: description`
|
||||
|
||||
### Step 9: Git Notes (Optional)
|
||||
|
||||
- Add implementation notes for complex changes
|
||||
- Reference relevant decisions or trade-offs
|
||||
|
||||
### Step 10: Plan Update
|
||||
|
||||
- Mark task as `[x]` completed in plan.md
|
||||
- Update any affected downstream tasks
|
||||
- Note blockers or follow-up items
|
||||
|
||||
### Step 11: Plan Commit
|
||||
|
||||
- Commit plan.md changes separately
|
||||
- Format: `[track-id] plan: mark task X complete`
|
||||
|
||||
## Phase Completion Protocol
|
||||
|
||||
### Checkpoint Commits
|
||||
|
||||
At the end of each phase:
|
||||
|
||||
1. Ensure all phase tasks are `[x]` complete
|
||||
2. Run full test suite
|
||||
3. Verify coverage meets threshold
|
||||
4. Create checkpoint commit: `[track-id] checkpoint: phase N complete`
|
||||
|
||||
### Test Verification
|
||||
|
||||
```bash
|
||||
{{TEST_COMMAND}}
|
||||
{{COVERAGE_COMMAND}}
|
||||
```
|
||||
|
||||
### Manual Approval Gates
|
||||
|
||||
Phases requiring approval before proceeding:
|
||||
|
||||
- Architecture changes
|
||||
- API contract modifications
|
||||
- Database schema changes
|
||||
- Security-sensitive implementations
|
||||
|
||||
## Quality Assurance Gates
|
||||
|
||||
All code must pass these criteria before merge:
|
||||
|
||||
| Gate | Requirement | Command |
|
||||
| ----------- | ------------------------ | ------------------------ |
|
||||
| 1. Tests | All tests passing | `{{TEST_COMMAND}}` |
|
||||
| 2. Coverage | Minimum 80% | `{{COVERAGE_COMMAND}}` |
|
||||
| 3. Style | Follows style guide | `{{LINT_COMMAND}}` |
|
||||
| 4. Docs | Public APIs documented | Manual review |
|
||||
| 5. Types | No type errors | `{{TYPE_CHECK_COMMAND}}` |
|
||||
| 6. Linting | No lint errors | `{{LINT_COMMAND}}` |
|
||||
| 7. Mobile | Responsive if applicable | Manual review |
|
||||
| 8. Security | No known vulnerabilities | `{{SECURITY_COMMAND}}` |
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Environment Setup
|
||||
|
||||
```bash
|
||||
{{SETUP_COMMAND}}
|
||||
```
|
||||
|
||||
### Development Server
|
||||
|
||||
```bash
|
||||
{{DEV_COMMAND}}
|
||||
```
|
||||
|
||||
### Pre-Commit Checks
|
||||
|
||||
```bash
|
||||
{{PRE_COMMIT_COMMAND}}
|
||||
```
|
||||
|
||||
### Full Validation
|
||||
|
||||
```bash
|
||||
{{VALIDATE_COMMAND}}
|
||||
```
|
||||
|
||||
## Workflow Diagram
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Select Task │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Mark [~] │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ RED: Write │
|
||||
│ Failing Test│
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ GREEN: Make │
|
||||
│ Test Pass │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ REFACTOR │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Verify │
|
||||
│ Coverage │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Commit Code │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Mark [x] │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Commit Plan │
|
||||
└─────────────┘
|
||||
```
|
||||
Reference in New Issue
Block a user