Compare commits

..

46 Commits

Author SHA1 Message Date
ecc-tools[bot]
9aac8d3904 feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill.md) 2026-03-24 10:44:24 +00:00
ecc-tools[bot]
3e8efe77c8 feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md) 2026-03-24 10:44:23 +00:00
ecc-tools[bot]
48f27e6919 feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md) 2026-03-24 10:44:22 +00:00
ecc-tools[bot]
f0ea102ec7 feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md) 2026-03-24 10:44:21 +00:00
ecc-tools[bot]
65cb56ced6 feat: add everything-claude-code ECC bundle (.claude/team/everything-claude-code-team-config.json) 2026-03-24 10:44:20 +00:00
ecc-tools[bot]
3e4a74e580 feat: add everything-claude-code ECC bundle (.claude/research/everything-claude-code-research-playbook.md) 2026-03-24 10:44:19 +00:00
ecc-tools[bot]
1b64d43b8d feat: add everything-claude-code ECC bundle (.claude/rules/everything-claude-code-guardrails.md) 2026-03-24 10:44:18 +00:00
ecc-tools[bot]
01bdcc9f5e feat: add everything-claude-code ECC bundle (.codex/agents/docs-researcher.toml) 2026-03-24 10:44:17 +00:00
ecc-tools[bot]
ecbe939a21 feat: add everything-claude-code ECC bundle (.codex/agents/reviewer.toml) 2026-03-24 10:44:17 +00:00
ecc-tools[bot]
af45edc199 feat: add everything-claude-code ECC bundle (.codex/agents/explorer.toml) 2026-03-24 10:44:16 +00:00
ecc-tools[bot]
232b94873b feat: add everything-claude-code ECC bundle (.claude/identity.json) 2026-03-24 10:44:15 +00:00
ecc-tools[bot]
1eca64a933 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/agents/openai.yaml) 2026-03-24 10:44:14 +00:00
ecc-tools[bot]
b1c1805671 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/SKILL.md) 2026-03-24 10:44:13 +00:00
ecc-tools[bot]
8d7908ee8c feat: add everything-claude-code ECC bundle (.claude/skills/everything-claude-code/SKILL.md) 2026-03-24 10:44:12 +00:00
ecc-tools[bot]
c547b77f47 feat: add everything-claude-code ECC bundle (.claude/ecc-tools.json) 2026-03-24 10:44:11 +00:00
ecc-tools[bot]
f8d5e9e54c feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill.md) 2026-03-24 10:43:36 +00:00
ecc-tools[bot]
30c6a93d62 feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md) 2026-03-24 10:43:35 +00:00
ecc-tools[bot]
b05e272549 feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md) 2026-03-24 10:43:35 +00:00
ecc-tools[bot]
ff9ded5b92 feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md) 2026-03-24 10:43:34 +00:00
ecc-tools[bot]
f448eeb07b feat: add everything-claude-code ECC bundle (.claude/team/everything-claude-code-team-config.json) 2026-03-24 10:43:33 +00:00
ecc-tools[bot]
ff0ac80fad feat: add everything-claude-code ECC bundle (.claude/research/everything-claude-code-research-playbook.md) 2026-03-24 10:43:32 +00:00
ecc-tools[bot]
ccd43b8dc2 feat: add everything-claude-code ECC bundle (.claude/rules/everything-claude-code-guardrails.md) 2026-03-24 10:43:31 +00:00
ecc-tools[bot]
4e05da7d18 feat: add everything-claude-code ECC bundle (.codex/agents/docs-researcher.toml) 2026-03-24 10:43:30 +00:00
ecc-tools[bot]
161e08890e feat: add everything-claude-code ECC bundle (.codex/agents/reviewer.toml) 2026-03-24 10:43:30 +00:00
ecc-tools[bot]
792f8ae002 feat: add everything-claude-code ECC bundle (.codex/agents/explorer.toml) 2026-03-24 10:43:29 +00:00
ecc-tools[bot]
238d5478b7 feat: add everything-claude-code ECC bundle (.claude/identity.json) 2026-03-24 10:43:27 +00:00
ecc-tools[bot]
a8f26f47fd feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/agents/openai.yaml) 2026-03-24 10:43:27 +00:00
ecc-tools[bot]
249f4f81ab feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/SKILL.md) 2026-03-24 10:43:26 +00:00
ecc-tools[bot]
c91a13693c feat: add everything-claude-code ECC bundle (.claude/skills/everything-claude-code/SKILL.md) 2026-03-24 10:43:25 +00:00
ecc-tools[bot]
9680dfb8ee feat: add everything-claude-code ECC bundle (.claude/ecc-tools.json) 2026-03-24 10:43:24 +00:00
ecc-tools[bot]
24386fe30d feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill-documentation.md) 2026-03-24 10:42:00 +00:00
ecc-tools[bot]
0468c2159d feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md) 2026-03-24 10:41:59 +00:00
ecc-tools[bot]
c13e1d45e3 feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md) 2026-03-24 10:41:58 +00:00
ecc-tools[bot]
fe4fecc27d feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md) 2026-03-24 10:41:57 +00:00
ecc-tools[bot]
a8b29d96e9 feat: add everything-claude-code ECC bundle (.claude/team/everything-claude-code-team-config.json) 2026-03-24 10:41:56 +00:00
ecc-tools[bot]
9207edec71 feat: add everything-claude-code ECC bundle (.claude/research/everything-claude-code-research-playbook.md) 2026-03-24 10:41:55 +00:00
ecc-tools[bot]
549971e234 feat: add everything-claude-code ECC bundle (.claude/rules/everything-claude-code-guardrails.md) 2026-03-24 10:41:54 +00:00
ecc-tools[bot]
2a37a8197c feat: add everything-claude-code ECC bundle (.codex/agents/docs-researcher.toml) 2026-03-24 10:41:53 +00:00
ecc-tools[bot]
781dbfcbfe feat: add everything-claude-code ECC bundle (.codex/agents/reviewer.toml) 2026-03-24 10:41:53 +00:00
ecc-tools[bot]
796f0e4b56 feat: add everything-claude-code ECC bundle (.codex/agents/explorer.toml) 2026-03-24 10:41:52 +00:00
ecc-tools[bot]
124969f3c4 feat: add everything-claude-code ECC bundle (.claude/identity.json) 2026-03-24 10:41:51 +00:00
ecc-tools[bot]
1ec027cb9a feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/agents/openai.yaml) 2026-03-24 10:41:50 +00:00
ecc-tools[bot]
c688bde7a8 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/SKILL.md) 2026-03-24 10:41:49 +00:00
ecc-tools[bot]
3ae5a975ba feat: add everything-claude-code ECC bundle (.claude/skills/everything-claude-code/SKILL.md) 2026-03-24 10:41:48 +00:00
ecc-tools[bot]
1cae8895c7 feat: add everything-claude-code ECC bundle (.claude/ecc-tools.json) 2026-03-24 10:41:47 +00:00
Affaan Mustafa
8b34f72df3 feat(ecc2): add tool call logging and history 2026-03-24 03:39:53 -07:00
21 changed files with 596 additions and 573 deletions

View File

@@ -228,95 +228,158 @@ fix: bump plugin.json and marketplace.json to v1.9.0
Add Turkish (tr) docs and update README (#744)
```
### Add Or Update Command Doc
### Add Or Update Skill
Adds or updates documentation for a command, typically in Markdown under a language or locale-specific docs directory.
**Frequency**: ~3 times per month
**Steps**:
1. Create or update a Markdown file for the command in the appropriate docs directory (e.g., docs/zh-CN/commands/ or docs/tr/commands/).
2. Commit the new or changed file with a message referencing the command.
3. Optionally update README or language count if adding a new language.
**Files typically involved**:
- `docs/zh-CN/commands/*.md`
- `docs/tr/commands/*.md`
- `docs/pt-BR/commands/*.md`
**Example commit sequence**:
```
Create or update a Markdown file for the command in the appropriate docs directory (e.g., docs/zh-CN/commands/ or docs/tr/commands/).
Commit the new or changed file with a message referencing the command.
Optionally update README or language count if adding a new language.
```
### Add Or Update Skill Doc
Adds or updates documentation for a skill, typically in SKILL.md under a language or locale-specific docs directory.
**Frequency**: ~3 times per month
**Steps**:
1. Create or update a SKILL.md file for the skill in the appropriate docs directory (e.g., docs/zh-CN/skills/, docs/tr/skills/, docs/pt-BR/skills/).
2. Commit the new or changed file with a message referencing the skill.
**Files typically involved**:
- `docs/zh-CN/skills/*/SKILL.md`
- `docs/tr/skills/*/SKILL.md`
- `docs/pt-BR/skills/*/SKILL.md`
**Example commit sequence**:
```
Create or update a SKILL.md file for the skill in the appropriate docs directory (e.g., docs/zh-CN/skills/, docs/tr/skills/, docs/pt-BR/skills/).
Commit the new or changed file with a message referencing the skill.
```
### Add Or Update Locale Docs
Adds or updates a full set of localized documentation for a new or existing language, including agents, commands, skills, and guides.
Adds or updates a skill, including its documentation and configuration.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update multiple Markdown files under a new or existing language directory (e.g., docs/tr/, docs/pt-BR/, docs/zh-CN/).
2. Update README.md to increment supported language count and add references.
3. Commit all new or changed files.
1. Create or update .claude/commands/add-or-update-skill.md
2. Create or update .claude/skills/<skill-name>/SKILL.md
3. Optionally update .agents/skills/<skill-name>/SKILL.md
4. Optionally update .agents/skills/<skill-name>/agents/*.yaml
**Files typically involved**:
- `docs/tr/**/*`
- `docs/pt-BR/**/*`
- `docs/zh-CN/**/*`
- `README.md`
- `.claude/commands/add-or-update-skill.md`
- `.claude/skills/*/SKILL.md`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
**Example commit sequence**:
```
Create or update multiple Markdown files under a new or existing language directory (e.g., docs/tr/, docs/pt-BR/, docs/zh-CN/).
Update README.md to increment supported language count and add references.
Commit all new or changed files.
Create or update .claude/commands/add-or-update-skill.md
Create or update .claude/skills/<skill-name>/SKILL.md
Optionally update .agents/skills/<skill-name>/SKILL.md
Optionally update .agents/skills/<skill-name>/agents/*.yaml
```
### Add Or Update Ecc Bundle Command
### Add Or Update Command Documentation
Adds or updates ECC bundle command documentation or configuration, typically in .claude/commands/ or related ECC config directories.
Adds or updates documentation for a CLI command or workflow.
**Frequency**: ~2 times per month
**Frequency**: ~3 times per month
**Steps**:
1. Create or update a Markdown file in .claude/commands/ for the new or updated command.
2. Optionally update related config files (e.g., .claude/ecc-tools.json, .claude/identity.json).
3. Commit the changes.
1. Create or update .claude/commands/<command-name>.md
**Files typically involved**:
- `.claude/commands/*.md`
- `.claude/ecc-tools.json`
**Example commit sequence**:
```
Create or update .claude/commands/<command-name>.md
```
### Add Or Update Database Migration Patterns
Adds or updates database migration patterns or documentation.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/commands/database-migration.md
2. Create or update skills/database-migrations/SKILL.md
**Files typically involved**:
- `.claude/commands/database-migration.md`
- `skills/database-migrations/SKILL.md`
**Example commit sequence**:
```
Create or update .claude/commands/database-migration.md
Create or update skills/database-migrations/SKILL.md
```
### Add Or Update Team Or Identity Config
Adds or updates team configuration or identity files.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/team/everything-claude-code-team-config.json
2. Create or update .claude/identity.json
**Files typically involved**:
- `.claude/team/everything-claude-code-team-config.json`
- `.claude/identity.json`
**Example commit sequence**:
```
Create or update a Markdown file in .claude/commands/ for the new or updated command.
Optionally update related config files (e.g., .claude/ecc-tools.json, .claude/identity.json).
Commit the changes.
Create or update .claude/team/everything-claude-code-team-config.json
Create or update .claude/identity.json
```
### Add Or Update Guardrails Or Controls
Adds or updates project guardrails, rules, or enterprise controls.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/rules/everything-claude-code-guardrails.md
2. Create or update .claude/enterprise/controls.md
**Files typically involved**:
- `.claude/rules/everything-claude-code-guardrails.md`
- `.claude/enterprise/controls.md`
**Example commit sequence**:
```
Create or update .claude/rules/everything-claude-code-guardrails.md
Create or update .claude/enterprise/controls.md
```
### Add Or Update Agent Config
Adds or updates agent configuration TOML files.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .codex/agents/*.toml
**Files typically involved**:
- `.codex/agents/*.toml`
**Example commit sequence**:
```
Create or update .codex/agents/*.toml
```
### Add Or Update Research Playbook
Adds or updates research playbooks or process documentation.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/research/everything-claude-code-research-playbook.md
**Files typically involved**:
- `.claude/research/everything-claude-code-research-playbook.md`
**Example commit sequence**:
```
Create or update .claude/research/everything-claude-code-research-playbook.md
```
### Add Or Update Ecc Tools Config
Adds or updates ECC tools configuration.
**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
```

View File

@@ -1,37 +0,0 @@
---
name: add-or-update-command-doc
description: Workflow command scaffold for add-or-update-command-doc in everything-claude-code.
allowed_tools: ["Bash", "Read", "Write", "Grep", "Glob"]
---
# /add-or-update-command-doc
Use this workflow when working on **add-or-update-command-doc** in `everything-claude-code`.
## Goal
Adds or updates documentation for a command, typically in Markdown under a language or locale-specific docs directory.
## Common Files
- `docs/zh-CN/commands/*.md`
- `docs/tr/commands/*.md`
- `docs/pt-BR/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 for the command in the appropriate docs directory (e.g., docs/zh-CN/commands/ or docs/tr/commands/).
- Commit the new or changed file with a message referencing the command.
- Optionally update README or language count if adding a new language.
## Notes
- Treat this as a scaffold, not a hard-coded script.
- Update the command if the workflow evolves materially.

View File

@@ -10,14 +10,12 @@ Use this workflow when working on **add-or-update-skill-documentation** in `ever
## Goal
Adds a new skill or updates documentation for an existing skill, typically in the form of a SKILL.md file under skills/ or skills/*/SKILL.md, sometimes with translations in docs/xx/skills/*/SKILL.md.
Adds a new skill or updates existing skill documentation, typically in SKILL.md under skills/<skill-name>/ or docs/<lang>/skills/<skill-name>/SKILL.md.
## Common Files
- `skills/*/SKILL.md`
- `docs/zh-CN/skills/*/SKILL.md`
- `docs/tr/skills/*/SKILL.md`
- `docs/pt-BR/skills/*/SKILL.md`
- `docs/*/skills/*/SKILL.md`
## Suggested Sequence
@@ -28,9 +26,9 @@ Adds a new skill or updates documentation for an existing skill, typically in th
## Typical Commit Signals
- Create or update skills/<skill-name>/SKILL.md
- Optionally update docs/xx/skills/<skill-name>/SKILL.md for translations
- Commit with a message referencing the skill and a summary of changes
- Create or update SKILL.md in the appropriate skills/<skill-name>/ directory.
- Optionally update language-localized documentation under docs/<lang>/skills/<skill-name>/SKILL.md.
- Commit with a message referencing the skill and summary of the change.
## Notes

View File

@@ -10,13 +10,14 @@ Use this workflow when working on **add-or-update-skill** in `everything-claude-
## Goal
Adds a new skill or updates documentation for an existing skill.
Adds or updates a skill, including its documentation and configuration.
## Common Files
- `skills/*/SKILL.md`
- `docs/zh-CN/skills/*/SKILL.md`
- `docs/tr/skills/*/SKILL.md`
- `.claude/commands/add-or-update-skill.md`
- `.claude/skills/*/SKILL.md`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
## Suggested Sequence
@@ -27,8 +28,10 @@ Adds a new skill or updates documentation for an existing skill.
## Typical Commit Signals
- Create or update SKILL.md in the relevant skills directory.
- Optionally add architecture diagrams, implementation notes, or integration guidance.
- Create or update .claude/commands/add-or-update-skill.md
- Create or update .claude/skills/<skill-name>/SKILL.md
- Optionally update .agents/skills/<skill-name>/SKILL.md
- Optionally update .agents/skills/<skill-name>/agents/*.yaml
## Notes

View File

@@ -2,7 +2,7 @@
"version": "1.3",
"schemaVersion": "1.0",
"generatedBy": "ecc-tools",
"generatedAt": "2026-03-24T10:44:32.276Z",
"generatedAt": "2026-03-24T10:43:54.594Z",
"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-command-doc.md"
".claude/commands/add-or-update-skill.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-command-doc.md"
".claude/commands/add-or-update-skill.md"
]
},
"moduleFiles": {
@@ -211,7 +211,7 @@
"workflow-pack": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-command-doc.md"
".claude/commands/add-or-update-skill.md"
]
},
"files": [
@@ -297,8 +297,8 @@
},
{
"moduleId": "workflow-pack",
"path": ".claude/commands/add-or-update-command-doc.md",
"description": "Workflow command scaffold for add-or-update-command-doc."
"path": ".claude/commands/add-or-update-skill.md",
"description": "Workflow command scaffold for add-or-update-skill."
}
],
"workflows": [
@@ -311,8 +311,8 @@
"path": ".claude/commands/feature-development.md"
},
{
"command": "add-or-update-command-doc",
"path": ".claude/commands/add-or-update-command-doc.md"
"command": "add-or-update-skill",
"path": ".claude/commands/add-or-update-skill.md"
}
],
"adapters": {
@@ -322,7 +322,7 @@
"commandPaths": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-command-doc.md"
".claude/commands/add-or-update-skill.md"
]
},
"codex": {

View File

@@ -10,5 +10,5 @@
"javascript"
],
"suggestedBy": "ecc-tools-repo-analysis",
"createdAt": "2026-03-24T10:44:42.288Z"
"createdAt": "2026-03-24T10:44:09.205Z"
}

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: 6
- Workflows detected: 10

View File

@@ -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-command-doc: Adds or updates documentation for a command, typically in Markdown under a language or locale-specific docs directory.
- add-or-update-skill: Adds or updates a skill, including its documentation and configuration.
## Review Reminder

View File

@@ -228,95 +228,158 @@ fix: bump plugin.json and marketplace.json to v1.9.0
Add Turkish (tr) docs and update README (#744)
```
### Add Or Update Command Doc
### Add Or Update Skill
Adds or updates documentation for a command, typically in Markdown under a language or locale-specific docs directory.
**Frequency**: ~3 times per month
**Steps**:
1. Create or update a Markdown file for the command in the appropriate docs directory (e.g., docs/zh-CN/commands/ or docs/tr/commands/).
2. Commit the new or changed file with a message referencing the command.
3. Optionally update README or language count if adding a new language.
**Files typically involved**:
- `docs/zh-CN/commands/*.md`
- `docs/tr/commands/*.md`
- `docs/pt-BR/commands/*.md`
**Example commit sequence**:
```
Create or update a Markdown file for the command in the appropriate docs directory (e.g., docs/zh-CN/commands/ or docs/tr/commands/).
Commit the new or changed file with a message referencing the command.
Optionally update README or language count if adding a new language.
```
### Add Or Update Skill Doc
Adds or updates documentation for a skill, typically in SKILL.md under a language or locale-specific docs directory.
**Frequency**: ~3 times per month
**Steps**:
1. Create or update a SKILL.md file for the skill in the appropriate docs directory (e.g., docs/zh-CN/skills/, docs/tr/skills/, docs/pt-BR/skills/).
2. Commit the new or changed file with a message referencing the skill.
**Files typically involved**:
- `docs/zh-CN/skills/*/SKILL.md`
- `docs/tr/skills/*/SKILL.md`
- `docs/pt-BR/skills/*/SKILL.md`
**Example commit sequence**:
```
Create or update a SKILL.md file for the skill in the appropriate docs directory (e.g., docs/zh-CN/skills/, docs/tr/skills/, docs/pt-BR/skills/).
Commit the new or changed file with a message referencing the skill.
```
### Add Or Update Locale Docs
Adds or updates a full set of localized documentation for a new or existing language, including agents, commands, skills, and guides.
Adds or updates a skill, including its documentation and configuration.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update multiple Markdown files under a new or existing language directory (e.g., docs/tr/, docs/pt-BR/, docs/zh-CN/).
2. Update README.md to increment supported language count and add references.
3. Commit all new or changed files.
1. Create or update .claude/commands/add-or-update-skill.md
2. Create or update .claude/skills/<skill-name>/SKILL.md
3. Optionally update .agents/skills/<skill-name>/SKILL.md
4. Optionally update .agents/skills/<skill-name>/agents/*.yaml
**Files typically involved**:
- `docs/tr/**/*`
- `docs/pt-BR/**/*`
- `docs/zh-CN/**/*`
- `README.md`
- `.claude/commands/add-or-update-skill.md`
- `.claude/skills/*/SKILL.md`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
**Example commit sequence**:
```
Create or update multiple Markdown files under a new or existing language directory (e.g., docs/tr/, docs/pt-BR/, docs/zh-CN/).
Update README.md to increment supported language count and add references.
Commit all new or changed files.
Create or update .claude/commands/add-or-update-skill.md
Create or update .claude/skills/<skill-name>/SKILL.md
Optionally update .agents/skills/<skill-name>/SKILL.md
Optionally update .agents/skills/<skill-name>/agents/*.yaml
```
### Add Or Update Ecc Bundle Command
### Add Or Update Command Documentation
Adds or updates ECC bundle command documentation or configuration, typically in .claude/commands/ or related ECC config directories.
Adds or updates documentation for a CLI command or workflow.
**Frequency**: ~2 times per month
**Frequency**: ~3 times per month
**Steps**:
1. Create or update a Markdown file in .claude/commands/ for the new or updated command.
2. Optionally update related config files (e.g., .claude/ecc-tools.json, .claude/identity.json).
3. Commit the changes.
1. Create or update .claude/commands/<command-name>.md
**Files typically involved**:
- `.claude/commands/*.md`
- `.claude/ecc-tools.json`
**Example commit sequence**:
```
Create or update .claude/commands/<command-name>.md
```
### Add Or Update Database Migration Patterns
Adds or updates database migration patterns or documentation.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/commands/database-migration.md
2. Create or update skills/database-migrations/SKILL.md
**Files typically involved**:
- `.claude/commands/database-migration.md`
- `skills/database-migrations/SKILL.md`
**Example commit sequence**:
```
Create or update .claude/commands/database-migration.md
Create or update skills/database-migrations/SKILL.md
```
### Add Or Update Team Or Identity Config
Adds or updates team configuration or identity files.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/team/everything-claude-code-team-config.json
2. Create or update .claude/identity.json
**Files typically involved**:
- `.claude/team/everything-claude-code-team-config.json`
- `.claude/identity.json`
**Example commit sequence**:
```
Create or update a Markdown file in .claude/commands/ for the new or updated command.
Optionally update related config files (e.g., .claude/ecc-tools.json, .claude/identity.json).
Commit the changes.
Create or update .claude/team/everything-claude-code-team-config.json
Create or update .claude/identity.json
```
### Add Or Update Guardrails Or Controls
Adds or updates project guardrails, rules, or enterprise controls.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/rules/everything-claude-code-guardrails.md
2. Create or update .claude/enterprise/controls.md
**Files typically involved**:
- `.claude/rules/everything-claude-code-guardrails.md`
- `.claude/enterprise/controls.md`
**Example commit sequence**:
```
Create or update .claude/rules/everything-claude-code-guardrails.md
Create or update .claude/enterprise/controls.md
```
### Add Or Update Agent Config
Adds or updates agent configuration TOML files.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .codex/agents/*.toml
**Files typically involved**:
- `.codex/agents/*.toml`
**Example commit sequence**:
```
Create or update .codex/agents/*.toml
```
### Add Or Update Research Playbook
Adds or updates research playbooks or process documentation.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update .claude/research/everything-claude-code-research-playbook.md
**Files typically involved**:
- `.claude/research/everything-claude-code-research-playbook.md`
**Example commit sequence**:
```
Create or update .claude/research/everything-claude-code-research-playbook.md
```
### Add Or Update Ecc Tools Config
Adds or updates ECC tools configuration.
**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
```

View File

@@ -9,7 +9,7 @@
"commandFiles": [
".claude/commands/database-migration.md",
".claude/commands/feature-development.md",
".claude/commands/add-or-update-command-doc.md"
".claude/commands/add-or-update-skill.md"
],
"updatedAt": "2026-03-24T10:44:32.276Z"
"updatedAt": "2026-03-24T10:43:54.594Z"
}

1
ecc2/Cargo.lock generated
View File

@@ -332,7 +332,6 @@ dependencies = [
"crossterm",
"dirs",
"git2",
"libc",
"ratatui",
"rusqlite",
"serde",

View File

@@ -36,7 +36,6 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Error handling
anyhow = "1"
thiserror = "2"
libc = "0.2"
# Time
chrono = { version = "0.4", features = ["serde"] }

View File

@@ -13,7 +13,10 @@ pub enum MessageType {
/// Response to a query
Response { answer: String },
/// Notification of completion
Completed { summary: String, files_changed: Vec<String> },
Completed {
summary: String,
files_changed: Vec<String>,
},
/// Conflict detected (e.g., two agents editing the same file)
Conflict { file: String, description: String },
}

View File

@@ -44,11 +44,6 @@ enum Commands {
/// Session ID or alias
session_id: String,
},
/// Resume a failed or stopped session
Resume {
/// Session ID or alias
session_id: String,
},
/// Run as background daemon
Daemon,
}
@@ -92,10 +87,6 @@ async fn main() -> Result<()> {
session::manager::stop_session(&db, &session_id).await?;
println!("Session stopped: {session_id}");
}
Some(Commands::Resume { session_id }) => {
let resumed_id = session::manager::resume_session(&db, &session_id).await?;
println!("Session resumed: {resumed_id}");
}
Some(Commands::Daemon) => {
println!("Starting ECC daemon...");
session::daemon::run(db, cfg).await?;
@@ -104,19 +95,3 @@ async fn main() -> Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cli_parses_resume_command() {
let cli = Cli::try_parse_from(["ecc", "resume", "deadbeef"])
.expect("resume subcommand should parse");
match cli.command {
Some(Commands::Resume { session_id }) => assert_eq!(session_id, "deadbeef"),
_ => panic!("expected resume subcommand"),
}
}
}

View File

@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use crate::session::store::StateStore;
@@ -14,6 +14,26 @@ pub struct ToolCallEvent {
}
impl ToolCallEvent {
pub fn new(
session_id: impl Into<String>,
tool_name: impl Into<String>,
input_summary: impl Into<String>,
output_summary: impl Into<String>,
duration_ms: u64,
) -> Self {
let tool_name = tool_name.into();
let input_summary = input_summary.into();
Self {
session_id: session_id.into(),
risk_score: Self::compute_risk(&tool_name, &input_summary),
tool_name,
input_summary,
output_summary: output_summary.into(),
duration_ms,
}
}
/// Compute risk score based on tool type and input patterns.
pub fn compute_risk(tool_name: &str, input: &str) -> f64 {
let mut score: f64 = 0.0;
@@ -43,12 +63,119 @@ impl ToolCallEvent {
}
}
pub fn log_tool_call(db: &StateStore, event: &ToolCallEvent) -> Result<()> {
db.send_message(
&event.session_id,
"observability",
&serde_json::to_string(event)?,
"tool_call",
)?;
Ok(())
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolLogEntry {
pub id: i64,
pub session_id: String,
pub tool_name: String,
pub input_summary: String,
pub output_summary: String,
pub duration_ms: u64,
pub risk_score: f64,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolLogPage {
pub entries: Vec<ToolLogEntry>,
pub page: u64,
pub page_size: u64,
pub total: u64,
}
pub struct ToolLogger<'a> {
db: &'a StateStore,
}
impl<'a> ToolLogger<'a> {
pub fn new(db: &'a StateStore) -> Self {
Self { db }
}
pub fn log(&self, event: &ToolCallEvent) -> Result<ToolLogEntry> {
let timestamp = chrono::Utc::now().to_rfc3339();
self.db.insert_tool_log(
&event.session_id,
&event.tool_name,
&event.input_summary,
&event.output_summary,
event.duration_ms,
event.risk_score,
&timestamp,
)
}
pub fn query(&self, session_id: &str, page: u64, page_size: u64) -> Result<ToolLogPage> {
if page_size == 0 {
bail!("page_size must be greater than 0");
}
self.db.query_tool_logs(session_id, page.max(1), page_size)
}
}
pub fn log_tool_call(db: &StateStore, event: &ToolCallEvent) -> Result<ToolLogEntry> {
ToolLogger::new(db).log(event)
}
#[cfg(test)]
mod tests {
use super::{ToolCallEvent, ToolLogger};
use crate::session::store::StateStore;
use crate::session::{Session, SessionMetrics, SessionState};
use std::path::PathBuf;
fn test_db_path() -> PathBuf {
std::env::temp_dir().join(format!("ecc2-observability-{}.db", uuid::Uuid::new_v4()))
}
fn test_session(id: &str) -> Session {
let now = chrono::Utc::now();
Session {
id: id.to_string(),
task: "test task".to_string(),
agent_type: "claude".to_string(),
state: SessionState::Pending,
worktree: None,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
}
}
#[test]
fn compute_risk_caps_high_risk_bash_commands() {
let score = ToolCallEvent::compute_risk("Bash", "sudo rm -rf /tmp --force");
assert_eq!(score, 1.0);
}
#[test]
fn logger_persists_entries_and_paginates() -> anyhow::Result<()> {
let db_path = test_db_path();
let db = StateStore::open(&db_path)?;
db.insert_session(&test_session("sess-1"))?;
let logger = ToolLogger::new(&db);
logger.log(&ToolCallEvent::new("sess-1", "Read", "first", "ok", 5))?;
logger.log(&ToolCallEvent::new("sess-1", "Write", "second", "ok", 15))?;
logger.log(&ToolCallEvent::new("sess-1", "Bash", "third", "ok", 25))?;
let first_page = logger.query("sess-1", 1, 2)?;
assert_eq!(first_page.total, 3);
assert_eq!(first_page.entries.len(), 2);
assert_eq!(first_page.entries[0].tool_name, "Bash");
assert_eq!(first_page.entries[1].tool_name, "Write");
let second_page = logger.query("sess-1", 2, 2)?;
assert_eq!(second_page.total, 3);
assert_eq!(second_page.entries.len(), 1);
assert_eq!(second_page.entries[0].tool_name, "Read");
std::fs::remove_file(&db_path).ok();
Ok(())
}
}

View File

@@ -10,7 +10,6 @@ use crate::config::Config;
/// and cleans up stale resources.
pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
tracing::info!("ECC daemon started");
resume_crashed_sessions(&db)?;
let heartbeat_interval = Duration::from_secs(cfg.heartbeat_interval_secs);
let timeout = Duration::from_secs(cfg.session_timeout_secs);
@@ -24,43 +23,6 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
}
}
pub fn resume_crashed_sessions(db: &StateStore) -> Result<()> {
let failed_sessions = resume_crashed_sessions_with(db, pid_is_alive)?;
if failed_sessions > 0 {
tracing::warn!("Marked {failed_sessions} crashed sessions as failed during daemon startup");
}
Ok(())
}
fn resume_crashed_sessions_with<F>(db: &StateStore, is_pid_alive: F) -> Result<usize>
where
F: Fn(u32) -> bool,
{
let sessions = db.list_sessions()?;
let mut failed_sessions = 0;
for session in sessions {
if session.state != SessionState::Running {
continue;
}
let is_alive = session.pid.is_some_and(&is_pid_alive);
if is_alive {
continue;
}
tracing::warn!(
"Session {} was left running with stale pid {:?}; marking it failed",
session.id,
session.pid
);
db.update_state_and_pid(&session.id, &SessionState::Failed, None)?;
failed_sessions += 1;
}
Ok(failed_sessions)
}
fn check_sessions(db: &StateStore, timeout: Duration) -> Result<()> {
let sessions = db.list_sessions()?;
@@ -76,102 +38,9 @@ fn check_sessions(db: &StateStore, timeout: Duration) -> Result<()> {
if elapsed > timeout {
tracing::warn!("Session {} timed out after {:?}", session.id, elapsed);
db.update_state_and_pid(&session.id, &SessionState::Failed, None)?;
db.update_state(&session.id, &SessionState::Failed)?;
}
}
Ok(())
}
#[cfg(unix)]
fn pid_is_alive(pid: u32) -> bool {
if pid == 0 {
return false;
}
// SAFETY: kill(pid, 0) probes process existence without delivering a signal.
let result = unsafe { libc::kill(pid as libc::pid_t, 0) };
if result == 0 {
return true;
}
matches!(
std::io::Error::last_os_error().raw_os_error(),
Some(code) if code == libc::EPERM
)
}
#[cfg(not(unix))]
fn pid_is_alive(_pid: u32) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::{Session, SessionMetrics, SessionState};
use std::path::PathBuf;
fn temp_db_path() -> PathBuf {
std::env::temp_dir().join(format!("ecc2-daemon-test-{}.db", uuid::Uuid::new_v4()))
}
fn sample_session(id: &str, state: SessionState, pid: Option<u32>) -> Session {
let now = chrono::Utc::now();
Session {
id: id.to_string(),
task: "Recover crashed worker".to_string(),
agent_type: "claude".to_string(),
state,
pid,
worktree: None,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
}
}
#[test]
fn resume_crashed_sessions_marks_dead_running_sessions_failed() -> Result<()> {
let path = temp_db_path();
let store = StateStore::open(&path)?;
store.insert_session(&sample_session(
"deadbeef",
SessionState::Running,
Some(4242),
))?;
resume_crashed_sessions_with(&store, |_| false)?;
let session = store
.get_session("deadbeef")?
.expect("session should still exist");
assert_eq!(session.state, SessionState::Failed);
assert_eq!(session.pid, None);
let _ = std::fs::remove_file(path);
Ok(())
}
#[test]
fn resume_crashed_sessions_keeps_live_running_sessions_running() -> Result<()> {
let path = temp_db_path();
let store = StateStore::open(&path)?;
store.insert_session(&sample_session(
"alive123",
SessionState::Running,
Some(7777),
))?;
resume_crashed_sessions_with(&store, |_| true)?;
let session = store
.get_session("alive123")?
.expect("session should still exist");
assert_eq!(session.state, SessionState::Running);
assert_eq!(session.pid, Some(7777));
let _ = std::fs::remove_file(path);
Ok(())
}
}

View File

@@ -4,6 +4,7 @@ use std::fmt;
use super::store::StateStore;
use super::{Session, SessionMetrics, SessionState};
use crate::config::Config;
use crate::observability::{log_tool_call, ToolCallEvent, ToolLogEntry, ToolLogPage, ToolLogger};
use crate::worktree;
pub async fn create_session(
@@ -27,7 +28,6 @@ pub async fn create_session(
task: task.to_string(),
agent_type: agent_type.to_string(),
state: SessionState::Pending,
pid: None,
worktree: wt,
created_at: now,
updated_at: now,
@@ -50,77 +50,46 @@ pub fn get_status(db: &StateStore, id: &str) -> Result<SessionStatus> {
}
pub async fn stop_session(db: &StateStore, id: &str) -> Result<()> {
let session = db
.get_session(id)?
.ok_or_else(|| anyhow::anyhow!("Session not found: {id}"))?;
db.update_state_and_pid(&session.id, &SessionState::Stopped, None)?;
db.update_state(id, &SessionState::Stopped)?;
Ok(())
}
pub async fn resume_session(db: &StateStore, id: &str) -> Result<String> {
pub fn record_tool_call(
db: &StateStore,
session_id: &str,
tool_name: &str,
input_summary: &str,
output_summary: &str,
duration_ms: u64,
) -> Result<ToolLogEntry> {
let session = db
.get_session(id)?
.ok_or_else(|| anyhow::anyhow!("Session not found: {id}"))?;
.get_session(session_id)?
.ok_or_else(|| anyhow::anyhow!("Session not found: {session_id}"))?;
if session.state == SessionState::Completed {
anyhow::bail!("Completed sessions cannot be resumed: {}", session.id);
}
let event = ToolCallEvent::new(
session.id.clone(),
tool_name,
input_summary,
output_summary,
duration_ms,
);
let entry = log_tool_call(db, &event)?;
db.increment_tool_calls(&session.id)?;
if session.state == SessionState::Running {
anyhow::bail!("Session is already running: {}", session.id);
}
db.update_state_and_pid(&session.id, &SessionState::Pending, None)?;
Ok(session.id)
Ok(entry)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::store::StateStore;
use std::path::PathBuf;
pub fn query_tool_calls(
db: &StateStore,
session_id: &str,
page: u64,
page_size: u64,
) -> Result<ToolLogPage> {
let session = db
.get_session(session_id)?
.ok_or_else(|| anyhow::anyhow!("Session not found: {session_id}"))?;
fn temp_db_path() -> PathBuf {
std::env::temp_dir().join(format!("ecc2-manager-test-{}.db", uuid::Uuid::new_v4()))
}
fn sample_session(id: &str, state: SessionState, pid: Option<u32>) -> Session {
let now = chrono::Utc::now();
Session {
id: id.to_string(),
task: "Resume previous task".to_string(),
agent_type: "claude".to_string(),
state,
pid,
worktree: None,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
}
}
#[tokio::test]
async fn resume_session_requeues_failed_session() -> Result<()> {
let path = temp_db_path();
let store = StateStore::open(&path)?;
store.insert_session(&sample_session(
"deadbeef",
SessionState::Failed,
Some(31337),
))?;
let resumed_id = resume_session(&store, "deadbeef").await?;
let resumed = store
.get_session(&resumed_id)?
.expect("resumed session should exist");
assert_eq!(resumed.state, SessionState::Pending);
assert_eq!(resumed.pid, None);
let _ = std::fs::remove_file(path);
Ok(())
}
ToolLogger::new(db).query(&session.id, page, page_size)
}
pub struct SessionStatus(Session);
@@ -132,9 +101,6 @@ impl fmt::Display for SessionStatus {
writeln!(f, "Task: {}", s.task)?;
writeln!(f, "Agent: {}", s.agent_type)?;
writeln!(f, "State: {}", s.state)?;
if let Some(pid) = s.pid {
writeln!(f, "PID: {pid}")?;
}
if let Some(ref wt) = s.worktree {
writeln!(f, "Branch: {}", wt.branch)?;
writeln!(f, "Worktree: {}", wt.path.display())?;
@@ -147,3 +113,41 @@ impl fmt::Display for SessionStatus {
write!(f, "Updated: {}", s.updated_at)
}
}
#[cfg(test)]
mod tests {
use super::{create_session, query_tool_calls, record_tool_call};
use crate::config::Config;
use crate::session::store::StateStore;
#[tokio::test]
async fn record_tool_call_updates_session_metrics() -> anyhow::Result<()> {
let db_path =
std::env::temp_dir().join(format!("ecc2-session-manager-{}.db", uuid::Uuid::new_v4()));
let db = StateStore::open(&db_path)?;
let cfg = Config {
db_path: db_path.clone(),
..Config::default()
};
let session_id =
create_session(&db, &cfg, "implement tool logging", "claude", false).await?;
let entry = record_tool_call(&db, &session_id, "Bash", "git status", "clean worktree", 18)?;
assert_eq!(entry.session_id, session_id);
assert_eq!(entry.tool_name, "Bash");
let session = db.get_session(&session_id)?.expect("session should exist");
assert_eq!(session.metrics.tool_calls, 1);
let page = query_tool_calls(&db, &session_id[..4], 1, 10)?;
assert_eq!(page.total, 1);
assert_eq!(page.entries[0].output_summary, "clean worktree");
std::fs::remove_file(&db_path).ok();
Ok(())
}
}

View File

@@ -13,7 +13,6 @@ pub struct Session {
pub task: String,
pub agent_type: String,
pub state: SessionState,
pub pid: Option<u32>,
pub worktree: Option<WorktreeInfo>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,

View File

@@ -3,6 +3,7 @@ use rusqlite::Connection;
use std::path::Path;
use super::{Session, SessionMetrics, SessionState};
use crate::observability::{ToolLogEntry, ToolLogPage};
pub struct StateStore {
conn: Connection,
@@ -24,7 +25,6 @@ impl StateStore {
task TEXT NOT NULL,
agent_type TEXT NOT NULL,
state TEXT NOT NULL DEFAULT 'pending',
pid INTEGER,
worktree_path TEXT,
worktree_branch TEXT,
worktree_base TEXT,
@@ -63,36 +63,18 @@ impl StateStore {
CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_session, read);
",
)?;
self.ensure_sessions_pid_column()?;
Ok(())
}
fn ensure_sessions_pid_column(&self) -> Result<()> {
let mut stmt = self.conn.prepare("PRAGMA table_info(sessions)")?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let column_name: String = row.get(1)?;
if column_name == "pid" {
return Ok(());
}
}
self.conn
.execute("ALTER TABLE sessions ADD COLUMN pid INTEGER", [])?;
Ok(())
}
pub fn insert_session(&self, session: &Session) -> Result<()> {
self.conn.execute(
"INSERT INTO sessions (id, task, agent_type, state, pid, worktree_path, worktree_branch, worktree_base, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
"INSERT INTO sessions (id, task, agent_type, state, worktree_path, worktree_branch, worktree_base, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
rusqlite::params![
session.id,
session.task,
session.agent_type,
session.state.to_string(),
session.pid.map(i64::from),
session.worktree.as_ref().map(|w| w.path.to_string_lossy().to_string()),
session.worktree.as_ref().map(|w| w.branch.clone()),
session.worktree.as_ref().map(|w| w.base_branch.clone()),
@@ -103,24 +85,6 @@ impl StateStore {
Ok(())
}
pub fn update_state_and_pid(
&self,
session_id: &str,
state: &SessionState,
pid: Option<u32>,
) -> Result<()> {
self.conn.execute(
"UPDATE sessions SET state = ?1, pid = ?2, updated_at = ?3 WHERE id = ?4",
rusqlite::params![
state.to_string(),
pid.map(i64::from),
chrono::Utc::now().to_rfc3339(),
session_id,
],
)?;
Ok(())
}
pub fn update_state(&self, session_id: &str, state: &SessionState) -> Result<()> {
self.conn.execute(
"UPDATE sessions SET state = ?1, updated_at = ?2 WHERE id = ?3",
@@ -149,9 +113,17 @@ impl StateStore {
Ok(())
}
pub fn increment_tool_calls(&self, session_id: &str) -> Result<()> {
self.conn.execute(
"UPDATE sessions SET tool_calls = tool_calls + 1, updated_at = ?1 WHERE id = ?2",
rusqlite::params![chrono::Utc::now().to_rfc3339(), session_id],
)?;
Ok(())
}
pub fn list_sessions(&self) -> Result<Vec<Session>> {
let mut stmt = self.conn.prepare(
"SELECT id, task, agent_type, state, pid, worktree_path, worktree_branch, worktree_base,
"SELECT id, task, agent_type, state, worktree_path, worktree_branch, worktree_base,
tokens_used, tool_calls, files_changed, duration_secs, cost_usd,
created_at, updated_at
FROM sessions ORDER BY updated_at DESC",
@@ -169,26 +141,21 @@ impl StateStore {
_ => SessionState::Pending,
};
let pid = row
.get::<_, Option<i64>>(4)?
.and_then(|value| u32::try_from(value).ok());
let worktree_path: Option<String> = row.get(5)?;
let worktree_path: Option<String> = row.get(4)?;
let worktree = worktree_path.map(|p| super::WorktreeInfo {
path: std::path::PathBuf::from(p),
branch: row.get::<_, String>(6).unwrap_or_default(),
base_branch: row.get::<_, String>(7).unwrap_or_default(),
branch: row.get::<_, String>(5).unwrap_or_default(),
base_branch: row.get::<_, String>(6).unwrap_or_default(),
});
let created_str: String = row.get(13)?;
let updated_str: String = row.get(14)?;
let created_str: String = row.get(12)?;
let updated_str: String = row.get(13)?;
Ok(Session {
id: row.get(0)?,
task: row.get(1)?,
agent_type: row.get(2)?,
state,
pid,
worktree,
created_at: chrono::DateTime::parse_from_rfc3339(&created_str)
.unwrap_or_default()
@@ -197,11 +164,11 @@ impl StateStore {
.unwrap_or_default()
.with_timezone(&chrono::Utc),
metrics: SessionMetrics {
tokens_used: row.get(8)?,
tool_calls: row.get(9)?,
files_changed: row.get(10)?,
duration_secs: row.get(11)?,
cost_usd: row.get(12)?,
tokens_used: row.get(7)?,
tool_calls: row.get(8)?,
files_changed: row.get(9)?,
duration_secs: row.get(10)?,
cost_usd: row.get(11)?,
},
})
})?
@@ -225,97 +192,86 @@ impl StateStore {
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::{Session, SessionMetrics, SessionState};
use std::path::PathBuf;
fn temp_db_path() -> PathBuf {
std::env::temp_dir().join(format!("ecc2-store-test-{}.db", uuid::Uuid::new_v4()))
}
fn sample_session(id: &str, state: SessionState, pid: Option<u32>) -> Session {
let now = chrono::Utc::now();
Session {
id: id.to_string(),
task: "Investigate crash".to_string(),
agent_type: "claude".to_string(),
state,
pid,
worktree: None,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
}
}
#[test]
fn open_migrates_existing_sessions_table_with_pid_column() -> Result<()> {
let path = temp_db_path();
let conn = Connection::open(&path)?;
conn.execute_batch(
"
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
task TEXT NOT NULL,
agent_type TEXT NOT NULL,
state TEXT NOT NULL DEFAULT 'pending',
worktree_path TEXT,
worktree_branch TEXT,
worktree_base TEXT,
tokens_used INTEGER DEFAULT 0,
tool_calls INTEGER DEFAULT 0,
files_changed INTEGER DEFAULT 0,
duration_secs INTEGER DEFAULT 0,
cost_usd REAL DEFAULT 0.0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
",
)?;
conn.execute(
"INSERT INTO sessions (id, task, agent_type, state, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
pub fn insert_tool_log(
&self,
session_id: &str,
tool_name: &str,
input_summary: &str,
output_summary: &str,
duration_ms: u64,
risk_score: f64,
timestamp: &str,
) -> Result<ToolLogEntry> {
self.conn.execute(
"INSERT INTO tool_log (session_id, tool_name, input_summary, output_summary, duration_ms, risk_score, timestamp)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
rusqlite::params![
"legacy",
"Recover state",
"claude",
"running",
chrono::Utc::now().to_rfc3339(),
chrono::Utc::now().to_rfc3339(),
session_id,
tool_name,
input_summary,
output_summary,
duration_ms,
risk_score,
timestamp,
],
)?;
drop(conn);
let store = StateStore::open(&path)?;
let session = store
.get_session("legacy")?
.expect("legacy session should load");
assert_eq!(session.pid, None);
let _ = std::fs::remove_file(path);
Ok(())
Ok(ToolLogEntry {
id: self.conn.last_insert_rowid(),
session_id: session_id.to_string(),
tool_name: tool_name.to_string(),
input_summary: input_summary.to_string(),
output_summary: output_summary.to_string(),
duration_ms,
risk_score,
timestamp: timestamp.to_string(),
})
}
#[test]
fn insert_session_persists_pid() -> Result<()> {
let path = temp_db_path();
let store = StateStore::open(&path)?;
let session = sample_session("abc12345", SessionState::Running, Some(4242));
pub fn query_tool_logs(
&self,
session_id: &str,
page: u64,
page_size: u64,
) -> Result<ToolLogPage> {
let page = page.max(1);
let offset = (page - 1) * page_size;
store.insert_session(&session)?;
let total: u64 = self.conn.query_row(
"SELECT COUNT(*) FROM tool_log WHERE session_id = ?1",
rusqlite::params![session_id],
|row| row.get(0),
)?;
let loaded = store
.get_session("abc12345")?
.expect("session should be persisted");
assert_eq!(loaded.pid, Some(4242));
assert_eq!(loaded.state, SessionState::Running);
let mut stmt = self.conn.prepare(
"SELECT id, session_id, tool_name, input_summary, output_summary, duration_ms, risk_score, timestamp
FROM tool_log
WHERE session_id = ?1
ORDER BY timestamp DESC, id DESC
LIMIT ?2 OFFSET ?3",
)?;
let _ = std::fs::remove_file(path);
Ok(())
let entries = stmt
.query_map(rusqlite::params![session_id, page_size, offset], |row| {
Ok(ToolLogEntry {
id: row.get(0)?,
session_id: row.get(1)?,
tool_name: row.get(2)?,
input_summary: row.get::<_, Option<String>>(3)?.unwrap_or_default(),
output_summary: row.get::<_, Option<String>>(4)?.unwrap_or_default(),
duration_ms: row.get::<_, Option<u64>>(5)?.unwrap_or_default(),
risk_score: row.get::<_, Option<f64>>(6)?.unwrap_or_default(),
timestamp: row.get(7)?,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(ToolLogPage {
entries,
page,
page_size,
total,
})
}
}

View File

@@ -271,9 +271,7 @@ impl Dashboard {
pub fn stop_selected(&mut self) {
if let Some(session) = self.sessions.get(self.selected_session) {
let _ = self
.db
.update_state_and_pid(&session.id, &SessionState::Stopped, None);
let _ = self.db.update_state(&session.id, &SessionState::Stopped);
self.refresh();
}
}

View File

@@ -28,7 +28,11 @@ pub fn create_for_session(session_id: &str, cfg: &Config) -> Result<WorktreeInfo
anyhow::bail!("git worktree add failed: {stderr}");
}
tracing::info!("Created worktree at {} on branch {}", path.display(), branch);
tracing::info!(
"Created worktree at {} on branch {}",
path.display(),
branch
);
Ok(WorktreeInfo {
path,