Compare commits

...

206 Commits

Author SHA1 Message Date
Affaan Mustafa
284d2995ba fix: resolve stop hooks without plugin root 2026-03-29 21:24:03 -04:00
Affaan Mustafa
dd675d4258 Merge pull request #1007 from AndriyKalashnykov/chore/pin-actions-and-update-claude-md
chore: pin actions to commit SHAs and add Skills section to CLAUDE.md
2026-03-29 21:16:09 -04:00
Affaan Mustafa
db12d3d838 Merge pull request #1004 from ohashi-mizuki/fix/pre-push-skip-branch-deletion
fix: skip pre-push checks on branch deletion
2026-03-29 21:16:01 -04:00
Andriy Kalashnykov
46f37ae4fb chore: pin actions to commit SHAs and add Skills section to CLAUDE.md
Pin all GitHub Actions to commit SHAs instead of mutable version tags
across ci.yml, release.yml, maintenance.yml, and all reusable workflows.
This prevents supply-chain attacks via tag hijacking.

Add the required Skills section to CLAUDE.md mapping project files
(README.md, .github/workflows/*.yml) to their respective review skills.
2026-03-29 17:16:56 -04:00
ohashi-mizuki
0c166e14da fix: skip pre-push checks on branch deletion
The pre-push hook runs lint/typecheck/test/build checks on every push,
including `git push origin --delete <branch>`. Branch deletion does not
push any code, so verification checks are unnecessary and block the
delete operation.

Detect deletion pushes by reading stdin (local sha is all zeros for
deletes) and exit early.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:09:26 +09:00
Affaan Mustafa
527c79350c Merge pull request #1000 from affaan-m/fix/codex-context7-compat-tests
fix(codex): broaden context7 config checks
2026-03-29 00:26:32 -04:00
Affaan Mustafa
0ebcfc368e fix(codex): broaden context7 config checks 2026-03-29 00:26:16 -04:00
Affaan Mustafa
bec1ebf76d Merge pull request #999 from affaan-m/fix/clv2-config-override-rebase
fix(clv2): honor CLV2_CONFIG in start-observer
2026-03-29 00:22:23 -04:00
Affaan Mustafa
be76918850 fix(clv2): honor CLV2_CONFIG in start-observer 2026-03-29 00:21:55 -04:00
Affaan Mustafa
99a154a908 Merge pull request #998 from affaan-m/fix/token-budget-advisor-trigger-clarity
fix(skills): clarify token-budget-advisor triggers
2026-03-29 00:20:29 -04:00
Affaan Mustafa
ebf0f135bb fix(skills): clarify token-budget-advisor triggers 2026-03-29 00:20:04 -04:00
Affaan Mustafa
2d27da52e2 Merge pull request #997 from affaan-m/fix/readme-agent-count-tree
docs(readme): fix agent count in repo tree
2026-03-29 00:20:01 -04:00
Affaan Mustafa
65c4a0f6ba docs(readme): fix agent count in repo tree 2026-03-29 00:19:52 -04:00
Affaan Mustafa
ab49c9adf5 Merge pull request #920 from Xabilimon1/main
Add skill: token-budget-advisor (TBA)
2026-03-29 00:16:21 -04:00
Affaan Mustafa
b7a82cf240 Merge origin/main into Xabilimon1/main 2026-03-29 00:15:54 -04:00
Affaan Mustafa
9a55fd069b fix(skills): harden token budget advisor skill 2026-03-29 00:14:17 -04:00
Affaan Mustafa
d9e8305aa1 Merge pull request #992 from Lidang-Jiang/fix/doc-file-warning-denylist
fix(hooks): port doc-file-warning denylist policy to current hook runtime
2026-03-29 00:14:00 -04:00
Affaan Mustafa
f2bf72c005 Merge branch 'main' into fix/doc-file-warning-denylist 2026-03-29 00:13:48 -04:00
Affaan Mustafa
3ae0df781f Merge pull request #893 from up2itnow0822/feat/agent-payment-x402-skill
feat: add agent-payment-x402 skill for autonomous agent payments
2026-03-29 00:08:03 -04:00
Affaan Mustafa
a346a304b0 Merge pull request #926 from xingzihai/feature/pre-commit-quality-hook
feat(hooks): add pre-commit quality check hook
2026-03-29 00:07:28 -04:00
Affaan Mustafa
81acf0c928 fix(hooks): make pre-commit quality checks enforce staged state 2026-03-29 00:07:18 -04:00
Affaan Mustafa
06a77911e6 Merge pull request #993 from affaan-m/fix/healthcare-eval-harness-followup
fix(docs): repair healthcare eval harness examples
2026-03-29 00:04:54 -04:00
Affaan Mustafa
9406f35fab fix(docs): repair healthcare eval harness examples 2026-03-29 00:04:36 -04:00
Affaan Mustafa
c5e3658ba6 Merge pull request #955 from drkeyurpatel-wq/feat/healthcare-patterns
feat: Healthcare domain — 4 skills + 1 agent for health-tech applications
2026-03-28 23:25:30 -04:00
Affaan Mustafa
eeeea506a6 Merge pull request #959 from sreedhargs89/feat/skill-context-keeper
feat(skill): ck — persistent per-project memory for Claude Code
2026-03-28 23:24:10 -04:00
Affaan Mustafa
fc1ea4fbea Merge pull request #818 from 694344851/docs/zh-cn-prune-command
Docs/zh cn prune command
2026-03-28 23:24:03 -04:00
Affaan Mustafa
00787d68e4 fix(ck): preserve display names and harden git helpers 2026-03-28 23:23:54 -04:00
Affaan Mustafa
1e3572becf fix(docs): correct zh-CN prune frontmatter 2026-03-28 23:23:51 -04:00
Lidang-Jiang
7462168377 fix(lint): prefix unused options parameter with underscore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-29 10:14:53 +08:00
Lidang-Jiang
3c3781ca43 refactor: address reviewer feedback
- Add options={} parameter to run() to match run-with-flags.js contract
- Remove case-insensitive flag from extension pre-filter for consistency
  with ADHOC_FILENAMES regex (both now case-sensitive)
- Expand warning text to list more structured paths
- Add test cases for uppercase extensions (TODO.MD, NOTES.TXT)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-29 10:09:02 +08:00
Lidang-Jiang
27d71c9548 fix(hooks): port doc-file-warning denylist policy to current hook runtime
Replace the broad allowlist approach with a targeted denylist that only
warns on known ad-hoc filenames (NOTES, TODO, SCRATCH, TEMP, DRAFT,
BRAINSTORM, SPIKE, DEBUG, WIP) outside structured directories. This
eliminates false positives for legitimate markdown-heavy workflows while
still catching impulse documentation files.

Closes #988

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-29 09:54:23 +08:00
Affaan Mustafa
6f16e75f9d Merge pull request #985 from likzn/feature/trae-integration
Add Trae IDE support (.trae/)
2026-03-28 20:46:08 -04:00
Affaan Mustafa
0d30da1fc7 Merge branch 'main' into feature/trae-integration 2026-03-28 20:45:51 -04:00
Affaan Mustafa
e686bcbc82 fix(trae): harden install and uninstall flow 2026-03-28 20:45:37 -04:00
Affaan Mustafa
25c8a5de08 Merge pull request #991 from affaan-m/affaan/laravel-plugin-discovery-refresh
feat(skills): add laravel-plugin-discovery skill with LaraPlugins MCP
2026-03-28 20:44:28 -04:00
Affaan Mustafa
ec104c94c5 fix(skills): wire laravel plugin discovery into installs 2026-03-28 20:44:04 -04:00
Affaan Mustafa
14a51404c0 fix(skills): align laravel plugin discovery docs 2026-03-28 20:44:04 -04:00
Daniel Petrica
666c639206 feat(skills): add laravel-plugin-discovery skill with LaraPlugins MCP
- Add laraplugins MCP server to mcp-configs/mcp-servers.json
- Create laravel-plugin-discovery skill for Laravel package discovery
- Supports searching by keyword, health score, Laravel/PHP version
- No API key required - free for Laravel community
2026-03-28 20:44:04 -04:00
Affaan Mustafa
a8e088a54e Merge pull request #923 from danielpetrica/main
feat(skills): add laravel-plugin-discovery skill with LaraPlugins MCP
2026-03-28 20:41:59 -04:00
Affaan Mustafa
eac0228f88 fix(skills): align laravel plugin discovery docs 2026-03-28 20:41:45 -04:00
Affaan Mustafa
b6e3434ff4 Merge pull request #858 from sliver2er/fix/install-missing-ajv-dependency
fix: move ajv to dependencies and auto-install deps in install scripts
2026-03-28 20:36:59 -04:00
Affaan Mustafa
4eaee83448 fix(install): stop after npm bootstrap failures on powershell 2026-03-28 20:36:43 -04:00
Affaan Mustafa
1e43639cc7 Merge pull request #855 from Yumerain/fix/zh-cn-doc-format
docs(zh-CN): fix missing newline before origin in prompt-optimizer skill
2026-03-28 20:36:11 -04:00
Affaan Mustafa
766f846478 Merge pull request #897 from techiro/docs/ja-JP-translate-plain-text-blocks
docs(ja-JP): Add translation support for code blocks with lang=plain text
2026-03-28 20:35:44 -04:00
Affaan Mustafa
dd38518afe fix(docs): restore canonical runtime strings in ja-JP docs 2026-03-28 20:35:25 -04:00
Affaan Mustafa
c1d98b071e Merge pull request #892 from chris-yyau/fix/remove-redundant-skill-sync
fix: remove redundant skill copy from sync-ecc-to-codex.sh
2026-03-28 20:32:12 -04:00
Affaan Mustafa
70b98f3178 Merge pull request #911 from haibindev/main
feat(skills): add repo-scan skill
2026-03-28 20:30:16 -04:00
Affaan Mustafa
dcc4d914d2 fix(skills): tighten repo-scan install flow 2026-03-28 20:29:51 -04:00
Affaan Mustafa
71219ff656 Merge pull request #929 from xingzihai/feat/skill-development-guide
docs: add comprehensive Skill Development Guide
2026-03-28 20:24:01 -04:00
Affaan Mustafa
e815f0d05c fix(docs): resolve skill guide review issues 2026-03-28 20:23:34 -04:00
Affaan Mustafa
b3a43f34e6 Merge pull request #896 from ToniDonDoni/codex/tdd-workflow-red-green-guards
docs: tighten tdd workflow red-green validation
2026-03-28 20:22:07 -04:00
Affaan Mustafa
0d26f5295d Merge pull request #990 from affaan-m/fix/yarn-lock-sync
fix(ci): sync yarn lockfile
2026-03-28 20:21:28 -04:00
Affaan Mustafa
9181382065 fix(ci): sync yarn lockfile 2026-03-28 20:20:54 -04:00
Affaan Mustafa
9434e07749 Merge pull request #989 from affaan-m/fix/ecc2-analysis-report-facts
fix(docs): correct ecc2 analysis report facts
2026-03-28 20:16:26 -04:00
Affaan Mustafa
9cde3427e2 fix(docs): correct ecc2 analysis report facts 2026-03-28 20:15:46 -04:00
Affaan Mustafa
c6b4c719b2 Merge pull request #952 from anuragg-saxenaa/pr-950
docs: refine ecc2 analysis report recommendations
2026-03-28 20:11:46 -04:00
Affaan Mustafa
f98207feea Merge pull request #960 from senoldogann/feat/codex-plugin-manifest
feat(codex): add Codex native plugin manifest and fix Claude plugin.json
2026-03-28 20:09:57 -04:00
Affaan Mustafa
52e9bd58f1 fix(codex): tighten manifest docs and test guards 2026-03-28 20:09:14 -04:00
Affaan Mustafa
4257c093ca fix(codex): point marketplace plugin path at repo root 2026-03-28 20:06:42 -04:00
senoldogann
23d743b92c fix(skills): add missing YAML frontmatter to 7 skills
Codex CLI requires YAML frontmatter (---) in SKILL.md files.
6 skills were missing frontmatter entirely; laravel-verification had
a bare colon in its description causing an invalid YAML parse error.
2026-03-28 20:06:42 -04:00
senoldogann
414ea90e11 fix(codex): correct marketplace.json plugin source path
Codex CLI resolves plugin paths relative to the home directory (~),
not relative to marketplace.json. The previous path "./everything-claude-code"
resolved to ~/everything-claude-code (non-existent), causing "plugin/read failed"
error in the TUI.
2026-03-28 20:06:42 -04:00
senoldogann
d473cf87e6 feat(codex): add Codex native plugin manifest and fix Claude plugin.json
- Add .codex-plugin/plugin.json — Codex-native plugin manifest with
  skills reference and MCP server config pointer
- Add .codex-plugin/.mcp.json — standalone MCP server config bundle
  (github, context7, exa, memory, playwright, sequential-thinking)
- Add .codex-plugin/README.md — installation guide and server reference
- Fix .claude-plugin/plugin.json — add missing agents[] (28 explicit
  file paths per validator rules), skills[], and commands[] arrays;
  remove hooks field (auto-loaded by Claude Code v2.1+ convention)
- Add tests/plugin-manifest.test.js — 16 CI tests enforcing
  PLUGIN_SCHEMA_NOTES.md rules (no hooks, arrays throughout, explicit
  agent paths, version required, .mcp.json structural checks)
- Update package.json: add .codex-plugin/ to files[], add plugin
  manifest test to npm test chain

Refs: .claude-plugin/PLUGIN_SCHEMA_NOTES.md
2026-03-28 20:06:42 -04:00
Affaan Mustafa
64847d0a21 Merge pull request #986 from Infiniteyieldai/claude/evaluate-repo-comparison-ASZ9Y
docs: Add repo assessment, commands reference, and evaluation docs
2026-03-28 20:04:53 -04:00
Affaan Mustafa
c865d4c676 docs: fix ECC setup reference drift 2026-03-28 20:03:26 -04:00
Claude
72de19effd chore: apply Claude setup improvements
- Expand permissions.allow (git, node, npm, npx, ls, cat, Read, Edit, Write, Glob, Grep) to reduce approval prompts
- Set CLAUDE_ECC_MODE=standard in env for consistent hook profile
- Gate stop-hook-git-check.sh behind ECC_GIT_CHECK=1 to prevent blocking mid-session
- Add project-level Node.js rules for ECC repo (stack, file conventions, hook dev standards)
- observe.sh confirmed already async — no change needed

https://claude.ai/code/session_014SqRA9y6HwVVPGeSCJfwVF
2026-03-28 20:03:26 -04:00
Claude
56076edd48 docs: add commands quick reference guide (59 commands)
https://claude.ai/code/session_014SqRA9y6HwVVPGeSCJfwVF
2026-03-28 20:03:26 -04:00
Claude
04d7eeb16f docs: add repo and fork assessment with setup recommendations
Covers fork health, gap analysis, install profile guidance, and priority
components to add to the current minimal ~/.claude/ setup.

https://claude.ai/code/session_014SqRA9y6HwVVPGeSCJfwVF
2026-03-28 20:03:26 -04:00
Claude
4e7773c2ce docs: add repo evaluation vs current setup comparison
Compares everything-claude-code v1.9.0 against the minimal ~/.claude/ installation.
Documents gap analysis across agents, skills, commands, hooks, and rules.

https://claude.ai/code/session_014SqRA9y6HwVVPGeSCJfwVF
2026-03-28 20:03:26 -04:00
Affaan Mustafa
a3fc90f7ac Merge pull request #964 from affaan-m/fix/claude-hooks-settings-merge-safe
fix(installer): preserve existing Claude hook settings
2026-03-28 19:58:55 -04:00
Affaan Mustafa
55efeb7f20 Merge pull request #987 from dagecko/runner-guard/fix-ci-security
fix: pin 6 actions to commit SHA, extract 1 expression to env var
2026-03-28 19:58:04 -04:00
Affaan Mustafa
1e7c299706 Merge pull request #972 from Lidang-Jiang/fix/observer-windows-temp-files
fix(observer): improve Windows compatibility for temp files and Haiku prompt
2026-03-28 19:57:22 -04:00
Affaan Mustafa
47aa415b06 fix(installer): validate hooks and settings before install 2026-03-28 19:55:34 -04:00
Affaan Mustafa
d7e6bb242a fix(installer): reject invalid claude settings roots 2026-03-28 19:55:34 -04:00
Affaan Mustafa
9f37a5d8c7 fix(installer): preserve existing claude hook settings 2026-03-28 19:55:34 -04:00
Affaan Mustafa
d9ec51c9e9 Merge pull request #932 from KT-lcz/readme
docs: fix rule installation examples
2026-03-28 19:49:35 -04:00
Affaan Mustafa
9033f2a997 Merge pull request #970 from seancheick/codex/context7-consistency
fix: normalize Codex Context7 naming
2026-03-28 19:46:45 -04:00
Affaan Mustafa
67660540ac docs: use directory-level rule copy examples 2026-03-28 19:45:44 -04:00
Sean Cheick Baradji
432788d0b5 fix: clean up legacy Context7 aliases on update 2026-03-28 19:44:07 -04:00
Sean Cheick Baradji
6a7a115e18 fix: normalize Codex Context7 naming 2026-03-28 19:43:36 -04:00
Affaan Mustafa
1181d93498 Merge pull request #974 from Lidang-Jiang/fix/codex-sanity-check-persistent-instructions
fix(codex): add persistent_instructions to baseline and relax sanity check
2026-03-28 19:42:50 -04:00
Affaan Mustafa
80d6a89f12 Merge pull request #971 from Lidang-Jiang/fix/codex-mcp-startup-timeout
fix(codex): add startup_timeout_sec to MCP servers to prevent first-run timeouts
2026-03-28 19:42:39 -04:00
dagecko
28a1fbc3f2 fix: pin 6 actions to commit SHA, extract 1 expression to env var 2026-03-28 15:57:55 -04:00
likzn
4fcaaf8a89 feat: add .trae directory with install/uninstall scripts
- Add install.sh for Trae IDE integration
- Add uninstall.sh with manifest-based safe removal
- Add README.md (English)
- Add README.zh-CN.md (Chinese)
- Support local and global installation
- Support TRAE_ENV=cn for CN environment
- Non-destructive installation (won't overwrite existing files)
- Manifest-based uninstallation (preserves user files)

Change-Id: I9870874e272fffd9e1966d9bc40d20142314b969
2026-03-29 01:01:21 +08:00
Lidang-Jiang
7a4cb8c570 fix(observer): clean up prompt_file early and fix test for analysis_relpath
- Remove prompt_file immediately after shell expansion into -p arg,
  avoiding stale temp files during long analysis windows (greptile feedback)
- Update test assertion to check analysis_relpath instead of analysis_file,
  matching the cross-platform relative path change from earlier commits

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 23:32:44 +08:00
Affaan Mustafa
4b4f077d18 fix(codex): allow indented persistent_instructions 2026-03-28 11:00:28 -04:00
Affaan Mustafa
78c98dd4fd fix(codex): reuse shared MCP startup timeout constant 2026-03-28 10:59:23 -04:00
Affaan Mustafa
f07797533d Merge pull request #931 from KT-lcz/main
docs: clarify multi-model command setup
2026-03-28 09:13:18 -04:00
Affaan Mustafa
87d883eb1b Merge pull request #963 from affaan-m/fix/install-show-help-on-error
fix(installer): show help text on error and document --profile full
2026-03-28 09:12:38 -04:00
Affaan Mustafa
652f87c5b6 fix(installer): tighten error help spacing 2026-03-28 09:11:14 -04:00
Affaan Mustafa
70b65a9d06 fix: tighten installer error spacing 2026-03-28 09:11:14 -04:00
Prabith Balagopalan
24674a7bd6 fix(installer): write error and help text to stderr for consistent stream output
Extracted help text into getHelpText() and write both the error message
and usage help to stderr via process.stderr.write(). This ensures that
when output is redirected (e.g. 2>errors.txt), both the error and the
guidance appear in the same stream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:11:02 -04:00
Prabith Balagopalan
d49c95a5ec fix(installer): show help text on error and document --profile full in README
Running install.ps1/install.sh with no arguments gave a cryptic error
with no guidance. Now the usage help is printed after the error so users
know what arguments to pass.

Also added --profile full as the recommended install option in the README
quick-start section, which was previously undocumented.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 09:11:02 -04:00
Affaan Mustafa
70a96bd363 Merge pull request #977 from Lidang-Jiang/fix/cli-homedir-windows-fallback
fix(scripts): add os.homedir() fallback for Windows compatibility
2026-03-28 09:09:59 -04:00
Affaan Mustafa
8f7445a260 Merge pull request #976 from ymdvsymd/fix/ci-pnpm-yarn-compat-v2
fix(ci): enable Corepack for yarn and relax pnpm strict mode
2026-03-28 09:09:44 -04:00
Lidang-Jiang
9ad4351f53 fix(codex): align context7-mcp package specifier with config.toml
Add @latest suffix to '@upstash/context7-mcp' in ECC_SERVERS so the
generated merge spec matches .codex/config.toml exactly, preventing
configDiffers from flagging false drift on --update-mcp runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 11:38:38 +08:00
Lidang-Jiang
451732164f fix(observer): clean up temp files on cd failure early return
The cd "$PROJECT_DIR" failure path returned without removing prompt_file
and analysis_file, leaving stale temp files in .observer-tmp/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 11:38:10 +08:00
Lidang-Jiang
ebd14cde7d fix(codex): allow leading whitespace in persistent_instructions regex
The rg pattern anchored at line start (^persistent_instructions) would
miss indented TOML entries. Use ^\s* prefix to match both top-level and
indented configurations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 11:37:36 +08:00
Lidang-Jiang
ae21a8df85 fix(scripts): add os.homedir() fallback for Windows compatibility
On Windows (native cmd/PowerShell), process.env.HOME is undefined.
Seven CLI entry points and two library files pass process.env.HOME
directly as homeDir without a cross-platform fallback, causing all
path resolutions to silently fail (resolving to "undefined/.claude/...").

Node.js os.homedir() correctly handles all platforms (HOME on Unix,
USERPROFILE on Windows, OS-level fallback). The project already uses
this pattern in scripts/lib/state-store/index.js and has a getHomeDir()
utility in scripts/lib/utils.js, but it was not applied consistently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 11:28:12 +08:00
to.watanabe
d8e3b9d593 fix(ci): remove --ignore-engines for Yarn Berry (v4+)
Yarn Berry removed the --ignore-engines flag; engine checking is no
longer a core feature. The deprecated flag causes yarn install to exit
with error code 1.
2026-03-28 12:27:04 +09:00
to.watanabe
7148d9006f fix(ci): enable Corepack for yarn and relax pnpm strict mode
All 18 pnpm/yarn CI jobs fail on main because:
1. pnpm v9+ refuses to install when package.json declares
   "packageManager": "yarn@4.9.2" — fixed by setting
   COREPACK_ENABLE_STRICT=0 and --no-frozen-lockfile
2. CI runners only have Yarn Classic (v1.x) but the project
   uses Yarn Berry (v4.x) — fixed by activating Corepack
   before the cache/install steps
2026-03-28 12:27:04 +09:00
Lidang-Jiang
c14765e701 fix(codex): add persistent_instructions to baseline and relax sanity check
The global sanity check (check-codex-global-state.sh) hard-fails when
persistent_instructions is missing from ~/.codex/config.toml, but neither
the baseline .codex/config.toml nor the sync script ever define this field.
This causes a clean install to report a failing sanity check even though the
sync otherwise succeeds (#967).

- Add persistent_instructions to the baseline .codex/config.toml so that
  users who cp the config get a working default.
- Downgrade the sanity check from fail to warn, since persistent_instructions
  is additive and optional — users who rely solely on AGENTS.md should not be
  blocked.

Fixes #967 (persistent_instructions part; context7 naming addressed by #970)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 10:59:19 +08:00
Lidang-Jiang
194bc0000b fix(observer): guard cd failure with early return and log message
Address reviewer feedback: under set +e, a failing cd would silently
leave CWD unchanged, causing the relative analysis path to break.
Add || return with a diagnostic log entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 10:45:41 +08:00
Lidang-Jiang
1e44475458 fix(codex): sync startup_timeout_sec into merge-mcp-config.js ECC_SERVERS
Reviewers identified that merge-mcp-config.js --update-mcp would
silently strip the startup_timeout_sec from config.toml because the
ECC_SERVERS spec did not include it. Add startup_timeout_sec = 30 to
playwright, context7-mcp, github, memory, and sequential-thinking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 10:36:04 +08:00
Lidang-Jiang
31af1adcc8 fix(observer): anchor CWD to PROJECT_DIR before Claude invocation
Reviewers correctly identified that the relative analysis_relpath
(.observer-tmp/<file>) only resolves when CWD equals PROJECT_DIR.
Without an explicit cd, non-Windows users launching the observer from
a different directory would fail to read the analysis file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 10:34:40 +08:00
Lidang-Jiang
c80631fc1d fix(observer): improve Windows compatibility for temp files and Haiku prompt
Address remaining issues from #842 after PR #903 moved temp files to
PROJECT_DIR/.observer-tmp:

Bug A (path resolution): Use relative paths (.observer-tmp/filename)
in the prompt instead of absolute paths from mktemp. On Windows
Git Bash/MSYS2, absolute paths use MSYS-style prefixes (/c/Users/...)
that the spawned Claude subprocess may fail to resolve.

Bug B (asks for permission): Add explicit IMPORTANT instruction block
at the prompt start telling the Haiku agent it is in non-interactive
--print mode and must use the Write tool directly without asking for
confirmation.

Additional improvements:
- Pass prompt via -p flag instead of stdin redirect for Windows compat
- Add .observer-tmp/ to .gitignore to prevent accidental commits

Fixes #842

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 09:54:35 +08:00
Lidang-Jiang
00f8628b83 fix(codex): add startup_timeout_sec to MCP servers to prevent first-run timeouts
On first startup, npx-based MCP servers need to download packages before
they can respond. The default timeout is too short for this, causing
frequent "timed out after 10 seconds" errors reported in #544.

Add startup_timeout_sec = 30 to all five command-based MCP servers
(github, context7, memory, playwright, sequential-thinking). The URL-based
exa server is unaffected and left unchanged.

30 seconds was chosen over the 20s precedent in merge-mcp-config.js to
give extra headroom for slow networks on first run.

Fixes #544

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
2026-03-28 09:52:17 +08:00
Affaan Mustafa
ba09a34432 docs: renumber ecc2 analysis recommendations 2026-03-27 07:57:07 -04:00
Affaan Mustafa
27e0d53f6d docs: resolve ecc2 analysis review nits 2026-03-27 06:35:21 -04:00
Affaan Mustafa
8b6140dedc Merge pull request #956 from tae1344/fix/ajv-runtime-dependency
fix(install): move ajv to dependencies and add .yarnrc.yml for node-modules linker
2026-03-27 06:25:02 -04:00
Affaan Mustafa
7633386e04 Merge pull request #878 from affaan-m/feat/install-catalog-project-config
feat: add install catalog and project config autodetection
2026-03-27 06:00:05 -04:00
Affaan Mustafa
b4296c7095 feat: add install catalog and project config autodetection 2026-03-27 05:56:39 -04:00
Sreedhara GS
17f6f95090 fix(ck): address Greptile + CodeRabbit review bugs
- Fix read-after-write in session-start.mjs: read prevSession BEFORE
  overwriting current-session.json so unsaved-session detection fires
- Fix shell injection in resume.mjs: replace execSync shell string with
  fs.existsSync for directory existence check
- Fix shell injection in shared.mjs gitSummary: replace nested \$(git ...)
  subshell with a separate runGit() call to get rev count
- Fix displayName never shown: render functions now use ctx.displayName
  ?? ctx.name so user-supplied names show instead of the slug
- Fix renderListTable: uses context.displayName ?? entry.name
- Fix init.mjs: use path.basename() instead of cwd.split('/').pop()
- Fix save.mjs confirmation: show original name, not contextDir slug

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:44:11 +09:00
Sreedhara GS
1e226ba556 feat(skill): ck — context-keeper v2, persistent per-project memory
Adds the ck (Context Keeper) skill — deterministic Node.js scripts
that give Claude Code persistent, per-project memory across sessions.

Architecture:
- commands/ — 8 Node.js scripts handle all command logic (init, save,
  resume, info, list, forget, migrate, shared). Claude calls scripts
  and displays output — no LLM interpretation of command logic.
- hooks/session-start.mjs — injects ~100 token compact summary on
  session start (not kilobytes). Detects unsaved sessions, git
  activity since last save, goal mismatch vs CLAUDE.md.
- context.json as source of truth — CONTEXT.md is generated from it.
  Full session history, session IDs, git activity per save.

Commands: /ck:init /ck:save /ck:resume /ck:info /ck:list /ck:forget /ck:migrate
Source: https://github.com/sreedhargs89/context-keeper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:30:39 +09:00
Affaan Mustafa
cc60bf6b65 Merge pull request #947 from chris-yyau/fix/shell-script-permissions
fix: add execute permissions to codex sync shell scripts
2026-03-27 02:47:13 -04:00
Affaan Mustafa
160624d0ed Merge branch 'main' into fix/shell-script-permissions 2026-03-27 02:46:42 -04:00
Affaan Mustafa
73c10122fe Merge pull request #938 from affaan-m/dependabot/npm_and_yarn/npm_and_yarn-3f9ee708be
chore(deps-dev): bump picomatch from 4.0.3 to 4.0.4 in the npm_and_yarn group across 1 directory
2026-03-27 02:46:29 -04:00
Dr. Keyur Patel
9b24bedf85 fix: address Greptile review — frontmatter, CI safety, null guards
Greptile fixes:
- Removed non-standard YAML frontmatter fields (observe, feedback, rollback) from all 4 skills — only name, description, origin, version per CONTRIBUTING.md
- Added null guard to checkInteractions implementation (was missing despite test)
- CI: replaced 2>/dev/null with 2>&1 (was silencing safety-critical errors)
- CI: quoted $RESULT variable (was breaking jq on JSON with spaces)
- CI: added division-by-zero guard when test suite is empty
- CI: added note that Jest is reference implementation, thresholds are framework-agnostic
2026-03-27 04:02:44 +00:00
Dr. Keyur Patel
e3f2bda9fc fix: address all CodeRabbit + Cubic review comments on PR #955
CodeRabbit fixes (6 comments):
- All 4 skills: renamed 'When to Activate' → 'When to Use', added 'How It Works' and 'Examples' sections
- CDSS: DoseValidationResult.suggestedRange now typed as '| null'
- PHI: hyphenated 'Non-patient-sensitive'

Cubic fixes (7 issues):
- P1: CDSS weight-based check now BLOCKS when weight missing (was false-negative pass)
- P1: EMR medication safety clarified — critical = hard block, override requires documented reason
- P1: PHI logging guidance clarified — use opaque UUIDs only, not medical record numbers
- P2: CDSS validateDose now uses age and renal function params (ageAdjusted, renalAdjusted rules)
- P2: Eval CI example now enforces 95% threshold with jq + bc calculation
- P2: Eval CI example now includes --coverage --coverageThreshold on CDSS suite
- P2: CDSS suggestedRange null type fixed (same as CodeRabbit)
2026-03-27 03:54:20 +00:00
tae1344
fe6a6fc106 fix: move ajv to dependencies and add .yarnrc.yml for node-modules linker
ajv was in devDependencies but required at runtime by scripts/lib/install/config.js,
causing 'Cannot find module ajv' when running ./install.sh. Also adds .yarnrc.yml
with nodeLinker: node-modules so plain `node` can resolve packages without PnP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 12:18:44 +09:00
Dr. Keyur Patel
63737544a1 feat: add healthcare domain skills and agent
New skills:
- healthcare-emr-patterns: EMR/EHR encounter workflows, smart templates, medication safety, clinical UI patterns
- healthcare-phi-compliance: PHI/PII protection patterns, RLS templates, leak vector checklist, audit trail patterns
- healthcare-cdss-patterns: Drug interaction checking, dose validation, clinical scoring (NEWS2/qSOFA), alert severity
- healthcare-eval-harness: Patient safety CI/CD gate — CDSS accuracy, PHI exposure, data integrity, clinical workflows

New agent:
- healthcare-reviewer: Clinical safety reviewer for CDSS accuracy, PHI compliance, medical data integrity

All patterns are generalized and framework-agnostic. Applicable to any health-tech stack.
Origin: Health1 Super Speciality Hospitals, Ahmedabad, India.
2026-03-27 03:17:49 +00:00
Anurag Saxena
dafc9bcd60 Update research/ecc2-codebase-analysis.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-26 17:35:00 -04:00
Anurag Saxena
2d0fddf174 Update research/ecc2-codebase-analysis.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-26 17:34:50 -04:00
anuragg-saxenaa
f471f27658 fix: address CodeRabbit review — dependency versions, risk wording, style, security audit rec
- Fix dependency table: update outdated versions, remove unused git2
- Fix "No...No...No" repetitive sentence in Config section
- Add task string security audit to Section 7 recommendations
- Fix risk assessment: dashboard 1,273 lines (not >1500) — mark as projected
- Renumber P3 items after inserting new recommendation
2026-03-26 17:31:09 -04:00
Toast (gastown)
925d830c53 docs: add ECC2 codebase analysis research report
Covers architecture overview, code quality metrics, identified gaps,
test coverage analysis, security observations, dependency health,
and prioritized recommendations. Key findings: comms module has send
without receive, new-session dialog is a stub, git2 dependency is
unused, dashboard.rs at 1273 lines needs extraction.
2026-03-26 16:20:57 +00:00
Chris Yau
2243f15581 fix: add execute permissions to codex sync shell scripts
Three .sh files were committed without the execute bit, causing
`install-global-git-hooks.sh` to fail with "Permission denied"
when invoked by `sync-ecc-to-codex.sh`.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-26 22:14:51 +08:00
dependabot[bot]
6408511611 chore(deps-dev): bump picomatch
Bumps the npm_and_yarn group with 1 update in the / directory: [picomatch](https://github.com/micromatch/picomatch).


Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-26 12:04:05 +00:00
lichangze
9348751b8e docs: fix rule installation examples
Clarify that manual rule installation should preserve the rules directory structure so references keep working and filename collisions are avoided.
2026-03-26 17:07:47 +08:00
lichangze
c96c4d2742 docs: clarify multi-model command setup
Document that multi-* commands require the ccg-workflow runtime so users know they must initialize the extra wrapper and prompt assets before use.
2026-03-26 16:42:08 +08:00
xingzihai
da74f85c10 fix: address review feedback from PR #929
- Add missing code-review.md and development-workflow.md to zh/README.md directory listing
- Add mkdir -p command before copy in manual install instructions
- Fix TypeScript test command path in SKILL-DEVELOPMENT-GUIDE.md
- Add Anti-Patterns section to SKILL.md template
- Add Template category to Skill Categories table in CONTRIBUTING.md
- Add Pre-Review Requirements section to code-review.md (both en and zh)
- Add Pre-Review Checks step to development-workflow.md (both en and zh)
- Add trailing newlines to all files that were missing them
2026-03-26 04:37:08 +00:00
xingzihai
c146fae2ce docs: add comprehensive Skill Development Guide
- Add docs/SKILL-DEVELOPMENT-GUIDE.md with detailed guidance on creating skills
- Update CONTRIBUTING.md with enhanced skills section linking to the new guide
- Covers skill architecture, categories, best practices, testing, and examples

The new guide provides:
- What skills are and when they activate
- Skill file structure and format
- Step-by-step skill creation tutorial
- Writing effective skill content
- Common patterns and anti-patterns
- Testing and validation checklist
- Complete examples gallery
2026-03-26 02:28:02 +00:00
xingzihai
3f5e042b40 feat: add Chinese (zh-CN) translation for rules/common
- Add rules/zh/ directory with complete Chinese translations
- Translate all 10 common rule files:
  - coding-style.md
  - security.md
  - testing.md
  - git-workflow.md
  - performance.md
  - patterns.md
  - hooks.md
  - agents.md
  - development-workflow.md
  - code-review.md
- Add README.md for the zh directory explaining structure and installation
- Maintain consistent formatting with original English versions
- Keep technical terms and code examples in English where appropriate
2026-03-26 01:38:39 +00:00
xingzihai
b5148f184a feat(rules): add code-review.md rule to common rules
- Add comprehensive code review standards for all languages
- Define when to review (after code changes, before commits)
- Include security review triggers and severity levels
- Reference relevant agents (code-reviewer, security-reviewer, etc.)
- Add review checklist covering security, quality, and performance
- Define approval criteria (Approve/Warning/Block)

This rule complements the existing code-reviewer agent by providing
clear guidelines on when and how to conduct code reviews.
2026-03-26 00:59:46 +00:00
xingzihai
b44ba7096f feat(hooks): add pre-commit quality check hook
- Add pre-bash-commit-quality.js hook script
- Runs quality checks before git commit commands:
  - Lints staged files (ESLint, Pylint, golint)
  - Validates commit message format (conventional commits)
  - Detects console.log/debugger statements
  - Warns about TODO/FIXME without issue references
  - Detects potential hardcoded secrets
- Updates hooks.json with new hook configuration
- Updates README.md with hook documentation

Cross-platform (Windows, macOS, Linux)
2026-03-26 00:28:26 +00:00
Daniel Petrica
45baaa1ea5 feat(skills): add laravel-plugin-discovery skill with LaraPlugins MCP
- Add laraplugins MCP server to mcp-configs/mcp-servers.json
- Create laravel-plugin-discovery skill for Laravel package discovery
- Supports searching by keyword, health score, Laravel/PHP version
- No API key required - free for Laravel community
2026-03-25 22:39:22 +01:00
Xabilimon
4da1fb388c Update skills/token-budget-advisor/SKILL.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 22:02:20 +01:00
Xabilimon
917c35bb6f Update skills/token-budget-advisor/SKILL.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 22:02:13 +01:00
Xabilimon
ee3f348dcb Update skills/token-budget-advisor/SKILL.md
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-03-25 21:59:15 +01:00
Xabilimon
e6eb99271f Update skills/token-budget-advisor/SKILL.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-25 21:58:59 +01:00
Xabilimon
7cabf77142 Update skills/token-budget-advisor/SKILL.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-25 21:58:51 +01:00
Xabilimon
9cfcfac665 Update skills/token-budget-advisor/SKILL.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 21:58:31 +01:00
Xabilimon
0284f60871 Update skills/token-budget-advisor/SKILL.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 21:58:23 +01:00
Xabilimon
7a17ec9b14 Update skills/token-budget-advisor/SKILL.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 21:58:09 +01:00
Xabilimon
243fae8476 Add token-budget-advisor skill
Skill that intercepts prompts, estimates token consumption across 4
depth levels, and lets the user choose before Claude responds.
2026-03-25 20:07:35 +01:00
xingzihai
dc92b5c62b feat: Add performance-optimizer agent for code performance analysis and optimization 2026-03-25 17:24:31 +00:00
xingzihai
3fbfd7f7ff feat: Add git-workflow skill
Add comprehensive Git workflow skill covering:
- Branching strategies (GitHub Flow, Trunk-Based, GitFlow)
- Conventional commits format and best practices
- Merge vs rebase with clear guidance
- Pull request workflow and templates
- Conflict resolution strategies
- Branch management and naming conventions
- Release management with semantic versioning
- Git configuration and useful aliases
- Common workflows and anti-patterns

This skill helps developers and teams establish consistent
Git practices for collaborative development.
2026-03-25 17:05:02 +00:00
Hirokazu Tanaka
a6a81490f6 revert(ja-JP): keep commit message examples in English in CONTRIBUTING.md 2026-03-25 23:30:54 +09:00
Chris Yau
d170cdd175 fix: remove redundant skill copy from sync-ecc-to-codex.sh
Codex CLI reads skills natively from ~/.agents/skills/ (installed by
ECC installer / npx skills). The sync script was redundantly copying
the same skills from .agents/skills/ to ~/.codex/skills/.

Changes:
- Remove skill copy loop, variables, and path validation from sync script
- Update sanity checker to validate ~/.agents/skills/ instead of
  ~/.codex/skills/, downgrade missing skills from FAIL to WARN
- Update test assertions to verify skill sync removal

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-25 21:33:45 +08:00
海滨code
57e9983c88 fix: address review feedback — rename sections, pin install commit, fix frontmatter 2026-03-25 20:05:55 +08:00
海滨code
d952a07c73 fix: populate SKILL.md with actual content 2026-03-25 19:54:57 +08:00
海滨code
369f66297a fix: populate SKILL.md with actual content 2026-03-25 19:54:01 +08:00
Toni Doni
9cc5d085e1 adjust: scope tdd checkpoints to active branch 2026-03-25 14:09:59 +03:00
Affaan Mustafa
678fb6f0d3 Merge pull request #846 from pythonstrup/feat/desktop-notify-hook
feat: add macOS desktop notification Stop hook
2026-03-25 03:19:13 -07:00
Affaan Mustafa
401e26a45a Merge pull request #880 from affaan-m/dependabot/cargo/ecc2/cargo-3169503097
chore(deps): bump git2 from 0.19.0 to 0.20.4 in /ecc2 in the cargo group across 1 directory
2026-03-25 03:04:43 -07:00
Affaan Mustafa
eb934afbb5 Merge pull request #888 from affaan-m/feat/ecc2-risk-scoring
feat(ecc2): add tool risk scoring and actions
2026-03-25 03:01:12 -07:00
Affaan Mustafa
8303970258 feat(ecc2): add tool risk scoring and actions 2026-03-25 06:00:34 -04:00
Affaan Mustafa
319f9efafb Merge pull request #887 from affaan-m/feat/ecc2-tool-logging
feat(ecc2): add tool call logging and history
2026-03-25 02:51:20 -07:00
Affaan Mustafa
6c2a3a2bae feat(ecc2): add tool call logging and history 2026-03-25 05:50:31 -04:00
Affaan Mustafa
adaeab9dba Merge pull request #886 from affaan-m/feat/ecc2-split-pane
feat(ecc2): add split-pane dashboard resizing
2026-03-25 02:46:08 -07:00
Affaan Mustafa
8981dd6067 feat(ecc2): add split-pane dashboard resizing 2026-03-25 05:45:43 -04:00
海滨code
7229e09df1 feat(skills): add repo-scan skill 2026-03-25 17:11:47 +08:00
Affaan Mustafa
4105a2f36c Merge pull request #885 from affaan-m/feat/ecc2-crash-resume
feat(ecc2): add crash resume session recovery
2026-03-25 01:37:35 -07:00
Affaan Mustafa
0166231ddb feat(ecc2): add crash resume session recovery 2026-03-25 04:36:12 -04:00
Affaan Mustafa
cf439dd481 Merge pull request #882 from affaan-m/feat/ecc2-live-streaming
feat(ecc2): live output streaming per agent
2026-03-25 01:31:53 -07:00
Affaan Mustafa
9903ae528b fix: restore antigravity install target metadata 2026-03-25 04:24:19 -04:00
Affaan Mustafa
44c2bf6f7b feat(ecc2): implement live output streaming per agent (#774)
- PTY output capture via tokio::process with stdout/stderr piping
- Ring buffer (1000 lines) per session
- Output pane wired to show selected session with auto-scroll
- Broadcast channel for output events
2026-03-25 04:19:50 -04:00
Affaan Mustafa
e78c092499 fix(ci): restore validation and antigravity target safety 2026-03-25 04:19:50 -04:00
Affaan Mustafa
61f70de479 Merge pull request #903 from affaan-m/fix/session-manager-843-supersede-853
fix: fold blocker-lane session and hook hardening into one PR
2026-03-25 01:16:28 -07:00
Affaan Mustafa
776ac439f3 test: cover canonical session duplicate precedence 2026-03-25 04:01:23 -04:00
Affaan Mustafa
b19b4c6b5e fix: finish blocker lane hook and install regressions 2026-03-25 04:00:50 -04:00
Affaan Mustafa
b5157f4ed1 test: relax sync-ecc shell parsing 2026-03-25 03:56:10 -04:00
Affaan Mustafa
2d1e384eef test: isolate suggest-compact counter fixtures 2026-03-25 03:51:15 -04:00
Affaan Mustafa
9c5ca92e6e fix: finish hook fallback and canonical session follow-ups 2026-03-25 03:44:03 -04:00
Affaan Mustafa
7b510c886e fix: harden session hook guards and session ID handling 2026-03-25 03:36:36 -04:00
Affaan Mustafa
c1b47ac9db Merge pull request #883 from affaan-m/feat/ecc2-status-panel
feat(ecc2): agent status panel with Table widget
2026-03-25 00:18:05 -07:00
Affaan Mustafa
3f02fa439a feat(ecc2): implement agent status panel with Table widget (#773)
- Table widget with columns: ID, Agent, State, Branch, Tokens, Duration
- Color-coded states: green=Running, yellow=Idle, red=Failed, gray=Stopped, blue=Completed
- Summary bar with running/completed/failed counts
- Row selection highlighting
2026-03-25 03:07:51 -04:00
Jonghyeok Park
f6b10481f3 fix: add spawnSync error logging and restore 5s timeout
- Check spawnSync result and log warning on failure via stderr
- Restore osascript timeout to 5000ms, increase hook deadline to 10s
  for sufficient headroom
2026-03-25 16:03:21 +09:00
Jonghyeok Park
d3699f9010 fix: use AppleScript-safe escaping and reduce spawnSync timeout
- Replace JSON.stringify with curly quote substitution for AppleScript
  compatibility (AppleScript does not support \" backslash escapes)
- Reduce spawnSync timeout from 5000ms to 3000ms to leave headroom
  within the 5s hook deadline
2026-03-25 16:03:21 +09:00
Jonghyeok Park
445ae5099d feat: add macOS desktop notification Stop hook
Add a new Stop hook that sends a native macOS notification with the
task summary (first line of last_assistant_message) when Claude finishes
responding. Uses osascript via spawnSync for shell injection safety.
Supports run-with-flags fast require() path. Only active on standard
and strict profiles; silently skips on non-macOS platforms.
2026-03-25 16:03:21 +09:00
Affaan Mustafa
00bc7f30be fix: resolve blocker PR validation regressions 2026-03-25 01:34:29 -04:00
Affaan Mustafa
1d0aa5ac2a fix: fold session manager blockers into one candidate 2026-03-24 23:08:27 -04:00
Affaan Mustafa
7f7e319d9f Merge pull request #884 from affaan-m/feat/ecc2-token-meter
feat(ecc2): token/cost meter widget
2026-03-24 19:53:59 -07:00
Affaan Mustafa
d7bcc92007 feat(ecc2): add token/cost meter widget (#775)
- TokenMeter widget using ratatui Gauge with color gradient (green->yellow->red)
- Budget fields (cost_budget_usd, token_budget) in Config
- Aggregate cost display in status bar
- Warning state at 80%+ budget consumption
- Tests for gradient, config fallback, and meter rendering
2026-03-24 22:52:52 -04:00
dependabot[bot]
e883385ab0 chore(deps): bump git2 in /ecc2 in the cargo group across 1 directory
Bumps the cargo group with 1 update in the /ecc2 directory: [git2](https://github.com/rust-lang/git2-rs).


Updates `git2` from 0.19.0 to 0.20.4
- [Changelog](https://github.com/rust-lang/git2-rs/blob/git2-0.20.4/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/git2-0.19.0...git2-0.20.4)

---
updated-dependencies:
- dependency-name: git2
  dependency-version: 0.20.4
  dependency-type: direct:production
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 02:45:34 +00:00
Affaan Mustafa
e7d827548c Merge pull request #881 from affaan-m/feat/ecc2-session-lifecycle
feat(ecc2): session create/destroy lifecycle
2026-03-24 19:44:28 -07:00
Hirokazu Tanaka
bf7ed1fce2 docs(ja-JP): translate plain text code blocks to Japanese
Translate English prose inside plain text code blocks (```text, ```)
across ja-JP documentation to Japanese, following the same approach
as PR #753 (zh-CN translation).

Translated content includes:
- Output template labels and status messages
- Folder tree inline comments
- CLI workflow descriptions
- Error/warning message examples
- Commit message templates and PR title examples

Technical identifiers, file paths, and actual code remain untranslated.
2026-03-25 08:20:14 +09:00
ToniDonDoni
fee93f2dab Apply suggestions from code review
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 02:19:09 +03:00
Toni Doni
a61947bb5c adjust: generalize refactor commit placeholder 2026-03-25 02:14:39 +03:00
Toni Doni
3c59d8dc60 adjust: clarify runtime vs compile-time red validation 2026-03-25 02:10:01 +03:00
ToniDonDoni
46f6e3644b Apply suggestions from code review
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 02:07:53 +03:00
Toni Doni
39a34e46db docs: tighten tdd workflow red-green validation 2026-03-25 01:42:45 +03:00
AI Agent Economy
95a1435f61 Update skills/agent-payment-x402/SKILL.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-24 15:43:59 -05:00
up2itnow0822
e57ad5c33d fix: address all automated review feedback on code example
Security model:
- Remove set_policy from agent-callable tools table; document as
  orchestrator-only to prevent self-privilege escalation
- Pin agentwallet-sdk@6.0.0 in MCP config with pre-install guidance
  (npx without -y hangs in non-interactive MCP startup)
- Whitelist only required env vars (PATH, NODE_ENV, WALLET_PRIVATE_KEY)
  instead of forwarding entire process.env to subprocess

Code example (complete rewrite):
- Add StdioClientTransport import and client.connect() for runnable code
- Wrap in async main() for CJS/ESM compatibility (top-level await)
- Verify set_policy result via isError before delegating
- Five distinct fail-closed error paths in preToolCheck:
  1. Invalid apiCost input (NaN/Infinity bypass prevention)
  2. Transport/connectivity failure
  3. Tool-level error (isError: true, e.g., auth failure)
  4. Unexpected response format (missing/non-finite remaining)
  5. Budget exceeded (clear amounts in message)
- Use Number.isFinite() for both apiCost and remaining validation

Documentation:
- Rename headings per CONTRIBUTING.md format
- Replace broken mcp-server-patterns cross-ref with security-review
- Add 'Pin your dependencies' to Best Practices
- Add security note about supply-chain risk
2026-03-24 15:36:31 -05:00
up2itnow0822
f7d589ce21 feat: add agent-payment-x402 skill for autonomous agent payments
Adds a skill for x402 payment execution with MCP integration:
- Per-task and per-session spending controls
- Non-custodial wallet management (ERC-4337)
- Pairs with mcp-server-patterns and cost-aware-llm-pipeline skills
- Production reference: merged into NVIDIA NeMo Agent Toolkit (PR #17)
- npm package: agentwallet-sdk
2026-03-24 12:24:25 -05:00
Affaan Mustafa
2787b8e92f feat(ecc2): implement session create/destroy lifecycle (#764)
- Process spawning via tokio::process::Command
- Session state transitions with guards (Pending->Running->Completed/Failed/Stopped)
- Stop with process kill and optional worktree cleanup
- Latest alias resolver in get_status
- SQLite store migrations for state tracking
2026-03-24 03:56:23 -07:00
Affaan Mustafa
2166d80d58 Merge pull request #762 from affaan-m/feat/ecc2-tui-scaffold
feat: ECC 2.0 — Rust TUI agentic IDE control plane
2026-03-24 03:54:54 -07:00
Seunghyun Woo
9c381b4469 fix: move ajv to dependencies and auto-install deps in install scripts
`ajv` is required at runtime by the installer (`scripts/lib/install/config.js`)
but was listed under `devDependencies`. This caused `Error: Cannot find module
'ajv'` when running `./install.sh` from a fresh git clone or via `npx`.

- Move `ajv` from devDependencies to dependencies in package.json
- Add auto `npm install` in install.sh when node_modules is missing
- Add matching auto-install in install.ps1 for Windows parity
2026-03-24 16:38:40 +09:00
crispyrice
e3510f62a8 docs(zh-CN): fix missing newline before origin in prompt-optimizer skill 2026-03-24 13:46:05 +08:00
Affaan Mustafa
7726c25e46 fix(ci): restore validation and antigravity target safety 2026-03-23 14:29:21 -07:00
cjp
6af7ca1afc Update docs/zh-CN/commands/prune.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-23 13:58:44 +08:00
cjp
d6061cf937 Update docs/zh-CN/commands/prune.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-23 13:58:34 +08:00
cjp
ec921e5202 Update docs/zh-CN/commands/prune.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-23 13:54:04 +08:00
cjp
d016e68cee Update docs/zh-CN/commands/prune.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-23 13:53:57 +08:00
cjp
aed18eb571 Update docs/zh-CN/commands/prune.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-23 13:53:48 +08:00
cjp
f3cf808814 Update docs/zh-CN/commands/prune.md
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-23 13:53:35 +08:00
cjp
e22cb57718 docs(zh-CN): add prune command translation 2026-03-23 13:41:59 +08:00
cjp
4811e8c73b docs(zh-CN): add prune command translation 2026-03-23 11:48:31 +08:00
180 changed files with 17205 additions and 1116 deletions

View File

@@ -0,0 +1,20 @@
{
"name": "everything-claude-code",
"interface": {
"displayName": "Everything Claude Code"
},
"plugins": [
{
"name": "everything-claude-code",
"source": {
"source": "local",
"path": "../.."
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}

View File

@@ -21,5 +21,37 @@
"workflow",
"automation",
"best-practices"
]
],
"agents": [
"./agents/architect.md",
"./agents/build-error-resolver.md",
"./agents/chief-of-staff.md",
"./agents/code-reviewer.md",
"./agents/cpp-build-resolver.md",
"./agents/cpp-reviewer.md",
"./agents/database-reviewer.md",
"./agents/doc-updater.md",
"./agents/docs-lookup.md",
"./agents/e2e-runner.md",
"./agents/flutter-reviewer.md",
"./agents/go-build-resolver.md",
"./agents/go-reviewer.md",
"./agents/harness-optimizer.md",
"./agents/java-build-resolver.md",
"./agents/java-reviewer.md",
"./agents/kotlin-build-resolver.md",
"./agents/kotlin-reviewer.md",
"./agents/loop-operator.md",
"./agents/planner.md",
"./agents/python-reviewer.md",
"./agents/pytorch-build-resolver.md",
"./agents/refactor-cleaner.md",
"./agents/rust-build-resolver.md",
"./agents/rust-reviewer.md",
"./agents/security-reviewer.md",
"./agents/tdd-guide.md",
"./agents/typescript-reviewer.md"
],
"skills": ["./skills/"],
"commands": ["./commands/"]
}

47
.claude/rules/node.md Normal file
View File

@@ -0,0 +1,47 @@
# Node.js Rules for everything-claude-code
> Project-specific rules for the ECC codebase. Extends common rules.
## Stack
- **Runtime**: Node.js >=18 (no transpilation, plain CommonJS)
- **Test runner**: `node tests/run-all.js` — individual files via `node tests/**/*.test.js`
- **Linter**: ESLint (`@eslint/js`, flat config)
- **Coverage**: c8
- **Lint**: markdownlint-cli for `.md` files
## File Conventions
- `scripts/` — Node.js utilities, hooks. CommonJS (`require`/`module.exports`)
- `agents/`, `commands/`, `skills/`, `rules/` — Markdown with YAML frontmatter
- `tests/` — Mirror the `scripts/` structure. Test files named `*.test.js`
- File naming: **lowercase with hyphens** (e.g. `session-start.js`, `post-edit-format.js`)
## Code Style
- CommonJS only — no ESM (`import`/`export`) unless file ends in `.mjs`
- No TypeScript — plain `.js` throughout
- Prefer `const` over `let`; never `var`
- Keep hook scripts under 200 lines — extract helpers to `scripts/lib/`
- All hooks must `exit 0` on non-critical errors (never block tool execution unexpectedly)
## Hook Development
- Hook scripts normally receive JSON on stdin, but hooks routed through `scripts/hooks/run-with-flags.js` can export `run(rawInput)` and let the wrapper handle parsing/gating
- Async hooks: mark `"async": true` in `settings.json` with a timeout ≤30s
- Blocking hooks (PreToolUse, stop): keep fast (<200ms) — no network calls
- Use `run-with-flags.js` wrapper for all hooks so `ECC_HOOK_PROFILE` and `ECC_DISABLED_HOOKS` runtime gating works
- Always exit 0 on parse errors; log to stderr with `[HookName]` prefix
## Testing Requirements
- Run `node tests/run-all.js` before committing
- New scripts in `scripts/lib/` require a matching test in `tests/lib/`
- New hooks require at least one integration test in `tests/hooks/`
## Markdown / Agent Files
- Agents: YAML frontmatter with `name`, `description`, `tools`, `model`
- Skills: sections — When to Use, How It Works, Examples
- Commands: `description:` frontmatter line required
- Run `npx markdownlint-cli '**/*.md' --ignore node_modules` before committing

49
.codex-plugin/README.md Normal file
View File

@@ -0,0 +1,49 @@
# .codex-plugin — Codex Native Plugin for ECC
This directory contains the **Codex plugin manifest** for Everything Claude Code.
## Structure
```
.codex-plugin/
└── plugin.json — Codex plugin manifest (name, version, skills ref, MCP ref)
.mcp.json — MCP server configurations at plugin root (NOT inside .codex-plugin/)
```
## What This Provides
- **125 skills** from `./skills/` — reusable Codex workflows for TDD, security,
code review, architecture, and more
- **6 MCP servers** — GitHub, Context7, Exa, Memory, Playwright, Sequential Thinking
## Installation
Codex plugin support is currently in preview. Once generally available:
```bash
# Install from Codex CLI
codex plugin install affaan-m/everything-claude-code
# Or reference locally during development
codex plugin install ./
Run this from the repository root so `./` points to the repo root and `.mcp.json` resolves correctly.
```
## MCP Servers Included
| Server | Purpose |
|---|---|
| `github` | GitHub API access |
| `context7` | Live documentation lookup |
| `exa` | Neural web search |
| `memory` | Persistent memory across sessions |
| `playwright` | Browser automation & E2E testing |
| `sequential-thinking` | Step-by-step reasoning |
## Notes
- The `skills/` directory at the repo root is shared between Claude Code (`.claude-plugin/`)
and Codex (`.codex-plugin/`) — same source of truth, no duplication
- MCP server credentials are inherited from the launching environment (env vars)
- This manifest does **not** override `~/.codex/config.toml` settings

30
.codex-plugin/plugin.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "everything-claude-code",
"version": "1.9.0",
"description": "Battle-tested Codex workflows — 125 skills, production-ready MCP configs, and agent definitions for TDD, security scanning, code review, and autonomous development.",
"author": {
"name": "Affaan Mustafa",
"email": "me@affaanmustafa.com",
"url": "https://x.com/affaanmustafa"
},
"homepage": "https://github.com/affaan-m/everything-claude-code",
"repository": "https://github.com/affaan-m/everything-claude-code",
"license": "MIT",
"keywords": ["codex", "agents", "skills", "tdd", "code-review", "security", "workflow", "automation"],
"skills": "./skills/",
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "Everything Claude Code",
"shortDescription": "125 battle-tested skills for TDD, security, code review, and autonomous development.",
"longDescription": "Everything Claude Code (ECC) is a community-maintained collection of Codex skills and MCP configs evolved over 10+ months of intensive daily use. It covers TDD workflows, security scanning, code review, architecture decisions, and more — all in one installable plugin.",
"developerName": "Affaan Mustafa",
"category": "Productivity",
"capabilities": ["Read", "Write"],
"websiteURL": "https://github.com/affaan-m/everything-claude-code",
"defaultPrompt": [
"Use the tdd-workflow skill to write tests before implementation.",
"Use the security-review skill to scan for OWASP Top 10 vulnerabilities.",
"Use the code-review skill to review this PR for correctness and security."
]
}
}

View File

@@ -46,12 +46,15 @@ Available skills:
Treat the project-local `.codex/config.toml` as the default Codex baseline for ECC. The current ECC baseline enables GitHub, Context7, Exa, Memory, Playwright, and Sequential Thinking; add heavier extras in `~/.codex/config.toml` only when a task actually needs them.
ECC's canonical Codex section name is `[mcp_servers.context7]`. The launcher package remains `@upstash/context7-mcp`; only the TOML section name is normalized for consistency with `codex mcp list` and the reference config.
### Automatic config.toml merging
The sync script (`scripts/sync-ecc-to-codex.sh`) uses a Node-based TOML parser to safely merge ECC MCP servers into `~/.codex/config.toml`:
- **Add-only by default** — missing ECC servers are appended; existing servers are never modified or removed.
- **7 managed servers** — Supabase, Playwright, Context7, Exa, GitHub, Memory, Sequential Thinking.
- **Canonical naming** — ECC manages Context7 as `[mcp_servers.context7]`; legacy `[mcp_servers.context7-mcp]` entries are treated as aliases during updates.
- **Package-manager aware** — uses the project's configured package manager (npm/pnpm/yarn/bun) instead of hardcoding `pnpm`.
- **Drift warnings** — if an existing server's config differs from the ECC recommendation, the script logs a warning.
- **`--update-mcp`** — explicitly replaces all ECC-managed servers with the latest recommended config (safely removes subtables like `[mcp_servers.supabase.env]`).

View File

@@ -27,7 +27,10 @@ notify = [
"-sound", "default",
]
# Prefer AGENTS.md and project-local .codex/AGENTS.md for instructions.
# Persistent instructions are appended to every prompt (additive, unlike
# model_instructions_file which replaces AGENTS.md).
persistent_instructions = "Follow project AGENTS.md guidelines. Use available MCP servers when they can help."
# model_instructions_file replaces built-in instructions instead of AGENTS.md,
# so leave it unset unless you intentionally want a single override file.
# model_instructions_file = "/absolute/path/to/instructions.md"
@@ -38,10 +41,14 @@ notify = [
[mcp_servers.github]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
startup_timeout_sec = 30
[mcp_servers.context7]
command = "npx"
# Canonical Codex section name is `context7`; the package itself remains
# `@upstash/context7-mcp`.
args = ["-y", "@upstash/context7-mcp@latest"]
startup_timeout_sec = 30
[mcp_servers.exa]
url = "https://mcp.exa.ai/mcp"
@@ -49,14 +56,17 @@ url = "https://mcp.exa.ai/mcp"
[mcp_servers.memory]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-memory"]
startup_timeout_sec = 30
[mcp_servers.playwright]
command = "npx"
args = ["-y", "@playwright/mcp@latest", "--extension"]
startup_timeout_sec = 30
[mcp_servers.sequential-thinking]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-sequential-thinking"]
startup_timeout_sec = 30
# Additional MCP servers (uncomment as needed):
# [mcp_servers.supabase]
@@ -76,7 +86,8 @@ args = ["-y", "@modelcontextprotocol/server-sequential-thinking"]
# args = ["-y", "@cloudflare/mcp-server-cloudflare"]
[features]
# Codex multi-agent support is experimental as of March 2026.
# Codex multi-agent collaboration is stable and on by default in current builds.
# Keep the explicit toggle here so the repo documents its expectation clearly.
multi_agent = true
# Profiles — switch with `codex -p <name>`
@@ -91,6 +102,9 @@ sandbox_mode = "workspace-write"
web_search = "live"
[agents]
[agents]
# Multi-agent role limits and local role definitions.
# These map to `.codex/agents/*.toml` and mirror the repo's explorer/reviewer/docs workflow.
max_threads = 6
max_depth = 1

View File

@@ -34,23 +34,30 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ matrix.node }}
# Package manager setup
- name: Setup pnpm
if: matrix.pm == 'pnpm'
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4
with:
version: latest
- name: Setup Yarn (via Corepack)
if: matrix.pm == 'yarn'
shell: bash
run: |
corepack enable
corepack prepare yarn@stable --activate
- name: Setup Bun
if: matrix.pm == 'bun'
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
# Cache configuration
- name: Get npm cache directory
@@ -61,7 +68,7 @@ jobs:
- name: Cache npm
if: matrix.pm == 'npm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.json') }}
@@ -76,7 +83,7 @@ jobs:
- name: Cache pnpm
if: matrix.pm == 'pnpm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ matrix.node }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
@@ -97,7 +104,7 @@ jobs:
- name: Cache yarn
if: matrix.pm == 'yarn'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
@@ -106,7 +113,7 @@ jobs:
- name: Cache bun
if: matrix.pm == 'bun'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
@@ -114,14 +121,18 @@ jobs:
${{ runner.os }}-bun-
# Install dependencies
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
# package.json declares "packageManager": "yarn@..."
- name: Install dependencies
shell: bash
env:
COREPACK_ENABLE_STRICT: '0'
run: |
case "${{ matrix.pm }}" in
npm) npm ci ;;
pnpm) pnpm install ;;
# --ignore-engines required for Node 18 compat with some devDependencies (e.g., markdownlint-cli)
yarn) yarn install --ignore-engines ;;
pnpm) pnpm install --no-frozen-lockfile ;;
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
yarn) yarn install ;;
bun) bun install ;;
*) echo "Unsupported package manager: ${{ matrix.pm }}" && exit 1 ;;
esac
@@ -135,7 +146,7 @@ jobs:
# Upload test artifacts on failure
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
path: |
@@ -149,10 +160,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
@@ -175,6 +186,10 @@ jobs:
run: node scripts/ci/validate-skills.js
continue-on-error: false
- name: Validate install manifests
run: node scripts/ci/validate-install-manifests.js
continue-on-error: false
- name: Validate rules
run: node scripts/ci/validate-rules.js
continue-on-error: false
@@ -190,10 +205,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
@@ -208,10 +223,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'

View File

@@ -15,8 +15,8 @@ jobs:
name: Check Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
- name: Check for outdated packages
@@ -26,8 +26,8 @@ jobs:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
- name: Run security audit
@@ -43,7 +43,7 @@ jobs:
name: Stale Issues/PRs
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
with:
stale-issue-message: 'This issue is stale due to inactivity.'
stale-pr-message: 'This PR is stale due to inactivity.'

View File

@@ -14,17 +14,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Validate version tag
run: |
if ! [[ "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
if ! [[ "${REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid version tag format. Expected vX.Y.Z"
exit 1
fi
env:
REF_NAME: ${{ github.ref_name }}
- name: Verify plugin.json version matches tag
env:
TAG_NAME: ${{ github.ref_name }}
@@ -61,7 +63,7 @@ jobs:
EOF
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
with:
body_path: release_body.md
generate_release_notes: true

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
@@ -49,7 +49,7 @@ jobs:
EOF
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
with:
tag_name: ${{ inputs.tag }}
body_path: release_body.md

View File

@@ -27,22 +27,29 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ inputs.node-version }}
- name: Setup pnpm
if: inputs.package-manager == 'pnpm'
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4
with:
version: latest
- name: Setup Yarn (via Corepack)
if: inputs.package-manager == 'yarn'
shell: bash
run: |
corepack enable
corepack prepare yarn@stable --activate
- name: Setup Bun
if: inputs.package-manager == 'bun'
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- name: Get npm cache directory
if: inputs.package-manager == 'npm'
@@ -52,7 +59,7 @@ jobs:
- name: Cache npm
if: inputs.package-manager == 'npm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ inputs.node-version }}-npm-${{ hashFiles('**/package-lock.json') }}
@@ -67,7 +74,7 @@ jobs:
- name: Cache pnpm
if: inputs.package-manager == 'pnpm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ inputs.node-version }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
@@ -88,7 +95,7 @@ jobs:
- name: Cache yarn
if: inputs.package-manager == 'yarn'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ inputs.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }}
@@ -97,20 +104,25 @@ jobs:
- name: Cache bun
if: inputs.package-manager == 'bun'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
restore-keys: |
${{ runner.os }}-bun-
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
# package.json declares "packageManager": "yarn@..."
- name: Install dependencies
shell: bash
env:
COREPACK_ENABLE_STRICT: '0'
run: |
case "${{ inputs.package-manager }}" in
npm) npm ci ;;
pnpm) pnpm install ;;
yarn) yarn install --ignore-engines ;;
pnpm) pnpm install --no-frozen-lockfile ;;
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
yarn) yarn install ;;
bun) bun install ;;
*) echo "Unsupported package manager: ${{ inputs.package-manager }}" && exit 1 ;;
esac
@@ -122,7 +134,7 @@ jobs:
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-results-${{ inputs.os }}-node${{ inputs.node-version }}-${{ inputs.package-manager }}
path: |

View File

@@ -17,10 +17,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ inputs.node-version }}
@@ -39,5 +39,8 @@ jobs:
- name: Validate skills
run: node scripts/ci/validate-skills.js
- name: Validate install manifests
run: node scripts/ci/validate-install-manifests.js
- name: Validate rules
run: node scripts/ci/validate-rules.js

3
.gitignore vendored
View File

@@ -83,6 +83,9 @@ temp/
*.bak
*.backup
# Observer temp files (continuous-learning-v2)
.observer-tmp/
# Rust build artifacts
ecc2/target/

27
.mcp.json Normal file
View File

@@ -0,0 +1,27 @@
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"]
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@2.1.4"]
},
"exa": {
"url": "https://mcp.exa.ai/mcp"
},
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
},
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@0.0.68", "--extension"]
},
"sequential-thinking": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
}
}
}

184
.trae/README.md Normal file
View File

@@ -0,0 +1,184 @@
# Everything Claude Code for Trae
Bring Everything Claude Code (ECC) workflows to Trae IDE. This repository provides custom commands, agents, skills, and rules that can be installed into any Trae project with a single command.
## Quick Start
### Option 1: Local Installation (Current Project Only)
```bash
# Install to current project
cd /path/to/your/project
TRAE_ENV=cn .trae/install.sh
```
This creates `.trae-cn/` in your project directory.
### Option 2: Global Installation (All Projects)
```bash
# Install globally to ~/.trae-cn/
cd /path/to/your/project
TRAE_ENV=cn .trae/install.sh ~
# Or from the .trae folder directly
cd /path/to/your/project/.trae
TRAE_ENV=cn ./install.sh ~
```
This creates `~/.trae-cn/` which applies to all Trae projects.
### Option 3: Quick Install to Current Directory
```bash
# If already in project directory with .trae folder
cd .trae
./install.sh
```
The installer uses non-destructive copy - it will not overwrite your existing files.
## Installation Modes
### Local Installation
Install to the current project's `.trae-cn` directory:
```bash
cd /path/to/your/project
TRAE_ENV=cn .trae/install.sh
```
This creates `/path/to/your/project/.trae-cn/` with all ECC components.
### Global Installation
Install to your home directory's `.trae-cn` directory (applies to all Trae projects):
```bash
# From project directory
TRAE_ENV=cn .trae/install.sh ~
# Or directly from .trae folder
cd .trae
TRAE_ENV=cn ./install.sh ~
```
This creates `~/.trae-cn/` with all ECC components. All Trae projects will use these global installations.
**Note**: Global installation is useful when you want to maintain a single copy of ECC across all your projects.
## Environment Support
- **Default**: Uses `.trae` directory
- **CN Environment**: Uses `.trae-cn` directory (set via `TRAE_ENV=cn`)
### Force Environment
```bash
# From project root, force the CN environment
TRAE_ENV=cn .trae/install.sh
# From inside the .trae folder
cd .trae
TRAE_ENV=cn ./install.sh
```
**Note**: `TRAE_ENV` is a global environment variable that applies to the entire installation session.
## Uninstall
The uninstaller uses a manifest file (`.ecc-manifest`) to track installed files, ensuring safe removal:
```bash
# Uninstall from current directory (if already inside .trae or .trae-cn)
cd .trae-cn
./uninstall.sh
# Or uninstall from project root
cd /path/to/your/project
TRAE_ENV=cn .trae/uninstall.sh
# Uninstall globally from home directory
TRAE_ENV=cn .trae/uninstall.sh ~
# Will ask for confirmation before uninstalling
```
### Uninstall Behavior
- **Safe removal**: Only removes files tracked in the manifest (installed by ECC)
- **User files preserved**: Any files you added manually are kept
- **Non-empty directories**: Directories containing user-added files are skipped
- **Manifest-based**: Requires `.ecc-manifest` file (created during install)
### Environment Support
Uninstall respects the same `TRAE_ENV` environment variable as install:
```bash
# Uninstall from .trae-cn (CN environment)
TRAE_ENV=cn ./uninstall.sh
# Uninstall from .trae (default environment)
./uninstall.sh
```
**Note**: If no manifest file is found (old installation), the uninstaller will ask whether to remove the entire directory.
## What's Included
### Commands
Commands are on-demand workflows invocable via the `/` menu in Trae chat. All commands are reused directly from the project root's `commands/` folder.
### Agents
Agents are specialized AI assistants with specific tool configurations. All agents are reused directly from the project root's `agents/` folder.
### Skills
Skills are on-demand workflows invocable via the `/` menu in chat. All skills are reused directly from the project's `skills/` folder.
### Rules
Rules provide always-on rules and context that shape how the agent works with your code. All rules are reused directly from the project root's `rules/` folder.
## Usage
1. Type `/` in chat to open the commands menu
2. Select a command or skill
3. The agent will guide you through the workflow with specific instructions and checklists
## Project Structure
```
.trae/ (or .trae-cn/)
├── commands/ # Command files (reused from project root)
├── agents/ # Agent files (reused from project root)
├── skills/ # Skill files (reused from skills/)
├── rules/ # Rule files (reused from project root)
├── install.sh # Install script
├── uninstall.sh # Uninstall script
└── README.md # This file
```
## Customization
All files are yours to modify after installation. The installer never overwrites existing files, so your customizations are safe across re-installs.
**Note**: The `install.sh` and `uninstall.sh` scripts are automatically copied to the target directory during installation, so you can run these commands directly from your project.
## Recommended Workflow
1. **Start with planning**: Use `/plan` command to break down complex features
2. **Write tests first**: Invoke `/tdd` command before implementing
3. **Review your code**: Use `/code-review` after writing code
4. **Check security**: Use `/code-review` again for auth, API endpoints, or sensitive data handling
5. **Fix build errors**: Use `/build-fix` if there are build errors
## Next Steps
- Open your project in Trae
- Type `/` to see available commands
- Enjoy the ECC workflows!

192
.trae/README.zh-CN.md Normal file
View File

@@ -0,0 +1,192 @@
# Everything Claude Code for Trae
为 Trae IDE 带来 Everything Claude Code (ECC) 工作流。此仓库提供自定义命令、智能体、技能和规则,可以通过单个命令安装到任何 Trae 项目中。
## 快速开始
### 方式一:本地安装到 `.trae` 目录(默认环境)
```bash
# 安装到当前项目的 .trae 目录
cd /path/to/your/project
.trae/install.sh
```
这将在您的项目目录中创建 `.trae/`
### 方式二:本地安装到 `.trae-cn` 目录CN 环境)
```bash
# 安装到当前项目的 .trae-cn 目录
cd /path/to/your/project
TRAE_ENV=cn .trae/install.sh
```
这将在您的项目目录中创建 `.trae-cn/`
### 方式三:全局安装到 `~/.trae` 目录(默认环境)
```bash
# 全局安装到 ~/.trae/
cd /path/to/your/project
.trae/install.sh ~
```
这将创建 `~/.trae/`,适用于所有 Trae 项目。
### 方式四:全局安装到 `~/.trae-cn` 目录CN 环境)
```bash
# 全局安装到 ~/.trae-cn/
cd /path/to/your/project
TRAE_ENV=cn .trae/install.sh ~
```
这将创建 `~/.trae-cn/`,适用于所有 Trae 项目。
安装程序使用非破坏性复制 - 它不会覆盖您现有的文件。
## 安装模式
### 本地安装
安装到当前项目的 `.trae``.trae-cn` 目录:
```bash
# 安装到当前项目的 .trae 目录(默认)
cd /path/to/your/project
.trae/install.sh
# 安装到当前项目的 .trae-cn 目录CN 环境)
cd /path/to/your/project
TRAE_ENV=cn .trae/install.sh
```
### 全局安装
安装到您主目录的 `.trae``.trae-cn` 目录(适用于所有 Trae 项目):
```bash
# 全局安装到 ~/.trae/(默认)
.trae/install.sh ~
# 全局安装到 ~/.trae-cn/CN 环境)
TRAE_ENV=cn .trae/install.sh ~
```
**注意**:全局安装适用于希望在所有项目之间维护单个 ECC 副本的场景。
## 环境支持
- **默认**:使用 `.trae` 目录
- **CN 环境**:使用 `.trae-cn` 目录(通过 `TRAE_ENV=cn` 设置)
### 强制指定环境
```bash
# 从项目根目录强制使用 CN 环境
TRAE_ENV=cn .trae/install.sh
# 进入 .trae 目录后使用默认环境
cd .trae
./install.sh
```
**注意**`TRAE_ENV` 是一个全局环境变量,适用于整个安装会话。
## 卸载
卸载程序使用清单文件(`.ecc-manifest`)跟踪已安装的文件,确保安全删除:
```bash
# 从当前目录卸载(如果已经在 .trae 或 .trae-cn 目录中)
cd .trae-cn
./uninstall.sh
# 或者从项目根目录卸载
cd /path/to/your/project
TRAE_ENV=cn .trae/uninstall.sh
# 从主目录全局卸载
TRAE_ENV=cn .trae/uninstall.sh ~
# 卸载前会询问确认
```
### 卸载行为
- **安全删除**:仅删除清单中跟踪的文件(由 ECC 安装的文件)
- **保留用户文件**:您手动添加的任何文件都会被保留
- **非空目录**:包含用户添加文件的目录会被跳过
- **基于清单**:需要 `.ecc-manifest` 文件(在安装时创建)
### 环境支持
卸载程序遵循与安装程序相同的 `TRAE_ENV` 环境变量:
```bash
# 从 .trae-cn 卸载CN 环境)
TRAE_ENV=cn ./uninstall.sh
# 从 .trae 卸载(默认环境)
./uninstall.sh
```
**注意**:如果找不到清单文件(旧版本安装),卸载程序将询问是否删除整个目录。
## 包含的内容
### 命令
命令是通过 Trae 聊天中的 `/` 菜单调用的按需工作流。所有命令都直接复用自项目根目录的 `commands/` 文件夹。
### 智能体
智能体是具有特定工具配置的专门 AI 助手。所有智能体都直接复用自项目根目录的 `agents/` 文件夹。
### 技能
技能是通过聊天中的 `/` 菜单调用的按需工作流。所有技能都直接复用自项目的 `skills/` 文件夹。
### 规则
规则提供始终适用的规则和上下文,塑造智能体处理代码的方式。所有规则都直接复用自项目根目录的 `rules/` 文件夹。
## 使用方法
1. 在聊天中输入 `/` 以打开命令菜单
2. 选择一个命令或技能
3. 智能体将通过具体说明和检查清单指导您完成工作流
## 项目结构
```
.trae/ (或 .trae-cn/)
├── commands/ # 命令文件(复用自项目根目录)
├── agents/ # 智能体文件(复用自项目根目录)
├── skills/ # 技能文件(复用自 skills/
├── rules/ # 规则文件(复用自项目根目录)
├── install.sh # 安装脚本
├── uninstall.sh # 卸载脚本
└── README.md # 此文件
```
## 自定义
安装后,所有文件都归您修改。安装程序永远不会覆盖现有文件,因此您的自定义在重新安装时是安全的。
**注意**:安装时会自动将 `install.sh``uninstall.sh` 脚本复制到目标目录,这样您可以在项目本地直接运行这些命令。
## 推荐的工作流
1. **从计划开始**:使用 `/plan` 命令分解复杂功能
2. **先写测试**:在实现之前调用 `/tdd` 命令
3. **审查您的代码**:编写代码后使用 `/code-review`
4. **检查安全性**对于身份验证、API 端点或敏感数据处理,再次使用 `/code-review`
5. **修复构建错误**:如果有构建错误,使用 `/build-fix`
## 下一步
- 在 Trae 中打开您的项目
- 输入 `/` 以查看可用命令
- 享受 ECC 工作流!

221
.trae/install.sh Executable file
View File

@@ -0,0 +1,221 @@
#!/bin/bash
#
# ECC Trae Installer
# Installs Everything Claude Code workflows into a Trae project.
#
# Usage:
# ./install.sh # Install to current directory
# ./install.sh ~ # Install globally to ~/.trae/ or ~/.trae-cn/
#
# Environment:
# TRAE_ENV=cn # Force use .trae-cn directory
#
set -euo pipefail
# When globs match nothing, expand to empty list instead of the literal pattern
shopt -s nullglob
# Resolve the directory where this script lives (the repo root)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
# Get the trae directory name (.trae or .trae-cn)
get_trae_dir() {
if [ "${TRAE_ENV:-}" = "cn" ]; then
echo ".trae-cn"
else
echo ".trae"
fi
}
ensure_manifest_entry() {
local manifest="$1"
local entry="$2"
touch "$manifest"
if ! grep -Fqx "$entry" "$manifest"; then
echo "$entry" >> "$manifest"
fi
}
# Install function
do_install() {
local target_dir="$PWD"
local trae_dir="$(get_trae_dir)"
# Check if ~ was specified (or expanded to $HOME)
if [ "$#" -ge 1 ]; then
if [ "$1" = "~" ] || [ "$1" = "$HOME" ]; then
target_dir="$HOME"
fi
fi
# Check if we're already inside a .trae or .trae-cn directory
local current_dir_name="$(basename "$target_dir")"
local trae_full_path
if [ "$current_dir_name" = ".trae" ] || [ "$current_dir_name" = ".trae-cn" ]; then
# Already inside the trae directory, use it directly
trae_full_path="$target_dir"
else
# Normal case: append trae_dir to target_dir
trae_full_path="$target_dir/$trae_dir"
fi
echo "ECC Trae Installer"
echo "=================="
echo ""
echo "Source: $REPO_ROOT"
echo "Target: $trae_full_path/"
echo ""
# Subdirectories to create
SUBDIRS="commands agents skills rules"
# Create all required trae subdirectories
for dir in $SUBDIRS; do
mkdir -p "$trae_full_path/$dir"
done
# Manifest file to track installed files
MANIFEST="$trae_full_path/.ecc-manifest"
touch "$MANIFEST"
# Counters for summary
commands=0
agents=0
skills=0
rules=0
other=0
# Copy commands from repo root
if [ -d "$REPO_ROOT/commands" ]; then
for f in "$REPO_ROOT/commands"/*.md; do
[ -f "$f" ] || continue
local_name=$(basename "$f")
target_path="$trae_full_path/commands/$local_name"
if [ ! -f "$target_path" ]; then
cp "$f" "$target_path"
ensure_manifest_entry "$MANIFEST" "commands/$local_name"
commands=$((commands + 1))
else
ensure_manifest_entry "$MANIFEST" "commands/$local_name"
fi
done
fi
# Copy agents from repo root
if [ -d "$REPO_ROOT/agents" ]; then
for f in "$REPO_ROOT/agents"/*.md; do
[ -f "$f" ] || continue
local_name=$(basename "$f")
target_path="$trae_full_path/agents/$local_name"
if [ ! -f "$target_path" ]; then
cp "$f" "$target_path"
ensure_manifest_entry "$MANIFEST" "agents/$local_name"
agents=$((agents + 1))
else
ensure_manifest_entry "$MANIFEST" "agents/$local_name"
fi
done
fi
# Copy skills from repo root (if available)
if [ -d "$REPO_ROOT/skills" ]; then
for d in "$REPO_ROOT/skills"/*/; do
[ -d "$d" ] || continue
skill_name="$(basename "$d")"
target_skill_dir="$trae_full_path/skills/$skill_name"
skill_copied=0
while IFS= read -r source_file; do
relative_path="${source_file#$d}"
target_path="$target_skill_dir/$relative_path"
mkdir -p "$(dirname "$target_path")"
if [ ! -f "$target_path" ]; then
cp "$source_file" "$target_path"
skill_copied=1
fi
ensure_manifest_entry "$MANIFEST" "skills/$skill_name/$relative_path"
done < <(find "$d" -type f | sort)
if [ "$skill_copied" -eq 1 ]; then
skills=$((skills + 1))
fi
done
fi
# Copy rules from repo root
if [ -d "$REPO_ROOT/rules" ]; then
while IFS= read -r rule_file; do
relative_path="${rule_file#$REPO_ROOT/rules/}"
target_path="$trae_full_path/rules/$relative_path"
mkdir -p "$(dirname "$target_path")"
if [ ! -f "$target_path" ]; then
cp "$rule_file" "$target_path"
rules=$((rules + 1))
fi
ensure_manifest_entry "$MANIFEST" "rules/$relative_path"
done < <(find "$REPO_ROOT/rules" -type f | sort)
fi
# Copy README files from this directory
for readme_file in "$SCRIPT_DIR/README.md" "$SCRIPT_DIR/README.zh-CN.md"; do
if [ -f "$readme_file" ]; then
local_name=$(basename "$readme_file")
target_path="$trae_full_path/$local_name"
if [ ! -f "$target_path" ]; then
cp "$readme_file" "$target_path"
ensure_manifest_entry "$MANIFEST" "$local_name"
other=$((other + 1))
else
ensure_manifest_entry "$MANIFEST" "$local_name"
fi
fi
done
# Copy install and uninstall scripts
for script_file in "$SCRIPT_DIR/install.sh" "$SCRIPT_DIR/uninstall.sh"; do
if [ -f "$script_file" ]; then
local_name=$(basename "$script_file")
target_path="$trae_full_path/$local_name"
if [ ! -f "$target_path" ]; then
cp "$script_file" "$target_path"
chmod +x "$target_path"
ensure_manifest_entry "$MANIFEST" "$local_name"
other=$((other + 1))
else
ensure_manifest_entry "$MANIFEST" "$local_name"
fi
fi
done
# Add manifest file itself to manifest
ensure_manifest_entry "$MANIFEST" ".ecc-manifest"
# Installation summary
echo "Installation complete!"
echo ""
echo "Components installed:"
echo " Commands: $commands"
echo " Agents: $agents"
echo " Skills: $skills"
echo " Rules: $rules"
echo ""
echo "Directory: $(basename "$trae_full_path")"
echo ""
echo "Next steps:"
echo " 1. Open your project in Trae"
echo " 2. Type / to see available commands"
echo " 3. Enjoy the ECC workflows!"
echo ""
echo "To uninstall later:"
echo " cd $trae_full_path"
echo " ./uninstall.sh"
}
# Main logic
do_install "$@"

194
.trae/uninstall.sh Executable file
View File

@@ -0,0 +1,194 @@
#!/bin/bash
#
# ECC Trae Uninstaller
# Uninstalls Everything Claude Code workflows from a Trae project.
#
# Usage:
# ./uninstall.sh # Uninstall from current directory
# ./uninstall.sh ~ # Uninstall globally from ~/.trae/
#
# Environment:
# TRAE_ENV=cn # Force use .trae-cn directory
#
set -euo pipefail
# Resolve the directory where this script lives
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Get the trae directory name (.trae or .trae-cn)
get_trae_dir() {
# Check environment variable first
if [ "${TRAE_ENV:-}" = "cn" ]; then
echo ".trae-cn"
else
echo ".trae"
fi
}
resolve_path() {
python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$1"
}
is_valid_manifest_entry() {
local file_path="$1"
case "$file_path" in
""|/*|~*|*/../*|../*|*/..|..)
return 1
;;
esac
return 0
}
# Main uninstall function
do_uninstall() {
local target_dir="$PWD"
local trae_dir="$(get_trae_dir)"
# Check if ~ was specified (or expanded to $HOME)
if [ "$#" -ge 1 ]; then
if [ "$1" = "~" ] || [ "$1" = "$HOME" ]; then
target_dir="$HOME"
fi
fi
# Check if we're already inside a .trae or .trae-cn directory
local current_dir_name="$(basename "$target_dir")"
local trae_full_path
if [ "$current_dir_name" = ".trae" ] || [ "$current_dir_name" = ".trae-cn" ]; then
# Already inside the trae directory, use it directly
trae_full_path="$target_dir"
else
# Normal case: append trae_dir to target_dir
trae_full_path="$target_dir/$trae_dir"
fi
echo "ECC Trae Uninstaller"
echo "===================="
echo ""
echo "Target: $trae_full_path/"
echo ""
if [ ! -d "$trae_full_path" ]; then
echo "Error: $trae_dir directory not found at $target_dir"
exit 1
fi
trae_root_resolved="$(resolve_path "$trae_full_path")"
# Manifest file path
MANIFEST="$trae_full_path/.ecc-manifest"
if [ ! -f "$MANIFEST" ]; then
echo "Warning: No manifest file found (.ecc-manifest)"
echo ""
echo "This could mean:"
echo " 1. ECC was installed with an older version without manifest support"
echo " 2. The manifest file was manually deleted"
echo ""
read -p "Do you want to remove the entire $trae_dir directory? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Uninstall cancelled."
exit 0
fi
rm -rf "$trae_full_path"
echo "Uninstall complete!"
echo ""
echo "Removed: $trae_full_path/"
exit 0
fi
echo "Found manifest file - will only remove files installed by ECC"
echo ""
read -p "Are you sure you want to uninstall ECC from $trae_dir? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Uninstall cancelled."
exit 0
fi
# Counters
removed=0
skipped=0
# Read manifest and remove files
while IFS= read -r file_path; do
[ -z "$file_path" ] && continue
if ! is_valid_manifest_entry "$file_path"; then
echo "Skipped: $file_path (invalid manifest entry)"
skipped=$((skipped + 1))
continue
fi
full_path="$trae_full_path/$file_path"
resolved_full="$(resolve_path "$full_path")"
case "$resolved_full" in
"$trae_root_resolved"|"$trae_root_resolved"/*)
;;
*)
echo "Skipped: $file_path (invalid manifest entry)"
skipped=$((skipped + 1))
continue
;;
esac
if [ -f "$resolved_full" ]; then
rm -f "$resolved_full"
echo "Removed: $file_path"
removed=$((removed + 1))
elif [ -d "$resolved_full" ]; then
# Only remove directory if it's empty
if [ -z "$(ls -A "$resolved_full" 2>/dev/null)" ]; then
rmdir "$resolved_full" 2>/dev/null || true
if [ ! -d "$resolved_full" ]; then
echo "Removed: $file_path/"
removed=$((removed + 1))
fi
else
echo "Skipped: $file_path/ (not empty - contains user files)"
skipped=$((skipped + 1))
fi
else
skipped=$((skipped + 1))
fi
done < "$MANIFEST"
while IFS= read -r empty_dir; do
[ "$empty_dir" = "$trae_full_path" ] && continue
relative_dir="${empty_dir#$trae_full_path/}"
rmdir "$empty_dir" 2>/dev/null || true
if [ ! -d "$empty_dir" ]; then
echo "Removed: $relative_dir/"
removed=$((removed + 1))
fi
done < <(find "$trae_full_path" -depth -type d -empty 2>/dev/null | sort -r)
# Try to remove the main trae directory if it's empty
if [ -d "$trae_full_path" ] && [ -z "$(ls -A "$trae_full_path" 2>/dev/null)" ]; then
rmdir "$trae_full_path" 2>/dev/null || true
if [ ! -d "$trae_full_path" ]; then
echo "Removed: $trae_dir/"
removed=$((removed + 1))
fi
fi
echo ""
echo "Uninstall complete!"
echo ""
echo "Summary:"
echo " Removed: $removed items"
echo " Skipped: $skipped items (not found or user-modified)"
echo ""
if [ -d "$trae_full_path" ]; then
echo "Note: $trae_dir directory still exists (contains user-added files)"
fi
}
# Execute uninstall
do_uninstall "$@"

1
.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — Agent Instructions
This is a **production-ready AI coding plugin** providing 28 specialized agents, 125 skills, 60 commands, and automated hook workflows for software development.
This is a **production-ready AI coding plugin** providing 30 specialized agents, 135 skills, 60 commands, and automated hook workflows for software development.
**Version:** 1.9.0
@@ -141,8 +141,8 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
## Project Structure
```
agents/ — 28 specialized subagents
skills/ — 117 workflow skills and domain knowledge
agents/ — 30 specialized subagents
skills/ — 135 workflow skills and domain knowledge
commands/ — 60 slash commands
hooks/ — Trigger-based automations
rules/ — Always-follow guidelines (common + per-language)

View File

@@ -59,3 +59,14 @@ Follow the formats in CONTRIBUTING.md:
- Hooks: JSON with matcher and hooks array
File naming: lowercase with hyphens (e.g., `python-reviewer.md`, `tdd-workflow.md`)
## Skills
Use the following skills when working on related files:
| File(s) | Skill |
|---------|-------|
| `README.md` | `/readme` |
| `.github/workflows/*.yml` | `/ci-workflow` |
When spawning subagents, always pass conventions from the respective skill into the agent's prompt.

159
COMMANDS-QUICK-REF.md Normal file
View File

@@ -0,0 +1,159 @@
# Commands Quick Reference
> 59 slash commands installed globally. Type `/` in any Claude Code session to invoke.
---
## Core Workflow
| Command | What it does |
|---------|-------------|
| `/plan` | Restate requirements, assess risks, write step-by-step implementation plan — **waits for your confirm before touching code** |
| `/tdd` | Enforce test-driven development: scaffold interface → write failing test → implement → verify 80%+ coverage |
| `/code-review` | Full code quality, security, and maintainability review of changed files |
| `/build-fix` | Detect and fix build errors — delegates to the right build-resolver agent automatically |
| `/verify` | Run the full verification loop: build → lint → test → type-check |
| `/quality-gate` | Quality gate check against project standards |
---
## Testing
| Command | What it does |
|---------|-------------|
| `/tdd` | Universal TDD workflow (any language) |
| `/e2e` | Generate + run Playwright end-to-end tests, capture screenshots/videos/traces |
| `/test-coverage` | Report test coverage, identify gaps |
| `/go-test` | TDD workflow for Go (table-driven, 80%+ coverage with `go test -cover`) |
| `/kotlin-test` | TDD for Kotlin (Kotest + Kover) |
| `/rust-test` | TDD for Rust (cargo test, integration tests) |
| `/cpp-test` | TDD for C++ (GoogleTest + gcov/lcov) |
---
## Code Review
| Command | What it does |
|---------|-------------|
| `/code-review` | Universal code review |
| `/python-review` | Python — PEP 8, type hints, security, idiomatic patterns |
| `/go-review` | Go — idiomatic patterns, concurrency safety, error handling |
| `/kotlin-review` | Kotlin — null safety, coroutine safety, clean architecture |
| `/rust-review` | Rust — ownership, lifetimes, unsafe usage |
| `/cpp-review` | C++ — memory safety, modern idioms, concurrency |
---
## Build Fixers
| Command | What it does |
|---------|-------------|
| `/build-fix` | Auto-detect language and fix build errors |
| `/go-build` | Fix Go build errors and `go vet` warnings |
| `/kotlin-build` | Fix Kotlin/Gradle compiler errors |
| `/rust-build` | Fix Rust build + borrow checker issues |
| `/cpp-build` | Fix C++ CMake and linker problems |
| `/gradle-build` | Fix Gradle errors for Android / KMP |
---
## Planning & Architecture
| Command | What it does |
|---------|-------------|
| `/plan` | Implementation plan with risk assessment |
| `/multi-plan` | Multi-model collaborative planning |
| `/multi-workflow` | Multi-model collaborative development |
| `/multi-backend` | Backend-focused multi-model development |
| `/multi-frontend` | Frontend-focused multi-model development |
| `/multi-execute` | Multi-model collaborative execution |
| `/orchestrate` | Guide for tmux/worktree multi-agent orchestration |
| `/devfleet` | Orchestrate parallel Claude Code agents via DevFleet |
---
## Session Management
| Command | What it does |
|---------|-------------|
| `/save-session` | Save current session state to `~/.claude/session-data/` |
| `/resume-session` | Load the most recent saved session from the canonical session store and resume from where you left off |
| `/sessions` | Browse, search, and manage session history with aliases from `~/.claude/session-data/` (with legacy reads from `~/.claude/sessions/`) |
| `/checkpoint` | Mark a checkpoint in the current session |
| `/aside` | Answer a quick side question without losing current task context |
| `/context-budget` | Analyse context window usage — find token overhead, optimise |
---
## Learning & Improvement
| Command | What it does |
|---------|-------------|
| `/learn` | Extract reusable patterns from the current session |
| `/learn-eval` | Extract patterns + self-evaluate quality before saving |
| `/evolve` | Analyse learned instincts, suggest evolved skill structures |
| `/promote` | Promote project-scoped instincts to global scope |
| `/instinct-status` | Show all learned instincts (project + global) with confidence scores |
| `/instinct-export` | Export instincts to a file |
| `/instinct-import` | Import instincts from a file or URL |
| `/skill-create` | Analyse local git history → generate a reusable skill |
| `/skill-health` | Skill portfolio health dashboard with analytics |
| `/rules-distill` | Scan skills, extract cross-cutting principles, distill into rules |
---
## Refactoring & Cleanup
| Command | What it does |
|---------|-------------|
| `/refactor-clean` | Remove dead code, consolidate duplicates, clean up structure |
| `/prompt-optimize` | Analyse a draft prompt and output an optimised ECC-enriched version |
---
## Docs & Research
| Command | What it does |
|---------|-------------|
| `/docs` | Look up current library/API documentation via Context7 |
| `/update-docs` | Update project documentation |
| `/update-codemaps` | Regenerate codemaps for the codebase |
---
## Loops & Automation
| Command | What it does |
|---------|-------------|
| `/loop-start` | Start a recurring agent loop on an interval |
| `/loop-status` | Check status of running loops |
| `/claw` | Start NanoClaw v2 — persistent REPL with model routing, skill hot-load, branching, and metrics |
---
## Project & Infrastructure
| Command | What it does |
|---------|-------------|
| `/projects` | List known projects and their instinct statistics |
| `/harness-audit` | Audit the agent harness configuration for reliability and cost |
| `/eval` | Run the evaluation harness |
| `/model-route` | Route a task to the right model (Haiku / Sonnet / Opus) |
| `/pm2` | PM2 process manager initialisation |
| `/setup-pm` | Configure package manager (npm / pnpm / yarn / bun) |
---
## Quick Decision Guide
```
Starting a new feature? → /plan first, then /tdd
Code just written? → /code-review
Build broken? → /build-fix
Need live docs? → /docs <library>
Session about to end? → /save-session or /learn-eval
Resuming next day? → /resume-session
Context getting heavy? → /context-budget then /checkpoint
Want to extract what you learned? → /learn-eval then /evolve
Running repeated tasks? → /loop-start
```

View File

@@ -73,6 +73,13 @@ git add . && git commit -m "feat: add my-skill" && git push -u origin feat/my-co
Skills are knowledge modules that Claude Code loads based on context.
> **📚 Comprehensive Guide:** For detailed guidance on creating effective skills, see [Skill Development Guide](docs/SKILL-DEVELOPMENT-GUIDE.md). It covers:
> - Skill architecture and categories
> - Writing effective content with examples
> - Best practices and common patterns
> - Testing and validation
> - Complete examples gallery
### Directory Structure
```
@@ -86,7 +93,7 @@ skills/
```markdown
---
name: your-skill-name
description: Brief description shown in skill list
description: Brief description shown in skill list and used for auto-activation
origin: ECC
---
@@ -94,6 +101,10 @@ origin: ECC
Brief overview of what this skill covers.
## When to Activate
Describe scenarios where Claude should use this skill. This is critical for auto-activation.
## Core Concepts
Explain key patterns and guidelines.
@@ -107,33 +118,54 @@ function example() {
}
\`\`\`
## Anti-Patterns
Show what NOT to do with examples.
## Best Practices
- Actionable guidelines
- Do's and don'ts
- Common pitfalls to avoid
## When to Use
## Related Skills
Describe scenarios where this skill applies.
Link to complementary skills (e.g., `related-skill-1`, `related-skill-2`).
```
### Skill Categories
| Category | Purpose | Examples |
|----------|---------|----------|
| **Language Standards** | Idioms, conventions, best practices | `python-patterns`, `golang-patterns` |
| **Framework Patterns** | Framework-specific guidance | `django-patterns`, `nextjs-patterns` |
| **Workflow** | Step-by-step processes | `tdd-workflow`, `refactoring-workflow` |
| **Domain Knowledge** | Specialized domains | `security-review`, `api-design` |
| **Tool Integration** | Tool/library usage | `docker-patterns`, `supabase-patterns` |
| **Template** | Project-specific skill templates | `project-guidelines-example` |
### Skill Checklist
- [ ] Focused on one domain/technology
- [ ] Includes practical code examples
- [ ] Under 500 lines
- [ ] Focused on one domain/technology (not too broad)
- [ ] Includes "When to Activate" section for auto-activation
- [ ] Includes practical, copy-pasteable code examples
- [ ] Shows anti-patterns (what NOT to do)
- [ ] Under 500 lines (800 max)
- [ ] Uses clear section headers
- [ ] Tested with Claude Code
- [ ] Links to related skills
- [ ] No sensitive data (API keys, tokens, paths)
### Example Skills
| Skill | Purpose |
|-------|---------|
| `coding-standards/` | TypeScript/JavaScript patterns |
| `frontend-patterns/` | React and Next.js best practices |
| `backend-patterns/` | API and database patterns |
| `security-review/` | Security checklist |
| Skill | Category | Purpose |
|-------|----------|---------|
| `coding-standards/` | Language Standards | TypeScript/JavaScript patterns |
| `frontend-patterns/` | Framework Patterns | React and Next.js best practices |
| `backend-patterns/` | Framework Patterns | API and database patterns |
| `security-review/` | Domain Knowledge | Security checklist |
| `tdd-workflow/` | Workflow | Test-driven development process |
| `project-guidelines-example/` | Template | Project-specific skill template |
---

122
EVALUATION.md Normal file
View File

@@ -0,0 +1,122 @@
# Repo Evaluation vs Current Setup
**Date:** 2026-03-21
**Branch:** `claude/evaluate-repo-comparison-ASZ9Y`
---
## Current Setup (`~/.claude/`)
The active Claude Code installation is near-minimal:
| Component | Current |
|-----------|---------|
| Agents | 0 |
| Skills | 0 installed |
| Commands | 0 |
| Hooks | 1 (Stop: git check) |
| Rules | 0 |
| MCP configs | 0 |
**Installed hooks:**
- `Stop``stop-hook-git-check.sh` — blocks session end if there are uncommitted changes or unpushed commits
**Installed permissions:**
- `Skill` — allows skill invocations
**Plugins:** Only `blocklist.json` (no active plugins installed)
---
## This Repo (`everything-claude-code` v1.9.0)
| Component | Repo |
|-----------|------|
| Agents | 28 |
| Skills | 116 |
| Commands | 59 |
| Rules sets | 12 languages + common (60+ rule files) |
| Hooks | Comprehensive system (PreToolUse, PostToolUse, SessionStart, Stop) |
| MCP configs | 1 (Context7 + others) |
| Schemas | 9 JSON validators |
| Scripts/CLI | 46+ Node.js modules + multiple CLIs |
| Tests | 58 test files |
| Install profiles | core, developer, security, research, full |
| Supported harnesses | Claude Code, Codex, Cursor, OpenCode |
---
## Gap Analysis
### Hooks
- **Current:** 1 Stop hook (git hygiene check)
- **Repo:** Full hook matrix covering:
- Dangerous command blocking (`rm -rf`, force pushes)
- Auto-formatting on file edits
- Dev server tmux enforcement
- Cost tracking
- Session evaluation and governance capture
- MCP health monitoring
### Agents (28 missing)
The repo provides specialized agents for every major workflow:
- Language reviewers: TypeScript, Python, Go, Java, Kotlin, Rust, C++, Flutter
- Build resolvers: Go, Java, Kotlin, Rust, C++, PyTorch
- Workflow agents: planner, tdd-guide, code-reviewer, security-reviewer, architect
- Automation: loop-operator, doc-updater, refactor-cleaner, harness-optimizer
### Skills (116 missing)
Domain knowledge modules covering:
- Language patterns (Python, Go, Kotlin, Rust, C++, Java, Swift, Perl, Laravel, Django)
- Testing strategies (TDD, E2E, coverage)
- Architecture patterns (backend, frontend, API design, database migrations)
- AI/ML workflows (Claude API, eval harness, agent loops, cost-aware pipelines)
- Business workflows (investor materials, market research, content engine)
### Commands (59 missing)
- `/tdd`, `/plan`, `/e2e`, `/code-review` — core dev workflows
- `/sessions`, `/save-session`, `/resume-session` — session persistence
- `/orchestrate`, `/multi-plan`, `/multi-execute` — multi-agent coordination
- `/learn`, `/skill-create`, `/evolve` — continuous improvement
- `/build-fix`, `/verify`, `/quality-gate` — build/quality automation
### Rules (60+ files missing)
Language-specific coding style, patterns, testing, and security guidelines for:
TypeScript, Python, Go, Java, Kotlin, Rust, C++, C#, Swift, Perl, PHP, and common/cross-language rules.
---
## Recommendations
### Immediate value (core install)
Run `ecc install --profile core` to get:
- Core agents (code-reviewer, planner, tdd-guide, security-reviewer)
- Essential skills (tdd-workflow, coding-standards, security-review)
- Key commands (/tdd, /plan, /code-review, /build-fix)
### Full install
Run `ecc install --profile full` to get all 28 agents, 116 skills, and 59 commands.
### Hooks upgrade
The current Stop hook is solid. The repo's `hooks.json` adds:
- Dangerous command blocking (safety)
- Auto-formatting (quality)
- Cost tracking (observability)
- Session evaluation (learning)
### Rules
Adding language rules (e.g., TypeScript, Python) provides always-on coding guidelines without relying on per-session prompts.
---
## What the Current Setup Does Well
- The `stop-hook-git-check.sh` Stop hook is production-quality and already enforces good git hygiene
- The `Skill` permission is correctly configured
- The setup is clean with no conflicts or cruft
---
## Summary
The current setup is essentially a blank slate with one well-implemented git hygiene hook. This repo provides a complete, production-tested enhancement layer covering agents, skills, commands, hooks, and rules — with a selective install system so you can add exactly what you need without bloating the configuration.

View File

@@ -1,6 +1,4 @@
**Language:** English | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md)
[Türkçe](docs/tr/README.md)
**Language:** English | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md) | [Türkçe](docs/tr/README.md)
# Everything Claude Code
@@ -180,6 +178,11 @@ cd everything-claude-code
npm install # or: pnpm install | yarn install | bun install
# macOS/Linux
# Recommended: install everything (full profile)
./install.sh --profile full
# Or install for specific languages only
./install.sh typescript # or python or golang or swift or php
# ./install.sh typescript python golang swift php
# ./install.sh --target cursor typescript
@@ -188,6 +191,11 @@ npm install # or: pnpm install | yarn install | bun install
```powershell
# Windows PowerShell
# Recommended: install everything (full profile)
.\install.ps1 --profile full
# Or install for specific languages only
.\install.ps1 typescript # or python or golang or swift or php
# .\install.ps1 typescript python golang swift php
# .\install.ps1 --target cursor typescript
@@ -197,7 +205,7 @@ npm install # or: pnpm install | yarn install | bun install
npx ecc-install typescript
```
For manual install instructions see the README in the `rules/` folder.
For manual install instructions see the README in the `rules/` folder. When copying rules manually, copy the whole language directory (for example `rules/common` or `rules/golang`), not the files inside it, so relative references keep working and filenames do not collide.
### Step 3: Start Using
@@ -212,7 +220,21 @@ For manual install instructions see the README in the `rules/` folder.
/plugin list everything-claude-code@everything-claude-code
```
**That's it!** You now have access to 28 agents, 125 skills, and 60 commands.
**That's it!** You now have access to 30 agents, 135 skills, and 60 commands.
### Multi-model commands require additional setup
> ⚠️ `multi-*` commands are **not** covered by the base plugin/rules install above.
>
> To use `/multi-plan`, `/multi-execute`, `/multi-backend`, `/multi-frontend`, and `/multi-workflow`, you must also install the `ccg-workflow` runtime.
>
> Initialize it with `npx ccg-workflow`.
>
> That runtime provides the external dependencies these commands expect, including:
> - `~/.claude/bin/codeagent-wrapper`
> - `~/.claude/.ccg/prompts/*`
>
> Without `ccg-workflow`, these `multi-*` commands will not run correctly.
---
@@ -273,7 +295,7 @@ everything-claude-code/
| |-- plugin.json # Plugin metadata and component paths
| |-- marketplace.json # Marketplace catalog for /plugin marketplace add
|
|-- agents/ # 28 specialized subagents for delegation
|-- agents/ # 30 specialized subagents for delegation
| |-- planner.md # Feature implementation planning
| |-- architect.md # System design decisions
| |-- tdd-guide.md # Test-driven development
@@ -614,16 +636,16 @@ This gives you instant access to all commands, agents, skills, and hooks.
>
> # Option A: User-level rules (applies to all projects)
> mkdir -p ~/.claude/rules
> cp -r everything-claude-code/rules/common/* ~/.claude/rules/
> cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # pick your stack
> cp -r everything-claude-code/rules/python/* ~/.claude/rules/
> cp -r everything-claude-code/rules/golang/* ~/.claude/rules/
> cp -r everything-claude-code/rules/php/* ~/.claude/rules/
> cp -r everything-claude-code/rules/common ~/.claude/rules/
> cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # pick your stack
> cp -r everything-claude-code/rules/python ~/.claude/rules/
> cp -r everything-claude-code/rules/golang ~/.claude/rules/
> cp -r everything-claude-code/rules/php ~/.claude/rules/
>
> # Option B: Project-level rules (applies to current project only)
> mkdir -p .claude/rules
> cp -r everything-claude-code/rules/common/* .claude/rules/
> cp -r everything-claude-code/rules/typescript/* .claude/rules/ # pick your stack
> cp -r everything-claude-code/rules/common .claude/rules/
> cp -r everything-claude-code/rules/typescript .claude/rules/ # pick your stack
> ```
---
@@ -639,12 +661,13 @@ git clone https://github.com/affaan-m/everything-claude-code.git
# Copy agents to your Claude config
cp everything-claude-code/agents/*.md ~/.claude/agents/
# Copy rules (common + language-specific)
cp -r everything-claude-code/rules/common/* ~/.claude/rules/
cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # pick your stack
cp -r everything-claude-code/rules/python/* ~/.claude/rules/
cp -r everything-claude-code/rules/golang/* ~/.claude/rules/
cp -r everything-claude-code/rules/php/* ~/.claude/rules/
# Copy rules directories (common + language-specific)
mkdir -p ~/.claude/rules
cp -r everything-claude-code/rules/common ~/.claude/rules/
cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # pick your stack
cp -r everything-claude-code/rules/python ~/.claude/rules/
cp -r everything-claude-code/rules/golang ~/.claude/rules/
cp -r everything-claude-code/rules/php ~/.claude/rules/
# Copy commands
cp everything-claude-code/commands/*.md ~/.claude/commands/
@@ -850,7 +873,8 @@ Yes. Use Option 2 (manual installation) and copy only what you need:
cp everything-claude-code/agents/*.md ~/.claude/agents/
# Just rules
cp -r everything-claude-code/rules/common/* ~/.claude/rules/
mkdir -p ~/.claude/rules/
cp -r everything-claude-code/rules/common ~/.claude/rules/
```
Each component is fully independent.
@@ -999,6 +1023,8 @@ cp .codex/config.toml ~/.codex/config.toml
The sync script safely merges ECC MCP servers into your existing `~/.codex/config.toml` using an **add-only** strategy — it never removes or modifies your existing servers. Run with `--dry-run` to preview changes, or `--update-mcp` to force-refresh ECC servers to the latest recommended config.
For Context7, ECC uses the canonical Codex section name `[mcp_servers.context7]` while still launching the `@upstash/context7-mcp` package. If you already have a legacy `[mcp_servers.context7-mcp]` entry, `--update-mcp` migrates it to the canonical section name.
Codex macOS app:
- Open this repository as your workspace.
- The root `AGENTS.md` is auto-detected.
@@ -1083,9 +1109,9 @@ The configuration is automatically detected from `.opencode/opencode.json`.
| Feature | Claude Code | OpenCode | Status |
|---------|-------------|----------|--------|
| Agents | ✅ 28 agents | ✅ 12 agents | **Claude Code leads** |
| Agents | ✅ 30 agents | ✅ 12 agents | **Claude Code leads** |
| Commands | ✅ 60 commands | ✅ 31 commands | **Claude Code leads** |
| Skills | ✅ 125 skills | ✅ 37 skills | **Claude Code leads** |
| Skills | ✅ 135 skills | ✅ 37 skills | **Claude Code leads** |
| Hooks | ✅ 8 event types | ✅ 11 events | **OpenCode has more!** |
| Rules | ✅ 29 rules | ✅ 13 instructions | **Claude Code leads** |
| MCP Servers | ✅ 14 servers | ✅ Full | **Full parity** |

View File

@@ -82,14 +82,17 @@
# 首先克隆仓库
git clone https://github.com/affaan-m/everything-claude-code.git
# 复制规则(通用 + 语言特定)
cp -r everything-claude-code/rules/common/* ~/.claude/rules/
cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # 选择你的技术栈
cp -r everything-claude-code/rules/python/* ~/.claude/rules/
cp -r everything-claude-code/rules/golang/* ~/.claude/rules/
cp -r everything-claude-code/rules/perl/* ~/.claude/rules/
# 复制规则目录(通用 + 语言特定)
mkdir -p ~/.claude/rules
cp -r everything-claude-code/rules/common ~/.claude/rules/
cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # 选择你的技术栈
cp -r everything-claude-code/rules/python ~/.claude/rules/
cp -r everything-claude-code/rules/golang ~/.claude/rules/
cp -r everything-claude-code/rules/perl ~/.claude/rules/
```
复制规则时,请复制整个目录(例如 `rules/common``rules/golang`),而不是复制目录内的文件;这样可以保留相对引用,并避免不同规则集中的同名文件互相覆盖。
### 第三步:开始使用
```bash
@@ -105,6 +108,20 @@ cp -r everything-claude-code/rules/perl/* ~/.claude/rules/
**完成!** 你现在可以使用 13 个代理、43 个技能和 31 个命令。
### multi-* 命令需要额外配置
> ⚠️ 上面的基础插件 / rules 安装**不包含** `multi-*` 命令所需的运行时。
>
> 如果要使用 `/multi-plan`、`/multi-execute`、`/multi-backend`、`/multi-frontend` 和 `/multi-workflow`,还需要额外安装 `ccg-workflow` 运行时。
>
> 可通过 `npx ccg-workflow` 完成初始化安装。
>
> 该运行时会提供这些命令依赖的关键组件,包括:
> - `~/.claude/bin/codeagent-wrapper`
> - `~/.claude/.ccg/prompts/*`
>
> 未安装 `ccg-workflow` 时,这些 `multi-*` 命令将无法正常运行。
---
## 🌐 跨平台支持
@@ -352,11 +369,20 @@ everything-claude-code/
> git clone https://github.com/affaan-m/everything-claude-code.git
>
> # 选项 A用户级规则应用于所有项目
> cp -r everything-claude-code/rules/* ~/.claude/rules/
> mkdir -p ~/.claude/rules
> cp -r everything-claude-code/rules/common ~/.claude/rules/
> cp -r everything-claude-code/rules/typescript ~/.claude/rules/
> cp -r everything-claude-code/rules/python ~/.claude/rules/
> cp -r everything-claude-code/rules/golang ~/.claude/rules/
> cp -r everything-claude-code/rules/perl ~/.claude/rules/
>
> # 选项 B项目级规则仅应用于当前项目
> mkdir -p .claude/rules
> cp -r everything-claude-code/rules/* .claude/rules/
> cp -r everything-claude-code/rules/common .claude/rules/
> cp -r everything-claude-code/rules/typescript .claude/rules/
> cp -r everything-claude-code/rules/python .claude/rules/
> cp -r everything-claude-code/rules/golang .claude/rules/
> cp -r everything-claude-code/rules/perl .claude/rules/
> ```
---
@@ -372,12 +398,13 @@ git clone https://github.com/affaan-m/everything-claude-code.git
# 将代理复制到你的 Claude 配置
cp everything-claude-code/agents/*.md ~/.claude/agents/
# 复制规则(通用 + 语言特定)
cp -r everything-claude-code/rules/common/* ~/.claude/rules/
cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # 选择你的技术栈
cp -r everything-claude-code/rules/python/* ~/.claude/rules/
cp -r everything-claude-code/rules/golang/* ~/.claude/rules/
cp -r everything-claude-code/rules/perl/* ~/.claude/rules/
# 复制规则目录(通用 + 语言特定)
mkdir -p ~/.claude/rules
cp -r everything-claude-code/rules/common ~/.claude/rules/
cp -r everything-claude-code/rules/typescript ~/.claude/rules/ # 选择你的技术栈
cp -r everything-claude-code/rules/python ~/.claude/rules/
cp -r everything-claude-code/rules/golang ~/.claude/rules/
cp -r everything-claude-code/rules/perl ~/.claude/rules/
# 复制命令
cp everything-claude-code/commands/*.md ~/.claude/commands/

196
REPO-ASSESSMENT.md Normal file
View File

@@ -0,0 +1,196 @@
# Repo & Fork Assessment + Setup Recommendations
**Date:** 2026-03-21
---
## What's Available
### Repo: `Infiniteyieldai/everything-claude-code`
This is a **fork of `affaan-m/everything-claude-code`** (the upstream project with 50K+ stars, 6K+ forks).
| Attribute | Value |
|-----------|-------|
| Version | 1.9.0 (current) |
| Status | Clean fork — 1 commit ahead of upstream `main` (the EVALUATION.md doc added in this session) |
| Remote branches | `main`, `claude/evaluate-repo-comparison-ASZ9Y` |
| Upstream sync | Fully synced — last upstream commit merged was the zh-CN docs PR (#728) |
| License | MIT |
**This is the right repo to work from.** It's the latest upstream version with no divergence or merge conflicts.
---
### Current `~/.claude/` Installation
| Component | Installed | Available in Repo |
|-----------|-----------|-------------------|
| Agents | 0 | 28 |
| Skills | 0 | 116 |
| Commands | 0 | 59 |
| Rules | 0 | 60+ files (12 languages) |
| Hooks | 1 (git Stop check) | Full PreToolUse/PostToolUse matrix |
| MCP configs | 0 | 1 (Context7) |
The existing Stop hook (`stop-hook-git-check.sh`) is solid — blocks session end on uncommitted/unpushed work. Keep it.
---
## Install Profile Recommendations
The repo ships 5 install profiles. Choose based on your primary use case:
### Profile: `core` (Minimum viable setup)
> Fastest to install. Gets you commands, core agents, hooks runtime, and quality workflow.
**Best for:** Trying ECC out, minimal footprint, or a constrained environment.
```bash
node scripts/install-plan.js --profile core
node scripts/install-apply.js
```
**Installs:** rules-core, agents-core, commands-core, hooks-runtime, platform-configs, workflow-quality
---
### Profile: `developer` (Recommended for daily dev work)
> The default engineering profile for most ECC users.
**Best for:** General software development across app codebases.
```bash
node scripts/install-plan.js --profile developer
node scripts/install-apply.js
```
**Adds over core:** framework-language skills, database patterns, orchestration commands
---
### Profile: `security`
> Baseline runtime + security-specific agents and rules.
**Best for:** Security-focused workflows, code audits, vulnerability reviews.
---
### Profile: `research`
> Investigation, synthesis, and publishing workflows.
**Best for:** Content creation, investor materials, market research, cross-posting.
---
### Profile: `full`
> Everything — all 18 modules.
**Best for:** Power users who want the complete toolkit.
```bash
node scripts/install-plan.js --profile full
node scripts/install-apply.js
```
---
## Priority Additions (High Value, Low Risk)
Regardless of profile, these components add immediate value:
### 1. Core Agents (highest ROI)
| Agent | Why it matters |
|-------|----------------|
| `planner.md` | Breaks complex tasks into implementation plans |
| `code-reviewer.md` | Quality and maintainability review |
| `tdd-guide.md` | TDD workflow (RED→GREEN→IMPROVE) |
| `security-reviewer.md` | Vulnerability detection |
| `architect.md` | System design & scalability decisions |
### 2. Key Commands
| Command | Why it matters |
|---------|----------------|
| `/plan` | Implementation planning before coding |
| `/tdd` | Test-driven workflow |
| `/code-review` | On-demand review |
| `/build-fix` | Automated build error resolution |
| `/learn` | Extract patterns from current session |
### 3. Hook Upgrades (from `hooks/hooks.json`)
The repo's hook system adds these over the current single Stop hook:
| Hook | Trigger | Value |
|------|---------|-------|
| `block-no-verify` | PreToolUse: Bash | Blocks `--no-verify` git flag abuse |
| `pre-bash-git-push-reminder` | PreToolUse: Bash | Pre-push review reminder |
| `doc-file-warning` | PreToolUse: Write | Warns on non-standard doc files |
| `suggest-compact` | PreToolUse: Edit/Write | Suggests compaction at logical intervals |
| Continuous learning observer | PreToolUse: * | Captures tool use patterns for skill improvement |
### 4. Rules (Always-on guidelines)
The `rules/common/` directory provides baseline guidelines that fire on every session:
- `security.md` — Security guardrails
- `testing.md` — 80%+ coverage requirement
- `git-workflow.md` — Conventional commits, branch strategy
- `coding-style.md` — Cross-language style standards
---
## What to Do With the Fork
### Option A: Use as upstream tracker (current state)
Keep the fork synced with `affaan-m/everything-claude-code` upstream. Periodically merge upstream changes:
```bash
git fetch upstream
git merge upstream/main
```
Install from the local clone. This is clean and maintainable.
### Option B: Customize the fork
Add personal skills, agents, or commands to the fork. Good for:
- Business-specific domain skills (your vertical)
- Team-specific coding conventions
- Custom hooks for your stack
The fork already has the EVALUATION.md and REPO-ASSESSMENT.md docs — that's fine for a working fork.
### Option C: Install from npm (simplest for fresh machines)
```bash
npx ecc-universal install --profile developer
```
No need to clone the repo. This is the recommended install method for most users.
---
## Recommended Setup Steps
1. **Keep the existing Stop hook** — it's doing its job
2. **Run the developer profile install** from the local fork:
```bash
cd /path/to/everything-claude-code
node scripts/install-plan.js --profile developer
node scripts/install-apply.js
```
3. **Add language rules** for your primary stack (TypeScript, Python, Go, etc.):
```bash
node scripts/install-plan.js --add rules/typescript
node scripts/install-apply.js
```
4. **Enable MCP Context7** for live documentation lookup:
- Copy `mcp-configs/mcp-servers.json` into your project's `.claude/` dir
5. **Review hooks** — enable the `hooks/hooks.json` additions selectively, starting with `block-no-verify` and `pre-bash-git-push-reminder`
---
## Summary
| Question | Answer |
|----------|--------|
| Is the fork healthy? | Yes — fully synced with upstream v1.9.0 |
| Other forks to consider? | None visible in this environment; upstream `affaan-m/everything-claude-code` is the source of truth |
| Best install profile? | `developer` for day-to-day dev work |
| Biggest gap in current setup? | 0 agents installed — add at minimum: planner, code-reviewer, tdd-guide, security-reviewer |
| Quickest win? | Run `node scripts/install-plan.js --profile core && node scripts/install-apply.js` |

View File

@@ -0,0 +1,83 @@
---
name: healthcare-reviewer
description: Reviews healthcare application code for clinical safety, CDSS accuracy, PHI compliance, and medical data integrity. Specialized for EMR/EHR, clinical decision support, and health information systems.
tools: ["Read", "Grep", "Glob"]
model: opus
---
# Healthcare Reviewer — Clinical Safety & PHI Compliance
You are a clinical informatics reviewer for healthcare software. Patient safety is your top priority. You review code for clinical accuracy, data protection, and regulatory compliance.
## Your Responsibilities
1. **CDSS accuracy** — Verify drug interaction logic, dose validation rules, and clinical scoring implementations match published medical standards
2. **PHI/PII protection** — Scan for patient data exposure in logs, errors, responses, URLs, and client storage
3. **Clinical data integrity** — Ensure audit trails, locked records, and cascade protection
4. **Medical data correctness** — Verify ICD-10/SNOMED mappings, lab reference ranges, and drug database entries
5. **Integration compliance** — Validate HL7/FHIR message handling and error recovery
## Critical Checks
### CDSS Engine
- [ ] All drug interaction pairs produce correct alerts (both directions)
- [ ] Dose validation rules fire on out-of-range values
- [ ] Clinical scoring matches published specification (NEWS2 = Royal College of Physicians, qSOFA = Sepsis-3)
- [ ] No false negatives (missed interaction = patient safety event)
- [ ] Malformed inputs produce errors, NOT silent passes
### PHI Protection
- [ ] No patient data in `console.log`, `console.error`, or error messages
- [ ] No PHI in URL parameters or query strings
- [ ] No PHI in browser localStorage/sessionStorage
- [ ] No `service_role` key in client-side code
- [ ] RLS enabled on all tables with patient data
- [ ] Cross-facility data isolation verified
### Clinical Workflow
- [ ] Encounter lock prevents edits (addendum only)
- [ ] Audit trail entry on every create/read/update/delete of clinical data
- [ ] Critical alerts are non-dismissable (not toast notifications)
- [ ] Override reasons logged when clinician proceeds past critical alert
- [ ] Red flag symptoms trigger visible alerts
### Data Integrity
- [ ] No CASCADE DELETE on patient records
- [ ] Concurrent edit detection (optimistic locking or conflict resolution)
- [ ] No orphaned records across clinical tables
- [ ] Timestamps use consistent timezone
## Output Format
```
## Healthcare Review: [module/feature]
### Patient Safety Impact: [CRITICAL / HIGH / MEDIUM / LOW / NONE]
### Clinical Accuracy
- CDSS: [checks passed/failed]
- Drug DB: [verified/issues]
- Scoring: [matches spec/deviates]
### PHI Compliance
- Exposure vectors checked: [list]
- Issues found: [list or none]
### Issues
1. [PATIENT SAFETY / CLINICAL / PHI / TECHNICAL] Description
- Impact: [potential harm or exposure]
- Fix: [required change]
### Verdict: [SAFE TO DEPLOY / NEEDS FIXES / BLOCK — PATIENT SAFETY RISK]
```
## Rules
- When in doubt about clinical accuracy, flag as NEEDS REVIEW — never approve uncertain clinical logic
- A single missed drug interaction is worse than a hundred false alarms
- PHI exposure is always CRITICAL severity, regardless of how small the leak
- Never approve code that silently catches CDSS errors

View File

@@ -0,0 +1,446 @@
---
name: performance-optimizer
description: Performance analysis and optimization specialist. Use PROACTIVELY for identifying bottlenecks, optimizing slow code, reducing bundle sizes, and improving runtime performance. Profiling, memory leaks, render optimization, and algorithmic improvements.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: sonnet
---
# Performance Optimizer
You are an expert performance specialist focused on identifying bottlenecks and optimizing application speed, memory usage, and efficiency. Your mission is to make code faster, lighter, and more responsive.
## Core Responsibilities
1. **Performance Profiling** — Identify slow code paths, memory leaks, and bottlenecks
2. **Bundle Optimization** — Reduce JavaScript bundle sizes, lazy loading, code splitting
3. **Runtime Optimization** — Improve algorithmic efficiency, reduce unnecessary computations
4. **React/Rendering Optimization** — Prevent unnecessary re-renders, optimize component trees
5. **Database & Network** — Optimize queries, reduce API calls, implement caching
6. **Memory Management** — Detect leaks, optimize memory usage, cleanup resources
## Analysis Commands
```bash
# Bundle analysis
npx bundle-analyzer
npx source-map-explorer build/static/js/*.js
# Lighthouse performance audit
npx lighthouse https://your-app.com --view
# Node.js profiling
node --prof your-app.js
node --prof-process isolate-*.log
# Memory analysis
node --inspect your-app.js # Then use Chrome DevTools
# React profiling (in browser)
# React DevTools > Profiler tab
# Network analysis
npx webpack-bundle-analyzer
```
## Performance Review Workflow
### 1. Identify Performance Issues
**Critical Performance Indicators:**
| Metric | Target | Action if Exceeded |
|--------|--------|-------------------|
| First Contentful Paint | < 1.8s | Optimize critical path, inline critical CSS |
| Largest Contentful Paint | < 2.5s | Lazy load images, optimize server response |
| Time to Interactive | < 3.8s | Code splitting, reduce JavaScript |
| Cumulative Layout Shift | < 0.1 | Reserve space for images, avoid layout thrashing |
| Total Blocking Time | < 200ms | Break up long tasks, use web workers |
| Bundle Size (gzipped) | < 200KB | Tree shaking, lazy loading, code splitting |
### 2. Algorithmic Analysis
Check for inefficient algorithms:
| Pattern | Complexity | Better Alternative |
|---------|------------|-------------------|
| Nested loops on same data | O(n²) | Use Map/Set for O(1) lookups |
| Repeated array searches | O(n) per search | Convert to Map for O(1) |
| Sorting inside loop | O(n² log n) | Sort once outside loop |
| String concatenation in loop | O(n²) | Use array.join() |
| Deep cloning large objects | O(n) each time | Use shallow copy or immer |
| Recursion without memoization | O(2^n) | Add memoization |
```typescript
// BAD: O(n²) - searching array in loop
for (const user of users) {
const posts = allPosts.filter(p => p.userId === user.id); // O(n) per user
}
// GOOD: O(n) - group once with Map
const postsByUser = new Map<number, Post[]>();
for (const post of allPosts) {
const userPosts = postsByUser.get(post.userId) || [];
userPosts.push(post);
postsByUser.set(post.userId, userPosts);
}
// Now O(1) lookup per user
```
### 3. React Performance Optimization
**Common React Anti-patterns:**
```tsx
// BAD: Inline function creation in render
<Button onClick={() => handleClick(id)}>Submit</Button>
// GOOD: Stable callback with useCallback
const handleButtonClick = useCallback(() => handleClick(id), [handleClick, id]);
<Button onClick={handleButtonClick}>Submit</Button>
// BAD: Object creation in render
<Child style={{ color: 'red' }} />
// GOOD: Stable object reference
const style = useMemo(() => ({ color: 'red' }), []);
<Child style={style} />
// BAD: Expensive computation on every render
const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name));
// GOOD: Memoize expensive computations
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// BAD: List without keys or with index
{items.map((item, index) => <Item key={index} />)}
// GOOD: Stable unique keys
{items.map(item => <Item key={item.id} item={item} />)}
```
**React Performance Checklist:**
- [ ] `useMemo` for expensive computations
- [ ] `useCallback` for functions passed to children
- [ ] `React.memo` for frequently re-rendered components
- [ ] Proper dependency arrays in hooks
- [ ] Virtualization for long lists (react-window, react-virtualized)
- [ ] Lazy loading for heavy components (`React.lazy`)
- [ ] Code splitting at route level
### 4. Bundle Size Optimization
**Bundle Analysis Checklist:**
```bash
# Analyze bundle composition
npx webpack-bundle-analyzer build/static/js/*.js
# Check for duplicate dependencies
npx duplicate-package-checker-analyzer
# Find largest files
du -sh node_modules/* | sort -hr | head -20
```
**Optimization Strategies:**
| Issue | Solution |
|-------|----------|
| Large vendor bundle | Tree shaking, smaller alternatives |
| Duplicate code | Extract to shared module |
| Unused exports | Remove dead code with knip |
| Moment.js | Use date-fns or dayjs (smaller) |
| Lodash | Use lodash-es or native methods |
| Large icons library | Import only needed icons |
```javascript
// BAD: Import entire library
import _ from 'lodash';
import moment from 'moment';
// GOOD: Import only what you need
import debounce from 'lodash/debounce';
import { format, addDays } from 'date-fns';
// Or use lodash-es with tree shaking
import { debounce, throttle } from 'lodash-es';
```
### 5. Database & Query Optimization
**Query Optimization Patterns:**
```sql
-- BAD: Select all columns
SELECT * FROM users WHERE active = true;
-- GOOD: Select only needed columns
SELECT id, name, email FROM users WHERE active = true;
-- BAD: N+1 queries (in application loop)
-- 1 query for users, then N queries for each user's orders
-- GOOD: Single query with JOIN or batch fetch
SELECT u.*, o.id as order_id, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.active = true;
-- Add index for frequently queried columns
CREATE INDEX idx_users_active ON users(active);
CREATE INDEX idx_orders_user_id ON orders(user_id);
```
**Database Performance Checklist:**
- [ ] Indexes on frequently queried columns
- [ ] Composite indexes for multi-column queries
- [ ] Avoid SELECT * in production code
- [ ] Use connection pooling
- [ ] Implement query result caching
- [ ] Use pagination for large result sets
- [ ] Monitor slow query logs
### 6. Network & API Optimization
**Network Optimization Strategies:**
```typescript
// BAD: Multiple sequential requests
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
// GOOD: Parallel requests when independent
const [user, posts] = await Promise.all([
fetchUser(id),
fetchPosts(id)
]);
// GOOD: Batch requests when possible
const results = await batchFetch(['user1', 'user2', 'user3']);
// Implement request caching
const fetchWithCache = async (url: string, ttl = 300000) => {
const cached = cache.get(url);
if (cached) return cached;
const data = await fetch(url).then(r => r.json());
cache.set(url, data, ttl);
return data;
};
// Debounce rapid API calls
const debouncedSearch = debounce(async (query: string) => {
const results = await searchAPI(query);
setResults(results);
}, 300);
```
**Network Optimization Checklist:**
- [ ] Parallel independent requests with `Promise.all`
- [ ] Implement request caching
- [ ] Debounce rapid-fire requests
- [ ] Use streaming for large responses
- [ ] Implement pagination for large datasets
- [ ] Use GraphQL or API batching to reduce requests
- [ ] Enable compression (gzip/brotli) on server
### 7. Memory Leak Detection
**Common Memory Leak Patterns:**
```typescript
// BAD: Event listener without cleanup
useEffect(() => {
window.addEventListener('resize', handleResize);
// Missing cleanup!
}, []);
// GOOD: Clean up event listeners
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// BAD: Timer without cleanup
useEffect(() => {
setInterval(() => pollData(), 1000);
// Missing cleanup!
}, []);
// GOOD: Clean up timers
useEffect(() => {
const interval = setInterval(() => pollData(), 1000);
return () => clearInterval(interval);
}, []);
// BAD: Holding references in closures
const Component = () => {
const largeData = useLargeData();
useEffect(() => {
eventEmitter.on('update', () => {
console.log(largeData); // Closure keeps reference
});
}, [largeData]);
};
// GOOD: Use refs or proper dependencies
const largeDataRef = useRef(largeData);
useEffect(() => {
largeDataRef.current = largeData;
}, [largeData]);
useEffect(() => {
const handleUpdate = () => {
console.log(largeDataRef.current);
};
eventEmitter.on('update', handleUpdate);
return () => eventEmitter.off('update', handleUpdate);
}, []);
```
**Memory Leak Detection:**
```bash
# Chrome DevTools Memory tab:
# 1. Take heap snapshot
# 2. Perform action
# 3. Take another snapshot
# 4. Compare to find objects that shouldn't exist
# 5. Look for detached DOM nodes, event listeners, closures
# Node.js memory debugging
node --inspect app.js
# Open chrome://inspect
# Take heap snapshots and compare
```
## Performance Testing
### Lighthouse Audits
```bash
# Run full lighthouse audit
npx lighthouse https://your-app.com --view --preset=desktop
# CI mode for automated checks
npx lighthouse https://your-app.com --output=json --output-path=./lighthouse.json
# Check specific metrics
npx lighthouse https://your-app.com --only-categories=performance
```
### Performance Budgets
```json
// package.json
{
"bundlesize": [
{
"path": "./build/static/js/*.js",
"maxSize": "200 kB"
}
]
}
```
### Web Vitals Monitoring
```typescript
// Track Core Web Vitals
import { getCLS, getFID, getLCP, getFCP, getTTFB } from 'web-vitals';
getCLS(console.log); // Cumulative Layout Shift
getFID(console.log); // First Input Delay
getLCP(console.log); // Largest Contentful Paint
getFCP(console.log); // First Contentful Paint
getTTFB(console.log); // Time to First Byte
```
## Performance Report Template
````markdown
# Performance Audit Report
## Executive Summary
- **Overall Score**: X/100
- **Critical Issues**: X
- **Recommendations**: X
## Bundle Analysis
| Metric | Current | Target | Status |
|--------|---------|--------|--------|
| Total Size (gzip) | XXX KB | < 200 KB | ⚠️ |
| Main Bundle | XXX KB | < 100 KB | ✅ |
| Vendor Bundle | XXX KB | < 150 KB | ⚠️ |
## Web Vitals
| Metric | Current | Target | Status |
|--------|---------|--------|--------|
| LCP | X.Xs | < 2.5s | ✅ |
| FID | XXms | < 100ms | ✅ |
| CLS | X.XX | < 0.1 | ⚠️ |
## Critical Issues
### 1. [Issue Title]
**File**: path/to/file.ts:42
**Impact**: High - Causes XXXms delay
**Fix**: [Description of fix]
```typescript
// Before (slow)
const slowCode = ...;
// After (optimized)
const fastCode = ...;
```
### 2. [Issue Title]
...
## Recommendations
1. [Priority recommendation]
2. [Priority recommendation]
3. [Priority recommendation]
## Estimated Impact
- Bundle size reduction: XX KB (XX%)
- LCP improvement: XXms
- Time to Interactive improvement: XXms
````
## When to Run
**ALWAYS:** Before major releases, after adding new features, when users report slowness, during performance regression testing.
**IMMEDIATELY:** Lighthouse score drops, bundle size increases >10%, memory usage grows, slow page loads.
## Red Flags - Act Immediately
| Issue | Action |
|-------|--------|
| Bundle > 500KB gzip | Code split, lazy load, tree shake |
| LCP > 4s | Optimize critical path, preload resources |
| Memory usage growing | Check for leaks, review useEffect cleanup |
| CPU spikes | Profile with Chrome DevTools |
| Database query > 1s | Add index, optimize query, cache results |
## Success Metrics
- Lighthouse performance score > 90
- All Core Web Vitals in "good" range
- Bundle size under budget
- No memory leaks detected
- Test suite still passing
- No performance regressions
---
**Remember**: Performance is a feature. Users notice speed. Every 100ms of improvement matters. Optimize for the 90th percentile, not the average.

View File

@@ -1,5 +1,5 @@
---
description: Load the most recent session file from ~/.claude/sessions/ and resume work with full context from where the last session ended.
description: Load the most recent session file from ~/.claude/session-data/ and resume work with full context from where the last session ended.
---
# Resume Session Command
@@ -17,10 +17,10 @@ This command is the counterpart to `/save-session`.
## Usage
```
/resume-session # loads most recent file in ~/.claude/sessions/
/resume-session # loads most recent file in ~/.claude/session-data/
/resume-session 2024-01-15 # loads most recent session for that date
/resume-session ~/.claude/sessions/2024-01-15-session.tmp # loads a specific legacy-format file
/resume-session ~/.claude/sessions/2024-01-15-abc123de-session.tmp # loads a current short-id session file
/resume-session ~/.claude/session-data/2024-01-15-abc123de-session.tmp # loads a current short-id session file
/resume-session ~/.claude/sessions/2024-01-15-session.tmp # loads a specific legacy-format file
```
## Process
@@ -29,19 +29,20 @@ This command is the counterpart to `/save-session`.
If no argument provided:
1. Check `~/.claude/sessions/`
1. Check `~/.claude/session-data/`
2. Pick the most recently modified `*-session.tmp` file
3. If the folder does not exist or has no matching files, tell the user:
```
No session files found in ~/.claude/sessions/
No session files found in ~/.claude/session-data/
Run /save-session at the end of a session to create one.
```
Then stop.
If an argument is provided:
- If it looks like a date (`YYYY-MM-DD`), search `~/.claude/sessions/` for files matching
`YYYY-MM-DD-session.tmp` (legacy format) or `YYYY-MM-DD-<shortid>-session.tmp` (current format)
- If it looks like a date (`YYYY-MM-DD`), search `~/.claude/session-data/` first, then the legacy
`~/.claude/sessions/`, for files matching `YYYY-MM-DD-session.tmp` (legacy format) or
`YYYY-MM-DD-<shortid>-session.tmp` (current format)
and load the most recently modified variant for that date
- If it looks like a file path, read that file directly
- If not found, report clearly and stop
@@ -114,7 +115,7 @@ Report: "Session file found but appears empty or unreadable. You may need to cre
## Example Output
```
SESSION LOADED: /Users/you/.claude/sessions/2024-01-15-abc123de-session.tmp
SESSION LOADED: /Users/you/.claude/session-data/2024-01-15-abc123de-session.tmp
════════════════════════════════════════════════
PROJECT: my-app — JWT Authentication

View File

@@ -1,5 +1,5 @@
---
description: Save current session state to a dated file in ~/.claude/sessions/ so work can be resumed in a future session with full context.
description: Save current session state to a dated file in ~/.claude/session-data/ so work can be resumed in a future session with full context.
---
# Save Session Command
@@ -29,19 +29,19 @@ Before writing the file, collect:
Create the canonical sessions folder in the user's Claude home directory:
```bash
mkdir -p ~/.claude/sessions
mkdir -p ~/.claude/session-data
```
### Step 3: Write the session file
Create `~/.claude/sessions/YYYY-MM-DD-<short-id>-session.tmp`, using today's actual date and a short-id that satisfies the rules enforced by `SESSION_FILENAME_REGEX` in `session-manager.js`:
Create `~/.claude/session-data/YYYY-MM-DD-<short-id>-session.tmp`, using today's actual date and a short-id that satisfies the rules enforced by `SESSION_FILENAME_REGEX` in `session-manager.js`:
- Allowed characters: lowercase `a-z`, digits `0-9`, hyphens `-`
- Minimum length: 8 characters
- No uppercase letters, no underscores, no spaces
- Compatibility characters: letters `a-z` / `A-Z`, digits `0-9`, hyphens `-`, underscores `_`
- Compatibility minimum length: 1 character
- Recommended style for new files: lowercase letters, digits, and hyphens with 8+ characters to avoid collisions
Valid examples: `abc123de`, `a1b2c3d4`, `frontend-worktree-1`
Invalid examples: `ABC123de` (uppercase), `short` (under 8 chars), `test_id1` (underscore)
Valid examples: `abc123de`, `a1b2c3d4`, `frontend-worktree-1`, `ChezMoi_2`
Avoid for new files: `A`, `test_id1`, `ABC123de`
Full valid filename example: `2024-01-15-abc123de-session.tmp`
@@ -271,5 +271,5 @@ Then test with Postman — the response should include a `Set-Cookie` header.
- The "What Did NOT Work" section is the most critical — future sessions will blindly retry failed approaches without it
- If the user asks to save mid-session (not just at the end), save what's known so far and mark in-progress items clearly
- The file is meant to be read by Claude at the start of the next session via `/resume-session`
- Use the canonical global session store: `~/.claude/sessions/`
- Use the canonical global session store: `~/.claude/session-data/`
- Prefer the short-id filename form (`YYYY-MM-DD-<short-id>-session.tmp`) for any new session file

View File

@@ -4,7 +4,7 @@ description: Manage Claude Code session history, aliases, and session metadata.
# Sessions Command
Manage Claude Code session history - list, load, alias, and edit sessions stored in `~/.claude/sessions/`.
Manage Claude Code session history - list, load, alias, and edit sessions stored in `~/.claude/session-data/` with legacy reads from `~/.claude/sessions/`.
## Usage
@@ -89,7 +89,7 @@ const size = sm.getSessionSize(session.sessionPath);
const aliases = aa.getAliasesForSession(session.filename);
console.log('Session: ' + session.filename);
console.log('Path: ~/.claude/sessions/' + session.filename);
console.log('Path: ' + session.sessionPath);
console.log('');
console.log('Statistics:');
console.log(' Lines: ' + stats.lineCount);
@@ -327,7 +327,7 @@ $ARGUMENTS:
## Notes
- Sessions are stored as markdown files in `~/.claude/sessions/`
- Sessions are stored as markdown files in `~/.claude/session-data/` with legacy reads from `~/.claude/sessions/`
- Aliases are stored in `~/.claude/session-aliases.json`
- Session IDs can be shortened (first 4-8 characters usually unique enough)
- Use aliases for frequently referenced sessions

View File

@@ -0,0 +1,919 @@
# Skill Development Guide
A comprehensive guide to creating effective skills for Everything Claude Code (ECC).
## Table of Contents
- [What Are Skills?](#what-are-skills)
- [Skill Architecture](#skill-architecture)
- [Creating Your First Skill](#creating-your-first-skill)
- [Skill Categories](#skill-categories)
- [Writing Effective Skill Content](#writing-effective-skill-content)
- [Best Practices](#best-practices)
- [Common Patterns](#common-patterns)
- [Testing Your Skill](#testing-your-skill)
- [Submitting Your Skill](#submitting-your-skill)
- [Examples Gallery](#examples-gallery)
---
## What Are Skills?
Skills are **knowledge modules** that Claude Code loads based on context. They provide:
- **Domain expertise**: Framework patterns, language idioms, best practices
- **Workflow definitions**: Step-by-step processes for common tasks
- **Reference material**: Code snippets, checklists, decision trees
- **Context injection**: Activate when specific conditions are met
Unlike **agents** (specialized subassistants) or **commands** (user-triggered actions), skills are passive knowledge that Claude Code references when relevant.
### When Skills Activate
Skills activate when:
- The user's task matches the skill's domain
- Claude Code detects relevant context
- A command references a skill
- An agent needs domain knowledge
### Skill vs Agent vs Command
| Component | Purpose | Activation |
|-----------|---------|------------|
| **Skill** | Knowledge repository | Context-based (automatic) |
| **Agent** | Task executor | Explicit delegation |
| **Command** | User action | User-invoked (`/command`) |
| **Hook** | Automation | Event-triggered |
| **Rule** | Always-on guidelines | Always active |
---
## Skill Architecture
### File Structure
```
skills/
└── your-skill-name/
├── SKILL.md # Required: Main skill definition
├── examples/ # Optional: Code examples
│ ├── basic.ts
│ └── advanced.ts
└── references/ # Optional: External references
└── links.md
```
### SKILL.md Format
```markdown
---
name: skill-name
description: Brief description shown in skill list and used for auto-activation
origin: ECC
---
# Skill Title
Brief overview of what this skill covers.
## When to Activate
Describe scenarios where Claude should use this skill.
## Core Concepts
Main patterns and guidelines.
## Code Examples
\`\`\`typescript
// Practical, tested examples
\`\`\`
## Anti-Patterns
Show what NOT to do with concrete examples.
## Best Practices
- Actionable guidelines
- Do's and don'ts
## Related Skills
Link to complementary skills.
```
### YAML Frontmatter Fields
| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Lowercase, hyphenated identifier (e.g., `react-patterns`) |
| `description` | Yes | One-line description for skill list and auto-activation |
| `origin` | No | Source identifier (e.g., `ECC`, `community`, project name) |
| `tags` | No | Array of tags for categorization |
| `version` | No | Skill version for tracking updates |
---
## Creating Your First Skill
### Step 1: Choose a Focus
Good skills are **focused and actionable**:
| ✅ Good Focus | ❌ Too Broad |
|---------------|--------------|
| `react-hook-patterns` | `react` |
| `postgresql-indexing` | `databases` |
| `pytest-fixtures` | `python-testing` |
| `nextjs-app-router` | `nextjs` |
### Step 2: Create the Directory
```bash
mkdir -p skills/your-skill-name
```
### Step 3: Write SKILL.md
Here's a minimal template:
```markdown
---
name: your-skill-name
description: Brief description of when to use this skill
---
# Your Skill Title
Brief overview (1-2 sentences).
## When to Activate
- Scenario 1
- Scenario 2
- Scenario 3
## Core Concepts
### Concept 1
Explanation with examples.
### Concept 2
Another pattern with code.
## Code Examples
\`\`\`typescript
// Practical example
\`\`\`
## Best Practices
- Do this
- Avoid that
## Related Skills
- `related-skill-1`
- `related-skill-2`
```
### Step 4: Add Content
Write content that Claude can **immediately use**:
- ✅ Copy-pasteable code examples
- ✅ Clear decision trees
- ✅ Checklists for verification
- ❌ Vague explanations without examples
- ❌ Long prose without actionable guidance
---
## Skill Categories
### Language Standards
Focus on idiomatic code, naming conventions, and language-specific patterns.
**Examples:** `python-patterns`, `golang-patterns`, `typescript-standards`
```markdown
---
name: python-patterns
description: Python idioms, best practices, and patterns for clean, idiomatic code.
---
# Python Patterns
## When to Activate
- Writing Python code
- Refactoring Python modules
- Python code review
## Core Concepts
### Context Managers
\`\`\`python
# Always use context managers for resources
with open('file.txt') as f:
content = f.read()
\`\`\`
```
### Framework Patterns
Focus on framework-specific conventions, common patterns, and anti-patterns.
**Examples:** `django-patterns`, `nextjs-patterns`, `springboot-patterns`
```markdown
---
name: django-patterns
description: Django best practices for models, views, URLs, and templates.
---
# Django Patterns
## When to Activate
- Building Django applications
- Creating models and views
- Django URL configuration
```
### Workflow Skills
Define step-by-step processes for common development tasks.
**Examples:** `tdd-workflow`, `code-review-workflow`, `deployment-checklist`
```markdown
---
name: code-review-workflow
description: Systematic code review process for quality and security.
---
# Code Review Workflow
## Steps
1. **Understand Context** - Read PR description and linked issues
2. **Check Tests** - Verify test coverage and quality
3. **Review Logic** - Analyze implementation for correctness
4. **Check Security** - Look for vulnerabilities
5. **Verify Style** - Ensure code follows conventions
```
### Domain Knowledge
Specialized knowledge for specific domains (security, performance, etc.).
**Examples:** `security-review`, `performance-optimization`, `api-design`
```markdown
---
name: api-design
description: REST and GraphQL API design patterns, versioning, and best practices.
---
# API Design Patterns
## RESTful Conventions
| Method | Endpoint | Purpose |
|--------|----------|---------|
| GET | /resources | List all |
| GET | /resources/:id | Get one |
| POST | /resources | Create |
```
### Tool Integration
Guidance for using specific tools, libraries, or services.
**Examples:** `supabase-patterns`, `docker-patterns`, `mcp-server-patterns`
---
## Writing Effective Skill Content
### 1. Start with "When to Activate"
This section is **critical** for auto-activation. Be specific:
```markdown
## When to Activate
- Creating new React components
- Refactoring existing components
- Debugging React state issues
- Reviewing React code for best practices
```
### 2. Use "Show, Don't Tell"
Bad:
```markdown
## Error Handling
Always handle errors properly in async functions.
```
Good:
```markdown
## Error Handling
\`\`\`typescript
async function fetchData(url: string) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`)
}
return await response.json()
} catch (error) {
console.error('Fetch failed:', error)
throw new Error('Failed to fetch data')
}
}
\`\`\`
### Key Points
- Check \`response.ok\` before parsing
- Log errors for debugging
- Re-throw with user-friendly message
```
### 3. Include Anti-Patterns
Show what NOT to do:
```markdown
## Anti-Patterns
### ❌ Direct State Mutation
\`\`\`typescript
// NEVER do this
user.name = 'New Name'
items.push(newItem)
\`\`\`
### ✅ Immutable Updates
\`\`\`typescript
// ALWAYS do this
const updatedUser = { ...user, name: 'New Name' }
const updatedItems = [...items, newItem]
\`\`\`
```
### 4. Provide Checklists
Checklists are actionable and easy to follow:
```markdown
## Pre-Deployment Checklist
- [ ] All tests passing
- [ ] No console.log in production code
- [ ] Environment variables documented
- [ ] Secrets not hardcoded
- [ ] Error handling complete
- [ ] Input validation in place
```
### 5. Use Decision Trees
For complex decisions:
```markdown
## Choosing the Right Approach
\`\`\`
Need to fetch data?
├── Single request → use fetch directly
├── Multiple independent → Promise.all()
├── Multiple dependent → await sequentially
└── With caching → use SWR or React Query
\`\`\`
```
---
## Best Practices
### DO
| Practice | Example |
|----------|---------|
| **Be specific** | "Use \`useCallback\` for event handlers passed to child components" |
| **Show examples** | Include copy-pasteable code |
| **Explain WHY** | "Immutability prevents unexpected side effects in React state" |
| **Link related skills** | "See also: \`react-performance\`" |
| **Keep focused** | One skill = one domain/concept |
| **Use sections** | Clear headers for easy scanning |
### DON'T
| Practice | Why It's Bad |
|----------|--------------|
| **Be vague** | "Write good code" - not actionable |
| **Long prose** | Hard to parse, better as code |
| **Cover too much** | "Python, Django, and Flask patterns" - too broad |
| **Skip examples** | Theory without practice is less useful |
| **Ignore anti-patterns** | Learning what NOT to do is valuable |
### Content Guidelines
1. **Length**: 200-500 lines typical, 800 lines maximum
2. **Code blocks**: Include language identifier
3. **Headers**: Use `##` and `###` hierarchy
4. **Lists**: Use `-` for unordered, `1.` for ordered
5. **Tables**: For comparisons and references
---
## Common Patterns
### Pattern 1: Standards Skill
```markdown
---
name: language-standards
description: Coding standards and best practices for [language].
---
# [Language] Coding Standards
## When to Activate
- Writing [language] code
- Code review
- Setting up linting
## Naming Conventions
| Element | Convention | Example |
|---------|------------|---------|
| Variables | camelCase | userName |
| Constants | SCREAMING_SNAKE | MAX_RETRY |
| Functions | camelCase | fetchUser |
| Classes | PascalCase | UserService |
## Code Examples
[Include practical examples]
## Linting Setup
[Include configuration]
## Related Skills
- `language-testing`
- `language-security`
```
### Pattern 2: Workflow Skill
```markdown
---
name: task-workflow
description: Step-by-step workflow for [task].
---
# [Task] Workflow
## When to Activate
- [Trigger 1]
- [Trigger 2]
## Prerequisites
- [Requirement 1]
- [Requirement 2]
## Steps
### Step 1: [Name]
[Description]
\`\`\`bash
[Commands]
\`\`\`
### Step 2: [Name]
[Description]
## Verification
- [ ] [Check 1]
- [ ] [Check 2]
## Troubleshooting
| Problem | Solution |
|---------|----------|
| [Issue] | [Fix] |
```
### Pattern 3: Reference Skill
```markdown
---
name: api-reference
description: Quick reference for [API/Library].
---
# [API/Library] Reference
## When to Activate
- Using [API/Library]
- Looking up [API/Library] syntax
## Common Operations
### Operation 1
\`\`\`typescript
// Basic usage
\`\`\`
### Operation 2
\`\`\`typescript
// Advanced usage
\`\`\`
## Configuration
[Include config examples]
## Error Handling
[Include error patterns]
```
---
## Testing Your Skill
### Local Testing
1. **Copy to Claude Code skills directory**:
```bash
cp -r skills/your-skill-name ~/.claude/skills/
```
2. **Test with Claude Code**:
```
You: "I need to [task that should trigger your skill]"
Claude should reference your skill's patterns.
```
3. **Verify activation**:
- Ask Claude to explain a concept from your skill
- Check if it uses your examples and patterns
- Ensure it follows your guidelines
### Validation Checklist
- [ ] **YAML frontmatter valid** - No syntax errors
- [ ] **Name follows convention** - lowercase-with-hyphens
- [ ] **Description is clear** - Tells when to use
- [ ] **Examples work** - Code compiles and runs
- [ ] **Links valid** - Related skills exist
- [ ] **No sensitive data** - No API keys, tokens, paths
### Code Example Testing
Test all code examples:
```bash
# From the repo root
npx tsc --noEmit skills/your-skill-name/examples/*.ts
# Or from inside the skill directory
npx tsc --noEmit examples/*.ts
# From the repo root
python -m py_compile skills/your-skill-name/examples/*.py
# Or from inside the skill directory
python -m py_compile examples/*.py
# From the repo root
go build ./skills/your-skill-name/examples/...
# Or from inside the skill directory
go build ./examples/...
```
---
## Submitting Your Skill
### 1. Fork and Clone
```bash
gh repo fork affaan-m/everything-claude-code --clone
cd everything-claude-code
```
### 2. Create Branch
```bash
git checkout -b feat/skill-your-skill-name
```
### 3. Add Your Skill
```bash
mkdir -p skills/your-skill-name
# Create SKILL.md
```
### 4. Validate
```bash
# Check YAML frontmatter
head -10 skills/your-skill-name/SKILL.md
# Verify structure
ls -la skills/your-skill-name/
# Run tests if available
npm test
```
### 5. Commit and Push
```bash
git add skills/your-skill-name/
git commit -m "feat(skills): add your-skill-name skill"
git push -u origin feat/skill-your-skill-name
```
### 6. Create Pull Request
Use this PR template:
```markdown
## Summary
Brief description of the skill and why it's valuable.
## Skill Type
- [ ] Language standards
- [ ] Framework patterns
- [ ] Workflow
- [ ] Domain knowledge
- [ ] Tool integration
## Testing
How I tested this skill locally.
## Checklist
- [ ] YAML frontmatter valid
- [ ] Code examples tested
- [ ] Follows skill guidelines
- [ ] No sensitive data
- [ ] Clear activation triggers
```
---
## Examples Gallery
### Example 1: Language Standards
**File:** `skills/rust-patterns/SKILL.md`
```markdown
---
name: rust-patterns
description: Rust idioms, ownership patterns, and best practices for safe, idiomatic code.
origin: ECC
---
# Rust Patterns
## When to Activate
- Writing Rust code
- Handling ownership and borrowing
- Error handling with Result/Option
- Implementing traits
## Ownership Patterns
### Borrowing Rules
\`\`\`rust
// ✅ CORRECT: Borrow when you don't need ownership
fn process_data(data: &str) -> usize {
data.len()
}
// ✅ CORRECT: Take ownership when you need to modify or consume
fn consume_data(data: Vec<u8>) -> String {
String::from_utf8(data).unwrap()
}
\`\`\`
## Error Handling
### Result Pattern
\`\`\`rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
}
pub type AppResult<T> = Result<T, AppError>;
\`\`\`
## Related Skills
- `rust-testing`
- `rust-security`
```
### Example 2: Framework Patterns
**File:** `skills/fastapi-patterns/SKILL.md`
```markdown
---
name: fastapi-patterns
description: FastAPI patterns for routing, dependency injection, validation, and async operations.
origin: ECC
---
# FastAPI Patterns
## When to Activate
- Building FastAPI applications
- Creating API endpoints
- Implementing dependency injection
- Handling async database operations
## Project Structure
\`\`\`
app/
├── main.py # FastAPI app entry point
├── routers/ # Route handlers
│ ├── users.py
│ └── items.py
├── models/ # Pydantic models
│ ├── user.py
│ └── item.py
├── services/ # Business logic
│ └── user_service.py
└── dependencies.py # Shared dependencies
\`\`\`
## Dependency Injection
\`\`\`python
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
async def get_db() -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
@router.get("/users/{user_id}")
async def get_user(
user_id: int,
db: AsyncSession = Depends(get_db)
):
# Use db session
pass
\`\`\`
## Related Skills
- `python-patterns`
- `pydantic-validation`
```
### Example 3: Workflow Skill
**File:** `skills/refactoring-workflow/SKILL.md`
```markdown
---
name: refactoring-workflow
description: Systematic refactoring workflow for improving code quality without changing behavior.
origin: ECC
---
# Refactoring Workflow
## When to Activate
- Improving code structure
- Reducing technical debt
- Simplifying complex code
- Extracting reusable components
## Prerequisites
- All tests passing
- Git working directory clean
- Feature branch created
## Workflow Steps
### Step 1: Identify Refactoring Target
- Look for code smells (long methods, duplicate code, large classes)
- Check test coverage for target area
- Document current behavior
### Step 2: Ensure Tests Exist
\`\`\`bash
# Run tests to verify current behavior
npm test
# Check coverage for target files
npm run test:coverage
\`\`\`
### Step 3: Make Small Changes
- One refactoring at a time
- Run tests after each change
- Commit frequently
### Step 4: Verify Behavior Unchanged
\`\`\`bash
# Run full test suite
npm test
# Run E2E tests
npm run test:e2e
\`\`\`
## Common Refactorings
| Smell | Refactoring |
|-------|-------------|
| Long method | Extract method |
| Duplicate code | Extract to shared function |
| Large class | Extract class |
| Long parameter list | Introduce parameter object |
## Checklist
- [ ] Tests exist for target code
- [ ] Made small, focused changes
- [ ] Tests pass after each change
- [ ] Behavior unchanged
- [ ] Committed with clear message
```
---
## Additional Resources
- [CONTRIBUTING.md](../CONTRIBUTING.md) - General contribution guidelines
- [project-guidelines-example](../skills/project-guidelines-example/SKILL.md) - Project-specific skill template
- [coding-standards](../skills/coding-standards/SKILL.md) - Example of standards skill
- [tdd-workflow](../skills/tdd-workflow/SKILL.md) - Example of workflow skill
- [security-review](../skills/security-review/SKILL.md) - Example of domain knowledge skill
---
**Remember**: A good skill is focused, actionable, and immediately useful. Write skills you'd want to use yourself.

View File

@@ -409,7 +409,7 @@ claude --version
Claude Code v2.1+は、インストール済みプラグインの`hooks/hooks.json`(規約)を自動読み込みします。`plugin.json`で明示的に宣言するとエラーが発生します:
```
Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded file
Duplicate hook file detected: ./hooks/hooks.json is already resolved to a loaded file
```
**背景:** これは本リポジトリで複数の修正/リバート循環を引き起こしました([#29](https://github.com/affaan-m/everything-claude-code/issues/29), [#52](https://github.com/affaan-m/everything-claude-code/issues/52), [#103](https://github.com/affaan-m/everything-claude-code/issues/103)。Claude Codeバージョン間で動作が変わったため混乱がありました。今後を防ぐため回帰テストがあります。

View File

@@ -77,9 +77,9 @@ model: opus
各問題について:
```
[CRITICAL] ハードコードされたAPIキー
File: src/api/client.ts:42
Issue: APIキーがソースコードに公開されている
Fix: 環境変数に移動
ファイル: src/api/client.ts:42
問題: APIキーがソースコードに公開されている
修正: 環境変数に移動
const apiKey = "sk-abc123"; // ❌ Bad
const apiKey = process.env.API_KEY; // ✓ Good

View File

@@ -341,20 +341,20 @@ x = x // 無意味な代入を削除
各修正試行後:
```text
[FIXED] internal/handler/user.go:42
Error: undefined: UserService
Fix: Added import "project/internal/service"
[修正済] internal/handler/user.go:42
エラー: undefined: UserService
修正: import を追加 "project/internal/service"
Remaining errors: 3
残りのエラー: 3
```
最終サマリー:
```text
Build Status: SUCCESS/FAILED
Errors Fixed: N
Vet Warnings Fixed: N
Files Modified: list
Remaining Issues: list (if any)
ビルドステータス: SUCCESS/FAILED
修正済みエラー: N
Vet 警告修正済み: N
変更ファイル: list
残りの問題: list (ある場合)
```
## 重要な注意事項

View File

@@ -228,9 +228,9 @@ model: opus
各問題について:
```text
[CRITICAL] SQLインジェクション脆弱性
File: internal/repository/user.go:42
Issue: ユーザー入力がSQLクエリに直接連結されている
Fix: パラメータ化クエリを使用
ファイル: internal/repository/user.go:42
問題: ユーザー入力がSQLクエリに直接連結されている
修正: パラメータ化クエリを使用
query := "SELECT * FROM users WHERE id = " + userID // Bad
query := "SELECT * FROM users WHERE id = $1" // Good

View File

@@ -399,9 +399,9 @@ model: opus
各問題について:
```text
[CRITICAL] SQLインジェクション脆弱性
File: app/routes/user.py:42
Issue: ユーザー入力がSQLクエリに直接補間されている
Fix: パラメータ化クエリを使用
ファイル: app/routes/user.py:42
問題: ユーザー入力がSQLクエリに直接補間されている
修正: パラメータ化クエリを使用
query = f"SELECT * FROM users WHERE id = {user_id}" # Bad
query = "SELECT * FROM users WHERE id = %s" # Good

View File

@@ -35,12 +35,12 @@ echo "$(date +%Y-%m-%d-%H:%M) | $CHECKPOINT_NAME | $(git rev-parse --short HEAD)
3. レポート:
```
CHECKPOINT COMPARISON: $NAME
チェックポイント比較: $NAME
============================
Files changed: X
Tests: +Y passed / -Z failed
Coverage: +X% / -Y%
Build: [PASS/FAIL]
変更されたファイル: X
テスト: +Y 合格 / -Z 失敗
カバレッジ: +X% / -Y%
ビルド: [PASS/FAIL]
```
## チェックポイント一覧表示
@@ -57,13 +57,13 @@ Build: [PASS/FAIL]
一般的なチェックポイント流:
```
[Start] --> /checkpoint create "feature-start"
[開始] --> /checkpoint create "feature-start"
|
[Implement] --> /checkpoint create "core-done"
[実装] --> /checkpoint create "core-done"
|
[Test] --> /checkpoint verify "core-done"
[テスト] --> /checkpoint verify "core-done"
|
[Refactor] --> /checkpoint create "refactor-done"
[リファクタリング] --> /checkpoint create "refactor-done"
|
[PR] --> /checkpoint verify "feature-start"
```

View File

@@ -38,24 +38,24 @@ e2e-runner エージェントは:
## 使用します示例
````
User: /e2e Test the market search and view flow
User: /e2e マーケット検索と表示フローをテスト
Agent (e2e-runner):
# E2E Test Generation: Market Search and View Flow
# E2Eテスト生成: マーケット検索と表示フロー
## Test Scenario Identified
## 特定されたテストシナリオ
**User Journey:** Market Search → View Results → Click Market → View Details
**ユーザージャーニー:** マーケット検索 → 結果表示 → マーケットクリック → 詳細表示
**Test Coverage:**
1. Navigate to markets page
2. Perform semantic search
3. Verify search results
4. Click on first result
5. Verify market details page loads
6. Verify chart renders
**テストカバレッジ:**
1. マーケットページに遷移
2. セマンティック検索を実行
3. 検索結果を検証
4. 最初の結果をクリック
5. マーケット詳細ページの読み込みを検証
6. チャートの描画を検証
## Generated Test Code
## 生成されたテストコード
```typescript
// tests/e2e/markets/search-and-view.spec.ts
@@ -160,7 +160,7 @@ test.describe('Market Search and View Flow', () => {
## テスト実行
```bash
# Run the generated test
# 生成されたテストを実行
npx playwright test tests/e2e/markets/search-and-view.spec.ts
Running 3 tests using 3 workers
@@ -181,54 +181,54 @@ Artifacts generated:
```
╔══════════════════════════════════════════════════════════════╗
║ E2E Test Results
║ E2Eテスト結果
╠══════════════════════════════════════════════════════════════╣
Status: ✅ ALL TESTS PASSED
Total: 3 tests
Passed: 3 (100%) ║
Failed: 0 ║
Flaky: 0 ║
Duration: 9.1s ║
ステータス: ✅ 全テスト合格
合計: 3テスト
合格: 3 (100%) ║
失敗: 0 ║
不安定: 0 ║
所要時間: 9.1s ║
╚══════════════════════════════════════════════════════════════╝
Artifacts:
📸 Screenshots: 2 files
📹 Videos: 0 files (only on failure)
🔍 Traces: 0 files (only on failure)
📊 HTML Report: playwright-report/index.html
アーティファクト:
📸 スクリーンショット: 2ファイル
📹 ビデオ: 0ファイル (失敗時のみ)
🔍 トレース: 0ファイル (失敗時のみ)
📊 HTMLレポート: playwright-report/index.html
View report: npx playwright show-report
レポート表示: npx playwright show-report
```
✅ E2E テストスイートは CI/CD 統合の準備ができました!
````
## Test Artifacts
## テストアーティファクト
When tests run, the following artifacts are captured:
テスト実行時、以下のアーティファクトがキャプチャされます:
**On All Tests:**
- HTML Report with timeline and results
- JUnit XML for CI integration
**全テスト共通:**
- タイムラインと結果を含むHTMLレポート
- CI統合用のJUnit XML
**On Failure Only:**
- Screenshot of the failing state
- Video recording of the test
- Trace file for debugging (step-by-step replay)
- Network logs
- Console logs
**失敗時のみ:**
- 失敗状態のスクリーンショット
- テストのビデオ録画
- デバッグ用トレースファイル (ステップバイステップ再生)
- ネットワークログ
- コンソールログ
## Viewing Artifacts
## アーティファクトの確認
```bash
# View HTML report in browser
# ブラウザでHTMLレポートを表示
npx playwright show-report
# View specific trace file
# 特定のトレースファイルを表示
npx playwright show-trace artifacts/trace-abc123.zip
# Screenshots are saved in artifacts/ directory
# スクリーンショットはartifacts/ディレクトリに保存
open artifacts/search-results.png
````
@@ -239,18 +239,18 @@ open artifacts/search-results.png
```
⚠️ FLAKY TEST DETECTED: tests/e2e/markets/trade.spec.ts
Test passed 7/10 runs (70% pass rate)
テストは10回中7回合格 (合格率70%)
Common failure:
よくある失敗:
"Timeout waiting for element '[data-testid="confirm-btn"]'"
Recommended fixes:
1. Add explicit wait: await page.waitForSelector('[data-testid="confirm-btn"]')
2. Increase timeout: { timeout: 10000 }
3. Check for race conditions in component
4. Verify element is not hidden by animation
推奨修正:
1. 明示的な待機を追加: await page.waitForSelector('[data-testid="confirm-btn"]')
2. タイムアウトを増加: { timeout: 10000 }
3. コンポーネントの競合状態を確認
4. 要素がアニメーションで隠れていないか確認
Quarantine recommendation: Mark as test.fixme() until fixed
隔離推奨: 修正されるまでtest.fixme()としてマーク
```
## ブラウザ設定
@@ -350,21 +350,21 @@ PMX の場合、以下の E2E テストを優先:
## 快速命令
```bash
# Run all E2E tests
# 全E2Eテストを実行
npx playwright test
# Run specific test file
# 特定のテストファイルを実行
npx playwright test tests/e2e/markets/search.spec.ts
# Run in headed mode (see browser)
# ヘッドモードで実行 (ブラウザ表示)
npx playwright test --headed
# Debug test
# テストをデバッグ
npx playwright test --debug
# Generate test code
# テストコードを生成
npx playwright codegen http://localhost:3000
# View report
# レポートを表示
npx playwright show-report
```

View File

@@ -92,36 +92,36 @@ instinctsが分離の恩恵を受ける複雑な複数ステップのプロセ
## 出力フォーマット
```
🧬 Evolve Analysis
🧬 進化分析
==================
進化の準備ができた3つのクラスターを発見:
## クラスター1: データベースマイグレーションワークフロー
Instincts: new-table-migration, update-schema, regenerate-types
Type: Command
Confidence: 85%(12件の観測に基づく)
タイプ: Command
信頼度: 85%(12件の観測に基づく)
作成: /new-tableコマンド
Files:
ファイル:
- ~/.claude/homunculus/evolved/commands/new-table.md
## クラスター2: 関数型コードスタイル
Instincts: prefer-functional, use-immutable, avoid-classes, pure-functions
Type: Skill
Confidence: 78%(8件の観測に基づく)
タイプ: Skill
信頼度: 78%(8件の観測に基づく)
作成: functional-patternsスキル
Files:
ファイル:
- ~/.claude/homunculus/evolved/skills/functional-patterns.md
## クラスター3: デバッグプロセス
Instincts: debug-check-logs, debug-isolate, debug-reproduce, debug-verify
Type: Agent
Confidence: 72%(6件の観測に基づく)
タイプ: Agent
信頼度: 72%(6件の観測に基づく)
作成: debuggerエージェント
Files:
ファイル:
- ~/.claude/homunculus/evolved/agents/debugger.md
---

View File

@@ -62,9 +62,9 @@ internal/handler/api.go:58:2: missing return at end of function
## 修正1: 未定義の識別子
File: internal/service/user.go:25
Error: undefined: UserRepository
Cause: インポート欠落
ファイル: internal/service/user.go:25
エラー: undefined: UserRepository
原因: インポート欠落
```go
// インポートを追加
@@ -83,8 +83,8 @@ $ go build ./...
## 修正2: 型の不一致
File: internal/handler/api.go:42
Error: cannot use x (type string) as type int
ファイル: internal/handler/api.go:42
エラー: cannot use x (type string) as type int
```go
// 変更前
@@ -101,8 +101,8 @@ $ go build ./...
## 修正3: 戻り値の欠落
File: internal/handler/api.go:58
Error: missing return at end of function
ファイル: internal/handler/api.go:58
エラー: missing return at end of function
```go
func GetUser(id string) (*User, error) {

View File

@@ -85,8 +85,8 @@ Agent:
## 発見された問題
[CRITICAL] 競合状態
File: internal/service/auth.go:45
Issue: 同期化なしで共有マップにアクセス
ファイル: internal/service/auth.go:45
問題: 同期化なしで共有マップにアクセス
```go
var cache = map[string]*Session{} // 並行アクセス!
@@ -94,7 +94,7 @@ func GetSession(id string) *Session {
return cache[id] // 競合状態
}
```
Fix: sync.RWMutexまたはsync.Mapを使用
修正: sync.RWMutexまたはsync.Mapを使用
```go
var (
cache = map[string]*Session{}
@@ -109,12 +109,12 @@ func GetSession(id string) *Session {
```
[HIGH] エラーコンテキストの欠落
File: internal/handler/user.go:28
Issue: コンテキストなしでエラーを返す
ファイル: internal/handler/user.go:28
問題: コンテキストなしでエラーを返す
```go
return err // コンテキストなし
```
Fix: コンテキストでラップ
修正: コンテキストでラップ
```go
return fmt.Errorf("get user %s: %w", userID, err)
```

View File

@@ -45,40 +45,40 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import <
## インポートプロセス
```
📥 Importing instincts from: team-instincts.yaml
📥 instinctsをインポート中: team-instincts.yaml
================================================
Found 12 instincts to import.
12件のinstinctsが見つかりました。
Analyzing conflicts...
競合を分析中...
## New Instincts (8)
These will be added:
## 新規instincts (8)
以下が追加されます:
✓ use-zod-validation (confidence: 0.7)
✓ prefer-named-exports (confidence: 0.65)
✓ test-async-functions (confidence: 0.8)
...
## Duplicate Instincts (3)
Already have similar instincts:
## 重複instincts (3)
類似のinstinctsが既に存在:
⚠️ prefer-functional-style
Local: 0.8 confidence, 12 observations
Import: 0.7 confidence
Keep local (higher confidence)
ローカル: 信頼度0.8, 12回の観測
インポート: 信頼度0.7
ローカルを保持 (信頼度が高い)
⚠️ test-first-workflow
Local: 0.75 confidence
Import: 0.9 confidence
Update to import (higher confidence)
ローカル: 信頼度0.75
インポート: 信頼度0.9
インポートに更新 (信頼度が高い)
## Conflicting Instincts (1)
These contradict local instincts:
## 競合instincts (1)
ローカルのinstinctsと矛盾:
❌ use-classes-for-services
Conflicts with: avoid-classes
Skip (requires manual resolution)
競合: avoid-classes
スキップ (手動解決が必要)
---
Import 8 new, update 1, skip 3?
8件を新規追加、1件を更新、3件をスキップしますか?
```
## マージ戦略
@@ -130,13 +130,13 @@ Skill Creatorからインポートする場合:
インポート後:
```
Import complete!
インポート完了!
Added: 8 instincts
Updated: 1 instinct
Skipped: 3 instincts (2 duplicates, 1 conflict)
追加: 8件のinstincts
更新: 1件のinstinct
スキップ: 3件のinstincts (2件の重複, 1件の競合)
New instincts saved to: ~/.claude/homunculus/instincts/inherited/
新規instinctsの保存先: ~/.claude/homunculus/instincts/inherited/
Run /instinct-status to see all instincts.
/instinct-statusを実行してすべてのinstinctsを確認できます。
```

View File

@@ -39,42 +39,42 @@ python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status
## 出力形式
```
📊 Instinct Status
📊 instinctステータス
==================
## Code Style (4 instincts)
## コードスタイル (4 instincts)
### prefer-functional-style
Trigger: when writing new functions
Action: Use functional patterns over classes
Confidence: ████████░░ 80%
Source: session-observation | Last updated: 2025-01-22
トリガー: 新しい関数を書くとき
アクション: クラスより関数型パターンを使用
信頼度: ████████░░ 80%
ソース: session-observation | 最終更新: 2025-01-22
### use-path-aliases
Trigger: when importing modules
Action: Use @/ path aliases instead of relative imports
Confidence: ██████░░░░ 60%
Source: repo-analysis (github.com/acme/webapp)
トリガー: モジュールをインポートするとき
アクション: 相対インポートの代わりに@/パスエイリアスを使用
信頼度: ██████░░░░ 60%
ソース: repo-analysis (github.com/acme/webapp)
## Testing (2 instincts)
## テスト (2 instincts)
### test-first-workflow
Trigger: when adding new functionality
Action: Write test first, then implementation
Confidence: █████████░ 90%
Source: session-observation
トリガー: 新しい機能を追加するとき
アクション: テストを先に書き、次に実装
信頼度: █████████░ 90%
ソース: session-observation
## Workflow (3 instincts)
## ワークフロー (3 instincts)
### grep-before-edit
Trigger: when modifying code
Action: Search with Grep, confirm with Read, then Edit
Confidence: ███████░░░ 70%
Source: session-observation
トリガー: コードを変更するとき
アクション: Grepで検索、Readで確認、次にEdit
信頼度: ███████░░░ 70%
ソース: session-observation
---
Total: 9 instincts (4 personal, 5 inherited)
Observer: Running (last analysis: 5 min ago)
合計: 9 instincts (4個人, 5継承)
オブザーバー: 実行中 (最終分析: 5分前)
```
## フラグ

View File

@@ -99,36 +99,36 @@ security-reviewer -> code-reviewer -> architect
## 最終レポート形式
```
ORCHESTRATION REPORT
オーケストレーションレポート
====================
Workflow: feature
Task: Add user authentication
Agents: planner -> tdd-guide -> code-reviewer -> security-reviewer
ワークフロー: feature
タスク: ユーザー認証の追加
エージェント: planner -> tdd-guide -> code-reviewer -> security-reviewer
SUMMARY
サマリー
-------
[1段落の要約]
AGENT OUTPUTS
エージェント出力
-------------
Planner: [要約]
TDD Guide: [要約]
Code Reviewer: [要約]
Security Reviewer: [要約]
FILES CHANGED
変更ファイル
-------------
[変更されたすべてのファイルをリスト]
TEST RESULTS
テスト結果
------------
[テスト合格/不合格の要約]
SECURITY STATUS
セキュリティステータス
---------------
[セキュリティの発見事項]
RECOMMENDATION
推奨事項
--------------
[リリース可 / 要修正 / ブロック中]
```

View File

@@ -95,26 +95,26 @@ Agent:
## 発見された問題
[CRITICAL] SQLインジェクション脆弱性
File: app/routes/user.py:42
Issue: ユーザー入力が直接SQLクエリに挿入されている
ファイル: app/routes/user.py:42
問題: ユーザー入力が直接SQLクエリに挿入されている
```python
query = f"SELECT * FROM users WHERE id = {user_id}" # 悪い
```
Fix: パラメータ化クエリを使用
修正: パラメータ化クエリを使用
```python
query = "SELECT * FROM users WHERE id = %s" # 良い
cursor.execute(query, (user_id,))
```
[HIGH] 可変デフォルト引数
File: app/services/auth.py:18
Issue: 可変デフォルト引数が共有状態を引き起こす
ファイル: app/services/auth.py:18
問題: 可変デフォルト引数が共有状態を引き起こす
```python
def process_items(items=[]): # 悪い
items.append("new")
return items
```
Fix: デフォルトにNoneを使用
修正: デフォルトにNoneを使用
```python
def process_items(items=None): # 良い
if items is None:
@@ -124,27 +124,27 @@ def process_items(items=None): # 良い
```
[MEDIUM] 型ヒントの欠落
File: app/services/auth.py:25
Issue: 型アノテーションのない公開関数
ファイル: app/services/auth.py:25
問題: 型アノテーションのない公開関数
```python
def get_user(user_id): # 悪い
return db.find(user_id)
```
Fix: 型ヒントを追加
修正: 型ヒントを追加
```python
def get_user(user_id: str) -> Optional[User]: # 良い
return db.find(user_id)
```
[MEDIUM] コンテキストマネージャーを使用していない
File: app/routes/user.py:55
Issue: 例外時にファイルがクローズされない
ファイル: app/routes/user.py:55
問題: 例外時にファイルがクローズされない
```python
f = open("config.json") # 悪い
data = f.read()
f.close()
```
Fix: コンテキストマネージャーを使用
修正: コンテキストマネージャーを使用
```python
with open("config.json") as f: # 良い
data = f.read()

View File

@@ -36,16 +36,16 @@
簡潔な検証レポートを生成します:
```
VERIFICATION: [PASS/FAIL]
検証結果: [PASS/FAIL]
Build: [OK/FAIL]
Types: [OK/X errors]
Lint: [OK/X issues]
Tests: [X/Y passed, Z% coverage]
Secrets: [OK/X found]
Logs: [OK/X console.logs]
ビルド: [OK/FAIL]
型: [OK/Xエラー]
Lint: [OK/X件の問題]
テスト: [X/Y合格, Z%カバレッジ]
シークレット: [OK/X件発見]
ログ: [OK/X件のconsole.log]
Ready for PR: [YES/NO]
PR準備完了: [YES/NO]
```
重大な問題がある場合は、修正案とともにリストアップします。

View File

@@ -43,7 +43,7 @@
```
src/
|-- app/ # Next.js app router
|-- app/ # Next.js App Router
|-- components/ # 再利用可能なUIコンポーネント
|-- hooks/ # カスタムReactフック
|-- lib/ # ユーティリティライブラリ

View File

@@ -234,14 +234,14 @@ setCount(count + 1) // Can be stale in async scenarios
### REST API規約
```
GET /api/markets # List all markets
GET /api/markets/:id # Get specific market
POST /api/markets # Create new market
PUT /api/markets/:id # Update market (full)
PATCH /api/markets/:id # Update market (partial)
DELETE /api/markets/:id # Delete market
GET /api/markets # すべてのマーケットを一覧
GET /api/markets/:id # 特定のマーケットを取得
POST /api/markets # 新しいマーケットを作成
PUT /api/markets/:id # マーケットを更新(全体)
PATCH /api/markets/:id # マーケットを更新(部分)
DELETE /api/markets/:id # マーケットを削除
# Query parameters for filtering
# フィルタリング用クエリパラメータ
GET /api/markets?status=active&limit=10&offset=0
```
@@ -312,29 +312,29 @@ export async function POST(request: Request) {
```
src/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ ├── markets/ # Market pages
│ └── (auth)/ # Auth pages (route groups)
├── components/ # React components
│ ├── ui/ # Generic UI components
│ ├── forms/ # Form components
│ └── layouts/ # Layout components
├── hooks/ # Custom React hooks
├── lib/ # Utilities and configs
│ ├── api/ # API clients
│ ├── utils/ # Helper functions
│ └── constants/ # Constants
├── types/ # TypeScript types
└── styles/ # Global styles
│ ├── api/ # API ルート
│ ├── markets/ # マーケットページ
│ └── (auth)/ # 認証ページ(ルートグループ)
├── components/ # React コンポーネント
│ ├── ui/ # 汎用 UI コンポーネント
│ ├── forms/ # フォームコンポーネント
│ └── layouts/ # レイアウトコンポーネント
├── hooks/ # カスタム React フック
├── lib/ # ユーティリティと設定
│ ├── api/ # API クライアント
│ ├── utils/ # ヘルパー関数
│ └── constants/ # 定数
├── types/ # TypeScript 型定義
└── styles/ # グローバルスタイル
```
### ファイル命名
```
components/Button.tsx # PascalCase for components
hooks/useAuth.ts # camelCase with 'use' prefix
lib/formatDate.ts # camelCase for utilities
types/market.types.ts # camelCase with .types suffix
components/Button.tsx # コンポーネントは PascalCase
hooks/useAuth.ts # フックは 'use' プレフィックス付き camelCase
lib/formatDate.ts # ユーティリティは camelCase
types/market.types.ts # 型定義は .types サフィックス付き camelCase
```
## コメントとドキュメント

View File

@@ -51,13 +51,13 @@ source: "session-observation"
## 仕組み
```
Session Activity
セッションアクティビティ
│ フックがプロンプト + ツール使用をキャプチャ100%信頼性)
┌─────────────────────────────────────────┐
│ observations.jsonl │
(prompts, tool calls, outcomes)
(プロンプト、ツール呼び出し、結果)
└─────────────────────────────────────────┘
│ Observerエージェントが読み取りバックグラウンド、Haiku

View File

@@ -22,24 +22,24 @@ Claude Codeセッションの正式な評価フレームワークで、評価駆
Claudeが以前できなかったことができるようになったかをテスト
```markdown
[CAPABILITY EVAL: feature-name]
Task: Claudeが達成すべきことの説明
Success Criteria:
タスク: Claudeが達成すべきことの説明
成功基準:
- [ ] 基準1
- [ ] 基準2
- [ ] 基準3
Expected Output: 期待される結果の説明
期待される出力: 期待される結果の説明
```
### リグレッション評価
変更が既存の機能を破壊しないことを確認:
```markdown
[REGRESSION EVAL: feature-name]
Baseline: SHAまたはチェックポイント名
Tests:
ベースライン: SHAまたはチェックポイント名
テスト:
- existing-test-1: PASS/FAIL
- existing-test-2: PASS/FAIL
- existing-test-3: PASS/FAIL
Result: X/Y passed (previously Y/Y)
結果: X/Y 成功(以前は Y/Y
```
## 評価者タイプ
@@ -67,17 +67,17 @@ Claudeを使用して自由形式の出力を評価
3. エッジケースは処理されていますか?
4. エラー処理は適切ですか?
Score: 1-5 (1=poor, 5=excellent)
Reasoning: [説明]
スコア: 1-51=不良、5=優秀)
理由: [説明]
```
### 3. 人間評価者
手動レビューのためにフラグを立てる:
```markdown
[HUMAN REVIEW REQUIRED]
Change: 何が変更されたかの説明
Reason: 人間のレビューが必要な理由
Risk Level: LOW/MEDIUM/HIGH
変更内容: 何が変更されたかの説明
理由: 人間のレビューが必要な理由
リスクレベル: LOW/MEDIUM/HIGH
```
## メトリクス
@@ -98,21 +98,21 @@ Risk Level: LOW/MEDIUM/HIGH
### 1. 定義(コーディング前)
```markdown
## EVAL DEFINITION: feature-xyz
## 評価定義: feature-xyz
### Capability Evals
### 能力評価
1. 新しいユーザーアカウントを作成できる
2. メール形式を検証できる
3. パスワードを安全にハッシュ化できる
### Regression Evals
### リグレッション評価
1. 既存のログインが引き続き機能する
2. セッション管理が変更されていない
3. ログアウトフローが維持されている
### Success Metrics
- pass@3 > 90% for capability evals
- pass^3 = 100% for regression evals
### 成功メトリクス
- 能力評価で pass@3 > 90%
- リグレッション評価で pass^3 = 100%
```
### 2. 実装
@@ -131,26 +131,26 @@ npm test -- --testPathPattern="existing"
### 4. レポート
```markdown
EVAL REPORT: feature-xyz
評価レポート: feature-xyz
========================
Capability Evals:
能力評価:
create-user: PASS (pass@1)
validate-email: PASS (pass@2)
hash-password: PASS (pass@1)
Overall: 3/3 passed
全体: 3/3 成功
Regression Evals:
リグレッション評価:
login-flow: PASS
session-mgmt: PASS
logout-flow: PASS
Overall: 3/3 passed
全体: 3/3 成功
Metrics:
メトリクス:
pass@1: 67% (2/3)
pass@3: 100% (3/3)
Status: READY FOR REVIEW
ステータス: レビュー準備完了
```
## 統合パターン
@@ -199,29 +199,29 @@ Status: READY FOR REVIEW
```markdown
## EVAL: add-authentication
### Phase 1: Define (10 min)
Capability Evals:
### フェーズ 1: 定義10分
能力評価:
- [ ] ユーザーはメール/パスワードで登録できる
- [ ] ユーザーは有効な資格情報でログインできる
- [ ] 無効な資格情報は適切なエラーで拒否される
- [ ] セッションはページリロード後も持続する
- [ ] ログアウトはセッションをクリアする
Regression Evals:
リグレッション評価:
- [ ] 公開ルートは引き続きアクセス可能
- [ ] APIレスポンスは変更されていない
- [ ] データベーススキーマは互換性がある
### Phase 2: Implement (varies)
### フェーズ 2: 実装(可変)
[コードを書く]
### Phase 3: Evaluate
### フェーズ 3: 評価
Run: /eval check add-authentication
### Phase 4: Report
EVAL REPORT: add-authentication
### フェーズ 4: レポート
評価レポート: add-authentication
==============================
Capability: 5/5 passed (pass@3: 100%)
Regression: 3/3 passed (pass^3: 100%)
Status: SHIP IT
能力: 5/5 成功(pass@3: 100%
リグレッション: 3/3 成功(pass^3: 100%
ステータス: 出荷可能
```

View File

@@ -368,17 +368,17 @@ func WriteAndFlush(w io.Writer, data []byte) error {
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # Entry point
│ └── main.go # エントリポイント
├── internal/
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── config/ # Configuration
│ ├── handler/ # HTTP ハンドラー
│ ├── service/ # ビジネスロジック
│ ├── repository/ # データアクセス
│ └── config/ # 設定
├── pkg/
│ └── client/ # Public API client
│ └── client/ # 公開 API クライアント
├── api/
│ └── v1/ # API definitions (proto, OpenAPI)
├── testdata/ # Test fixtures
│ └── v1/ # API 定義(protoOpenAPI
├── testdata/ # テストフィクスチャ
├── go.mod
├── go.sum
└── Makefile

View File

@@ -113,7 +113,7 @@ mypackage/
├── testdata/ # テストフィクスチャ
│ ├── valid_user.json
│ └── invalid_user.json
└── export_test.go # 内部テストのための非公開エクスポート
└── export_test.go # 内部テストの非公開エクスポート
```
### テストパッケージ

View File

@@ -594,18 +594,18 @@ def test_with_tmpdir(tmpdir):
```
tests/
├── conftest.py # Shared fixtures
├── conftest.py # 共有フィクスチャ
├── __init__.py
├── unit/ # Unit tests
├── unit/ # ユニットテスト
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_utils.py
│ └── test_services.py
├── integration/ # Integration tests
├── integration/ # 統合テスト
│ ├── __init__.py
│ ├── test_api.py
│ └── test_database.py
└── e2e/ # End-to-end tests
└── e2e/ # エンドツーエンドテスト
├── __init__.py
└── test_user_flow.py
```

View File

@@ -0,0 +1,31 @@
---
name: prune
description: 删除超过 30 天且从未被提升的待处理本能
command: true
---
# 清理待处理本能
删除那些由系统自动生成、但从未经过审查或提升的过期待处理本能。
## 实现
使用插件根目录路径运行本能 CLI
```bash
python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" prune
```
或者如果 `CLAUDE_PLUGIN_ROOT` 未设置(手动安装):
```bash
python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py prune
```
## 用法
```
/prune # 删除超过 30 天的本能
/prune --max-age 60 # 自定义年龄阈值(天)
/prune --dry-run # 仅预览,不实际删除
```

View File

@@ -1,6 +1,7 @@
---
name: prompt-optimizer
description: 分析原始提示识别意图和差距匹配ECC组件技能/命令/代理/钩子并输出一个可直接粘贴的优化提示。仅提供咨询角色——绝不自行执行任务。触发时机当用户说“优化提示”、“改进我的提示”、“如何编写提示”、“帮我优化这个指令”或明确要求提高提示质量时。中文等效表达同样触发“优化prompt”、“改进prompt”、“怎么写prompt”、“帮我优化这个指令”。不触发时机当用户希望直接执行任务或说“直接做”时。不触发时机当用户说“优化代码”、“优化性能”、“optimize performance”、“optimize this code”时——这些是重构/性能优化任务,而非提示优化。origin: community
description: 分析原始提示识别意图和差距匹配ECC组件技能/命令/代理/钩子并输出一个可直接粘贴的优化提示。仅提供咨询角色——绝不自行执行任务。触发时机当用户说“优化提示”、“改进我的提示”、“如何编写提示”、“帮我优化这个指令”或明确要求提高提示质量时。中文等效表达同样触发“优化prompt”、“改进prompt”、“怎么写prompt”、“帮我优化这个指令”。不触发时机当用户希望直接执行任务或说“直接做”时。不触发时机当用户说“优化代码”、“优化性能”、“optimize performance”、“optimize this code”时——这些是重构/性能优化任务,而非提示优化。
origin: community
metadata:
author: YannJY02
version: "1.0.0"

9
ecc2/Cargo.lock generated
View File

@@ -332,6 +332,7 @@ dependencies = [
"crossterm",
"dirs",
"git2",
"libc",
"ratatui",
"rusqlite",
"serde",
@@ -437,9 +438,9 @@ dependencies = [
[[package]]
name = "git2"
version = "0.19.0"
version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b"
dependencies = [
"bitflags",
"libc",
@@ -724,9 +725,9 @@ checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "libgit2-sys"
version = "0.17.0+1.8.1"
version = "0.18.3+1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487"
dependencies = [
"cc",
"libc",

View File

@@ -19,7 +19,7 @@ tokio = { version = "1", features = ["full"] }
rusqlite = { version = "0.32", features = ["bundled"] }
# Git integration
git2 = "0.19"
git2 = "0.20"
# Serialization
serde = { version = "1", features = ["derive"] }
@@ -36,6 +36,7 @@ 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,25 @@ use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaneLayout {
#[default]
Horizontal,
Vertical,
Grid,
}
#[derive(Debug, Clone, 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,
@@ -11,7 +29,11 @@ pub struct Config {
pub session_timeout_secs: u64,
pub heartbeat_interval_secs: u64,
pub default_agent: String,
pub cost_budget_usd: f64,
pub token_budget: u64,
pub theme: Theme,
pub pane_layout: PaneLayout,
pub risk_thresholds: RiskThresholds,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -31,12 +53,22 @@ impl Default for Config {
session_timeout_secs: 3600,
heartbeat_interval_secs: 30,
default_agent: "claude".to_string(),
cost_budget_usd: 10.0,
token_budget: 500_000,
theme: Theme::Dark,
pane_layout: PaneLayout::Horizontal,
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 +84,61 @@ impl Config {
}
}
}
impl Default for RiskThresholds {
fn default() -> Self {
Config::RISK_THRESHOLDS
}
}
#[cfg(test)]
mod tests {
use super::{Config, PaneLayout};
#[test]
fn default_includes_positive_budget_thresholds() {
let config = Config::default();
assert!(config.cost_budget_usd > 0.0);
assert!(config.token_budget > 0);
}
#[test]
fn missing_budget_fields_fall_back_to_defaults() {
let legacy_config = 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"
"#;
let config: Config = toml::from_str(legacy_config).unwrap();
let defaults = Config::default();
assert_eq!(config.cost_budget_usd, defaults.cost_budget_usd);
assert_eq!(config.token_budget, defaults.token_budget);
assert_eq!(config.pane_layout, defaults.pane_layout);
assert_eq!(config.risk_thresholds, defaults.risk_thresholds);
}
#[test]
fn default_pane_layout_is_horizontal() {
assert_eq!(Config::default().pane_layout, PaneLayout::Horizontal);
}
#[test]
fn pane_layout_deserializes_from_toml() {
let config: Config = toml::from_str(r#"pane_layout = "grid""#).unwrap();
assert_eq!(config.pane_layout, PaneLayout::Grid);
}
#[test]
fn default_risk_thresholds_are_applied() {
assert_eq!(Config::default().risk_thresholds, Config::RISK_THRESHOLDS);
}
}

View File

@@ -1,12 +1,13 @@
mod comms;
mod config;
mod observability;
mod session;
mod tui;
mod worktree;
mod observability;
mod comms;
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use tracing_subscriber::EnvFilter;
#[derive(Parser, Debug)]
@@ -44,8 +45,24 @@ 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,
#[command(hide = true)]
RunSession {
#[arg(long)]
session_id: String,
#[arg(long)]
task: String,
#[arg(long)]
agent: String,
#[arg(long)]
cwd: PathBuf,
},
}
#[tokio::main]
@@ -63,10 +80,13 @@ async fn main() -> Result<()> {
Some(Commands::Dashboard) | None => {
tui::app::run(db, cfg).await?;
}
Some(Commands::Start { task, agent, worktree: use_worktree }) => {
let session_id = session::manager::create_session(
&db, &cfg, &task, &agent, use_worktree,
).await?;
Some(Commands::Start {
task,
agent,
worktree: use_worktree,
}) => {
let session_id =
session::manager::create_session(&db, &cfg, &task, &agent, use_worktree).await?;
println!("Session started: {session_id}");
}
Some(Commands::Sessions) => {
@@ -84,11 +104,39 @@ 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?;
}
Some(Commands::RunSession {
session_id,
task,
agent,
cwd,
}) => {
session::manager::run_session(&cfg, &session_id, &task, &agent, &cwd).await?;
}
}
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 anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use crate::config::{Config, RiskThresholds};
use crate::session::store::StateStore;
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -13,42 +14,396 @@ 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;
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();
// Destructive tools get higher base risk
match tool_name {
"Bash" => score += 0.3,
"Write" => score += 0.2,
"Edit" => score += 0.1,
_ => score += 0.05,
Self {
session_id: session_id.into(),
risk_score: Self::compute_risk(&tool_name, &input_summary, &Config::RISK_THRESHOLDS)
.score,
tool_name,
input_summary,
output_summary: output_summary.into(),
duration_ms,
}
}
/// 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();
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,
}
}
}
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(())
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))
}
#[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::{SuggestedAction, ToolCallEvent, ToolLogger};
use crate::config::Config;
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,
pid: None,
worktree: None,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
}
}
#[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);
}
#[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,6 +10,7 @@ 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);
@@ -23,6 +24,43 @@ 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()?;
@@ -38,9 +76,102 @@ fn check_sessions(db: &StateStore, timeout: Duration) -> Result<()> {
if elapsed > timeout {
tracing::warn!("Session {} timed out after {:?}", session.id, elapsed);
db.update_state(&session.id, &SessionState::Failed)?;
db.update_state_and_pid(&session.id, &SessionState::Failed, None)?;
}
}
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

@@ -1,9 +1,15 @@
use anyhow::Result;
use anyhow::{Context, Result};
use std::fmt;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use tokio::process::Command;
use super::{Session, SessionMetrics, SessionState};
use super::output::SessionOutputStore;
use super::runtime::capture_command_output;
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(
@@ -13,28 +19,9 @@ pub async fn create_session(
agent_type: &str,
use_worktree: bool,
) -> Result<String> {
let id = uuid::Uuid::new_v4().to_string()[..8].to_string();
let now = chrono::Utc::now();
let wt = if use_worktree {
Some(worktree::create_for_session(&id, cfg)?)
} else {
None
};
let session = Session {
id: id.clone(),
task: task.to_string(),
agent_type: agent_type.to_string(),
state: SessionState::Pending,
worktree: wt,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
};
db.insert_session(&session)?;
Ok(id)
let repo_root =
std::env::current_dir().context("Failed to resolve current working directory")?;
queue_session_in_dir(db, cfg, task, agent_type, use_worktree, &repo_root).await
}
pub fn list_sessions(db: &StateStore) -> Result<Vec<Session>> {
@@ -42,17 +29,345 @@ pub fn list_sessions(db: &StateStore) -> Result<Vec<Session>> {
}
pub fn get_status(db: &StateStore, id: &str) -> Result<SessionStatus> {
let session = db
.get_session(id)?
.ok_or_else(|| anyhow::anyhow!("Session not found: {id}"))?;
let session = resolve_session(db, id)?;
Ok(SessionStatus(session))
}
pub async fn stop_session(db: &StateStore, id: &str) -> Result<()> {
db.update_state(id, &SessionState::Stopped)?;
stop_session_with_options(db, id, true).await
}
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(session_id)?
.ok_or_else(|| anyhow::anyhow!("Session not found: {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)?;
Ok(entry)
}
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}"))?;
ToolLogger::new(db).query(&session.id, page, page_size)
}
pub async fn resume_session(db: &StateStore, id: &str) -> Result<String> {
let session = resolve_session(db, 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)
}
fn agent_program(agent_type: &str) -> Result<PathBuf> {
match agent_type {
"claude" => Ok(PathBuf::from("claude")),
other => anyhow::bail!("Unsupported agent type: {other}"),
}
}
fn resolve_session(db: &StateStore, id: &str) -> Result<Session> {
let session = if id == "latest" {
db.get_latest_session()?
} else {
db.get_session(id)?
};
session.ok_or_else(|| anyhow::anyhow!("Session not found: {id}"))
}
pub async fn run_session(
cfg: &Config,
session_id: &str,
task: &str,
agent_type: &str,
working_dir: &Path,
) -> Result<()> {
let db = StateStore::open(&cfg.db_path)?;
let session = resolve_session(&db, session_id)?;
if session.state != SessionState::Pending {
tracing::info!(
"Skipping run_session for {} because state is {}",
session_id,
session.state
);
return Ok(());
}
let agent_program = agent_program(agent_type)?;
let command = build_agent_command(&agent_program, task, session_id, working_dir);
capture_command_output(
cfg.db_path.clone(),
session_id.to_string(),
command,
SessionOutputStore::default(),
)
.await?;
Ok(())
}
async fn queue_session_in_dir(
db: &StateStore,
cfg: &Config,
task: &str,
agent_type: &str,
use_worktree: bool,
repo_root: &Path,
) -> Result<String> {
let session = build_session_record(task, agent_type, use_worktree, cfg, repo_root)?;
db.insert_session(&session)?;
let working_dir = session
.worktree
.as_ref()
.map(|worktree| worktree.path.as_path())
.unwrap_or(repo_root);
match spawn_session_runner(task, &session.id, agent_type, working_dir).await {
Ok(()) => Ok(session.id),
Err(error) => {
db.update_state(&session.id, &SessionState::Failed)?;
if let Some(worktree) = session.worktree.as_ref() {
let _ = crate::worktree::remove(&worktree.path);
}
Err(error.context(format!("Failed to queue session {}", session.id)))
}
}
}
fn build_session_record(
task: &str,
agent_type: &str,
use_worktree: bool,
cfg: &Config,
repo_root: &Path,
) -> Result<Session> {
let id = uuid::Uuid::new_v4().to_string()[..8].to_string();
let now = chrono::Utc::now();
let worktree = if use_worktree {
Some(worktree::create_for_session_in_repo(&id, cfg, repo_root)?)
} else {
None
};
Ok(Session {
id,
task: task.to_string(),
agent_type: agent_type.to_string(),
state: SessionState::Pending,
pid: None,
worktree,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
})
}
async fn create_session_in_dir(
db: &StateStore,
cfg: &Config,
task: &str,
agent_type: &str,
use_worktree: bool,
repo_root: &Path,
agent_program: &Path,
) -> Result<String> {
let session = build_session_record(task, agent_type, use_worktree, cfg, repo_root)?;
db.insert_session(&session)?;
let working_dir = session
.worktree
.as_ref()
.map(|worktree| worktree.path.as_path())
.unwrap_or(repo_root);
match spawn_claude_code(agent_program, task, &session.id, working_dir).await {
Ok(pid) => {
db.update_pid(&session.id, Some(pid))?;
db.update_state(&session.id, &SessionState::Running)?;
Ok(session.id)
}
Err(error) => {
db.update_state(&session.id, &SessionState::Failed)?;
if let Some(worktree) = session.worktree.as_ref() {
let _ = crate::worktree::remove(&worktree.path);
}
Err(error.context(format!("Failed to start session {}", session.id)))
}
}
}
async fn spawn_session_runner(
task: &str,
session_id: &str,
agent_type: &str,
working_dir: &Path,
) -> Result<()> {
let current_exe = std::env::current_exe().context("Failed to resolve ECC executable path")?;
let child = Command::new(&current_exe)
.arg("run-session")
.arg("--session-id")
.arg(session_id)
.arg("--task")
.arg(task)
.arg("--agent")
.arg(agent_type)
.arg("--cwd")
.arg(working_dir)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.with_context(|| {
format!(
"Failed to spawn ECC runner from {}",
current_exe.display()
)
})?;
child
.id()
.ok_or_else(|| anyhow::anyhow!("ECC runner did not expose a process id"))?;
Ok(())
}
fn build_agent_command(agent_program: &Path, task: &str, session_id: &str, working_dir: &Path) -> Command {
let mut command = Command::new(agent_program);
command
.arg("--print")
.arg("--name")
.arg(format!("ecc-{session_id}"))
.arg(task)
.current_dir(working_dir)
.stdin(Stdio::null());
command
}
async fn spawn_claude_code(
agent_program: &Path,
task: &str,
session_id: &str,
working_dir: &Path,
) -> Result<u32> {
let mut command = build_agent_command(agent_program, task, session_id, working_dir);
let child = command
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.with_context(|| {
format!(
"Failed to spawn Claude Code from {}",
agent_program.display()
)
})?;
child
.id()
.ok_or_else(|| anyhow::anyhow!("Claude Code did not expose a process id"))
}
async fn stop_session_with_options(
db: &StateStore,
id: &str,
cleanup_worktree: bool,
) -> Result<()> {
let session = resolve_session(db, id)?;
if let Some(pid) = session.pid {
kill_process(pid).await?;
}
db.update_pid(&session.id, None)?;
db.update_state(&session.id, &SessionState::Stopped)?;
if cleanup_worktree {
if let Some(worktree) = session.worktree.as_ref() {
crate::worktree::remove(&worktree.path)?;
}
}
Ok(())
}
#[cfg(unix)]
async fn kill_process(pid: u32) -> Result<()> {
send_signal(pid, libc::SIGTERM)?;
tokio::time::sleep(std::time::Duration::from_millis(1200)).await;
send_signal(pid, libc::SIGKILL)?;
Ok(())
}
#[cfg(unix)]
fn send_signal(pid: u32, signal: i32) -> Result<()> {
let outcome = unsafe { libc::kill(pid as i32, signal) };
if outcome == 0 {
return Ok(());
}
let error = std::io::Error::last_os_error();
if error.raw_os_error() == Some(libc::ESRCH) {
return Ok(());
}
Err(error).with_context(|| format!("Failed to kill process {pid}"))
}
#[cfg(not(unix))]
async fn kill_process(pid: u32) -> Result<()> {
let status = Command::new("taskkill")
.args(["/F", "/PID", &pid.to_string()])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.await
.with_context(|| format!("Failed to invoke taskkill for process {pid}"))?;
if status.success() {
Ok(())
} else {
anyhow::bail!("taskkill failed for process {pid}");
}
}
pub struct SessionStatus(Session);
impl fmt::Display for SessionStatus {
@@ -62,6 +377,9 @@ 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())?;
@@ -74,3 +392,289 @@ impl fmt::Display for SessionStatus {
write!(f, "Updated: {}", s.updated_at)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{Config, PaneLayout, Theme};
use crate::session::{Session, SessionMetrics, SessionState};
use anyhow::{Context, Result};
use chrono::{Duration, Utc};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Command as StdCommand;
use std::thread;
use std::time::Duration as StdDuration;
struct TestDir {
path: PathBuf,
}
impl TestDir {
fn new(label: &str) -> Result<Self> {
let path =
std::env::temp_dir().join(format!("ecc2-{}-{}", label, uuid::Uuid::new_v4()));
fs::create_dir_all(&path)?;
Ok(Self { path })
}
fn path(&self) -> &Path {
&self.path
}
}
impl Drop for TestDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.path);
}
}
fn build_config(root: &Path) -> Config {
Config {
db_path: root.join("state.db"),
worktree_root: root.join("worktrees"),
max_parallel_sessions: 4,
max_parallel_worktrees: 4,
session_timeout_secs: 60,
heartbeat_interval_secs: 5,
default_agent: "claude".to_string(),
cost_budget_usd: 10.0,
token_budget: 500_000,
theme: Theme::Dark,
pane_layout: PaneLayout::Horizontal,
risk_thresholds: Config::RISK_THRESHOLDS,
}
}
fn build_session(id: &str, state: SessionState, updated_at: chrono::DateTime<Utc>) -> Session {
Session {
id: id.to_string(),
task: format!("task-{id}"),
agent_type: "claude".to_string(),
state,
pid: None,
worktree: None,
created_at: updated_at - Duration::minutes(1),
updated_at,
metrics: SessionMetrics::default(),
}
}
fn init_git_repo(path: &Path) -> Result<()> {
fs::create_dir_all(path)?;
run_git(path, ["init", "-q"])?;
fs::write(path.join("README.md"), "hello\n")?;
run_git(path, ["add", "README.md"])?;
run_git(
path,
[
"-c",
"user.name=ECC Tests",
"-c",
"user.email=ecc-tests@example.com",
"commit",
"-qm",
"init",
],
)?;
Ok(())
}
fn run_git<const N: usize>(path: &Path, args: [&str; N]) -> Result<()> {
let status = StdCommand::new("git")
.args(args)
.current_dir(path)
.status()
.with_context(|| format!("failed to run git in {}", path.display()))?;
if !status.success() {
anyhow::bail!("git command failed in {}", path.display());
}
Ok(())
}
fn write_fake_claude(root: &Path) -> Result<(PathBuf, PathBuf)> {
let script_path = root.join("fake-claude.sh");
let log_path = root.join("fake-claude.log");
let script = format!(
"#!/usr/bin/env python3\nimport os\nimport pathlib\nimport signal\nimport sys\nimport time\n\nlog_path = pathlib.Path(r\"{}\")\nlog_path.write_text(os.getcwd() + \"\\n\", encoding=\"utf-8\")\nwith log_path.open(\"a\", encoding=\"utf-8\") as handle:\n handle.write(\" \".join(sys.argv[1:]) + \"\\n\")\n\ndef handle_term(signum, frame):\n raise SystemExit(0)\n\nsignal.signal(signal.SIGTERM, handle_term)\nwhile True:\n time.sleep(0.1)\n",
log_path.display()
);
fs::write(&script_path, script)?;
let mut permissions = fs::metadata(&script_path)?.permissions();
permissions.set_mode(0o755);
fs::set_permissions(&script_path, permissions)?;
Ok((script_path, log_path))
}
fn wait_for_file(path: &Path) -> Result<String> {
for _ in 0..50 {
if path.exists() {
return fs::read_to_string(path)
.with_context(|| format!("failed to read {}", path.display()));
}
thread::sleep(StdDuration::from_millis(20));
}
anyhow::bail!("timed out waiting for {}", path.display());
}
#[tokio::test(flavor = "current_thread")]
async fn create_session_spawns_process_and_marks_session_running() -> Result<()> {
let tempdir = TestDir::new("manager-create-session")?;
let repo_root = tempdir.path().join("repo");
init_git_repo(&repo_root)?;
let cfg = build_config(tempdir.path());
let db = StateStore::open(&cfg.db_path)?;
let (fake_claude, log_path) = write_fake_claude(tempdir.path())?;
let session_id = create_session_in_dir(
&db,
&cfg,
"implement lifecycle",
"claude",
false,
&repo_root,
&fake_claude,
)
.await?;
let session = db
.get_session(&session_id)?
.context("session should exist")?;
assert_eq!(session.state, SessionState::Running);
assert!(
session.pid.is_some(),
"spawned session should persist a pid"
);
let log = wait_for_file(&log_path)?;
assert!(log.contains(repo_root.to_string_lossy().as_ref()));
assert!(log.contains("--print"));
assert!(log.contains("implement lifecycle"));
stop_session_with_options(&db, &session_id, false).await?;
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn stop_session_kills_process_and_optionally_cleans_worktree() -> Result<()> {
let tempdir = TestDir::new("manager-stop-session")?;
let repo_root = tempdir.path().join("repo");
init_git_repo(&repo_root)?;
let cfg = build_config(tempdir.path());
let db = StateStore::open(&cfg.db_path)?;
let (fake_claude, _) = write_fake_claude(tempdir.path())?;
let keep_id = create_session_in_dir(
&db,
&cfg,
"keep worktree",
"claude",
true,
&repo_root,
&fake_claude,
)
.await?;
let keep_session = db.get_session(&keep_id)?.context("keep session missing")?;
keep_session.pid.context("keep session pid missing")?;
let keep_worktree = keep_session
.worktree
.clone()
.context("keep session worktree missing")?
.path;
stop_session_with_options(&db, &keep_id, false).await?;
let stopped_keep = db
.get_session(&keep_id)?
.context("stopped keep session missing")?;
assert_eq!(stopped_keep.state, SessionState::Stopped);
assert_eq!(stopped_keep.pid, None);
assert!(
keep_worktree.exists(),
"worktree should remain when cleanup is disabled"
);
let cleanup_id = create_session_in_dir(
&db,
&cfg,
"cleanup worktree",
"claude",
true,
&repo_root,
&fake_claude,
)
.await?;
let cleanup_session = db
.get_session(&cleanup_id)?
.context("cleanup session missing")?;
let cleanup_worktree = cleanup_session
.worktree
.clone()
.context("cleanup session worktree missing")?
.path;
stop_session_with_options(&db, &cleanup_id, true).await?;
assert!(
!cleanup_worktree.exists(),
"worktree should be removed when cleanup is enabled"
);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn resume_session_requeues_failed_session() -> Result<()> {
let tempdir = TestDir::new("manager-resume-session")?;
let cfg = build_config(tempdir.path());
let db = StateStore::open(&cfg.db_path)?;
let now = Utc::now();
db.insert_session(&Session {
id: "deadbeef".to_string(),
task: "resume previous task".to_string(),
agent_type: "claude".to_string(),
state: SessionState::Failed,
pid: Some(31337),
worktree: None,
created_at: now - Duration::minutes(1),
updated_at: now,
metrics: SessionMetrics::default(),
})?;
let resumed_id = resume_session(&db, "deadbeef").await?;
let resumed = db
.get_session(&resumed_id)?
.context("resumed session should exist")?;
assert_eq!(resumed.state, SessionState::Pending);
assert_eq!(resumed.pid, None);
Ok(())
}
#[test]
fn get_status_supports_latest_alias() -> Result<()> {
let tempdir = TestDir::new("manager-latest-status")?;
let cfg = build_config(tempdir.path());
let db = StateStore::open(&cfg.db_path)?;
let older = Utc::now() - Duration::minutes(2);
let newer = Utc::now();
db.insert_session(&build_session("older", SessionState::Running, older))?;
db.insert_session(&build_session("newer", SessionState::Idle, newer))?;
let status = get_status(&db, "latest")?;
assert_eq!(status.0.id, "newer");
Ok(())
}
}

View File

@@ -1,5 +1,7 @@
pub mod daemon;
pub mod manager;
pub mod output;
pub mod runtime;
pub mod store;
use chrono::{DateTime, Utc};
@@ -13,13 +15,14 @@ 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>,
pub metrics: SessionMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SessionState {
Pending,
Running,
@@ -42,6 +45,46 @@ impl fmt::Display for SessionState {
}
}
impl SessionState {
pub fn can_transition_to(&self, next: &Self) -> bool {
if self == next {
return true;
}
matches!(
(self, next),
(
SessionState::Pending,
SessionState::Running | SessionState::Failed | SessionState::Stopped
) | (
SessionState::Running,
SessionState::Idle
| SessionState::Completed
| SessionState::Failed
| SessionState::Stopped
) | (
SessionState::Idle,
SessionState::Running
| SessionState::Completed
| SessionState::Failed
| SessionState::Stopped
) | (SessionState::Completed, SessionState::Stopped)
| (SessionState::Failed, SessionState::Stopped)
)
}
pub fn from_db_value(value: &str) -> Self {
match value {
"running" => SessionState::Running,
"idle" => SessionState::Idle,
"completed" => SessionState::Completed,
"failed" => SessionState::Failed,
"stopped" => SessionState::Stopped,
_ => SessionState::Pending,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorktreeInfo {
pub path: PathBuf,

149
ecc2/src/session/output.rs Normal file
View File

@@ -0,0 +1,149 @@
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex, MutexGuard};
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
pub const OUTPUT_BUFFER_LIMIT: usize = 1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OutputStream {
Stdout,
Stderr,
}
impl OutputStream {
pub fn as_str(self) -> &'static str {
match self {
Self::Stdout => "stdout",
Self::Stderr => "stderr",
}
}
pub fn from_db_value(value: &str) -> Self {
match value {
"stderr" => Self::Stderr,
_ => Self::Stdout,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OutputLine {
pub stream: OutputStream,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputEvent {
pub session_id: String,
pub line: OutputLine,
}
#[derive(Clone)]
pub struct SessionOutputStore {
capacity: usize,
buffers: Arc<Mutex<HashMap<String, VecDeque<OutputLine>>>>,
tx: broadcast::Sender<OutputEvent>,
}
impl Default for SessionOutputStore {
fn default() -> Self {
Self::new(OUTPUT_BUFFER_LIMIT)
}
}
impl SessionOutputStore {
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
let (tx, _) = broadcast::channel(capacity.max(16));
Self {
capacity,
buffers: Arc::new(Mutex::new(HashMap::new())),
tx,
}
}
pub fn subscribe(&self) -> broadcast::Receiver<OutputEvent> {
self.tx.subscribe()
}
pub fn push_line(&self, session_id: &str, stream: OutputStream, text: impl Into<String>) {
let line = OutputLine {
stream,
text: text.into(),
};
{
let mut buffers = self.lock_buffers();
let buffer = buffers.entry(session_id.to_string()).or_default();
buffer.push_back(line.clone());
while buffer.len() > self.capacity {
let _ = buffer.pop_front();
}
}
let _ = self.tx.send(OutputEvent {
session_id: session_id.to_string(),
line,
});
}
pub fn replace_lines(&self, session_id: &str, lines: Vec<OutputLine>) {
let mut buffer: VecDeque<OutputLine> = lines.into_iter().collect();
while buffer.len() > self.capacity {
let _ = buffer.pop_front();
}
self.lock_buffers().insert(session_id.to_string(), buffer);
}
pub fn lines(&self, session_id: &str) -> Vec<OutputLine> {
self.lock_buffers()
.get(session_id)
.map(|buffer| buffer.iter().cloned().collect())
.unwrap_or_default()
}
fn lock_buffers(&self) -> MutexGuard<'_, HashMap<String, VecDeque<OutputLine>>> {
self.buffers
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
#[cfg(test)]
mod tests {
use super::{OutputStream, SessionOutputStore};
#[test]
fn ring_buffer_keeps_most_recent_lines() {
let store = SessionOutputStore::new(3);
store.push_line("session-1", OutputStream::Stdout, "line-1");
store.push_line("session-1", OutputStream::Stdout, "line-2");
store.push_line("session-1", OutputStream::Stdout, "line-3");
store.push_line("session-1", OutputStream::Stdout, "line-4");
let lines = store.lines("session-1");
let texts: Vec<_> = lines.iter().map(|line| line.text.as_str()).collect();
assert_eq!(texts, vec!["line-2", "line-3", "line-4"]);
}
#[tokio::test]
async fn pushing_output_broadcasts_events() {
let store = SessionOutputStore::new(8);
let mut rx = store.subscribe();
store.push_line("session-1", OutputStream::Stderr, "problem");
let event = rx.recv().await.expect("broadcast event");
assert_eq!(event.session_id, "session-1");
assert_eq!(event.line.stream, OutputStream::Stderr);
assert_eq!(event.line.text, "problem");
}
}

290
ecc2/src/session/runtime.rs Normal file
View File

@@ -0,0 +1,290 @@
use std::path::PathBuf;
use std::process::{ExitStatus, Stdio};
use anyhow::{Context, Result};
use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader};
use tokio::process::Command;
use tokio::sync::{mpsc, oneshot};
use super::output::{OutputStream, SessionOutputStore};
use super::store::StateStore;
use super::SessionState;
type DbAck = std::result::Result<(), String>;
enum DbMessage {
UpdateState {
state: SessionState,
ack: oneshot::Sender<DbAck>,
},
UpdatePid {
pid: Option<u32>,
ack: oneshot::Sender<DbAck>,
},
AppendOutputLine {
stream: OutputStream,
line: String,
ack: oneshot::Sender<DbAck>,
},
}
#[derive(Clone)]
struct DbWriter {
tx: mpsc::UnboundedSender<DbMessage>,
}
impl DbWriter {
fn start(db_path: PathBuf, session_id: String) -> Self {
let (tx, rx) = mpsc::unbounded_channel();
std::thread::spawn(move || run_db_writer(db_path, session_id, rx));
Self { tx }
}
async fn update_state(&self, state: SessionState) -> Result<()> {
self.send(|ack| DbMessage::UpdateState { state, ack }).await
}
async fn update_pid(&self, pid: Option<u32>) -> Result<()> {
self.send(|ack| DbMessage::UpdatePid { pid, ack }).await
}
async fn append_output_line(&self, stream: OutputStream, line: String) -> Result<()> {
self.send(|ack| DbMessage::AppendOutputLine { stream, line, ack })
.await
}
async fn send<F>(&self, build: F) -> Result<()>
where
F: FnOnce(oneshot::Sender<DbAck>) -> DbMessage,
{
let (ack_tx, ack_rx) = oneshot::channel();
self.tx
.send(build(ack_tx))
.map_err(|_| anyhow::anyhow!("DB writer channel closed"))?;
match ack_rx.await {
Ok(Ok(())) => Ok(()),
Ok(Err(error)) => Err(anyhow::anyhow!(error)),
Err(_) => Err(anyhow::anyhow!("DB writer acknowledgement dropped")),
}
}
}
fn run_db_writer(
db_path: PathBuf,
session_id: String,
mut rx: mpsc::UnboundedReceiver<DbMessage>,
) {
let (opened, open_error) = match StateStore::open(&db_path) {
Ok(db) => (Some(db), None),
Err(error) => (None, Some(error.to_string())),
};
while let Some(message) = rx.blocking_recv() {
match message {
DbMessage::UpdateState { state, ack } => {
let result = match opened.as_ref() {
Some(db) => db.update_state(&session_id, &state).map_err(|error| error.to_string()),
None => Err(open_error
.clone()
.unwrap_or_else(|| "Failed to open state store".to_string())),
};
let _ = ack.send(result);
}
DbMessage::UpdatePid { pid, ack } => {
let result = match opened.as_ref() {
Some(db) => db.update_pid(&session_id, pid).map_err(|error| error.to_string()),
None => Err(open_error
.clone()
.unwrap_or_else(|| "Failed to open state store".to_string())),
};
let _ = ack.send(result);
}
DbMessage::AppendOutputLine { stream, line, ack } => {
let result = match opened.as_ref() {
Some(db) => db
.append_output_line(&session_id, stream, &line)
.map_err(|error| error.to_string()),
None => Err(open_error
.clone()
.unwrap_or_else(|| "Failed to open state store".to_string())),
};
let _ = ack.send(result);
}
}
}
}
pub async fn capture_command_output(
db_path: PathBuf,
session_id: String,
mut command: Command,
output_store: SessionOutputStore,
) -> Result<ExitStatus> {
let db_writer = DbWriter::start(db_path, session_id.clone());
let result = async {
let mut child = command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("Failed to start process for session {}", session_id))?;
let stdout = match child.stdout.take() {
Some(stdout) => stdout,
None => {
let _ = child.kill().await;
let _ = child.wait().await;
anyhow::bail!("Child stdout was not piped");
}
};
let stderr = match child.stderr.take() {
Some(stderr) => stderr,
None => {
let _ = child.kill().await;
let _ = child.wait().await;
anyhow::bail!("Child stderr was not piped");
}
};
let pid = child
.id()
.ok_or_else(|| anyhow::anyhow!("Spawned process did not expose a process id"))?;
db_writer.update_pid(Some(pid)).await?;
db_writer.update_state(SessionState::Running).await?;
let stdout_task = tokio::spawn(capture_stream(
session_id.clone(),
stdout,
OutputStream::Stdout,
output_store.clone(),
db_writer.clone(),
));
let stderr_task = tokio::spawn(capture_stream(
session_id.clone(),
stderr,
OutputStream::Stderr,
output_store,
db_writer.clone(),
));
let status = child.wait().await?;
stdout_task.await??;
stderr_task.await??;
let final_state = if status.success() {
SessionState::Completed
} else {
SessionState::Failed
};
db_writer.update_pid(None).await?;
db_writer.update_state(final_state).await?;
Ok(status)
}
.await;
if result.is_err() {
let _ = db_writer.update_pid(None).await;
let _ = db_writer.update_state(SessionState::Failed).await;
}
result
}
async fn capture_stream<R>(
session_id: String,
reader: R,
stream: OutputStream,
output_store: SessionOutputStore,
db_writer: DbWriter,
) -> Result<()>
where
R: AsyncRead + Unpin,
{
let mut lines = BufReader::new(reader).lines();
while let Some(line) = lines.next_line().await? {
db_writer
.append_output_line(stream, line.clone())
.await?;
output_store.push_line(&session_id, stream, line);
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::env;
use anyhow::Result;
use chrono::Utc;
use tokio::process::Command;
use uuid::Uuid;
use super::capture_command_output;
use crate::session::output::{SessionOutputStore, OUTPUT_BUFFER_LIMIT};
use crate::session::store::StateStore;
use crate::session::{Session, SessionMetrics, SessionState};
#[tokio::test]
async fn capture_command_output_persists_lines_and_events() -> Result<()> {
let db_path = env::temp_dir().join(format!("ecc2-runtime-{}.db", Uuid::new_v4()));
let db = StateStore::open(&db_path)?;
let session_id = "session-1".to_string();
let now = Utc::now();
db.insert_session(&Session {
id: session_id.clone(),
task: "stream output".to_string(),
agent_type: "test".to_string(),
state: SessionState::Pending,
pid: None,
worktree: None,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
})?;
let output_store = SessionOutputStore::default();
let mut rx = output_store.subscribe();
let mut command = Command::new("/bin/sh");
command
.arg("-c")
.arg("printf 'alpha\\n'; printf 'beta\\n' >&2");
let status =
capture_command_output(db_path.clone(), session_id.clone(), command, output_store)
.await?;
assert!(status.success());
let db = StateStore::open(&db_path)?;
let session = db
.get_session(&session_id)?
.expect("session should still exist");
assert_eq!(session.state, SessionState::Completed);
assert_eq!(session.pid, None);
let lines = db.get_output_lines(&session_id, OUTPUT_BUFFER_LIMIT)?;
let texts: HashSet<_> = lines.iter().map(|line| line.text.as_str()).collect();
assert_eq!(lines.len(), 2);
assert!(texts.contains("alpha"));
assert!(texts.contains("beta"));
let mut events = Vec::new();
while let Ok(event) = rx.try_recv() {
events.push(event.line.text);
}
assert_eq!(events.len(), 2);
assert!(events.iter().any(|line| line == "alpha"));
assert!(events.iter().any(|line| line == "beta"));
let _ = std::fs::remove_file(db_path);
Ok(())
}
}

View File

@@ -1,7 +1,11 @@
use anyhow::Result;
use rusqlite::Connection;
use std::path::Path;
use anyhow::{Context, Result};
use rusqlite::{Connection, OptionalExtension};
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::observability::{ToolLogEntry, ToolLogPage};
use super::output::{OutputLine, OutputStream, OUTPUT_BUFFER_LIMIT};
use super::{Session, SessionMetrics, SessionState};
pub struct StateStore {
@@ -11,6 +15,8 @@ pub struct StateStore {
impl StateStore {
pub fn open(path: &Path) -> Result<Self> {
let conn = Connection::open(path)?;
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
conn.busy_timeout(Duration::from_secs(5))?;
let store = Self { conn };
store.init_schema()?;
Ok(store)
@@ -24,6 +30,7 @@ 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,
@@ -57,24 +64,59 @@ impl StateStore {
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS session_output (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL REFERENCES sessions(id),
stream TEXT NOT NULL,
line TEXT NOT NULL,
timestamp TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_sessions_state ON sessions(state);
CREATE INDEX IF NOT EXISTS idx_tool_log_session ON tool_log(session_id);
CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_session, read);
CREATE INDEX IF NOT EXISTS idx_session_output_session
ON session_output(session_id, id);
",
)?;
self.ensure_session_columns()?;
Ok(())
}
fn ensure_session_columns(&self) -> Result<()> {
if !self.has_column("sessions", "pid")? {
self.conn
.execute("ALTER TABLE sessions ADD COLUMN pid INTEGER", [])
.context("Failed to add pid column to sessions table")?;
}
Ok(())
}
fn has_column(&self, table: &str, column: &str) -> Result<bool> {
let pragma = format!("PRAGMA table_info({table})");
let mut stmt = self.conn.prepare(&pragma)?;
let columns = stmt
.query_map([], |row| row.get::<_, String>(1))?
.collect::<std::result::Result<Vec<_>, _>>()?;
Ok(columns.iter().any(|existing| existing == column))
}
pub fn insert_session(&self, session: &Session) -> Result<()> {
self.conn.execute(
"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)",
"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)",
rusqlite::params![
session.id,
session.task,
session.agent_type,
session.state.to_string(),
session.worktree.as_ref().map(|w| w.path.to_string_lossy().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()),
session.created_at.to_rfc3339(),
@@ -84,8 +126,50 @@ impl StateStore {
Ok(())
}
pub fn update_state_and_pid(
&self,
session_id: &str,
state: &SessionState,
pid: Option<u32>,
) -> Result<()> {
let updated = 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,
],
)?;
if updated == 0 {
anyhow::bail!("Session not found: {session_id}");
}
Ok(())
}
pub fn update_state(&self, session_id: &str, state: &SessionState) -> Result<()> {
self.conn.execute(
let current_state = self
.conn
.query_row(
"SELECT state FROM sessions WHERE id = ?1",
[session_id],
|row| row.get::<_, String>(0),
)
.optional()?
.map(|raw| SessionState::from_db_value(&raw))
.ok_or_else(|| anyhow::anyhow!("Session not found: {session_id}"))?;
if !current_state.can_transition_to(state) {
anyhow::bail!(
"Invalid session state transition: {} -> {}",
current_state,
state
);
}
let updated = self.conn.execute(
"UPDATE sessions SET state = ?1, updated_at = ?2 WHERE id = ?3",
rusqlite::params![
state.to_string(),
@@ -93,6 +177,28 @@ impl StateStore {
session_id,
],
)?;
if updated == 0 {
anyhow::bail!("Session not found: {session_id}");
}
Ok(())
}
pub fn update_pid(&self, session_id: &str, pid: Option<u32>) -> Result<()> {
let updated = self.conn.execute(
"UPDATE sessions SET pid = ?1, updated_at = ?2 WHERE id = ?3",
rusqlite::params![
pid.map(i64::from),
chrono::Utc::now().to_rfc3339(),
session_id,
],
)?;
if updated == 0 {
anyhow::bail!("Session not found: {session_id}");
}
Ok(())
}
@@ -112,9 +218,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, worktree_path, worktree_branch, worktree_base,
"SELECT id, task, agent_type, state, pid, 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",
@@ -123,30 +237,24 @@ impl StateStore {
let sessions = stmt
.query_map([], |row| {
let state_str: String = row.get(3)?;
let state = match state_str.as_str() {
"running" => SessionState::Running,
"idle" => SessionState::Idle,
"completed" => SessionState::Completed,
"failed" => SessionState::Failed,
"stopped" => SessionState::Stopped,
_ => SessionState::Pending,
};
let state = SessionState::from_db_value(&state_str);
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>(5).unwrap_or_default(),
base_branch: row.get::<_, String>(6).unwrap_or_default(),
let worktree_path: Option<String> = row.get(5)?;
let worktree = worktree_path.map(|path| super::WorktreeInfo {
path: PathBuf::from(path),
branch: row.get::<_, String>(6).unwrap_or_default(),
base_branch: row.get::<_, String>(7).unwrap_or_default(),
});
let created_str: String = row.get(12)?;
let updated_str: String = row.get(13)?;
let created_str: String = row.get(13)?;
let updated_str: String = row.get(14)?;
Ok(Session {
id: row.get(0)?,
task: row.get(1)?,
agent_type: row.get(2)?,
state,
pid: row.get::<_, Option<u32>>(4)?,
worktree,
created_at: chrono::DateTime::parse_from_rfc3339(&created_str)
.unwrap_or_default()
@@ -155,11 +263,11 @@ impl StateStore {
.unwrap_or_default()
.with_timezone(&chrono::Utc),
metrics: SessionMetrics {
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)?,
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)?,
},
})
})?
@@ -168,18 +276,18 @@ impl StateStore {
Ok(sessions)
}
pub fn get_session(&self, id: &str) -> Result<Option<Session>> {
let sessions = self.list_sessions()?;
Ok(sessions.into_iter().find(|s| s.id == id || s.id.starts_with(id)))
pub fn get_latest_session(&self) -> Result<Option<Session>> {
Ok(self.list_sessions()?.into_iter().next())
}
pub fn send_message(
&self,
from: &str,
to: &str,
content: &str,
msg_type: &str,
) -> Result<()> {
pub fn get_session(&self, id: &str) -> Result<Option<Session>> {
let sessions = self.list_sessions()?;
Ok(sessions
.into_iter()
.find(|session| session.id == id || session.id.starts_with(id)))
}
pub fn send_message(&self, from: &str, to: &str, content: &str, msg_type: &str) -> Result<()> {
self.conn.execute(
"INSERT INTO messages (from_session, to_session, content, msg_type, timestamp)
VALUES (?1, ?2, ?3, ?4, ?5)",
@@ -187,4 +295,282 @@ impl StateStore {
)?;
Ok(())
}
pub fn append_output_line(
&self,
session_id: &str,
stream: OutputStream,
line: &str,
) -> Result<()> {
let now = chrono::Utc::now().to_rfc3339();
self.conn.execute(
"INSERT INTO session_output (session_id, stream, line, timestamp)
VALUES (?1, ?2, ?3, ?4)",
rusqlite::params![session_id, stream.as_str(), line, now],
)?;
self.conn.execute(
"DELETE FROM session_output
WHERE session_id = ?1
AND id NOT IN (
SELECT id
FROM session_output
WHERE session_id = ?1
ORDER BY id DESC
LIMIT ?2
)",
rusqlite::params![session_id, OUTPUT_BUFFER_LIMIT as i64],
)?;
self.conn.execute(
"UPDATE sessions SET updated_at = ?1 WHERE id = ?2",
rusqlite::params![chrono::Utc::now().to_rfc3339(), session_id],
)?;
Ok(())
}
pub fn get_output_lines(&self, session_id: &str, limit: usize) -> Result<Vec<OutputLine>> {
let mut stmt = self.conn.prepare(
"SELECT stream, line
FROM (
SELECT id, stream, line
FROM session_output
WHERE session_id = ?1
ORDER BY id DESC
LIMIT ?2
)
ORDER BY id ASC",
)?;
let lines = stmt
.query_map(rusqlite::params![session_id, limit as i64], |row| {
let stream: String = row.get(0)?;
let text: String = row.get(1)?;
Ok(OutputLine {
stream: OutputStream::from_db_value(&stream),
text,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(lines)
}
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![
session_id,
tool_name,
input_summary,
output_summary,
duration_ms,
risk_score,
timestamp,
],
)?;
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(),
})
}
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;
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 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 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,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration as ChronoDuration, Utc};
use std::fs;
struct TestDir {
path: PathBuf,
}
impl TestDir {
fn new(label: &str) -> Result<Self> {
let path =
std::env::temp_dir().join(format!("ecc2-{}-{}", label, uuid::Uuid::new_v4()));
fs::create_dir_all(&path)?;
Ok(Self { path })
}
fn path(&self) -> &Path {
&self.path
}
}
impl Drop for TestDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.path);
}
}
fn build_session(id: &str, state: SessionState) -> Session {
let now = Utc::now();
Session {
id: id.to_string(),
task: "task".to_string(),
agent_type: "claude".to_string(),
state,
pid: None,
worktree: None,
created_at: now - ChronoDuration::minutes(1),
updated_at: now,
metrics: SessionMetrics::default(),
}
}
#[test]
fn update_state_rejects_invalid_terminal_transition() -> Result<()> {
let tempdir = TestDir::new("store-invalid-transition")?;
let db = StateStore::open(&tempdir.path().join("state.db"))?;
db.insert_session(&build_session("done", SessionState::Completed))?;
let error = db
.update_state("done", &SessionState::Running)
.expect_err("completed sessions must not transition back to running");
assert!(error
.to_string()
.contains("Invalid session state transition"));
Ok(())
}
#[test]
fn open_migrates_existing_sessions_table_with_pid_column() -> Result<()> {
let tempdir = TestDir::new("store-migration")?;
let db_path = tempdir.path().join("state.db");
let conn = Connection::open(&db_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
);
",
)?;
drop(conn);
let db = StateStore::open(&db_path)?;
let mut stmt = db.conn.prepare("PRAGMA table_info(sessions)")?;
let column_names = stmt
.query_map([], |row| row.get::<_, String>(1))?
.collect::<std::result::Result<Vec<_>, _>>()?;
assert!(column_names.iter().any(|column| column == "pid"));
Ok(())
}
#[test]
fn append_output_line_keeps_latest_buffer_window() -> Result<()> {
let tempdir = TestDir::new("store-output")?;
let db = StateStore::open(&tempdir.path().join("state.db"))?;
let now = Utc::now();
db.insert_session(&Session {
id: "session-1".to_string(),
task: "buffer output".to_string(),
agent_type: "claude".to_string(),
state: SessionState::Running,
pid: None,
worktree: None,
created_at: now,
updated_at: now,
metrics: SessionMetrics::default(),
})?;
for index in 0..(OUTPUT_BUFFER_LIMIT + 5) {
db.append_output_line("session-1", OutputStream::Stdout, &format!("line-{index}"))?;
}
let lines = db.get_output_lines("session-1", OUTPUT_BUFFER_LIMIT)?;
let texts: Vec<_> = lines.iter().map(|line| line.text.as_str()).collect();
assert_eq!(lines.len(), OUTPUT_BUFFER_LIMIT);
assert_eq!(texts.first().copied(), Some("line-5"));
let expected_last_line = format!("line-{}", OUTPUT_BUFFER_LIMIT + 4);
assert_eq!(texts.last().copied(), Some(expected_last_line.as_str()));
Ok(())
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,281 @@
// Custom TUI widgets for ECC 2.0
// TODO: Implement custom widgets:
// - TokenMeter: visual token usage bar with budget threshold
// - DiffViewer: side-by-side syntax-highlighted diff display
// - ProgressTimeline: session timeline with tool call markers
// - AgentTree: hierarchical view of parent/child agent sessions
use ratatui::{
prelude::*,
text::{Line, Span},
widgets::{Gauge, Paragraph, Widget},
};
pub(crate) const WARNING_THRESHOLD: f64 = 0.8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum BudgetState {
Unconfigured,
Normal,
Warning,
OverBudget,
}
impl BudgetState {
pub(crate) const fn is_warning(self) -> bool {
matches!(self, Self::Warning | Self::OverBudget)
}
fn badge(self) -> Option<&'static str> {
match self {
Self::Warning => Some("warning"),
Self::OverBudget => Some("over budget"),
Self::Unconfigured => Some("no budget"),
Self::Normal => None,
}
}
pub(crate) fn style(self) -> Style {
let base = Style::default().fg(match self {
Self::Unconfigured => Color::DarkGray,
Self::Normal => Color::DarkGray,
Self::Warning => Color::Yellow,
Self::OverBudget => Color::Red,
});
if self.is_warning() {
base.add_modifier(Modifier::BOLD)
} else {
base
}
}
}
#[derive(Debug, Clone, Copy)]
enum MeterFormat {
Tokens,
Currency,
}
#[derive(Debug, Clone)]
pub(crate) struct TokenMeter<'a> {
title: &'a str,
used: f64,
budget: f64,
format: MeterFormat,
}
impl<'a> TokenMeter<'a> {
pub(crate) fn tokens(title: &'a str, used: u64, budget: u64) -> Self {
Self {
title,
used: used as f64,
budget: budget as f64,
format: MeterFormat::Tokens,
}
}
pub(crate) fn currency(title: &'a str, used: f64, budget: f64) -> Self {
Self {
title,
used,
budget,
format: MeterFormat::Currency,
}
}
pub(crate) fn state(&self) -> BudgetState {
budget_state(self.used, self.budget)
}
fn ratio(&self) -> f64 {
budget_ratio(self.used, self.budget)
}
fn clamped_ratio(&self) -> f64 {
self.ratio().clamp(0.0, 1.0)
}
fn title_line(&self) -> Line<'static> {
let mut spans = vec![Span::styled(
self.title.to_string(),
Style::default()
.fg(Color::Gray)
.add_modifier(Modifier::BOLD),
)];
if let Some(badge) = self.state().badge() {
spans.push(Span::raw(" "));
spans.push(Span::styled(format!("[{badge}]"), self.state().style()));
}
Line::from(spans)
}
fn display_label(&self) -> String {
if self.budget <= 0.0 {
return match self.format {
MeterFormat::Tokens => format!("{} tok used | no budget", self.used_label()),
MeterFormat::Currency => format!("{} spent | no budget", self.used_label()),
};
}
format!(
"{} / {}{} ({}%)",
self.used_label(),
self.budget_label(),
self.unit_suffix(),
(self.ratio() * 100.0).round() as u64
)
}
fn used_label(&self) -> String {
match self.format {
MeterFormat::Tokens => format_token_count(self.used.max(0.0).round() as u64),
MeterFormat::Currency => format_currency(self.used.max(0.0)),
}
}
fn budget_label(&self) -> String {
match self.format {
MeterFormat::Tokens => format_token_count(self.budget.max(0.0).round() as u64),
MeterFormat::Currency => format_currency(self.budget.max(0.0)),
}
}
fn unit_suffix(&self) -> &'static str {
match self.format {
MeterFormat::Tokens => " tok",
MeterFormat::Currency => "",
}
}
}
impl Widget for TokenMeter<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
if area.is_empty() {
return;
}
let mut gauge_area = area;
if area.height > 1 {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(1)])
.split(area);
Paragraph::new(self.title_line()).render(chunks[0], buf);
gauge_area = chunks[1];
}
Gauge::default()
.ratio(self.clamped_ratio())
.label(self.display_label())
.gauge_style(
Style::default()
.fg(gradient_color(self.ratio()))
.add_modifier(Modifier::BOLD),
)
.style(Style::default().fg(Color::DarkGray))
.use_unicode(true)
.render(gauge_area, buf);
}
}
pub(crate) fn budget_ratio(used: f64, budget: f64) -> f64 {
if budget <= 0.0 {
0.0
} else {
used / budget
}
}
pub(crate) fn budget_state(used: f64, budget: f64) -> BudgetState {
if budget <= 0.0 {
BudgetState::Unconfigured
} else if used / budget >= 1.0 {
BudgetState::OverBudget
} else if used / budget >= WARNING_THRESHOLD {
BudgetState::Warning
} else {
BudgetState::Normal
}
}
pub(crate) fn gradient_color(ratio: f64) -> Color {
const GREEN: (u8, u8, u8) = (34, 197, 94);
const YELLOW: (u8, u8, u8) = (234, 179, 8);
const RED: (u8, u8, u8) = (239, 68, 68);
let clamped = ratio.clamp(0.0, 1.0);
if clamped <= WARNING_THRESHOLD {
interpolate_rgb(GREEN, YELLOW, clamped / WARNING_THRESHOLD)
} else {
interpolate_rgb(
YELLOW,
RED,
(clamped - WARNING_THRESHOLD) / (1.0 - WARNING_THRESHOLD),
)
}
}
pub(crate) fn format_currency(value: f64) -> String {
format!("${value:.2}")
}
pub(crate) fn format_token_count(value: u64) -> String {
let digits = value.to_string();
let mut formatted = String::with_capacity(digits.len() + digits.len() / 3);
for (index, ch) in digits.chars().rev().enumerate() {
if index != 0 && index % 3 == 0 {
formatted.push(',');
}
formatted.push(ch);
}
formatted.chars().rev().collect()
}
fn interpolate_rgb(from: (u8, u8, u8), to: (u8, u8, u8), ratio: f64) -> Color {
let ratio = ratio.clamp(0.0, 1.0);
let channel = |start: u8, end: u8| -> u8 {
(f64::from(start) + (f64::from(end) - f64::from(start)) * ratio).round() as u8
};
Color::Rgb(
channel(from.0, to.0),
channel(from.1, to.1),
channel(from.2, to.2),
)
}
#[cfg(test)]
mod tests {
use ratatui::{buffer::Buffer, layout::Rect, style::Color, widgets::Widget};
use super::{gradient_color, BudgetState, TokenMeter};
#[test]
fn warning_state_starts_at_eighty_percent() {
let meter = TokenMeter::tokens("Token Budget", 80, 100);
assert_eq!(meter.state(), BudgetState::Warning);
}
#[test]
fn gradient_runs_from_green_to_yellow_to_red() {
assert_eq!(gradient_color(0.0), Color::Rgb(34, 197, 94));
assert_eq!(gradient_color(0.8), Color::Rgb(234, 179, 8));
assert_eq!(gradient_color(1.0), Color::Rgb(239, 68, 68));
}
#[test]
fn token_meter_renders_compact_usage_label() {
let meter = TokenMeter::tokens("Token Budget", 4_000, 10_000);
let area = Rect::new(0, 0, 48, 2);
let mut buffer = Buffer::empty(area);
meter.render(area, &mut buffer);
let rendered = buffer
.content()
.chunks(area.width as usize)
.flat_map(|row| row.iter().map(|cell| cell.symbol()))
.collect::<String>();
assert!(rendered.contains("4,000 / 10,000 tok (40%)"));
}
}

View File

@@ -1,5 +1,5 @@
use anyhow::{Context, Result};
use std::path::PathBuf;
use std::path::Path;
use std::process::Command;
use crate::config::Config;
@@ -7,16 +7,27 @@ use crate::session::WorktreeInfo;
/// Create a new git worktree for an agent session.
pub fn create_for_session(session_id: &str, cfg: &Config) -> Result<WorktreeInfo> {
let repo_root = std::env::current_dir().context("Failed to resolve repository root")?;
create_for_session_in_repo(session_id, cfg, &repo_root)
}
pub(crate) fn create_for_session_in_repo(
session_id: &str,
cfg: &Config,
repo_root: &Path,
) -> Result<WorktreeInfo> {
let branch = format!("ecc/{session_id}");
let path = cfg.worktree_root.join(session_id);
// Get current branch as base
let base = get_current_branch()?;
let base = get_current_branch(repo_root)?;
std::fs::create_dir_all(&cfg.worktree_root)
.context("Failed to create worktree root directory")?;
let output = Command::new("git")
.arg("-C")
.arg(repo_root)
.args(["worktree", "add", "-b", &branch])
.arg(&path)
.arg("HEAD")
@@ -28,7 +39,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,
@@ -38,8 +53,10 @@ pub fn create_for_session(session_id: &str, cfg: &Config) -> Result<WorktreeInfo
}
/// Remove a worktree and its branch.
pub fn remove(path: &PathBuf) -> Result<()> {
pub fn remove(path: &Path) -> Result<()> {
let output = Command::new("git")
.arg("-C")
.arg(path)
.args(["worktree", "remove", "--force"])
.arg(path)
.output()
@@ -70,8 +87,10 @@ pub fn list() -> Result<Vec<String>> {
Ok(worktrees)
}
fn get_current_branch() -> Result<String> {
fn get_current_branch(repo_root: &Path) -> Result<String> {
let output = Command::new("git")
.arg("-C")
.arg(repo_root)
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
.context("Failed to get current branch")?;

View File

@@ -23,6 +23,7 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
| **Dev server blocker** | `Bash` | Blocks `npm run dev` etc. outside tmux — ensures log access | 2 (blocks) |
| **Tmux reminder** | `Bash` | Suggests tmux for long-running commands (npm test, cargo build, docker) | 0 (warns) |
| **Git push reminder** | `Bash` | Reminds to review changes before `git push` | 0 (warns) |
| **Pre-commit quality check** | `Bash` | Runs quality checks before `git commit`: lints staged files, validates commit message format when provided via `-m/--message`, detects console.log/debugger/secrets | 2 (blocks critical) / 0 (warns) |
| **Doc file warning** | `Write` | Warns about non-standard `.md`/`.txt` files (allows README, CLAUDE, CONTRIBUTING, CHANGELOG, LICENSE, SKILL, docs/, skills/); cross-platform path handling | 0 (warns) |
| **Strategic compact** | `Edit\|Write` | Suggests manual `/compact` at logical intervals (every ~50 tool calls) | 0 (warns) |
| **InsAIts security monitor (opt-in)** | `Bash\|Write\|Edit\|MultiEdit` | Optional security scan for high-signal tool inputs. Disabled unless `ECC_ENABLE_INSAITS=1`. Blocks on critical findings, warns on non-critical, and writes audit log to `.insaits_audit_session.jsonl`. Requires `pip install insa-its`. [Details](../scripts/hooks/insaits-security-monitor.py) | 2 (blocks critical) / 0 (warns) |
@@ -48,6 +49,7 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
| **Session summary** | `Stop` | Persists session state when transcript path is available |
| **Pattern extraction** | `Stop` | Evaluates session for extractable patterns (continuous learning) |
| **Cost tracker** | `Stop` | Emits lightweight run-cost telemetry markers |
| **Desktop notify** | `Stop` | Sends macOS desktop notification with task summary (standard+) |
| **Session end marker** | `SessionEnd` | Lifecycle marker and cleanup log |
## Customizing Hooks

View File

@@ -42,6 +42,16 @@
],
"description": "Reminder before git push to review changes"
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:bash:commit-quality\" \"scripts/hooks/pre-bash-commit-quality.js\" \"strict\""
}
],
"description": "Pre-commit quality check: lint staged files, validate commit message format, detect console.log/debugger/secrets before committing"
},
{
"matcher": "Write",
"hooks": [
@@ -136,7 +146,7 @@
"hooks": [
{
"type": "command",
"command": "bash -lc 'input=$(cat); for root in \"${CLAUDE_PLUGIN_ROOT:-}\" \"$HOME/.claude/plugins/everything-claude-code\" \"$HOME/.claude/plugins/everything-claude-code@everything-claude-code\" \"$HOME/.claude/plugins/marketplace/everything-claude-code\"; do if [ -n \"$root\" ] && [ -f \"$root/scripts/hooks/run-with-flags.js\" ]; then printf \"%s\" \"$input\" | node \"$root/scripts/hooks/run-with-flags.js\" \"session:start\" \"scripts/hooks/session-start.js\" \"minimal,standard,strict\"; exit $?; fi; done; for parent in \"$HOME/.claude/plugins\" \"$HOME/.claude/plugins/marketplace\"; do if [ -d \"$parent\" ]; then candidate=$(find \"$parent\" -maxdepth 2 -type f -path \"*/scripts/hooks/run-with-flags.js\" 2>/dev/null | head -n 1); if [ -n \"$candidate\" ]; then root=$(dirname \"$(dirname \"$(dirname \"$candidate\")\")\"); printf \"%s\" \"$input\" | node \"$root/scripts/hooks/run-with-flags.js\" \"session:start\" \"scripts/hooks/session-start.js\" \"minimal,standard,strict\"; exit $?; fi; fi; done; echo \"[SessionStart] WARNING: could not resolve ECC plugin root; skipping session-start hook\" >&2; printf \"%s\" \"$input\"; exit 0'"
"command": "node -e \"const fs=require('fs');const path=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const rel=path.join('scripts','hooks','run-with-flags.js');const hasRunnerRoot=candidate=>{const value=typeof candidate==='string'?candidate.trim():'';return value.length>0&&fs.existsSync(path.join(path.resolve(value),rel));};const root=(()=>{const envRoot=process.env.CLAUDE_PLUGIN_ROOT||'';if(hasRunnerRoot(envRoot))return path.resolve(envRoot.trim());const home=require('os').homedir();const claudeDir=path.join(home,'.claude');if(hasRunnerRoot(claudeDir))return claudeDir;for(const candidate of [path.join(claudeDir,'plugins','everything-claude-code'),path.join(claudeDir,'plugins','everything-claude-code@everything-claude-code'),path.join(claudeDir,'plugins','marketplace','everything-claude-code')]){if(hasRunnerRoot(candidate))return candidate;}try{const cacheBase=path.join(claudeDir,'plugins','cache','everything-claude-code');for(const org of fs.readdirSync(cacheBase,{withFileTypes:true})){if(!org.isDirectory())continue;for(const version of fs.readdirSync(path.join(cacheBase,org.name),{withFileTypes:true})){if(!version.isDirectory())continue;const candidate=path.join(cacheBase,org.name,version.name);if(hasRunnerRoot(candidate))return candidate;}}}catch{}return claudeDir;})();const script=path.join(root,rel);if(fs.existsSync(script)){const result=spawnSync(process.execPath,[script,'session:start','scripts/hooks/session-start.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});const stdout=typeof result.stdout==='string'?result.stdout:'';if(stdout)process.stdout.write(stdout);else process.stdout.write(raw);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[SessionStart] ERROR: session-start hook failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);}process.stderr.write('[SessionStart] WARNING: could not resolve ECC plugin root; skipping session-start hook'+String.fromCharCode(10));process.stdout.write(raw);\""
}
],
"description": "Load previous context and detect package manager on new session"
@@ -249,7 +259,7 @@
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:check-console-log\" \"scripts/hooks/check-console-log.js\" \"standard,strict\""
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:check-console-log','scripts/hooks/check-console-log.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\""
}
],
"description": "Check for console.log in modified files after each response"
@@ -259,7 +269,7 @@
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:session-end\" \"scripts/hooks/session-end.js\" \"minimal,standard,strict\"",
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:session-end','scripts/hooks/session-end.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
"async": true,
"timeout": 10
}
@@ -271,7 +281,7 @@
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:evaluate-session\" \"scripts/hooks/evaluate-session.js\" \"minimal,standard,strict\"",
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:evaluate-session','scripts/hooks/evaluate-session.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
"async": true,
"timeout": 10
}
@@ -283,12 +293,24 @@
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:cost-tracker\" \"scripts/hooks/cost-tracker.js\" \"minimal,standard,strict\"",
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:cost-tracker','scripts/hooks/cost-tracker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
"async": true,
"timeout": 10
}
],
"description": "Track token and cost metrics per session"
},
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:desktop-notify','scripts/hooks/desktop-notify.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
"async": true,
"timeout": 10
}
],
"description": "Send macOS desktop notification with task summary when Claude responds"
}
],
"SessionEnd": [
@@ -297,7 +319,7 @@
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"session:end:marker\" \"scripts/hooks/session-end-marker.js\" \"minimal,standard,strict\"",
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[SessionEnd] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'session:end:marker','scripts/hooks/session-end-marker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[SessionEnd] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
"async": true,
"timeout": 10
}

View File

@@ -34,5 +34,20 @@ while ($true) {
$scriptDir = Split-Path -Parent $scriptPath
$installerScript = Join-Path -Path (Join-Path -Path $scriptDir -ChildPath 'scripts') -ChildPath 'install-apply.js'
# Auto-install Node dependencies when running from a git clone
$nodeModules = Join-Path -Path $scriptDir -ChildPath 'node_modules'
if (-not (Test-Path -LiteralPath $nodeModules)) {
Write-Host '[ECC] Installing dependencies...'
Push-Location $scriptDir
try {
& npm install --no-audit --no-fund --loglevel=error
if ($LASTEXITCODE -ne 0) {
Write-Error "npm install failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
}
finally { Pop-Location }
}
& node $installerScript @args
exit $LASTEXITCODE

View File

@@ -14,4 +14,10 @@ while [ -L "$SCRIPT_PATH" ]; do
done
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
# Auto-install Node dependencies when running from a git clone
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
echo "[ECC] Installing dependencies..."
(cd "$SCRIPT_DIR" && npm install --no-audit --no-fund --loglevel=error)
fi
exec node "$SCRIPT_DIR/scripts/install-apply.js" "$@"

View File

@@ -63,7 +63,8 @@
"description": "Runtime hook configs and hook script helpers.",
"paths": [
"hooks",
"scripts/hooks"
"scripts/hooks",
"scripts/lib"
],
"targets": [
"claude",
@@ -124,6 +125,7 @@
"skills/kotlin-ktor-patterns",
"skills/kotlin-patterns",
"skills/kotlin-testing",
"skills/laravel-plugin-discovery",
"skills/laravel-patterns",
"skills/laravel-tdd",
"skills/laravel-verification",
@@ -406,6 +408,7 @@
"skills/ralphinho-rfc-pipeline",
"skills/regex-vs-llm-structured-text",
"skills/search-first",
"skills/token-budget-advisor",
"skills/team-builder"
],
"targets": [

View File

@@ -133,6 +133,11 @@
"args": ["-y", "token-optimizer-mcp"],
"description": "Token optimization for 95%+ context reduction via content deduplication and compression"
},
"laraplugins": {
"type": "http",
"url": "https://laraplugins.io/mcp/plugins",
"description": "Laravel plugin discovery — search packages by keyword, health score, Laravel/PHP version compatibility. Use with laravel-plugin-discovery skill."
},
"confluence": {
"command": "npx",
"args": ["-y", "confluence-mcp-server"],

13
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",
"ajv": "^8.18.0",
"sql.js": "^1.14.1"
},
"bin": {
@@ -19,7 +20,6 @@
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"ajv": "^8.18.0",
"c8": "^10.1.2",
"eslint": "^9.39.2",
"globals": "^17.1.0",
@@ -449,7 +449,6 @@
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -1043,7 +1042,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
@@ -1064,7 +1062,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -1491,7 +1488,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
@@ -2457,9 +2453,9 @@
}
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2512,7 +2508,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"

View File

@@ -89,6 +89,9 @@
"AGENTS.md",
".claude-plugin/plugin.json",
".claude-plugin/README.md",
".codex-plugin/plugin.json",
".codex-plugin/README.md",
".mcp.json",
"install.sh",
"install.ps1",
"llms.txt"
@@ -110,11 +113,11 @@
},
"dependencies": {
"@iarna/toml": "^2.2.5",
"ajv": "^8.18.0",
"sql.js": "^1.14.1"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"ajv": "^8.18.0",
"c8": "^10.1.2",
"eslint": "^9.39.2",
"globals": "^17.1.0",
@@ -122,5 +125,6 @@
},
"engines": {
"node": ">=18"
}
},
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c"
}

View File

@@ -0,0 +1,172 @@
# ECC2 Codebase Research Report
**Date:** 2026-03-26
**Subject:** `ecc-tui` v0.1.0 — Agentic IDE Control Plane
**Total Lines:** 4,417 across 15 `.rs` files
## 1. Architecture Overview
ECC2 is a Rust TUI application that orchestrates AI coding agent sessions. It uses:
- **ratatui 0.29** + **crossterm 0.28** for terminal UI
- **rusqlite 0.32** (bundled) for local state persistence
- **tokio 1** (full) for async runtime
- **clap 4** (derive) for CLI
### Module Breakdown
| Module | Lines | Purpose |
|--------|------:|---------|
| `session/` | 1,974 | Session lifecycle, persistence, runtime, output |
| `tui/` | 1,613 | Dashboard, app loop, custom widgets |
| `observability/` | 409 | Tool call risk scoring and logging |
| `config/` | 144 | Configuration (TOML file) |
| `main.rs` | 142 | CLI entry point |
| `worktree/` | 99 | Git worktree management |
| `comms/` | 36 | Inter-agent messaging (send only) |
### Key Architectural Patterns
- **DbWriter thread** in `session/runtime.rs` — dedicated OS thread for SQLite writes from async context via `mpsc::unbounded_channel` with oneshot acknowledgements. Clean solution to the "SQLite from async" problem.
- **Session state machine** with enforced transitions: `Pending → {Running, Failed, Stopped}`, `Running → {Idle, Completed, Failed, Stopped}`, etc.
- **Ring buffer** for session output — `OUTPUT_BUFFER_LIMIT = 1000` lines per session with automatic eviction.
- **Risk scoring** on tool calls — 4-axis analysis (base tool risk, file sensitivity, blast radius, irreversibility) producing composite 0.01.0 scores with suggested actions (Allow/Review/RequireConfirmation/Block).
## 2. Code Quality Metrics
| Metric | Value |
|--------|-------|
| Total lines | 4,417 |
| Test functions | 29 |
| `unwrap()` calls | 3 |
| `unsafe` blocks | 0 |
| TODO/FIXME comments | 0 |
| Max file size | 1,273 lines (`dashboard.rs`) |
**Assessment:** The codebase is clean. Only 3 `unwrap()` calls (2 in tests, 1 in config `default()`), zero `unsafe`, and all modules use proper `anyhow::Result` error propagation. The `dashboard.rs` file at 1,273 lines exceeds the repo's 800-line max-file guideline, but it is still manageable at the current scope.
## 3. Identified Gaps
### 3.1 Comms Module — Send Without Receive
`comms/mod.rs` (36 lines) has `send()` but no `receive()`, `poll()`, `inbox()`, or `subscribe()`. The `messages` table exists in SQLite, but nothing reads from it. The inter-agent messaging story is half-built.
**Impact:** Agents cannot coordinate. The `TaskHandoff`, `Query`, `Response`, and `Conflict` message types are defined but unusable.
### 3.2 New Session Dialog — Stub
`dashboard.rs:495``new_session()` logs `"New session dialog requested"` but does nothing. Users must use the CLI (`ecc start --task "..."`) to create sessions; the TUI dashboard cannot.
### 3.3 Single Agent Support
`session/manager.rs``agent_program()` only supports `"claude"`. The CLI accepts `--agent` but anything other than `"claude"` fails. No codex, opencode, or custom agent support.
### 3.4 Config — File-Only
`Config::load()` reads `~/.claude/ecc2.toml` only. The implementation lacks environment variable overrides (e.g., `ECC_DB_PATH`, `ECC_WORKTREE_ROOT`) and CLI flags for configuration.
### 3.5 Legacy Dependency Candidate: `git2`
`git2 = "0.20"` is still declared in `Cargo.toml`, but the `worktree` module shells out to the `git` CLI instead. That makes `git2` a strong removal candidate rather than an already-completed cleanup.
### 3.6 No Metrics Aggregation
`SessionMetrics` tracks tokens, cost, duration, tool_calls, files_changed per session. But there's no aggregate view: total cost across sessions, average duration, top tools by usage, etc. The Metrics pane in the dashboard shows per-session detail only.
### 3.7 Daemon — No Health Reporting
`session/daemon.rs` runs an infinite loop checking session timeouts. No health endpoint, no log rotation, no PID file, no signal handling for graceful shutdown. `Ctrl+C` during daemon mode kills the process uncleanly.
## 4. Test Coverage Analysis
34 test functions across 10 source modules:
| Module | Tests | Coverage Focus |
|--------|------:|----------------|
| `main.rs` | 1 | CLI parsing |
| `config/mod.rs` | 5 | Defaults, deserialization, legacy fallback |
| `observability/mod.rs` | 5 | Risk scoring, persistence, pagination |
| `session/daemon.rs` | 2 | Crash recovery / liveness handling |
| `session/manager.rs` | 4 | Session lifecycle, resume, stop, latest status |
| `session/output.rs` | 2 | Ring buffer, broadcast |
| `session/runtime.rs` | 1 | Output capture persistence/events |
| `session/store.rs` | 3 | Buffer window, migration, state transitions |
| `tui/dashboard.rs` | 8 | Rendering, selection, pane navigation, scrolling |
| `tui/widgets.rs` | 3 | Token meter rendering and thresholds |
**Direct coverage gaps:**
- `comms/mod.rs` — 0 tests
- `worktree/mod.rs` — 0 tests
The core I/O-heavy paths are no longer completely untested: `manager.rs`, `runtime.rs`, and `daemon.rs` each have targeted tests. The remaining gap is breadth rather than total absence, especially around `comms/`, `worktree/`, and more adversarial process/worktree failure cases.
## 5. Security Observations
- **No secrets in code.** Config reads from TOML file, no hardcoded credentials.
- **Process spawning** uses `tokio::process::Command` with explicit `Stdio::piped()` — no shell injection vectors.
- **Risk scoring** is a strong feature — catches `rm -rf`, `git push --force origin main`, file access to `.env`/secrets.
- **No input sanitization on session task strings.** The task string is passed directly to `claude --print`. If the task contains shell metacharacters, it could be exploited depending on how `Command` handles argument quoting. Currently safe (arguments are not shell-interpreted), but worth auditing.
## 6. Dependency Health
| Crate | Version | Latest | Notes |
|-------|---------|--------|-------|
| ratatui | 0.29 | **0.30.0** | Update available |
| crossterm | 0.28 | **0.29.0** | Update available |
| rusqlite | 0.32 | **0.39.0** | Update available |
| tokio | 1 | **1.50.0** | Update available |
| serde | 1 | **1.0.228** | Update available |
| clap | 4 | **4.6.0** | Update available |
| chrono | 0.4 | **0.4.44** | Update available |
| uuid | 1 | **1.22.0** | Update available |
`git2` is still present in `Cargo.toml` even though the `worktree` module shells out to the `git` CLI. Several other dependencies are outdated; either remove `git2` or start using it before the next release.
## 7. Recommendations (Prioritized)
### P0 — Quick Wins
1. **Add environment variable support to `Config::load()`**`ECC_DB_PATH`, `ECC_WORKTREE_ROOT`, `ECC_DEFAULT_AGENT`. Standard practice for CLI tools.
### P1 — Feature Completions
2. **Implement `comms::receive()` / `comms::poll()`** — read unread messages from the `messages` table, optionally with a `broadcast` channel for real-time delivery. Wire it into the dashboard.
3. **Build the new-session dialog in the TUI** — modal form with task input, agent selector, worktree toggle. Should call `session::manager::create_session()`.
4. **Add aggregate metrics** — total cost, average session duration, tool call frequency, cost per session. Show in the Metrics pane.
### P2 — Robustness
5. **Expand integration coverage for `manager.rs`, `runtime.rs`, and `daemon.rs`** — the repo now has baseline tests here, but it still needs failure-path coverage around process crashes, timeouts, and cleanup edge cases.
6. **Add first-party tests for `worktree/mod.rs` and `comms/mod.rs`** — these are still uncovered and back important orchestration features.
7. **Add daemon health reporting** — PID file, structured logging, graceful shutdown via signal handler.
8. **Task string security audit** — The session task uses `claude --print` via `tokio::process::Command`. Verify arguments are never shell-interpreted. Checklist: confirm `Command` arg usage, threat-model metacharacter injection, input validation/escaping strategy, logging of raw inputs, and automated tests. Re-audit if invocation code changes.
9. **Break up `dashboard.rs`** — extract SessionsPane, OutputPane, MetricsPane, LogPane into separate files under `tui/panes/`.
### P3 — Extensibility
10. **Multi-agent support** — make `agent_program()` pluggable. Add `codex`, `opencode`, `custom` agent types.
11. **Config validation** — validate risk thresholds sum correctly, budget values are positive, paths exist.
## 8. Comparison with Ratatui 0.29 Best Practices
The codebase follows ratatui conventions well:
- Uses `TableState` for stateful selection (correct pattern)
- Custom `Widget` trait implementation for `TokenMeter` (idiomatic)
- `tick()` method for periodic state sync (standard)
- `broadcast::channel` for real-time output events (appropriate)
**Minor deviations:**
- The `Dashboard` struct directly holds `StateStore` (SQLite connection). Ratatui best practice is to keep the state store behind an `Arc<Mutex<>>` to allow background updates. Currently the TUI owns the DB exclusively, which blocks adding a background metrics refresh task.
- No `Clear` widget usage when rendering the help overlay — could cause rendering artifacts on some terminals.
## 9. Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Dashboard file exceeds 1500 lines (projected) | High | Medium | At 1,273 lines currently (Section 2); extract panes into modules before it grows further |
| SQLite lock contention | Low | High | DbWriter pattern already handles this |
| No agent diversity | Medium | Medium | Pluggable agent support |
| Task-string handling assumptions drift over time | Medium | Medium | Keep `Command` argument handling shell-free, document the threat model, and add regression tests for metacharacter-heavy task input |
---
**Bottom line:** ECC2 is a well-structured Rust project with clean error handling, good separation of concerns, and strong security features (risk scoring). The main gaps are incomplete features (comms, new-session dialog, single agent) rather than architectural problems. The codebase is ready for feature work on top of the solid foundation.

124
rules/common/code-review.md Normal file
View File

@@ -0,0 +1,124 @@
# Code Review Standards
## Purpose
Code review ensures quality, security, and maintainability before code is merged. This rule defines when and how to conduct code reviews.
## When to Review
**MANDATORY review triggers:**
- After writing or modifying code
- Before any commit to shared branches
- When security-sensitive code is changed (auth, payments, user data)
- When architectural changes are made
- Before merging pull requests
**Pre-Review Requirements:**
Before requesting review, ensure:
- All automated checks (CI/CD) are passing
- Merge conflicts are resolved
- Branch is up to date with target branch
## Review Checklist
Before marking code complete:
- [ ] Code is readable and well-named
- [ ] Functions are focused (<50 lines)
- [ ] Files are cohesive (<800 lines)
- [ ] No deep nesting (>4 levels)
- [ ] Errors are handled explicitly
- [ ] No hardcoded secrets or credentials
- [ ] No console.log or debug statements
- [ ] Tests exist for new functionality
- [ ] Test coverage meets 80% minimum
## Security Review Triggers
**STOP and use security-reviewer agent when:**
- Authentication or authorization code
- User input handling
- Database queries
- File system operations
- External API calls
- Cryptographic operations
- Payment or financial code
## Review Severity Levels
| Level | Meaning | Action |
|-------|---------|--------|
| CRITICAL | Security vulnerability or data loss risk | **BLOCK** - Must fix before merge |
| HIGH | Bug or significant quality issue | **WARN** - Should fix before merge |
| MEDIUM | Maintainability concern | **INFO** - Consider fixing |
| LOW | Style or minor suggestion | **NOTE** - Optional |
## Agent Usage
Use these agents for code review:
| Agent | Purpose |
|-------|---------|
| **code-reviewer** | General code quality, patterns, best practices |
| **security-reviewer** | Security vulnerabilities, OWASP Top 10 |
| **typescript-reviewer** | TypeScript/JavaScript specific issues |
| **python-reviewer** | Python specific issues |
| **go-reviewer** | Go specific issues |
| **rust-reviewer** | Rust specific issues |
## Review Workflow
```
1. Run git diff to understand changes
2. Check security checklist first
3. Review code quality checklist
4. Run relevant tests
5. Verify coverage >= 80%
6. Use appropriate agent for detailed review
```
## Common Issues to Catch
### Security
- Hardcoded credentials (API keys, passwords, tokens)
- SQL injection (string concatenation in queries)
- XSS vulnerabilities (unescaped user input)
- Path traversal (unsanitized file paths)
- CSRF protection missing
- Authentication bypasses
### Code Quality
- Large functions (>50 lines) - split into smaller
- Large files (>800 lines) - extract modules
- Deep nesting (>4 levels) - use early returns
- Missing error handling - handle explicitly
- Mutation patterns - prefer immutable operations
- Missing tests - add test coverage
### Performance
- N+1 queries - use JOINs or batching
- Missing pagination - add LIMIT to queries
- Unbounded queries - add constraints
- Missing caching - cache expensive operations
## Approval Criteria
- **Approve**: No CRITICAL or HIGH issues
- **Warning**: Only HIGH issues (merge with caution)
- **Block**: CRITICAL issues found
## Integration with Other Rules
This rule works with:
- [testing.md](testing.md) - Test coverage requirements
- [security.md](security.md) - Security checklist
- [git-workflow.md](git-workflow.md) - Commit standards
- [agents.md](agents.md) - Agent delegation

View File

@@ -36,3 +36,9 @@ The Feature Implementation Workflow describes the development pipeline: research
- Detailed commit messages
- Follow conventional commits format
- See [git-workflow.md](./git-workflow.md) for commit message format and PR process
5. **Pre-Review Checks**
- Verify all automated checks (CI/CD) are passing
- Resolve any merge conflicts
- Ensure branch is up to date with target branch
- Only request review after these checks pass

108
rules/zh/README.md Normal file
View File

@@ -0,0 +1,108 @@
# 规则
## 结构
规则按**通用**层和**语言特定**目录组织:
```
rules/
├── common/ # 语言无关的原则(始终安装)
│ ├── coding-style.md
│ ├── git-workflow.md
│ ├── testing.md
│ ├── performance.md
│ ├── patterns.md
│ ├── hooks.md
│ ├── agents.md
│ ├── security.md
│ ├── code-review.md
│ └── development-workflow.md
├── zh/ # 中文翻译版本
│ ├── coding-style.md
│ ├── git-workflow.md
│ ├── testing.md
│ ├── performance.md
│ ├── patterns.md
│ ├── hooks.md
│ ├── agents.md
│ ├── security.md
│ ├── code-review.md
│ └── development-workflow.md
├── typescript/ # TypeScript/JavaScript 特定
├── python/ # Python 特定
├── golang/ # Go 特定
├── swift/ # Swift 特定
└── php/ # PHP 特定
```
- **common/** 包含通用原则 — 无语言特定的代码示例。
- **zh/** 包含 common 目录的中文翻译版本。
- **语言目录** 扩展通用规则,包含框架特定的模式、工具和代码示例。每个文件引用其对应的通用版本。
## 安装
### 选项 1安装脚本推荐
```bash
# 安装通用 + 一个或多个语言特定的规则集
./install.sh typescript
./install.sh python
./install.sh golang
./install.sh swift
./install.sh php
# 同时安装多种语言
./install.sh typescript python
```
### 选项 2手动安装
> **重要提示:** 复制整个目录 — 不要使用 `/*` 展开。
> 通用和语言特定目录包含同名文件。
> 将它们展开到一个目录会导致语言特定文件覆盖通用规则,
> 并破坏语言特定文件使用的 `../common/` 相对引用。
```bash
# 创建目标目录
mkdir -p ~/.claude/rules
# 安装通用规则(所有项目必需)
cp -r rules/common ~/.claude/rules/common
# 安装中文翻译版本(可选)
cp -r rules/zh ~/.claude/rules/zh
# 根据项目技术栈安装语言特定规则
cp -r rules/typescript ~/.claude/rules/typescript
cp -r rules/python ~/.claude/rules/python
cp -r rules/golang ~/.claude/rules/golang
cp -r rules/swift ~/.claude/rules/swift
cp -r rules/php ~/.claude/rules/php
```
## 规则 vs 技能
- **规则** 定义广泛适用的标准、约定和检查清单(如"80% 测试覆盖率"、"禁止硬编码密钥")。
- **技能**`skills/` 目录)为特定任务提供深入、可操作的参考材料(如 `python-patterns``golang-testing`)。
语言特定的规则文件在适当的地方引用相关技能。规则告诉你*做什么*;技能告诉你*怎么做*。
## 规则优先级
当语言特定规则与通用规则冲突时,**语言特定规则优先**(特定覆盖通用)。这遵循标准的分层配置模式(类似于 CSS 特异性或 `.gitignore` 优先级)。
- `rules/common/` 定义适用于所有项目的通用默认值。
- `rules/golang/``rules/python/``rules/swift/``rules/php/``rules/typescript/` 等在语言习惯不同时覆盖这些默认值。
- `rules/zh/` 是通用规则的中文翻译,与英文版本内容一致。
### 示例
`common/coding-style.md` 推荐不可变性作为默认原则。语言特定的 `golang/coding-style.md` 可以覆盖这一点:
> 惯用的 Go 使用指针接收器进行结构体变更 — 参见 [common/coding-style.md](../common/coding-style.md) 了解通用原则,但这里首选符合 Go 习惯的变更方式。
### 带覆盖说明的通用规则
`rules/common/` 中可能被语言特定文件覆盖的规则会被标记:
> **语言说明**:此规则可能会被语言特定规则覆盖;对于某些语言,该模式可能并不符合惯用写法。

50
rules/zh/agents.md Normal file
View File

@@ -0,0 +1,50 @@
# 代理编排
## 可用代理
位于 `~/.claude/agents/`
| 代理 | 用途 | 何时使用 |
|-------|---------|------------|
| planner | 实现规划 | 复杂功能、重构 |
| architect | 系统设计 | 架构决策 |
| tdd-guide | 测试驱动开发 | 新功能、bug 修复 |
| code-reviewer | 代码审查 | 编写代码后 |
| security-reviewer | 安全分析 | 提交前 |
| build-error-resolver | 修复构建错误 | 构建失败时 |
| e2e-runner | E2E 测试 | 关键用户流程 |
| refactor-cleaner | 死代码清理 | 代码维护 |
| doc-updater | 文档 | 更新文档 |
| rust-reviewer | Rust 代码审查 | Rust 项目 |
## 立即使用代理
无需用户提示:
1. 复杂功能请求 - 使用 **planner** 代理
2. 刚编写/修改的代码 - 使用 **code-reviewer** 代理
3. Bug 修复或新功能 - 使用 **tdd-guide** 代理
4. 架构决策 - 使用 **architect** 代理
## 并行任务执行
对独立操作始终使用并行 Task 执行:
```markdown
# 好:并行执行
同时启动 3 个代理:
1. 代理 1认证模块安全分析
2. 代理 2缓存系统性能审查
3. 代理 3工具类型检查
# 坏:不必要的顺序
先代理 1然后代理 2然后代理 3
```
## 多视角分析
对于复杂问题,使用分角色子代理:
- 事实审查者
- 高级工程师
- 安全专家
- 一致性审查者
- 冗余检查者

124
rules/zh/code-review.md Normal file
View File

@@ -0,0 +1,124 @@
# 代码审查标准
## 目的
代码审查确保代码合并前的质量、安全性和可维护性。此规则定义何时以及如何进行代码审查。
## 何时审查
**强制审查触发条件:**
- 编写或修改代码后
- 提交到共享分支之前
- 更改安全敏感代码时(认证、支付、用户数据)
- 进行架构更改时
- 合并 pull request 之前
**审查前要求:**
在请求审查之前,确保:
- 所有自动化检查CI/CD已通过
- 合并冲突已解决
- 分支已与目标分支同步
## 审查检查清单
在标记代码完成之前:
- [ ] 代码可读且命名良好
- [ ] 函数聚焦(<50 行)
- [ ] 文件内聚(<800 行)
- [ ] 无深层嵌套(>4 层)
- [ ] 错误显式处理
- [ ] 无硬编码密钥或凭据
- [ ] 无 console.log 或调试语句
- [ ] 新功能有测试
- [ ] 测试覆盖率满足 80% 最低要求
## 安全审查触发条件
**停止并使用 security-reviewer 代理当:**
- 认证或授权代码
- 用户输入处理
- 数据库查询
- 文件系统操作
- 外部 API 调用
- 加密操作
- 支付或金融代码
## 审查严重级别
| 级别 | 含义 | 行动 |
|-------|---------|--------|
| CRITICAL关键 | 安全漏洞或数据丢失风险 | **阻止** - 合并前必须修复 |
| HIGH | Bug 或重大质量问题 | **警告** - 合并前应修复 |
| MEDIUM | 可维护性问题 | **信息** - 考虑修复 |
| LOW | 风格或次要建议 | **注意** - 可选 |
## 代理使用
使用这些代理进行代码审查:
| 代理 | 用途 |
|-------|--------|
| **code-reviewer** | 通用代码质量、模式、最佳实践 |
| **security-reviewer** | 安全漏洞、OWASP Top 10 |
| **typescript-reviewer** | TypeScript/JavaScript 特定问题 |
| **python-reviewer** | Python 特定问题 |
| **go-reviewer** | Go 特定问题 |
| **rust-reviewer** | Rust 特定问题 |
## 审查工作流
```
1. 运行 git diff 了解更改
2. 先检查安全检查清单
3. 审查代码质量检查清单
4. 运行相关测试
5. 验证覆盖率 >= 80%
6. 使用适当的代理进行详细审查
```
## 常见问题捕获
### 安全
- 硬编码凭据API 密钥、密码、令牌)
- SQL 注入(查询中的字符串拼接)
- XSS 漏洞(未转义的用户输入)
- 路径遍历(未净化的文件路径)
- CSRF 保护缺失
- 认证绕过
### 代码质量
- 大函数(>50 行)- 拆分为更小的
- 大文件(>800 行)- 提取模块
- 深层嵌套(>4 层)- 使用提前返回
- 缺少错误处理 - 显式处理
- 变更模式 - 优先使用不可变操作
- 缺少测试 - 添加测试覆盖
### 性能
- N+1 查询 - 使用 JOIN 或批处理
- 缺少分页 - 给查询添加 LIMIT
- 无界查询 - 添加约束
- 缺少缓存 - 缓存昂贵操作
## 批准标准
- **批准**:无关键或高优先级问题
- **警告**:仅有高优先级问题(谨慎合并)
- **阻止**:发现关键问题
## 与其他规则的集成
此规则与以下规则配合:
- [testing.md](testing.md) - 测试覆盖率要求
- [security.md](security.md) - 安全检查清单
- [git-workflow.md](git-workflow.md) - 提交标准
- [agents.md](agents.md) - 代理委托

48
rules/zh/coding-style.md Normal file
View File

@@ -0,0 +1,48 @@
# 编码风格
## 不可变性(关键)
始终创建新对象,永远不要修改现有对象:
```
// 伪代码
错误: modify(original, field, value) → 就地修改 original
正确: update(original, field, value) → 返回带有更改的新副本
```
原理:不可变数据防止隐藏的副作用,使调试更容易,并启用安全的并发。
## 文件组织
多个小文件 > 少量大文件:
- 高内聚,低耦合
- 典型 200-400 行,最多 800 行
- 从大模块中提取工具函数
- 按功能/领域组织,而非按类型
## 错误处理
始终全面处理错误:
- 在每一层显式处理错误
- 在面向 UI 的代码中提供用户友好的错误消息
- 在服务器端记录详细的错误上下文
- 永远不要静默吞掉错误
## 输入验证
始终在系统边界验证:
- 处理前验证所有用户输入
- 在可用的情况下使用基于模式的验证
- 快速失败并给出清晰的错误消息
- 永远不要信任外部数据API 响应、用户输入、文件内容)
## 代码质量检查清单
在标记工作完成前:
- [ ] 代码可读且命名良好
- [ ] 函数很小(<50 行)
- [ ] 文件聚焦(<800 行)
- [ ] 没有深层嵌套(>4 层)
- [ ] 正确的错误处理
- [ ] 没有硬编码值(使用常量或配置)
- [ ] 没有变更(使用不可变模式)

View File

@@ -0,0 +1,44 @@
# 开发工作流
> 此文件扩展 [common/git-workflow.md](./git-workflow.md),包含 git 操作之前的完整功能开发流程。
功能实现工作流描述了开发管道研究、规划、TDD、代码审查然后提交到 git。
## 功能实现工作流
0. **研究与重用** _(任何新实现前必需)_
- **GitHub 代码搜索优先:** 在编写任何新代码之前,运行 `gh search repos``gh search code` 查找现有实现、模板和模式。
- **库文档其次:** 使用 Context7 或主要供应商文档确认 API 行为、包使用和版本特定细节。
- **仅当前两者不足时使用 Exa** 在 GitHub 搜索和主要文档之后,使用 Exa 进行更广泛的网络研究或发现。
- **检查包注册表:** 在编写工具代码之前搜索 npm、PyPI、crates.io 和其他注册表。首选久经考验的库而非手工编写的解决方案。
- **搜索可适配的实现:** 寻找解决问题 80%+ 且可以分支、移植或包装的开源项目。
- 当满足需求时,优先采用或移植经验证的方法而非从头编写新代码。
1. **先规划**
- 使用 **planner** 代理创建实现计划
- 编码前生成规划文档PRD、架构、系统设计、技术文档、任务列表
- 识别依赖和风险
- 分解为阶段
2. **TDD 方法**
- 使用 **tdd-guide** 代理
- 先写测试RED
- 实现以通过测试GREEN
- 重构IMPROVE
- 验证 80%+ 覆盖率
3. **代码审查**
- 编写代码后立即使用 **code-reviewer** 代理
- 解决关键和高优先级问题
- 尽可能修复中优先级问题
4. **提交与推送**
- 详细的提交消息
- 遵循约定式提交格式
- 参见 [git-workflow.md](./git-workflow.md) 了解提交消息格式和 PR 流程
5. **审查前检查**
- 验证所有自动化检查CI/CD已通过
- 解决任何合并冲突
- 确保分支已与目标分支同步
- 仅在这些检查通过后请求审查

24
rules/zh/git-workflow.md Normal file
View File

@@ -0,0 +1,24 @@
# Git 工作流
## 提交消息格式
```
<类型>: <描述>
<可选正文>
```
类型feat, fix, refactor, docs, test, chore, perf, ci
注意:通过 ~/.claude/settings.json 全局禁用归属。
## Pull Request 工作流
创建 PR 时:
1. 分析完整提交历史(不仅是最新提交)
2. 使用 `git diff [base-branch]...HEAD` 查看所有更改
3. 起草全面的 PR 摘要
4. 包含带有 TODO 的测试计划
5. 如果是新分支,使用 `-u` 标志推送
> 对于 git 操作之前的完整开发流程规划、TDD、代码审查
> 参见 [development-workflow.md](./development-workflow.md)。

30
rules/zh/hooks.md Normal file
View File

@@ -0,0 +1,30 @@
# 钩子系统
## 钩子类型
- **PreToolUse**:工具执行前(验证、参数修改)
- **PostToolUse**:工具执行后(自动格式化、检查)
- **Stop**:会话结束时(最终验证)
## 自动接受权限
谨慎使用:
- 为可信、定义明确的计划启用
- 探索性工作时禁用
- 永远不要使用 dangerously-skip-permissions 标志
- 改为在 `~/.claude.json` 中配置 `allowedTools`
## TodoWrite 最佳实践
使用 TodoWrite 工具:
- 跟踪多步骤任务的进度
- 验证对指令的理解
- 启用实时引导
- 显示细粒度的实现步骤
待办列表揭示:
- 顺序错误的步骤
- 缺失的项目
- 多余的不必要项目
- 错误的粒度
- 误解的需求

31
rules/zh/patterns.md Normal file
View File

@@ -0,0 +1,31 @@
# 常用模式
## 骨架项目
实现新功能时:
1. 搜索久经考验的骨架项目
2. 使用并行代理评估选项:
- 安全性评估
- 可扩展性分析
- 相关性评分
- 实现规划
3. 克隆最佳匹配作为基础
4. 在经验证的结构内迭代
## 设计模式
### 仓储模式
将数据访问封装在一致的接口后面:
- 定义标准操作findAll、findById、create、update、delete
- 具体实现处理存储细节数据库、API、文件等
- 业务逻辑依赖抽象接口,而非存储机制
- 便于轻松切换数据源,并简化使用模拟的测试
### API 响应格式
对所有 API 响应使用一致的信封:
- 包含成功/状态指示器
- 包含数据负载(错误时可为空)
- 包含错误消息字段(成功时可为空)
- 包含分页响应的元数据total、page、limit

55
rules/zh/performance.md Normal file
View File

@@ -0,0 +1,55 @@
# 性能优化
## 模型选择策略
**Haiku 4.5**Sonnet 90% 的能力3 倍成本节省):
- 频繁调用的轻量级代理
- 结对编程和代码生成
- 多代理系统中的工作者代理
**Sonnet 4.6**(最佳编码模型):
- 主要开发工作
- 编排多代理工作流
- 复杂编码任务
**Opus 4.5**(最深度推理):
- 复杂架构决策
- 最大推理需求
- 研究和分析任务
## 上下文窗口管理
避免在上下文窗口的最后 20% 进行以下操作:
- 大规模重构
- 跨多个文件的功能实现
- 调试复杂交互
上下文敏感度较低的任务:
- 单文件编辑
- 独立工具创建
- 文档更新
- 简单 bug 修复
## 扩展思考 + 规划模式
扩展思考默认启用,为内部推理保留最多 31,999 个 token。
通过以下方式控制扩展思考:
- **切换**Option+TmacOS/ Alt+TWindows/Linux
- **配置**:在 `~/.claude/settings.json` 中设置 `alwaysThinkingEnabled`
- **预算上限**`export MAX_THINKING_TOKENS=10000`
- **详细模式**Ctrl+O 查看思考输出
对于需要深度推理的复杂任务:
1. 确保扩展思考已启用(默认开启)
2. 启用**规划模式**进行结构化方法
3. 使用多轮审查进行彻底分析
4. 使用分角色子代理获得多样化视角
## 构建排查
如果构建失败:
1. 使用 **build-error-resolver** 代理
2. 分析错误消息
3. 增量修复
4. 每次修复后验证

29
rules/zh/security.md Normal file
View File

@@ -0,0 +1,29 @@
# 安全指南
## 强制安全检查
在任何提交之前:
- [ ] 无硬编码密钥API 密钥、密码、令牌)
- [ ] 所有用户输入已验证
- [ ] SQL 注入防护(参数化查询)
- [ ] XSS 防护(净化 HTML
- [ ] CSRF 保护已启用
- [ ] 认证/授权已验证
- [ ] 所有端点启用速率限制
- [ ] 错误消息不泄露敏感数据
## 密钥管理
- 永远不要在源代码中硬编码密钥
- 始终使用环境变量或密钥管理器
- 启动时验证所需的密钥是否存在
- 轮换任何可能已暴露的密钥
## 安全响应协议
如果发现安全问题:
1. 立即停止
2. 使用 **security-reviewer** 代理
3. 在继续之前修复关键问题
4. 轮换任何已暴露的密钥
5. 审查整个代码库中的类似问题

29
rules/zh/testing.md Normal file
View File

@@ -0,0 +1,29 @@
# 测试要求
## 最低测试覆盖率80%
测试类型(全部必需):
1. **单元测试** - 单个函数、工具、组件
2. **集成测试** - API 端点、数据库操作
3. **E2E 测试** - 关键用户流程(框架根据语言选择)
## 测试驱动开发
强制工作流:
1. 先写测试RED
2. 运行测试 - 应该失败
3. 编写最小实现GREEN
4. 运行测试 - 应该通过
5. 重构IMPROVE
6. 验证覆盖率80%+
## 测试失败排查
1. 使用 **tdd-guide** 代理
2. 检查测试隔离
3. 验证模拟是否正确
4. 修复实现,而非测试(除非测试有误)
## 代理支持
- **tdd-guide** - 主动用于新功能,强制先写测试

186
scripts/catalog.js Normal file
View File

@@ -0,0 +1,186 @@
#!/usr/bin/env node
const {
getInstallComponent,
listInstallComponents,
listInstallProfiles,
} = require('./lib/install-manifests');
const FAMILY_ALIASES = Object.freeze({
baseline: 'baseline',
baselines: 'baseline',
language: 'language',
languages: 'language',
lang: 'language',
framework: 'framework',
frameworks: 'framework',
capability: 'capability',
capabilities: 'capability',
agent: 'agent',
agents: 'agent',
skill: 'skill',
skills: 'skill',
});
function showHelp(exitCode = 0) {
console.log(`
Discover ECC install components and profiles
Usage:
node scripts/catalog.js profiles [--json]
node scripts/catalog.js components [--family <family>] [--target <target>] [--json]
node scripts/catalog.js show <component-id> [--json]
Examples:
node scripts/catalog.js profiles
node scripts/catalog.js components --family language
node scripts/catalog.js show framework:nextjs
`);
process.exit(exitCode);
}
function normalizeFamily(value) {
if (!value) {
return null;
}
const normalized = String(value).trim().toLowerCase();
return FAMILY_ALIASES[normalized] || normalized;
}
function parseArgs(argv) {
const args = argv.slice(2);
const parsed = {
command: null,
componentId: null,
family: null,
target: null,
json: false,
help: false,
};
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
parsed.help = true;
return parsed;
}
parsed.command = args[0];
for (let index = 1; index < args.length; index += 1) {
const arg = args[index];
if (arg === '--help' || arg === '-h') {
parsed.help = true;
} else if (arg === '--json') {
parsed.json = true;
} else if (arg === '--family') {
if (!args[index + 1]) {
throw new Error('Missing value for --family');
}
parsed.family = normalizeFamily(args[index + 1]);
index += 1;
} else if (arg === '--target') {
if (!args[index + 1]) {
throw new Error('Missing value for --target');
}
parsed.target = args[index + 1];
index += 1;
} else if (parsed.command === 'show' && !parsed.componentId) {
parsed.componentId = arg;
} else {
throw new Error(`Unknown argument: ${arg}`);
}
}
return parsed;
}
function printProfiles(profiles) {
console.log('Install profiles:\n');
for (const profile of profiles) {
console.log(`- ${profile.id} (${profile.moduleCount} modules)`);
console.log(` ${profile.description}`);
}
}
function printComponents(components) {
console.log('Install components:\n');
for (const component of components) {
console.log(`- ${component.id} [${component.family}]`);
console.log(` targets=${component.targets.join(', ')} modules=${component.moduleIds.join(', ')}`);
console.log(` ${component.description}`);
}
}
function printComponent(component) {
console.log(`Install component: ${component.id}\n`);
console.log(`Family: ${component.family}`);
console.log(`Targets: ${component.targets.join(', ')}`);
console.log(`Modules: ${component.moduleIds.join(', ')}`);
console.log(`Description: ${component.description}`);
if (component.modules.length > 0) {
console.log('\nResolved modules:');
for (const module of component.modules) {
console.log(`- ${module.id} [${module.kind}]`);
console.log(
` targets=${module.targets.join(', ')} default=${module.defaultInstall} cost=${module.cost} stability=${module.stability}`
);
console.log(` ${module.description}`);
}
}
}
function main() {
try {
const options = parseArgs(process.argv);
if (options.help) {
showHelp(0);
}
if (options.command === 'profiles') {
const profiles = listInstallProfiles();
if (options.json) {
console.log(JSON.stringify({ profiles }, null, 2));
} else {
printProfiles(profiles);
}
return;
}
if (options.command === 'components') {
const components = listInstallComponents({
family: options.family,
target: options.target,
});
if (options.json) {
console.log(JSON.stringify({ components }, null, 2));
} else {
printComponents(components);
}
return;
}
if (options.command === 'show') {
if (!options.componentId) {
throw new Error('Catalog show requires an install component ID');
}
const component = getInstallComponent(options.componentId);
if (options.json) {
console.log(JSON.stringify(component, null, 2));
} else {
printComponent(component);
}
return;
}
throw new Error(`Unknown catalog command: ${options.command}`);
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
main();

View File

@@ -16,6 +16,20 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
exit 0
fi
# Skip checks for branch deletion pushes (e.g., git push origin --delete <branch>).
# The pre-push hook receives lines on stdin: <local ref> <local sha> <remote ref> <remote sha>.
# For deletions, the local sha is the zero OID.
is_delete_only=true
while read -r _local_ref local_sha _remote_ref _remote_sha; do
if [[ "$local_sha" != "0000000000000000000000000000000000000000" ]]; then
is_delete_only=false
break
fi
done
if [[ "$is_delete_only" == "true" ]]; then
exit 0
fi
ran_any_check=0
log() {

37
scripts/codex/check-codex-global-state.sh Normal file → Executable file
View File

@@ -11,7 +11,7 @@ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
CONFIG_FILE="$CODEX_HOME/config.toml"
AGENTS_FILE="$CODEX_HOME/AGENTS.md"
PROMPTS_DIR="$CODEX_HOME/prompts"
SKILLS_DIR="$CODEX_HOME/skills"
SKILLS_DIR="${AGENTS_HOME:-$HOME/.agents}/skills"
HOOKS_DIR_EXPECT="${ECC_GLOBAL_HOOKS_DIR:-$CODEX_HOME/git-hooks}"
failures=0
@@ -89,7 +89,13 @@ fi
if [[ -f "$CONFIG_FILE" ]]; then
check_config_pattern '^multi_agent\s*=\s*true' "multi_agent is enabled"
check_config_absent '^\s*collab\s*=' "deprecated collab flag is absent"
check_config_pattern '^persistent_instructions\s*=' "persistent_instructions is configured"
# persistent_instructions is recommended but optional; warn instead of fail
# so users who rely on AGENTS.md alone are not blocked (#967).
if rg -n '^[[:space:]]*persistent_instructions\s*=' "$CONFIG_FILE" >/dev/null 2>&1; then
ok "persistent_instructions is configured"
else
warn "persistent_instructions is not set (recommended but optional)"
fi
check_config_pattern '^\[profiles\.strict\]' "profiles.strict exists"
check_config_pattern '^\[profiles\.yolo\]' "profiles.yolo exists"
@@ -97,7 +103,7 @@ if [[ -f "$CONFIG_FILE" ]]; then
'mcp_servers.github' \
'mcp_servers.memory' \
'mcp_servers.sequential-thinking' \
'mcp_servers.context7-mcp'
'mcp_servers.context7'
do
if rg -n "^\[$section\]" "$CONFIG_FILE" >/dev/null 2>&1; then
ok "MCP section [$section] exists"
@@ -106,10 +112,25 @@ if [[ -f "$CONFIG_FILE" ]]; then
fi
done
has_context7_legacy=0
has_context7_current=0
if rg -n '^\[mcp_servers\.context7\]' "$CONFIG_FILE" >/dev/null 2>&1; then
warn "Duplicate [mcp_servers.context7] exists (context7-mcp is preferred)"
has_context7_legacy=1
fi
if rg -n '^\[mcp_servers\.context7-mcp\]' "$CONFIG_FILE" >/dev/null 2>&1; then
has_context7_current=1
fi
if [[ "$has_context7_legacy" -eq 1 || "$has_context7_current" -eq 1 ]]; then
ok "MCP section [mcp_servers.context7] or [mcp_servers.context7-mcp] exists"
else
ok "No duplicate [mcp_servers.context7] section"
fail "MCP section [mcp_servers.context7] or [mcp_servers.context7-mcp] missing"
fi
if [[ "$has_context7_legacy" -eq 1 && "$has_context7_current" -eq 1 ]]; then
warn "Both [mcp_servers.context7] and [mcp_servers.context7-mcp] exist; prefer one name"
fi
fi
@@ -144,12 +165,12 @@ if [[ -d "$SKILLS_DIR" ]]; then
done
if [[ "$missing_skills" -eq 0 ]]; then
ok "All 16 ECC Codex skills are present"
ok "All 16 ECC skills are present in $SKILLS_DIR"
else
fail "$missing_skills required skills are missing"
warn "$missing_skills ECC skills missing from $SKILLS_DIR (install via ECC installer or npx skills)"
fi
else
fail "Skills directory missing ($SKILLS_DIR)"
warn "Skills directory missing ($SKILLS_DIR) — install via ECC installer or npx skills"
fi
if [[ -f "$PROMPTS_DIR/ecc-prompts-manifest.txt" ]]; then

20
scripts/codex/install-global-git-hooks.sh Normal file → Executable file
View File

@@ -24,9 +24,11 @@ log() {
run_or_echo() {
if [[ "$MODE" == "dry-run" ]]; then
printf '[dry-run] %s\n' "$*"
printf '[dry-run]'
printf ' %q' "$@"
printf '\n'
else
eval "$*"
"$@"
fi
}
@@ -41,14 +43,14 @@ log "Global hooks destination: $DEST_DIR"
if [[ -d "$DEST_DIR" ]]; then
log "Backing up existing hooks directory to $BACKUP_DIR"
run_or_echo "mkdir -p \"$BACKUP_DIR\""
run_or_echo "cp -R \"$DEST_DIR\" \"$BACKUP_DIR/hooks\""
run_or_echo mkdir -p "$BACKUP_DIR"
run_or_echo cp -R "$DEST_DIR" "$BACKUP_DIR/hooks"
fi
run_or_echo "mkdir -p \"$DEST_DIR\""
run_or_echo "cp \"$SOURCE_DIR/pre-commit\" \"$DEST_DIR/pre-commit\""
run_or_echo "cp \"$SOURCE_DIR/pre-push\" \"$DEST_DIR/pre-push\""
run_or_echo "chmod +x \"$DEST_DIR/pre-commit\" \"$DEST_DIR/pre-push\""
run_or_echo mkdir -p "$DEST_DIR"
run_or_echo cp "$SOURCE_DIR/pre-commit" "$DEST_DIR/pre-commit"
run_or_echo cp "$SOURCE_DIR/pre-push" "$DEST_DIR/pre-push"
run_or_echo chmod +x "$DEST_DIR/pre-commit" "$DEST_DIR/pre-push"
if [[ "$MODE" == "apply" ]]; then
prev_hooks_path="$(git config --global core.hooksPath || true)"
@@ -56,7 +58,7 @@ if [[ "$MODE" == "apply" ]]; then
log "Previous global hooksPath: $prev_hooks_path"
fi
fi
run_or_echo "git config --global core.hooksPath \"$DEST_DIR\""
run_or_echo git config --global core.hooksPath "$DEST_DIR"
log "Installed ECC global git hooks."
log "Disable per repo by creating .ecc-hooks-disable in project root."

Some files were not shown because too many files have changed in this diff Show More