Compare commits

..

1 Commits

Author SHA1 Message Date
Affaan Mustafa
c41b3f5bbc fix(ecc2): sync catalog counts for scaffold CI 2026-03-24 03:39:29 -07:00
18 changed files with 461 additions and 917 deletions

View File

@@ -5,7 +5,7 @@ description: Development conventions and patterns for everything-claude-code. Ja
# Everything Claude Code Conventions
> Generated from [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) on 2026-03-24
> Generated from [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) on 2026-03-20
## Overview
@@ -33,14 +33,14 @@ Follow these commit message conventions based on 500 analyzed commits.
### Prefixes Used
- `feat`
- `fix`
- `docs`
- `test`
- `feat`
- `docs`
### Message Guidelines
- Average message length: ~62 characters
- Average message length: ~65 characters
- Keep first line concise and descriptive
- Use imperative mood ("Add feature" not "Added feature")
@@ -48,49 +48,49 @@ Follow these commit message conventions based on 500 analyzed commits.
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill.md)
feat(rules): add C# language support
```
*Commit message example*
```text
perf(hooks): move post-edit-format and post-edit-typecheck to strict-only (#757)
chore(deps-dev): bump flatted (#675)
```
*Commit message example*
```text
fix: safe Codex config sync — merge AGENTS.md + add-only MCP servers (#723)
fix: auto-detect ECC root from plugin cache when CLAUDE_PLUGIN_ROOT is unset (#547) (#691)
```
*Commit message example*
```text
docs(zh-CN): translate code block(plain text) (#753)
docs: add Antigravity setup and usage guide (#552)
```
*Commit message example*
```text
security: remove supply chain risks, external promotions, and unauthorized credits
merge: PR #529 — feat(skills): add documentation-lookup, bun-runtime, nextjs-turbopack; feat(agents): add rust-reviewer
```
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md)
Revert "Add Kiro IDE support (.kiro/) (#548)"
```
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md)
Add Kiro IDE support (.kiro/) (#548)
```
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md)
feat: add block-no-verify hook for Claude Code and Cursor (#649)
```
## Architecture
@@ -196,20 +196,21 @@ Database schema changes with migration files
3. Generate/update types
**Files typically involved**:
- `**/schema.*`
- `migrations/*`
**Example commit sequence**:
```
Add Turkish (tr) docs and update README (#744)
docs(zh-CN): translate code block(plain text) (#753)
fix(install): add rust, cpp, csharp to legacy language alias map (#747)
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**: ~26 times per month
**Frequency**: ~22 times per month
**Steps**:
1. Add feature implementation
@@ -218,130 +219,204 @@ Standard feature implementation workflow
**Files typically involved**:
- `manifests/*`
- `schemas/*`
- `**/*.test.*`
- `**/api/**`
**Example commit sequence**:
```
Merge pull request #736 from pvgomes/docs/add-brazilian-portuguese-translation
fix: bump plugin.json and marketplace.json to v1.9.0
Add Turkish (tr) docs and update README (#744)
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 Or Update Ecc Command Doc
### Add Language Rules
Adds or updates documentation for an ECC command, typically as a Markdown file under .claude/commands.
Adds a new programming language to the rules system, including coding style, hooks, patterns, security, and testing guidelines.
**Frequency**: ~3 times per month
**Frequency**: ~2 times per month
**Steps**:
1. Create or update a Markdown file in .claude/commands/ describing the command.
2. Optionally, update related documentation elsewhere.
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**:
- `.claude/commands/*.md`
- `rules/*/coding-style.md`
- `rules/*/hooks.md`
- `rules/*/patterns.md`
- `rules/*/security.md`
- `rules/*/testing.md`
**Example commit sequence**:
```
Create or update a Markdown file in .claude/commands/ describing the command.
Optionally, update related documentation elsewhere.
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 Or Update Skill Doc
### Add New Skill
Adds or updates documentation for a skill, typically as SKILL.md under a skill directory.
Adds a new skill to the system, documenting its workflow, triggers, and usage, often with supporting scripts.
**Frequency**: ~3 times per month
**Frequency**: ~4 times per month
**Steps**:
1. Create or update SKILL.md in the relevant skill directory (e.g., skills/skill-name/SKILL.md).
2. Optionally, update related documentation or diagrams.
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`
- `.claude/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
- `.agents/skills/*/agents/openai.yaml`
**Example commit sequence**:
```
Create or update SKILL.md in the relevant skill directory (e.g., skills/skill-name/SKILL.md).
Optionally, update related documentation or diagrams.
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 Localization Docs
### Add Or Update Hook
Adds or updates localized documentation for agents, commands, skills, and guides in a new or existing language.
Adds or updates git or bash hooks to enforce workflow, quality, or security policies.
**Frequency**: ~2 times per month
**Frequency**: ~1 times per month
**Steps**:
1. Add or update multiple Markdown files under docs/<lang>/ for agents, commands, skills, rules, and guides.
2. Update README.md to reflect supported languages.
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**:
- `docs/*/README.md`
- `docs/*/agents/*.md`
- `docs/*/commands/*.md`
- `docs/*/skills/*/SKILL.md`
- `docs/*/rules/**/*.md`
- `hooks/*.hook`
- `hooks/hooks.json`
- `scripts/hooks/*.js`
- `tests/hooks/*.test.js`
- `.cursor/hooks.json`
**Example commit sequence**:
```
Add or update multiple Markdown files under docs/<lang>/ for agents, commands, skills, rules, and guides.
Update README.md to reflect supported languages.
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/
```
### Add Or Update Team Or Identity Config
### Address Review Feedback
Adds or updates team configuration or identity files for ECC.
Addresses code review feedback by updating documentation, scripts, or configuration for clarity, correctness, or convention alignment.
**Frequency**: ~2 times per month
**Frequency**: ~4 times per month
**Steps**:
1. Create or update .claude/team/everything-claude-code-team-config.json or .claude/identity.json.
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**:
- `.claude/team/everything-claude-code-team-config.json`
- `.claude/identity.json`
- `skills/*/SKILL.md`
- `agents/*.md`
- `commands/*.md`
- `.agents/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
**Example commit sequence**:
```
Create or update .claude/team/everything-claude-code-team-config.json or .claude/identity.json.
```
### Add Or Update Ecc Tools Config
Adds or updates the ECC tools configuration file.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/ecc-tools.json.
**Files typically involved**:
- `.claude/ecc-tools.json`
**Example commit sequence**:
```
Create or update .claude/ecc-tools.json.
```
### Add Or Update Agent Config
Adds or updates agent configuration TOML files for Codex or ECC agents.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .codex/agents/*.toml or .agents/skills/*/agents/*.yaml.
**Files typically involved**:
- `.codex/agents/*.toml`
- `.agents/skills/*/agents/*.yaml`
**Example commit sequence**:
```
Create or update .codex/agents/*.toml or .agents/skills/*/agents/*.yaml.
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
```

View File

@@ -1,34 +0,0 @@
---
name: add-or-update-ecc-command-doc
description: Workflow command scaffold for add-or-update-ecc-command-doc in everything-claude-code.
allowed_tools: ["Bash", "Read", "Write", "Grep", "Glob"]
---
# /add-or-update-ecc-command-doc
Use this workflow when working on **add-or-update-ecc-command-doc** in `everything-claude-code`.
## Goal
Adds or updates documentation for an ECC command, typically as a Markdown file under .claude/commands.
## Common Files
- `.claude/commands/*.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 a Markdown file in .claude/commands/ describing the command.
- Optionally, update related documentation elsewhere.
## Notes
- Treat this as a scaffold, not a hard-coded script.
- Update the command if the workflow evolves materially.

View File

@@ -1,38 +0,0 @@
---
name: add-or-update-skill-documentation
description: Workflow command scaffold for add-or-update-skill-documentation in everything-claude-code.
allowed_tools: ["Bash", "Read", "Write", "Grep", "Glob"]
---
# /add-or-update-skill-documentation
Use this workflow when working on **add-or-update-skill-documentation** in `everything-claude-code`.
## Goal
Adds a new skill or updates documentation for an existing skill. Typically involves creating or modifying SKILL.md files under skills/ or docs/xx/skills/ directories.
## Common Files
- `skills/*/SKILL.md`
- `docs/*/skills/*/SKILL.md`
- `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 under skills/<skill-name>/ or docs/<lang>/skills/<skill-name>/
- Optionally update AGENTS.md or README.md to reflect new skill count or catalog
- Commit with message referencing the skill and a summary of changes
## Notes
- Treat this as a scaffold, not a hard-coded script.
- Update the command if the workflow evolves materially.

View File

@@ -1,36 +0,0 @@
---
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, including documentation and configuration.
## Common Files
- `skills/*/SKILL.md`
- `docs/zh-CN/skills/*/SKILL.md`
- `docs/tr/skills/*/SKILL.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 the appropriate skills/ directory
- Optionally update related documentation or integration files
## Notes
- Treat this as a scaffold, not a hard-coded script.
- Update the command if the workflow evolves materially.

View File

@@ -14,6 +14,7 @@ Database schema changes with migration files
## Common Files
- `**/schema.*`
- `migrations/*`
## Suggested Sequence

View File

@@ -15,6 +15,7 @@ Standard feature implementation workflow
## Common Files
- `manifests/*`
- `schemas/*`
- `**/*.test.*`
- `**/api/**`

View File

@@ -2,7 +2,7 @@
"version": "1.3",
"schemaVersion": "1.0",
"generatedBy": "ecc-tools",
"generatedAt": "2026-03-24T10:43:58.912Z",
"generatedAt": "2026-03-20T12:07:36.496Z",
"repo": "https://github.com/affaan-m/everything-claude-code",
"profiles": {
"requested": "full",
@@ -150,7 +150,7 @@
".claude/enterprise/controls.md",
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-ecc-command-doc.md"
".claude/commands/add-language-rules.md"
],
"packageFiles": {
"runtime-core": [
@@ -180,7 +180,7 @@
"workflow-pack": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-ecc-command-doc.md"
".claude/commands/add-language-rules.md"
]
},
"moduleFiles": {
@@ -211,7 +211,7 @@
"workflow-pack": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-ecc-command-doc.md"
".claude/commands/add-language-rules.md"
]
},
"files": [
@@ -297,8 +297,8 @@
},
{
"moduleId": "workflow-pack",
"path": ".claude/commands/add-or-update-ecc-command-doc.md",
"description": "Workflow command scaffold for add-or-update-ecc-command-doc."
"path": ".claude/commands/add-language-rules.md",
"description": "Workflow command scaffold for add-language-rules."
}
],
"workflows": [
@@ -311,8 +311,8 @@
"path": ".claude/commands/feature-development.md"
},
{
"command": "add-or-update-ecc-command-doc",
"path": ".claude/commands/add-or-update-ecc-command-doc.md"
"command": "add-language-rules",
"path": ".claude/commands/add-language-rules.md"
}
],
"adapters": {
@@ -322,7 +322,7 @@
"commandPaths": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-ecc-command-doc.md"
".claude/commands/add-language-rules.md"
]
},
"codex": {

View File

@@ -10,5 +10,5 @@
"javascript"
],
"suggestedBy": "ecc-tools-repo-analysis",
"createdAt": "2026-03-24T10:44:13.997Z"
"createdAt": "2026-03-20T12:07:57.119Z"
}

View File

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

View File

@@ -4,7 +4,7 @@ Generated by ECC Tools from repository history. Review before treating it as a h
## Commit Workflow
- Prefer `conventional` commit messaging with prefixes such as feat, fix, docs, test.
- Prefer `conventional` commit messaging with prefixes such as fix, test, feat, docs.
- Keep new changes aligned with the existing pull-request and review flow already present in the repo.
## Architecture
@@ -26,7 +26,7 @@ Generated by ECC Tools from repository history. Review before treating it as a h
- database-migration: Database schema changes with migration files
- feature-development: Standard feature implementation workflow
- add-or-update-ecc-command-doc: Adds or updates documentation for an ECC command, typically as a Markdown file under .claude/commands.
- add-language-rules: Adds a new programming language to the rules system, including coding style, hooks, patterns, security, and testing guidelines.
## Review Reminder

View File

@@ -5,7 +5,7 @@ description: Development conventions and patterns for everything-claude-code. Ja
# Everything Claude Code Conventions
> Generated from [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) on 2026-03-24
> Generated from [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) on 2026-03-20
## Overview
@@ -33,14 +33,14 @@ Follow these commit message conventions based on 500 analyzed commits.
### Prefixes Used
- `feat`
- `fix`
- `docs`
- `test`
- `feat`
- `docs`
### Message Guidelines
- Average message length: ~62 characters
- Average message length: ~65 characters
- Keep first line concise and descriptive
- Use imperative mood ("Add feature" not "Added feature")
@@ -48,49 +48,49 @@ Follow these commit message conventions based on 500 analyzed commits.
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill.md)
feat(rules): add C# language support
```
*Commit message example*
```text
perf(hooks): move post-edit-format and post-edit-typecheck to strict-only (#757)
chore(deps-dev): bump flatted (#675)
```
*Commit message example*
```text
fix: safe Codex config sync — merge AGENTS.md + add-only MCP servers (#723)
fix: auto-detect ECC root from plugin cache when CLAUDE_PLUGIN_ROOT is unset (#547) (#691)
```
*Commit message example*
```text
docs(zh-CN): translate code block(plain text) (#753)
docs: add Antigravity setup and usage guide (#552)
```
*Commit message example*
```text
security: remove supply chain risks, external promotions, and unauthorized credits
merge: PR #529 — feat(skills): add documentation-lookup, bun-runtime, nextjs-turbopack; feat(agents): add rust-reviewer
```
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md)
Revert "Add Kiro IDE support (.kiro/) (#548)"
```
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md)
Add Kiro IDE support (.kiro/) (#548)
```
*Commit message example*
```text
feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md)
feat: add block-no-verify hook for Claude Code and Cursor (#649)
```
## Architecture
@@ -196,20 +196,21 @@ Database schema changes with migration files
3. Generate/update types
**Files typically involved**:
- `**/schema.*`
- `migrations/*`
**Example commit sequence**:
```
Add Turkish (tr) docs and update README (#744)
docs(zh-CN): translate code block(plain text) (#753)
fix(install): add rust, cpp, csharp to legacy language alias map (#747)
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**: ~26 times per month
**Frequency**: ~22 times per month
**Steps**:
1. Add feature implementation
@@ -218,130 +219,204 @@ Standard feature implementation workflow
**Files typically involved**:
- `manifests/*`
- `schemas/*`
- `**/*.test.*`
- `**/api/**`
**Example commit sequence**:
```
Merge pull request #736 from pvgomes/docs/add-brazilian-portuguese-translation
fix: bump plugin.json and marketplace.json to v1.9.0
Add Turkish (tr) docs and update README (#744)
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 Or Update Ecc Command Doc
### Add Language Rules
Adds or updates documentation for an ECC command, typically as a Markdown file under .claude/commands.
Adds a new programming language to the rules system, including coding style, hooks, patterns, security, and testing guidelines.
**Frequency**: ~3 times per month
**Frequency**: ~2 times per month
**Steps**:
1. Create or update a Markdown file in .claude/commands/ describing the command.
2. Optionally, update related documentation elsewhere.
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**:
- `.claude/commands/*.md`
- `rules/*/coding-style.md`
- `rules/*/hooks.md`
- `rules/*/patterns.md`
- `rules/*/security.md`
- `rules/*/testing.md`
**Example commit sequence**:
```
Create or update a Markdown file in .claude/commands/ describing the command.
Optionally, update related documentation elsewhere.
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 Or Update Skill Doc
### Add New Skill
Adds or updates documentation for a skill, typically as SKILL.md under a skill directory.
Adds a new skill to the system, documenting its workflow, triggers, and usage, often with supporting scripts.
**Frequency**: ~3 times per month
**Frequency**: ~4 times per month
**Steps**:
1. Create or update SKILL.md in the relevant skill directory (e.g., skills/skill-name/SKILL.md).
2. Optionally, update related documentation or diagrams.
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`
- `.claude/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
- `.agents/skills/*/agents/openai.yaml`
**Example commit sequence**:
```
Create or update SKILL.md in the relevant skill directory (e.g., skills/skill-name/SKILL.md).
Optionally, update related documentation or diagrams.
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 Localization Docs
### Add Or Update Hook
Adds or updates localized documentation for agents, commands, skills, and guides in a new or existing language.
Adds or updates git or bash hooks to enforce workflow, quality, or security policies.
**Frequency**: ~2 times per month
**Frequency**: ~1 times per month
**Steps**:
1. Add or update multiple Markdown files under docs/<lang>/ for agents, commands, skills, rules, and guides.
2. Update README.md to reflect supported languages.
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**:
- `docs/*/README.md`
- `docs/*/agents/*.md`
- `docs/*/commands/*.md`
- `docs/*/skills/*/SKILL.md`
- `docs/*/rules/**/*.md`
- `hooks/*.hook`
- `hooks/hooks.json`
- `scripts/hooks/*.js`
- `tests/hooks/*.test.js`
- `.cursor/hooks.json`
**Example commit sequence**:
```
Add or update multiple Markdown files under docs/<lang>/ for agents, commands, skills, rules, and guides.
Update README.md to reflect supported languages.
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/
```
### Add Or Update Team Or Identity Config
### Address Review Feedback
Adds or updates team configuration or identity files for ECC.
Addresses code review feedback by updating documentation, scripts, or configuration for clarity, correctness, or convention alignment.
**Frequency**: ~2 times per month
**Frequency**: ~4 times per month
**Steps**:
1. Create or update .claude/team/everything-claude-code-team-config.json or .claude/identity.json.
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**:
- `.claude/team/everything-claude-code-team-config.json`
- `.claude/identity.json`
- `skills/*/SKILL.md`
- `agents/*.md`
- `commands/*.md`
- `.agents/skills/*/SKILL.md`
- `.cursor/skills/*/SKILL.md`
**Example commit sequence**:
```
Create or update .claude/team/everything-claude-code-team-config.json or .claude/identity.json.
```
### Add Or Update Ecc Tools Config
Adds or updates the ECC tools configuration file.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/ecc-tools.json.
**Files typically involved**:
- `.claude/ecc-tools.json`
**Example commit sequence**:
```
Create or update .claude/ecc-tools.json.
```
### Add Or Update Agent Config
Adds or updates agent configuration TOML files for Codex or ECC agents.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .codex/agents/*.toml or .agents/skills/*/agents/*.yaml.
**Files typically involved**:
- `.codex/agents/*.toml`
- `.agents/skills/*/agents/*.yaml`
**Example commit sequence**:
```
Create or update .codex/agents/*.toml or .agents/skills/*/agents/*.yaml.
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
```

View File

@@ -9,7 +9,7 @@
"commandFiles": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-ecc-command-doc.md"
".claude/commands/add-language-rules.md"
],
"updatedAt": "2026-03-24T10:43:58.912Z"
"updatedAt": "2026-03-20T12:07:36.496Z"
}

View File

@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — Agent Instructions
This is a **production-ready AI coding plugin** providing 28 specialized agents, 116 skills, 60 commands, and automated hook workflows for software development.
This is a **production-ready AI coding plugin** providing 28 specialized agents, 118 skills, 60 commands, and automated hook workflows for software development.
**Version:** 1.9.0

View File

@@ -212,7 +212,7 @@ For manual install instructions see the README in the `rules/` folder.
/plugin list everything-claude-code@everything-claude-code
```
**That's it!** You now have access to 28 agents, 116 skills, and 60 commands.
**That's it!** You now have access to 28 agents, 118 skills, and 60 commands.
---
@@ -1085,7 +1085,7 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|---------|-------------|----------|--------|
| Agents | ✅ 28 agents | ✅ 12 agents | **Claude Code leads** |
| Commands | ✅ 60 commands | ✅ 31 commands | **Claude Code leads** |
| Skills | ✅ 116 skills | ✅ 37 skills | **Claude Code leads** |
| Skills | ✅ 118 skills | ✅ 37 skills | **Claude Code leads** |
| Hooks | ✅ 8 event types | ✅ 11 events | **OpenCode has more!** |
| Rules | ✅ 29 rules | ✅ 13 instructions | **Claude Code leads** |
| MCP Servers | ✅ 14 servers | ✅ Full | **Full parity** |

View File

@@ -2,17 +2,7 @@ use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaneLayout {
#[default]
Horizontal,
Vertical,
Grid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub db_path: PathBuf,
pub worktree_root: PathBuf,
@@ -22,7 +12,6 @@ pub struct Config {
pub heartbeat_interval_secs: u64,
pub default_agent: String,
pub theme: Theme,
pub pane_layout: PaneLayout,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -43,7 +32,6 @@ impl Default for Config {
heartbeat_interval_secs: 30,
default_agent: "claude".to_string(),
theme: Theme::Dark,
pane_layout: PaneLayout::Horizontal,
}
}
}
@@ -64,28 +52,3 @@ impl Config {
}
}
}
#[cfg(test)]
mod tests {
use super::{Config, PaneLayout};
#[test]
fn default_config_uses_horizontal_pane_layout() {
assert_eq!(Config::default().pane_layout, PaneLayout::Horizontal);
}
#[test]
fn missing_pane_layout_deserializes_to_default() {
let cfg: Config = toml::from_str(r#"default_agent = "codex""#).unwrap();
assert_eq!(cfg.pane_layout, PaneLayout::Horizontal);
assert_eq!(cfg.default_agent, "codex");
}
#[test]
fn pane_layout_deserializes_from_toml() {
let cfg: Config = toml::from_str(r#"pane_layout = "grid""#).unwrap();
assert_eq!(cfg.pane_layout, PaneLayout::Grid);
}
}

View File

@@ -3,17 +3,6 @@ use rusqlite::Connection;
use std::path::Path;
use super::{Session, SessionMetrics, SessionState};
use crate::observability::ToolCallEvent;
#[derive(Debug, Clone, PartialEq)]
pub struct ToolLogEntry {
pub tool_name: String,
pub input_summary: String,
pub output_summary: String,
pub duration_ms: u64,
pub risk_score: f64,
pub timestamp: String,
}
pub struct StateStore {
conn: Connection,
@@ -181,21 +170,16 @@ impl StateStore {
pub fn get_session(&self, id: &str) -> Result<Option<Session>> {
let sessions = self.list_sessions()?;
Ok(sessions
.into_iter()
.find(|s| s.id == id || s.id.starts_with(id)))
Ok(sessions.into_iter().find(|s| s.id == id || s.id.starts_with(id)))
}
pub fn list_tool_logs(&self, session_id: &str, limit: usize) -> Result<Vec<ToolLogEntry>> {
let table_entries = self.list_tool_logs_from_table(session_id, limit)?;
if !table_entries.is_empty() {
return Ok(table_entries);
}
self.list_tool_logs_from_messages(session_id, limit)
}
pub fn send_message(&self, from: &str, to: &str, content: &str, msg_type: &str) -> Result<()> {
pub fn send_message(
&self,
from: &str,
to: &str,
content: &str,
msg_type: &str,
) -> Result<()> {
self.conn.execute(
"INSERT INTO messages (from_session, to_session, content, msg_type, timestamp)
VALUES (?1, ?2, ?3, ?4, ?5)",
@@ -203,76 +187,4 @@ impl StateStore {
)?;
Ok(())
}
fn list_tool_logs_from_table(
&self,
session_id: &str,
limit: usize,
) -> Result<Vec<ToolLogEntry>> {
let mut stmt = self.conn.prepare(
"SELECT tool_name,
COALESCE(input_summary, ''),
COALESCE(output_summary, ''),
COALESCE(duration_ms, 0),
risk_score,
timestamp
FROM tool_log
WHERE session_id = ?1
ORDER BY timestamp DESC
LIMIT ?2",
)?;
let entries = stmt
.query_map(rusqlite::params![session_id, limit as i64], |row| {
Ok(ToolLogEntry {
tool_name: row.get(0)?,
input_summary: row.get(1)?,
output_summary: row.get(2)?,
duration_ms: row.get(3)?,
risk_score: row.get(4)?,
timestamp: row.get(5)?,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(entries)
}
fn list_tool_logs_from_messages(
&self,
session_id: &str,
limit: usize,
) -> Result<Vec<ToolLogEntry>> {
let mut stmt = self.conn.prepare(
"SELECT content, timestamp
FROM messages
WHERE from_session = ?1 AND msg_type = 'tool_call'
ORDER BY timestamp DESC
LIMIT ?2",
)?;
let rows = stmt
.query_map(rusqlite::params![session_id, limit as i64], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?
.collect::<Result<Vec<_>, _>>()?;
let entries = rows
.into_iter()
.filter_map(|(content, timestamp)| {
serde_json::from_str::<ToolCallEvent>(&content)
.ok()
.map(|event| ToolLogEntry {
tool_name: event.tool_name,
input_summary: event.input_summary,
output_summary: event.output_summary,
duration_ms: event.duration_ms,
risk_score: event.risk_score,
timestamp,
})
})
.collect();
Ok(entries)
}
}

View File

@@ -32,10 +32,6 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
(_, KeyCode::Char('q')) => break,
(_, KeyCode::Tab) => dashboard.next_pane(),
(KeyModifiers::SHIFT, KeyCode::BackTab) => dashboard.prev_pane(),
(_, KeyCode::Char('+')) | (_, KeyCode::Char('=')) => {
dashboard.increase_pane_size()
}
(_, KeyCode::Char('-')) => dashboard.decrease_pane_size(),
(_, KeyCode::Char('j')) | (_, KeyCode::Down) => dashboard.scroll_down(),
(_, KeyCode::Char('k')) | (_, KeyCode::Up) => dashboard.scroll_up(),
(_, KeyCode::Char('n')) => dashboard.new_session(),

View File

@@ -1,78 +1,50 @@
use ratatui::{
prelude::*,
widgets::{Block, Borders, List, ListItem, Paragraph, Tabs, Wrap},
widgets::{Block, Borders, List, ListItem, Paragraph, Tabs},
};
use crate::config::{Config, PaneLayout};
use crate::session::store::{StateStore, ToolLogEntry};
use crate::config::Config;
use crate::session::{Session, SessionState};
const DEFAULT_PANE_SIZE_PERCENT: u16 = 35;
const DEFAULT_GRID_SIZE_PERCENT: u16 = 50;
const OUTPUT_PANE_PERCENT: u16 = 70;
const MIN_PANE_SIZE_PERCENT: u16 = 20;
const MAX_PANE_SIZE_PERCENT: u16 = 80;
const PANE_RESIZE_STEP_PERCENT: u16 = 5;
const MAX_LOG_ENTRIES: usize = 12;
use crate::session::store::StateStore;
pub struct Dashboard {
db: StateStore,
cfg: Config,
sessions: Vec<Session>,
logs: Vec<ToolLogEntry>,
selected_pane: Pane,
selected_session: usize,
show_help: bool,
scroll_offset: usize,
pane_size_percent: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum Pane {
Sessions,
Output,
Metrics,
Log,
}
#[derive(Debug, Clone, Copy)]
struct PaneAreas {
sessions: Rect,
output: Rect,
metrics: Rect,
log: Option<Rect>,
}
impl Dashboard {
pub fn new(db: StateStore, cfg: Config) -> Self {
let pane_size_percent = match cfg.pane_layout {
PaneLayout::Grid => DEFAULT_GRID_SIZE_PERCENT,
PaneLayout::Horizontal | PaneLayout::Vertical => DEFAULT_PANE_SIZE_PERCENT,
};
let sessions = db.list_sessions().unwrap_or_default();
let mut dashboard = Self {
Self {
db,
cfg,
sessions,
logs: Vec::new(),
selected_pane: Pane::Sessions,
selected_session: 0,
show_help: false,
scroll_offset: 0,
pane_size_percent,
};
dashboard.refresh_logs();
dashboard
}
}
pub fn render(&self, frame: &mut Frame) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(10),
Constraint::Length(3),
Constraint::Length(3), // Header
Constraint::Min(10), // Main content
Constraint::Length(3), // Status bar
])
.split(frame.area());
@@ -81,204 +53,137 @@ impl Dashboard {
if self.show_help {
self.render_help(frame, chunks[1]);
} else {
let pane_areas = self.pane_areas(chunks[1]);
self.render_sessions(frame, pane_areas.sessions);
self.render_output(frame, pane_areas.output);
self.render_metrics(frame, pane_areas.metrics);
let main_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(35), // Session list
Constraint::Percentage(65), // Output/details
])
.split(chunks[1]);
if let Some(log_area) = pane_areas.log {
self.render_log(frame, log_area);
}
self.render_sessions(frame, main_chunks[0]);
let right_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(70), // Output
Constraint::Percentage(30), // Metrics
])
.split(main_chunks[1]);
self.render_output(frame, right_chunks[0]);
self.render_metrics(frame, right_chunks[1]);
}
self.render_status_bar(frame, chunks[2]);
}
fn render_header(&self, frame: &mut Frame, area: Rect) {
let running = self
.sessions
.iter()
.filter(|session| session.state == SessionState::Running)
.count();
let running = self.sessions.iter().filter(|s| s.state == SessionState::Running).count();
let total = self.sessions.len();
let title = format!(
" ECC 2.0 | {running} running / {total} total | {} {}% ",
self.layout_label(),
self.pane_size_percent
);
let tabs = Tabs::new(
self.visible_panes()
.iter()
.map(|pane| pane.title())
.collect::<Vec<_>>(),
)
.block(Block::default().borders(Borders::ALL).title(title))
.select(self.selected_pane_index())
.highlight_style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
);
let title = format!(" ECC 2.0 | {running} running / {total} total ");
let tabs = Tabs::new(vec!["Sessions", "Output", "Metrics"])
.block(Block::default().borders(Borders::ALL).title(title))
.select(match self.selected_pane {
Pane::Sessions => 0,
Pane::Output => 1,
Pane::Metrics => 2,
})
.highlight_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD));
frame.render_widget(tabs, area);
}
fn render_sessions(&self, frame: &mut Frame, area: Rect) {
let items: Vec<ListItem> = if self.sessions.is_empty() {
vec![ListItem::new("No sessions. Press 'n' to start one.")]
let items: Vec<ListItem> = self
.sessions
.iter()
.enumerate()
.map(|(i, s)| {
let state_icon = match s.state {
SessionState::Running => "",
SessionState::Idle => "",
SessionState::Completed => "",
SessionState::Failed => "",
SessionState::Stopped => "",
SessionState::Pending => "",
};
let style = if i == self.selected_session {
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let text = format!("{state_icon} {} [{}] {}", &s.id[..8.min(s.id.len())], s.agent_type, s.task);
ListItem::new(text).style(style)
})
.collect();
let border_style = if self.selected_pane == Pane::Sessions {
Style::default().fg(Color::Cyan)
} else {
self.sessions
.iter()
.enumerate()
.map(|(index, session)| {
let state_icon = match session.state {
SessionState::Running => "",
SessionState::Idle => "",
SessionState::Completed => "",
SessionState::Failed => "",
SessionState::Stopped => "",
SessionState::Pending => "",
};
let style = if index == self.selected_session {
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let text = format!(
"{state_icon} {} [{}] {}",
&session.id[..8.min(session.id.len())],
session.agent_type,
session.task
);
ListItem::new(text).style(style)
})
.collect()
Style::default()
};
let list = List::new(items).block(
Block::default()
.borders(Borders::ALL)
.title(" Sessions ")
.border_style(self.pane_border_style(Pane::Sessions)),
.border_style(border_style),
);
frame.render_widget(list, area);
}
fn render_output(&self, frame: &mut Frame, area: Rect) {
let content = if let Some(session) = self.current_session() {
let worktree = session
.worktree
.as_ref()
.map(|worktree| {
format!(
"Worktree: {}\nBranch: {}\n",
worktree.path.display(),
worktree.branch
)
})
.unwrap_or_default();
format!(
"Session: {}\nAgent: {}\nState: {}\nTask: {}\nUpdated: {}\n{}\
\nLive streaming output is not wired yet. Session context is shown here until the stream viewer lands.",
session.id,
session.agent_type,
session.state,
session.task,
session.updated_at.format("%Y-%m-%d %H:%M:%S UTC"),
worktree
)
let content = if let Some(session) = self.sessions.get(self.selected_session) {
format!("Agent output for session {}...\n\n(Live streaming coming soon)", session.id)
} else {
"No sessions. Press 'n' to start one.".to_string()
};
let paragraph = Paragraph::new(content)
.block(
Block::default()
.borders(Borders::ALL)
.title(" Output ")
.border_style(self.pane_border_style(Pane::Output)),
)
.scroll((self.scroll_offset_u16(), 0))
.wrap(Wrap { trim: false });
let border_style = if self.selected_pane == Pane::Output {
Style::default().fg(Color::Cyan)
} else {
Style::default()
};
let paragraph = Paragraph::new(content).block(
Block::default()
.borders(Borders::ALL)
.title(" Output ")
.border_style(border_style),
);
frame.render_widget(paragraph, area);
}
fn render_metrics(&self, frame: &mut Frame, area: Rect) {
let content = if let Some(session) = self.current_session() {
let metrics = &session.metrics;
let content = if let Some(session) = self.sessions.get(self.selected_session) {
let m = &session.metrics;
format!(
"Tokens: {}\nTools: {}\nFiles: {}\nCost: ${:.4}\nDuration: {}s",
metrics.tokens_used,
metrics.tool_calls,
metrics.files_changed,
metrics.cost_usd,
metrics.duration_secs
"Tokens: {} | Tools: {} | Files: {} | Cost: ${:.4} | Duration: {}s",
m.tokens_used, m.tool_calls, m.files_changed, m.cost_usd, m.duration_secs
)
} else {
"No metrics available".to_string()
};
let paragraph = Paragraph::new(content)
.block(
Block::default()
.borders(Borders::ALL)
.title(" Metrics ")
.border_style(self.pane_border_style(Pane::Metrics)),
)
.scroll((self.scroll_offset_u16(), 0))
.wrap(Wrap { trim: false });
frame.render_widget(paragraph, area);
}
fn render_log(&self, frame: &mut Frame, area: Rect) {
let content = if self.current_session().is_none() {
"No session selected".to_string()
} else if self.logs.is_empty() {
"No tool logs available for this session.\n\nTool call observability events will appear here when they are recorded."
.to_string()
let border_style = if self.selected_pane == Pane::Metrics {
Style::default().fg(Color::Cyan)
} else {
self.logs
.iter()
.map(|entry| {
format!(
"[{}] {} | {}ms | risk {:.0}%\ninput: {}\noutput: {}",
self.short_timestamp(&entry.timestamp),
entry.tool_name,
entry.duration_ms,
entry.risk_score * 100.0,
self.log_field(&entry.input_summary),
self.log_field(&entry.output_summary)
)
})
.collect::<Vec<_>>()
.join("\n\n")
Style::default()
};
let paragraph = Paragraph::new(content)
.block(
Block::default()
.borders(Borders::ALL)
.title(" Log ")
.border_style(self.pane_border_style(Pane::Log)),
)
.scroll((self.scroll_offset_u16(), 0))
.wrap(Wrap { trim: false });
let paragraph = Paragraph::new(content).block(
Block::default()
.borders(Borders::ALL)
.title(" Metrics ")
.border_style(border_style),
);
frame.render_widget(paragraph, area);
}
fn render_status_bar(&self, frame: &mut Frame, area: Rect) {
let text = format!(
" [n]ew session [s]top [Tab] switch pane [j/k] scroll [+/-] resize [{}] layout [?] help [q]uit ",
self.layout_label()
);
let text = " [n]ew session [s]top [Tab] switch pane [j/k] scroll [?] help [q]uit ";
let paragraph = Paragraph::new(text)
.style(Style::default().fg(Color::DarkGray))
.block(Block::default().borders(Borders::ALL));
@@ -295,8 +200,6 @@ impl Dashboard {
" S-Tab Previous pane",
" j/↓ Scroll down",
" k/↑ Scroll up",
" +/= Increase pane size",
" - Decrease pane size",
" r Refresh",
" ? Toggle help",
" q/C-c Quit",
@@ -312,46 +215,24 @@ impl Dashboard {
}
pub fn next_pane(&mut self) {
let visible_panes = self.visible_panes();
let next_index = self
.selected_pane_index()
.checked_add(1)
.map(|index| index % visible_panes.len())
.unwrap_or(0);
self.selected_pane = visible_panes[next_index];
self.scroll_offset = 0;
self.selected_pane = match self.selected_pane {
Pane::Sessions => Pane::Output,
Pane::Output => Pane::Metrics,
Pane::Metrics => Pane::Sessions,
};
}
pub fn prev_pane(&mut self) {
let visible_panes = self.visible_panes();
let previous_index = if self.selected_pane_index() == 0 {
visible_panes.len() - 1
} else {
self.selected_pane_index() - 1
self.selected_pane = match self.selected_pane {
Pane::Sessions => Pane::Metrics,
Pane::Output => Pane::Sessions,
Pane::Metrics => Pane::Output,
};
self.selected_pane = visible_panes[previous_index];
self.scroll_offset = 0;
}
pub fn increase_pane_size(&mut self) {
self.pane_size_percent =
(self.pane_size_percent + PANE_RESIZE_STEP_PERCENT).min(MAX_PANE_SIZE_PERCENT);
}
pub fn decrease_pane_size(&mut self) {
self.pane_size_percent = self
.pane_size_percent
.saturating_sub(PANE_RESIZE_STEP_PERCENT)
.max(MIN_PANE_SIZE_PERCENT);
}
pub fn scroll_down(&mut self) {
if self.selected_pane == Pane::Sessions && !self.sessions.is_empty() {
self.selected_session = (self.selected_session + 1).min(self.sessions.len() - 1);
self.scroll_offset = 0;
self.refresh_logs();
} else {
self.scroll_offset = self.scroll_offset.saturating_add(1);
}
@@ -359,19 +240,14 @@ impl Dashboard {
pub fn scroll_up(&mut self) {
if self.selected_pane == Pane::Sessions {
let previous_index = self.selected_session;
self.selected_session = self.selected_session.saturating_sub(1);
if self.selected_session != previous_index {
self.scroll_offset = 0;
self.refresh_logs();
}
} else {
self.scroll_offset = self.scroll_offset.saturating_sub(1);
}
}
pub fn new_session(&mut self) {
// TODO: Open a dialog to create a new session
tracing::info!("New session dialog requested");
}
@@ -383,7 +259,7 @@ impl Dashboard {
}
pub fn refresh(&mut self) {
self.sync_from_store();
self.sessions = self.db.list_sessions().unwrap_or_default();
}
pub fn toggle_help(&mut self) {
@@ -391,254 +267,7 @@ impl Dashboard {
}
pub async fn tick(&mut self) {
self.sync_from_store();
}
fn sync_from_store(&mut self) {
// Periodic refresh every few ticks
self.sessions = self.db.list_sessions().unwrap_or_default();
self.clamp_selected_session();
self.ensure_selected_pane_visible();
self.refresh_logs();
}
fn current_session(&self) -> Option<&Session> {
self.sessions.get(self.selected_session)
}
fn refresh_logs(&mut self) {
let session_id = self.current_session().map(|session| session.id.clone());
self.logs = session_id
.and_then(|id| self.db.list_tool_logs(&id, MAX_LOG_ENTRIES).ok())
.unwrap_or_default();
}
fn clamp_selected_session(&mut self) {
if self.sessions.is_empty() {
self.selected_session = 0;
return;
}
self.selected_session = self.selected_session.min(self.sessions.len() - 1);
}
fn ensure_selected_pane_visible(&mut self) {
if !self.visible_panes().contains(&self.selected_pane) {
self.selected_pane = Pane::Sessions;
}
}
fn pane_areas(&self, area: Rect) -> PaneAreas {
match self.cfg.pane_layout {
PaneLayout::Horizontal => {
let columns = Layout::default()
.direction(Direction::Horizontal)
.constraints(self.primary_constraints())
.split(area);
let right_rows = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(OUTPUT_PANE_PERCENT),
Constraint::Percentage(100 - OUTPUT_PANE_PERCENT),
])
.split(columns[1]);
PaneAreas {
sessions: columns[0],
output: right_rows[0],
metrics: right_rows[1],
log: None,
}
}
PaneLayout::Vertical => {
let rows = Layout::default()
.direction(Direction::Vertical)
.constraints(self.primary_constraints())
.split(area);
let bottom_columns = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(OUTPUT_PANE_PERCENT),
Constraint::Percentage(100 - OUTPUT_PANE_PERCENT),
])
.split(rows[1]);
PaneAreas {
sessions: rows[0],
output: bottom_columns[0],
metrics: bottom_columns[1],
log: None,
}
}
PaneLayout::Grid => {
let rows = Layout::default()
.direction(Direction::Vertical)
.constraints(self.primary_constraints())
.split(area);
let top_columns = Layout::default()
.direction(Direction::Horizontal)
.constraints(self.primary_constraints())
.split(rows[0]);
let bottom_columns = Layout::default()
.direction(Direction::Horizontal)
.constraints(self.primary_constraints())
.split(rows[1]);
PaneAreas {
sessions: top_columns[0],
output: top_columns[1],
metrics: bottom_columns[0],
log: Some(bottom_columns[1]),
}
}
}
}
fn primary_constraints(&self) -> [Constraint; 2] {
[
Constraint::Percentage(self.pane_size_percent),
Constraint::Percentage(100 - self.pane_size_percent),
]
}
fn visible_panes(&self) -> &'static [Pane] {
match self.cfg.pane_layout {
PaneLayout::Grid => &[Pane::Sessions, Pane::Output, Pane::Metrics, Pane::Log],
PaneLayout::Horizontal | PaneLayout::Vertical => {
&[Pane::Sessions, Pane::Output, Pane::Metrics]
}
}
}
fn selected_pane_index(&self) -> usize {
self.visible_panes()
.iter()
.position(|pane| *pane == self.selected_pane)
.unwrap_or(0)
}
fn pane_border_style(&self, pane: Pane) -> Style {
if self.selected_pane == pane {
Style::default().fg(Color::Cyan)
} else {
Style::default()
}
}
fn layout_label(&self) -> &'static str {
match self.cfg.pane_layout {
PaneLayout::Horizontal => "horizontal",
PaneLayout::Vertical => "vertical",
PaneLayout::Grid => "grid",
}
}
fn scroll_offset_u16(&self) -> u16 {
self.scroll_offset.min(u16::MAX as usize) as u16
}
fn log_field<'a>(&self, value: &'a str) -> &'a str {
let trimmed = value.trim();
if trimmed.is_empty() {
"n/a"
} else {
trimmed
}
}
fn short_timestamp(&self, timestamp: &str) -> String {
chrono::DateTime::parse_from_rfc3339(timestamp)
.map(|value| value.format("%H:%M:%S").to_string())
.unwrap_or_else(|_| timestamp.to_string())
}
}
impl Pane {
fn title(self) -> &'static str {
match self {
Pane::Sessions => "Sessions",
Pane::Output => "Output",
Pane::Metrics => "Metrics",
Pane::Log => "Log",
}
}
}
#[cfg(test)]
mod tests {
use super::{
Dashboard, Pane, DEFAULT_GRID_SIZE_PERCENT, MAX_PANE_SIZE_PERCENT, MIN_PANE_SIZE_PERCENT,
};
use crate::config::{Config, PaneLayout};
use crate::session::store::StateStore;
use ratatui::layout::Rect;
fn dashboard_for(layout: PaneLayout) -> Dashboard {
let mut cfg = Config::default();
cfg.pane_layout = layout;
let db_path =
std::env::temp_dir().join(format!("ecc-dashboard-test-{}.db", uuid::Uuid::new_v4()));
let db = StateStore::open(&db_path).unwrap();
Dashboard::new(db, cfg)
}
#[test]
fn grid_layout_uses_four_panes_in_two_rows() {
let dashboard = dashboard_for(PaneLayout::Grid);
let areas = dashboard.pane_areas(Rect::new(0, 0, 100, 40));
let log_area = areas.log.expect("grid layout should render a log pane");
assert_eq!(areas.sessions.y, areas.output.y);
assert_eq!(areas.metrics.y, log_area.y);
assert!(areas.metrics.y > areas.sessions.y);
assert_eq!(areas.sessions.x, 0);
assert_eq!(areas.metrics.x, 0);
assert!(areas.output.x > areas.sessions.x);
assert!(log_area.x > areas.metrics.x);
}
#[test]
fn non_grid_layouts_hide_the_log_pane() {
let horizontal = dashboard_for(PaneLayout::Horizontal);
let vertical = dashboard_for(PaneLayout::Vertical);
assert!(horizontal
.pane_areas(Rect::new(0, 0, 100, 40))
.log
.is_none());
assert!(vertical.pane_areas(Rect::new(0, 0, 100, 40)).log.is_none());
}
#[test]
fn pane_navigation_includes_log_only_for_grid_layouts() {
let mut horizontal = dashboard_for(PaneLayout::Horizontal);
horizontal.next_pane();
horizontal.next_pane();
horizontal.next_pane();
assert_eq!(horizontal.selected_pane, Pane::Sessions);
let mut grid = dashboard_for(PaneLayout::Grid);
grid.next_pane();
grid.next_pane();
grid.next_pane();
assert_eq!(grid.selected_pane, Pane::Log);
}
#[test]
fn pane_resize_clamps_to_supported_bounds() {
let mut dashboard = dashboard_for(PaneLayout::Grid);
assert_eq!(dashboard.pane_size_percent, DEFAULT_GRID_SIZE_PERCENT);
for _ in 0..20 {
dashboard.increase_pane_size();
}
assert_eq!(dashboard.pane_size_percent, MAX_PANE_SIZE_PERCENT);
for _ in 0..40 {
dashboard.decrease_pane_size();
}
assert_eq!(dashboard.pane_size_percent, MIN_PANE_SIZE_PERCENT);
}
}