Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .opencode/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ Then in your `opencode.json`:
}
```

This only loads the published ECC OpenCode plugin module (hooks/events and exported plugin tools).
It does **not** automatically inject ECC's full `agent`, `command`, or `instructions` config into your project.

If you want the full ECC OpenCode workflow surface, use the repository's bundled `.opencode/opencode.json` as your base config or copy these pieces into your project:
- `.opencode/commands/`
- `.opencode/prompts/`
- `.opencode/instructions/INSTRUCTIONS.md`
- the `agent` and `command` sections from `.opencode/opencode.json`

## Troubleshooting

### Configuration Not Loading
Expand All @@ -322,6 +331,7 @@ Then in your `opencode.json`:
1. Verify the command is defined in `opencode.json` or as `.md` file in `.opencode/commands/`
2. Check the referenced agent exists
3. Ensure the template uses `$ARGUMENTS` for user input
4. If you installed only `plugin: ["ecc-universal"]`, note that npm plugin install does not auto-add ECC commands or agents to your project config

## Best Practices

Expand Down
11 changes: 10 additions & 1 deletion .opencode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ Add to your `opencode.json`:
"plugin": ["ecc-universal"]
}
```
After installation, the `ecc-install` CLI becomes available:

This loads the ECC OpenCode plugin module from npm:
- hook/event integrations
- bundled custom tools exported by the plugin

It does **not** auto-register the full ECC command/agent/instruction catalog in your project config. For the full OpenCode setup, either:
- run OpenCode inside this repository, or
- copy the relevant `.opencode/commands/`, `.opencode/prompts/`, `.opencode/instructions/`, and the `instructions`, `agent`, and `command` config entries into your own project
Comment on lines +40 to +42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Include skills/ in the manual-copy instructions.

Line 42 still leaves out the skills/ assets that the documented instructions entries reference later in this README (skills/.../SKILL.md on Lines 193-195). As written, the “full OpenCode setup” path can still produce missing instruction files.

✏️ Suggested wording
-It does **not** auto-register the full ECC command/agent/instruction catalog in your project config. For the full OpenCode setup, either:
+It does **not** auto-register the full ECC command/agent/instruction catalog in your project config. For the full OpenCode setup, either:
 - run OpenCode inside this repository, or
-- copy the relevant `.opencode/commands/`, `.opencode/prompts/`, `.opencode/instructions/`, and the `instructions`, `agent`, and `command` config entries into your own project
+- copy the relevant `.opencode/commands/`, `.opencode/prompts/`, `.opencode/instructions/`, `skills/`, and the `instructions`, `agent`, and `command` config entries into your own project
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.opencode/README.md around lines 40 - 42, The README's manual-copy
instructions omit the skills/ directory, causing missing SKILL.md files
referenced by instructions; update the sentence that lists
`.opencode/commands/`, `.opencode/prompts/`, `.opencode/instructions/` to also
include `.opencode/skills/` and mention copying the `skills` config entries (or
explicitly state to copy the entire `.opencode/skills/` tree) so the
`instructions` entries referencing `skills/.../SKILL.md` resolve correctly.


After installation, the `ecc-install` CLI is also available:

```bash
npx ecc-install typescript
Comment on lines +44 to 47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the .opencode/README.md file exists and examine the relevant section
find . -name "README.md" -path "*opencode*" | head -20

Repository: affaan-m/everything-claude-code

Length of output: 96


🏁 Script executed:

# Also, let's look for the specific file mentioned
git ls-files | grep -i "opencode" | grep -i readme

Repository: affaan-m/everything-claude-code

Length of output: 94


🏁 Script executed:

# Check the repository structure around opencode directory
fd "README" | grep -E "(opencode|ecc)" | head -20

Repository: affaan-m/everything-claude-code

Length of output: 57


🏁 Script executed:

# Read the .opencode/README.md file to see the actual content around lines 44-47
cat -n .opencode/README.md | head -60

Repository: affaan-m/everything-claude-code

Length of output: 2064


🏁 Script executed:

# Check if there are other examples of package manager invocation patterns in documentation
rg "npm|bun|yarn|pnpm" .opencode/README.md | head -20

Repository: affaan-m/everything-claude-code

Length of output: 460


🏁 Script executed:

# Also check the main README for how package managers are handled
rg -A2 -B2 "npx|bunx|yarn" README.md | head -30

Repository: affaan-m/everything-claude-code

Length of output: 853


Provide package-manager-specific examples for the ecc-install command.

Line 15 documents support for "npm/bun/yarn", but the example on line 47 uses only npx ecc-install typescript, which is npm-specific. Bun users should see bunx ecc-install typescript, and Yarn users may need alternative syntax. Either show all three variants or use a package-manager-agnostic instruction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.opencode/README.md around lines 44 - 47, The README currently shows only
the npm-specific example "npx ecc-install typescript"; update that example to
include package-manager-specific variants or a neutral instruction: add lines
showing "npx ecc-install typescript", "bunx ecc-install typescript", and "yarn
dlx ecc-install typescript" (or replace the single example with a
package-manager-agnostic note like "use your package manager's exec/dlx command
to run ecc-install"), referencing the ecc-install command so Bun and Yarn users
have clear examples.

Expand Down
11 changes: 7 additions & 4 deletions .opencode/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
/**
* Everything Claude Code (ECC) Plugin for OpenCode
*
* This package provides a complete OpenCode plugin with:
* - 13 specialized agents (planner, architect, code-reviewer, etc.)
* - 31 commands (/plan, /tdd, /code-review, etc.)
* This package provides the published ECC OpenCode plugin module:
* - Plugin hooks (auto-format, TypeScript check, console.log warning, env injection, etc.)
* - Custom tools (run-tests, check-coverage, security-audit, format-code, lint-check, git-summary)
* - 37 skills (coding-standards, security-review, tdd-workflow, etc.)
* - Bundled reference config/assets for the wider ECC OpenCode setup
*
* Usage:
*
Expand All @@ -22,6 +20,10 @@
* }
* ```
*
* That enables the published plugin module only. For ECC commands, agents,
* prompts, and instructions, use this repository's `.opencode/opencode.json`
* as a base or copy the bundled `.opencode/` assets into your project.
*
* Option 2: Clone and use directly
* ```bash
* git clone https://github.com/affaan-m/everything-claude-code
Expand Down Expand Up @@ -51,6 +53,7 @@ export const metadata = {
agents: 13,
commands: 31,
skills: 37,
configAssets: true,
hookEvents: [
"file.edited",
"tool.execute.before",
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,13 @@ Then add to your `opencode.json`:
}
```

That npm plugin entry enables ECC's published OpenCode plugin module (hooks/events and plugin tools).
It does **not** automatically add ECC's full command/agent/instruction catalog to your project config.

For the full ECC OpenCode setup, either:
- run OpenCode inside this repository, or
- copy the bundled `.opencode/` config assets into your project and wire the `instructions`, `agent`, and `command` entries in `opencode.json`

### Documentation

- **Migration Guide**: `.opencode/MIGRATION.md`
Expand Down
6 changes: 4 additions & 2 deletions commands/e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,10 @@ For PMX, prioritize these E2E tests:

## Related Agents

This command invokes the `e2e-runner` agent located at:
`~/.claude/agents/e2e-runner.md`
This command invokes the `e2e-runner` agent provided by ECC.

For manual installs, the source file lives at:
`agents/e2e-runner.md`

## Quick Commands

Expand Down
6 changes: 4 additions & 2 deletions commands/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,7 @@ After planning:

## Related Agents

This command invokes the `planner` agent located at:
`~/.claude/agents/planner.md`
This command invokes the `planner` agent provided by ECC.

For manual installs, the source file lives at:
`agents/planner.md`
10 changes: 6 additions & 4 deletions commands/tdd.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,10 @@ Never skip the RED phase. Never write code before tests.

## Related Agents

This command invokes the `tdd-guide` agent located at:
`~/.claude/agents/tdd-guide.md`
This command invokes the `tdd-guide` agent provided by ECC.

And can reference the `tdd-workflow` skill at:
`~/.claude/skills/tdd-workflow/`
The related `tdd-workflow` skill is also bundled with ECC.

For manual installs, the source files live at:
- `agents/tdd-guide.md`
- `skills/tdd-workflow/SKILL.md`
21 changes: 20 additions & 1 deletion schemas/plugin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@
"agents": {
"type": "array",
"items": { "type": "string" }
},
"features": {
"type": "object",
"properties": {
"agents": { "type": "integer", "minimum": 0 },
"commands": { "type": "integer", "minimum": 0 },
"skills": { "type": "integer", "minimum": 0 },
"configAssets": { "type": "boolean" },
"hookEvents": {
"type": "array",
"items": { "type": "string" }
},
"customTools": {
"type": "array",
"items": { "type": "string" }
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
133 changes: 102 additions & 31 deletions scripts/hooks/post-edit-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,34 @@
* Fails silently if no formatter is found or installed.
*/

const { execFileSync } = require('child_process');
const { execFileSync, spawnSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const { getPackageManager } = require('../lib/package-manager');

const MAX_STDIN = 1024 * 1024; // 1MB limit
const BIOME_CONFIGS = ['biome.json', 'biome.jsonc'];
const PRETTIER_CONFIGS = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.json5',
'.prettierrc.js',
'.prettierrc.cjs',
'.prettierrc.mjs',
'.prettierrc.ts',
'.prettierrc.cts',
'.prettierrc.mts',
'.prettierrc.yml',
'.prettierrc.yaml',
'.prettierrc.toml',
'prettier.config.js',
'prettier.config.cjs',
'prettier.config.mjs',
'prettier.config.ts',
'prettier.config.cts',
'prettier.config.mts',
];
const PROJECT_ROOT_MARKERS = ['package.json', ...BIOME_CONFIGS, ...PRETTIER_CONFIGS];
let data = '';
process.stdin.setEncoding('utf8');

Expand All @@ -27,50 +50,102 @@ process.stdin.on('data', chunk => {

function findProjectRoot(startDir) {
let dir = startDir;
while (dir !== path.dirname(dir)) {
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
dir = path.dirname(dir);
let fallbackDir = null;

while (true) {
if (detectFormatter(dir)) {
return dir;
}

if (!fallbackDir && PROJECT_ROOT_MARKERS.some(marker => fs.existsSync(path.join(dir, marker)))) {
fallbackDir = dir;
}

const parentDir = path.dirname(dir);
if (parentDir === dir) break;
dir = parentDir;
}
return startDir;

return fallbackDir || startDir;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function detectFormatter(projectRoot) {
const biomeConfigs = ['biome.json', 'biome.jsonc'];
for (const cfg of biomeConfigs) {
for (const cfg of BIOME_CONFIGS) {
if (fs.existsSync(path.join(projectRoot, cfg))) return 'biome';
}

const prettierConfigs = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.js',
'.prettierrc.cjs',
'.prettierrc.mjs',
'.prettierrc.yml',
'.prettierrc.yaml',
'.prettierrc.toml',
'prettier.config.js',
'prettier.config.cjs',
'prettier.config.mjs',
];
for (const cfg of prettierConfigs) {
for (const cfg of PRETTIER_CONFIGS) {
if (fs.existsSync(path.join(projectRoot, cfg))) return 'prettier';
}

return null;
}

function getFormatterCommand(formatter, filePath) {
const npxBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
function getRunnerBin(bin) {
if (process.platform !== 'win32') return bin;
if (bin === 'npx') return 'npx.cmd';
if (bin === 'pnpm') return 'pnpm.cmd';
if (bin === 'yarn') return 'yarn.cmd';
if (bin === 'bunx') return 'bunx.cmd';
return bin;
}

function getFormatterRunner(projectRoot) {
const pm = getPackageManager({ projectDir: projectRoot });
const execCmd = pm?.config?.execCmd || 'npx';
const [bin = 'npx', ...prefix] = execCmd.split(/\s+/).filter(Boolean);

return {
bin: getRunnerBin(bin),
prefix
};
}

function getFormatterCommand(formatter, filePath, projectRoot) {
const runner = getFormatterRunner(projectRoot);

if (formatter === 'biome') {
return { bin: npxBin, args: ['@biomejs/biome', 'format', '--write', filePath] };
return {
bin: runner.bin,
args: [...runner.prefix, '@biomejs/biome', 'format', '--write', filePath]
};
}
if (formatter === 'prettier') {
return { bin: npxBin, args: ['prettier', '--write', filePath] };
return {
bin: runner.bin,
args: [...runner.prefix, 'prettier', '--write', filePath]
};
Comment on lines +93 to +117
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "post-edit-format.js" -type f

Repository: affaan-m/everything-claude-code

Length of output: 110


🏁 Script executed:

find . -name "package-manager.js" -type f

Repository: affaan-m/everything-claude-code

Length of output: 107


🏁 Script executed:

wc -l ./scripts/hooks/post-edit-format.js

Repository: affaan-m/everything-claude-code

Length of output: 114


🏁 Script executed:

cat -n ./scripts/hooks/post-edit-format.js

Repository: affaan-m/everything-claude-code

Length of output: 5699


🏁 Script executed:

cat -n ./scripts/lib/package-manager.js

Repository: affaan-m/everything-claude-code

Length of output: 15103


Prefer the project-local formatter before dlx/bunx fallback.

getPackageManager() returns execCmd values like pnpm dlx and yarn dlx, so this code unconditionally uses those runners instead of checking the repo's installed formatter first. Unlike npx, pnpm dlx and yarn dlx ignore local node_modules/.bin/ binaries and always download the package. This can pull a different formatter version, miss local plugins, or fail offline even though the project already has the formatter pinned. Probe node_modules/.bin/ first, and only fall back to the package-manager runner when the local binary is missing.

Possible fix
 function getFormatterCommand(formatter, filePath, projectRoot) {
+  const localBinName = process.platform === 'win32'
+    ? (formatter === 'biome' ? 'biome.cmd' : 'prettier.cmd')
+    : (formatter === 'biome' ? 'biome' : 'prettier');
+  const localBin = path.join(projectRoot, 'node_modules', '.bin', localBinName);
+
+  if (fs.existsSync(localBin)) {
+    return formatter === 'biome'
+      ? { bin: localBin, args: ['format', '--write', filePath] }
+      : { bin: localBin, args: ['--write', filePath] };
+  }
+
   const runner = getFormatterRunner(projectRoot);
 
   if (formatter === 'biome') {
     return {
       bin: runner.bin,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/hooks/post-edit-format.js` around lines 93 - 117, The runner
currently always uses the package-manager execCmd (e.g., "pnpm dlx" / "yarn
dlx") which bypasses project-local binaries; update getFormatterCommand to
prefer a project-local binary under projectRoot/node_modules/.bin (check
existence/readability using fs.existsSync or fs.access) for the formatter binary
name ("biome" for `@biomejs/biome`, "prettier" for prettier) and return that
binary (no prefix) when present; only if the local binary is missing, fall back
to the existing behavior that uses getFormatterRunner(projectRoot) with
runner.bin and runner.prefix plus the package name/args. Ensure you reference
getFormatterRunner and keep formatter-specific package names (`@biomejs/biome` and
prettier) when constructing fallback args.

}
return null;
}

function runFormatterCommand(cmd, projectRoot) {
if (process.platform === 'win32' && cmd.bin.endsWith('.cmd')) {
const result = spawnSync(cmd.bin, cmd.args, {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Potential command injection on Windows: spawnSync with shell: true passes the unsanitized filePath through cmd.exe, where shell metacharacters (&, |, >, etc.) in the path can execute arbitrary commands. Validate or quote the file path before passing it to a shell-enabled spawn.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/hooks/post-edit-format.js, line 124:

<comment>Potential command injection on Windows: `spawnSync` with `shell: true` passes the unsanitized `filePath` through `cmd.exe`, where shell metacharacters (`&`, `|`, `>`, etc.) in the path can execute arbitrary commands. Validate or quote the file path before passing it to a shell-enabled spawn.</comment>

<file context>
@@ -114,6 +119,33 @@ function getFormatterCommand(formatter, filePath, projectRoot) {
 
+function runFormatterCommand(cmd, projectRoot) {
+  if (process.platform === 'win32' && cmd.bin.endsWith('.cmd')) {
+    const result = spawnSync(cmd.bin, cmd.args, {
+      cwd: projectRoot,
+      shell: true,
</file context>
Fix with Cubic

cwd: projectRoot,
shell: true,
stdio: 'pipe',
timeout: 15000
});

if (result.error) {
throw result.error;
}

if (typeof result.status === 'number' && result.status !== 0) {
throw new Error(result.stderr?.toString() || `Formatter exited with status ${result.status}`);
}

return;
}

execFileSync(cmd.bin, cmd.args, {
cwd: projectRoot,
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 15000
});
}

process.stdin.on('end', () => {
try {
const input = JSON.parse(data);
Expand All @@ -80,14 +155,10 @@ process.stdin.on('end', () => {
try {
const projectRoot = findProjectRoot(path.dirname(path.resolve(filePath)));
const formatter = detectFormatter(projectRoot);
const cmd = getFormatterCommand(formatter, filePath);
const cmd = getFormatterCommand(formatter, filePath, projectRoot);

if (cmd) {
execFileSync(cmd.bin, cmd.args, {
cwd: projectRoot,
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 15000
});
runFormatterCommand(cmd, projectRoot);
}
} catch {
// Formatter not installed, file missing, or failed — non-blocking
Expand Down
Loading
Loading