From e46deb93c87560d747f16027eb028d73a235ec91 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Tue, 14 Apr 2026 19:44:32 -0700 Subject: [PATCH 1/3] fix: harden dashboard terminal launch helpers --- ecc_dashboard.py | 17 ++-- scripts/lib/ecc_dashboard_runtime.py | 61 +++++++++++++ tests/scripts/ecc-dashboard.test.js | 128 +++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 scripts/lib/ecc_dashboard_runtime.py create mode 100644 tests/scripts/ecc-dashboard.test.js diff --git a/ecc_dashboard.py b/ecc_dashboard.py index b2ea49d3..dfe54ea0 100644 --- a/ecc_dashboard.py +++ b/ecc_dashboard.py @@ -8,8 +8,11 @@ import tkinter as tk from tkinter import ttk, scrolledtext, messagebox import os import json +import subprocess from typing import Dict, List, Optional +from scripts.lib.ecc_dashboard_runtime import build_terminal_launch, maximize_window + # ============================================================================ # DATA LOADERS - Load ECC data from the project # ============================================================================ @@ -18,6 +21,7 @@ def get_project_path() -> str: """Get the ECC project path - assumes this script is run from the project dir""" return os.path.dirname(os.path.abspath(__file__)) + def load_agents(project_path: str) -> List[Dict]: """Load agents from AGENTS.md""" agents_file = os.path.join(project_path, "AGENTS.md") @@ -257,7 +261,7 @@ class ECCDashboard(tk.Tk): self.project_path = get_project_path() self.title("ECC Dashboard - Everything Claude Code") - self.state('zoomed') + maximize_window(self) try: self.icon_image = tk.PhotoImage(file='assets/images/ecc-logo.png') @@ -789,14 +793,9 @@ Project: github.com/affaan-m/everything-claude-code""" def open_terminal(self): """Open terminal at project path""" - import subprocess path = self.path_entry.get() - if os.name == 'nt': # Windows - subprocess.Popen(['cmd', '/c', 'start', 'cmd', '/k', f'cd /d "{path}"']) - elif os.uname().sysname == 'Darwin': # macOS - subprocess.Popen(['open', '-a', 'Terminal', path]) - else: # Linux - subprocess.Popen(['x-terminal-emulator', '-e', f'cd {path}']) + argv, kwargs = build_terminal_launch(path) + subprocess.Popen(argv, **kwargs) def open_readme(self): """Open README in default browser/reader""" @@ -911,4 +910,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/scripts/lib/ecc_dashboard_runtime.py b/scripts/lib/ecc_dashboard_runtime.py new file mode 100644 index 00000000..f882c919 --- /dev/null +++ b/scripts/lib/ecc_dashboard_runtime.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Runtime helpers for ecc_dashboard.py that do not depend on tkinter. +""" + +from __future__ import annotations + +import os +import platform +import subprocess +from typing import Optional, Tuple, Dict, List + + +def maximize_window(window) -> None: + """Maximize the dashboard window using the safest supported method.""" + try: + window.state('zoomed') + return + except Exception: + pass + + system_name = platform.system() + if system_name == 'Linux': + try: + window.attributes('-zoomed', True) + except Exception: + pass + elif system_name == 'Darwin': + try: + window.attributes('-fullscreen', True) + except Exception: + pass + + +def build_terminal_launch( + path: str, + *, + os_name: Optional[str] = None, + system_name: Optional[str] = None, +) -> Tuple[List[str], Dict[str, object]]: + """Return safe argv/kwargs for opening a terminal rooted at the requested path.""" + resolved_os_name = os_name or os.name + resolved_system_name = system_name or platform.system() + + if resolved_os_name == 'nt': + creationflags = getattr(subprocess, 'CREATE_NEW_CONSOLE', 0) + return ( + ['cmd.exe', '/k', 'cd', '/d', path], + { + 'cwd': path, + 'creationflags': creationflags, + }, + ) + + if resolved_system_name == 'Darwin': + return (['open', '-a', 'Terminal', path], {}) + + return ( + ['x-terminal-emulator', '-e', 'bash', '-lc', 'cd -- "$1"; exec bash', 'bash', path], + {}, + ) diff --git a/tests/scripts/ecc-dashboard.test.js b/tests/scripts/ecc-dashboard.test.js new file mode 100644 index 00000000..2eed98b9 --- /dev/null +++ b/tests/scripts/ecc-dashboard.test.js @@ -0,0 +1,128 @@ +/** + * Behavioral tests for ecc_dashboard.py helper functions. + */ + +const assert = require('assert'); +const path = require('path'); +const { spawnSync } = require('child_process'); + +const repoRoot = path.join(__dirname, '..', '..'); +const runtimeHelpersPath = path.join(repoRoot, 'scripts', 'lib', 'ecc_dashboard_runtime.py'); + +function test(name, fn) { + try { + fn(); + console.log(` ✓ ${name}`); + return true; + } catch (error) { + console.log(` ✗ ${name}`); + console.log(` Error: ${error.message}`); + return false; + } +} + +function runPython(source) { + const candidates = process.platform === 'win32' ? ['python', 'python3'] : ['python3', 'python']; + let lastError = null; + + for (const command of candidates) { + const result = spawnSync(command, ['-c', source], { + cwd: repoRoot, + encoding: 'utf8', + }); + + if (result.error && result.error.code === 'ENOENT') { + lastError = result.error; + continue; + } + + if (result.status !== 0) { + throw new Error((result.stderr || result.stdout || '').trim() || `${command} exited ${result.status}`); + } + + return result.stdout.trim(); + } + + throw lastError || new Error('No Python interpreter available'); +} + +function runTests() { + console.log('\n=== Testing ecc_dashboard.py ===\n'); + + let passed = 0; + let failed = 0; + + if (test('build_terminal_launch keeps Linux path separate from shell command text', () => { + const output = runPython(` +import importlib.util, json +spec = importlib.util.spec_from_file_location("ecc_dashboard_runtime", r"""${runtimeHelpersPath}""") +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) +argv, kwargs = module.build_terminal_launch('/tmp/proj; rm -rf ~', os_name='posix', system_name='Linux') +print(json.dumps({'argv': argv, 'kwargs': kwargs})) +`); + const parsed = JSON.parse(output); + assert.deepStrictEqual( + parsed.argv, + ['x-terminal-emulator', '-e', 'bash', '-lc', 'cd -- "$1"; exec bash', 'bash', '/tmp/proj; rm -rf ~'] + ); + assert.deepStrictEqual(parsed.kwargs, {}); + })) passed++; else failed++; + + if (test('build_terminal_launch uses cwd + CREATE_NEW_CONSOLE style launch on Windows', () => { + const output = runPython(` +import importlib.util, json +spec = importlib.util.spec_from_file_location("ecc_dashboard_runtime", r"""${runtimeHelpersPath}""") +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) +argv, kwargs = module.build_terminal_launch(r'C:\\\\Users\\\\user\\\\proj & del C:\\\\*', os_name='nt', system_name='Windows') +print(json.dumps({'argv': argv, 'kwargs': kwargs})) +`); + const parsed = JSON.parse(output); + assert.deepStrictEqual(parsed.argv.slice(0, 4), ['cmd.exe', '/k', 'cd', '/d']); + assert.strictEqual(parsed.argv[4], parsed.kwargs.cwd); + assert.ok(parsed.argv[4].includes('proj & del'), 'path should remain a literal argv entry'); + assert.ok(parsed.argv[4].includes('C:'), 'windows drive prefix should be preserved'); + assert.ok(Object.prototype.hasOwnProperty.call(parsed.kwargs, 'creationflags')); + })) passed++; else failed++; + + if (test('maximize_window falls back to Linux zoom attribute when zoomed state is unsupported', () => { + const output = runPython(` +import importlib.util, json +spec = importlib.util.spec_from_file_location("ecc_dashboard_runtime", r"""${runtimeHelpersPath}""") +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + +class FakeWindow: + def __init__(self): + self.calls = [] + + def state(self, value): + self.calls.append(['state', value]) + raise RuntimeError('bad argument "zoomed"') + + def attributes(self, name, value): + self.calls.append(['attributes', name, value]) + +original = module.platform.system +module.platform.system = lambda: 'Linux' +try: + window = FakeWindow() + module.maximize_window(window) +finally: + module.platform.system = original + +print(json.dumps(window.calls)) +`); + const parsed = JSON.parse(output); + assert.deepStrictEqual(parsed, [ + ['state', 'zoomed'], + ['attributes', '-zoomed', true], + ]); + })) passed++; else failed++; + + console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); + process.exit(failed > 0 ? 1 : 0); +} + +runTests(); From c924290b5b3946fa04ada41ed4a91dd4f65f2396 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Tue, 14 Apr 2026 19:46:00 -0700 Subject: [PATCH 2/3] fix: restore dashboard branch CI baseline --- agents/a11y-architect.md | 1 + 1 file changed, 1 insertion(+) diff --git a/agents/a11y-architect.md b/agents/a11y-architect.md index 7ef2e517..531d43ff 100644 --- a/agents/a11y-architect.md +++ b/agents/a11y-architect.md @@ -1,6 +1,7 @@ --- name: a11y-architect description: Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI components, establishing design systems, or auditing code for inclusive user experiences. +model: sonnet tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"] --- From 2691cfc0f160baa028d531843d88f4fc6141a27a Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Tue, 14 Apr 2026 19:54:28 -0700 Subject: [PATCH 3/3] fix: restore dashboard branch ci baseline --- AGENTS.md | 6 +++--- README.md | 10 +++++----- README.zh-CN.md | 2 +- docs/zh-CN/AGENTS.md | 6 +++--- docs/zh-CN/README.md | 10 +++++----- scripts/hooks/gateguard-fact-force.js | 12 +++++++++++- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fd7b24cf..e25ea6e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Everything Claude Code (ECC) — Agent Instructions -This is a **production-ready AI coding plugin** providing 47 specialized agents, 181 skills, 79 commands, and automated hook workflows for software development. +This is a **production-ready AI coding plugin** providing 48 specialized agents, 183 skills, 79 commands, and automated hook workflows for software development. **Version:** 1.10.0 @@ -145,8 +145,8 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat ## Project Structure ``` -agents/ — 47 specialized subagents -skills/ — 181 workflow skills and domain knowledge +agents/ — 48 specialized subagents +skills/ — 183 workflow skills and domain knowledge commands/ — 79 slash commands hooks/ — Trigger-based automations rules/ — Always-follow guidelines (common + per-language) diff --git a/README.md b/README.md index 28251aa2..02935022 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ For manual install instructions see the README in the `rules/` folder. When copy /plugin list ecc@ecc ``` -**That's it!** You now have access to 47 agents, 181 skills, and 79 legacy command shims. +**That's it!** You now have access to 48 agents, 183 skills, and 79 legacy command shims. ### Dashboard GUI @@ -1205,9 +1205,9 @@ The configuration is automatically detected from `.opencode/opencode.json`. | Feature | Claude Code | OpenCode | Status | |---------|-------------|----------|--------| -| Agents | PASS: 47 agents | PASS: 12 agents | **Claude Code leads** | +| Agents | PASS: 48 agents | PASS: 12 agents | **Claude Code leads** | | Commands | PASS: 79 commands | PASS: 31 commands | **Claude Code leads** | -| Skills | PASS: 181 skills | PASS: 37 skills | **Claude Code leads** | +| Skills | PASS: 183 skills | PASS: 37 skills | **Claude Code leads** | | Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** | | Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** | | MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** | @@ -1314,9 +1314,9 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e | Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode | |---------|------------|------------|-----------|----------| -| **Agents** | 47 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | +| **Agents** | 48 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 | | **Commands** | 79 | Shared | Instruction-based | 31 | -| **Skills** | 181 | Shared | 10 (native format) | 37 | +| **Skills** | 183 | Shared | 10 (native format) | 37 | | **Hook Events** | 8 types | 15 types | None yet | 11 types | | **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks | | **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions | diff --git a/README.zh-CN.md b/README.zh-CN.md index 7e7e9553..ff1c8058 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -162,7 +162,7 @@ npx ecc-install typescript /plugin list ecc@ecc ``` -**完成!** 你现在可以使用 47 个代理、181 个技能和 79 个命令。 +**完成!** 你现在可以使用 48 个代理、183 个技能和 79 个命令。 ### multi-* 命令需要额外配置 diff --git a/docs/zh-CN/AGENTS.md b/docs/zh-CN/AGENTS.md index 0bad9c1c..e94456c2 100644 --- a/docs/zh-CN/AGENTS.md +++ b/docs/zh-CN/AGENTS.md @@ -1,6 +1,6 @@ # Everything Claude Code (ECC) — 智能体指令 -这是一个**生产就绪的 AI 编码插件**,提供 47 个专业代理、181 项技能、79 条命令以及自动化钩子工作流,用于软件开发。 +这是一个**生产就绪的 AI 编码插件**,提供 48 个专业代理、183 项技能、79 条命令以及自动化钩子工作流,用于软件开发。 **版本:** 1.10.0 @@ -146,8 +146,8 @@ ## 项目结构 ``` -agents/ — 47 个专业子代理 -skills/ — 181 个工作流技能和领域知识 +agents/ — 48 个专业子代理 +skills/ — 183 个工作流技能和领域知识 commands/ — 79 个斜杠命令 hooks/ — 基于触发的自动化 rules/ — 始终遵循的指导方针(通用 + 每种语言) diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 851e9697..30a89bef 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -209,7 +209,7 @@ npx ecc-install typescript /plugin list ecc@ecc ``` -**搞定!** 你现在可以使用 47 个智能体、181 项技能和 79 个命令了。 +**搞定!** 你现在可以使用 48 个智能体、183 项技能和 79 个命令了。 *** @@ -1094,9 +1094,9 @@ opencode | 功能特性 | Claude Code | OpenCode | 状态 | |---------|-------------|----------|--------| -| 智能体 | PASS: 47 个 | PASS: 12 个 | **Claude Code 领先** | +| 智能体 | PASS: 48 个 | PASS: 12 个 | **Claude Code 领先** | | 命令 | PASS: 79 个 | PASS: 31 个 | **Claude Code 领先** | -| 技能 | PASS: 181 项 | PASS: 37 项 | **Claude Code 领先** | +| 技能 | PASS: 183 项 | PASS: 37 项 | **Claude Code 领先** | | 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** | | 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** | | MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** | @@ -1206,9 +1206,9 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以 | 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode | |---------|------------|------------|-----------|----------| -| **智能体** | 47 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 | +| **智能体** | 48 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 | | **命令** | 79 | 共享 | 基于指令 | 31 | -| **技能** | 181 | 共享 | 10 (原生格式) | 37 | +| **技能** | 183 | 共享 | 10 (原生格式) | 37 | | **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 | | **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 | | **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 | diff --git a/scripts/hooks/gateguard-fact-force.js b/scripts/hooks/gateguard-fact-force.js index b8a0fcc8..912ce8a8 100644 --- a/scripts/hooks/gateguard-fact-force.js +++ b/scripts/hooks/gateguard-fact-force.js @@ -121,7 +121,17 @@ function isChecked(key) { function sanitizePath(filePath) { // Strip control chars (including null), bidi overrides, and newlines - return filePath.replace(/[\x00-\x1f\x7f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, ' ').trim().slice(0, 500); + let sanitized = ''; + for (const char of String(filePath || '')) { + const code = char.codePointAt(0); + const isAsciiControl = code <= 0x1f || code === 0x7f; + const isBidiOverride = + (code >= 0x200e && code <= 0x200f) || + (code >= 0x202a && code <= 0x202e) || + (code >= 0x2066 && code <= 0x2069); + sanitized += isAsciiControl || isBidiOverride ? ' ' : char; + } + return sanitized.trim().slice(0, 500); } // --- Gate messages ---