Compare commits

..

46 Commits

Author SHA1 Message Date
ecc-tools[bot]
06c8402b19 feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill.md) 2026-03-24 10:44:35 +00:00
ecc-tools[bot]
e9264d84f5 feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md) 2026-03-24 10:44:34 +00:00
ecc-tools[bot]
9c74492f86 feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md) 2026-03-24 10:44:34 +00:00
ecc-tools[bot]
f38fff2155 feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md) 2026-03-24 10:44:33 +00:00
ecc-tools[bot]
080194436d feat: add everything-claude-code ECC bundle (.claude/team/everything-claude-code-team-config.json) 2026-03-24 10:44:32 +00:00
ecc-tools[bot]
6ef930ed3e feat: add everything-claude-code ECC bundle (.claude/research/everything-claude-code-research-playbook.md) 2026-03-24 10:44:31 +00:00
ecc-tools[bot]
a5e40ef0f7 feat: add everything-claude-code ECC bundle (.claude/rules/everything-claude-code-guardrails.md) 2026-03-24 10:44:30 +00:00
ecc-tools[bot]
cffe9cf953 feat: add everything-claude-code ECC bundle (.codex/agents/docs-researcher.toml) 2026-03-24 10:44:29 +00:00
ecc-tools[bot]
09e85c4350 feat: add everything-claude-code ECC bundle (.codex/agents/reviewer.toml) 2026-03-24 10:44:28 +00:00
ecc-tools[bot]
ae19bad71f feat: add everything-claude-code ECC bundle (.codex/agents/explorer.toml) 2026-03-24 10:44:27 +00:00
ecc-tools[bot]
9a24665935 feat: add everything-claude-code ECC bundle (.claude/identity.json) 2026-03-24 10:44:26 +00:00
ecc-tools[bot]
c1327505ad feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/agents/openai.yaml) 2026-03-24 10:44:25 +00:00
ecc-tools[bot]
1571d23a40 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/SKILL.md) 2026-03-24 10:44:25 +00:00
ecc-tools[bot]
c8c6ebabb3 feat: add everything-claude-code ECC bundle (.claude/skills/everything-claude-code/SKILL.md) 2026-03-24 10:44:24 +00:00
ecc-tools[bot]
db4ffbc398 feat: add everything-claude-code ECC bundle (.claude/ecc-tools.json) 2026-03-24 10:44:23 +00:00
ecc-tools[bot]
f2de3453a7 feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill.md) 2026-03-24 10:43:47 +00:00
ecc-tools[bot]
aa98844de4 feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md) 2026-03-24 10:43:46 +00:00
ecc-tools[bot]
bad5ebf7b0 feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md) 2026-03-24 10:43:45 +00:00
ecc-tools[bot]
62e4d16008 feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md) 2026-03-24 10:43:44 +00:00
ecc-tools[bot]
4a4c01b7ec feat: add everything-claude-code ECC bundle (.claude/team/everything-claude-code-team-config.json) 2026-03-24 10:43:43 +00:00
ecc-tools[bot]
e3d1500efb feat: add everything-claude-code ECC bundle (.claude/research/everything-claude-code-research-playbook.md) 2026-03-24 10:43:43 +00:00
ecc-tools[bot]
7eaae99cc7 feat: add everything-claude-code ECC bundle (.claude/rules/everything-claude-code-guardrails.md) 2026-03-24 10:43:42 +00:00
ecc-tools[bot]
b7012da28b feat: add everything-claude-code ECC bundle (.codex/agents/docs-researcher.toml) 2026-03-24 10:43:41 +00:00
ecc-tools[bot]
4be983ac03 feat: add everything-claude-code ECC bundle (.codex/agents/reviewer.toml) 2026-03-24 10:43:40 +00:00
ecc-tools[bot]
350c43e10a feat: add everything-claude-code ECC bundle (.codex/agents/explorer.toml) 2026-03-24 10:43:39 +00:00
ecc-tools[bot]
10177640ff feat: add everything-claude-code ECC bundle (.claude/identity.json) 2026-03-24 10:43:38 +00:00
ecc-tools[bot]
72f8ec41f3 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/agents/openai.yaml) 2026-03-24 10:43:37 +00:00
ecc-tools[bot]
49788ec1f3 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/SKILL.md) 2026-03-24 10:43:36 +00:00
ecc-tools[bot]
203ce6a7d8 feat: add everything-claude-code ECC bundle (.claude/skills/everything-claude-code/SKILL.md) 2026-03-24 10:43:35 +00:00
ecc-tools[bot]
280b7ef537 feat: add everything-claude-code ECC bundle (.claude/ecc-tools.json) 2026-03-24 10:43:34 +00:00
ecc-tools[bot]
22bb2ff0e7 feat: add everything-claude-code ECC bundle (.claude/commands/add-or-update-skill-documentation.md) 2026-03-24 10:42:54 +00:00
ecc-tools[bot]
0f3c64d7f9 feat: add everything-claude-code ECC bundle (.claude/commands/feature-development.md) 2026-03-24 10:42:53 +00:00
ecc-tools[bot]
b5017c2a70 feat: add everything-claude-code ECC bundle (.claude/commands/database-migration.md) 2026-03-24 10:42:52 +00:00
ecc-tools[bot]
e416d9459f feat: add everything-claude-code ECC bundle (.claude/enterprise/controls.md) 2026-03-24 10:42:51 +00:00
ecc-tools[bot]
7e16420feb feat: add everything-claude-code ECC bundle (.claude/team/everything-claude-code-team-config.json) 2026-03-24 10:42:51 +00:00
ecc-tools[bot]
81ad3abb82 feat: add everything-claude-code ECC bundle (.claude/research/everything-claude-code-research-playbook.md) 2026-03-24 10:42:50 +00:00
ecc-tools[bot]
830e591fba feat: add everything-claude-code ECC bundle (.claude/rules/everything-claude-code-guardrails.md) 2026-03-24 10:42:49 +00:00
ecc-tools[bot]
8a76b4a93f feat: add everything-claude-code ECC bundle (.codex/agents/docs-researcher.toml) 2026-03-24 10:42:48 +00:00
ecc-tools[bot]
bed40d72a9 feat: add everything-claude-code ECC bundle (.codex/agents/reviewer.toml) 2026-03-24 10:42:47 +00:00
ecc-tools[bot]
c19d101e09 feat: add everything-claude-code ECC bundle (.codex/agents/explorer.toml) 2026-03-24 10:42:46 +00:00
ecc-tools[bot]
e006aacac8 feat: add everything-claude-code ECC bundle (.claude/identity.json) 2026-03-24 10:42:45 +00:00
ecc-tools[bot]
d7802cfba1 feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/agents/openai.yaml) 2026-03-24 10:42:44 +00:00
ecc-tools[bot]
60070e54fb feat: add everything-claude-code ECC bundle (.agents/skills/everything-claude-code/SKILL.md) 2026-03-24 10:42:43 +00:00
ecc-tools[bot]
3782530e32 feat: add everything-claude-code ECC bundle (.claude/skills/everything-claude-code/SKILL.md) 2026-03-24 10:42:43 +00:00
ecc-tools[bot]
23c3284848 feat: add everything-claude-code ECC bundle (.claude/ecc-tools.json) 2026-03-24 10:42:42 +00:00
Affaan Mustafa
72497ccae7 feat(ecc2): add tool risk scoring and actions 2026-03-24 03:39:53 -07:00
22 changed files with 525 additions and 568 deletions

View File

@@ -228,95 +228,126 @@ 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.
Adds or updates a skill in the ECC system, including documentation and provenance.
**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.
1. Create or update SKILL.md in the appropriate skills directory (e.g., skills/{skill-name}/SKILL.md or .claude/skills/{skill-name}/SKILL.md)
2. Optionally add or update agent YAML files (e.g., .agents/skills/{skill-name}/agents/*.yaml)
3. Optionally update provenance or placement policy files (e.g., schemas/provenance.schema.json, docs/SKILL-PLACEMENT-POLICY.md)
**Files typically involved**:
- `skills/*/SKILL.md`
- `.claude/skills/*/SKILL.md`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
- `schemas/provenance.schema.json`
- `docs/SKILL-PLACEMENT-POLICY.md`
**Example commit sequence**:
```
Create or update SKILL.md in the appropriate skills directory (e.g., skills/{skill-name}/SKILL.md or .claude/skills/{skill-name}/SKILL.md)
Optionally add or update agent YAML files (e.g., .agents/skills/{skill-name}/agents/*.yaml)
Optionally update provenance or placement policy files (e.g., schemas/provenance.schema.json, docs/SKILL-PLACEMENT-POLICY.md)
```
### Add Or Update Command Documentation
Adds or updates documentation for a command, often as part of a new feature or workflow.
**Frequency**: ~4 times per month
**Steps**:
1. Create or update .claude/commands/{command-name}.md
2. Optionally update localized docs (e.g., docs/zh-CN/commands/{command-name}.md, docs/tr/commands/{command-name}.md, docs/pt-BR/commands/{command-name}.md)
**Files typically involved**:
- `.claude/commands/*.md`
- `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.
Create or update .claude/commands/{command-name}.md
Optionally update localized docs (e.g., docs/zh-CN/commands/{command-name}.md, docs/tr/commands/{command-name}.md, docs/pt-BR/commands/{command-name}.md)
```
### Add Or Update Skill Doc
### Add Or Update Localized Documentation
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 documentation in a new or existing language (localization).
**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. Add or update files in docs/{locale}/ (where locale is zh-CN, tr, pt-BR, etc.)
2. Update README.md to reflect new language support
**Files typically involved**:
- `docs/zh-CN/**/*`
- `docs/tr/**/*`
- `docs/pt-BR/**/*`
- `docs/zh-CN/**/*`
- `README.md`
**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.
Add or update files in docs/{locale}/ (where locale is zh-CN, tr, pt-BR, etc.)
Update README.md to reflect new language support
```
### Add Or Update Ecc Bundle Command
### Update Or Add Hooks
Adds or updates ECC bundle command documentation or configuration, typically in .claude/commands/ or related ECC config directories.
Adds or updates hooks for validation, config protection, or workflow automation.
**Frequency**: ~2 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. Edit hooks/hooks.json to add or update hook definitions
2. Create or update scripts/hooks/*.js or .ts for hook logic
3. Optionally update plugin files (e.g., .opencode/plugins/ecc-hooks.ts)
**Files typically involved**:
- `.claude/commands/*.md`
- `.claude/ecc-tools.json`
- `.claude/identity.json`
- `hooks/hooks.json`
- `scripts/hooks/*.js`
- `.opencode/plugins/*.ts`
**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.
Edit hooks/hooks.json to add or update hook definitions
Create or update scripts/hooks/*.js or .ts for hook logic
Optionally update plugin files (e.g., .opencode/plugins/ecc-hooks.ts)
```
### Add Or Update Ecc Bundle
Adds or updates a set of ECC configuration, command, or skill files as a bundle.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update files in .claude/ (commands, skills, rules, team, identity, etc.)
2. Optionally update .codex/agents/*.toml and .agents/skills/*
3. Repeat for each bundle component as needed
**Files typically involved**:
- `.claude/commands/*.md`
- `.claude/skills/*/SKILL.md`
- `.claude/rules/*.md`
- `.claude/team/*.json`
- `.claude/identity.json`
- `.claude/ecc-tools.json`
- `.codex/agents/*.toml`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
**Example commit sequence**:
```
Create or update files in .claude/ (commands, skills, rules, team, identity, etc.)
Optionally update .codex/agents/*.toml and .agents/skills/*
Repeat for each bundle component as needed
```

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,13 @@ 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 documentation for an existing skill, typically in the form of a SKILL.md file under skills/ or skills/*/SKILL.md.
## Common Files
- `skills/*/SKILL.md`
- `docs/zh-CN/skills/*/SKILL.md`
- `docs/tr/skills/*/SKILL.md`
- `docs/pt-BR/skills/*/SKILL.md`
- `AGENTS.md`
- `README.md`
## Suggested Sequence
@@ -29,8 +28,8 @@ 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
- Optionally update AGENTS.md or README.md to reflect new skill
- Optionally add architecture diagrams or implementation notes
## Notes

View File

@@ -10,13 +10,16 @@ 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 in the ECC system, including documentation and provenance.
## Common Files
- `skills/*/SKILL.md`
- `docs/zh-CN/skills/*/SKILL.md`
- `docs/tr/skills/*/SKILL.md`
- `.claude/skills/*/SKILL.md`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
- `schemas/provenance.schema.json`
- `docs/SKILL-PLACEMENT-POLICY.md`
## Suggested Sequence
@@ -27,8 +30,9 @@ 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 SKILL.md in the appropriate skills directory (e.g., skills/{skill-name}/SKILL.md or .claude/skills/{skill-name}/SKILL.md)
- Optionally add or update agent YAML files (e.g., .agents/skills/{skill-name}/agents/*.yaml)
- Optionally update provenance or placement policy files (e.g., schemas/provenance.schema.json, docs/SKILL-PLACEMENT-POLICY.md)
## 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:44:06.345Z",
"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:20.963Z"
}

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: 7

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 in the ECC system, including documentation and provenance.
## Review Reminder

View File

@@ -228,95 +228,126 @@ 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.
Adds or updates a skill in the ECC system, including documentation and provenance.
**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.
1. Create or update SKILL.md in the appropriate skills directory (e.g., skills/{skill-name}/SKILL.md or .claude/skills/{skill-name}/SKILL.md)
2. Optionally add or update agent YAML files (e.g., .agents/skills/{skill-name}/agents/*.yaml)
3. Optionally update provenance or placement policy files (e.g., schemas/provenance.schema.json, docs/SKILL-PLACEMENT-POLICY.md)
**Files typically involved**:
- `skills/*/SKILL.md`
- `.claude/skills/*/SKILL.md`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
- `schemas/provenance.schema.json`
- `docs/SKILL-PLACEMENT-POLICY.md`
**Example commit sequence**:
```
Create or update SKILL.md in the appropriate skills directory (e.g., skills/{skill-name}/SKILL.md or .claude/skills/{skill-name}/SKILL.md)
Optionally add or update agent YAML files (e.g., .agents/skills/{skill-name}/agents/*.yaml)
Optionally update provenance or placement policy files (e.g., schemas/provenance.schema.json, docs/SKILL-PLACEMENT-POLICY.md)
```
### Add Or Update Command Documentation
Adds or updates documentation for a command, often as part of a new feature or workflow.
**Frequency**: ~4 times per month
**Steps**:
1. Create or update .claude/commands/{command-name}.md
2. Optionally update localized docs (e.g., docs/zh-CN/commands/{command-name}.md, docs/tr/commands/{command-name}.md, docs/pt-BR/commands/{command-name}.md)
**Files typically involved**:
- `.claude/commands/*.md`
- `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.
Create or update .claude/commands/{command-name}.md
Optionally update localized docs (e.g., docs/zh-CN/commands/{command-name}.md, docs/tr/commands/{command-name}.md, docs/pt-BR/commands/{command-name}.md)
```
### Add Or Update Skill Doc
### Add Or Update Localized Documentation
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 documentation in a new or existing language (localization).
**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. Add or update files in docs/{locale}/ (where locale is zh-CN, tr, pt-BR, etc.)
2. Update README.md to reflect new language support
**Files typically involved**:
- `docs/zh-CN/**/*`
- `docs/tr/**/*`
- `docs/pt-BR/**/*`
- `docs/zh-CN/**/*`
- `README.md`
**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.
Add or update files in docs/{locale}/ (where locale is zh-CN, tr, pt-BR, etc.)
Update README.md to reflect new language support
```
### Add Or Update Ecc Bundle Command
### Update Or Add Hooks
Adds or updates ECC bundle command documentation or configuration, typically in .claude/commands/ or related ECC config directories.
Adds or updates hooks for validation, config protection, or workflow automation.
**Frequency**: ~2 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. Edit hooks/hooks.json to add or update hook definitions
2. Create or update scripts/hooks/*.js or .ts for hook logic
3. Optionally update plugin files (e.g., .opencode/plugins/ecc-hooks.ts)
**Files typically involved**:
- `.claude/commands/*.md`
- `.claude/ecc-tools.json`
- `.claude/identity.json`
- `hooks/hooks.json`
- `scripts/hooks/*.js`
- `.opencode/plugins/*.ts`
**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.
Edit hooks/hooks.json to add or update hook definitions
Create or update scripts/hooks/*.js or .ts for hook logic
Optionally update plugin files (e.g., .opencode/plugins/ecc-hooks.ts)
```
### Add Or Update Ecc Bundle
Adds or updates a set of ECC configuration, command, or skill files as a bundle.
**Frequency**: ~2 times per month
**Steps**:
1. Create or update files in .claude/ (commands, skills, rules, team, identity, etc.)
2. Optionally update .codex/agents/*.toml and .agents/skills/*
3. Repeat for each bundle component as needed
**Files typically involved**:
- `.claude/commands/*.md`
- `.claude/skills/*/SKILL.md`
- `.claude/rules/*.md`
- `.claude/team/*.json`
- `.claude/identity.json`
- `.claude/ecc-tools.json`
- `.codex/agents/*.toml`
- `.agents/skills/*/SKILL.md`
- `.agents/skills/*/agents/*.yaml`
**Example commit sequence**:
```
Create or update files in .claude/ (commands, skills, rules, team, identity, etc.)
Optionally update .codex/agents/*.toml and .agents/skills/*
Repeat for each bundle component as needed
```

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:44:06.345Z"
}

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

@@ -2,7 +2,16 @@ use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct RiskThresholds {
pub review: f64,
pub confirm: f64,
pub block: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub db_path: PathBuf,
pub worktree_root: PathBuf,
@@ -12,6 +21,7 @@ pub struct Config {
pub heartbeat_interval_secs: u64,
pub default_agent: String,
pub theme: Theme,
pub risk_thresholds: RiskThresholds,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -32,11 +42,18 @@ impl Default for Config {
heartbeat_interval_secs: 30,
default_agent: "claude".to_string(),
theme: Theme::Dark,
risk_thresholds: Self::RISK_THRESHOLDS,
}
}
}
impl Config {
pub const RISK_THRESHOLDS: RiskThresholds = RiskThresholds {
review: 0.35,
confirm: 0.60,
block: 0.85,
};
pub fn load() -> Result<Self> {
let config_path = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
@@ -52,3 +69,40 @@ impl Config {
}
}
}
impl Default for RiskThresholds {
fn default() -> Self {
Config::RISK_THRESHOLDS
}
}
#[cfg(test)]
mod tests {
use super::Config;
#[test]
fn default_config_uses_default_risk_thresholds() {
let config = Config::default();
assert_eq!(config.risk_thresholds, Config::RISK_THRESHOLDS);
}
#[test]
fn deserialization_defaults_risk_thresholds() {
let config: Config = toml::from_str(
r#"
db_path = "/tmp/ecc2.db"
worktree_root = "/tmp/ecc-worktrees"
max_parallel_sessions = 8
max_parallel_worktrees = 6
session_timeout_secs = 3600
heartbeat_interval_secs = 30
default_agent = "claude"
theme = "Dark"
"#,
)
.expect("config should deserialize");
assert_eq!(config.risk_thresholds, Config::RISK_THRESHOLDS);
}
}

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,6 +1,7 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::config::RiskThresholds;
use crate::session::store::StateStore;
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -13,36 +14,203 @@ pub struct ToolCallEvent {
pub risk_score: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RiskAssessment {
pub score: f64,
pub reasons: Vec<String>,
pub suggested_action: SuggestedAction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SuggestedAction {
Allow,
Review,
RequireConfirmation,
Block,
}
impl ToolCallEvent {
/// 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;
/// Compute risk from the tool type and input characteristics.
pub fn compute_risk(
tool_name: &str,
input: &str,
thresholds: &RiskThresholds,
) -> RiskAssessment {
let normalized_tool = tool_name.to_ascii_lowercase();
let normalized_input = input.to_ascii_lowercase();
let mut score = 0.0;
let mut reasons = Vec::new();
// Destructive tools get higher base risk
match tool_name {
"Bash" => score += 0.3,
"Write" => score += 0.2,
"Edit" => score += 0.1,
_ => score += 0.05,
let (base_score, base_reason) = base_tool_risk(&normalized_tool);
score += base_score;
if let Some(reason) = base_reason {
reasons.push(reason.to_string());
}
// Dangerous patterns in bash commands
if tool_name == "Bash" {
if input.contains("rm -rf") || input.contains("--force") {
score += 0.4;
}
if input.contains("git push") || input.contains("git reset") {
score += 0.3;
}
if input.contains("sudo") || input.contains("chmod 777") {
score += 0.5;
}
let (file_sensitivity_score, file_sensitivity_reason) =
assess_file_sensitivity(&normalized_input);
score += file_sensitivity_score;
if let Some(reason) = file_sensitivity_reason {
reasons.push(reason);
}
score.min(1.0)
let (blast_radius_score, blast_radius_reason) = assess_blast_radius(&normalized_input);
score += blast_radius_score;
if let Some(reason) = blast_radius_reason {
reasons.push(reason);
}
let (irreversibility_score, irreversibility_reason) =
assess_irreversibility(&normalized_input);
score += irreversibility_score;
if let Some(reason) = irreversibility_reason {
reasons.push(reason);
}
let score = score.clamp(0.0, 1.0);
let suggested_action = SuggestedAction::from_score(score, thresholds);
RiskAssessment {
score,
reasons,
suggested_action,
}
}
}
impl SuggestedAction {
fn from_score(score: f64, thresholds: &RiskThresholds) -> Self {
if score >= thresholds.block {
Self::Block
} else if score >= thresholds.confirm {
Self::RequireConfirmation
} else if score >= thresholds.review {
Self::Review
} else {
Self::Allow
}
}
}
fn base_tool_risk(tool_name: &str) -> (f64, Option<&'static str>) {
match tool_name {
"bash" => (
0.20,
Some("shell execution can modify local or shared state"),
),
"write" | "multiedit" => (0.15, Some("writes files directly")),
"edit" => (0.10, Some("modifies existing files")),
_ => (0.05, None),
}
}
fn assess_file_sensitivity(input: &str) -> (f64, Option<String>) {
const SECRET_PATTERNS: &[&str] = &[
".env",
"secret",
"credential",
"token",
"api_key",
"apikey",
"auth",
"id_rsa",
".pem",
".key",
];
const SHARED_INFRA_PATTERNS: &[&str] = &[
"cargo.toml",
"package.json",
"dockerfile",
".github/workflows",
"schema",
"migration",
"production",
];
if contains_any(input, SECRET_PATTERNS) {
(
0.25,
Some("targets a sensitive file or credential surface".to_string()),
)
} else if contains_any(input, SHARED_INFRA_PATTERNS) {
(
0.15,
Some("targets shared infrastructure or release-critical files".to_string()),
)
} else {
(0.0, None)
}
}
fn assess_blast_radius(input: &str) -> (f64, Option<String>) {
const LARGE_SCOPE_PATTERNS: &[&str] = &[
"**",
"/*",
"--all",
"--recursive",
"entire repo",
"all files",
"across src/",
"find ",
" xargs ",
];
const SHARED_STATE_PATTERNS: &[&str] = &[
"git push --force",
"git push -f",
"origin main",
"origin master",
"rm -rf .",
"rm -rf /",
];
if contains_any(input, SHARED_STATE_PATTERNS) {
(
0.35,
Some("has a broad blast radius across shared state or history".to_string()),
)
} else if contains_any(input, LARGE_SCOPE_PATTERNS) {
(
0.25,
Some("has a broad blast radius across multiple files or directories".to_string()),
)
} else {
(0.0, None)
}
}
fn assess_irreversibility(input: &str) -> (f64, Option<String>) {
const HIGH_IRREVERSIBILITY_PATTERNS: &[&str] = &[
"rm -rf",
"git reset --hard",
"git clean -fd",
"drop database",
"drop table",
"truncate ",
"shred ",
];
const MODERATE_IRREVERSIBILITY_PATTERNS: &[&str] =
&["rm -f", "git push --force", "git push -f", "delete from"];
if contains_any(input, HIGH_IRREVERSIBILITY_PATTERNS) {
(
0.45,
Some("includes an irreversible or destructive operation".to_string()),
)
} else if contains_any(input, MODERATE_IRREVERSIBILITY_PATTERNS) {
(
0.40,
Some("includes an irreversible or difficult-to-undo operation".to_string()),
)
} else {
(0.0, None)
}
}
fn contains_any(input: &str, patterns: &[&str]) -> bool {
patterns.iter().any(|pattern| input.contains(pattern))
}
pub fn log_tool_call(db: &StateStore, event: &ToolCallEvent) -> Result<()> {
db.send_message(
&event.session_id,
@@ -52,3 +220,72 @@ pub fn log_tool_call(db: &StateStore, event: &ToolCallEvent) -> Result<()> {
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{SuggestedAction, ToolCallEvent};
use crate::config::Config;
#[test]
fn computes_sensitive_file_risk() {
let assessment = ToolCallEvent::compute_risk(
"Write",
"Update .env.production with rotated API token",
&Config::RISK_THRESHOLDS,
);
assert!(assessment.score >= Config::RISK_THRESHOLDS.review);
assert_eq!(assessment.suggested_action, SuggestedAction::Review);
assert!(assessment
.reasons
.iter()
.any(|reason| reason.contains("sensitive file")));
}
#[test]
fn computes_blast_radius_risk() {
let assessment = ToolCallEvent::compute_risk(
"Edit",
"Apply the same replacement across src/**/*.rs",
&Config::RISK_THRESHOLDS,
);
assert!(assessment.score >= Config::RISK_THRESHOLDS.review);
assert_eq!(assessment.suggested_action, SuggestedAction::Review);
assert!(assessment
.reasons
.iter()
.any(|reason| reason.contains("blast radius")));
}
#[test]
fn computes_irreversible_risk() {
let assessment = ToolCallEvent::compute_risk(
"Bash",
"rm -f /tmp/ecc-temp.txt",
&Config::RISK_THRESHOLDS,
);
assert!(assessment.score >= Config::RISK_THRESHOLDS.confirm);
assert_eq!(
assessment.suggested_action,
SuggestedAction::RequireConfirmation,
);
assert!(assessment
.reasons
.iter()
.any(|reason| reason.contains("irreversible")));
}
#[test]
fn blocks_combined_high_risk_operations() {
let assessment = ToolCallEvent::compute_risk(
"Bash",
"rm -rf . && git push --force origin main",
&Config::RISK_THRESHOLDS,
);
assert!(assessment.score >= Config::RISK_THRESHOLDS.block);
assert_eq!(assessment.suggested_action, SuggestedAction::Block);
}
}

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

@@ -27,7 +27,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,79 +49,10 @@ 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> {
let session = db
.get_session(id)?
.ok_or_else(|| anyhow::anyhow!("Session not found: {id}"))?;
if session.state == SessionState::Completed {
anyhow::bail!("Completed sessions cannot be resumed: {}", 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)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::store::StateStore;
use std::path::PathBuf;
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(())
}
}
pub struct SessionStatus(Session);
impl fmt::Display for SessionStatus {
@@ -132,9 +62,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())?;

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

@@ -24,7 +24,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 +62,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 +84,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",
@@ -151,7 +114,7 @@ impl StateStore {
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 +132,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 +155,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)?,
},
})
})?
@@ -226,96 +184,3 @@ 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)",
rusqlite::params![
"legacy",
"Recover state",
"claude",
"running",
chrono::Utc::now().to_rfc3339(),
chrono::Utc::now().to_rfc3339(),
],
)?;
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(())
}
#[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));
store.insert_session(&session)?;
let loaded = store
.get_session("abc12345")?
.expect("session should be persisted");
assert_eq!(loaded.pid, Some(4242));
assert_eq!(loaded.state, SessionState::Running);
let _ = std::fs::remove_file(path);
Ok(())
}
}

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,