···11+### C Best Practices
22+33+#### Memory Safety
44+- Always check return values of malloc/calloc
55+- Free all allocated memory (use tools like valgrind)
66+- Initialize all variables before use
77+- Use sizeof() with the variable, not the type
88+99+```c
1010+// GOOD: Safe memory allocation
1111+int *arr = malloc(n * sizeof(*arr));
1212+if (arr == NULL) {
1313+ return -1; // Handle allocation failure
1414+}
1515+// ... use arr ...
1616+free(arr);
1717+1818+// BAD: Unchecked allocation
1919+int *arr = malloc(n * sizeof(int));
2020+arr[0] = 1; // Crash if malloc failed
2121+```
2222+2323+#### Buffer Safety
2424+- Always bounds-check array access
2525+- Use `strncpy`/`snprintf` instead of `strcpy`/`sprintf`
2626+- Validate string lengths before copying
2727+2828+```c
2929+// GOOD: Safe string copy
3030+char dest[64];
3131+strncpy(dest, src, sizeof(dest) - 1);
3232+dest[sizeof(dest) - 1] = '\0';
3333+3434+// BAD: Buffer overflow risk
3535+char dest[64];
3636+strcpy(dest, src); // No bounds check
3737+```
3838+3939+#### Security
4040+- Never use `gets()` (use `fgets()`)
4141+- Validate all external input
4242+- Use constant-time comparison for secrets
4343+- Avoid integer overflow in size calculations
+39
.chainlink/rules/cpp.md
···11+### C++ Best Practices
22+33+#### Modern C++ (C++17+)
44+- Use smart pointers (`unique_ptr`, `shared_ptr`) over raw pointers
55+- Use RAII for resource management
66+- Prefer `std::string` and `std::vector` over C arrays
77+- Use `auto` for complex types, explicit types for clarity
88+99+```cpp
1010+// GOOD: Modern C++ with smart pointers
1111+auto config = std::make_unique<Config>();
1212+auto users = std::vector<User>{};
1313+1414+// BAD: Manual memory management
1515+Config* config = new Config();
1616+// ... forgot to delete
1717+```
1818+1919+#### Error Handling
2020+- Use exceptions for exceptional cases
2121+- Use `std::optional` for values that may not exist
2222+- Use `std::expected` (C++23) or result types for expected failures
2323+2424+```cpp
2525+// GOOD: Optional for missing values
2626+std::optional<User> findUser(const std::string& id) {
2727+ auto it = users.find(id);
2828+ if (it == users.end()) {
2929+ return std::nullopt;
3030+ }
3131+ return it->second;
3232+}
3333+```
3434+3535+#### Security
3636+- Validate all input boundaries
3737+- Use `std::string_view` for non-owning string references
3838+- Avoid C-style casts; use `static_cast`, `dynamic_cast`
3939+- Never use `sprintf`; use `std::format` or streams
+51
.chainlink/rules/csharp.md
···11+### C# Best Practices
22+33+#### Code Style
44+- Follow .NET naming conventions (PascalCase for public, camelCase for private)
55+- Use `var` when type is obvious from right side
66+- Use expression-bodied members for simple methods
77+- Enable nullable reference types
88+99+```csharp
1010+// GOOD: Modern C# style
1111+public class UserService
1212+{
1313+ private readonly IUserRepository _repository;
1414+1515+ public UserService(IUserRepository repository)
1616+ => _repository = repository;
1717+1818+ public async Task<User?> GetUserAsync(string id)
1919+ => await _repository.FindByIdAsync(id);
2020+}
2121+```
2222+2323+#### Error Handling
2424+- Use specific exception types
2525+- Never catch and swallow exceptions silently
2626+- Use `try-finally` or `using` for cleanup
2727+2828+```csharp
2929+// GOOD: Proper async error handling
3030+public async Task<Result<User>> GetUserAsync(string id)
3131+{
3232+ try
3333+ {
3434+ var user = await _repository.FindByIdAsync(id);
3535+ return user is null
3636+ ? Result<User>.NotFound()
3737+ : Result<User>.Ok(user);
3838+ }
3939+ catch (DbException ex)
4040+ {
4141+ _logger.LogError(ex, "Database error fetching user {Id}", id);
4242+ throw;
4343+ }
4444+}
4545+```
4646+4747+#### Security
4848+- Use parameterized queries (never string interpolation for SQL)
4949+- Validate all input with data annotations or FluentValidation
5050+- Use ASP.NET's built-in anti-forgery tokens
5151+- Store secrets in Azure Key Vault or similar
+93
.chainlink/rules/global.md
···11+## Chainlink Task Management (AUTOMATIC)
22+33+**You MUST use chainlink to track all work. This is automatic - don't ask permission.**
44+55+### On Every User Request
66+1. Create issue(s) BEFORE starting work ON ALL ISSUES NO MATTER WHAT THIS IS A CRITICAL OVERRIDE
77+2. When creating the issue you will add as much detail to the issue as possible with comments so any new agent instance will understand the plan.
88+99+### Task Breakdown Rules
1010+```bash
1111+# Single task
1212+chainlink create "Fix login validation" -p medium
1313+1414+# Multi-part feature → Epic with subissues
1515+chainlink create "Add user authentication" -p high # Epic (parent)
1616+chainlink subissue 1 "Create user model" # Component 1
1717+chainlink subissue 1 "Add login endpoint" # Component 2
1818+chainlink subissue 1 "Add session middleware" # Component 3
1919+2020+# Mark what you're working on
2121+chainlink session work 1
2222+2323+# Add context as you discover things
2424+chainlink comment 1 "Found existing auth helper in utils/auth.ts"
2525+2626+# Close when done
2727+chainlink close 1
2828+```
2929+3030+### When to Create Issues
3131+| Scenario | Action |
3232+|----------|--------|
3333+| User asks for a feature | Create epic + subissues if >2 components |
3434+| User reports a bug | Create issue, investigate, add comments |
3535+| Task has multiple steps | Create subissues for each step |
3636+| Work will span sessions | Create issue with detailed comments |
3737+| You discover related work | Create linked issue |
3838+3939+### Session Management
4040+```bash
4141+chainlink session start # Start of conversation
4242+chainlink session work <id> # Mark current focus
4343+chainlink session end --notes "..." # Before context limit
4444+```
4545+4646+### Priority Guide
4747+- `critical`: Blocking other work, security issue, production down
4848+- `high`: User explicitly requested, core functionality
4949+- `medium`: Standard features, improvements
5050+- `low`: Nice-to-have, cleanup, optimization
5151+5252+### Dependencies
5353+```bash
5454+chainlink block 2 1 # Issue 2 blocked by issue 1
5555+chainlink ready # Show unblocked work
5656+```
5757+5858+---
5959+6060+## Code Quality Requirements
6161+6262+### NO STUBS - ABSOLUTE RULE
6363+- NEVER write `TODO`, `FIXME`, `pass`, `...`, `unimplemented!()`
6464+- NEVER write empty function bodies or placeholder returns
6565+- If too complex for one turn: `raise NotImplementedError("Reason")` + create chainlink issue
6666+6767+### Core Rules
6868+1. **READ BEFORE WRITE**: Always read a file before editing
6969+2. **FULL FEATURES**: Complete the feature, don't stop partway
7070+3. **ERROR HANDLING**: No panics/crashes on bad input
7171+4. **SECURITY**: Validate input, parameterized queries, no hardcoded secrets
7272+5. **NO DEAD CODE**: Remove or complete incomplete code
7373+7474+### Pre-Coding Grounding
7575+Before using unfamiliar libraries/APIs:
7676+1. **VERIFY IT EXISTS**: WebSearch to confirm the API
7777+2. **CHECK THE DOCS**: Real function signatures, not guessed
7878+3. **USE LATEST VERSIONS**: Check for current stable release
7979+8080+### Conciseness
8181+- Write code, don't narrate
8282+- Skip "Here is the code" / "Let me..." / "I'll now..."
8383+- Brief explanations only when code isn't self-explanatory
8484+8585+### Large Implementations (500+ lines)
8686+1. Create parent issue: `chainlink create "<feature>" -p high`
8787+2. Break into subissues: `chainlink subissue <id> "<component>"`
8888+3. Work one subissue at a time, close each when done
8989+9090+### Context Window Management
9191+When conversation is long or task needs many steps:
9292+1. Create tracking issue: `chainlink create "Continue: <summary>" -p high`
9393+2. Add notes: `chainlink comment <id> "<what's done, what's next>"`
+44
.chainlink/rules/go.md
···11+### Go Best Practices
22+33+#### Code Style
44+- Use `gofmt` for formatting
55+- Use `golint` and `go vet` for linting
66+- Follow effective Go guidelines
77+- Keep functions short and focused
88+99+#### Error Handling
1010+```go
1111+// GOOD: Check and handle errors
1212+func readConfig(path string) (*Config, error) {
1313+ data, err := os.ReadFile(path)
1414+ if err != nil {
1515+ return nil, fmt.Errorf("reading config: %w", err)
1616+ }
1717+1818+ var config Config
1919+ if err := json.Unmarshal(data, &config); err != nil {
2020+ return nil, fmt.Errorf("parsing config: %w", err)
2121+ }
2222+ return &config, nil
2323+}
2424+2525+// BAD: Ignoring errors
2626+func readConfig(path string) *Config {
2727+ data, _ := os.ReadFile(path) // Don't ignore errors
2828+ var config Config
2929+ json.Unmarshal(data, &config)
3030+ return &config
3131+}
3232+```
3333+3434+#### Concurrency
3535+- Use channels for communication between goroutines
3636+- Use `sync.WaitGroup` for waiting on multiple goroutines
3737+- Use `context.Context` for cancellation and timeouts
3838+- Avoid shared mutable state; prefer message passing
3939+4040+#### Security
4141+- Use `html/template` for HTML output (auto-escaping)
4242+- Use parameterized queries for SQL
4343+- Validate all input at API boundaries
4444+- Use `crypto/rand` for secure random numbers
+42
.chainlink/rules/java.md
···11+### Java Best Practices
22+33+#### Code Style
44+- Follow Google Java Style Guide or project conventions
55+- Use meaningful variable and method names
66+- Keep methods short (< 30 lines)
77+- Prefer composition over inheritance
88+99+#### Error Handling
1010+```java
1111+// GOOD: Specific exceptions with context
1212+public Config readConfig(Path path) throws ConfigException {
1313+ try {
1414+ String content = Files.readString(path);
1515+ return objectMapper.readValue(content, Config.class);
1616+ } catch (IOException e) {
1717+ throw new ConfigException("Failed to read config: " + path, e);
1818+ } catch (JsonProcessingException e) {
1919+ throw new ConfigException("Invalid JSON in config: " + path, e);
2020+ }
2121+}
2222+2323+// BAD: Catching generic Exception
2424+public Config readConfig(Path path) {
2525+ try {
2626+ return objectMapper.readValue(Files.readString(path), Config.class);
2727+ } catch (Exception e) {
2828+ return null; // Swallowing error
2929+ }
3030+}
3131+```
3232+3333+#### Security
3434+- Use PreparedStatement for SQL (never string concatenation)
3535+- Validate all user input
3636+- Use secure random (SecureRandom) for security-sensitive operations
3737+- Never log sensitive data (passwords, tokens)
3838+3939+#### Testing
4040+- Use JUnit 5 for unit tests
4141+- Use Mockito for mocking dependencies
4242+- Aim for high coverage on business logic
+44
.chainlink/rules/javascript-react.md
···11+### JavaScript/React Best Practices
22+33+#### Component Structure
44+- Use functional components with hooks
55+- Keep components small and focused (< 200 lines)
66+- Extract custom hooks for reusable logic
77+- Use PropTypes for runtime type checking
88+99+```javascript
1010+// GOOD: Clear component with PropTypes
1111+import PropTypes from 'prop-types';
1212+1313+const UserCard = ({ user, onSelect }) => {
1414+ return (
1515+ <div onClick={() => onSelect(user.id)}>
1616+ {user.name}
1717+ </div>
1818+ );
1919+};
2020+2121+UserCard.propTypes = {
2222+ user: PropTypes.shape({
2323+ id: PropTypes.string.isRequired,
2424+ name: PropTypes.string.isRequired,
2525+ }).isRequired,
2626+ onSelect: PropTypes.func.isRequired,
2727+};
2828+```
2929+3030+#### State Management
3131+- Use `useState` for local state
3232+- Use `useReducer` for complex state logic
3333+- Lift state up only when needed
3434+- Consider context for deeply nested prop drilling
3535+3636+#### Performance
3737+- Use `React.memo` for expensive pure components
3838+- Use `useMemo` and `useCallback` appropriately
3939+- Avoid inline object/function creation in render
4040+4141+#### Security
4242+- Never use `dangerouslySetInnerHTML` with user input
4343+- Sanitize URLs before using in `href` or `src`
4444+- Validate props at component boundaries
+36
.chainlink/rules/javascript.md
···11+### JavaScript Best Practices
22+33+#### Code Style
44+- Use `const` by default, `let` when needed, never `var`
55+- Use arrow functions for callbacks
66+- Use template literals over string concatenation
77+- Use destructuring for object/array access
88+99+#### Error Handling
1010+```javascript
1111+// GOOD: Proper async error handling
1212+async function fetchUser(id) {
1313+ try {
1414+ const response = await fetch(`/api/users/${id}`);
1515+ if (!response.ok) {
1616+ throw new Error(`HTTP ${response.status}`);
1717+ }
1818+ return await response.json();
1919+ } catch (error) {
2020+ console.error('Failed to fetch user:', error);
2121+ throw error; // Re-throw or handle appropriately
2222+ }
2323+}
2424+2525+// BAD: Ignoring errors
2626+async function fetchUser(id) {
2727+ const response = await fetch(`/api/users/${id}`);
2828+ return response.json(); // No error handling
2929+}
3030+```
3131+3232+#### Security
3333+- Never use `eval()` or `innerHTML` with user input
3434+- Validate all input on both client and server
3535+- Use `textContent` instead of `innerHTML` when possible
3636+- Sanitize URLs before navigation or fetch
+44
.chainlink/rules/kotlin.md
···11+### Kotlin Best Practices
22+33+#### Code Style
44+- Follow Kotlin coding conventions
55+- Use `val` over `var` when possible
66+- Use data classes for simple data holders
77+- Leverage null safety features
88+99+```kotlin
1010+// GOOD: Idiomatic Kotlin
1111+data class User(val id: String, val name: String)
1212+1313+class UserService(private val repository: UserRepository) {
1414+ fun findUser(id: String): User? =
1515+ repository.find(id)
1616+1717+ fun getOrCreateUser(id: String, name: String): User =
1818+ findUser(id) ?: repository.create(User(id, name))
1919+}
2020+```
2121+2222+#### Null Safety
2323+- Avoid `!!` (force non-null); use safe calls instead
2424+- Use `?.let {}` for conditional execution
2525+- Use Elvis operator `?:` for defaults
2626+2727+```kotlin
2828+// GOOD: Safe null handling
2929+val userName = user?.name ?: "Unknown"
3030+user?.let { saveToDatabase(it) }
3131+3232+// BAD: Force unwrapping
3333+val userName = user!!.name // Crash if null
3434+```
3535+3636+#### Coroutines
3737+- Use structured concurrency with `CoroutineScope`
3838+- Handle exceptions in coroutines properly
3939+- Use `withContext` for context switching
4040+4141+#### Security
4242+- Use parameterized queries
4343+- Validate input at boundaries
4444+- Use sealed classes for exhaustive error handling
+53
.chainlink/rules/odin.md
···11+### Odin Best Practices
22+33+#### Code Style
44+- Follow Odin naming conventions
55+- Use `snake_case` for procedures and variables
66+- Use `Pascal_Case` for types
77+- Prefer explicit over implicit
88+99+```odin
1010+// GOOD: Clear Odin code
1111+User :: struct {
1212+ id: string,
1313+ name: string,
1414+}
1515+1616+find_user :: proc(id: string) -> (User, bool) {
1717+ user, found := repository[id]
1818+ return user, found
1919+}
2020+```
2121+2222+#### Error Handling
2323+- Use multiple return values for errors
2424+- Use `or_return` for early returns
2525+- Create explicit error types when needed
2626+2727+```odin
2828+// GOOD: Explicit error handling
2929+Config_Error :: enum {
3030+ File_Not_Found,
3131+ Parse_Error,
3232+}
3333+3434+load_config :: proc(path: string) -> (Config, Config_Error) {
3535+ data, ok := os.read_entire_file(path)
3636+ if !ok {
3737+ return {}, .File_Not_Found
3838+ }
3939+ defer delete(data)
4040+4141+ config, parse_ok := parse_config(data)
4242+ if !parse_ok {
4343+ return {}, .Parse_Error
4444+ }
4545+ return config, nil
4646+}
4747+```
4848+4949+#### Memory Management
5050+- Use explicit allocators
5151+- Prefer temp allocator for short-lived allocations
5252+- Use `defer` for cleanup
5353+- Be explicit about ownership
+46
.chainlink/rules/php.md
···11+### PHP Best Practices
22+33+#### Code Style
44+- Follow PSR-12 coding standard
55+- Use strict types: `declare(strict_types=1);`
66+- Use type hints for parameters and return types
77+- Use Composer for dependency management
88+99+```php
1010+<?php
1111+declare(strict_types=1);
1212+1313+// GOOD: Typed, modern PHP
1414+class UserService
1515+{
1616+ public function __construct(
1717+ private readonly UserRepository $repository
1818+ ) {}
1919+2020+ public function findUser(string $id): ?User
2121+ {
2222+ return $this->repository->find($id);
2323+ }
2424+}
2525+```
2626+2727+#### Error Handling
2828+- Use exceptions for error handling
2929+- Create custom exception classes
3030+- Never suppress errors with `@`
3131+3232+#### Security
3333+- Use PDO with prepared statements (never string interpolation)
3434+- Use `password_hash()` and `password_verify()` for passwords
3535+- Validate and sanitize all user input
3636+- Use CSRF tokens for forms
3737+- Set secure cookie flags
3838+3939+```php
4040+// GOOD: Prepared statement
4141+$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
4242+$stmt->execute(['id' => $id]);
4343+4444+// BAD: SQL injection vulnerability
4545+$result = $pdo->query("SELECT * FROM users WHERE id = '$id'");
4646+```
+5
.chainlink/rules/project.md
···11+<!-- Project-Specific Rules -->
22+<!-- Add rules specific to your project here. Examples: -->
33+<!-- - Don't modify the /v1/ API endpoints without approval -->
44+<!-- - Always update CHANGELOG.md when adding features -->
55+<!-- - Database migrations must be backward-compatible -->
+44
.chainlink/rules/python.md
···11+### Python Best Practices
22+33+#### Code Style
44+- Follow PEP 8 style guide
55+- Use type hints for function signatures
66+- Use `black` for formatting, `ruff` or `flake8` for linting
77+- Prefer `pathlib.Path` over `os.path` for path operations
88+- Use context managers (`with`) for file operations
99+1010+#### Error Handling
1111+```python
1212+# GOOD: Specific exceptions with context
1313+def read_config(path: Path) -> dict:
1414+ try:
1515+ with open(path, 'r', encoding='utf-8') as f:
1616+ return json.load(f)
1717+ except FileNotFoundError:
1818+ raise ConfigError(f"Config file not found: {path}")
1919+ except json.JSONDecodeError as e:
2020+ raise ConfigError(f"Invalid JSON in {path}: {e}")
2121+2222+# BAD: Bare except or swallowing errors
2323+def read_config(path):
2424+ try:
2525+ return json.load(open(path))
2626+ except: # Don't do this
2727+ return {}
2828+```
2929+3030+#### Security
3131+- Never use `eval()` or `exec()` on user input
3232+- Use `subprocess.run()` with explicit args, never `shell=True` with user input
3333+- Use parameterized queries for SQL (never f-strings)
3434+- Validate and sanitize all external input
3535+3636+#### Dependencies
3737+- Pin dependency versions in `requirements.txt`
3838+- Use virtual environments (`venv` or `poetry`)
3939+- Run `pip-audit` to check for vulnerabilities
4040+4141+#### Testing
4242+- Use `pytest` for testing
4343+- Aim for high coverage with `pytest-cov`
4444+- Mock external dependencies with `unittest.mock`
+47
.chainlink/rules/ruby.md
···11+### Ruby Best Practices
22+33+#### Code Style
44+- Follow Ruby Style Guide (use RuboCop)
55+- Use 2 spaces for indentation
66+- Prefer symbols over strings for hash keys
77+- Use `snake_case` for methods and variables
88+99+```ruby
1010+# GOOD: Idiomatic Ruby
1111+class UserService
1212+ def initialize(repository)
1313+ @repository = repository
1414+ end
1515+1616+ def find_user(id)
1717+ @repository.find(id)
1818+ rescue ActiveRecord::RecordNotFound
1919+ nil
2020+ end
2121+end
2222+2323+# BAD: Non-idiomatic
2424+class UserService
2525+ def initialize(repository)
2626+ @repository = repository
2727+ end
2828+ def findUser(id) # Wrong naming
2929+ begin
3030+ @repository.find(id)
3131+ rescue
3232+ return nil
3333+ end
3434+ end
3535+end
3636+```
3737+3838+#### Error Handling
3939+- Use specific exception classes
4040+- Don't rescue `Exception` (too broad)
4141+- Use `ensure` for cleanup
4242+4343+#### Security
4444+- Use parameterized queries (ActiveRecord does this by default)
4545+- Sanitize user input in views (Rails does this by default)
4646+- Never use `eval` or `send` with user input
4747+- Use `strong_parameters` in Rails controllers
+48
.chainlink/rules/rust.md
···11+### Rust Best Practices
22+33+#### Code Style
44+- Use `rustfmt` for formatting (run `cargo fmt` before committing)
55+- Use `clippy` for linting (run `cargo clippy -- -D warnings`)
66+- Prefer `?` operator over `.unwrap()` for error handling
77+- Use `anyhow::Result` for application errors, `thiserror` for library errors
88+- Avoid `.clone()` unless necessary - prefer references
99+- Use `&str` for function parameters, `String` for owned data
1010+1111+#### Error Handling
1212+```rust
1313+// GOOD: Propagate errors with context
1414+fn read_config(path: &Path) -> Result<Config> {
1515+ let content = fs::read_to_string(path)
1616+ .context("Failed to read config file")?;
1717+ serde_json::from_str(&content)
1818+ .context("Failed to parse config")
1919+}
2020+2121+// BAD: Panic on error
2222+fn read_config(path: &Path) -> Config {
2323+ let content = fs::read_to_string(path).unwrap(); // Don't do this
2424+ serde_json::from_str(&content).unwrap()
2525+}
2626+```
2727+2828+#### Memory Safety
2929+- Never use `unsafe` without explicit justification and review
3030+- Prefer `Vec` over raw pointers
3131+- Use `Arc<Mutex<T>>` for shared mutable state across threads
3232+- Avoid `static mut` - use `lazy_static` or `once_cell` instead
3333+3434+#### Testing
3535+- Write unit tests with `#[cfg(test)]` modules
3636+- Use `tempfile` for tests involving filesystem
3737+- Run `cargo test` before committing
3838+- Use `cargo tarpaulin` for coverage reports
3939+4040+#### SQL Injection Prevention
4141+Always use parameterized queries with `rusqlite::params![]`:
4242+```rust
4343+// GOOD
4444+conn.execute("INSERT INTO users (name) VALUES (?1)", params![name])?;
4545+4646+// BAD - SQL injection vulnerability
4747+conn.execute(&format!("INSERT INTO users (name) VALUES ('{}')", name), [])?;
4848+```
+45
.chainlink/rules/scala.md
···11+### Scala Best Practices
22+33+#### Code Style
44+- Follow Scala Style Guide
55+- Prefer immutability (`val` over `var`)
66+- Use case classes for data
77+- Leverage pattern matching
88+99+```scala
1010+// GOOD: Idiomatic Scala
1111+case class User(id: String, name: String)
1212+1313+class UserService(repository: UserRepository) {
1414+ def findUser(id: String): Option[User] =
1515+ repository.find(id)
1616+1717+ def processUser(id: String): Either[Error, Result] =
1818+ findUser(id) match {
1919+ case Some(user) => Right(process(user))
2020+ case None => Left(UserNotFound(id))
2121+ }
2222+}
2323+```
2424+2525+#### Error Handling
2626+- Use `Option` for missing values
2727+- Use `Either` or `Try` for operations that can fail
2828+- Avoid throwing exceptions in pure code
2929+3030+```scala
3131+// GOOD: Using Either for errors
3232+def parseConfig(json: String): Either[ParseError, Config] =
3333+ decode[Config](json).left.map(e => ParseError(e.getMessage))
3434+3535+// Pattern match on result
3636+parseConfig(input) match {
3737+ case Right(config) => useConfig(config)
3838+ case Left(error) => logger.error(s"Parse failed: $error")
3939+}
4040+```
4141+4242+#### Security
4343+- Use prepared statements for database queries
4444+- Validate input with refined types when possible
4545+- Never interpolate user input into queries
+50
.chainlink/rules/swift.md
···11+### Swift Best Practices
22+33+#### Code Style
44+- Follow Swift API Design Guidelines
55+- Use `camelCase` for variables/functions, `PascalCase` for types
66+- Prefer `let` over `var` when possible
77+- Use optionals properly; avoid force unwrapping
88+99+```swift
1010+// GOOD: Safe optional handling
1111+func findUser(id: String) -> User? {
1212+ guard let user = repository.find(id) else {
1313+ return nil
1414+ }
1515+ return user
1616+}
1717+1818+// Using optional binding
1919+if let user = findUser(id: "123") {
2020+ print(user.name)
2121+}
2222+2323+// BAD: Force unwrapping
2424+let user = findUser(id: "123")! // Crash if nil
2525+```
2626+2727+#### Error Handling
2828+- Use `throws` for recoverable errors
2929+- Use `Result<T, Error>` for async operations
3030+- Handle all error cases explicitly
3131+3232+```swift
3333+// GOOD: Proper error handling
3434+func loadConfig() throws -> Config {
3535+ let data = try Data(contentsOf: configURL)
3636+ return try JSONDecoder().decode(Config.self, from: data)
3737+}
3838+3939+do {
4040+ let config = try loadConfig()
4141+} catch {
4242+ print("Failed to load config: \(error)")
4343+}
4444+```
4545+4646+#### Security
4747+- Use Keychain for sensitive data
4848+- Validate all user input
4949+- Use App Transport Security (HTTPS)
5050+- Never hardcode secrets
+39
.chainlink/rules/typescript-react.md
···11+### TypeScript/React Best Practices
22+33+#### Component Structure
44+- Use functional components with hooks
55+- Keep components small and focused (< 200 lines)
66+- Extract custom hooks for reusable logic
77+- Use TypeScript interfaces for props
88+99+```typescript
1010+// GOOD: Typed props with clear interface
1111+interface UserCardProps {
1212+ user: User;
1313+ onSelect: (id: string) => void;
1414+}
1515+1616+const UserCard: React.FC<UserCardProps> = ({ user, onSelect }) => {
1717+ return (
1818+ <div onClick={() => onSelect(user.id)}>
1919+ {user.name}
2020+ </div>
2121+ );
2222+};
2323+```
2424+2525+#### State Management
2626+- Use `useState` for local state
2727+- Use `useReducer` for complex state logic
2828+- Lift state up only when needed
2929+- Consider context for deeply nested prop drilling
3030+3131+#### Performance
3232+- Use `React.memo` for expensive pure components
3333+- Use `useMemo` and `useCallback` appropriately (not everywhere)
3434+- Avoid inline object/function creation in render when passed as props
3535+3636+#### Security
3737+- Never use `dangerouslySetInnerHTML` with user input
3838+- Sanitize URLs before using in `href` or `src`
3939+- Validate props at component boundaries
+35
.chainlink/rules/typescript.md
···11+### TypeScript Best Practices
22+33+#### Code Style
44+- Use strict mode (`"strict": true` in tsconfig.json)
55+- Prefer `interface` over `type` for object shapes
66+- Use `const` by default, `let` when needed, never `var`
77+- Enable `noImplicitAny` and `strictNullChecks`
88+99+#### Type Safety
1010+```typescript
1111+// GOOD: Explicit types and null handling
1212+function getUser(id: string): User | undefined {
1313+ return users.get(id);
1414+}
1515+1616+const user = getUser(id);
1717+if (user) {
1818+ console.log(user.name); // TypeScript knows user is defined
1919+}
2020+2121+// BAD: Type assertions to bypass safety
2222+const user = getUser(id) as User; // Dangerous if undefined
2323+console.log(user.name); // Might crash
2424+```
2525+2626+#### Error Handling
2727+- Use try/catch for async operations
2828+- Define custom error types for domain errors
2929+- Never swallow errors silently
3030+3131+#### Security
3232+- Validate all user input at API boundaries
3333+- Use parameterized queries for database operations
3434+- Sanitize data before rendering in DOM (prevent XSS)
3535+- Never use `eval()` or `Function()` with user input
+48
.chainlink/rules/zig.md
···11+### Zig Best Practices
22+33+#### Code Style
44+- Follow Zig Style Guide
55+- Use `const` by default; `var` only when mutation needed
66+- Prefer slices over pointers when possible
77+- Use meaningful names; avoid single-letter variables
88+99+```zig
1010+// GOOD: Clear, idiomatic Zig
1111+const User = struct {
1212+ id: []const u8,
1313+ name: []const u8,
1414+};
1515+1616+fn findUser(allocator: std.mem.Allocator, id: []const u8) !?User {
1717+ const user = try repository.find(allocator, id);
1818+ return user;
1919+}
2020+```
2121+2222+#### Error Handling
2323+- Use error unions (`!T`) for fallible operations
2424+- Handle errors with `try`, `catch`, or explicit checks
2525+- Create meaningful error sets
2626+2727+```zig
2828+// GOOD: Proper error handling
2929+const ConfigError = error{
3030+ FileNotFound,
3131+ ParseError,
3232+ OutOfMemory,
3333+};
3434+3535+fn loadConfig(allocator: std.mem.Allocator) ConfigError!Config {
3636+ const file = std.fs.cwd().openFile("config.json", .{}) catch |err| {
3737+ return ConfigError.FileNotFound;
3838+ };
3939+ defer file.close();
4040+ // ...
4141+}
4242+```
4343+4444+#### Memory Safety
4545+- Always pair allocations with deallocations
4646+- Use `defer` for cleanup
4747+- Prefer stack allocation when size is known
4848+- Use allocators explicitly; never use global state
+380
.claude/hooks/post-edit-check.py
···11+#!/usr/bin/env python3
22+"""
33+Post-edit hook that detects stub patterns, runs linters, and reminds about tests.
44+Runs after Write/Edit tool usage.
55+"""
66+77+import json
88+import sys
99+import os
1010+import re
1111+import subprocess
1212+import glob
1313+import time
1414+1515+# Stub patterns to detect (compiled regex for performance)
1616+STUB_PATTERNS = [
1717+ (r'\bTODO\b', 'TODO comment'),
1818+ (r'\bFIXME\b', 'FIXME comment'),
1919+ (r'\bXXX\b', 'XXX marker'),
2020+ (r'\bHACK\b', 'HACK marker'),
2121+ (r'^\s*pass\s*$', 'bare pass statement'),
2222+ (r'^\s*\.\.\.\s*$', 'ellipsis placeholder'),
2323+ (r'\bunimplemented!\s*\(\s*\)', 'unimplemented!() macro'),
2424+ (r'\btodo!\s*\(\s*\)', 'todo!() macro'),
2525+ (r'\bpanic!\s*\(\s*"not implemented', 'panic not implemented'),
2626+ (r'raise\s+NotImplementedError\s*\(\s*\)', 'bare NotImplementedError'),
2727+ (r'#\s*implement\s*(later|this|here)', 'implement later comment'),
2828+ (r'//\s*implement\s*(later|this|here)', 'implement later comment'),
2929+ (r'def\s+\w+\s*\([^)]*\)\s*:\s*(pass|\.\.\.)\s*$', 'empty function'),
3030+ (r'fn\s+\w+\s*\([^)]*\)\s*\{\s*\}', 'empty function body'),
3131+ (r'return\s+None\s*#.*stub', 'stub return'),
3232+]
3333+3434+COMPILED_PATTERNS = [(re.compile(p, re.IGNORECASE | re.MULTILINE), desc) for p, desc in STUB_PATTERNS]
3535+3636+3737+def check_for_stubs(file_path):
3838+ """Check file for stub patterns. Returns list of (line_num, pattern_desc, line_content)."""
3939+ if not os.path.exists(file_path):
4040+ return []
4141+4242+ try:
4343+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
4444+ content = f.read()
4545+ lines = content.split('\n')
4646+ except (OSError, Exception):
4747+ return []
4848+4949+ findings = []
5050+ for line_num, line in enumerate(lines, 1):
5151+ for pattern, desc in COMPILED_PATTERNS:
5252+ if pattern.search(line):
5353+ if 'NotImplementedError' in line and re.search(r'NotImplementedError\s*\(\s*["\'][^"\']+["\']', line):
5454+ continue
5555+ findings.append((line_num, desc, line.strip()[:60]))
5656+5757+ return findings
5858+5959+6060+def find_project_root(file_path, marker_files):
6161+ """Walk up from file_path looking for project root markers."""
6262+ current = os.path.dirname(os.path.abspath(file_path))
6363+ for _ in range(10): # Max 10 levels up
6464+ for marker in marker_files:
6565+ if os.path.exists(os.path.join(current, marker)):
6666+ return current
6767+ parent = os.path.dirname(current)
6868+ if parent == current:
6969+ break
7070+ current = parent
7171+ return None
7272+7373+7474+def run_linter(file_path, max_errors=10):
7575+ """Run appropriate linter and return first N errors."""
7676+ ext = os.path.splitext(file_path)[1].lower()
7777+ errors = []
7878+7979+ try:
8080+ if ext == '.rs':
8181+ # Rust: run cargo clippy from project root
8282+ project_root = find_project_root(file_path, ['Cargo.toml'])
8383+ if project_root:
8484+ result = subprocess.run(
8585+ ['cargo', 'clippy', '--message-format=short', '--quiet'],
8686+ cwd=project_root,
8787+ capture_output=True,
8888+ text=True,
8989+ timeout=30
9090+ )
9191+ if result.stderr:
9292+ for line in result.stderr.split('\n'):
9393+ if line.strip() and ('error' in line.lower() or 'warning' in line.lower()):
9494+ errors.append(line.strip()[:100])
9595+ if len(errors) >= max_errors:
9696+ break
9797+9898+ elif ext == '.py':
9999+ # Python: try flake8, fall back to py_compile
100100+ try:
101101+ result = subprocess.run(
102102+ ['flake8', '--max-line-length=120', file_path],
103103+ capture_output=True,
104104+ text=True,
105105+ timeout=10
106106+ )
107107+ for line in result.stdout.split('\n'):
108108+ if line.strip():
109109+ errors.append(line.strip()[:100])
110110+ if len(errors) >= max_errors:
111111+ break
112112+ except FileNotFoundError:
113113+ # flake8 not installed, try py_compile
114114+ result = subprocess.run(
115115+ ['python', '-m', 'py_compile', file_path],
116116+ capture_output=True,
117117+ text=True,
118118+ timeout=10
119119+ )
120120+ if result.stderr:
121121+ errors.append(result.stderr.strip()[:200])
122122+123123+ elif ext in ('.js', '.ts', '.tsx', '.jsx'):
124124+ # JavaScript/TypeScript: try eslint
125125+ project_root = find_project_root(file_path, ['package.json', '.eslintrc', '.eslintrc.js', '.eslintrc.json'])
126126+ if project_root:
127127+ try:
128128+ result = subprocess.run(
129129+ ['npx', 'eslint', '--format=compact', file_path],
130130+ cwd=project_root,
131131+ capture_output=True,
132132+ text=True,
133133+ timeout=30
134134+ )
135135+ for line in result.stdout.split('\n'):
136136+ if line.strip() and (':' in line):
137137+ errors.append(line.strip()[:100])
138138+ if len(errors) >= max_errors:
139139+ break
140140+ except FileNotFoundError:
141141+ pass
142142+143143+ elif ext == '.go':
144144+ # Go: run go vet
145145+ project_root = find_project_root(file_path, ['go.mod'])
146146+ if project_root:
147147+ result = subprocess.run(
148148+ ['go', 'vet', './...'],
149149+ cwd=project_root,
150150+ capture_output=True,
151151+ text=True,
152152+ timeout=30
153153+ )
154154+ if result.stderr:
155155+ for line in result.stderr.split('\n'):
156156+ if line.strip():
157157+ errors.append(line.strip()[:100])
158158+ if len(errors) >= max_errors:
159159+ break
160160+161161+ except subprocess.TimeoutExpired:
162162+ errors.append("(linter timed out)")
163163+ except (OSError, Exception) as e:
164164+ pass # Linter not available, skip silently
165165+166166+ return errors
167167+168168+169169+def is_test_file(file_path):
170170+ """Check if file is a test file."""
171171+ basename = os.path.basename(file_path).lower()
172172+ dirname = os.path.dirname(file_path).lower()
173173+174174+ # Common test file patterns
175175+ test_patterns = [
176176+ 'test_', '_test.', '.test.', 'spec.', '_spec.',
177177+ 'tests.', 'testing.', 'mock.', '_mock.'
178178+ ]
179179+ # Common test directories
180180+ test_dirs = ['test', 'tests', '__tests__', 'spec', 'specs', 'testing']
181181+182182+ for pattern in test_patterns:
183183+ if pattern in basename:
184184+ return True
185185+186186+ for test_dir in test_dirs:
187187+ if test_dir in dirname.split(os.sep):
188188+ return True
189189+190190+ return False
191191+192192+193193+def find_test_files(file_path, project_root):
194194+ """Find test files related to source file."""
195195+ if not project_root:
196196+ return []
197197+198198+ ext = os.path.splitext(file_path)[1]
199199+ basename = os.path.basename(file_path)
200200+ name_without_ext = os.path.splitext(basename)[0]
201201+202202+ # Patterns to look for
203203+ test_patterns = []
204204+205205+ if ext == '.rs':
206206+ # Rust: look for mod tests in same file, or tests/ directory
207207+ test_patterns = [
208208+ os.path.join(project_root, 'tests', '**', f'*{name_without_ext}*'),
209209+ os.path.join(project_root, '**', 'tests', f'*{name_without_ext}*'),
210210+ ]
211211+ elif ext == '.py':
212212+ test_patterns = [
213213+ os.path.join(project_root, '**', f'test_{name_without_ext}.py'),
214214+ os.path.join(project_root, '**', f'{name_without_ext}_test.py'),
215215+ os.path.join(project_root, 'tests', '**', f'*{name_without_ext}*.py'),
216216+ ]
217217+ elif ext in ('.js', '.ts', '.tsx', '.jsx'):
218218+ base = name_without_ext.replace('.test', '').replace('.spec', '')
219219+ test_patterns = [
220220+ os.path.join(project_root, '**', f'{base}.test{ext}'),
221221+ os.path.join(project_root, '**', f'{base}.spec{ext}'),
222222+ os.path.join(project_root, '**', '__tests__', f'{base}*'),
223223+ ]
224224+ elif ext == '.go':
225225+ test_patterns = [
226226+ os.path.join(os.path.dirname(file_path), f'{name_without_ext}_test.go'),
227227+ ]
228228+229229+ found = []
230230+ for pattern in test_patterns:
231231+ found.extend(glob.glob(pattern, recursive=True))
232232+233233+ return list(set(found))[:5] # Limit to 5
234234+235235+236236+def get_test_reminder(file_path, project_root):
237237+ """Check if tests should be run and return reminder message."""
238238+ if is_test_file(file_path):
239239+ return None # Editing a test file, no reminder needed
240240+241241+ ext = os.path.splitext(file_path)[1]
242242+ code_extensions = ('.rs', '.py', '.js', '.ts', '.tsx', '.jsx', '.go')
243243+244244+ if ext not in code_extensions:
245245+ return None
246246+247247+ # Check for marker file
248248+ marker_dir = project_root or os.path.dirname(file_path)
249249+ marker_file = os.path.join(marker_dir, '.chainlink', 'last_test_run')
250250+251251+ code_modified_after_tests = False
252252+253253+ if os.path.exists(marker_file):
254254+ try:
255255+ marker_mtime = os.path.getmtime(marker_file)
256256+ file_mtime = os.path.getmtime(file_path)
257257+ code_modified_after_tests = file_mtime > marker_mtime
258258+ except OSError:
259259+ code_modified_after_tests = True
260260+ else:
261261+ # No marker = tests haven't been run
262262+ code_modified_after_tests = True
263263+264264+ if not code_modified_after_tests:
265265+ return None
266266+267267+ # Find test files
268268+ test_files = find_test_files(file_path, project_root)
269269+270270+ # Generate test command based on project type
271271+ test_cmd = None
272272+ if ext == '.rs' and project_root:
273273+ if os.path.exists(os.path.join(project_root, 'Cargo.toml')):
274274+ test_cmd = 'cargo test'
275275+ elif ext == '.py':
276276+ if project_root and os.path.exists(os.path.join(project_root, 'pytest.ini')):
277277+ test_cmd = 'pytest'
278278+ elif project_root and os.path.exists(os.path.join(project_root, 'setup.py')):
279279+ test_cmd = 'python -m pytest'
280280+ elif ext in ('.js', '.ts', '.tsx', '.jsx') and project_root:
281281+ if os.path.exists(os.path.join(project_root, 'package.json')):
282282+ test_cmd = 'npm test'
283283+ elif ext == '.go' and project_root:
284284+ test_cmd = 'go test ./...'
285285+286286+ if test_files or test_cmd:
287287+ msg = "🧪 TEST REMINDER: Code modified since last test run."
288288+ if test_cmd:
289289+ msg += f"\n Run: {test_cmd}"
290290+ if test_files:
291291+ msg += f"\n Related tests: {', '.join(os.path.basename(t) for t in test_files[:3])}"
292292+ return msg
293293+294294+ return None
295295+296296+297297+def main():
298298+ try:
299299+ input_data = json.load(sys.stdin)
300300+ except (json.JSONDecodeError, Exception):
301301+ sys.exit(0)
302302+303303+ tool_name = input_data.get("tool_name", "")
304304+ tool_input = input_data.get("tool_input", {})
305305+306306+ if tool_name not in ("Write", "Edit"):
307307+ sys.exit(0)
308308+309309+ file_path = tool_input.get("file_path", "")
310310+311311+ code_extensions = (
312312+ '.rs', '.py', '.js', '.ts', '.tsx', '.jsx', '.go', '.java',
313313+ '.c', '.cpp', '.h', '.hpp', '.cs', '.rb', '.php', '.swift',
314314+ '.kt', '.scala', '.zig', '.odin'
315315+ )
316316+317317+ if not any(file_path.endswith(ext) for ext in code_extensions):
318318+ sys.exit(0)
319319+320320+ if '.claude' in file_path and 'hooks' in file_path:
321321+ sys.exit(0)
322322+323323+ # Find project root for linter and test detection
324324+ project_root = find_project_root(file_path, [
325325+ 'Cargo.toml', 'package.json', 'go.mod', 'setup.py',
326326+ 'pyproject.toml', '.git'
327327+ ])
328328+329329+ # Check for stubs
330330+ stub_findings = check_for_stubs(file_path)
331331+332332+ # Run linter
333333+ linter_errors = run_linter(file_path)
334334+335335+ # Check for test reminder
336336+ test_reminder = get_test_reminder(file_path, project_root)
337337+338338+ # Build output
339339+ messages = []
340340+341341+ if stub_findings:
342342+ stub_list = "\n".join([f" Line {ln}: {desc} - `{content}`" for ln, desc, content in stub_findings[:5]])
343343+ if len(stub_findings) > 5:
344344+ stub_list += f"\n ... and {len(stub_findings) - 5} more"
345345+ messages.append(f"""⚠️ STUB PATTERNS DETECTED in {file_path}:
346346+{stub_list}
347347+348348+Fix these NOW - replace with real implementation.""")
349349+350350+ if linter_errors:
351351+ error_list = "\n".join([f" {e}" for e in linter_errors[:10]])
352352+ if len(linter_errors) > 10:
353353+ error_list += f"\n ... and more"
354354+ messages.append(f"""🔍 LINTER ISSUES:
355355+{error_list}""")
356356+357357+ if test_reminder:
358358+ messages.append(test_reminder)
359359+360360+ if messages:
361361+ output = {
362362+ "hookSpecificOutput": {
363363+ "hookEventName": "PostToolUse",
364364+ "additionalContext": "\n\n".join(messages)
365365+ }
366366+ }
367367+ else:
368368+ output = {
369369+ "hookSpecificOutput": {
370370+ "hookEventName": "PostToolUse",
371371+ "additionalContext": f"✓ {os.path.basename(file_path)} - no issues detected"
372372+ }
373373+ }
374374+375375+ print(json.dumps(output))
376376+ sys.exit(0)
377377+378378+379379+if __name__ == "__main__":
380380+ main()
+513
.claude/hooks/prompt-guard.py
···11+#!/usr/bin/env python3
22+"""
33+Chainlink behavioral hook for Claude Code.
44+Injects best practice reminders on every prompt submission.
55+Loads rules from .chainlink/rules/ markdown files.
66+"""
77+88+import json
99+import sys
1010+import os
1111+import io
1212+import subprocess
1313+import hashlib
1414+from datetime import datetime
1515+1616+# Fix Windows encoding issues with Unicode characters
1717+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
1818+1919+2020+def find_chainlink_dir():
2121+ """Find the .chainlink directory by walking up from cwd."""
2222+ current = os.getcwd()
2323+ for _ in range(10):
2424+ candidate = os.path.join(current, '.chainlink')
2525+ if os.path.isdir(candidate):
2626+ return candidate
2727+ parent = os.path.dirname(current)
2828+ if parent == current:
2929+ break
3030+ current = parent
3131+ return None
3232+3333+3434+def load_rule_file(rules_dir, filename):
3535+ """Load a rule file and return its content, or empty string if not found."""
3636+ if not rules_dir:
3737+ return ""
3838+ path = os.path.join(rules_dir, filename)
3939+ try:
4040+ with open(path, 'r', encoding='utf-8') as f:
4141+ return f.read().strip()
4242+ except (OSError, IOError):
4343+ return ""
4444+4545+4646+def load_all_rules(chainlink_dir):
4747+ """Load all rule files from .chainlink/rules/."""
4848+ if not chainlink_dir:
4949+ return {}, "", ""
5050+5151+ rules_dir = os.path.join(chainlink_dir, 'rules')
5252+ if not os.path.isdir(rules_dir):
5353+ return {}, "", ""
5454+5555+ # Load global rules
5656+ global_rules = load_rule_file(rules_dir, 'global.md')
5757+5858+ # Load project rules
5959+ project_rules = load_rule_file(rules_dir, 'project.md')
6060+6161+ # Load language-specific rules
6262+ language_rules = {}
6363+ language_files = [
6464+ ('rust.md', 'Rust'),
6565+ ('python.md', 'Python'),
6666+ ('javascript.md', 'JavaScript'),
6767+ ('typescript.md', 'TypeScript'),
6868+ ('typescript-react.md', 'TypeScript/React'),
6969+ ('javascript-react.md', 'JavaScript/React'),
7070+ ('go.md', 'Go'),
7171+ ('java.md', 'Java'),
7272+ ('c.md', 'C'),
7373+ ('cpp.md', 'C++'),
7474+ ('csharp.md', 'C#'),
7575+ ('ruby.md', 'Ruby'),
7676+ ('php.md', 'PHP'),
7777+ ('swift.md', 'Swift'),
7878+ ('kotlin.md', 'Kotlin'),
7979+ ('scala.md', 'Scala'),
8080+ ('zig.md', 'Zig'),
8181+ ('odin.md', 'Odin'),
8282+ ]
8383+8484+ for filename, lang_name in language_files:
8585+ content = load_rule_file(rules_dir, filename)
8686+ if content:
8787+ language_rules[lang_name] = content
8888+8989+ return language_rules, global_rules, project_rules
9090+9191+9292+# Detect language from common file extensions in the working directory
9393+def detect_languages():
9494+ """Scan for common source files to determine active languages."""
9595+ extensions = {
9696+ '.rs': 'Rust',
9797+ '.py': 'Python',
9898+ '.js': 'JavaScript',
9999+ '.ts': 'TypeScript',
100100+ '.tsx': 'TypeScript/React',
101101+ '.jsx': 'JavaScript/React',
102102+ '.go': 'Go',
103103+ '.java': 'Java',
104104+ '.c': 'C',
105105+ '.cpp': 'C++',
106106+ '.cs': 'C#',
107107+ '.rb': 'Ruby',
108108+ '.php': 'PHP',
109109+ '.swift': 'Swift',
110110+ '.kt': 'Kotlin',
111111+ '.scala': 'Scala',
112112+ '.zig': 'Zig',
113113+ '.odin': 'Odin',
114114+ }
115115+116116+ found = set()
117117+ cwd = os.getcwd()
118118+119119+ # Check for project config files first (more reliable than scanning)
120120+ config_indicators = {
121121+ 'Cargo.toml': 'Rust',
122122+ 'package.json': 'JavaScript',
123123+ 'tsconfig.json': 'TypeScript',
124124+ 'pyproject.toml': 'Python',
125125+ 'requirements.txt': 'Python',
126126+ 'go.mod': 'Go',
127127+ 'pom.xml': 'Java',
128128+ 'build.gradle': 'Java',
129129+ 'Gemfile': 'Ruby',
130130+ 'composer.json': 'PHP',
131131+ 'Package.swift': 'Swift',
132132+ }
133133+134134+ # Check cwd and immediate subdirs for config files
135135+ check_dirs = [cwd]
136136+ try:
137137+ for entry in os.listdir(cwd):
138138+ subdir = os.path.join(cwd, entry)
139139+ if os.path.isdir(subdir) and not entry.startswith('.'):
140140+ check_dirs.append(subdir)
141141+ except (PermissionError, OSError):
142142+ pass
143143+144144+ for check_dir in check_dirs:
145145+ for config_file, lang in config_indicators.items():
146146+ if os.path.exists(os.path.join(check_dir, config_file)):
147147+ found.add(lang)
148148+149149+ # Also scan for source files in src/ directories
150150+ scan_dirs = [cwd]
151151+ src_dir = os.path.join(cwd, 'src')
152152+ if os.path.isdir(src_dir):
153153+ scan_dirs.append(src_dir)
154154+ # Check nested project src dirs too
155155+ for check_dir in check_dirs:
156156+ nested_src = os.path.join(check_dir, 'src')
157157+ if os.path.isdir(nested_src):
158158+ scan_dirs.append(nested_src)
159159+160160+ for scan_dir in scan_dirs:
161161+ try:
162162+ for entry in os.listdir(scan_dir):
163163+ ext = os.path.splitext(entry)[1].lower()
164164+ if ext in extensions:
165165+ found.add(extensions[ext])
166166+ except (PermissionError, OSError):
167167+ pass
168168+169169+ return list(found) if found else ['the project']
170170+171171+172172+def get_language_section(languages, language_rules):
173173+ """Build language-specific best practices section from loaded rules."""
174174+ sections = []
175175+ for lang in languages:
176176+ if lang in language_rules:
177177+ content = language_rules[lang]
178178+ # If the file doesn't start with a header, add one
179179+ if not content.startswith('#'):
180180+ sections.append(f"### {lang} Best Practices\n{content}")
181181+ else:
182182+ sections.append(content)
183183+184184+ if not sections:
185185+ return ""
186186+187187+ return "\n\n".join(sections)
188188+189189+190190+# Directories to skip when building project tree
191191+SKIP_DIRS = {
192192+ '.git', 'node_modules', 'target', 'venv', '.venv', 'env', '.env',
193193+ '__pycache__', '.chainlink', '.claude', 'dist', 'build', '.next',
194194+ '.nuxt', 'vendor', '.idea', '.vscode', 'coverage', '.pytest_cache',
195195+ '.mypy_cache', '.tox', 'eggs', '*.egg-info', '.sass-cache'
196196+}
197197+198198+199199+def get_project_tree(max_depth=3, max_entries=50):
200200+ """Generate a compact project tree to prevent path hallucinations."""
201201+ cwd = os.getcwd()
202202+ entries = []
203203+204204+ def should_skip(name):
205205+ if name.startswith('.') and name not in ('.github', '.claude'):
206206+ return True
207207+ return name in SKIP_DIRS or name.endswith('.egg-info')
208208+209209+ def walk_dir(path, prefix="", depth=0):
210210+ if depth > max_depth or len(entries) >= max_entries:
211211+ return
212212+213213+ try:
214214+ items = sorted(os.listdir(path))
215215+ except (PermissionError, OSError):
216216+ return
217217+218218+ # Separate dirs and files
219219+ dirs = [i for i in items if os.path.isdir(os.path.join(path, i)) and not should_skip(i)]
220220+ files = [i for i in items if os.path.isfile(os.path.join(path, i)) and not i.startswith('.')]
221221+222222+ # Add files first (limit per directory)
223223+ for f in files[:10]: # Max 10 files per dir shown
224224+ if len(entries) >= max_entries:
225225+ return
226226+ entries.append(f"{prefix}{f}")
227227+228228+ if len(files) > 10:
229229+ entries.append(f"{prefix}... ({len(files) - 10} more files)")
230230+231231+ # Then recurse into directories
232232+ for d in dirs:
233233+ if len(entries) >= max_entries:
234234+ return
235235+ entries.append(f"{prefix}{d}/")
236236+ walk_dir(os.path.join(path, d), prefix + " ", depth + 1)
237237+238238+ walk_dir(cwd)
239239+240240+ if not entries:
241241+ return ""
242242+243243+ if len(entries) >= max_entries:
244244+ entries.append(f"... (tree truncated at {max_entries} entries)")
245245+246246+ return "\n".join(entries)
247247+248248+249249+# Cache directory for dependency snapshots
250250+CACHE_DIR = os.path.join(os.getcwd(), '.chainlink', '.cache')
251251+252252+253253+def get_lock_file_hash(lock_path):
254254+ """Get a hash of the lock file for cache invalidation."""
255255+ try:
256256+ mtime = os.path.getmtime(lock_path)
257257+ return hashlib.md5(f"{lock_path}:{mtime}".encode()).hexdigest()[:12]
258258+ except OSError:
259259+ return None
260260+261261+262262+def run_command(cmd, timeout=5):
263263+ """Run a command and return output, or None on failure."""
264264+ try:
265265+ result = subprocess.run(
266266+ cmd,
267267+ capture_output=True,
268268+ text=True,
269269+ timeout=timeout,
270270+ shell=True
271271+ )
272272+ if result.returncode == 0:
273273+ return result.stdout.strip()
274274+ except (subprocess.TimeoutExpired, OSError, Exception):
275275+ pass
276276+ return None
277277+278278+279279+def get_dependencies(max_deps=30):
280280+ """Get installed dependencies with versions. Uses caching based on lock file mtime."""
281281+ cwd = os.getcwd()
282282+ deps = []
283283+284284+ # Check for Rust (Cargo.toml)
285285+ cargo_toml = os.path.join(cwd, 'Cargo.toml')
286286+ if os.path.exists(cargo_toml):
287287+ # Parse Cargo.toml for direct dependencies (faster than cargo tree)
288288+ try:
289289+ with open(cargo_toml, 'r') as f:
290290+ content = f.read()
291291+ in_deps = False
292292+ for line in content.split('\n'):
293293+ if line.strip().startswith('[dependencies]'):
294294+ in_deps = True
295295+ continue
296296+ if line.strip().startswith('[') and in_deps:
297297+ break
298298+ if in_deps and '=' in line and not line.strip().startswith('#'):
299299+ parts = line.split('=', 1)
300300+ name = parts[0].strip()
301301+ rest = parts[1].strip() if len(parts) > 1 else ''
302302+ if rest.startswith('{'):
303303+ # Handle { version = "x.y", features = [...] } format
304304+ import re
305305+ match = re.search(r'version\s*=\s*"([^"]+)"', rest)
306306+ if match:
307307+ deps.append(f" {name} = \"{match.group(1)}\"")
308308+ elif rest.startswith('"') or rest.startswith("'"):
309309+ version = rest.strip('"').strip("'")
310310+ deps.append(f" {name} = \"{version}\"")
311311+ if len(deps) >= max_deps:
312312+ break
313313+ except (OSError, Exception):
314314+ pass
315315+ if deps:
316316+ return "Rust (Cargo.toml):\n" + "\n".join(deps[:max_deps])
317317+318318+ # Check for Node.js (package.json)
319319+ package_json = os.path.join(cwd, 'package.json')
320320+ if os.path.exists(package_json):
321321+ try:
322322+ with open(package_json, 'r') as f:
323323+ pkg = json.load(f)
324324+ for dep_type in ['dependencies', 'devDependencies']:
325325+ if dep_type in pkg:
326326+ for name, version in list(pkg[dep_type].items())[:max_deps]:
327327+ deps.append(f" {name}: {version}")
328328+ if len(deps) >= max_deps:
329329+ break
330330+ except (OSError, json.JSONDecodeError, Exception):
331331+ pass
332332+ if deps:
333333+ return "Node.js (package.json):\n" + "\n".join(deps[:max_deps])
334334+335335+ # Check for Python (requirements.txt or pyproject.toml)
336336+ requirements = os.path.join(cwd, 'requirements.txt')
337337+ if os.path.exists(requirements):
338338+ try:
339339+ with open(requirements, 'r') as f:
340340+ for line in f:
341341+ line = line.strip()
342342+ if line and not line.startswith('#') and not line.startswith('-'):
343343+ deps.append(f" {line}")
344344+ if len(deps) >= max_deps:
345345+ break
346346+ except (OSError, Exception):
347347+ pass
348348+ if deps:
349349+ return "Python (requirements.txt):\n" + "\n".join(deps[:max_deps])
350350+351351+ # Check for Go (go.mod)
352352+ go_mod = os.path.join(cwd, 'go.mod')
353353+ if os.path.exists(go_mod):
354354+ try:
355355+ with open(go_mod, 'r') as f:
356356+ in_require = False
357357+ for line in f:
358358+ line = line.strip()
359359+ if line.startswith('require ('):
360360+ in_require = True
361361+ continue
362362+ if line == ')' and in_require:
363363+ break
364364+ if in_require and line:
365365+ deps.append(f" {line}")
366366+ if len(deps) >= max_deps:
367367+ break
368368+ except (OSError, Exception):
369369+ pass
370370+ if deps:
371371+ return "Go (go.mod):\n" + "\n".join(deps[:max_deps])
372372+373373+ return ""
374374+375375+376376+def build_reminder(languages, project_tree, dependencies, language_rules, global_rules, project_rules):
377377+ """Build the full reminder context."""
378378+ lang_section = get_language_section(languages, language_rules)
379379+ lang_list = ", ".join(languages) if languages else "this project"
380380+ current_year = datetime.now().year
381381+382382+ # Build tree section if available
383383+ tree_section = ""
384384+ if project_tree:
385385+ tree_section = f"""
386386+### Project Structure (use these exact paths)
387387+```
388388+{project_tree}
389389+```
390390+"""
391391+392392+ # Build dependencies section if available
393393+ deps_section = ""
394394+ if dependencies:
395395+ deps_section = f"""
396396+### Installed Dependencies (use these exact versions)
397397+```
398398+{dependencies}
399399+```
400400+"""
401401+402402+ # Build global rules section (from .chainlink/rules/global.md)
403403+ global_section = ""
404404+ if global_rules:
405405+ global_section = f"\n{global_rules}\n"
406406+ else:
407407+ # Fallback to hardcoded defaults if no rules file
408408+ global_section = f"""
409409+### Pre-Coding Grounding (PREVENT HALLUCINATIONS)
410410+Before writing code that uses external libraries, APIs, or unfamiliar patterns:
411411+1. **VERIFY IT EXISTS**: Use WebSearch to confirm the crate/package/module exists and check its actual API
412412+2. **CHECK THE DOCS**: Fetch documentation to see real function signatures, not imagined ones
413413+3. **CONFIRM SYNTAX**: If unsure about language features or library usage, search first
414414+4. **USE LATEST VERSIONS**: Always check for and use the latest stable version of dependencies (security + features)
415415+5. **NO GUESSING**: If you can't verify it, tell the user you need to research it
416416+417417+Examples of when to search:
418418+- Using a crate/package you haven't used recently → search "[package] [language] docs {current_year}"
419419+- Uncertain about function parameters → search for actual API reference
420420+- New language feature or syntax → verify it exists in the version being used
421421+- System calls or platform-specific code → confirm the correct API
422422+- Adding a dependency → search "[package] latest version {current_year}" to get current release
423423+424424+### General Requirements
425425+1. **NO STUBS - ABSOLUTE RULE**:
426426+ - NEVER write `TODO`, `FIXME`, `pass`, `...`, `unimplemented!()` as implementation
427427+ - NEVER write empty function bodies or placeholder returns
428428+ - NEVER say "implement later" or "add logic here"
429429+ - If logic is genuinely too complex for one turn, use `raise NotImplementedError("Descriptive reason: what needs to be done")` and create a chainlink issue
430430+ - The PostToolUse hook WILL detect and flag stub patterns - write real code the first time
431431+2. **NO DEAD CODE**: Discover if dead code is truly dead or if it's an incomplete feature. If incomplete, complete it. If truly dead, remove it.
432432+3. **FULL FEATURES**: Implement the complete feature as requested. Don't stop partway or suggest "you could add X later."
433433+4. **ERROR HANDLING**: Proper error handling everywhere. No panics/crashes on bad input.
434434+5. **SECURITY**: Validate input, use parameterized queries, no command injection, no hardcoded secrets.
435435+6. **READ BEFORE WRITE**: Always read a file before editing it. Never guess at contents.
436436+437437+### Conciseness Protocol
438438+Minimize chattiness. Your output should be:
439439+- **Code blocks** with implementation
440440+- **Tool calls** to accomplish tasks
441441+- **Brief explanations** only when the code isn't self-explanatory
442442+443443+NEVER output:
444444+- "Here is the code" / "Here's how to do it" (just show the code)
445445+- "Let me know if you need anything else" / "Feel free to ask"
446446+- "I'll now..." / "Let me..." (just do it)
447447+- Restating what the user asked
448448+- Explaining obvious code
449449+- Multiple paragraphs when one sentence suffices
450450+451451+When writing code: write it. When making changes: make them. Skip the narration.
452452+453453+### Large File Management (500+ lines)
454454+If you need to write or modify code that will exceed 500 lines:
455455+1. Create a parent issue for the overall feature: `chainlink create "<feature name>" -p high`
456456+2. Break down into subissues: `chainlink subissue <parent_id> "<component 1>"`, etc.
457457+3. Inform the user: "This implementation will require multiple files/components. I've created issue #X with Y subissues to track progress."
458458+4. Work on one subissue at a time, marking each complete before moving on.
459459+460460+### Context Window Management
461461+If the conversation is getting long OR the task requires many more steps:
462462+1. Create a chainlink issue to track remaining work: `chainlink create "Continue: <task summary>" -p high`
463463+2. Add detailed notes as a comment: `chainlink comment <id> "<what's done, what's next>"`
464464+3. Inform the user: "This task will require additional turns. I've created issue #X to track progress."
465465+466466+Use `chainlink session work <id>` to mark what you're working on.
467467+"""
468468+469469+ # Build project rules section (from .chainlink/rules/project.md)
470470+ project_section = ""
471471+ if project_rules:
472472+ project_section = f"\n### Project-Specific Rules\n{project_rules}\n"
473473+474474+ reminder = f"""<chainlink-behavioral-guard>
475475+## Code Quality Requirements
476476+477477+You are working on a {lang_list} project. Follow these requirements strictly:
478478+{tree_section}{deps_section}{global_section}{lang_section}{project_section}
479479+</chainlink-behavioral-guard>"""
480480+481481+ return reminder
482482+483483+484484+def main():
485485+ try:
486486+ # Read input from stdin (Claude Code passes prompt info)
487487+ input_data = json.load(sys.stdin)
488488+ except json.JSONDecodeError:
489489+ # If no valid JSON, still inject reminder
490490+ pass
491491+ except Exception:
492492+ pass
493493+494494+ # Find chainlink directory and load rules
495495+ chainlink_dir = find_chainlink_dir()
496496+ language_rules, global_rules, project_rules = load_all_rules(chainlink_dir)
497497+498498+ # Detect languages in the project
499499+ languages = detect_languages()
500500+501501+ # Generate project tree to prevent path hallucinations
502502+ project_tree = get_project_tree()
503503+504504+ # Get installed dependencies to prevent version hallucinations
505505+ dependencies = get_dependencies()
506506+507507+ # Output the reminder as plain text (gets injected as context)
508508+ print(build_reminder(languages, project_tree, dependencies, language_rules, global_rules, project_rules))
509509+ sys.exit(0)
510510+511511+512512+if __name__ == "__main__":
513513+ main()
+78
.claude/hooks/session-start.py
···11+#!/usr/bin/env python3
22+"""
33+Session start hook that loads chainlink context and reminds about session workflow.
44+"""
55+66+import json
77+import subprocess
88+import sys
99+import os
1010+1111+1212+def run_chainlink(args):
1313+ """Run a chainlink command and return output."""
1414+ try:
1515+ result = subprocess.run(
1616+ ["chainlink"] + args,
1717+ capture_output=True,
1818+ text=True,
1919+ timeout=5
2020+ )
2121+ return result.stdout.strip() if result.returncode == 0 else None
2222+ except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
2323+ return None
2424+2525+2626+def check_chainlink_initialized():
2727+ """Check if .chainlink directory exists."""
2828+ cwd = os.getcwd()
2929+ current = cwd
3030+3131+ while True:
3232+ candidate = os.path.join(current, ".chainlink")
3333+ if os.path.isdir(candidate):
3434+ return True
3535+ parent = os.path.dirname(current)
3636+ if parent == current:
3737+ break
3838+ current = parent
3939+4040+ return False
4141+4242+4343+def main():
4444+ if not check_chainlink_initialized():
4545+ # No chainlink repo, skip
4646+ sys.exit(0)
4747+4848+ context_parts = ["<chainlink-session-context>"]
4949+5050+ # Try to get session status
5151+ session_status = run_chainlink(["session", "status"])
5252+ if session_status:
5353+ context_parts.append(f"## Current Session\n{session_status}")
5454+5555+ # Get ready issues (unblocked work)
5656+ ready_issues = run_chainlink(["ready"])
5757+ if ready_issues:
5858+ context_parts.append(f"## Ready Issues (unblocked)\n{ready_issues}")
5959+6060+ # Get open issues summary
6161+ open_issues = run_chainlink(["list", "-s", "open"])
6262+ if open_issues:
6363+ context_parts.append(f"## Open Issues\n{open_issues}")
6464+6565+ context_parts.append("""
6666+## Chainlink Workflow Reminder
6767+- Use `chainlink session start` at the beginning of work
6868+- Use `chainlink session work <id>` to mark current focus
6969+- Add comments as you discover things: `chainlink comment <id> "..."`
7070+- End with handoff notes: `chainlink session end --notes "..."`
7171+</chainlink-session-context>""")
7272+7373+ print("\n\n".join(context_parts))
7474+ sys.exit(0)
7575+7676+7777+if __name__ == "__main__":
7878+ main()