Compare commits

..

15 Commits

Author SHA1 Message Date
ecc-tools[bot]
2f3ff051ca feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill.md) 2026-04-02 18:09:48 +00:00
ecc-tools[bot]
3a55f1ef50 feat: add everything-claude-code ECC bundle (.claude/commands/refactoring.md) 2026-04-02 18:09:48 +00:00
ecc-tools[bot]
956e6d51d2 feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md) 2026-04-02 18:09:47 +00:00
ecc-tools[bot]
374a1c448f feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md) 2026-04-02 18:09:46 +00:00
ecc-tools[bot]
db3ea82643 feat: add everything-claude-code ECC bundle (.claude/team/everything-claude-code-team-config.json) 2026-04-02 18:09:45 +00:00
ecc-tools[bot]
466031da42 feat: add everything-claude-code ECC bundle (.claude/research/everything-claude-code-research-playbook.md) 2026-04-02 18:09:44 +00:00
ecc-tools[bot]
1f9d4cca7c feat: add everything-claude-code ECC bundle (.claude/rules/everything-claude-code-guardrails.md) 2026-04-02 18:09:43 +00:00
ecc-tools[bot]
74602e9ff5 feat: add everything-claude-code ECC bundle (.codex/agents/docs-researcher.toml) 2026-04-02 18:09:42 +00:00
ecc-tools[bot]
6a8eb7aeb1 feat: add everything-claude-code ECC bundle (.codex/agents/reviewer.toml) 2026-04-02 18:09:41 +00:00
ecc-tools[bot]
1dae9edb58 feat: add everything-claude-code ECC bundle (.codex/agents/explorer.toml) 2026-04-02 18:09:40 +00:00
ecc-tools[bot]
81569ab994 feat: add everything-claude-code ECC bundle (.claude/identity.json) 2026-04-02 18:09:39 +00:00
ecc-tools[bot]
8a8f159153 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/agents/openai.yaml) 2026-04-02 18:09:38 +00:00
ecc-tools[bot]
a083aa5811 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/SKILL.md) 2026-04-02 18:09:38 +00:00
ecc-tools[bot]
6f7bfd72ad feat: add everything-claude-code ECC bundle (.claude/skills/everything-claude-code/SKILL.md) 2026-04-02 18:09:37 +00:00
ecc-tools[bot]
c104217cab feat: add everything-claude-code ECC bundle (.claude/ecc-tools.json) 2026-04-02 18:09:36 +00:00
77 changed files with 580 additions and 5651 deletions

View File

@@ -23,23 +23,50 @@ Write long-form content that sounds like an actual person with a point of view,
4. Use proof instead of adjectives. 4. Use proof instead of adjectives.
5. Never invent facts, credibility, or customer evidence. 5. Never invent facts, credibility, or customer evidence.
## Voice Handling ## Voice Capture Workflow
If the user wants a specific voice, run `brand-voice` first and reuse its `VOICE PROFILE`. If the user wants a specific voice, collect one or more of:
Do not duplicate a second style-analysis pass here unless the user explicitly asks for one. - published articles
- newsletters
- X posts or threads
- docs or memos
- launch notes
- a style guide
Then extract:
- sentence length and rhythm
- whether the writing is compressed, explanatory, sharp, or formal
- how parentheses are used
- how often the writer asks questions
- whether the writer uses fragments, lists, or hard pivots
- formatting habits such as headers, bullets, code blocks, pull quotes
- what the writer clearly avoids
If no voice references are given, default to a sharp operator voice: concrete, unsentimental, useful. If no voice references are given, default to a sharp operator voice: concrete, unsentimental, useful.
## Affaan / ECC Voice Reference
When matching Affaan / ECC voice, bias toward:
- direct claims over scene-setting
- high specificity
- parentheticals used for qualification or over-clarification, not comedy
- capitalization chosen situationally, not as a gimmick
- very low tolerance for fake thought-leadership cadence
- almost no bait questions
## Banned Patterns ## Banned Patterns
Delete and rewrite any of these: Delete and rewrite any of these:
- "In today's rapidly evolving landscape" - "In today's rapidly evolving landscape"
- "game-changer", "cutting-edge", "revolutionary" - "game-changer", "cutting-edge", "revolutionary"
- "no fluff"
- "not X, just Y"
- "here's why this matters" as a standalone bridge - "here's why this matters" as a standalone bridge
- fake vulnerability arcs - fake vulnerability arcs
- a closing question added only to juice engagement - a closing question added only to juice engagement
- forced lowercase
- corny parenthetical asides
- biography padding that does not move the argument - biography padding that does not move the argument
- generic AI throat-clearing that delays the point
## Writing Process ## Writing Process
@@ -74,6 +101,6 @@ Delete and rewrite any of these:
Before delivering: Before delivering:
- factual claims are backed by provided sources - factual claims are backed by provided sources
- generic AI transitions are gone - generic AI transitions are gone
- the voice matches the supplied examples or the agreed `VOICE PROFILE` - the voice matches the supplied examples
- every section adds something new - every section adds something new
- formatting matches the intended medium - formatting matches the intended medium

View File

@@ -38,28 +38,50 @@ Before drafting, identify the source set:
If the user wants a specific voice, build a voice profile from real examples before writing. If the user wants a specific voice, build a voice profile from real examples before writing.
Use `brand-voice` as the canonical workflow when voice consistency matters across more than one output. Use `brand-voice` as the canonical workflow when voice consistency matters across more than one output.
## Voice Handling ## Voice Capture Workflow
`brand-voice` is the canonical voice layer. Run `brand-voice` first when:
Run it first when:
- there are multiple downstream outputs - there are multiple downstream outputs
- the user explicitly cares about writing style - the user explicitly cares about writing style
- the content is launch, outreach, or reputation-sensitive - the content is launch, outreach, or reputation-sensitive
Reuse the resulting `VOICE PROFILE` here instead of rebuilding a second voice model. At minimum, produce a compact `VOICE PROFILE` covering:
If the user wants Affaan / ECC voice specifically, still treat `brand-voice` as the source of truth and feed it the best live or source-derived material available. - rhythm
- compression
- capitalization
- parenthetical use
- question use
- preferred moves
- banned moves
Do not start drafting until the voice profile is clear enough to enforce.
## Affaan / ECC Voice Reference
When the user wants Affaan / ECC voice specifically, default to this unless newer examples clearly override it:
- direct, compressed, concrete
- strong preference for specific claims, numbers, mechanisms, and receipts
- parentheticals used to qualify, narrow, or over-clarify, not to do corny bits
- lowercase is optional, not mandatory
- questions are rare and should not be added as bait
- transitions should feel earned, not polished
- tone can be sharp or blunt, but should not sound like a content marketer
## Hard Bans ## Hard Bans
Delete and rewrite any of these: Delete and rewrite any of these:
- "In today's rapidly evolving landscape" - "In today's rapidly evolving landscape"
- "game-changer", "revolutionary", "cutting-edge" - "game-changer", "revolutionary", "cutting-edge"
- "no fluff"
- "not X, just Y"
- "here's why this matters" unless it is followed immediately by something concrete - "here's why this matters" unless it is followed immediately by something concrete
- "Excited to share"
- fake curiosity gaps
- ending with a LinkedIn-style question just to farm replies - ending with a LinkedIn-style question just to farm replies
- forced lowercase when the source voice does not call for it
- forced casualness on LinkedIn - forced casualness on LinkedIn
- fake engagement padding that was not present in the source material - parenthetical jokes that were not present in the source voice
## Platform Adaptation Rules ## Platform Adaptation Rules

View File

@@ -39,8 +39,13 @@ Use `content-engine` first if the source still needs voice shaping.
Run `brand-voice` first if the source voice is not already captured in the current session. Run `brand-voice` first if the source voice is not already captured in the current session.
Reuse the resulting `VOICE PROFILE` directly. Before adapting, note:
Do not build a second ad hoc voice checklist here unless the user explicitly wants a fresh override for this campaign. - how blunt or explanatory the source is
- whether the source uses fragments, lists, or longer transitions
- whether the source uses parentheses
- whether the source avoids questions, hashtags, or CTA language
The adaptation should preserve that fingerprint.
### Step 3: Adapt by Platform Constraint ### Step 3: Adapt by Platform Constraint
@@ -86,6 +91,7 @@ Delete and rewrite any of these:
- "Here's what I learned" - "Here's what I learned"
- "What do you think?" - "What do you think?"
- "link in bio" unless that is literally true - "link in bio" unless that is literally true
- "not X, just Y"
- generic "professional takeaway" paragraphs that were not in the source - generic "professional takeaway" paragraphs that were not in the source
## Output Format ## Output Format

View File

@@ -1,442 +1,150 @@
--- ```markdown
name: everything-claude-code-conventions # everything-claude-code Development Patterns
description: Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
---
# Everything Claude Code Conventions > Auto-generated skill from repository analysis
> Generated from [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) on 2026-03-20
## Overview ## Overview
This skill teaches Claude the development patterns and conventions used in everything-claude-code. This skill provides a comprehensive guide to the development patterns, workflows, and coding conventions used in the `everything-claude-code` repository. It covers how to add or update skills, commands, install targets, hooks, and documentation, as well as the repository's JavaScript coding style and testing patterns. This guide is intended for contributors looking to maintain consistency and efficiency when working within this codebase.
## Tech Stack ## Coding Conventions
- **Primary Language**: JavaScript - **Language:** JavaScript (no framework detected)
- **Architecture**: hybrid module organization - **File Naming:** Use `camelCase` for JavaScript files and folders.
- **Test Location**: separate - Example: `mySkill.js`, `installTarget.js`
- **Import Style:** Use relative imports.
- Example:
```js
import myUtil from './utils/myUtil.js';
```
- **Export Style:** Mixed (both default and named exports are used).
- Example:
```js
// Named export
export function doSomething() { ... }
## When to Use This Skill // Default export
export default MyComponent;
```
- **Commit Messages:** Follow [Conventional Commits](https://www.conventionalcommits.org/) with prefixes like `fix`, `feat`, `docs`, `chore`.
- Example: `feat: add support for new install target`
- **Documentation:** Each skill or command should have a `SKILL.md` or markdown documentation file in its directory.
Activate this skill when: ## Workflows
- Making changes to this repository
- Adding new features following established patterns
- Writing tests that match project conventions
- Creating commits with proper message format
## Commit Conventions ### Add or Update a Skill
**Trigger:** When adding or updating a skill for agents or workflows
**Command:** `/add-skill`
Follow these commit message conventions based on 500 analyzed commits. 1. Create or update `SKILL.md` in `skills/[skill-name]/` or `.agents/skills/[skill-name]/`.
2. Optionally add or update related reference files (e.g., `references/*.md`, `assets/*.py`).
3. Register the skill in `manifests/install-modules.json` and/or `manifests/install-components.json`.
4. Update `AGENTS.md`, `README.md`, and `docs/zh-CN/AGENTS.md` as needed.
5. If the skill is agent-facing, update `.agents/skills/[skill-name]/SKILL.md`.
### Commit Style: Conventional Commits **Example:**
```json
### Prefixes Used // manifests/install-modules.json
{
- `fix` "skills": [
- `test` "myNewSkill"
- `feat` ]
- `docs`
### Message Guidelines
- Average message length: ~65 characters
- Keep first line concise and descriptive
- Use imperative mood ("Add feature" not "Added feature")
*Commit message example*
```text
feat(rules): add C# language support
```
*Commit message example*
```text
chore(deps-dev): bump flatted (#675)
```
*Commit message example*
```text
fix: auto-detect ECC root from plugin cache when CLAUDE_PLUGIN_ROOT is unset (#547) (#691)
```
*Commit message example*
```text
docs: add Antigravity setup and usage guide (#552)
```
*Commit message example*
```text
merge: PR #529 — feat(skills): add documentation-lookup, bun-runtime, nextjs-turbopack; feat(agents): add rust-reviewer
```
*Commit message example*
```text
Revert "Add Kiro IDE support (.kiro/) (#548)"
```
*Commit message example*
```text
Add Kiro IDE support (.kiro/) (#548)
```
*Commit message example*
```text
feat: add block-no-verify hook for Claude Code and Cursor (#649)
```
## Architecture
### Project Structure: Single Package
This project uses **hybrid** module organization.
### Configuration Files
- `.github/workflows/ci.yml`
- `.github/workflows/maintenance.yml`
- `.github/workflows/monthly-metrics.yml`
- `.github/workflows/release.yml`
- `.github/workflows/reusable-release.yml`
- `.github/workflows/reusable-test.yml`
- `.github/workflows/reusable-validate.yml`
- `.opencode/package.json`
- `.opencode/tsconfig.json`
- `.prettierrc`
- `eslint.config.js`
- `package.json`
### Guidelines
- This project uses a hybrid organization
- Follow existing patterns when adding new code
## Code Style
### Language: JavaScript
### Naming Conventions
| Element | Convention |
|---------|------------|
| Files | camelCase |
| Functions | camelCase |
| Classes | PascalCase |
| Constants | SCREAMING_SNAKE_CASE |
### Import Style: Relative Imports
### Export Style: Mixed Style
*Preferred import style*
```typescript
// Use relative imports
import { Button } from '../components/Button'
import { useAuth } from './hooks/useAuth'
```
## Testing
### Test Framework
No specific test framework detected — use the repository's existing test patterns.
### File Pattern: `*.test.js`
### Test Types
- **Unit tests**: Test individual functions and components in isolation
- **Integration tests**: Test interactions between multiple components/services
### Coverage
This project has coverage reporting configured. Aim for 80%+ coverage.
## Error Handling
### Error Handling Style: Try-Catch Blocks
*Standard error handling pattern*
```typescript
try {
const result = await riskyOperation()
return result
} catch (error) {
console.error('Operation failed:', error)
throw new Error('User-friendly message')
} }
``` ```
## Common Workflows ---
These workflows were detected from analyzing commit patterns. ### Add or Update a Command Workflow
**Trigger:** When introducing or refining a workflow command
**Command:** `/add-command`
### Database Migration 1. Create or update a command markdown file in `commands/` or `.claude/commands/`.
2. Iterate based on review feedback (fixes, improvements, removal of external links, etc.).
3. Update related documentation or references if needed.
Database schema changes with migration files **Example:**
```markdown
**Frequency**: ~2 times per month // commands/myCommand.md
# My Command
**Steps**: Description and usage...
1. Create migration file
2. Update schema definitions
3. Generate/update types
**Files typically involved**:
- `**/schema.*`
- `migrations/*`
**Example commit sequence**:
``` ```
feat: implement --with/--without selective install flags (#679)
fix: sync catalog counts with filesystem (27 agents, 113 skills, 58 commands) (#693)
feat(rules): add Rust language rules (rebased #660) (#686)
```
### Feature Development
Standard feature implementation workflow
**Frequency**: ~22 times per month
**Steps**:
1. Add feature implementation
2. Add tests for feature
3. Update documentation
**Files typically involved**:
- `manifests/*`
- `schemas/*`
- `**/*.test.*`
- `**/api/**`
**Example commit sequence**:
```
feat(skills): add documentation-lookup, bun-runtime, nextjs-turbopack; feat(agents): add rust-reviewer
docs(skills): align documentation-lookup with CONTRIBUTING template; add cross-harness (Codex/Cursor) skill copies
fix: address PR review — skill template (When to use, How it works, Examples), bun.lock, next build note, rust-reviewer CI note, doc-lookup privacy/uncertainty
```
### Add Language Rules
Adds a new programming language to the rules system, including coding style, hooks, patterns, security, and testing guidelines.
**Frequency**: ~2 times per month
**Steps**:
1. Create a new directory under rules/{language}/
2. Add coding-style.md, hooks.md, patterns.md, security.md, and testing.md files with language-specific content
3. Optionally reference or link to related skills
**Files typically involved**:
- `rules/*/coding-style.md`
- `rules/*/hooks.md`
- `rules/*/patterns.md`
- `rules/*/security.md`
- `rules/*/testing.md`
**Example commit sequence**:
```
Create a new directory under rules/{language}/
Add coding-style.md, hooks.md, patterns.md, security.md, and testing.md files with language-specific content
Optionally reference or link to related skills
```
### Add New Skill
Adds a new skill to the system, documenting its workflow, triggers, and usage, often with supporting scripts.
**Frequency**: ~4 times per month
**Steps**:
1. Create a new directory under skills/{skill-name}/
2. Add SKILL.md with documentation (When to Use, How It Works, Examples, etc.)
3. Optionally add scripts or supporting files under skills/{skill-name}/scripts/
4. Address review feedback and iterate on documentation
**Files typically involved**:
- `skills/*/SKILL.md`
- `skills/*/scripts/*.sh`
- `skills/*/scripts/*.js`
**Example commit sequence**:
```
Create a new directory under skills/{skill-name}/
Add SKILL.md with documentation (When to Use, How It Works, Examples, etc.)
Optionally add scripts or supporting files under skills/{skill-name}/scripts/
Address review feedback and iterate on documentation
```
### Add New Agent
Adds a new agent to the system for code review, build resolution, or other automated tasks.
**Frequency**: ~2 times per month
**Steps**:
1. Create a new agent markdown file under agents/{agent-name}.md
2. Register the agent in AGENTS.md
3. Optionally update README.md and docs/COMMAND-AGENT-MAP.md
**Files typically involved**:
- `agents/*.md`
- `AGENTS.md`
- `README.md`
- `docs/COMMAND-AGENT-MAP.md`
**Example commit sequence**:
```
Create a new agent markdown file under agents/{agent-name}.md
Register the agent in AGENTS.md
Optionally update README.md and docs/COMMAND-AGENT-MAP.md
```
### Add New Workflow Surface
Adds or updates a workflow entrypoint. Default to skills-first; only add a command shim when legacy slash compatibility is still required.
**Frequency**: ~1 times per month
**Steps**:
1. Create or update the canonical workflow under skills/{skill-name}/SKILL.md
2. Only if needed, add or update commands/{command-name}.md as a compatibility shim
**Files typically involved**:
- `skills/*/SKILL.md`
- `commands/*.md` (only when a legacy shim is intentionally retained)
**Example commit sequence**:
```
Create or update the canonical skill under skills/{skill-name}/SKILL.md
Only if needed, add or update commands/{command-name}.md as a compatibility shim
```
### Sync Catalog Counts
Synchronizes the documented counts of agents, skills, and commands in AGENTS.md and README.md with the actual repository state.
**Frequency**: ~3 times per month
**Steps**:
1. Update agent, skill, and command counts in AGENTS.md
2. Update the same counts in README.md (quick-start, comparison table, etc.)
3. Optionally update other documentation files
**Files typically involved**:
- `AGENTS.md`
- `README.md`
**Example commit sequence**:
```
Update agent, skill, and command counts in AGENTS.md
Update the same counts in README.md (quick-start, comparison table, etc.)
Optionally update other documentation files
```
### Add Cross Harness Skill Copies
Adds skill copies for different agent harnesses (e.g., Codex, Cursor, Antigravity) to ensure compatibility across platforms.
**Frequency**: ~2 times per month
**Steps**:
1. Copy or adapt SKILL.md to .agents/skills/{skill}/SKILL.md and/or .cursor/skills/{skill}/SKILL.md
2. Optionally add harness-specific openai.yaml or config files
3. Address review feedback to align with CONTRIBUTING template
**Files typically involved**:
- `.agents/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
- `.agents/skills/*/agents/openai.yaml`
**Example commit sequence**:
```
Copy or adapt SKILL.md to .agents/skills/{skill}/SKILL.md and/or .cursor/skills/{skill}/SKILL.md
Optionally add harness-specific openai.yaml or config files
Address review feedback to align with CONTRIBUTING template
```
### Add Or Update Hook
Adds or updates git or bash hooks to enforce workflow, quality, or security policies.
**Frequency**: ~1 times per month
**Steps**:
1. Add or update hook scripts in hooks/ or scripts/hooks/
2. Register the hook in hooks/hooks.json or similar config
3. Optionally add or update tests in tests/hooks/
**Files typically involved**:
- `hooks/*.hook`
- `hooks/hooks.json`
- `scripts/hooks/*.js`
- `tests/hooks/*.test.js`
- `.cursor/hooks.json`
**Example commit sequence**:
```
Add or update hook scripts in hooks/ or scripts/hooks/
Register the hook in hooks/hooks.json or similar config
Optionally add or update tests in tests/hooks/
```
### Address Review Feedback
Addresses code review feedback by updating documentation, scripts, or configuration for clarity, correctness, or convention alignment.
**Frequency**: ~4 times per month
**Steps**:
1. Edit SKILL.md, agent, or command files to address reviewer comments
2. Update examples, headings, or configuration as requested
3. Iterate until all review feedback is resolved
**Files typically involved**:
- `skills/*/SKILL.md`
- `agents/*.md`
- `commands/*.md`
- `.agents/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
**Example commit sequence**:
```
Edit SKILL.md, agent, or command files to address reviewer comments
Update examples, headings, or configuration as requested
Iterate until all review feedback is resolved
```
## Best Practices
Based on analysis of the codebase, follow these practices:
### Do
- Use conventional commit format (feat:, fix:, etc.)
- Follow *.test.js naming pattern
- Use camelCase for file names
- Prefer mixed exports
### Don't
- Don't write vague commit messages
- Don't skip tests for new features
- Don't deviate from established patterns without discussion
--- ---
*This skill was auto-generated by [ECC Tools](https://ecc.tools). Review and customize as needed for your team.* ### Refactor Skill or Agent Structure
**Trigger:** When reorganizing skills, merging/splitting logic, or extracting shared code
**Command:** `/refactor-skill`
1. Edit multiple `SKILL.md` files in `skills/` and/or `.agents/skills/`.
2. Update `AGENTS.md`, `README.md`, `README.zh-CN.md`, and `docs/zh-CN/*` as needed.
3. Update `manifests/install-modules.json` or related manifests.
4. Remove or merge obsolete command files.
---
### Add or Update Install Target
**Trigger:** When supporting a new platform or updating install logic
**Command:** `/add-install-target`
1. Add or update install scripts and documentation under a dot-directory (e.g., `.codebuddy/`, `.gemini/`).
2. Update `manifests/install-modules.json` and `schemas/ecc-install-config.schema.json`.
3. Update or create scripts in `scripts/lib/install-targets/[target].js`.
4. Update `registry.js` and `install-manifests.js` as needed.
5. Add or update tests in `tests/lib/install-targets.test.js`.
**Example:**
```js
// scripts/lib/install-targets/codeBuddy.js
export function installCodeBuddy() { ... }
```
---
### Update Hooks and Automation
**Trigger:** When improving or fixing hooks, audit logs, or CI/CD automation
**Command:** `/update-hook`
1. Edit `hooks/hooks.json` and/or add/update `scripts/hooks/*.js`.
2. Update or add related test files in `tests/hooks/` or `tests/scripts/`.
3. Edit `.github/workflows/*.yml` for CI/CD changes.
4. Update `package.json` or related config if needed.
---
### Documentation Sync and Guidance Update
**Trigger:** When updating repo guidance, syncing documentation, or clarifying workflows
**Command:** `/update-docs`
1. Edit one or more of `AGENTS.md`, `README.md`, `README.zh-CN.md`, `WORKING-CONTEXT.md`, and `docs/zh-CN/*`.
2. Update or add `.claude-plugin/README.md`, `.codex-plugin/README.md`, or `the-shortform-guide.md` as needed.
3. Optionally update `package.json` or `scripts/ci/catalog.js` if documentation affects tooling.
---
## Testing Patterns
- **Test Files:** Use the `*.test.js` naming convention.
- Example: `mySkill.test.js`
- **Framework:** Not explicitly detected; follow standard JavaScript testing practices.
- **Location:** Tests are typically located in `tests/` directories, mirroring the structure of the code they test.
- **Example:**
```js
// tests/mySkill.test.js
import { mySkill } from '../skills/mySkill.js';
test('mySkill does something', () => {
expect(mySkill()).toBe(true);
});
```
## Commands
| Command | Purpose |
|--------------------|--------------------------------------------------------|
| /add-skill | Add or update a skill and register it in manifests |
| /add-command | Add or update a command workflow |
| /refactor-skill | Refactor skill or agent structure |
| /add-install-target| Add or update an install target integration |
| /update-hook | Update hooks, automation, or CI/CD workflows |
| /update-docs | Sync or update documentation and repo guidance |
```

View File

@@ -24,11 +24,6 @@ Write investor communication that is short, concrete, and easy to act on.
4. Stay concise. 4. Stay concise.
5. Never send copy that could go to any investor. 5. Never send copy that could go to any investor.
## Voice Handling
If the user's voice matters, run `brand-voice` first and reuse its `VOICE PROFILE`.
This skill should keep the investor-specific structure and ask discipline, not recreate its own parallel voice system.
## Hard Bans ## Hard Bans
Delete and rewrite any of these: Delete and rewrite any of these:
@@ -36,6 +31,7 @@ Delete and rewrite any of these:
- "excited to share" - "excited to share"
- generic thesis praise without a real tie-in - generic thesis praise without a real tie-in
- vague founder adjectives - vague founder adjectives
- "no fluff"
- begging language - begging language
- soft closing questions when a direct ask is clearer - soft closing questions when a direct ask is clearer

View File

@@ -0,0 +1,42 @@
---
name: add-or-update-skill
description: Workflow command scaffold for add-or-update-skill in everything-claude-code.
allowed_tools: ["Bash", "Read", "Write", "Grep", "Glob"]
---
# /add-or-update-skill
Use this workflow when working on **add-or-update-skill** in `everything-claude-code`.
## Goal
Adds a new skill or updates an existing skill in the agents or skills directory, with documentation and registration in manifests.
## Common Files
- `skills/*/SKILL.md`
- `.agents/skills/*/SKILL.md`
- `manifests/install-modules.json`
- `manifests/install-components.json`
- `AGENTS.md`
- `README.md`
## Suggested Sequence
1. Understand the current state and failure mode before editing.
2. Make the smallest coherent change that satisfies the workflow goal.
3. Run the most relevant verification for touched files.
4. Summarize what changed and what still needs review.
## Typical Commit Signals
- Create or update SKILL.md in skills/[skill-name]/ or .agents/skills/[skill-name]/
- Optionally add or update related reference files (e.g., references/*.md, assets/*.py)
- Update manifests/install-modules.json and/or manifests/install-components.json to register the skill
- Update AGENTS.md, README.md, and docs/zh-CN/AGENTS.md as needed
- If skill is agent-facing, update .agents/skills/[skill-name]/SKILL.md
## Notes
- Treat this as a scaffold, not a hard-coded script.
- Update the command if the workflow evolves materially.

View File

@@ -14,8 +14,9 @@ Standard feature implementation workflow
## Common Files ## Common Files
- `manifests/*` - `.opencode/*`
- `schemas/*` - `.opencode/plugins/*`
- `.opencode/plugins/lib/*`
- `**/*.test.*` - `**/*.test.*`
- `**/api/**` - `**/api/**`

View File

@@ -0,0 +1,35 @@
---
name: refactoring
description: Workflow command scaffold for refactoring in everything-claude-code.
allowed_tools: ["Bash", "Read", "Write", "Grep", "Glob"]
---
# /refactoring
Use this workflow when working on **refactoring** in `everything-claude-code`.
## Goal
Code refactoring and cleanup workflow
## Common Files
- `src/**/*`
## Suggested Sequence
1. Understand the current state and failure mode before editing.
2. Make the smallest coherent change that satisfies the workflow goal.
3. Run the most relevant verification for touched files.
4. Summarize what changed and what still needs review.
## Typical Commit Signals
- Ensure tests pass before refactor
- Refactor code structure
- Verify tests still pass
## Notes
- Treat this as a scaffold, not a hard-coded script.
- Update the command if the workflow evolves materially.

View File

@@ -2,7 +2,7 @@
"version": "1.3", "version": "1.3",
"schemaVersion": "1.0", "schemaVersion": "1.0",
"generatedBy": "ecc-tools", "generatedBy": "ecc-tools",
"generatedAt": "2026-03-20T12:07:36.496Z", "generatedAt": "2026-04-02T18:08:28.699Z",
"repo": "https://github.com/affaan-m/everything-claude-code", "repo": "https://github.com/affaan-m/everything-claude-code",
"profiles": { "profiles": {
"requested": "full", "requested": "full",
@@ -148,9 +148,9 @@
".claude/research/everything-claude-code-research-playbook.md", ".claude/research/everything-claude-code-research-playbook.md",
".claude/team/everything-claude-code-team-config.json", ".claude/team/everything-claude-code-team-config.json",
".claude/enterprise/controls.md", ".claude/enterprise/controls.md",
".claude/commands/database-migration.md",
".claude/commands/feature-development.md", ".claude/commands/feature-development.md",
".claude/commands/add-language-rules.md" ".claude/commands/refactoring.md",
".claude/commands/add-or-update-skill.md"
], ],
"packageFiles": { "packageFiles": {
"runtime-core": [ "runtime-core": [
@@ -178,9 +178,9 @@
".claude/enterprise/controls.md" ".claude/enterprise/controls.md"
], ],
"workflow-pack": [ "workflow-pack": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md", ".claude/commands/feature-development.md",
".claude/commands/add-language-rules.md" ".claude/commands/refactoring.md",
".claude/commands/add-or-update-skill.md"
] ]
}, },
"moduleFiles": { "moduleFiles": {
@@ -209,9 +209,9 @@
".claude/enterprise/controls.md" ".claude/enterprise/controls.md"
], ],
"workflow-pack": [ "workflow-pack": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md", ".claude/commands/feature-development.md",
".claude/commands/add-language-rules.md" ".claude/commands/refactoring.md",
".claude/commands/add-or-update-skill.md"
] ]
}, },
"files": [ "files": [
@@ -285,11 +285,6 @@
"path": ".claude/enterprise/controls.md", "path": ".claude/enterprise/controls.md",
"description": "Enterprise governance scaffold for approvals, audit posture, and escalation." "description": "Enterprise governance scaffold for approvals, audit posture, and escalation."
}, },
{
"moduleId": "workflow-pack",
"path": ".claude/commands/database-migration.md",
"description": "Workflow command scaffold for database-migration."
},
{ {
"moduleId": "workflow-pack", "moduleId": "workflow-pack",
"path": ".claude/commands/feature-development.md", "path": ".claude/commands/feature-development.md",
@@ -297,22 +292,27 @@
}, },
{ {
"moduleId": "workflow-pack", "moduleId": "workflow-pack",
"path": ".claude/commands/add-language-rules.md", "path": ".claude/commands/refactoring.md",
"description": "Workflow command scaffold for add-language-rules." "description": "Workflow command scaffold for refactoring."
},
{
"moduleId": "workflow-pack",
"path": ".claude/commands/add-or-update-skill.md",
"description": "Workflow command scaffold for add-or-update-skill."
} }
], ],
"workflows": [ "workflows": [
{
"command": "database-migration",
"path": ".claude/commands/database-migration.md"
},
{ {
"command": "feature-development", "command": "feature-development",
"path": ".claude/commands/feature-development.md" "path": ".claude/commands/feature-development.md"
}, },
{ {
"command": "add-language-rules", "command": "refactoring",
"path": ".claude/commands/add-language-rules.md" "path": ".claude/commands/refactoring.md"
},
{
"command": "add-or-update-skill",
"path": ".claude/commands/add-or-update-skill.md"
} }
], ],
"adapters": { "adapters": {
@@ -320,9 +320,9 @@
"skillPath": ".claude/skills/everything-claude-code/SKILL.md", "skillPath": ".claude/skills/everything-claude-code/SKILL.md",
"identityPath": ".claude/identity.json", "identityPath": ".claude/identity.json",
"commandPaths": [ "commandPaths": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md", ".claude/commands/feature-development.md",
".claude/commands/add-language-rules.md" ".claude/commands/refactoring.md",
".claude/commands/add-or-update-skill.md"
] ]
}, },
"codex": { "codex": {

View File

@@ -10,5 +10,5 @@
"javascript" "javascript"
], ],
"suggestedBy": "ecc-tools-repo-analysis", "suggestedBy": "ecc-tools-repo-analysis",
"createdAt": "2026-03-20T12:07:57.119Z" "createdAt": "2026-04-02T18:09:33.367Z"
} }

View File

@@ -18,4 +18,4 @@ Use this when the task is documentation-heavy, source-sensitive, or requires bro
- Primary language: JavaScript - Primary language: JavaScript
- Framework: Not detected - Framework: Not detected
- Workflows detected: 10 - Workflows detected: 8

View File

@@ -4,7 +4,7 @@ Generated by ECC Tools from repository history. Review before treating it as a h
## Commit Workflow ## Commit Workflow
- Prefer `conventional` commit messaging with prefixes such as fix, test, feat, docs. - Prefer `conventional` commit messaging with prefixes such as fix, feat, docs, chore.
- Keep new changes aligned with the existing pull-request and review flow already present in the repo. - Keep new changes aligned with the existing pull-request and review flow already present in the repo.
## Architecture ## Architecture
@@ -24,9 +24,9 @@ Generated by ECC Tools from repository history. Review before treating it as a h
## Detected Workflows ## Detected Workflows
- database-migration: Database schema changes with migration files
- feature-development: Standard feature implementation workflow - feature-development: Standard feature implementation workflow
- add-language-rules: Adds a new programming language to the rules system, including coding style, hooks, patterns, security, and testing guidelines. - refactoring: Code refactoring and cleanup workflow
- add-or-update-skill: Adds a new skill or updates an existing skill in the agents or skills directory, with documentation and registration in manifests.
## Review Reminder ## Review Reminder

View File

@@ -1,442 +1,150 @@
--- ```markdown
name: everything-claude-code-conventions # everything-claude-code Development Patterns
description: Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
---
# Everything Claude Code Conventions > Auto-generated skill from repository analysis
> Generated from [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) on 2026-03-20
## Overview ## Overview
This skill teaches Claude the development patterns and conventions used in everything-claude-code. This skill provides a comprehensive guide to the development patterns, workflows, and coding conventions used in the `everything-claude-code` repository. It covers how to add or update skills, commands, install targets, hooks, and documentation, as well as the repository's JavaScript coding style and testing patterns. This guide is intended for contributors looking to maintain consistency and efficiency when working within this codebase.
## Tech Stack ## Coding Conventions
- **Primary Language**: JavaScript - **Language:** JavaScript (no framework detected)
- **Architecture**: hybrid module organization - **File Naming:** Use `camelCase` for JavaScript files and folders.
- **Test Location**: separate - Example: `mySkill.js`, `installTarget.js`
- **Import Style:** Use relative imports.
- Example:
```js
import myUtil from './utils/myUtil.js';
```
- **Export Style:** Mixed (both default and named exports are used).
- Example:
```js
// Named export
export function doSomething() { ... }
## When to Use This Skill // Default export
export default MyComponent;
```
- **Commit Messages:** Follow [Conventional Commits](https://www.conventionalcommits.org/) with prefixes like `fix`, `feat`, `docs`, `chore`.
- Example: `feat: add support for new install target`
- **Documentation:** Each skill or command should have a `SKILL.md` or markdown documentation file in its directory.
Activate this skill when: ## Workflows
- Making changes to this repository
- Adding new features following established patterns
- Writing tests that match project conventions
- Creating commits with proper message format
## Commit Conventions ### Add or Update a Skill
**Trigger:** When adding or updating a skill for agents or workflows
**Command:** `/add-skill`
Follow these commit message conventions based on 500 analyzed commits. 1. Create or update `SKILL.md` in `skills/[skill-name]/` or `.agents/skills/[skill-name]/`.
2. Optionally add or update related reference files (e.g., `references/*.md`, `assets/*.py`).
3. Register the skill in `manifests/install-modules.json` and/or `manifests/install-components.json`.
4. Update `AGENTS.md`, `README.md`, and `docs/zh-CN/AGENTS.md` as needed.
5. If the skill is agent-facing, update `.agents/skills/[skill-name]/SKILL.md`.
### Commit Style: Conventional Commits **Example:**
```json
### Prefixes Used // manifests/install-modules.json
{
- `fix` "skills": [
- `test` "myNewSkill"
- `feat` ]
- `docs`
### Message Guidelines
- Average message length: ~65 characters
- Keep first line concise and descriptive
- Use imperative mood ("Add feature" not "Added feature")
*Commit message example*
```text
feat(rules): add C# language support
```
*Commit message example*
```text
chore(deps-dev): bump flatted (#675)
```
*Commit message example*
```text
fix: auto-detect ECC root from plugin cache when CLAUDE_PLUGIN_ROOT is unset (#547) (#691)
```
*Commit message example*
```text
docs: add Antigravity setup and usage guide (#552)
```
*Commit message example*
```text
merge: PR #529 — feat(skills): add documentation-lookup, bun-runtime, nextjs-turbopack; feat(agents): add rust-reviewer
```
*Commit message example*
```text
Revert "Add Kiro IDE support (.kiro/) (#548)"
```
*Commit message example*
```text
Add Kiro IDE support (.kiro/) (#548)
```
*Commit message example*
```text
feat: add block-no-verify hook for Claude Code and Cursor (#649)
```
## Architecture
### Project Structure: Single Package
This project uses **hybrid** module organization.
### Configuration Files
- `.github/workflows/ci.yml`
- `.github/workflows/maintenance.yml`
- `.github/workflows/monthly-metrics.yml`
- `.github/workflows/release.yml`
- `.github/workflows/reusable-release.yml`
- `.github/workflows/reusable-test.yml`
- `.github/workflows/reusable-validate.yml`
- `.opencode/package.json`
- `.opencode/tsconfig.json`
- `.prettierrc`
- `eslint.config.js`
- `package.json`
### Guidelines
- This project uses a hybrid organization
- Follow existing patterns when adding new code
## Code Style
### Language: JavaScript
### Naming Conventions
| Element | Convention |
|---------|------------|
| Files | camelCase |
| Functions | camelCase |
| Classes | PascalCase |
| Constants | SCREAMING_SNAKE_CASE |
### Import Style: Relative Imports
### Export Style: Mixed Style
*Preferred import style*
```typescript
// Use relative imports
import { Button } from '../components/Button'
import { useAuth } from './hooks/useAuth'
```
## Testing
### Test Framework
No specific test framework detected — use the repository's existing test patterns.
### File Pattern: `*.test.js`
### Test Types
- **Unit tests**: Test individual functions and components in isolation
- **Integration tests**: Test interactions between multiple components/services
### Coverage
This project has coverage reporting configured. Aim for 80%+ coverage.
## Error Handling
### Error Handling Style: Try-Catch Blocks
*Standard error handling pattern*
```typescript
try {
const result = await riskyOperation()
return result
} catch (error) {
console.error('Operation failed:', error)
throw new Error('User-friendly message')
} }
``` ```
## Common Workflows ---
These workflows were detected from analyzing commit patterns. ### Add or Update a Command Workflow
**Trigger:** When introducing or refining a workflow command
**Command:** `/add-command`
### Database Migration 1. Create or update a command markdown file in `commands/` or `.claude/commands/`.
2. Iterate based on review feedback (fixes, improvements, removal of external links, etc.).
3. Update related documentation or references if needed.
Database schema changes with migration files **Example:**
```markdown
**Frequency**: ~2 times per month // commands/myCommand.md
# My Command
**Steps**: Description and usage...
1. Create migration file
2. Update schema definitions
3. Generate/update types
**Files typically involved**:
- `**/schema.*`
- `migrations/*`
**Example commit sequence**:
``` ```
feat: implement --with/--without selective install flags (#679)
fix: sync catalog counts with filesystem (27 agents, 113 skills, 58 commands) (#693)
feat(rules): add Rust language rules (rebased #660) (#686)
```
### Feature Development
Standard feature implementation workflow
**Frequency**: ~22 times per month
**Steps**:
1. Add feature implementation
2. Add tests for feature
3. Update documentation
**Files typically involved**:
- `manifests/*`
- `schemas/*`
- `**/*.test.*`
- `**/api/**`
**Example commit sequence**:
```
feat(skills): add documentation-lookup, bun-runtime, nextjs-turbopack; feat(agents): add rust-reviewer
docs(skills): align documentation-lookup with CONTRIBUTING template; add cross-harness (Codex/Cursor) skill copies
fix: address PR review — skill template (When to use, How it works, Examples), bun.lock, next build note, rust-reviewer CI note, doc-lookup privacy/uncertainty
```
### Add Language Rules
Adds a new programming language to the rules system, including coding style, hooks, patterns, security, and testing guidelines.
**Frequency**: ~2 times per month
**Steps**:
1. Create a new directory under rules/{language}/
2. Add coding-style.md, hooks.md, patterns.md, security.md, and testing.md files with language-specific content
3. Optionally reference or link to related skills
**Files typically involved**:
- `rules/*/coding-style.md`
- `rules/*/hooks.md`
- `rules/*/patterns.md`
- `rules/*/security.md`
- `rules/*/testing.md`
**Example commit sequence**:
```
Create a new directory under rules/{language}/
Add coding-style.md, hooks.md, patterns.md, security.md, and testing.md files with language-specific content
Optionally reference or link to related skills
```
### Add New Skill
Adds a new skill to the system, documenting its workflow, triggers, and usage, often with supporting scripts.
**Frequency**: ~4 times per month
**Steps**:
1. Create a new directory under skills/{skill-name}/
2. Add SKILL.md with documentation (When to Use, How It Works, Examples, etc.)
3. Optionally add scripts or supporting files under skills/{skill-name}/scripts/
4. Address review feedback and iterate on documentation
**Files typically involved**:
- `skills/*/SKILL.md`
- `skills/*/scripts/*.sh`
- `skills/*/scripts/*.js`
**Example commit sequence**:
```
Create a new directory under skills/{skill-name}/
Add SKILL.md with documentation (When to Use, How It Works, Examples, etc.)
Optionally add scripts or supporting files under skills/{skill-name}/scripts/
Address review feedback and iterate on documentation
```
### Add New Agent
Adds a new agent to the system for code review, build resolution, or other automated tasks.
**Frequency**: ~2 times per month
**Steps**:
1. Create a new agent markdown file under agents/{agent-name}.md
2. Register the agent in AGENTS.md
3. Optionally update README.md and docs/COMMAND-AGENT-MAP.md
**Files typically involved**:
- `agents/*.md`
- `AGENTS.md`
- `README.md`
- `docs/COMMAND-AGENT-MAP.md`
**Example commit sequence**:
```
Create a new agent markdown file under agents/{agent-name}.md
Register the agent in AGENTS.md
Optionally update README.md and docs/COMMAND-AGENT-MAP.md
```
### Add New Command
Adds a new command to the system, often paired with a backing skill.
**Frequency**: ~1 times per month
**Steps**:
1. Create a new markdown file under commands/{command-name}.md
2. Optionally add or update a backing skill under skills/{skill-name}/SKILL.md
**Files typically involved**:
- `commands/*.md`
- `skills/*/SKILL.md`
**Example commit sequence**:
```
Create a new markdown file under commands/{command-name}.md
Optionally add or update a backing skill under skills/{skill-name}/SKILL.md
```
### Sync Catalog Counts
Synchronizes the documented counts of agents, skills, and commands in AGENTS.md and README.md with the actual repository state.
**Frequency**: ~3 times per month
**Steps**:
1. Update agent, skill, and command counts in AGENTS.md
2. Update the same counts in README.md (quick-start, comparison table, etc.)
3. Optionally update other documentation files
**Files typically involved**:
- `AGENTS.md`
- `README.md`
**Example commit sequence**:
```
Update agent, skill, and command counts in AGENTS.md
Update the same counts in README.md (quick-start, comparison table, etc.)
Optionally update other documentation files
```
### Add Cross Harness Skill Copies
Adds skill copies for different agent harnesses (e.g., Codex, Cursor, Antigravity) to ensure compatibility across platforms.
**Frequency**: ~2 times per month
**Steps**:
1. Copy or adapt SKILL.md to .agents/skills/{skill}/SKILL.md and/or .cursor/skills/{skill}/SKILL.md
2. Optionally add harness-specific openai.yaml or config files
3. Address review feedback to align with CONTRIBUTING template
**Files typically involved**:
- `.agents/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
- `.agents/skills/*/agents/openai.yaml`
**Example commit sequence**:
```
Copy or adapt SKILL.md to .agents/skills/{skill}/SKILL.md and/or .cursor/skills/{skill}/SKILL.md
Optionally add harness-specific openai.yaml or config files
Address review feedback to align with CONTRIBUTING template
```
### Add Or Update Hook
Adds or updates git or bash hooks to enforce workflow, quality, or security policies.
**Frequency**: ~1 times per month
**Steps**:
1. Add or update hook scripts in hooks/ or scripts/hooks/
2. Register the hook in hooks/hooks.json or similar config
3. Optionally add or update tests in tests/hooks/
**Files typically involved**:
- `hooks/*.hook`
- `hooks/hooks.json`
- `scripts/hooks/*.js`
- `tests/hooks/*.test.js`
- `.cursor/hooks.json`
**Example commit sequence**:
```
Add or update hook scripts in hooks/ or scripts/hooks/
Register the hook in hooks/hooks.json or similar config
Optionally add or update tests in tests/hooks/
```
### Address Review Feedback
Addresses code review feedback by updating documentation, scripts, or configuration for clarity, correctness, or convention alignment.
**Frequency**: ~4 times per month
**Steps**:
1. Edit SKILL.md, agent, or command files to address reviewer comments
2. Update examples, headings, or configuration as requested
3. Iterate until all review feedback is resolved
**Files typically involved**:
- `skills/*/SKILL.md`
- `agents/*.md`
- `commands/*.md`
- `.agents/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
**Example commit sequence**:
```
Edit SKILL.md, agent, or command files to address reviewer comments
Update examples, headings, or configuration as requested
Iterate until all review feedback is resolved
```
## Best Practices
Based on analysis of the codebase, follow these practices:
### Do
- Use conventional commit format (feat:, fix:, etc.)
- Follow *.test.js naming pattern
- Use camelCase for file names
- Prefer mixed exports
### Don't
- Don't write vague commit messages
- Don't skip tests for new features
- Don't deviate from established patterns without discussion
--- ---
*This skill was auto-generated by [ECC Tools](https://ecc.tools). Review and customize as needed for your team.* ### Refactor Skill or Agent Structure
**Trigger:** When reorganizing skills, merging/splitting logic, or extracting shared code
**Command:** `/refactor-skill`
1. Edit multiple `SKILL.md` files in `skills/` and/or `.agents/skills/`.
2. Update `AGENTS.md`, `README.md`, `README.zh-CN.md`, and `docs/zh-CN/*` as needed.
3. Update `manifests/install-modules.json` or related manifests.
4. Remove or merge obsolete command files.
---
### Add or Update Install Target
**Trigger:** When supporting a new platform or updating install logic
**Command:** `/add-install-target`
1. Add or update install scripts and documentation under a dot-directory (e.g., `.codebuddy/`, `.gemini/`).
2. Update `manifests/install-modules.json` and `schemas/ecc-install-config.schema.json`.
3. Update or create scripts in `scripts/lib/install-targets/[target].js`.
4. Update `registry.js` and `install-manifests.js` as needed.
5. Add or update tests in `tests/lib/install-targets.test.js`.
**Example:**
```js
// scripts/lib/install-targets/codeBuddy.js
export function installCodeBuddy() { ... }
```
---
### Update Hooks and Automation
**Trigger:** When improving or fixing hooks, audit logs, or CI/CD automation
**Command:** `/update-hook`
1. Edit `hooks/hooks.json` and/or add/update `scripts/hooks/*.js`.
2. Update or add related test files in `tests/hooks/` or `tests/scripts/`.
3. Edit `.github/workflows/*.yml` for CI/CD changes.
4. Update `package.json` or related config if needed.
---
### Documentation Sync and Guidance Update
**Trigger:** When updating repo guidance, syncing documentation, or clarifying workflows
**Command:** `/update-docs`
1. Edit one or more of `AGENTS.md`, `README.md`, `README.zh-CN.md`, `WORKING-CONTEXT.md`, and `docs/zh-CN/*`.
2. Update or add `.claude-plugin/README.md`, `.codex-plugin/README.md`, or `the-shortform-guide.md` as needed.
3. Optionally update `package.json` or `scripts/ci/catalog.js` if documentation affects tooling.
---
## Testing Patterns
- **Test Files:** Use the `*.test.js` naming convention.
- Example: `mySkill.test.js`
- **Framework:** Not explicitly detected; follow standard JavaScript testing practices.
- **Location:** Tests are typically located in `tests/` directories, mirroring the structure of the code they test.
- **Example:**
```js
// tests/mySkill.test.js
import { mySkill } from '../skills/mySkill.js';
test('mySkill does something', () => {
expect(mySkill()).toBe(true);
});
```
## Commands
| Command | Purpose |
|--------------------|--------------------------------------------------------|
| /add-skill | Add or update a skill and register it in manifests |
| /add-command | Add or update a command workflow |
| /refactor-skill | Refactor skill or agent structure |
| /add-install-target| Add or update an install target integration |
| /update-hook | Update hooks, automation, or CI/CD workflows |
| /update-docs | Sync or update documentation and repo guidance |
```

View File

@@ -7,9 +7,9 @@
".agents/skills/everything-claude-code/SKILL.md" ".agents/skills/everything-claude-code/SKILL.md"
], ],
"commandFiles": [ "commandFiles": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md", ".claude/commands/feature-development.md",
".claude/commands/add-language-rules.md" ".claude/commands/refactoring.md",
".claude/commands/add-or-update-skill.md"
], ],
"updatedAt": "2026-03-20T12:07:36.496Z" "updatedAt": "2026-04-02T18:08:28.699Z"
} }

View File

@@ -37,7 +37,7 @@
{ {
"command": "node .cursor/hooks/after-file-edit.js", "command": "node .cursor/hooks/after-file-edit.js",
"event": "afterFileEdit", "event": "afterFileEdit",
"description": "Auto-format, TypeScript check, console.log warning, and frontend design-quality reminder" "description": "Auto-format, TypeScript check, console.log warning"
} }
], ],
"beforeMCPExecution": [ "beforeMCPExecution": [

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
const { hookEnabled, readStdin, runExistingHook, transformToClaude } = require('./adapter'); const { readStdin, runExistingHook, transformToClaude } = require('./adapter');
readStdin().then(raw => { readStdin().then(raw => {
try { try {
const input = JSON.parse(raw); const input = JSON.parse(raw);
@@ -11,9 +11,6 @@ readStdin().then(raw => {
// Accumulate edited paths for batch format+typecheck at stop time // Accumulate edited paths for batch format+typecheck at stop time
runExistingHook('post-edit-accumulator.js', claudeStr); runExistingHook('post-edit-accumulator.js', claudeStr);
runExistingHook('post-edit-console-warn.js', claudeStr); runExistingHook('post-edit-console-warn.js', claudeStr);
if (hookEnabled('post:edit:design-quality-check', ['standard', 'strict'])) {
runExistingHook('design-quality-check.js', claudeStr);
}
} catch {} } catch {}
process.stdout.write(raw); process.stdout.write(raw);
}).catch(() => process.exit(0)); }).catch(() => process.exit(0));

View File

@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — Agent Instructions # Everything Claude Code (ECC) — Agent Instructions
This is a **production-ready AI coding plugin** providing 38 specialized agents, 156 skills, 72 commands, and automated hook workflows for software development. This is a **production-ready AI coding plugin** providing 36 specialized agents, 151 skills, 68 commands, and automated hook workflows for software development.
**Version:** 1.9.0 **Version:** 1.9.0
@@ -145,9 +145,9 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
## Project Structure ## Project Structure
``` ```
agents/ — 38 specialized subagents agents/ — 36 specialized subagents
skills/ — 156 workflow skills and domain knowledge skills/ — 151 workflow skills and domain knowledge
commands/ — 72 slash commands commands/ — 68 slash commands
hooks/ — Trigger-based automations hooks/ — Trigger-based automations
rules/ — Always-follow guidelines (common + per-language) rules/ — Always-follow guidelines (common + per-language)
scripts/ — Cross-platform Node.js utilities scripts/ — Cross-platform Node.js utilities

View File

@@ -225,7 +225,7 @@ For manual install instructions see the README in the `rules/` folder. When copy
/plugin list everything-claude-code@everything-claude-code /plugin list everything-claude-code@everything-claude-code
``` ```
**That's it!** You now have access to 38 agents, 156 skills, and 72 legacy command shims. **That's it!** You now have access to 36 agents, 151 skills, and 68 legacy command shims.
### Multi-model commands require additional setup ### Multi-model commands require additional setup
@@ -943,7 +943,7 @@ Please contribute! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
### Ideas for Contributions ### Ideas for Contributions
- Language-specific skills (Rust, C#, Kotlin, Java) — Go, Python, Perl, Swift, and TypeScript already included - Language-specific skills (Rust, C#, Kotlin, Java) — Go, Python, Perl, Swift, and TypeScript already included
- Framework-specific configs (Rails, FastAPI) — Django, NestJS, Spring Boot, and Laravel already included - Framework-specific configs (Rails, FastAPI, NestJS) — Django, Spring Boot, Laravel already included
- DevOps agents (Kubernetes, Terraform, AWS, Docker) - DevOps agents (Kubernetes, Terraform, AWS, Docker)
- Testing strategies (different frameworks, visual regression) - Testing strategies (different frameworks, visual regression)
- Domain-specific knowledge (ML, data engineering, mobile) - Domain-specific knowledge (ML, data engineering, mobile)
@@ -1118,9 +1118,9 @@ The configuration is automatically detected from `.opencode/opencode.json`.
| Feature | Claude Code | OpenCode | Status | | Feature | Claude Code | OpenCode | Status |
|---------|-------------|----------|--------| |---------|-------------|----------|--------|
| Agents | PASS: 38 agents | PASS: 12 agents | **Claude Code leads** | | Agents | PASS: 36 agents | PASS: 12 agents | **Claude Code leads** |
| Commands | PASS: 72 commands | PASS: 31 commands | **Claude Code leads** | | Commands | PASS: 68 commands | PASS: 31 commands | **Claude Code leads** |
| Skills | PASS: 156 skills | PASS: 37 skills | **Claude Code leads** | | Skills | PASS: 151 skills | PASS: 37 skills | **Claude Code leads** |
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** | | Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** | | Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
| MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** | | MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** |
@@ -1227,9 +1227,9 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode | | Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|---------|------------|------------|-----------|----------| |---------|------------|------------|-----------|----------|
| **Agents** | 38 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | | **Agents** | 36 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
| **Commands** | 72 | Shared | Instruction-based | 31 | | **Commands** | 68 | Shared | Instruction-based | 31 |
| **Skills** | 156 | Shared | 10 (native format) | 37 | | **Skills** | 151 | Shared | 10 (native format) | 37 |
| **Hook Events** | 8 types | 15 types | None yet | 11 types | | **Hook Events** | 8 types | 15 types | None yet | 11 types |
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | | **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | | **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions |

View File

@@ -106,7 +106,7 @@ cp -r everything-claude-code/rules/perl ~/.claude/rules/
/plugin list everything-claude-code@everything-claude-code /plugin list everything-claude-code@everything-claude-code
``` ```
**完成!** 你现在可以使用 38 个代理、156 个技能和 72 个命令。 **完成!** 你现在可以使用 36 个代理、151 个技能和 68 个命令。
### multi-* 命令需要额外配置 ### multi-* 命令需要额外配置

View File

@@ -1,6 +1,6 @@
# Working Context # Working Context
Last updated: 2026-04-02 Last updated: 2026-04-01
## Purpose ## Purpose
@@ -13,7 +13,7 @@ Public ECC plugin repo for agents, skills, commands, hooks, rules, install surfa
- Local full suite status after fix: `1723/1723` passing - Local full suite status after fix: `1723/1723` passing
- Main active operational work: - Main active operational work:
- keep default branch green - keep default branch green
- continue issue-driven fixes from `main` now that the public PR backlog is at zero - audit and classify remaining open PR backlog by full diff
- continue ECC 2.0 control-plane and operator-surface buildout - continue ECC 2.0 control-plane and operator-surface buildout
## Current Constraints ## Current Constraints
@@ -24,7 +24,7 @@ Public ECC plugin repo for agents, skills, commands, hooks, rules, install surfa
## Active Queues ## Active Queues
- PR backlog: currently cleared on the public queue; new work should land through direct mainline fixes or fresh narrowly scoped PRs - PR backlog: audit and classify remaining open PRs into merge, port/rebuild, close, or park
- Product: - Product:
- selective install cleanup - selective install cleanup
- control plane primitives - control plane primitives
@@ -84,15 +84,6 @@ Keep this file detailed for only the current sprint, blockers, and next actions.
## Latest Execution Notes ## Latest Execution Notes
- 2026-04-02: `ECC-Tools/main` shipped `9566637` (`fix: prefer commit lookup over git ref resolution`). The PR-analysis fire is now fixed in the app repo by preferring explicit commit resolution before `git.getRef`, with regression coverage for pull refs and plain branch refs. Mirrored public tracking issue `#1184` in this repo was closed as resolved upstream.
- 2026-04-02: Direct-ported the clean native-support core of `#1043` into `main`: `agents/csharp-reviewer.md`, `skills/dotnet-patterns/SKILL.md`, and `skills/csharp-testing/SKILL.md`. This fills the gap between existing C# rule/docs mentions and actual shipped C# review/testing guidance.
- 2026-04-02: Direct-ported the clean native-support core of `#1055` into `main`: `agents/dart-build-resolver.md`, `commands/flutter-build.md`, `commands/flutter-review.md`, `commands/flutter-test.md`, `rules/dart/*`, and `skills/dart-flutter-patterns/SKILL.md`. The skill paths were wired into the current `framework-language` module instead of replaying the older PR's separate `flutter-dart` module layout.
- 2026-04-02: Closed `#1081` after diff audit. The PR only added vendor-marketing docs for an external X/Twitter backend (`Xquik` / `x-twitter-scraper`) to the canonical `x-api` skill instead of contributing an ECC-native capability.
- 2026-04-02: Direct-ported the useful Jira lane from `#894`, but sanitized it to match current supply-chain policy. `commands/jira.md`, `skills/jira-integration/SKILL.md`, and the pinned `jira` MCP template in `mcp-configs/mcp-servers.json` are in-tree, while the skill no longer tells users to install `uv` via `curl | bash`. `jira-integration` is classified under `operator-workflows` for selective installs.
- 2026-04-02: Closed `#1125` after full diff audit. The bundle/skill-router lane hardcoded many non-existent or non-canonical surfaces and created a second routing abstraction instead of a small ECC-native index layer.
- 2026-04-02: Closed `#1124` after full diff audit. The added agent roster was thoughtfully written, but it duplicated the existing ECC agent surface with a second competing catalog (`dispatch`, `explore`, `verifier`, `executor`, etc.) instead of strengthening canonical agents already in-tree.
- 2026-04-02: Closed the full Argus cluster `#1098`, `#1099`, `#1100`, `#1101`, and `#1102` after full diff audit. The common failure mode was the same across all five PRs: external multi-CLI dispatch was treated as a first-class runtime dependency of shipped ECC surfaces. Any useful protocol ideas should be re-ported later into ECC-native orchestration, review, or reflection lanes without external CLI fan-out assumptions.
- 2026-04-02: The previously open native-support / integration queue (`#1081`, `#1055`, `#1043`, `#894`) has now been fully resolved by direct-port or closure policy. The active public PR queue is currently zero; next focus stays on issue-driven mainline fixes and CI health, not backlog PR intake.
- 2026-04-01: `main` CI was restored locally with `1723/1723` tests passing after lockfile and hook validation fixes. - 2026-04-01: `main` CI was restored locally with `1723/1723` tests passing after lockfile and hook validation fixes.
- 2026-04-01: Auto-generated ECC bundle PRs `#1068` and `#1069` were closed instead of merged; useful ideas must be ported manually after explicit diff audit. - 2026-04-01: Auto-generated ECC bundle PRs `#1068` and `#1069` were closed instead of merged; useful ideas must be ported manually after explicit diff audit.
- 2026-04-01: Major-version ESLint bump PRs `#1063` and `#1064` were closed; revisit only inside a planned ESLint 10 migration lane. - 2026-04-01: Major-version ESLint bump PRs `#1063` and `#1064` were closed; revisit only inside a planned ESLint 10 migration lane.
@@ -120,12 +111,3 @@ Keep this file detailed for only the current sprint, blockers, and next actions.
- 2026-04-01: Added `connections-optimizer` as the review-first social-graph reorganization workflow for X and LinkedIn, with explicit pruning modes, browser fallback expectations, and Apple Mail drafting guidance. - 2026-04-01: Added `connections-optimizer` as the review-first social-graph reorganization workflow for X and LinkedIn, with explicit pruning modes, browser fallback expectations, and Apple Mail drafting guidance.
- 2026-04-01: Added `manim-video` as the reusable technical explainer lane and seeded it with a starter network-graph scene so launch and systems animations do not depend on one-off scratch scripts. - 2026-04-01: Added `manim-video` as the reusable technical explainer lane and seeded it with a starter network-graph scene so launch and systems animations do not depend on one-off scratch scripts.
- 2026-04-02: Re-extracted `social-graph-ranker` as a standalone primitive because the weighted bridge-decay model is reusable outside the full lead workflow. `lead-intelligence` now points to it for canonical graph ranking instead of carrying the full algorithm explanation inline, while `connections-optimizer` stays the broader operator layer for pruning, adds, and outbound review packs. - 2026-04-02: Re-extracted `social-graph-ranker` as a standalone primitive because the weighted bridge-decay model is reusable outside the full lead workflow. `lead-intelligence` now points to it for canonical graph ranking instead of carrying the full algorithm explanation inline, while `connections-optimizer` stays the broader operator layer for pruning, adds, and outbound review packs.
- 2026-04-02: Applied the same consolidation rule to the writing lane. `brand-voice` remains the canonical voice system, while `content-engine`, `crosspost`, `article-writing`, and `investor-outreach` now keep only workflow-specific guidance instead of duplicating a second Affaan/ECC voice model or repeating the full ban list in multiple places.
- 2026-04-02: Closed fresh auto-generated bundle PRs `#1182` and `#1183` under the existing policy. Useful ideas from generator output must be ported manually into canonical repo surfaces instead of merging `.claude`/bundle PRs wholesale.
- 2026-04-02: Ported the safe one-file macOS observer fix from `#1164` directly into `main` as a POSIX `mkdir` fallback for `continuous-learning-v2` lazy-start locking, then closed the PR as superseded by direct port.
- 2026-04-02: Ported the safe core of `#1153` directly into `main`: markdownlint cleanup for orchestration/docs surfaces plus the Windows `USERPROFILE` and path-normalization fixes in `install-apply` / `repair` tests. Local validation after installing repo deps: `node tests/scripts/install-apply.test.js`, `node tests/scripts/repair.test.js`, and targeted `yarn markdownlint` all passed.
- 2026-04-02: Direct-ported the safe web/frontend rules lane from `#1122` into `rules/web/`, but adapted `rules/web/hooks.md` to prefer project-local tooling and avoid remote one-off package execution examples.
- 2026-04-02: Adapted the design-quality reminder from `#1127` into the current ECC hook architecture with a local `scripts/hooks/design-quality-check.js`, Claude `hooks/hooks.json` wiring, Cursor `after-file-edit.js` wiring, and dedicated hook coverage in `tests/hooks/design-quality-check.test.js`.
- 2026-04-02: Fixed `#1141` on `main` in `16e9b17`. The observer lifecycle is now session-aware instead of purely detached: `SessionStart` writes a project-scoped lease, `SessionEnd` removes that lease and stops the observer when the final lease disappears, `observe.sh` records project activity, and `observer-loop.sh` now exits on idle when no leases remain. Targeted validation passed with `bash -n`, `node tests/hooks/observer-memory.test.js`, `node tests/integration/hooks.test.js`, `node scripts/ci/validate-hooks.js hooks/hooks.json`, and `node scripts/ci/check-unicode-safety.js`.
- 2026-04-02: Fixed the remaining Windows-only hook regression behind `#1070` by making `scripts/lib/utils.js#getHomeDir()` honor explicit `HOME` / `USERPROFILE` overrides before falling back to `os.homedir()`. This restores test-isolated observer state paths for hook integration runs on Windows. Added regression coverage in `tests/lib/utils.test.js`. Targeted validation passed with `node tests/lib/utils.test.js`, `node tests/integration/hooks.test.js`, `node tests/hooks/observer-memory.test.js`, and `node scripts/ci/check-unicode-safety.js`.
- 2026-04-02: Direct-ported NestJS support for `#1022` into `main` as `skills/nestjs-patterns/SKILL.md` and wired it into the `framework-language` install module. Synced the repo catalog afterward (`38` agents, `72` commands, `156` skills) and updated the docs so NestJS is no longer listed as an unfilled framework gap.

View File

@@ -1,101 +0,0 @@
---
name: csharp-reviewer
description: Expert C# code reviewer specializing in .NET conventions, async patterns, security, nullable reference types, and performance. Use for all C# code changes. MUST BE USED for C# projects.
tools: ["Read", "Grep", "Glob", "Bash"]
model: sonnet
---
You are a senior C# code reviewer ensuring high standards of idiomatic .NET code and best practices.
When invoked:
1. Run `git diff -- '*.cs'` to see recent C# file changes
2. Run `dotnet build` and `dotnet format --verify-no-changes` if available
3. Focus on modified `.cs` files
4. Begin review immediately
## Review Priorities
### CRITICAL — Security
- **SQL Injection**: String concatenation/interpolation in queries — use parameterized queries or EF Core
- **Command Injection**: Unvalidated input in `Process.Start` — validate and sanitize
- **Path Traversal**: User-controlled file paths — use `Path.GetFullPath` + prefix check
- **Insecure Deserialization**: `BinaryFormatter`, `JsonSerializer` with `TypeNameHandling.All`
- **Hardcoded secrets**: API keys, connection strings in source — use configuration/secret manager
- **CSRF/XSS**: Missing `[ValidateAntiForgeryToken]`, unencoded output in Razor
### CRITICAL — Error Handling
- **Empty catch blocks**: `catch { }` or `catch (Exception) { }` — handle or rethrow
- **Swallowed exceptions**: `catch { return null; }` — log context, throw specific
- **Missing `using`/`await using`**: Manual disposal of `IDisposable`/`IAsyncDisposable`
- **Blocking async**: `.Result`, `.Wait()`, `.GetAwaiter().GetResult()` — use `await`
### HIGH — Async Patterns
- **Missing CancellationToken**: Public async APIs without cancellation support
- **Fire-and-forget**: `async void` except event handlers — return `Task`
- **ConfigureAwait misuse**: Library code missing `ConfigureAwait(false)`
- **Sync-over-async**: Blocking calls in async context causing deadlocks
### HIGH — Type Safety
- **Nullable reference types**: Nullable warnings ignored or suppressed with `!`
- **Unsafe casts**: `(T)obj` without type check — use `obj is T t` or `obj as T`
- **Raw strings as identifiers**: Magic strings for config keys, routes — use constants or `nameof`
- **`dynamic` usage**: Avoid `dynamic` in application code — use generics or explicit models
### HIGH — Code Quality
- **Large methods**: Over 50 lines — extract helper methods
- **Deep nesting**: More than 4 levels — use early returns, guard clauses
- **God classes**: Classes with too many responsibilities — apply SRP
- **Mutable shared state**: Static mutable fields — use `ConcurrentDictionary`, `Interlocked`, or DI scoping
### MEDIUM — Performance
- **String concatenation in loops**: Use `StringBuilder` or `string.Join`
- **LINQ in hot paths**: Excessive allocations — consider `for` loops with pre-allocated buffers
- **N+1 queries**: EF Core lazy loading in loops — use `Include`/`ThenInclude`
- **Missing `AsNoTracking`**: Read-only queries tracking entities unnecessarily
### MEDIUM — Best Practices
- **Naming conventions**: PascalCase for public members, `_camelCase` for private fields
- **Record vs class**: Value-like immutable models should be `record` or `record struct`
- **Dependency injection**: `new`-ing services instead of injecting — use constructor injection
- **`IEnumerable` multiple enumeration**: Materialize with `.ToList()` when enumerated more than once
- **Missing `sealed`**: Non-inherited classes should be `sealed` for clarity and performance
## Diagnostic Commands
```bash
dotnet build # Compilation check
dotnet format --verify-no-changes # Format check
dotnet test --no-build # Run tests
dotnet test --collect:"XPlat Code Coverage" # Coverage
```
## Review Output Format
```text
[SEVERITY] Issue title
File: path/to/File.cs:42
Issue: Description
Fix: What to change
```
## Approval Criteria
- **Approve**: No CRITICAL or HIGH issues
- **Warning**: MEDIUM issues only (can merge with caution)
- **Block**: CRITICAL or HIGH issues found
## Framework Checks
- **ASP.NET Core**: Model validation, auth policies, middleware order, `IOptions<T>` pattern
- **EF Core**: Migration safety, `Include` for eager loading, `AsNoTracking` for reads
- **Minimal APIs**: Route grouping, endpoint filters, proper `TypedResults`
- **Blazor**: Component lifecycle, `StateHasChanged` usage, JS interop disposal
## Reference
For detailed C# patterns, see skill: `dotnet-patterns`.
For testing guidelines, see skill: `csharp-testing`.
---
Review with the mindset: "Would this code pass review at a top .NET shop or open-source project?"

View File

@@ -1,201 +0,0 @@
---
name: dart-build-resolver
description: Dart/Flutter build, analysis, and dependency error resolution specialist. Fixes `dart analyze` errors, Flutter compilation failures, pub dependency conflicts, and build_runner issues with minimal, surgical changes. Use when Dart/Flutter builds fail.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: sonnet
---
# Dart/Flutter Build Error Resolver
You are an expert Dart/Flutter build error resolution specialist. Your mission is to fix Dart analyzer errors, Flutter compilation issues, pub dependency conflicts, and build_runner failures with **minimal, surgical changes**.
## Core Responsibilities
1. Diagnose `dart analyze` and `flutter analyze` errors
2. Fix Dart type errors, null safety violations, and missing imports
3. Resolve `pubspec.yaml` dependency conflicts and version constraints
4. Fix `build_runner` code generation failures
5. Handle Flutter-specific build errors (Android Gradle, iOS CocoaPods, web)
## Diagnostic Commands
Run these in order:
```bash
# Check Dart/Flutter analysis errors
flutter analyze 2>&1
# or for pure Dart projects
dart analyze 2>&1
# Check pub dependency resolution
flutter pub get 2>&1
# Check if code generation is stale
dart run build_runner build --delete-conflicting-outputs 2>&1
# Flutter build for target platform
flutter build apk 2>&1 # Android
flutter build ipa --no-codesign 2>&1 # iOS (CI without signing)
flutter build web 2>&1 # Web
```
## Resolution Workflow
```text
1. flutter analyze -> Parse error messages
2. Read affected file -> Understand context
3. Apply minimal fix -> Only what's needed
4. flutter analyze -> Verify fix
5. flutter test -> Ensure nothing broke
```
## Common Fix Patterns
| Error | Cause | Fix |
|-------|-------|-----|
| `The name 'X' isn't defined` | Missing import or typo | Add correct `import` or fix name |
| `A value of type 'X?' can't be assigned to type 'X'` | Null safety — nullable not handled | Add `!`, `?? default`, or null check |
| `The argument type 'X' can't be assigned to 'Y'` | Type mismatch | Fix type, add explicit cast, or correct API call |
| `Non-nullable instance field 'x' must be initialized` | Missing initializer | Add initializer, mark `late`, or make nullable |
| `The method 'X' isn't defined for type 'Y'` | Wrong type or wrong import | Check type and imports |
| `'await' applied to non-Future` | Awaiting a non-async value | Remove `await` or make function async |
| `Missing concrete implementation of 'X'` | Abstract interface not fully implemented | Add missing method implementations |
| `The class 'X' doesn't implement 'Y'` | Missing `implements` or missing method | Add method or fix class signature |
| `Because X depends on Y >=A and Z depends on Y <B, version solving failed` | Pub version conflict | Adjust version constraints or add `dependency_overrides` |
| `Could not find a file named "pubspec.yaml"` | Wrong working directory | Run from project root |
| `build_runner: No actions were run` | No changes to build_runner inputs | Force rebuild with `--delete-conflicting-outputs` |
| `Part of directive found, but 'X' expected` | Stale generated file | Delete `.g.dart` file and re-run build_runner |
## Pub Dependency Troubleshooting
```bash
# Show full dependency tree
flutter pub deps
# Check why a specific package version was chosen
flutter pub deps --style=compact | grep <package>
# Upgrade packages to latest compatible versions
flutter pub upgrade
# Upgrade specific package
flutter pub upgrade <package_name>
# Clear pub cache if metadata is corrupted
flutter pub cache repair
# Verify pubspec.lock is consistent
flutter pub get --enforce-lockfile
```
## Null Safety Fix Patterns
```dart
// Error: A value of type 'String?' can't be assigned to type 'String'
// BAD — force unwrap
final name = user.name!;
// GOOD — provide fallback
final name = user.name ?? 'Unknown';
// GOOD — guard and return early
if (user.name == null) return;
final name = user.name!; // safe after null check
// GOOD — Dart 3 pattern matching
final name = switch (user.name) {
final n? => n,
null => 'Unknown',
};
```
## Type Error Fix Patterns
```dart
// Error: The argument type 'List<dynamic>' can't be assigned to 'List<String>'
// BAD
final ids = jsonList; // inferred as List<dynamic>
// GOOD
final ids = List<String>.from(jsonList);
// or
final ids = (jsonList as List).cast<String>();
```
## build_runner Troubleshooting
```bash
# Clean and regenerate all files
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs
# Watch mode for development
dart run build_runner watch --delete-conflicting-outputs
# Check for missing build_runner dependencies in pubspec.yaml
# Required: build_runner, json_serializable / freezed / riverpod_generator (as dev_dependencies)
```
## Android Build Troubleshooting
```bash
# Clean Android build cache
cd android && ./gradlew clean && cd ..
# Invalidate Flutter tool cache
flutter clean
# Rebuild
flutter pub get && flutter build apk
# Check Gradle/JDK version compatibility
cd android && ./gradlew --version
```
## iOS Build Troubleshooting
```bash
# Update CocoaPods
cd ios && pod install --repo-update && cd ..
# Clean iOS build
flutter clean && cd ios && pod deintegrate && pod install && cd ..
# Check for platform version mismatches in Podfile
# Ensure ios platform version >= minimum required by all pods
```
## Key Principles
- **Surgical fixes only** — don't refactor, just fix the error
- **Never** add `// ignore:` suppressions without approval
- **Never** use `dynamic` to silence type errors
- **Always** run `flutter analyze` after each fix to verify
- Fix root cause over suppressing symptoms
- Prefer null-safe patterns over bang operators (`!`)
## Stop Conditions
Stop and report if:
- Same error persists after 3 fix attempts
- Fix introduces more errors than it resolves
- Requires architectural changes or package upgrades that change behavior
- Conflicting platform constraints need user decision
## Output Format
```text
[FIXED] lib/features/cart/data/cart_repository_impl.dart:42
Error: A value of type 'String?' can't be assigned to type 'String'
Fix: Changed `final id = response.id` to `final id = response.id ?? ''`
Remaining errors: 2
[FIXED] pubspec.yaml
Error: Version solving failed — http >=0.13.0 required by dio and <0.13.0 required by retrofit
Fix: Upgraded dio to ^5.3.0 which allows http >=0.13.0
Remaining errors: 0
```
Final: `Build Status: SUCCESS/FAILED | Errors Fixed: N | Files Modified: list`
For detailed Dart patterns and code examples, see `skill: flutter-dart-code-review`.

View File

@@ -1,164 +0,0 @@
---
description: Fix Dart analyzer errors and Flutter build failures incrementally. Invokes the dart-build-resolver agent for minimal, surgical fixes.
---
# Flutter Build and Fix
This command invokes the **dart-build-resolver** agent to incrementally fix Dart/Flutter build errors with minimal changes.
## What This Command Does
1. **Run Diagnostics**: Execute `flutter analyze`, `flutter pub get`
2. **Parse Errors**: Group by file and sort by severity
3. **Fix Incrementally**: One error at a time
4. **Verify Each Fix**: Re-run analysis after each change
5. **Report Summary**: Show what was fixed and what remains
## When to Use
Use `/flutter-build` when:
- `flutter analyze` reports errors
- `flutter build` fails for any platform
- `dart pub get` / `flutter pub get` fails with version conflicts
- `build_runner` fails to generate code
- After pulling changes that break the build
## Diagnostic Commands Run
```bash
# Analysis
flutter analyze 2>&1
# Dependencies
flutter pub get 2>&1
# Code generation (if project uses build_runner)
dart run build_runner build --delete-conflicting-outputs 2>&1
# Platform builds
flutter build apk 2>&1
flutter build web 2>&1
```
## Example Session
````text
User: /flutter-build
Agent:
# Flutter Build Resolution
## Initial Diagnostics
```
$ flutter analyze
Analyzing project...
error • lib/features/user/data/user_repository_impl.dart:34:16 •
A value of type 'String?' can't be assigned to a variable of type 'String'. •
invalid_assignment
error • lib/features/cart/presentation/cart_page.dart:58:5 •
The method 'add' isn't defined for the type 'List<Item>'.
Try correcting the name to the name of an existing method, or defining a method named 'add'. •
undefined_method
2 errors found.
```
## Fix 1: Null Safety
File: lib/features/user/data/user_repository_impl.dart:34
Error: A value of type 'String?' can't be assigned to type 'String'
Changed:
```dart
final id = response.id;
```
To:
```dart
final id = response.id ?? '';
```
```
$ flutter analyze
1 error found.
```
## Fix 2: Immutable List
File: lib/features/cart/presentation/cart_page.dart:58
Error: The method 'add' isn't defined for the type 'List<Item>'
Cause: State holds an unmodifiable list; mutation goes through Cubit
Changed:
```dart
state.items.add(item);
```
To:
```dart
context.read<CartCubit>().addItem(item);
// Note: Cubit exposes named methods (addItem, removeItem);
// .add(event) is the BLoC event API — don't mix them.
```
```
$ flutter analyze
No issues found!
```
## Final Verification
```
$ flutter test
All tests passed.
```
## Summary
| Metric | Count |
|--------|-------|
| Analysis errors fixed | 2 |
| Files modified | 2 |
| Remaining issues | 0 |
Build Status: PASS ✓
````
## Common Errors Fixed
| Error | Typical Fix |
|-------|-------------|
| `A value of type 'X?' can't be assigned to 'X'` | Add `?? default` or null guard |
| `The name 'X' isn't defined` | Add import or fix typo |
| `Non-nullable instance field must be initialized` | Add initializer or `late` |
| `Version solving failed` | Adjust version constraints in pubspec.yaml |
| `Missing concrete implementation of 'X'` | Implement missing interface method |
| `build_runner: Part of X expected` | Delete stale `.g.dart` and rebuild |
## Fix Strategy
1. **Analysis errors first** — code must be error-free
2. **Warning triage second** — fix warnings that could cause runtime bugs
3. **pub conflicts third** — fix dependency resolution
4. **One fix at a time** — verify each change
5. **Minimal changes** — don't refactor, just fix
## Stop Conditions
The agent will stop and report if:
- Same error persists after 3 attempts
- Fix introduces more errors
- Requires architectural changes
- Package upgrade conflicts need user decision
## Related Commands
- `/flutter-test` — Run tests after build succeeds
- `/flutter-review` — Review code quality
- `/verify` — Full verification loop
## Related
- Agent: `agents/dart-build-resolver.md`
- Skill: `skills/flutter-dart-code-review/`

View File

@@ -1,116 +0,0 @@
---
description: Review Flutter/Dart code for idiomatic patterns, widget best practices, state management, performance, accessibility, and security. Invokes the flutter-reviewer agent.
---
# Flutter Code Review
This command invokes the **flutter-reviewer** agent to review Flutter/Dart code changes.
## What This Command Does
1. **Gather Context**: Review `git diff --staged` and `git diff`
2. **Inspect Project**: Check `pubspec.yaml`, `analysis_options.yaml`, state management solution
3. **Security Pre-scan**: Check for hardcoded secrets and critical security issues
4. **Full Review**: Apply the complete review checklist
5. **Report Findings**: Output issues grouped by severity with fix guidance
## Prerequisites
Before running `/flutter-review`, ensure:
1. **Build passes** — run `/flutter-build` first; a review on broken code is incomplete
2. **Tests pass** — run `/flutter-test` to confirm no regressions
3. **No merge conflicts** — resolve all conflicts so the diff reflects only intentional changes
4. **`flutter analyze` is clean** — fix analyzer warnings before review
## When to Use
Use `/flutter-review` when:
- Before submitting a PR with Flutter/Dart changes (after build and tests pass)
- After implementing a new feature to catch issues early
- When reviewing someone else's Flutter code
- To audit a widget, state management component, or service class
- Before a production release
## Review Areas
| Area | Severity |
|------|----------|
| Hardcoded secrets, cleartext HTTP | CRITICAL |
| Architecture violations, state management anti-patterns | CRITICAL |
| Widget rebuild issues, resource leaks | HIGH |
| Missing `dispose()`, `BuildContext` after await | HIGH |
| Dart null safety, missing error/loading states | HIGH |
| Const propagation, widget composition | HIGH |
| Performance: expensive work in `build()` | HIGH |
| Accessibility, semantic labels | MEDIUM |
| Missing tests for state transitions | HIGH |
| Hardcoded strings (l10n) | MEDIUM |
| Pub dependency hygiene | LOW |
## Example Session
````text
User: /flutter-review
Agent:
# Flutter Code Review
## Context
Changed files:
- lib/features/auth/presentation/login_page.dart
- lib/features/auth/data/auth_repository_impl.dart
State management: Riverpod (detected from pubspec.yaml)
Architecture: feature-first
## Security Pre-scan
✓ No hardcoded secrets detected
✓ No cleartext HTTP calls
## Review Findings
[HIGH] BuildContext used after async gap without mounted check
File: lib/features/auth/presentation/login_page.dart:67
Issue: `context.go('/home')` called after `await auth.login(...)` with no `mounted` check.
Fix: Add `if (!context.mounted) return;` before any navigation after awaits (Flutter 3.7+).
[HIGH] AsyncValue error state not handled
File: lib/features/auth/presentation/login_page.dart:42
Issue: `ref.watch(authProvider)` switches on loading/data but has no `error` branch.
Fix: Add error case to the switch expression or `when()` call to show a user-facing error message.
[MEDIUM] Hardcoded string not localized
File: lib/features/auth/presentation/login_page.dart:89
Issue: `Text('Login')` — user-visible string not using localization system.
Fix: Use the project's l10n accessor: `Text(context.l10n.loginButton)`.
## Review Summary
| Severity | Count | Status |
|----------|-------|--------|
| CRITICAL | 0 | pass |
| HIGH | 2 | block |
| MEDIUM | 1 | info |
| LOW | 0 | note |
Verdict: BLOCK — HIGH issues must be fixed before merge.
````
## Approval Criteria
- **Approve**: No CRITICAL or HIGH issues
- **Block**: Any CRITICAL or HIGH issues must be fixed before merge
## Related Commands
- `/flutter-build` — Fix build errors first
- `/flutter-test` — Run tests before reviewing
- `/code-review` — General code review (language-agnostic)
## Related
- Agent: `agents/flutter-reviewer.md`
- Skill: `skills/flutter-dart-code-review/`
- Rules: `rules/dart/`

View File

@@ -1,144 +0,0 @@
---
description: Run Flutter/Dart tests, report failures, and incrementally fix test issues. Covers unit, widget, golden, and integration tests.
---
# Flutter Test
This command runs the Flutter test suite and reports results. When failures occur, it diagnoses and fixes issues incrementally.
## What This Command Does
1. **Run Tests**: Execute `flutter test` (or scope to changed files)
2. **Parse Failures**: Identify failing tests by type and cause
3. **Fix Incrementally**: One failure at a time where possible
4. **Verify**: Re-run after each fix
5. **Report**: Show coverage summary and remaining failures
## When to Use
Use `/flutter-test` when:
- After implementing a feature to verify nothing broke
- After running `/flutter-build` to ensure tests pass
- To check test coverage on new code
- When a specific test file is failing
- Before submitting a PR
## Commands Run
```bash
# Run all tests
flutter test 2>&1
# Run with coverage
flutter test --coverage 2>&1
# Run specific test file
flutter test test/unit/domain/usecases/get_user_test.dart 2>&1
# Run tests matching a name pattern
flutter test --name "CartBloc" 2>&1
# Run integration tests (requires device/emulator)
flutter test integration_test/ 2>&1
# Update golden files when intentional visual changes are made
flutter test --update-goldens 2>&1
```
## Example Session
````text
User: /flutter-test
Agent:
# Flutter Test Run
## Test Execution
```
$ flutter test
00:05 +42 -2: Some tests failed.
FAILED: test/unit/data/user_repository_test.dart
UserRepository getById
returns null when user not found
Expected: null
Actual: User(id: 'test-id', name: 'Test User')
FAILED: test/widget/cart_page_test.dart
CartPage
shows empty state when cart is empty
Expected: exactly one widget with text 'Your cart is empty'
Found: no widget with text 'Your cart is empty'
```
## Fix 1: Repository Test
File: test/unit/data/user_repository_test.dart
Failure: `getById` returns a user when it should return null for missing ID
Root cause: Test setup adds a user with ID 'test-id' but queries with 'missing-id'.
Fix: Updated test to query with 'missing-id' — setup was correct, query was wrong.
```
$ flutter test test/unit/data/user_repository_test.dart
1 test passed.
```
## Fix 2: Widget Test
File: test/widget/cart_page_test.dart
Failure: Empty state text widget not found
Root cause: Empty state message was renamed from 'Your cart is empty' to 'Cart is empty' in the widget.
Fix: Updated test string to match current widget copy.
```
$ flutter test test/widget/cart_page_test.dart
1 test passed.
```
## Final Run
```
$ flutter test --coverage
All 44 tests passed.
Coverage: 84.2% (target: 80%)
```
## Summary
| Metric | Value |
|--------|-------|
| Total tests | 44 |
| Passed | 44 |
| Failed | 0 |
| Coverage | 84.2% |
Test Status: PASS ✓
````
## Common Test Failures
| Failure | Typical Fix |
|---------|-------------|
| `Expected: <X> Actual: <Y>` | Update assertion or fix implementation |
| `Widget not found` | Fix finder selector or update test after widget rename |
| `Golden file not found` | Run `flutter test --update-goldens` to generate |
| `Golden mismatch` | Inspect diff; run `--update-goldens` if change was intentional |
| `MissingPluginException` | Mock platform channel in test setup |
| `LateInitializationError` | Initialize `late` fields in `setUp()` |
| `pumpAndSettle timed out` | Replace with explicit `pump(Duration)` calls |
## Related Commands
- `/flutter-build` — Fix build errors before running tests
- `/flutter-review` — Review code after tests pass
- `/tdd` — Test-driven development workflow
## Related
- Agent: `agents/flutter-reviewer.md`
- Agent: `agents/dart-build-resolver.md`
- Skill: `skills/flutter-dart-code-review/`
- Rules: `rules/dart/testing.md`

View File

@@ -1,106 +0,0 @@
---
description: Retrieve a Jira ticket, analyze requirements, update status, or add comments. Uses the jira-integration skill and MCP or REST API.
---
# Jira Command
Interact with Jira tickets directly from your workflow — fetch tickets, analyze requirements, add comments, and transition status.
## Usage
```
/jira get <TICKET-KEY> # Fetch and analyze a ticket
/jira comment <TICKET-KEY> # Add a progress comment
/jira transition <TICKET-KEY> # Change ticket status
/jira search <JQL> # Search issues with JQL
```
## What This Command Does
1. **Get & Analyze** — Fetch a Jira ticket and extract requirements, acceptance criteria, test scenarios, and dependencies
2. **Comment** — Add structured progress updates to a ticket
3. **Transition** — Move a ticket through workflow states (To Do → In Progress → Done)
4. **Search** — Find issues using JQL queries
## How It Works
### `/jira get <TICKET-KEY>`
1. Fetch the ticket from Jira (via MCP `jira_get_issue` or REST API)
2. Extract all fields: summary, description, acceptance criteria, priority, labels, linked issues
3. Optionally fetch comments for additional context
4. Produce a structured analysis:
```
Ticket: PROJ-1234
Summary: [title]
Status: [status]
Priority: [priority]
Type: [Story/Bug/Task]
Requirements:
1. [extracted requirement]
2. [extracted requirement]
Acceptance Criteria:
- [ ] [criterion from ticket]
Test Scenarios:
- Happy Path: [description]
- Error Case: [description]
- Edge Case: [description]
Dependencies:
- [linked issues, APIs, services]
Recommended Next Steps:
- /plan to create implementation plan
- /tdd to implement with tests first
```
### `/jira comment <TICKET-KEY>`
1. Summarize current session progress (what was built, tested, committed)
2. Format as a structured comment
3. Post to the Jira ticket
### `/jira transition <TICKET-KEY>`
1. Fetch available transitions for the ticket
2. Show options to user
3. Execute the selected transition
### `/jira search <JQL>`
1. Execute the JQL query against Jira
2. Return a summary table of matching issues
## Prerequisites
This command requires Jira credentials. Choose one:
**Option A — MCP Server (recommended):**
Add `jira` to your `mcpServers` config (see `mcp-configs/mcp-servers.json` for the template).
**Option B — Environment variables:**
```bash
export JIRA_URL="https://yourorg.atlassian.net"
export JIRA_EMAIL="your.email@example.com"
export JIRA_API_TOKEN="your-api-token"
```
If credentials are missing, stop and direct the user to set them up.
## Integration with Other Commands
After analyzing a ticket:
- Use `/plan` to create an implementation plan from the requirements
- Use `/tdd` to implement with test-driven development
- Use `/code-review` after implementation
- Use `/jira comment` to post progress back to the ticket
- Use `/jira transition` to move the ticket when work is complete
## Related
- **Skill:** `skills/jira-integration/`
- **MCP config:** `mcp-configs/mcp-servers.json``jira`

View File

@@ -24,20 +24,20 @@ Apply the orchestration skills instead of maintaining a second workflow spec her
- Keep handoffs structured, but let the skills define the maintained sequencing rules. - Keep handoffs structured, but let the skills define the maintained sequencing rules.
Security Reviewer: [summary] Security Reviewer: [summary]
### FILES CHANGED FILES CHANGED
-------------
[List all files modified] [List all files modified]
### TEST RESULTS TEST RESULTS
------------
[Test pass/fail summary] [Test pass/fail summary]
### SECURITY STATUS SECURITY STATUS
---------------
[Security findings] [Security findings]
### RECOMMENDATION RECOMMENDATION
--------------
[SHIP / NEEDS WORK / BLOCKED] [SHIP / NEEDS WORK / BLOCKED]
``` ```
@@ -111,7 +111,7 @@ Telemetry:
This keeps planner, implementer, reviewer, and loop workers legible from the operator surface. This keeps planner, implementer, reviewer, and loop workers legible from the operator surface.
## Workflow Arguments ## Arguments
$ARGUMENTS: $ARGUMENTS:
- `feature <description>` - Full feature workflow - `feature <description>` - Full feature workflow

View File

@@ -177,7 +177,7 @@ Next steps:
## Edge Cases ## Edge Cases
- **No `gh` CLI**: Stop with: "GitHub CLI (`gh`) is required. Install: <https://cli.github.com/>" - **No `gh` CLI**: Stop with: "GitHub CLI (`gh`) is required. Install: https://cli.github.com/"
- **Not authenticated**: Stop with: "Run `gh auth login` first." - **Not authenticated**: Stop with: "Run `gh auth login` first."
- **Force push needed**: If remote has diverged and rebase was done, use `git push --force-with-lease` (never `--force`). - **Force push needed**: If remote has diverged and rebase was done, use `git push --force-with-lease` (never `--force`).
- **Multiple PR templates**: If `.github/PULL_REQUEST_TEMPLATE/` has multiple files, list them and ask user to choose. - **Multiple PR templates**: If `.github/PULL_REQUEST_TEMPLATE/` has multiple files, list them and ask user to choose.

View File

@@ -600,7 +600,7 @@ node tests/hooks/hooks.test.js
### 貢献アイデア ### 貢献アイデア
- 言語固有のスキルRust、C#、Swift、Kotlin — Go、Python、Javaは既に含まれています - 言語固有のスキルRust、C#、Swift、Kotlin — Go、Python、Javaは既に含まれています
- フレームワーク固有の設定Rails、Laravel、FastAPI — Django、NestJS、Spring Bootは既に含まれています - フレームワーク固有の設定Rails、Laravel、FastAPI、NestJS — Django、Spring Bootは既に含まれています
- DevOpsエージェントKubernetes、Terraform、AWS、Docker - DevOpsエージェントKubernetes、Terraform、AWS、Docker
- テスト戦略(異なるフレームワーク、ビジュアルリグレッション) - テスト戦略(異なるフレームワーク、ビジュアルリグレッション)
- 専門領域の知識ML、データエンジニアリング、モバイル開発 - 専門領域の知識ML、データエンジニアリング、モバイル開発

View File

@@ -631,7 +631,7 @@ node tests/hooks/hooks.test.js
### 기여 아이디어 ### 기여 아이디어
- 언어별 스킬 (Rust, C#, Swift, Kotlin) — Go, Python, Java는 이미 포함 - 언어별 스킬 (Rust, C#, Swift, Kotlin) — Go, Python, Java는 이미 포함
- 프레임워크별 설정 (Rails, Laravel, FastAPI) — Django, NestJS, Spring Boot는 이미 포함 - 프레임워크별 설정 (Rails, Laravel, FastAPI, NestJS) — Django, Spring Boot는 이미 포함
- DevOps 에이전트 (Kubernetes, Terraform, AWS, Docker) - DevOps 에이전트 (Kubernetes, Terraform, AWS, Docker)
- 테스팅 전략 (다양한 프레임워크, 비주얼 리그레션) - 테스팅 전략 (다양한 프레임워크, 비주얼 리그레션)
- 도메인별 지식 (ML, 데이터 엔지니어링, 모바일) - 도메인별 지식 (ML, 데이터 엔지니어링, 모바일)

View File

@@ -441,7 +441,7 @@ Lütfen katkıda bulunun! Rehber için [CONTRIBUTING.md](../../CONTRIBUTING.md)'
### Katkı Fikirleri ### Katkı Fikirleri
- Dile özel skill'ler (Rust, C#, Kotlin, Java) — Go, Python, Perl, Swift ve TypeScript zaten dahil - Dile özel skill'ler (Rust, C#, Kotlin, Java) — Go, Python, Perl, Swift ve TypeScript zaten dahil
- Framework'e özel config'ler (Rails, FastAPI) — Django, NestJS, Spring Boot ve Laravel zaten dahil - Framework'e özel config'ler (Rails, FastAPI, NestJS) — Django, Spring Boot, Laravel zaten dahil
- DevOps agent'ları (Kubernetes, Terraform, AWS, Docker) - DevOps agent'ları (Kubernetes, Terraform, AWS, Docker)
- Test stratejileri (farklı framework'ler, görsel regresyon) - Test stratejileri (farklı framework'ler, görsel regresyon)
- Domain'e özel bilgi (ML, data engineering, mobile) - Domain'e özel bilgi (ML, data engineering, mobile)

View File

@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — 智能体指令 # Everything Claude Code (ECC) — 智能体指令
这是一个**生产就绪的 AI 编码插件**,提供 38 个专业代理、156 项技能、72 条命令以及自动化钩子工作流,用于软件开发。 这是一个**生产就绪的 AI 编码插件**,提供 36 个专业代理、151 项技能、68 条命令以及自动化钩子工作流,用于软件开发。
**版本:** 1.9.0 **版本:** 1.9.0
@@ -146,9 +146,9 @@
## 项目结构 ## 项目结构
``` ```
agents/ — 38 个专业子代理 agents/ — 36 个专业子代理
skills/ — 156 个工作流技能和领域知识 skills/ — 151 个工作流技能和领域知识
commands/ — 72 个斜杠命令 commands/ — 68 个斜杠命令
hooks/ — 基于触发的自动化 hooks/ — 基于触发的自动化
rules/ — 始终遵循的指导方针(通用 + 每种语言) rules/ — 始终遵循的指导方针(通用 + 每种语言)
scripts/ — 跨平台 Node.js 实用工具 scripts/ — 跨平台 Node.js 实用工具

View File

@@ -209,7 +209,7 @@ npx ecc-install typescript
/plugin list everything-claude-code@everything-claude-code /plugin list everything-claude-code@everything-claude-code
``` ```
**搞定!** 你现在可以使用 38 个智能体、156 项技能和 72 个命令了。 **搞定!** 你现在可以使用 36 个智能体、151 项技能和 68 个命令了。
*** ***
@@ -927,7 +927,7 @@ node tests/hooks/hooks.test.js
### 贡献想法 ### 贡献想法
* 特定语言技能 (Rust, C#, Kotlin, Java) — Go、Python、Perl、Swift 和 TypeScript 已包含在内 * 特定语言技能 (Rust, C#, Kotlin, Java) — Go、Python、Perl、Swift 和 TypeScript 已包含在内
* 特定框架配置 (Rails, FastAPI) — Django、NestJS、Spring Boot、Laravel 已包含在内 * 特定框架配置 (Rails, FastAPI, NestJS) — Django、Spring Boot、Laravel 已包含在内
* DevOps 智能体 (Kubernetes, Terraform, AWS, Docker) * DevOps 智能体 (Kubernetes, Terraform, AWS, Docker)
* 测试策略 (不同框架、视觉回归) * 测试策略 (不同框架、视觉回归)
* 领域特定知识 (ML, 数据工程, 移动端) * 领域特定知识 (ML, 数据工程, 移动端)
@@ -1094,9 +1094,9 @@ opencode
| 功能特性 | Claude Code | OpenCode | 状态 | | 功能特性 | Claude Code | OpenCode | 状态 |
|---------|-------------|----------|--------| |---------|-------------|----------|--------|
| 智能体 | PASS: 38 个 | PASS: 12 个 | **Claude Code 领先** | | 智能体 | PASS: 36 个 | PASS: 12 个 | **Claude Code 领先** |
| 命令 | PASS: 72 个 | PASS: 31 个 | **Claude Code 领先** | | 命令 | PASS: 68 个 | PASS: 31 个 | **Claude Code 领先** |
| 技能 | PASS: 156 项 | PASS: 37 项 | **Claude Code 领先** | | 技能 | PASS: 151 项 | PASS: 37 项 | **Claude Code 领先** |
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** | | 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** | | 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** | | MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
@@ -1206,9 +1206,9 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode | | 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|---------|------------|------------|-----------|----------| |---------|------------|------------|-----------|----------|
| **智能体** | 38 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 | | **智能体** | 36 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
| **命令** | 72 | 共享 | 基于指令 | 31 | | **命令** | 68 | 共享 | 基于指令 | 31 |
| **技能** | 156 | 共享 | 10 (原生格式) | 37 | | **技能** | 151 | 共享 | 10 (原生格式) | 37 |
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 | | **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 | | **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 | | **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |

View File

@@ -35,7 +35,6 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
| **PR logger** | `Bash` | Logs PR URL and review command after `gh pr create` | | **PR logger** | `Bash` | Logs PR URL and review command after `gh pr create` |
| **Build analysis** | `Bash` | Background analysis after build commands (async, non-blocking) | | **Build analysis** | `Bash` | Background analysis after build commands (async, non-blocking) |
| **Quality gate** | `Edit\|Write\|MultiEdit` | Runs fast quality checks after edits | | **Quality gate** | `Edit\|Write\|MultiEdit` | Runs fast quality checks after edits |
| **Design quality check** | `Edit\|Write\|MultiEdit` | Warns when frontend edits drift toward generic template-looking UI |
| **Prettier format** | `Edit` | Auto-formats JS/TS files with Prettier after edits | | **Prettier format** | `Edit` | Auto-formats JS/TS files with Prettier after edits |
| **TypeScript check** | `Edit` | Runs `tsc --noEmit` after editing `.ts`/`.tsx` files | | **TypeScript check** | `Edit` | Runs `tsc --noEmit` after editing `.ts`/`.tsx` files |
| **console.log warning** | `Edit` | Warns about `console.log` statements in edited files | | **console.log warning** | `Edit` | Warns about `console.log` statements in edited files |

View File

@@ -226,18 +226,6 @@
"description": "Run quality gate checks after file edits", "description": "Run quality gate checks after file edits",
"id": "post:quality-gate" "id": "post:quality-gate"
}, },
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"post:edit:design-quality-check\" \"scripts/hooks/design-quality-check.js\" \"standard,strict\"",
"timeout": 10
}
],
"description": "Warn when frontend edits drift toward generic template-looking UI",
"id": "post:edit:design-quality-check"
},
{ {
"matcher": "Edit|Write|MultiEdit", "matcher": "Edit|Write|MultiEdit",
"hooks": [ "hooks": [

View File

@@ -117,14 +117,11 @@
"skills/backend-patterns", "skills/backend-patterns",
"skills/coding-standards", "skills/coding-standards",
"skills/compose-multiplatform-patterns", "skills/compose-multiplatform-patterns",
"skills/csharp-testing",
"skills/cpp-coding-standards", "skills/cpp-coding-standards",
"skills/cpp-testing", "skills/cpp-testing",
"skills/dart-flutter-patterns",
"skills/django-patterns", "skills/django-patterns",
"skills/django-tdd", "skills/django-tdd",
"skills/django-verification", "skills/django-verification",
"skills/dotnet-patterns",
"skills/frontend-patterns", "skills/frontend-patterns",
"skills/frontend-slides", "skills/frontend-slides",
"skills/golang-patterns", "skills/golang-patterns",
@@ -140,7 +137,6 @@
"skills/laravel-tdd", "skills/laravel-tdd",
"skills/laravel-verification", "skills/laravel-verification",
"skills/mcp-server-patterns", "skills/mcp-server-patterns",
"skills/nestjs-patterns",
"skills/perl-patterns", "skills/perl-patterns",
"skills/perl-testing", "skills/perl-testing",
"skills/python-patterns", "skills/python-patterns",
@@ -317,7 +313,6 @@
"skills/connections-optimizer", "skills/connections-optimizer",
"skills/customer-billing-ops", "skills/customer-billing-ops",
"skills/google-workspace-ops", "skills/google-workspace-ops",
"skills/jira-integration",
"skills/project-flow-ops", "skills/project-flow-ops",
"skills/workspace-surface-audit" "skills/workspace-surface-audit"
], ],

View File

@@ -1,15 +1,5 @@
{ {
"mcpServers": { "mcpServers": {
"jira": {
"command": "uvx",
"args": ["mcp-atlassian==0.21.0"],
"env": {
"JIRA_URL": "YOUR_JIRA_URL_HERE",
"JIRA_EMAIL": "YOUR_JIRA_EMAIL_HERE",
"JIRA_API_TOKEN": "YOUR_JIRA_API_TOKEN_HERE"
},
"description": "Jira issue tracking — search, create, update, comment, transition issues"
},
"github": { "github": {
"command": "npx", "command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"], "args": ["-y", "@modelcontextprotocol/server-github"],

View File

@@ -17,7 +17,6 @@ rules/
├── typescript/ # TypeScript/JavaScript specific ├── typescript/ # TypeScript/JavaScript specific
├── python/ # Python specific ├── python/ # Python specific
├── golang/ # Go specific ├── golang/ # Go specific
├── web/ # Web and frontend specific
├── swift/ # Swift specific ├── swift/ # Swift specific
└── php/ # PHP specific └── php/ # PHP specific
``` ```
@@ -34,7 +33,6 @@ rules/
./install.sh typescript ./install.sh typescript
./install.sh python ./install.sh python
./install.sh golang ./install.sh golang
./install.sh web
./install.sh swift ./install.sh swift
./install.sh php ./install.sh php
@@ -58,7 +56,6 @@ cp -r rules/common ~/.claude/rules/common
cp -r rules/typescript ~/.claude/rules/typescript cp -r rules/typescript ~/.claude/rules/typescript
cp -r rules/python ~/.claude/rules/python cp -r rules/python ~/.claude/rules/python
cp -r rules/golang ~/.claude/rules/golang cp -r rules/golang ~/.claude/rules/golang
cp -r rules/web ~/.claude/rules/web
cp -r rules/swift ~/.claude/rules/swift cp -r rules/swift ~/.claude/rules/swift
cp -r rules/php ~/.claude/rules/php cp -r rules/php ~/.claude/rules/php
@@ -89,8 +86,6 @@ To add support for a new language (e.g., `rust/`):
``` ```
4. Reference existing skills if available, or create new ones under `skills/`. 4. Reference existing skills if available, or create new ones under `skills/`.
For non-language domains like `web/`, follow the same layered pattern when there is enough reusable domain-specific guidance to justify a standalone ruleset.
## Rule Priority ## Rule Priority
When language-specific rules and common rules conflict, **language-specific rules take precedence** (specific overrides general). This follows the standard layered configuration pattern (similar to CSS specificity or `.gitignore` precedence). When language-specific rules and common rules conflict, **language-specific rules take precedence** (specific overrides general). This follows the standard layered configuration pattern (similar to CSS specificity or `.gitignore` precedence).

View File

@@ -1,159 +0,0 @@
---
paths:
- "**/*.dart"
- "**/pubspec.yaml"
- "**/analysis_options.yaml"
---
# Dart/Flutter Coding Style
> This file extends [common/coding-style.md](../common/coding-style.md) with Dart and Flutter-specific content.
## Formatting
- **dart format** for all `.dart` files — enforced in CI (`dart format --set-exit-if-changed .`)
- Line length: 80 characters (dart format default)
- Trailing commas on multi-line argument/parameter lists to improve diffs and formatting
## Immutability
- Prefer `final` for local variables and `const` for compile-time constants
- Use `const` constructors wherever all fields are `final`
- Return unmodifiable collections from public APIs (`List.unmodifiable`, `Map.unmodifiable`)
- Use `copyWith()` for state mutations in immutable state classes
```dart
// BAD
var count = 0;
List<String> items = ['a', 'b'];
// GOOD
final count = 0;
const items = ['a', 'b'];
```
## Naming
Follow Dart conventions:
- `camelCase` for variables, parameters, and named constructors
- `PascalCase` for classes, enums, typedefs, and extensions
- `snake_case` for file names and library names
- `SCREAMING_SNAKE_CASE` for constants declared with `const` at top level
- Prefix private members with `_`
- Extension names describe the type they extend: `StringExtensions`, not `MyHelpers`
## Null Safety
- Avoid `!` (bang operator) — prefer `?.`, `??`, `if (x != null)`, or Dart 3 pattern matching; reserve `!` only where a null value is a programming error and crashing is the right behaviour
- Avoid `late` unless initialization is guaranteed before first use (prefer nullable or constructor init)
- Use `required` for constructor parameters that must always be provided
```dart
// BAD — crashes at runtime if user is null
final name = user!.name;
// GOOD — null-aware operators
final name = user?.name ?? 'Unknown';
// GOOD — Dart 3 pattern matching (exhaustive, compiler-checked)
final name = switch (user) {
User(:final name) => name,
null => 'Unknown',
};
// GOOD — early-return null guard
String getUserName(User? user) {
if (user == null) return 'Unknown';
return user.name; // promoted to non-null after the guard
}
```
## Sealed Types and Pattern Matching (Dart 3+)
Use sealed classes to model closed state hierarchies:
```dart
sealed class AsyncState<T> {
const AsyncState();
}
final class Loading<T> extends AsyncState<T> {
const Loading();
}
final class Success<T> extends AsyncState<T> {
const Success(this.data);
final T data;
}
final class Failure<T> extends AsyncState<T> {
const Failure(this.error);
final Object error;
}
```
Always use exhaustive `switch` with sealed types — no default/wildcard:
```dart
// BAD
if (state is Loading) { ... }
// GOOD
return switch (state) {
Loading() => const CircularProgressIndicator(),
Success(:final data) => DataWidget(data),
Failure(:final error) => ErrorWidget(error.toString()),
};
```
## Error Handling
- Specify exception types in `on` clauses — never use bare `catch (e)`
- Never catch `Error` subtypes — they indicate programming bugs
- Use `Result`-style types or sealed classes for recoverable errors
- Avoid using exceptions for control flow
```dart
// BAD
try {
await fetchUser();
} catch (e) {
log(e.toString());
}
// GOOD
try {
await fetchUser();
} on NetworkException catch (e) {
log('Network error: ${e.message}');
} on NotFoundException {
handleNotFound();
}
```
## Async / Futures
- Always `await` Futures or explicitly call `unawaited()` to signal intentional fire-and-forget
- Never mark a function `async` if it never `await`s anything
- Use `Future.wait` / `Future.any` for concurrent operations
- Check `context.mounted` before using `BuildContext` after any `await` (Flutter 3.7+)
```dart
// BAD — ignoring Future
fetchData(); // fire-and-forget without marking intent
// GOOD
unawaited(fetchData()); // explicit fire-and-forget
await fetchData(); // or properly awaited
```
## Imports
- Use `package:` imports throughout — never relative imports (`../`) for cross-feature or cross-layer code
- Order: `dart:` → external `package:` → internal `package:` (same package)
- No unused imports — `dart analyze` enforces this with `unused_import`
## Code Generation
- Generated files (`.g.dart`, `.freezed.dart`, `.gr.dart`) must be committed or gitignored consistently — pick one strategy per project
- Never manually edit generated files
- Keep generator annotations (`@JsonSerializable`, `@freezed`, `@riverpod`, etc.) on the canonical source file only

View File

@@ -1,66 +0,0 @@
---
paths:
- "**/*.dart"
- "**/pubspec.yaml"
- "**/analysis_options.yaml"
---
# Dart/Flutter Hooks
> This file extends [common/hooks.md](../common/hooks.md) with Dart and Flutter-specific content.
## PostToolUse Hooks
Configure in `~/.claude/settings.json`:
- **dart format**: Auto-format `.dart` files after edit
- **dart analyze**: Run static analysis after editing Dart files and surface warnings
- **flutter test**: Optionally run affected tests after significant changes
## Recommended Hook Configuration
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": { "tool_name": "Edit", "file_paths": ["**/*.dart"] },
"hooks": [
{ "type": "command", "command": "dart format $CLAUDE_FILE_PATHS" }
]
}
]
}
}
```
## Pre-commit Checks
Run before committing Dart/Flutter changes:
```bash
dart format --set-exit-if-changed .
dart analyze --fatal-infos
flutter test
```
## Useful One-liners
```bash
# Format all Dart files
dart format .
# Analyze and report issues
dart analyze
# Run all tests with coverage
flutter test --coverage
# Regenerate code-gen files
dart run build_runner build --delete-conflicting-outputs
# Check for outdated packages
flutter pub outdated
# Upgrade packages within constraints
flutter pub upgrade
```

View File

@@ -1,261 +0,0 @@
---
paths:
- "**/*.dart"
- "**/pubspec.yaml"
---
# Dart/Flutter Patterns
> This file extends [common/patterns.md](../common/patterns.md) with Dart, Flutter, and common ecosystem-specific content.
## Repository Pattern
```dart
abstract interface class UserRepository {
Future<User?> getById(String id);
Future<List<User>> getAll();
Stream<List<User>> watchAll();
Future<void> save(User user);
Future<void> delete(String id);
}
class UserRepositoryImpl implements UserRepository {
const UserRepositoryImpl(this._remote, this._local);
final UserRemoteDataSource _remote;
final UserLocalDataSource _local;
@override
Future<User?> getById(String id) async {
final local = await _local.getById(id);
if (local != null) return local;
final remote = await _remote.getById(id);
if (remote != null) await _local.save(remote);
return remote;
}
@override
Future<List<User>> getAll() async {
final remote = await _remote.getAll();
for (final user in remote) {
await _local.save(user);
}
return remote;
}
@override
Stream<List<User>> watchAll() => _local.watchAll();
@override
Future<void> save(User user) => _local.save(user);
@override
Future<void> delete(String id) async {
await _remote.delete(id);
await _local.delete(id);
}
}
```
## State Management: BLoC/Cubit
```dart
// Cubit — simple state transitions
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
// BLoC — event-driven
@immutable
sealed class CartEvent {}
class CartItemAdded extends CartEvent { CartItemAdded(this.item); final Item item; }
class CartItemRemoved extends CartEvent { CartItemRemoved(this.id); final String id; }
class CartCleared extends CartEvent {}
@immutable
class CartState {
const CartState({this.items = const []});
final List<Item> items;
CartState copyWith({List<Item>? items}) => CartState(items: items ?? this.items);
}
class CartBloc extends Bloc<CartEvent, CartState> {
CartBloc() : super(const CartState()) {
on<CartItemAdded>((event, emit) =>
emit(state.copyWith(items: [...state.items, event.item])));
on<CartItemRemoved>((event, emit) =>
emit(state.copyWith(items: state.items.where((i) => i.id != event.id).toList())));
on<CartCleared>((_, emit) => emit(const CartState()));
}
}
```
## State Management: Riverpod
```dart
// Simple provider
@riverpod
Future<List<User>> users(Ref ref) async {
final repo = ref.watch(userRepositoryProvider);
return repo.getAll();
}
// Notifier for mutable state
@riverpod
class CartNotifier extends _$CartNotifier {
@override
List<Item> build() => [];
void add(Item item) => state = [...state, item];
void remove(String id) => state = state.where((i) => i.id != id).toList();
void clear() => state = [];
}
// ConsumerWidget
class CartPage extends ConsumerWidget {
const CartPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(cartNotifierProvider);
return ListView(
children: items.map((item) => CartItemTile(item: item)).toList(),
);
}
}
```
## Dependency Injection
Constructor injection is preferred. Use `get_it` or Riverpod providers at composition root:
```dart
// get_it registration (in a setup file)
void setupDependencies() {
final di = GetIt.instance;
di.registerSingleton<ApiClient>(ApiClient(baseUrl: Env.apiUrl));
di.registerSingleton<UserRepository>(
UserRepositoryImpl(di<ApiClient>(), di<LocalDatabase>()),
);
di.registerFactory(() => UserListViewModel(di<UserRepository>()));
}
```
## ViewModel Pattern (without BLoC/Riverpod)
```dart
class UserListViewModel extends ChangeNotifier {
UserListViewModel(this._repository);
final UserRepository _repository;
AsyncState<List<User>> _state = const Loading();
AsyncState<List<User>> get state => _state;
Future<void> load() async {
_state = const Loading();
notifyListeners();
try {
final users = await _repository.getAll();
_state = Success(users);
} on Exception catch (e) {
_state = Failure(e);
}
notifyListeners();
}
}
```
## UseCase Pattern
```dart
class GetUserUseCase {
const GetUserUseCase(this._repository);
final UserRepository _repository;
Future<User?> call(String id) => _repository.getById(id);
}
class CreateUserUseCase {
const CreateUserUseCase(this._repository, this._idGenerator);
final UserRepository _repository;
final IdGenerator _idGenerator; // injected — domain layer must not depend on uuid package directly
Future<void> call(CreateUserInput input) async {
// Validate, apply business rules, then persist
final user = User(id: _idGenerator.generate(), name: input.name, email: input.email);
await _repository.save(user);
}
}
```
## Immutable State with freezed
```dart
@freezed
class UserState with _$UserState {
const factory UserState({
@Default([]) List<User> users,
@Default(false) bool isLoading,
String? errorMessage,
}) = _UserState;
}
```
## Clean Architecture Layer Boundaries
```
lib/
├── domain/ # Pure Dart — no Flutter, no external packages
│ ├── entities/
│ ├── repositories/ # Abstract interfaces
│ └── usecases/
├── data/ # Implements domain interfaces
│ ├── datasources/
│ ├── models/ # DTOs with fromJson/toJson
│ └── repositories/
└── presentation/ # Flutter widgets + state management
├── pages/
├── widgets/
└── providers/ (or blocs/ or viewmodels/)
```
- Domain must not import `package:flutter` or any data-layer package
- Data layer maps DTOs to domain entities at repository boundaries
- Presentation calls use cases, not repositories directly
## Navigation (GoRouter)
```dart
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/users/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return UserDetailPage(userId: id);
},
),
],
// refreshListenable re-evaluates redirect whenever auth state changes
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
final isLoggedIn = context.read<AuthCubit>().state is AuthAuthenticated;
if (!isLoggedIn && !state.matchedLocation.startsWith('/login')) {
return '/login';
}
return null;
},
);
```
## References
See skill: `flutter-dart-code-review` for the comprehensive review checklist.
See skill: `compose-multiplatform-patterns` for Kotlin Multiplatform/Flutter interop patterns.

View File

@@ -1,135 +0,0 @@
---
paths:
- "**/*.dart"
- "**/pubspec.yaml"
- "**/AndroidManifest.xml"
- "**/Info.plist"
---
# Dart/Flutter Security
> This file extends [common/security.md](../common/security.md) with Dart, Flutter, and mobile-specific content.
## Secrets Management
- Never hardcode API keys, tokens, or credentials in Dart source
- Use `--dart-define` or `--dart-define-from-file` for compile-time config (values are not truly secret — use a backend proxy for server-side secrets)
- Use `flutter_dotenv` or equivalent, with `.env` files listed in `.gitignore`
- Store runtime secrets in platform-secure storage: `flutter_secure_storage` (Keychain on iOS, EncryptedSharedPreferences on Android)
```dart
// BAD
const apiKey = 'sk-abc123...';
// GOOD — compile-time config (not secret, just configurable)
const apiKey = String.fromEnvironment('API_KEY');
// GOOD — runtime secret from secure storage
final token = await secureStorage.read(key: 'auth_token');
```
## Network Security
- Enforce HTTPS — no `http://` calls in production
- Configure Android `network_security_config.xml` to block cleartext traffic
- Set `NSAppTransportSecurity` in `Info.plist` to disallow arbitrary loads
- Set request timeouts on all HTTP clients — never leave defaults
- Consider certificate pinning for high-security endpoints
```dart
// Dio with timeout and HTTPS enforcement
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
));
```
## Input Validation
- Validate and sanitize all user input before sending to API or storage
- Never pass unsanitized input to SQL queries — use parameterized queries (sqflite, drift)
- Sanitize deep link URLs before navigation — validate scheme, host, and path parameters
- Use `Uri.tryParse` and validate before navigating
```dart
// BAD — SQL injection
await db.rawQuery("SELECT * FROM users WHERE email = '$userInput'");
// GOOD — parameterized
await db.query('users', where: 'email = ?', whereArgs: [userInput]);
// BAD — unvalidated deep link
final uri = Uri.parse(incomingLink);
context.go(uri.path); // could navigate to any route
// GOOD — validated deep link
final uri = Uri.tryParse(incomingLink);
if (uri != null && uri.host == 'myapp.com' && _allowedPaths.contains(uri.path)) {
context.go(uri.path);
}
```
## Data Protection
- Store tokens, PII, and credentials only in `flutter_secure_storage`
- Never write sensitive data to `SharedPreferences` or local files in plaintext
- Clear auth state on logout: tokens, cached user data, cookies
- Use biometric authentication (`local_auth`) for sensitive operations
- Avoid logging sensitive data — no `print(token)` or `debugPrint(password)`
## Android-Specific
- Declare only required permissions in `AndroidManifest.xml`
- Export Android components (`Activity`, `Service`, `BroadcastReceiver`) only when necessary; add `android:exported="false"` where not needed
- Review intent filters — exported components with implicit intent filters are accessible by any app
- Use `FLAG_SECURE` for screens displaying sensitive data (prevents screenshots)
```xml
<!-- AndroidManifest.xml — restrict exported components -->
<activity android:name=".MainActivity" android:exported="true">
<!-- Only the launcher activity needs exported=true -->
</activity>
<activity android:name=".SensitiveActivity" android:exported="false" />
```
## iOS-Specific
- Declare only required usage descriptions in `Info.plist` (`NSCameraUsageDescription`, etc.)
- Store secrets in Keychain — `flutter_secure_storage` uses Keychain on iOS
- Use App Transport Security (ATS) — disallow arbitrary loads
- Enable data protection entitlement for sensitive files
## WebView Security
- Use `webview_flutter` v4+ (`WebViewController` / `WebViewWidget`) — the legacy `WebView` widget is removed
- Disable JavaScript unless explicitly required (`JavaScriptMode.disabled`)
- Validate URLs before loading — never load arbitrary URLs from deep links
- Never expose Dart callbacks to JavaScript unless absolutely needed and carefully sandboxed
- Use `NavigationDelegate.onNavigationRequest` to intercept and validate navigation requests
```dart
// webview_flutter v4+ API (WebViewController + WebViewWidget)
final controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.disabled) // disabled unless required
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (request) {
final uri = Uri.tryParse(request.url);
if (uri == null || uri.host != 'trusted.example.com') {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
);
// In your widget tree:
WebViewWidget(controller: controller)
```
## Obfuscation and Build Security
- Enable obfuscation in release builds: `flutter build apk --obfuscate --split-debug-info=./debug-info/`
- Keep `--split-debug-info` output out of version control (used for crash symbolication only)
- Ensure ProGuard/R8 rules don't inadvertently expose serialized classes
- Run `flutter analyze` and address all warnings before release

View File

@@ -1,215 +0,0 @@
---
paths:
- "**/*.dart"
- "**/pubspec.yaml"
- "**/analysis_options.yaml"
---
# Dart/Flutter Testing
> This file extends [common/testing.md](../common/testing.md) with Dart and Flutter-specific content.
## Test Framework
- **flutter_test** / **dart:test** — built-in test runner
- **mockito** (with `@GenerateMocks`) or **mocktail** (no codegen) for mocking
- **bloc_test** for BLoC/Cubit unit tests
- **fake_async** for controlling time in unit tests
- **integration_test** for end-to-end device tests
## Test Types
| Type | Tool | Location | When to Write |
|------|------|----------|---------------|
| Unit | `dart:test` | `test/unit/` | All domain logic, state managers, repositories |
| Widget | `flutter_test` | `test/widget/` | All widgets with meaningful behavior |
| Golden | `flutter_test` | `test/golden/` | Design-critical UI components |
| Integration | `integration_test` | `integration_test/` | Critical user flows on real device/emulator |
## Unit Tests: State Managers
### BLoC with `bloc_test`
```dart
group('CartBloc', () {
late CartBloc bloc;
late MockCartRepository repository;
setUp(() {
repository = MockCartRepository();
bloc = CartBloc(repository);
});
tearDown(() => bloc.close());
blocTest<CartBloc, CartState>(
'emits updated items when CartItemAdded',
build: () => bloc,
act: (b) => b.add(CartItemAdded(testItem)),
expect: () => [CartState(items: [testItem])],
);
blocTest<CartBloc, CartState>(
'emits empty cart when CartCleared',
seed: () => CartState(items: [testItem]),
build: () => bloc,
act: (b) => b.add(CartCleared()),
expect: () => [const CartState()],
);
});
```
### Riverpod with `ProviderContainer`
```dart
test('usersProvider loads users from repository', () async {
final container = ProviderContainer(
overrides: [userRepositoryProvider.overrideWithValue(FakeUserRepository())],
);
addTearDown(container.dispose);
final result = await container.read(usersProvider.future);
expect(result, isNotEmpty);
});
```
## Widget Tests
```dart
testWidgets('CartPage shows item count badge', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
cartNotifierProvider.overrideWith(() => FakeCartNotifier([testItem])),
],
child: const MaterialApp(home: CartPage()),
),
);
await tester.pump();
expect(find.text('1'), findsOneWidget);
expect(find.byType(CartItemTile), findsOneWidget);
});
testWidgets('shows empty state when cart is empty', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [cartNotifierProvider.overrideWith(() => FakeCartNotifier([]))],
child: const MaterialApp(home: CartPage()),
),
);
await tester.pump();
expect(find.text('Your cart is empty'), findsOneWidget);
});
```
## Fakes Over Mocks
Prefer hand-written fakes for complex dependencies:
```dart
class FakeUserRepository implements UserRepository {
final _users = <String, User>{};
Object? fetchError;
@override
Future<User?> getById(String id) async {
if (fetchError != null) throw fetchError!;
return _users[id];
}
@override
Future<List<User>> getAll() async {
if (fetchError != null) throw fetchError!;
return _users.values.toList();
}
@override
Stream<List<User>> watchAll() => Stream.value(_users.values.toList());
@override
Future<void> save(User user) async {
_users[user.id] = user;
}
@override
Future<void> delete(String id) async {
_users.remove(id);
}
void addUser(User user) => _users[user.id] = user;
}
```
## Async Testing
```dart
// Use fake_async for controlling timers and Futures
test('debounce triggers after 300ms', () {
fakeAsync((async) {
final debouncer = Debouncer(delay: const Duration(milliseconds: 300));
var callCount = 0;
debouncer.run(() => callCount++);
expect(callCount, 0);
async.elapse(const Duration(milliseconds: 200));
expect(callCount, 0);
async.elapse(const Duration(milliseconds: 200));
expect(callCount, 1);
});
});
```
## Golden Tests
```dart
testWidgets('UserCard golden test', (tester) async {
await tester.pumpWidget(
MaterialApp(home: UserCard(user: testUser)),
);
await expectLater(
find.byType(UserCard),
matchesGoldenFile('goldens/user_card.png'),
);
});
```
Run `flutter test --update-goldens` when intentional visual changes are made.
## Test Naming
Use descriptive, behavior-focused names:
```dart
test('returns null when user does not exist', () { ... });
test('throws NotFoundException when id is empty string', () { ... });
testWidgets('disables submit button while form is invalid', (tester) async { ... });
```
## Test Organization
```
test/
├── unit/
│ ├── domain/
│ │ └── usecases/
│ └── data/
│ └── repositories/
├── widget/
│ └── presentation/
│ └── pages/
└── golden/
└── widgets/
integration_test/
└── flows/
├── login_flow_test.dart
└── checkout_flow_test.dart
```
## Coverage
- Target 80%+ line coverage for business logic (domain + state managers)
- All state transitions must have tests: loading → success, loading → error, retry
- Run `flutter test --coverage` and inspect `lcov.info` with a coverage reporter
- Coverage failures should block CI when below threshold

View File

@@ -1,96 +0,0 @@
> This file extends [common/coding-style.md](../common/coding-style.md) with web-specific frontend content.
# Web Coding Style
## File Organization
Organize by feature or surface area, not by file type:
```text
src/
├── components/
│ ├── hero/
│ │ ├── Hero.tsx
│ │ ├── HeroVisual.tsx
│ │ └── hero.css
│ ├── scrolly-section/
│ │ ├── ScrollySection.tsx
│ │ ├── StickyVisual.tsx
│ │ └── scrolly.css
│ └── ui/
│ ├── Button.tsx
│ ├── SurfaceCard.tsx
│ └── AnimatedText.tsx
├── hooks/
│ ├── useReducedMotion.ts
│ └── useScrollProgress.ts
├── lib/
│ ├── animation.ts
│ └── color.ts
└── styles/
├── tokens.css
├── typography.css
└── global.css
```
## CSS Custom Properties
Define design tokens as variables. Do not hardcode palette, typography, or spacing repeatedly:
```css
:root {
--color-surface: oklch(98% 0 0);
--color-text: oklch(18% 0 0);
--color-accent: oklch(68% 0.21 250);
--text-base: clamp(1rem, 0.92rem + 0.4vw, 1.125rem);
--text-hero: clamp(3rem, 1rem + 7vw, 8rem);
--space-section: clamp(4rem, 3rem + 5vw, 10rem);
--duration-fast: 150ms;
--duration-normal: 300ms;
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
```
## Animation-Only Properties
Prefer compositor-friendly motion:
- `transform`
- `opacity`
- `clip-path`
- `filter` (sparingly)
Avoid animating layout-bound properties:
- `width`
- `height`
- `top`
- `left`
- `margin`
- `padding`
- `border`
- `font-size`
## Semantic HTML First
```html
<header>
<nav aria-label="Main navigation">...</nav>
</header>
<main>
<section aria-labelledby="hero-heading">
<h1 id="hero-heading">...</h1>
</section>
</main>
<footer>...</footer>
```
Do not reach for generic wrapper `div` stacks when a semantic element exists.
## Naming
- Components: PascalCase (`ScrollySection`, `SurfaceCard`)
- Hooks: `use` prefix (`useReducedMotion`)
- CSS classes: kebab-case or utility classes
- Animation timelines: camelCase with intent (`heroRevealTl`)

View File

@@ -1,63 +0,0 @@
> This file extends [common/patterns.md](../common/patterns.md) with web-specific design-quality guidance.
# Web Design Quality Standards
## Anti-Template Policy
Do not ship generic template-looking UI. Frontend output should look intentional, opinionated, and specific to the product.
### Banned Patterns
- Default card grids with uniform spacing and no hierarchy
- Stock hero section with centered headline, gradient blob, and generic CTA
- Unmodified library defaults passed off as finished design
- Flat layouts with no layering, depth, or motion
- Uniform radius, spacing, and shadows across every component
- Safe gray-on-white styling with one decorative accent color
- Dashboard-by-numbers layouts with sidebar + cards + charts and no point of view
- Default font stacks used without a deliberate reason
### Required Qualities
Every meaningful frontend surface should demonstrate at least four of these:
1. Clear hierarchy through scale contrast
2. Intentional rhythm in spacing, not uniform padding everywhere
3. Depth or layering through overlap, shadows, surfaces, or motion
4. Typography with character and a real pairing strategy
5. Color used semantically, not just decoratively
6. Hover, focus, and active states that feel designed
7. Grid-breaking editorial or bento composition where appropriate
8. Texture, grain, or atmosphere when it fits the visual direction
9. Motion that clarifies flow instead of distracting from it
10. Data visualization treated as part of the design system, not an afterthought
## Before Writing Frontend Code
1. Pick a specific style direction. Avoid vague defaults like "clean minimal".
2. Define a palette intentionally.
3. Choose typography deliberately.
4. Gather at least a small set of real references.
5. Use ECC design/frontend skills where relevant.
## Worthwhile Style Directions
- Editorial / magazine
- Neo-brutalism
- Glassmorphism with real depth
- Dark luxury or light luxury with disciplined contrast
- Bento layouts
- Scrollytelling
- 3D integration
- Swiss / International
- Retro-futurism
Do not default to dark mode automatically. Choose the visual direction the product actually wants.
## Component Checklist
- [ ] Does it avoid looking like a default Tailwind or shadcn template?
- [ ] Does it have intentional hover/focus/active states?
- [ ] Does it use hierarchy rather than uniform emphasis?
- [ ] Would this look believable in a real product screenshot?
- [ ] If it supports both themes, do both light and dark feel intentional?

View File

@@ -1,120 +0,0 @@
> This file extends [common/hooks.md](../common/hooks.md) with web-specific hook recommendations.
# Web Hooks
## Recommended PostToolUse Hooks
Prefer project-local tooling. Do not wire hooks to remote one-off package execution.
### Format on Save
Use the project's existing formatter entrypoint after edits:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm prettier --write \"$FILE_PATH\"",
"description": "Format edited frontend files"
}
]
}
}
```
Equivalent local commands via `yarn prettier` or `npm exec prettier --` are fine when they use repo-owned dependencies.
### Lint Check
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm eslint --fix \"$FILE_PATH\"",
"description": "Run ESLint on edited frontend files"
}
]
}
}
```
### Type Check
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm tsc --noEmit --pretty false",
"description": "Type-check after frontend edits"
}
]
}
}
```
### CSS Lint
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "pnpm stylelint --fix \"$FILE_PATH\"",
"description": "Lint edited stylesheets"
}
]
}
}
```
## PreToolUse Hooks
### Guard File Size
Block oversized writes from tool input content, not from a file that may not exist yet:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller modules');process.exit(2)}console.log(d)})\"",
"description": "Block writes that exceed 800 lines"
}
]
}
}
```
## Stop Hooks
### Final Build Verification
```json
{
"hooks": {
"Stop": [
{
"command": "pnpm build",
"description": "Verify the production build at session end"
}
]
}
}
```
## Ordering
Recommended order:
1. format
2. lint
3. type check
4. build verification

View File

@@ -1,79 +0,0 @@
> This file extends [common/patterns.md](../common/patterns.md) with web-specific patterns.
# Web Patterns
## Component Composition
### Compound Components
Use compound components when related UI shares state and interaction semantics:
```tsx
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">...</Tabs.Content>
<Tabs.Content value="settings">...</Tabs.Content>
</Tabs>
```
- Parent owns state
- Children consume via context
- Prefer this over prop drilling for complex widgets
### Render Props / Slots
- Use render props or slot patterns when behavior is shared but markup must vary
- Keep keyboard handling, ARIA, and focus logic in the headless layer
### Container / Presentational Split
- Container components own data loading and side effects
- Presentational components receive props and render UI
- Presentational components should stay pure
## State Management
Treat these separately:
| Concern | Tooling |
|---------|---------|
| Server state | TanStack Query, SWR, tRPC |
| Client state | Zustand, Jotai, signals |
| URL state | search params, route segments |
| Form state | React Hook Form or equivalent |
- Do not duplicate server state into client stores
- Derive values instead of storing redundant computed state
## URL As State
Persist shareable state in the URL:
- filters
- sort order
- pagination
- active tab
- search query
## Data Fetching
### Stale-While-Revalidate
- Return cached data immediately
- Revalidate in the background
- Prefer existing libraries instead of rolling this by hand
### Optimistic Updates
- Snapshot current state
- Apply optimistic update
- Roll back on failure
- Emit visible error feedback when rolling back
### Parallel Loading
- Fetch independent data in parallel
- Avoid parent-child request waterfalls
- Prefetch likely next routes or states when justified

View File

@@ -1,64 +0,0 @@
> This file extends [common/performance.md](../common/performance.md) with web-specific performance content.
# Web Performance Rules
## Core Web Vitals Targets
| Metric | Target |
|--------|--------|
| LCP | < 2.5s |
| INP | < 200ms |
| CLS | < 0.1 |
| FCP | < 1.5s |
| TBT | < 200ms |
## Bundle Budget
| Page Type | JS Budget (gzipped) | CSS Budget |
|-----------|---------------------|------------|
| Landing page | < 150kb | < 30kb |
| App page | < 300kb | < 50kb |
| Microsite | < 80kb | < 15kb |
## Loading Strategy
1. Inline critical above-the-fold CSS where justified
2. Preload the hero image and primary font only
3. Defer non-critical CSS or JS
4. Dynamically import heavy libraries
```js
const gsapModule = await import('gsap');
const { ScrollTrigger } = await import('gsap/ScrollTrigger');
```
## Image Optimization
- Explicit `width` and `height`
- `loading="eager"` plus `fetchpriority="high"` for hero media only
- `loading="lazy"` for below-the-fold assets
- Prefer AVIF or WebP with fallbacks
- Never ship source images far beyond rendered size
## Font Loading
- Max two font families unless there is a clear exception
- `font-display: swap`
- Subset where possible
- Preload only the truly critical weight/style
## Animation Performance
- Animate compositor-friendly properties only
- Use `will-change` narrowly and remove it when done
- Prefer CSS for simple transitions
- Use `requestAnimationFrame` or established animation libraries for JS motion
- Avoid scroll handler churn; use IntersectionObserver or well-behaved libraries
## Performance Checklist
- [ ] All images have explicit dimensions
- [ ] No accidental render-blocking resources
- [ ] No layout shifts from dynamic content
- [ ] Motion stays on compositor-friendly properties
- [ ] Third-party scripts load async/defer and only when needed

View File

@@ -1,57 +0,0 @@
> This file extends [common/security.md](../common/security.md) with web-specific security content.
# Web Security Rules
## Content Security Policy
Always configure a production CSP.
### Nonce-Based CSP
Use a per-request nonce for scripts instead of `'unsafe-inline'`.
```text
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://*.example.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
```
Adjust origins to the project. Do not cargo-cult this block unchanged.
## XSS Prevention
- Never inject unsanitized HTML
- Avoid `innerHTML` / `dangerouslySetInnerHTML` unless sanitized first
- Escape dynamic template values
- Sanitize user HTML with a vetted local sanitizer when absolutely necessary
## Third-Party Scripts
- Load asynchronously
- Use SRI when serving from a CDN
- Audit quarterly
- Prefer self-hosting for critical dependencies when practical
## HTTPS and Headers
```text
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
```
## Forms
- CSRF protection on state-changing forms
- Rate limiting on submission endpoints
- Validate client and server side
- Prefer honeypots or light anti-abuse controls over heavy-handed CAPTCHA defaults

View File

@@ -1,55 +0,0 @@
> This file extends [common/testing.md](../common/testing.md) with web-specific testing content.
# Web Testing Rules
## Priority Order
### 1. Visual Regression
- Screenshot key breakpoints: 320, 768, 1024, 1440
- Test hero sections, scrollytelling sections, and meaningful states
- Use Playwright screenshots for visual-heavy work
- If both themes exist, test both
### 2. Accessibility
- Run automated accessibility checks
- Test keyboard navigation
- Verify reduced-motion behavior
- Verify color contrast
### 3. Performance
- Run Lighthouse or equivalent against meaningful pages
- Keep CWV targets from [performance.md](performance.md)
### 4. Cross-Browser
- Minimum: Chrome, Firefox, Safari
- Test scrolling, motion, and fallback behavior
### 5. Responsive
- Test 320, 375, 768, 1024, 1440, 1920
- Verify no overflow
- Verify touch interactions
## E2E Shape
```ts
import { test, expect } from '@playwright/test';
test('landing hero loads', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});
```
- Avoid flaky timeout-based assertions
- Prefer deterministic waits
## Unit Tests
- Test utilities, data transforms, and custom hooks
- For highly visual components, visual regression often carries more signal than brittle markup assertions
- Visual regression supplements coverage targets; it does not replace them

View File

@@ -1,131 +0,0 @@
#!/usr/bin/env node
/**
* PostToolUse hook: lightweight frontend design-quality reminder.
*
* This stays self-contained inside ECC. It does not call remote models or
* install packages. The goal is to catch obviously generic UI drift and keep
* frontend edits aligned with ECC's stronger design standards.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const FRONTEND_EXTENSIONS = /\.(astro|css|html|jsx|scss|svelte|tsx|vue)$/i;
const MAX_STDIN = 1024 * 1024;
const GENERIC_SIGNALS = [
{ pattern: /\bget started\b/i, label: '"Get Started" CTA copy' },
{ pattern: /\blearn more\b/i, label: '"Learn more" CTA copy' },
{ pattern: /\bgrid-cols-(3|4)\b/, label: 'uniform multi-card grid' },
{ pattern: /\bbg-gradient-to-[trbl]/, label: 'stock gradient utility usage' },
{ pattern: /\btext-center\b/, label: 'centered default layout cues' },
{ pattern: /\bfont-(sans|inter)\b/i, label: 'default font utility' },
];
const CHECKLIST = [
'visual hierarchy with real contrast',
'intentional spacing rhythm',
'depth, layering, or overlap',
'purposeful hover and focus states',
'color and typography that feel specific',
];
function getFilePaths(input) {
const toolInput = input?.tool_input || {};
if (toolInput.file_path) {
return [String(toolInput.file_path)];
}
if (Array.isArray(toolInput.edits)) {
return toolInput.edits
.map(edit => String(edit?.file_path || ''))
.filter(Boolean);
}
return [];
}
function readContent(filePath) {
try {
return fs.readFileSync(path.resolve(filePath), 'utf8');
} catch {
return '';
}
}
function detectSignals(content) {
return GENERIC_SIGNALS.filter(signal => signal.pattern.test(content)).map(signal => signal.label);
}
function buildWarning(frontendPaths, findings) {
const pathLines = frontendPaths.map(fp => ` - ${fp}`).join('\n');
const signalLines = findings.length > 0
? findings.map(item => ` - ${item}`).join('\n')
: ' - no obvious canned-template strings detected';
return [
'[Hook] DESIGN CHECK: frontend file(s) modified:',
pathLines,
'[Hook] Review for generic/template drift. Frontend should have:',
CHECKLIST.map(item => ` - ${item}`).join('\n'),
'[Hook] Heuristic signals:',
signalLines,
].join('\n');
}
function run(inputOrRaw) {
let input;
let rawInput = inputOrRaw;
try {
if (typeof inputOrRaw === 'string') {
rawInput = inputOrRaw;
input = inputOrRaw.trim() ? JSON.parse(inputOrRaw) : {};
} else {
input = inputOrRaw || {};
rawInput = JSON.stringify(inputOrRaw ?? {});
}
} catch {
return { exitCode: 0, stdout: typeof rawInput === 'string' ? rawInput : '' };
}
const filePaths = getFilePaths(input);
const frontendPaths = filePaths.filter(filePath => FRONTEND_EXTENSIONS.test(filePath));
if (frontendPaths.length === 0) {
return { exitCode: 0, stdout: typeof rawInput === 'string' ? rawInput : '' };
}
const findings = [];
for (const filePath of frontendPaths) {
const content = readContent(filePath);
findings.push(...detectSignals(content));
}
return {
exitCode: 0,
stdout: typeof rawInput === 'string' ? rawInput : '',
stderr: buildWarning(frontendPaths, findings),
};
}
module.exports = { run };
if (require.main === module) {
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) {
const remaining = MAX_STDIN - raw.length;
raw += chunk.substring(0, remaining);
}
});
process.stdin.on('end', () => {
const result = run(raw);
if (result.stderr) process.stderr.write(`${result.stderr}\n`);
process.stdout.write(typeof result.stdout === 'string' ? result.stdout : raw);
process.exit(Number.isInteger(result.exitCode) ? result.exitCode : 0);
});
}

View File

@@ -2,50 +2,12 @@
'use strict'; 'use strict';
/** /**
* Session end marker hook - performs lightweight observer cleanup and * Session end marker hook - outputs stdin to stdout unchanged.
* outputs stdin to stdout unchanged. Exports run() for in-process execution. * Exports run() for in-process execution (avoids spawnSync issues on Windows).
*/ */
const {
resolveProjectContext,
removeSessionLease,
listSessionLeases,
stopObserverForContext,
resolveSessionId
} = require('../lib/observer-sessions');
function log(message) {
process.stderr.write(`[SessionEnd] ${message}\n`);
}
function run(rawInput) { function run(rawInput) {
const output = rawInput || ''; return rawInput || '';
const sessionId = resolveSessionId();
if (!sessionId) {
log('No CLAUDE_SESSION_ID available; skipping observer cleanup');
return output;
}
try {
const observerContext = resolveProjectContext();
removeSessionLease(observerContext, sessionId);
const remainingLeases = listSessionLeases(observerContext);
if (remainingLeases.length === 0) {
if (stopObserverForContext(observerContext)) {
log(`Stopped observer for project ${observerContext.projectId} after final session lease ended`);
} else {
log(`No running observer to stop for project ${observerContext.projectId}`);
}
} else {
log(`Retained observer for project ${observerContext.projectId}; ${remainingLeases.length} session lease(s) remain`);
}
} catch (err) {
log(`Observer cleanup skipped: ${err.message}`);
}
return output;
} }
// Legacy CLI execution (when run directly) // Legacy CLI execution (when run directly)
@@ -60,7 +22,7 @@ if (require.main === module) {
} }
}); });
process.stdin.on('end', () => { process.stdin.on('end', () => {
process.stdout.write(run(raw)); process.stdout.write(raw);
}); });
} }

View File

@@ -20,7 +20,6 @@ const {
stripAnsi, stripAnsi,
log log
} = require('../lib/utils'); } = require('../lib/utils');
const { resolveProjectContext, writeSessionLease, resolveSessionId } = require('../lib/observer-sessions');
const { getPackageManager, getSelectionPrompt } = require('../lib/package-manager'); const { getPackageManager, getSelectionPrompt } = require('../lib/package-manager');
const { listAliases } = require('../lib/session-aliases'); const { listAliases } = require('../lib/session-aliases');
const { detectProjectType } = require('../lib/project-detect'); const { detectProjectType } = require('../lib/project-detect');
@@ -164,18 +163,6 @@ async function main() {
ensureDir(sessionsDir); ensureDir(sessionsDir);
ensureDir(learnedDir); ensureDir(learnedDir);
const observerSessionId = resolveSessionId();
if (observerSessionId) {
const observerContext = resolveProjectContext();
writeSessionLease(observerContext, observerSessionId, {
hook: 'SessionStart',
projectRoot: observerContext.projectRoot
});
log(`[SessionStart] Registered observer lease for ${observerSessionId}`);
} else {
log('[SessionStart] No CLAUDE_SESSION_ID available; skipping observer lease registration');
}
// Check for recent session files (last 7 days) // Check for recent session files (last 7 days)
const recentSessions = dedupeRecentSessions(getSessionSearchDirs()); const recentSessions = dedupeRecentSessions(getSessionSearchDirs());

View File

@@ -1,175 +0,0 @@
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { spawnSync } = require('child_process');
const { getClaudeDir, ensureDir, sanitizeSessionId } = require('./utils');
function getHomunculusDir() {
return path.join(getClaudeDir(), 'homunculus');
}
function getProjectsDir() {
return path.join(getHomunculusDir(), 'projects');
}
function getProjectRegistryPath() {
return path.join(getHomunculusDir(), 'projects.json');
}
function readProjectRegistry() {
try {
return JSON.parse(fs.readFileSync(getProjectRegistryPath(), 'utf8'));
} catch {
return {};
}
}
function runGit(args, cwd) {
const result = spawnSync('git', args, {
cwd,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore']
});
if (result.status !== 0) return '';
return (result.stdout || '').trim();
}
function stripRemoteCredentials(remoteUrl) {
if (!remoteUrl) return '';
return String(remoteUrl).replace(/:\/\/[^@]+@/, '://');
}
function resolveProjectRoot(cwd = process.cwd()) {
const envRoot = process.env.CLAUDE_PROJECT_DIR;
if (envRoot && fs.existsSync(envRoot)) {
return path.resolve(envRoot);
}
const gitRoot = runGit(['rev-parse', '--show-toplevel'], cwd);
if (gitRoot) return path.resolve(gitRoot);
return '';
}
function computeProjectId(projectRoot) {
const remoteUrl = stripRemoteCredentials(runGit(['remote', 'get-url', 'origin'], projectRoot));
return crypto.createHash('sha256').update(remoteUrl || projectRoot).digest('hex').slice(0, 12);
}
function resolveProjectContext(cwd = process.cwd()) {
const projectRoot = resolveProjectRoot(cwd);
if (!projectRoot) {
const projectDir = getHomunculusDir();
ensureDir(projectDir);
return { projectId: 'global', projectRoot: '', projectDir, isGlobal: true };
}
const registry = readProjectRegistry();
const registryEntry = Object.values(registry).find(entry => entry && path.resolve(entry.root || '') === projectRoot);
const projectId = registryEntry?.id || computeProjectId(projectRoot);
const projectDir = path.join(getProjectsDir(), projectId);
ensureDir(projectDir);
return { projectId, projectRoot, projectDir, isGlobal: false };
}
function getObserverPidFile(context) {
return path.join(context.projectDir, '.observer.pid');
}
function getObserverSignalCounterFile(context) {
return path.join(context.projectDir, '.observer-signal-counter');
}
function getObserverActivityFile(context) {
return path.join(context.projectDir, '.observer-last-activity');
}
function getSessionLeaseDir(context) {
return path.join(context.projectDir, '.observer-sessions');
}
function resolveSessionId(rawSessionId = process.env.CLAUDE_SESSION_ID) {
return sanitizeSessionId(rawSessionId || '') || '';
}
function getSessionLeaseFile(context, rawSessionId = process.env.CLAUDE_SESSION_ID) {
const sessionId = resolveSessionId(rawSessionId);
if (!sessionId) return '';
return path.join(getSessionLeaseDir(context), `${sessionId}.json`);
}
function writeSessionLease(context, rawSessionId = process.env.CLAUDE_SESSION_ID, extra = {}) {
const leaseFile = getSessionLeaseFile(context, rawSessionId);
if (!leaseFile) return '';
ensureDir(getSessionLeaseDir(context));
const payload = {
sessionId: resolveSessionId(rawSessionId),
cwd: process.cwd(),
pid: process.pid,
updatedAt: new Date().toISOString(),
...extra
};
fs.writeFileSync(leaseFile, JSON.stringify(payload, null, 2) + '\n');
return leaseFile;
}
function removeSessionLease(context, rawSessionId = process.env.CLAUDE_SESSION_ID) {
const leaseFile = getSessionLeaseFile(context, rawSessionId);
if (!leaseFile) return false;
try {
fs.rmSync(leaseFile, { force: true });
return true;
} catch {
return false;
}
}
function listSessionLeases(context) {
const leaseDir = getSessionLeaseDir(context);
if (!fs.existsSync(leaseDir)) return [];
return fs.readdirSync(leaseDir)
.filter(name => name.endsWith('.json'))
.map(name => path.join(leaseDir, name));
}
function stopObserverForContext(context) {
const pidFile = getObserverPidFile(context);
if (!fs.existsSync(pidFile)) return false;
const pid = (fs.readFileSync(pidFile, 'utf8') || '').trim();
if (!/^[0-9]+$/.test(pid) || pid === '0' || pid === '1') {
fs.rmSync(pidFile, { force: true });
return false;
}
try {
process.kill(Number(pid), 0);
} catch {
fs.rmSync(pidFile, { force: true });
return false;
}
try {
process.kill(Number(pid), 'SIGTERM');
} catch {
return false;
}
fs.rmSync(pidFile, { force: true });
fs.rmSync(getObserverSignalCounterFile(context), { force: true });
return true;
}
module.exports = {
resolveProjectContext,
getObserverActivityFile,
getObserverPidFile,
getSessionLeaseDir,
writeSessionLease,
removeSessionLease,
listSessionLeases,
stopObserverForContext,
resolveSessionId
};

View File

@@ -25,10 +25,6 @@ const WINDOWS_RESERVED_SESSION_IDS = new Set([
* Get the user's home directory (cross-platform) * Get the user's home directory (cross-platform)
*/ */
function getHomeDir() { function getHomeDir() {
const explicitHome = process.env.HOME || process.env.USERPROFILE;
if (explicitHome && explicitHome.trim().length > 0) {
return path.resolve(explicitHome);
}
return os.homedir(); return os.homedir();
} }

View File

@@ -23,23 +23,50 @@ Write long-form content that sounds like an actual person with a point of view,
4. Use proof instead of adjectives. 4. Use proof instead of adjectives.
5. Never invent facts, credibility, or customer evidence. 5. Never invent facts, credibility, or customer evidence.
## Voice Handling ## Voice Capture Workflow
If the user wants a specific voice, run `brand-voice` first and reuse its `VOICE PROFILE`. If the user wants a specific voice, collect one or more of:
Do not duplicate a second style-analysis pass here unless the user explicitly asks for one. - published articles
- newsletters
- X posts or threads
- docs or memos
- launch notes
- a style guide
Then extract:
- sentence length and rhythm
- whether the writing is compressed, explanatory, sharp, or formal
- how parentheses are used
- how often the writer asks questions
- whether the writer uses fragments, lists, or hard pivots
- formatting habits such as headers, bullets, code blocks, pull quotes
- what the writer clearly avoids
If no voice references are given, default to a sharp operator voice: concrete, unsentimental, useful. If no voice references are given, default to a sharp operator voice: concrete, unsentimental, useful.
## Affaan / ECC Voice Reference
When matching Affaan / ECC voice, bias toward:
- direct claims over scene-setting
- high specificity
- parentheticals used for qualification or over-clarification, not comedy
- capitalization chosen situationally, not as a gimmick
- very low tolerance for fake thought-leadership cadence
- almost no bait questions
## Banned Patterns ## Banned Patterns
Delete and rewrite any of these: Delete and rewrite any of these:
- "In today's rapidly evolving landscape" - "In today's rapidly evolving landscape"
- "game-changer", "cutting-edge", "revolutionary" - "game-changer", "cutting-edge", "revolutionary"
- "no fluff"
- "not X, just Y"
- "here's why this matters" as a standalone bridge - "here's why this matters" as a standalone bridge
- fake vulnerability arcs - fake vulnerability arcs
- a closing question added only to juice engagement - a closing question added only to juice engagement
- forced lowercase
- corny parenthetical asides
- biography padding that does not move the argument - biography padding that does not move the argument
- generic AI throat-clearing that delays the point
## Writing Process ## Writing Process
@@ -74,6 +101,6 @@ Delete and rewrite any of these:
Before delivering: Before delivering:
- factual claims are backed by provided sources - factual claims are backed by provided sources
- generic AI transitions are gone - generic AI transitions are gone
- the voice matches the supplied examples or the agreed `VOICE PROFILE` - the voice matches the supplied examples
- every section adds something new - every section adds something new
- formatting matches the intended medium - formatting matches the intended medium

View File

@@ -38,28 +38,50 @@ Before drafting, identify the source set:
If the user wants a specific voice, build a voice profile from real examples before writing. If the user wants a specific voice, build a voice profile from real examples before writing.
Use `brand-voice` as the canonical workflow when voice consistency matters across more than one output. Use `brand-voice` as the canonical workflow when voice consistency matters across more than one output.
## Voice Handling ## Voice Capture Workflow
`brand-voice` is the canonical voice layer. Run `brand-voice` first when:
Run it first when:
- there are multiple downstream outputs - there are multiple downstream outputs
- the user explicitly cares about writing style - the user explicitly cares about writing style
- the content is launch, outreach, or reputation-sensitive - the content is launch, outreach, or reputation-sensitive
Reuse the resulting `VOICE PROFILE` here instead of rebuilding a second voice model. At minimum, produce a compact `VOICE PROFILE` covering:
If the user wants Affaan / ECC voice specifically, still treat `brand-voice` as the source of truth and feed it the best live or source-derived material available. - rhythm
- compression
- capitalization
- parenthetical use
- question use
- preferred moves
- banned moves
Do not start drafting until the voice profile is clear enough to enforce.
## Affaan / ECC Voice Reference
When the user wants Affaan / ECC voice specifically, default to this unless newer examples clearly override it:
- direct, compressed, concrete
- strong preference for specific claims, numbers, mechanisms, and receipts
- parentheticals used to qualify, narrow, or over-clarify, not to do corny bits
- lowercase is optional, not mandatory
- questions are rare and should not be added as bait
- transitions should feel earned, not polished
- tone can be sharp or blunt, but should not sound like a content marketer
## Hard Bans ## Hard Bans
Delete and rewrite any of these: Delete and rewrite any of these:
- "In today's rapidly evolving landscape" - "In today's rapidly evolving landscape"
- "game-changer", "revolutionary", "cutting-edge" - "game-changer", "revolutionary", "cutting-edge"
- "no fluff"
- "not X, just Y"
- "here's why this matters" unless it is followed immediately by something concrete - "here's why this matters" unless it is followed immediately by something concrete
- "Excited to share"
- fake curiosity gaps
- ending with a LinkedIn-style question just to farm replies - ending with a LinkedIn-style question just to farm replies
- forced lowercase when the source voice does not call for it
- forced casualness on LinkedIn - forced casualness on LinkedIn
- fake engagement padding that was not present in the source material - parenthetical jokes that were not present in the source voice
## Platform Adaptation Rules ## Platform Adaptation Rules

View File

@@ -14,9 +14,6 @@ ANALYZING=0
LAST_ANALYSIS_EPOCH=0 LAST_ANALYSIS_EPOCH=0
# Minimum seconds between analyses (prevents rapid re-triggering) # Minimum seconds between analyses (prevents rapid re-triggering)
ANALYSIS_COOLDOWN="${ECC_OBSERVER_ANALYSIS_COOLDOWN:-60}" ANALYSIS_COOLDOWN="${ECC_OBSERVER_ANALYSIS_COOLDOWN:-60}"
IDLE_TIMEOUT_SECONDS="${ECC_OBSERVER_IDLE_TIMEOUT_SECONDS:-1800}"
SESSION_LEASE_DIR="${PROJECT_DIR}/.observer-sessions"
ACTIVITY_FILE="${PROJECT_DIR}/.observer-last-activity"
cleanup() { cleanup() {
[ -n "$SLEEP_PID" ] && kill "$SLEEP_PID" 2>/dev/null [ -n "$SLEEP_PID" ] && kill "$SLEEP_PID" 2>/dev/null
@@ -27,62 +24,6 @@ cleanup() {
} }
trap cleanup TERM INT trap cleanup TERM INT
file_mtime_epoch() {
local file="$1"
if [ ! -f "$file" ]; then
printf '0\n'
return
fi
if stat -c %Y "$file" >/dev/null 2>&1; then
stat -c %Y "$file" 2>/dev/null || printf '0\n'
return
fi
if stat -f %m "$file" >/dev/null 2>&1; then
stat -f %m "$file" 2>/dev/null || printf '0\n'
return
fi
printf '0\n'
}
has_active_session_leases() {
if [ ! -d "$SESSION_LEASE_DIR" ]; then
return 1
fi
find "$SESSION_LEASE_DIR" -type f -name '*.json' -print -quit 2>/dev/null | grep -q .
}
latest_activity_epoch() {
local observations_epoch activity_epoch
observations_epoch="$(file_mtime_epoch "$OBSERVATIONS_FILE")"
activity_epoch="$(file_mtime_epoch "$ACTIVITY_FILE")"
if [ "$activity_epoch" -gt "$observations_epoch" ] 2>/dev/null; then
printf '%s\n' "$activity_epoch"
else
printf '%s\n' "$observations_epoch"
fi
}
exit_if_idle_without_sessions() {
if has_active_session_leases; then
return
fi
local last_activity now_epoch idle_for
last_activity="$(latest_activity_epoch)"
now_epoch="$(date +%s)"
idle_for=$(( now_epoch - last_activity ))
if [ "$last_activity" -eq 0 ] || [ "$idle_for" -ge "$IDLE_TIMEOUT_SECONDS" ]; then
echo "[$(date)] Observer idle without active session leases for ${idle_for}s; exiting" >> "$LOG_FILE"
cleanup
fi
}
analyze_observations() { analyze_observations() {
if [ ! -f "$OBSERVATIONS_FILE" ]; then if [ ! -f "$OBSERVATIONS_FILE" ]; then
return return
@@ -256,13 +197,11 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
"${CLV2_PYTHON_CMD:-python3}" "${SCRIPT_DIR}/../scripts/instinct-cli.py" prune --quiet >> "$LOG_FILE" 2>&1 || echo "[$(date)] Warning: instinct prune failed (non-fatal)" >> "$LOG_FILE" "${CLV2_PYTHON_CMD:-python3}" "${SCRIPT_DIR}/../scripts/instinct-cli.py" prune --quiet >> "$LOG_FILE" 2>&1 || echo "[$(date)] Warning: instinct prune failed (non-fatal)" >> "$LOG_FILE"
while true; do while true; do
exit_if_idle_without_sessions
sleep "$OBSERVER_INTERVAL_SECONDS" & sleep "$OBSERVER_INTERVAL_SECONDS" &
SLEEP_PID=$! SLEEP_PID=$!
wait "$SLEEP_PID" 2>/dev/null wait "$SLEEP_PID" 2>/dev/null
SLEEP_PID="" SLEEP_PID=""
exit_if_idle_without_sessions
if [ "$USR1_FIRED" -eq 1 ]; then if [ "$USR1_FIRED" -eq 1 ]; then
USR1_FIRED=0 USR1_FIRED=0
else else

View File

@@ -352,7 +352,7 @@ if [ "$OBSERVER_ENABLED" = "true" ]; then
fi fi
) 9>"$LAZY_START_LOCK" ) 9>"$LAZY_START_LOCK"
else else
# macOS fallback: use lockfile if available, otherwise mkdir-based lock # macOS fallback: use lockfile if available, otherwise skip
if command -v lockfile >/dev/null 2>&1; then if command -v lockfile >/dev/null 2>&1; then
# Use subshell to isolate exit and add trap for cleanup # Use subshell to isolate exit and add trap for cleanup
( (
@@ -365,17 +365,6 @@ if [ "$OBSERVER_ENABLED" = "true" ]; then
fi fi
rm -f "$LAZY_START_LOCK" 2>/dev/null || true rm -f "$LAZY_START_LOCK" 2>/dev/null || true
) )
else
# POSIX fallback: mkdir is atomic -- fails if dir already exists
(
trap 'rmdir "${LAZY_START_LOCK}.d" 2>/dev/null || true' EXIT
mkdir "${LAZY_START_LOCK}.d" 2>/dev/null || exit 0
_CHECK_OBSERVER_RUNNING "${PROJECT_DIR}/.observer.pid" || true
_CHECK_OBSERVER_RUNNING "${CONFIG_DIR}/.observer.pid" || true
if [ ! -f "${PROJECT_DIR}/.observer.pid" ] && [ ! -f "${CONFIG_DIR}/.observer.pid" ]; then
nohup "${SKILL_ROOT}/agents/start-observer.sh" start >/dev/null 2>&1 &
fi
)
fi fi
fi fi
fi fi
@@ -386,9 +375,6 @@ fi
# which caused runaway parallel Claude analysis processes. # which caused runaway parallel Claude analysis processes.
SIGNAL_EVERY_N="${ECC_OBSERVER_SIGNAL_EVERY_N:-20}" SIGNAL_EVERY_N="${ECC_OBSERVER_SIGNAL_EVERY_N:-20}"
SIGNAL_COUNTER_FILE="${PROJECT_DIR}/.observer-signal-counter" SIGNAL_COUNTER_FILE="${PROJECT_DIR}/.observer-signal-counter"
ACTIVITY_FILE="${PROJECT_DIR}/.observer-last-activity"
touch "$ACTIVITY_FILE" 2>/dev/null || true
should_signal=0 should_signal=0
if [ -f "$SIGNAL_COUNTER_FILE" ]; then if [ -f "$SIGNAL_COUNTER_FILE" ]; then

View File

@@ -39,8 +39,13 @@ Use `content-engine` first if the source still needs voice shaping.
Run `brand-voice` first if the source voice is not already captured in the current session. Run `brand-voice` first if the source voice is not already captured in the current session.
Reuse the resulting `VOICE PROFILE` directly. Before adapting, note:
Do not build a second ad hoc voice checklist here unless the user explicitly wants a fresh override for this campaign. - how blunt or explanatory the source is
- whether the source uses fragments, lists, or longer transitions
- whether the source uses parentheses
- whether the source avoids questions, hashtags, or CTA language
The adaptation should preserve that fingerprint.
### Step 3: Adapt by Platform Constraint ### Step 3: Adapt by Platform Constraint
@@ -86,6 +91,7 @@ Delete and rewrite any of these:
- "Here's what I learned" - "Here's what I learned"
- "What do you think?" - "What do you think?"
- "link in bio" unless that is literally true - "link in bio" unless that is literally true
- "not X, just Y"
- generic "professional takeaway" paragraphs that were not in the source - generic "professional takeaway" paragraphs that were not in the source
## Output Format ## Output Format

View File

@@ -1,321 +0,0 @@
---
name: csharp-testing
description: C# and .NET testing patterns with xUnit, FluentAssertions, mocking, integration tests, and test organization best practices.
origin: ECC
---
# C# Testing Patterns
Comprehensive testing patterns for .NET applications using xUnit, FluentAssertions, and modern testing practices.
## When to Activate
- Writing new tests for C# code
- Reviewing test quality and coverage
- Setting up test infrastructure for .NET projects
- Debugging flaky or slow tests
## Test Framework Stack
| Tool | Purpose |
|---|---|
| **xUnit** | Test framework (preferred for .NET) |
| **FluentAssertions** | Readable assertion syntax |
| **NSubstitute** or **Moq** | Mocking dependencies |
| **Testcontainers** | Real infrastructure in integration tests |
| **WebApplicationFactory** | ASP.NET Core integration tests |
| **Bogus** | Realistic test data generation |
## Unit Test Structure
### Arrange-Act-Assert
```csharp
public sealed class OrderServiceTests
{
private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();
private readonly ILogger<OrderService> _logger = Substitute.For<ILogger<OrderService>>();
private readonly OrderService _sut;
public OrderServiceTests()
{
_sut = new OrderService(_repository, _logger);
}
[Fact]
public async Task PlaceOrderAsync_ReturnsSuccess_WhenRequestIsValid()
{
// Arrange
var request = new CreateOrderRequest
{
CustomerId = "cust-123",
Items = [new OrderItem("SKU-001", 2, 29.99m)]
};
// Act
var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeNull();
result.Value!.CustomerId.Should().Be("cust-123");
}
[Fact]
public async Task PlaceOrderAsync_ReturnsFailure_WhenNoItems()
{
// Arrange
var request = new CreateOrderRequest
{
CustomerId = "cust-123",
Items = []
};
// Act
var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("at least one item");
}
}
```
### Parameterized Tests with Theory
```csharp
[Theory]
[InlineData("", false)]
[InlineData("a", false)]
[InlineData("ab@c.d", false)]
[InlineData("user@example.com", true)]
[InlineData("user+tag@example.co.uk", true)]
public void IsValidEmail_ReturnsExpected(string email, bool expected)
{
EmailValidator.IsValid(email).Should().Be(expected);
}
[Theory]
[MemberData(nameof(InvalidOrderCases))]
public async Task PlaceOrderAsync_RejectsInvalidOrders(CreateOrderRequest request, string expectedError)
{
var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain(expectedError);
}
public static TheoryData<CreateOrderRequest, string> InvalidOrderCases => new()
{
{ new() { CustomerId = "", Items = [ValidItem()] }, "CustomerId" },
{ new() { CustomerId = "c1", Items = [] }, "at least one item" },
{ new() { CustomerId = "c1", Items = [new("", 1, 10m)] }, "SKU" },
};
```
## Mocking with NSubstitute
```csharp
[Fact]
public async Task GetOrderAsync_ReturnsNull_WhenNotFound()
{
// Arrange
var orderId = Guid.NewGuid();
_repository.FindByIdAsync(orderId, Arg.Any<CancellationToken>())
.Returns((Order?)null);
// Act
var result = await _sut.GetOrderAsync(orderId, CancellationToken.None);
// Assert
result.Should().BeNull();
}
[Fact]
public async Task PlaceOrderAsync_PersistsOrder()
{
// Arrange
var request = ValidOrderRequest();
// Act
await _sut.PlaceOrderAsync(request, CancellationToken.None);
// Assert — verify the repository was called
await _repository.Received(1).AddAsync(
Arg.Is<Order>(o => o.CustomerId == request.CustomerId),
Arg.Any<CancellationToken>());
}
```
## ASP.NET Core Integration Tests
### WebApplicationFactory Setup
```csharp
public sealed class OrderApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrderApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace real DB with in-memory for tests
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
});
}).CreateClient();
}
[Fact]
public async Task GetOrder_Returns404_WhenNotFound()
{
var response = await _client.GetAsync($"/api/orders/{Guid.NewGuid()}");
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task CreateOrder_Returns201_WithValidRequest()
{
var request = new CreateOrderRequest
{
CustomerId = "cust-1",
Items = [new("SKU-001", 1, 19.99m)]
};
var response = await _client.PostAsJsonAsync("/api/orders", request);
response.StatusCode.Should().Be(HttpStatusCode.Created);
response.Headers.Location.Should().NotBeNull();
}
}
```
### Testing with Testcontainers
```csharp
public sealed class PostgresOrderRepositoryTests : IAsyncLifetime
{
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.Build();
private AppDbContext _db = null!;
public async Task InitializeAsync()
{
await _postgres.StartAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(_postgres.GetConnectionString())
.Options;
_db = new AppDbContext(options);
await _db.Database.MigrateAsync();
}
public async Task DisposeAsync()
{
await _db.DisposeAsync();
await _postgres.DisposeAsync();
}
[Fact]
public async Task AddAsync_PersistsOrder()
{
var repo = new SqlOrderRepository(_db);
var order = Order.Create("cust-1", [new OrderItem("SKU-001", 2, 10m)]);
await repo.AddAsync(order, CancellationToken.None);
var found = await repo.FindByIdAsync(order.Id, CancellationToken.None);
found.Should().NotBeNull();
found!.Items.Should().HaveCount(1);
}
}
```
## Test Organization
```
tests/
MyApp.UnitTests/
Services/
OrderServiceTests.cs
PaymentServiceTests.cs
Validators/
EmailValidatorTests.cs
MyApp.IntegrationTests/
Api/
OrderApiTests.cs
Repositories/
OrderRepositoryTests.cs
MyApp.TestHelpers/
Builders/
OrderBuilder.cs
Fixtures/
DatabaseFixture.cs
```
## Test Data Builders
```csharp
public sealed class OrderBuilder
{
private string _customerId = "cust-default";
private readonly List<OrderItem> _items = [new("SKU-001", 1, 10m)];
public OrderBuilder WithCustomer(string customerId)
{
_customerId = customerId;
return this;
}
public OrderBuilder WithItem(string sku, int quantity, decimal price)
{
_items.Add(new OrderItem(sku, quantity, price));
return this;
}
public Order Build() => Order.Create(_customerId, _items);
}
// Usage in tests
var order = new OrderBuilder()
.WithCustomer("cust-vip")
.WithItem("SKU-PREMIUM", 3, 99.99m)
.Build();
```
## Common Anti-Patterns
| Anti-Pattern | Fix |
|---|---|
| Testing implementation details | Test behavior and outcomes |
| Shared mutable test state | Fresh instance per test (xUnit does this via constructors) |
| `Thread.Sleep` in async tests | Use `Task.Delay` with timeout, or polling helpers |
| Asserting on `ToString()` output | Assert on typed properties |
| One giant assertion per test | One logical assertion per test |
| Test names describing implementation | Name by behavior: `Method_ExpectedResult_WhenCondition` |
| Ignoring `CancellationToken` | Always pass and verify cancellation |
## Running Tests
```bash
# Run all tests
dotnet test
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"
# Run specific project
dotnet test tests/MyApp.UnitTests/
# Filter by test name
dotnet test --filter "FullyQualifiedName~OrderService"
# Watch mode during development
dotnet watch test --project tests/MyApp.UnitTests/
```

View File

@@ -1,563 +0,0 @@
---
name: dart-flutter-patterns
description: Production-ready Dart and Flutter patterns covering null safety, immutable state, async composition, widget architecture, popular state management frameworks (BLoC, Riverpod, Provider), GoRouter navigation, Dio networking, Freezed code generation, and clean architecture.
origin: ECC
---
# Dart/Flutter Patterns
## When to Use
Use this skill when:
- Starting a new Flutter feature and need idiomatic patterns for state management, navigation, or data access
- Reviewing or writing Dart code and need guidance on null safety, sealed types, or async composition
- Setting up a new Flutter project and choosing between BLoC, Riverpod, or Provider
- Implementing secure HTTP clients, WebView integration, or local storage
- Writing tests for Flutter widgets, Cubits, or Riverpod providers
- Wiring up GoRouter with authentication guards
## How It Works
This skill provides copy-paste-ready Dart/Flutter code patterns organized by concern:
1. **Null safety** — avoid `!`, prefer `?.`/`??`/pattern matching
2. **Immutable state** — sealed classes, `freezed`, `copyWith`
3. **Async composition** — concurrent `Future.wait`, safe `BuildContext` after `await`
4. **Widget architecture** — extract to classes (not methods), `const` propagation, scoped rebuilds
5. **State management** — BLoC/Cubit events, Riverpod notifiers and derived providers
6. **Navigation** — GoRouter with reactive auth guards via `refreshListenable`
7. **Networking** — Dio with interceptors, token refresh with one-time retry guard
8. **Error handling** — global capture, `ErrorWidget.builder`, crashlytics wiring
9. **Testing** — unit (BLoC test), widget (ProviderScope overrides), fakes over mocks
## Examples
```dart
// Sealed state — prevents impossible states
sealed class AsyncState<T> {}
final class Loading<T> extends AsyncState<T> {}
final class Success<T> extends AsyncState<T> { final T data; const Success(this.data); }
final class Failure<T> extends AsyncState<T> { final Object error; const Failure(this.error); }
// GoRouter with reactive auth redirect
final router = GoRouter(
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
final authed = context.read<AuthCubit>().state is AuthAuthenticated;
if (!authed && !state.matchedLocation.startsWith('/login')) return '/login';
return null;
},
routes: [...],
);
// Riverpod derived provider with safe firstWhereOrNull
@riverpod
double cartTotal(Ref ref) {
final cart = ref.watch(cartNotifierProvider);
final products = ref.watch(productsProvider).valueOrNull ?? [];
return cart.fold(0.0, (total, item) {
final product = products.firstWhereOrNull((p) => p.id == item.productId);
return total + (product?.price ?? 0) * item.quantity;
});
}
```
---
Practical, production-ready patterns for Dart and Flutter applications. Library-agnostic where possible, with explicit coverage of the most common ecosystem packages.
---
## 1. Null Safety Fundamentals
### Prefer Patterns Over Bang Operator
```dart
// BAD — crashes at runtime if null
final name = user!.name;
// GOOD — provide fallback
final name = user?.name ?? 'Unknown';
// GOOD — Dart 3 pattern matching (preferred for complex cases)
final display = switch (user) {
User(:final name, :final email) => '$name <$email>',
null => 'Guest',
};
// GOOD — guard early return
String getUserName(User? user) {
if (user == null) return 'Unknown';
return user.name; // promoted to non-null after check
}
```
### Avoid `late` Overuse
```dart
// BAD — defers null error to runtime
late String userId;
// GOOD — nullable with explicit initialization
String? userId;
// OK — use late only when initialization is guaranteed before first access
// (e.g., in initState() before any widget interaction)
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
}
```
---
## 2. Immutable State
### Sealed Classes for State Hierarchies
```dart
sealed class UserState {}
final class UserInitial extends UserState {}
final class UserLoading extends UserState {}
final class UserLoaded extends UserState {
const UserLoaded(this.user);
final User user;
}
final class UserError extends UserState {
const UserError(this.message);
final String message;
}
// Exhaustive switch — compiler enforces all branches
Widget buildFrom(UserState state) => switch (state) {
UserInitial() => const SizedBox.shrink(),
UserLoading() => const CircularProgressIndicator(),
UserLoaded(:final user) => UserCard(user: user),
UserError(:final message) => ErrorText(message),
};
```
### Freezed for Boilerplate-Free Immutability
```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 String id,
required String name,
required String email,
@Default(false) bool isAdmin,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Usage
final user = User(id: '1', name: 'Alice', email: 'alice@example.com');
final updated = user.copyWith(name: 'Alice Smith'); // immutable update
final json = user.toJson();
final fromJson = User.fromJson(json);
```
---
## 3. Async Composition
### Structured Concurrency with Future.wait
```dart
Future<DashboardData> loadDashboard(UserRepository users, OrderRepository orders) async {
// Run concurrently — don't await sequentially
final (userList, orderList) = await (
users.getAll(),
orders.getRecent(),
).wait; // Dart 3 record destructuring + Future.wait extension
return DashboardData(users: userList, orders: orderList);
}
```
### Stream Patterns
```dart
// Repository exposes reactive streams for live data
Stream<List<Item>> watchCartItems() => _db
.watchTable('cart_items')
.map((rows) => rows.map(Item.fromRow).toList());
// In widget layer — declarative, no manual subscription
StreamBuilder<List<Item>>(
stream: cartRepository.watchCartItems(),
builder: (context, snapshot) => switch (snapshot) {
AsyncSnapshot(connectionState: ConnectionState.waiting) =>
const CircularProgressIndicator(),
AsyncSnapshot(:final error?) => ErrorWidget(error.toString()),
AsyncSnapshot(:final data?) => CartList(items: data),
_ => const SizedBox.shrink(),
},
)
```
### BuildContext After Await
```dart
// CRITICAL — always check mounted after any await in StatefulWidget
Future<void> _handleSubmit() async {
setState(() => _isLoading = true);
try {
await authService.login(_email, _password);
if (!mounted) return; // ← guard before using context
context.go('/home');
} on AuthException catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.message)));
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
```
---
## 4. Widget Architecture
### Extract to Classes, Not Methods
```dart
// BAD — private method returning widget, prevents optimization
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.all(16),
child: Text(title, style: Theme.of(context).textTheme.headlineMedium),
);
}
// GOOD — separate widget class, enables const, element reuse
class _PageHeader extends StatelessWidget {
const _PageHeader(this.title);
final String title;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
child: Text(title, style: Theme.of(context).textTheme.headlineMedium),
);
}
}
```
### const Propagation
```dart
// BAD — new instances every rebuild
child: Padding(
padding: EdgeInsets.all(16.0), // not const
child: Icon(Icons.home, size: 24.0), // not const
)
// GOOD — const stops rebuild propagation
child: const Padding(
padding: EdgeInsets.all(16.0),
child: Icon(Icons.home, size: 24.0),
)
```
### Scoped Rebuilds
```dart
// BAD — entire page rebuilds on every counter change
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider); // rebuilds everything
return Scaffold(
body: Column(children: [
const ExpensiveHeader(), // unnecessarily rebuilt
Text('$count'),
const ExpensiveFooter(), // unnecessarily rebuilt
]),
);
}
}
// GOOD — isolate the rebuilding part
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Column(children: [
ExpensiveHeader(), // never rebuilt (const)
_CounterDisplay(), // only this rebuilds
ExpensiveFooter(), // never rebuilt (const)
]),
);
}
}
class _CounterDisplay extends ConsumerWidget {
const _CounterDisplay();
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
```
---
## 5. State Management: BLoC/Cubit
```dart
// Cubit — synchronous or simple async state
class AuthCubit extends Cubit<AuthState> {
AuthCubit(this._authService) : super(const AuthState.initial());
final AuthService _authService;
Future<void> login(String email, String password) async {
emit(const AuthState.loading());
try {
final user = await _authService.login(email, password);
emit(AuthState.authenticated(user));
} on AuthException catch (e) {
emit(AuthState.error(e.message));
}
}
void logout() {
_authService.logout();
emit(const AuthState.initial());
}
}
// In widget
BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) => switch (state) {
AuthInitial() => const LoginForm(),
AuthLoading() => const CircularProgressIndicator(),
AuthAuthenticated(:final user) => HomePage(user: user),
AuthError(:final message) => ErrorView(message: message),
},
)
```
---
## 6. State Management: Riverpod
```dart
// Auto-dispose async provider
@riverpod
Future<List<Product>> products(Ref ref) async {
final repo = ref.watch(productRepositoryProvider);
return repo.getAll();
}
// Notifier with complex mutations
@riverpod
class CartNotifier extends _$CartNotifier {
@override
List<CartItem> build() => [];
void add(Product product) {
final existing = state.where((i) => i.productId == product.id).firstOrNull;
if (existing != null) {
state = [
for (final item in state)
if (item.productId == product.id) item.copyWith(quantity: item.quantity + 1)
else item,
];
} else {
state = [...state, CartItem(productId: product.id, quantity: 1)];
}
}
void remove(String productId) =>
state = state.where((i) => i.productId != productId).toList();
void clear() => state = [];
}
// Derived provider (selector pattern)
@riverpod
int cartCount(Ref ref) => ref.watch(cartNotifierProvider).length;
@riverpod
double cartTotal(Ref ref) {
final cart = ref.watch(cartNotifierProvider);
final products = ref.watch(productsProvider).valueOrNull ?? [];
return cart.fold(0.0, (total, item) {
// firstWhereOrNull (from collection package) avoids StateError when product is missing
final product = products.firstWhereOrNull((p) => p.id == item.productId);
return total + (product?.price ?? 0) * item.quantity;
});
}
```
---
## 7. Navigation with GoRouter
```dart
final router = GoRouter(
initialLocation: '/',
// refreshListenable re-evaluates redirect whenever auth state changes
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
final isLoggedIn = context.read<AuthCubit>().state is AuthAuthenticated;
final isGoingToLogin = state.matchedLocation == '/login';
if (!isLoggedIn && !isGoingToLogin) return '/login';
if (isLoggedIn && isGoingToLogin) return '/';
return null;
},
routes: [
GoRoute(path: '/login', builder: (_, __) => const LoginPage()),
ShellRoute(
builder: (context, state, child) => AppShell(child: child),
routes: [
GoRoute(path: '/', builder: (_, __) => const HomePage()),
GoRoute(
path: '/products/:id',
builder: (context, state) =>
ProductDetailPage(id: state.pathParameters['id']!),
),
],
),
],
);
```
---
## 8. HTTP with Dio
```dart
final dio = Dio(BaseOptions(
baseUrl: const String.fromEnvironment('API_URL'),
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
headers: {'Content-Type': 'application/json'},
));
// Add auth interceptor
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) async {
final token = await secureStorage.read(key: 'auth_token');
if (token != null) options.headers['Authorization'] = 'Bearer $token';
handler.next(options);
},
onError: (error, handler) async {
// Guard against infinite retry loops: only attempt refresh once per request
final isRetry = error.requestOptions.extra['_isRetry'] == true;
if (!isRetry && error.response?.statusCode == 401) {
final refreshed = await attemptTokenRefresh();
if (refreshed) {
error.requestOptions.extra['_isRetry'] = true;
return handler.resolve(await dio.fetch(error.requestOptions));
}
}
handler.next(error);
},
));
// Repository using Dio
class UserApiDataSource {
const UserApiDataSource(this._dio);
final Dio _dio;
Future<User> getById(String id) async {
final response = await _dio.get<Map<String, dynamic>>('/users/$id');
return User.fromJson(response.data!);
}
}
```
---
## 9. Error Handling Architecture
```dart
// Global error capture — set up in main()
void main() {
FlutterError.onError = (details) {
FlutterError.presentError(details);
crashlytics.recordFlutterFatalError(details);
};
PlatformDispatcher.instance.onError = (error, stack) {
crashlytics.recordError(error, stack, fatal: true);
return true;
};
runApp(const App());
}
// Custom ErrorWidget for production
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
ErrorWidget.builder = (details) => ProductionErrorWidget(details);
return MaterialApp.router(routerConfig: router);
}
}
```
---
## 10. Testing Quick Reference
```dart
// Unit test — use case
test('GetUserUseCase returns null for missing user', () async {
final repo = FakeUserRepository();
final useCase = GetUserUseCase(repo);
expect(await useCase('missing-id'), isNull);
});
// BLoC test
blocTest<AuthCubit, AuthState>(
'emits loading then error on failed login',
build: () => AuthCubit(FakeAuthService(throwsOn: 'login')),
act: (cubit) => cubit.login('user@test.com', 'wrong'),
expect: () => [const AuthState.loading(), isA<AuthError>()],
);
// Widget test
testWidgets('CartBadge shows item count', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [cartNotifierProvider.overrideWith(() => FakeCartNotifier(count: 3))],
child: const MaterialApp(home: CartBadge()),
),
);
expect(find.text('3'), findsOneWidget);
});
```
---
## References
- [Effective Dart: Design](https://dart.dev/effective-dart/design)
- [Flutter Performance Best Practices](https://docs.flutter.dev/perf/best-practices)
- [Riverpod Documentation](https://riverpod.dev/)
- [BLoC Library](https://bloclibrary.dev/)
- [GoRouter](https://pub.dev/packages/go_router)
- [Freezed](https://pub.dev/packages/freezed)
- Skill: `flutter-dart-code-review` — comprehensive review checklist
- Rules: `rules/dart/` — coding style, patterns, security, testing, hooks

View File

@@ -1,321 +0,0 @@
---
name: dotnet-patterns
description: Idiomatic C# and .NET patterns, conventions, dependency injection, async/await, and best practices for building robust, maintainable .NET applications.
origin: ECC
---
# .NET Development Patterns
Idiomatic C# and .NET patterns for building robust, performant, and maintainable applications.
## When to Activate
- Writing new C# code
- Reviewing C# code
- Refactoring existing .NET applications
- Designing service architectures with ASP.NET Core
## Core Principles
### 1. Prefer Immutability
Use records and init-only properties for data models. Mutability should be an explicit, justified choice.
```csharp
// Good: Immutable value object
public sealed record Money(decimal Amount, string Currency);
// Good: Immutable DTO with init setters
public sealed class CreateOrderRequest
{
public required string CustomerId { get; init; }
public required IReadOnlyList<OrderItem> Items { get; init; }
}
// Bad: Mutable model with public setters
public class Order
{
public string CustomerId { get; set; }
public List<OrderItem> Items { get; set; }
}
```
### 2. Explicit Over Implicit
Be clear about nullability, access modifiers, and intent.
```csharp
// Good: Explicit access modifiers and nullability
public sealed class UserService
{
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;
public UserService(IUserRepository repository, ILogger<UserService> logger)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<User?> FindByIdAsync(Guid id, CancellationToken cancellationToken)
{
return await _repository.FindByIdAsync(id, cancellationToken);
}
}
```
### 3. Depend on Abstractions
Use interfaces for service boundaries. Register via DI container.
```csharp
// Good: Interface-based dependency
public interface IOrderRepository
{
Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken);
Task<IReadOnlyList<Order>> FindByCustomerAsync(string customerId, CancellationToken cancellationToken);
Task AddAsync(Order order, CancellationToken cancellationToken);
}
// Registration
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
```
## Async/Await Patterns
### Proper Async Usage
```csharp
// Good: Async all the way, with CancellationToken
public async Task<OrderSummary> GetOrderSummaryAsync(
Guid orderId,
CancellationToken cancellationToken)
{
var order = await _repository.FindByIdAsync(orderId, cancellationToken)
?? throw new NotFoundException($"Order {orderId} not found");
var customer = await _customerService.GetAsync(order.CustomerId, cancellationToken);
return new OrderSummary(order, customer);
}
// Bad: Blocking on async
public OrderSummary GetOrderSummary(Guid orderId)
{
var order = _repository.FindByIdAsync(orderId, CancellationToken.None).Result; // Deadlock risk
return new OrderSummary(order);
}
```
### Parallel Async Operations
```csharp
// Good: Concurrent independent operations
public async Task<DashboardData> LoadDashboardAsync(CancellationToken cancellationToken)
{
var ordersTask = _orderService.GetRecentAsync(cancellationToken);
var metricsTask = _metricsService.GetCurrentAsync(cancellationToken);
var alertsTask = _alertService.GetActiveAsync(cancellationToken);
await Task.WhenAll(ordersTask, metricsTask, alertsTask);
return new DashboardData(
Orders: await ordersTask,
Metrics: await metricsTask,
Alerts: await alertsTask);
}
```
## Options Pattern
Bind configuration sections to strongly-typed objects.
```csharp
public sealed class SmtpOptions
{
public const string SectionName = "Smtp";
public required string Host { get; init; }
public required int Port { get; init; }
public required string Username { get; init; }
public bool UseSsl { get; init; } = true;
}
// Registration
builder.Services.Configure<SmtpOptions>(
builder.Configuration.GetSection(SmtpOptions.SectionName));
// Usage via injection
public class EmailService(IOptions<SmtpOptions> options)
{
private readonly SmtpOptions _smtp = options.Value;
}
```
## Result Pattern
Return explicit success/failure instead of throwing for expected failures.
```csharp
public sealed record Result<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string? Error { get; }
private Result(T value) { IsSuccess = true; Value = value; }
private Result(string error) { IsSuccess = false; Error = error; }
public static Result<T> Success(T value) => new(value);
public static Result<T> Failure(string error) => new(error);
}
// Usage
public async Task<Result<Order>> PlaceOrderAsync(CreateOrderRequest request)
{
if (request.Items.Count == 0)
return Result<Order>.Failure("Order must contain at least one item");
var order = Order.Create(request);
await _repository.AddAsync(order, CancellationToken.None);
return Result<Order>.Success(order);
}
```
## Repository Pattern with EF Core
```csharp
public sealed class SqlOrderRepository : IOrderRepository
{
private readonly AppDbContext _db;
public SqlOrderRepository(AppDbContext db) => _db = db;
public async Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken)
{
return await _db.Orders
.Include(o => o.Items)
.AsNoTracking()
.FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
}
public async Task<IReadOnlyList<Order>> FindByCustomerAsync(
string customerId,
CancellationToken cancellationToken)
{
return await _db.Orders
.Where(o => o.CustomerId == customerId)
.OrderByDescending(o => o.CreatedAt)
.AsNoTracking()
.ToListAsync(cancellationToken);
}
public async Task AddAsync(Order order, CancellationToken cancellationToken)
{
_db.Orders.Add(order);
await _db.SaveChangesAsync(cancellationToken);
}
}
```
## Middleware and Pipeline
```csharp
// Custom middleware
public sealed class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestTimingMiddleware> _logger;
public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
await _next(context);
}
finally
{
stopwatch.Stop();
_logger.LogInformation(
"Request {Method} {Path} completed in {ElapsedMs}ms with status {StatusCode}",
context.Request.Method,
context.Request.Path,
stopwatch.ElapsedMilliseconds,
context.Response.StatusCode);
}
}
}
```
## Minimal API Patterns
```csharp
// Organized with route groups
var orders = app.MapGroup("/api/orders")
.RequireAuthorization()
.WithTags("Orders");
orders.MapGet("/{id:guid}", async (
Guid id,
IOrderRepository repository,
CancellationToken cancellationToken) =>
{
var order = await repository.FindByIdAsync(id, cancellationToken);
return order is not null
? TypedResults.Ok(order)
: TypedResults.NotFound();
});
orders.MapPost("/", async (
CreateOrderRequest request,
IOrderService service,
CancellationToken cancellationToken) =>
{
var result = await service.PlaceOrderAsync(request, cancellationToken);
return result.IsSuccess
? TypedResults.Created($"/api/orders/{result.Value!.Id}", result.Value)
: TypedResults.BadRequest(result.Error);
});
```
## Guard Clauses
```csharp
// Good: Early returns with clear validation
public async Task<ProcessResult> ProcessPaymentAsync(
PaymentRequest request,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
if (request.Amount <= 0)
throw new ArgumentOutOfRangeException(nameof(request.Amount), "Amount must be positive");
if (string.IsNullOrWhiteSpace(request.Currency))
throw new ArgumentException("Currency is required", nameof(request.Currency));
// Happy path continues here without nesting
var gateway = _gatewayFactory.Create(request.Currency);
return await gateway.ChargeAsync(request, cancellationToken);
}
```
## Anti-Patterns to Avoid
| Anti-Pattern | Fix |
|---|---|
| `async void` methods | Return `Task` (except event handlers) |
| `.Result` or `.Wait()` | Use `await` |
| `catch (Exception) { }` | Handle or rethrow with context |
| `new Service()` in constructors | Use constructor injection |
| `public` fields | Use properties with appropriate accessors |
| `dynamic` in business logic | Use generics or explicit types |
| Mutable `static` state | Use DI scoping or `ConcurrentDictionary` |
| `string.Format` in loops | Use `StringBuilder` or interpolated string handlers |

View File

@@ -206,25 +206,25 @@ export const buildCreateOrderUseCase = (deps: { db: SqlClient; stripe: StripeCli
Use the same boundary rules across ecosystems; only syntax and wiring style change. Use the same boundary rules across ecosystems; only syntax and wiring style change.
- **TypeScript/JavaScript** - **TypeScript/JavaScript**
- Ports: `application/ports/*` as interfaces/types. - Ports: `application/ports/*` as interfaces/types.
- Use cases: classes/functions with constructor/argument injection. - Use cases: classes/functions with constructor/argument injection.
- Adapters: `adapters/inbound/*`, `adapters/outbound/*`. - Adapters: `adapters/inbound/*`, `adapters/outbound/*`.
- Composition: explicit factory/container module (no hidden globals). - Composition: explicit factory/container module (no hidden globals).
- **Java** - **Java**
- Packages: `domain`, `application.port.in`, `application.port.out`, `application.usecase`, `adapter.in`, `adapter.out`. - Packages: `domain`, `application.port.in`, `application.port.out`, `application.usecase`, `adapter.in`, `adapter.out`.
- Ports: interfaces in `application.port.*`. - Ports: interfaces in `application.port.*`.
- Use cases: plain classes (Spring `@Service` is optional, not required). - Use cases: plain classes (Spring `@Service` is optional, not required).
- Composition: Spring config or manual wiring class; keep wiring out of domain/use-case classes. - Composition: Spring config or manual wiring class; keep wiring out of domain/use-case classes.
- **Kotlin** - **Kotlin**
- Modules/packages mirror the Java split (`domain`, `application.port`, `application.usecase`, `adapter`). - Modules/packages mirror the Java split (`domain`, `application.port`, `application.usecase`, `adapter`).
- Ports: Kotlin interfaces. - Ports: Kotlin interfaces.
- Use cases: classes with constructor injection (Koin/Dagger/Spring/manual). - Use cases: classes with constructor injection (Koin/Dagger/Spring/manual).
- Composition: module definitions or dedicated composition functions; avoid service locator patterns. - Composition: module definitions or dedicated composition functions; avoid service locator patterns.
- **Go** - **Go**
- Packages: `internal/<feature>/domain`, `application`, `ports`, `adapters/inbound`, `adapters/outbound`. - Packages: `internal/<feature>/domain`, `application`, `ports`, `adapters/inbound`, `adapters/outbound`.
- Ports: small interfaces owned by the consuming application package. - Ports: small interfaces owned by the consuming application package.
- Use cases: structs with interface fields plus explicit `New...` constructors. - Use cases: structs with interface fields plus explicit `New...` constructors.
- Composition: wire in `cmd/<app>/main.go` (or dedicated wiring package), keep constructors explicit. - Composition: wire in `cmd/<app>/main.go` (or dedicated wiring package), keep constructors explicit.
## Anti-Patterns to Avoid ## Anti-Patterns to Avoid

View File

@@ -24,11 +24,6 @@ Write investor communication that is short, concrete, and easy to act on.
4. Stay concise. 4. Stay concise.
5. Never send copy that could go to any investor. 5. Never send copy that could go to any investor.
## Voice Handling
If the user's voice matters, run `brand-voice` first and reuse its `VOICE PROFILE`.
This skill should keep the investor-specific structure and ask discipline, not recreate its own parallel voice system.
## Hard Bans ## Hard Bans
Delete and rewrite any of these: Delete and rewrite any of these:
@@ -36,6 +31,7 @@ Delete and rewrite any of these:
- "excited to share" - "excited to share"
- generic thesis praise without a real tie-in - generic thesis praise without a real tie-in
- vague founder adjectives - vague founder adjectives
- "no fluff"
- begging language - begging language
- soft closing questions when a direct ask is clearer - soft closing questions when a direct ask is clearer

View File

@@ -1,293 +0,0 @@
---
name: jira-integration
description: Use this skill when retrieving Jira tickets, analyzing requirements, updating ticket status, adding comments, or transitioning issues. Provides Jira API patterns via MCP or direct REST calls.
origin: ECC
---
# Jira Integration Skill
Retrieve, analyze, and update Jira tickets directly from your AI coding workflow. Supports both **MCP-based** (recommended) and **direct REST API** approaches.
## When to Activate
- Fetching a Jira ticket to understand requirements
- Extracting testable acceptance criteria from a ticket
- Adding progress comments to a Jira issue
- Transitioning a ticket status (To Do → In Progress → Done)
- Linking merge requests or branches to a Jira issue
- Searching for issues by JQL query
## Prerequisites
### Option A: MCP Server (Recommended)
Install the `mcp-atlassian` MCP server. This exposes Jira tools directly to your AI agent.
**Requirements:**
- Python 3.10+
- `uvx` (from `uv`), installed via your package manager or the official `uv` installation documentation
**Add to your MCP config** (e.g., `~/.claude.json``mcpServers`):
```json
{
"jira": {
"command": "uvx",
"args": ["mcp-atlassian==0.21.0"],
"env": {
"JIRA_URL": "https://YOUR_ORG.atlassian.net",
"JIRA_EMAIL": "your.email@example.com",
"JIRA_API_TOKEN": "your-api-token"
},
"description": "Jira issue tracking — search, create, update, comment, transition"
}
}
```
> **Security:** Never hardcode secrets. Prefer setting `JIRA_URL`, `JIRA_EMAIL`, and `JIRA_API_TOKEN` in your system environment (or a secrets manager). Only use the MCP `env` block for local, uncommitted config files.
**To get a Jira API token:**
1. Go to <https://id.atlassian.com/manage-profile/security/api-tokens>
2. Click **Create API token**
3. Copy the token — store it in your environment, never in source code
### Option B: Direct REST API
If MCP is not available, use the Jira REST API v3 directly via `curl` or a helper script.
**Required environment variables:**
| Variable | Description |
|----------|-------------|
| `JIRA_URL` | Your Jira instance URL (e.g., `https://yourorg.atlassian.net`) |
| `JIRA_EMAIL` | Your Atlassian account email |
| `JIRA_API_TOKEN` | API token from id.atlassian.com |
Store these in your shell environment, secrets manager, or an untracked local env file. Do not commit them to the repo.
## MCP Tools Reference
When the `mcp-atlassian` MCP server is configured, these tools are available:
| Tool | Purpose | Example |
|------|---------|---------|
| `jira_search` | JQL queries | `project = PROJ AND status = "In Progress"` |
| `jira_get_issue` | Fetch full issue details by key | `PROJ-1234` |
| `jira_create_issue` | Create issues (Task, Bug, Story, Epic) | New bug report |
| `jira_update_issue` | Update fields (summary, description, assignee) | Change assignee |
| `jira_transition_issue` | Change status | Move to "In Review" |
| `jira_add_comment` | Add comments | Progress update |
| `jira_get_sprint_issues` | List issues in a sprint | Active sprint review |
| `jira_create_issue_link` | Link issues (Blocks, Relates to) | Dependency tracking |
| `jira_get_issue_development_info` | See linked PRs, branches, commits | Dev context |
> **Tip:** Always call `jira_get_transitions` before transitioning — transition IDs vary per project workflow.
## Direct REST API Reference
### Fetch a Ticket
```bash
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-H "Content-Type: application/json" \
"$JIRA_URL/rest/api/3/issue/PROJ-1234" | jq '{
key: .key,
summary: .fields.summary,
status: .fields.status.name,
priority: .fields.priority.name,
type: .fields.issuetype.name,
assignee: .fields.assignee.displayName,
labels: .fields.labels,
description: .fields.description
}'
```
### Fetch Comments
```bash
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-H "Content-Type: application/json" \
"$JIRA_URL/rest/api/3/issue/PROJ-1234?fields=comment" | jq '.fields.comment.comments[] | {
author: .author.displayName,
created: .created[:10],
body: .body
}'
```
### Add a Comment
```bash
curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"body": {
"version": 1,
"type": "doc",
"content": [{
"type": "paragraph",
"content": [{"type": "text", "text": "Your comment here"}]
}]
}
}' \
"$JIRA_URL/rest/api/3/issue/PROJ-1234/comment"
```
### Transition a Ticket
```bash
# 1. Get available transitions
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
"$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions" | jq '.transitions[] | {id, name: .name}'
# 2. Execute transition (replace TRANSITION_ID)
curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"transition": {"id": "TRANSITION_ID"}}' \
"$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions"
```
### Search with JQL
```bash
curl -s -G -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
--data-urlencode "jql=project = PROJ AND status = 'In Progress'" \
"$JIRA_URL/rest/api/3/search"
```
## Analyzing a Ticket
When retrieving a ticket for development or test automation, extract:
### 1. Testable Requirements
- **Functional requirements** — What the feature does
- **Acceptance criteria** — Conditions that must be met
- **Testable behaviors** — Specific actions and expected outcomes
- **User roles** — Who uses this feature and their permissions
- **Data requirements** — What data is needed
- **Integration points** — APIs, services, or systems involved
### 2. Test Types Needed
- **Unit tests** — Individual functions and utilities
- **Integration tests** — API endpoints and service interactions
- **E2E tests** — User-facing UI flows
- **API tests** — Endpoint contracts and error handling
### 3. Edge Cases & Error Scenarios
- Invalid inputs (empty, too long, special characters)
- Unauthorized access
- Network failures or timeouts
- Concurrent users or race conditions
- Boundary conditions
- Missing or null data
- State transitions (back navigation, refresh, etc.)
### 4. Structured Analysis Output
```
Ticket: PROJ-1234
Summary: [ticket title]
Status: [current status]
Priority: [High/Medium/Low]
Test Types: Unit, Integration, E2E
Requirements:
1. [requirement 1]
2. [requirement 2]
Acceptance Criteria:
- [ ] [criterion 1]
- [ ] [criterion 2]
Test Scenarios:
- Happy Path: [description]
- Error Case: [description]
- Edge Case: [description]
Test Data Needed:
- [data item 1]
- [data item 2]
Dependencies:
- [dependency 1]
- [dependency 2]
```
## Updating Tickets
### When to Update
| Workflow Step | Jira Update |
|---|---|
| Start work | Transition to "In Progress" |
| Tests written | Comment with test coverage summary |
| Branch created | Comment with branch name |
| PR/MR created | Comment with link, link issue |
| Tests passing | Comment with results summary |
| PR/MR merged | Transition to "Done" or "In Review" |
### Comment Templates
**Starting Work:**
```
Starting implementation for this ticket.
Branch: feat/PROJ-1234-feature-name
```
**Tests Implemented:**
```
Automated tests implemented:
Unit Tests:
- [test file 1] — [what it covers]
- [test file 2] — [what it covers]
Integration Tests:
- [test file] — [endpoints/flows covered]
All tests passing locally. Coverage: XX%
```
**PR Created:**
```
Pull request created:
[PR Title](https://github.com/org/repo/pull/XXX)
Ready for review.
```
**Work Complete:**
```
Implementation complete.
PR merged: [link]
Test results: All passing (X/Y)
Coverage: XX%
```
## Security Guidelines
- **Never hardcode** Jira API tokens in source code or skill files
- **Always use** environment variables or a secrets manager
- **Add `.env`** to `.gitignore` in every project
- **Rotate tokens** immediately if exposed in git history
- **Use least-privilege** API tokens scoped to required projects
- **Validate** that credentials are set before making API calls — fail fast with a clear message
## Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| `401 Unauthorized` | Invalid or expired API token | Regenerate at id.atlassian.com |
| `403 Forbidden` | Token lacks project permissions | Check token scopes and project access |
| `404 Not Found` | Wrong ticket key or base URL | Verify `JIRA_URL` and ticket key |
| `spawn uvx ENOENT` | IDE cannot find `uvx` on PATH | Use full path (e.g., `~/.local/bin/uvx`) or set PATH in `~/.zprofile` |
| Connection timeout | Network/VPN issue | Check VPN connection and firewall rules |
## Best Practices
- Update Jira as you go, not all at once at the end
- Keep comments concise but informative
- Link rather than copy — point to PRs, test reports, and dashboards
- Use @mentions if you need input from others
- Check linked issues to understand full feature scope before starting
- If acceptance criteria are vague, ask for clarification before writing code

View File

@@ -1,230 +0,0 @@
---
name: nestjs-patterns
description: NestJS architecture patterns for modules, controllers, providers, DTO validation, guards, interceptors, config, and production-grade TypeScript backends.
origin: ECC
---
# NestJS Development Patterns
Production-grade NestJS patterns for modular TypeScript backends.
## When to Activate
- Building NestJS APIs or services
- Structuring modules, controllers, and providers
- Adding DTO validation, guards, interceptors, or exception filters
- Configuring environment-aware settings and database integrations
- Testing NestJS units or HTTP endpoints
## Project Structure
```text
src/
├── app.module.ts
├── main.ts
├── common/
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/
│ ├── configuration.ts
│ └── validation.ts
├── modules/
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto/
│ │ ├── guards/
│ │ └── strategies/
│ └── users/
│ ├── dto/
│ ├── entities/
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
└── prisma/ or database/
```
- Keep domain code inside feature modules.
- Put cross-cutting filters, decorators, guards, and interceptors in `common/`.
- Keep DTOs close to the module that owns them.
## Bootstrap and Global Validation
```ts
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
```
- Always enable `whitelist` and `forbidNonWhitelisted` on public APIs.
- Prefer one global validation pipe instead of repeating validation config per route.
## Modules, Controllers, and Providers
```ts
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
getById(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.getById(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
@Injectable()
export class UsersService {
constructor(private readonly usersRepo: UsersRepository) {}
async create(dto: CreateUserDto) {
return this.usersRepo.create(dto);
}
}
```
- Controllers should stay thin: parse HTTP input, call a provider, return response DTOs.
- Put business logic in injectable services, not controllers.
- Export only the providers other modules genuinely need.
## DTOs and Validation
```ts
export class CreateUserDto {
@IsEmail()
email!: string;
@IsString()
@Length(2, 80)
name!: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
```
- Validate every request DTO with `class-validator`.
- Use dedicated response DTOs or serializers instead of returning ORM entities directly.
- Avoid leaking internal fields such as password hashes, tokens, or audit columns.
## Auth, Guards, and Request Context
```ts
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
return this.reportService.getForUser(req.user.id);
}
```
- Keep auth strategies and guards module-local unless they are truly shared.
- Encode coarse access rules in guards, then do resource-specific authorization in services.
- Prefer explicit request types for authenticated request objects.
## Exception Filters and Error Shape
```ts
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
const request = host.switchToHttp().getRequest<Request>();
if (exception instanceof HttpException) {
return response.status(exception.getStatus()).json({
path: request.url,
error: exception.getResponse(),
});
}
return response.status(500).json({
path: request.url,
error: 'Internal server error',
});
}
}
```
- Keep one consistent error envelope across the API.
- Throw framework exceptions for expected client errors; log and wrap unexpected failures centrally.
## Config and Environment Validation
```ts
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validate: validateEnv,
});
```
- Validate env at boot, not lazily at first request.
- Keep config access behind typed helpers or config services.
- Split dev/staging/prod concerns in config factories instead of branching throughout feature code.
## Persistence and Transactions
- Keep repository / ORM code behind providers that speak domain language.
- For Prisma or TypeORM, isolate transactional workflows in services that own the unit of work.
- Do not let controllers coordinate multi-step writes directly.
## Testing
```ts
describe('UsersController', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [UsersModule],
}).compile();
app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.init();
});
});
```
- Unit test providers in isolation with mocked dependencies.
- Add request-level tests for guards, validation pipes, and exception filters.
- Reuse the same global pipes/filters in tests that you use in production.
## Production Defaults
- Enable structured logging and request correlation ids.
- Terminate on invalid env/config instead of booting partially.
- Prefer async provider initialization for DB/cache clients with explicit health checks.
- Keep background jobs and event consumers in their own modules, not inside HTTP controllers.
- Make rate limiting, auth, and audit logging explicit for public endpoints.

View File

@@ -83,4 +83,4 @@ const { width, height } = useVideoConfig();
</mesh> </mesh>
</Sequence> </Sequence>
</ThreeCanvas> </ThreeCanvas>
``` ```

View File

@@ -26,4 +26,4 @@ export const FadeIn = () => {
``` ```
CSS transitions or animations are FORBIDDEN - they will not render correctly. CSS transitions or animations are FORBIDDEN - they will not render correctly.
Tailwind animation class names are FORBIDDEN - they will not render correctly. Tailwind animation class names are FORBIDDEN - they will not render correctly.

View File

@@ -143,4 +143,4 @@ export const RemotionRoot = () => {
}; };
``` ```
The function can return `props`, `durationInFrames`, `width`, `height`, `fps`, and codec-related defaults. It runs once before rendering begins. The function can return `props`, `durationInFrames`, `width`, `height`, `fps`, and codec-related defaults. It runs once before rendering begins.

View File

@@ -8,4 +8,4 @@ You can and should use TailwindCSS in Remotion, if TailwindCSS is installed in t
Don't use `transition-*` or `animate-*` classes - always animate using the `useCurrentFrame()` hook. Don't use `transition-*` or `animate-*` classes - always animate using the `useCurrentFrame()` hook.
Tailwind must be installed and enabled first in a Remotion project - fetch <https://www.remotion.dev/docs/tailwind> using WebFetch for instructions. Tailwind must be installed and enabled first in a Remotion project - fetch <https://www.remotion.dev/docs/tailwind> using WebFetch for instructions.

View File

@@ -1,82 +0,0 @@
/**
* Tests for scripts/hooks/design-quality-check.js
*
* Run with: node tests/hooks/design-quality-check.test.js
*/
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const hook = require('../../scripts/hooks/design-quality-check');
function test(name, fn) {
try {
fn();
console.log(`${name}`);
return true;
} catch (err) {
console.log(`${name}`);
console.log(` Error: ${err.message}`);
return false;
}
}
let passed = 0;
let failed = 0;
console.log('\nDesign Quality Hook Tests');
console.log('=========================\n');
if (test('passes through non-frontend files silently', () => {
const input = JSON.stringify({ tool_input: { file_path: '/tmp/file.py' } });
const result = hook.run(input);
assert.strictEqual(result.exitCode, 0);
assert.strictEqual(result.stdout, input);
assert.ok(!result.stderr);
})) passed++; else failed++;
if (test('warns for frontend file path', () => {
const tmpFile = path.join(os.tmpdir(), `design-quality-${Date.now()}.tsx`);
fs.writeFileSync(tmpFile, 'export function Hero(){ return <div className="text-center">Get Started</div>; }\n');
try {
const input = JSON.stringify({ tool_input: { file_path: tmpFile } });
const result = hook.run(input);
assert.strictEqual(result.exitCode, 0);
assert.strictEqual(result.stdout, input);
assert.match(result.stderr, /DESIGN CHECK/);
assert.match(result.stderr, /Get Started/);
} finally {
fs.unlinkSync(tmpFile);
}
})) passed++; else failed++;
if (test('handles MultiEdit edits[] payloads', () => {
const tmpFile = path.join(os.tmpdir(), `design-quality-${Date.now()}.css`);
fs.writeFileSync(tmpFile, '.hero{background:linear-gradient(to right,#000,#333)}\n');
try {
const input = JSON.stringify({
tool_input: {
edits: [{ file_path: tmpFile }, { file_path: '/tmp/notes.md' }]
}
});
const result = hook.run(input);
assert.strictEqual(result.exitCode, 0);
assert.strictEqual(result.stdout, input);
assert.match(result.stderr, /frontend file\(s\) modified/);
assert.match(result.stderr, /\.css/);
} finally {
fs.unlinkSync(tmpFile);
}
})) passed++; else failed++;
if (test('returns original stdout on invalid JSON', () => {
const input = '{not valid json';
const result = hook.run(input);
assert.strictEqual(result.exitCode, 0);
assert.strictEqual(result.stdout, input);
})) passed++; else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);

View File

@@ -76,12 +76,6 @@ test('observe.sh default throttle is 20 observations per signal', () => {
assert.ok(content.includes('ECC_OBSERVER_SIGNAL_EVERY_N:-20'), 'Default signal frequency should be every 20 observations'); assert.ok(content.includes('ECC_OBSERVER_SIGNAL_EVERY_N:-20'), 'Default signal frequency should be every 20 observations');
}); });
test('observe.sh touches observer activity marker on each observation', () => {
const content = fs.readFileSync(observeShPath, 'utf8');
assert.ok(content.includes('ACTIVITY_FILE="${PROJECT_DIR}/.observer-last-activity"'), 'observe.sh should define a project-scoped activity marker');
assert.ok(content.includes('touch "$ACTIVITY_FILE"'), 'observe.sh should update activity marker during observation capture');
});
// ────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────
// Test group 2: observer-loop.sh re-entrancy guard // Test group 2: observer-loop.sh re-entrancy guard
// ────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────
@@ -132,19 +126,6 @@ test('default cooldown is 60 seconds', () => {
assert.ok(content.includes('ECC_OBSERVER_ANALYSIS_COOLDOWN:-60'), 'Default cooldown should be 60 seconds'); assert.ok(content.includes('ECC_OBSERVER_ANALYSIS_COOLDOWN:-60'), 'Default cooldown should be 60 seconds');
}); });
test('observer-loop.sh defines idle timeout fallback', () => {
const content = fs.readFileSync(observerLoopPath, 'utf8');
assert.ok(content.includes('IDLE_TIMEOUT_SECONDS'), 'observer-loop.sh should define an idle timeout');
assert.ok(content.includes('ECC_OBSERVER_IDLE_TIMEOUT_SECONDS:-1800'), 'Default idle timeout should be 30 minutes');
});
test('observer-loop.sh checks session lease directory before self-termination', () => {
const content = fs.readFileSync(observerLoopPath, 'utf8');
assert.ok(content.includes('SESSION_LEASE_DIR="${PROJECT_DIR}/.observer-sessions"'), 'observer-loop.sh should track active observer session leases');
assert.ok(content.includes('has_active_session_leases'), 'observer-loop.sh should define active session lease checks');
assert.ok(content.includes('exit_if_idle_without_sessions'), 'observer-loop.sh should define idle self-termination helper');
});
// ────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────
// Test group 4: Tail-based sampling (no full file load) // Test group 4: Tail-based sampling (no full file load)
// ────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────

View File

@@ -303,101 +303,6 @@ async function runTests() {
assert.strictEqual(result.code, 0, 'Non-blocking hook should exit 0'); assert.strictEqual(result.code, 0, 'Non-blocking hook should exit 0');
})) passed++; else failed++; })) passed++; else failed++;
if (await asyncTest('session-start registers an observer lease for the active session', async () => {
const testDir = createTestDir();
const projectDir = path.join(testDir, 'project');
fs.mkdirSync(projectDir, { recursive: true });
try {
const sessionId = `session-${Date.now()}`;
const result = await runHookWithInput(
path.join(scriptsDir, 'session-start.js'),
{},
{
HOME: testDir,
CLAUDE_PROJECT_DIR: projectDir,
CLAUDE_SESSION_ID: sessionId
}
);
assert.strictEqual(result.code, 0, 'SessionStart should exit 0');
const homunculusDir = path.join(testDir, '.claude', 'homunculus');
const projectsDir = path.join(homunculusDir, 'projects');
const projectEntries = fs.existsSync(projectsDir) ? fs.readdirSync(projectsDir) : [];
assert.ok(projectEntries.length > 0, 'SessionStart should create a homunculus project directory');
const leaseDir = path.join(projectsDir, projectEntries[0], '.observer-sessions');
const leaseFiles = fs.existsSync(leaseDir) ? fs.readdirSync(leaseDir).filter(name => name.endsWith('.json')) : [];
assert.ok(leaseFiles.length === 1, `Expected one observer lease file, found ${leaseFiles.length}`);
} finally {
cleanupTestDir(testDir);
}
})) passed++; else failed++;
if (await asyncTest('session-end-marker removes the last lease and stops the observer process', async () => {
const testDir = createTestDir();
const projectDir = path.join(testDir, 'project');
fs.mkdirSync(projectDir, { recursive: true });
const sessionId = `session-${Date.now()}`;
const sleeper = spawn(process.execPath, ['-e', "process.on('SIGTERM', () => process.exit(0)); setInterval(() => {}, 1000)"], {
stdio: 'ignore'
});
try {
await runHookWithInput(
path.join(scriptsDir, 'session-start.js'),
{},
{
HOME: testDir,
CLAUDE_PROJECT_DIR: projectDir,
CLAUDE_SESSION_ID: sessionId
}
);
const homunculusDir = path.join(testDir, '.claude', 'homunculus');
const projectsDir = path.join(homunculusDir, 'projects');
const projectEntries = fs.existsSync(projectsDir) ? fs.readdirSync(projectsDir) : [];
assert.ok(projectEntries.length > 0, 'Expected SessionStart to create a homunculus project directory');
const projectStorageDir = path.join(projectsDir, projectEntries[0]);
const pidFile = path.join(projectStorageDir, '.observer.pid');
fs.writeFileSync(pidFile, `${sleeper.pid}\n`);
const markerInput = { hook_event_name: 'SessionEnd' };
const result = await runHookWithInput(
path.join(scriptsDir, 'session-end-marker.js'),
markerInput,
{
HOME: testDir,
CLAUDE_PROJECT_DIR: projectDir,
CLAUDE_SESSION_ID: sessionId
}
);
assert.strictEqual(result.code, 0, 'SessionEnd marker should exit 0');
assert.strictEqual(result.stdout, JSON.stringify(markerInput), 'SessionEnd marker should pass stdin through unchanged');
await new Promise(resolve => setTimeout(resolve, 150));
const exited = sleeper.exitCode !== null || sleeper.signalCode !== null;
let processAlive = !exited;
if (processAlive) {
try {
process.kill(sleeper.pid, 0);
} catch {
processAlive = false;
}
}
assert.strictEqual(processAlive, false, 'SessionEnd marker should stop the observer process when the last lease ends');
const leaseDir = path.join(projectStorageDir, '.observer-sessions');
const leaseFiles = fs.existsSync(leaseDir) ? fs.readdirSync(leaseDir).filter(name => name.endsWith('.json')) : [];
assert.strictEqual(leaseFiles.length, 0, 'SessionEnd marker should remove the finished session lease');
assert.strictEqual(fs.existsSync(pidFile), false, 'SessionEnd marker should remove the observer pid file after stopping it');
} finally {
sleeper.kill();
cleanupTestDir(testDir);
}
})) passed++; else failed++;
if (await asyncTest('dev server hook transforms yarn dev to tmux session', async () => { if (await asyncTest('dev server hook transforms yarn dev to tmux session', async () => {
// The auto-tmux dev hook transforms dev commands (yarn dev, npm run dev, etc.) // The auto-tmux dev hook transforms dev commands (yarn dev, npm run dev, etc.)
const hookCommand = getHookCommandByDescription( const hookCommand = getHookCommandByDescription(

View File

@@ -60,50 +60,6 @@ function runTests() {
assert.ok(fs.existsSync(home), 'Home dir should exist'); assert.ok(fs.existsSync(home), 'Home dir should exist');
})) passed++; else failed++; })) passed++; else failed++;
if (test('getHomeDir prefers HOME override when set', () => {
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
const fakeHome = path.join(process.cwd(), 'tmp-home-override');
try {
process.env.HOME = fakeHome;
process.env.USERPROFILE = '';
assert.strictEqual(utils.getHomeDir(), fakeHome);
} finally {
if (originalHome === undefined) {
delete process.env.HOME;
} else {
process.env.HOME = originalHome;
}
if (originalUserProfile === undefined) {
delete process.env.USERPROFILE;
} else {
process.env.USERPROFILE = originalUserProfile;
}
}
})) passed++; else failed++;
if (test('getHomeDir falls back to USERPROFILE when HOME is empty', () => {
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
const fakeHome = path.join(process.cwd(), 'tmp-userprofile-override');
try {
process.env.HOME = '';
process.env.USERPROFILE = fakeHome;
assert.strictEqual(utils.getHomeDir(), fakeHome);
} finally {
if (originalHome === undefined) {
delete process.env.HOME;
} else {
process.env.HOME = originalHome;
}
if (originalUserProfile === undefined) {
delete process.env.USERPROFILE;
} else {
process.env.USERPROFILE = originalUserProfile;
}
}
})) passed++; else failed++;
if (test('getClaudeDir returns path under home', () => { if (test('getClaudeDir returns path under home', () => {
const claudeDir = utils.getClaudeDir(); const claudeDir = utils.getClaudeDir();
const homeDir = utils.getHomeDir(); const homeDir = utils.getHomeDir();

View File

@@ -25,11 +25,9 @@ function readJson(filePath) {
const REPO_ROOT = path.join(__dirname, '..', '..'); const REPO_ROOT = path.join(__dirname, '..', '..');
function run(args = [], options = {}) { function run(args = [], options = {}) {
const homeDir = options.homeDir || process.env.HOME;
const env = { const env = {
...process.env, ...process.env,
HOME: homeDir, HOME: options.homeDir || process.env.HOME,
USERPROFILE: homeDir,
...(options.env || {}), ...(options.env || {}),
}; };
@@ -367,13 +365,10 @@ function runTests() {
const settings = readJson(path.join(claudeRoot, 'settings.json')); const settings = readJson(path.join(claudeRoot, 'settings.json'));
const installedHooks = readJson(path.join(claudeRoot, 'hooks', 'hooks.json')); const installedHooks = readJson(path.join(claudeRoot, 'hooks', 'hooks.json'));
const normSep = (s) => s.replace(/\\/g, '/');
const expectedFragment = normSep(path.join(claudeRoot, 'scripts', 'hooks', 'auto-tmux-dev.js'));
const autoTmuxEntry = settings.hooks.PreToolUse.find(entry => entry.id === 'pre:bash:auto-tmux-dev'); const autoTmuxEntry = settings.hooks.PreToolUse.find(entry => entry.id === 'pre:bash:auto-tmux-dev');
assert.ok(autoTmuxEntry, 'settings.json should include the auto tmux hook'); assert.ok(autoTmuxEntry, 'settings.json should include the auto tmux hook');
assert.ok( assert.ok(
normSep(autoTmuxEntry.hooks[0].command).includes(expectedFragment), autoTmuxEntry.hooks[0].command.includes(path.join(claudeRoot, 'scripts', 'hooks', 'auto-tmux-dev.js')),
'settings.json should use the installed Claude root for hook commands' 'settings.json should use the installed Claude root for hook commands'
); );
assert.ok( assert.ok(
@@ -384,7 +379,7 @@ function runTests() {
const installedAutoTmuxEntry = installedHooks.hooks.PreToolUse.find(entry => entry.id === 'pre:bash:auto-tmux-dev'); const installedAutoTmuxEntry = installedHooks.hooks.PreToolUse.find(entry => entry.id === 'pre:bash:auto-tmux-dev');
assert.ok(installedAutoTmuxEntry, 'hooks/hooks.json should include the auto tmux hook'); assert.ok(installedAutoTmuxEntry, 'hooks/hooks.json should include the auto tmux hook');
assert.ok( assert.ok(
normSep(installedAutoTmuxEntry.hooks[0].command).includes(expectedFragment), installedAutoTmuxEntry.hooks[0].command.includes(path.join(claudeRoot, 'scripts', 'hooks', 'auto-tmux-dev.js')),
'hooks/hooks.json should use the installed Claude root for hook commands' 'hooks/hooks.json should use the installed Claude root for hook commands'
); );
assert.ok( assert.ok(

View File

@@ -38,11 +38,9 @@ function writeState(filePath, options) {
} }
function runNode(scriptPath, args = [], options = {}) { function runNode(scriptPath, args = [], options = {}) {
const homeDir = options.homeDir || process.env.HOME;
const env = { const env = {
...process.env, ...process.env,
HOME: homeDir, HOME: options.homeDir || process.env.HOME,
USERPROFILE: homeDir,
}; };
try { try {