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
246 changes: 238 additions & 8 deletions .agents/plugins/opencode-aidevops/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
const QUALITY_LOG = join(LOGS_DIR, "quality-hooks.log");
const IS_MACOS = platform() === "darwin";

/**
* Cached oh-my-opencode detection result.
* @type {{ detected: boolean, version: string, mcps: string[], hooks: string[], configPath: string } | null}
*/
let _omocState = null;

// ---------------------------------------------------------------------------
// Utility helpers
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -92,6 +98,184 @@
return { data, body };
}

// ---------------------------------------------------------------------------
// Phase 0: oh-my-opencode (OMOC) Detection & Compatibility (t008.4)
// ---------------------------------------------------------------------------

/**
* MCPs known to be managed by oh-my-opencode.
* When OMOC is detected, aidevops skips registering these to avoid duplicates.
* Maps OMOC MCP name → aidevops registry name (if different) or null (no equivalent).
*/
const OMOC_MANAGED_MCPS = {
websearch: null, // Exa web search — no aidevops equivalent
context7: "context7", // Both plugins register context7
grep_app: null, // GitHub code search — no aidevops equivalent
};

/**
* Hooks known to be provided by oh-my-opencode.
* aidevops skips overlapping hook behaviour when these are active.
*/
const OMOC_HOOK_NAMES = [
"comment-checker",
"todo-enforcer",
"todo-continuation-enforcer",
"aggressive-truncation",
"auto-resume",
"think-mode",
"ralph-loop",
];

/**
* Detect oh-my-opencode presence and capabilities.
*
* Detection strategy (ordered by reliability):
* 1. Check OpenCode config for OMOC in plugin array
* 2. Check for OMOC config files (project-level, then user-level)
* 3. Check for OMOC npm installation
*
* Results are cached after first call.
*
* @param {string} [directory] - Project directory to check for local config
* @returns {{ detected: boolean, version: string, mcps: string[], hooks: string[], configPath: string }}
*/
function detectOhMyOpenCode(directory) {

Check warning on line 143 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.agents/plugins/opencode-aidevops/index.mjs#L143

Method detectOhMyOpenCode has a cyclomatic complexity of 16 (limit is 8)
if (_omocState !== null) return _omocState;

_omocState = {
detected: false,
version: "",
mcps: [],
hooks: [],
configPath: "",
};

// 1. Check OpenCode config for OMOC in plugin array
const ocConfigPaths = [
join(HOME, ".config", "opencode", "opencode.json"),
join(HOME, ".config", "opencode", "opencode.jsonc"),
];

for (const configPath of ocConfigPaths) {
const content = readIfExists(configPath);
if (!content) continue;

try {
// Strip JSONC comments for parsing
const cleaned = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
const config = JSON.parse(cleaned);

if (Array.isArray(config.plugin) && config.plugin.includes("oh-my-opencode")) {
_omocState.detected = true;
break;
}
} catch {
// JSON parse error — try next config
}
}

// 2. Check for OMOC config files
const omocConfigPaths = [
directory ? join(directory, ".opencode", "oh-my-opencode.json") : "",

Check failure on line 180 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.agents/plugins/opencode-aidevops/index.mjs#L180

Detected possible user input going into a `path.join` or `path.resolve` function.
join(HOME, ".config", "opencode", "oh-my-opencode.json"),
].filter(Boolean);

for (const configPath of omocConfigPaths) {
if (existsSync(configPath)) {

Check warning on line 185 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.agents/plugins/opencode-aidevops/index.mjs#L185

Detected that function argument `directory` has entered the fs module.
_omocState.detected = true;
_omocState.configPath = configPath;

// Parse OMOC config to discover disabled hooks and MCP overrides
const content = readIfExists(configPath);
if (content) {
try {
const cleaned = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
const omocConfig = JSON.parse(cleaned);

// Discover which hooks are active (all enabled unless in disabled_hooks)
const disabledHooks = omocConfig.disabled_hooks || [];
_omocState.hooks = OMOC_HOOK_NAMES.filter((h) => !disabledHooks.includes(h));

// Discover which MCPs are active (all enabled unless explicitly disabled)
const mcpConfig = omocConfig.mcp || {};
_omocState.mcps = Object.keys(OMOC_MANAGED_MCPS).filter((name) => {
const mcpEntry = mcpConfig[name];
// MCP is active unless explicitly disabled in OMOC config
return !mcpEntry || mcpEntry.enabled !== false;
});
} catch {
// Parse error — assume defaults (all MCPs and hooks active)
_omocState.mcps = Object.keys(OMOC_MANAGED_MCPS);
_omocState.hooks = [...OMOC_HOOK_NAMES];
}
}
break;
}
}

// 3. If not yet detected, check npm for OMOC installation
if (!_omocState.detected) {
const npmCheck = run("npm ls oh-my-opencode --json 2>/dev/null", 5000);

Choose a reason for hiding this comment

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

medium

The use of 2>/dev/null here and on line 227 appears to violate the repository style guide (line 50), which states that blanket suppression should be avoided. The npm ls command is expected to fail with a non-zero exit code if the package is not found. It would be better to allow the run utility to handle this expected failure gracefully, perhaps with an option to ignore non-zero exit codes, rather than suppressing stderr at the command level.

References
  1. The style guide prohibits blanket error suppression with 2>/dev/null, allowing it only when redirecting to log files. (link)

if (npmCheck && npmCheck.includes("oh-my-opencode")) {
_omocState.detected = true;
}
Comment on lines +220 to +222

Choose a reason for hiding this comment

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

medium

Checking for the package by using npmCheck.includes("oh-my-opencode") on the JSON output of npm ls is not robust. It could lead to false positives if another package has "oh-my-opencode" in its name or description. Since you're using the --json flag, it would be more reliable to parse the JSON output and check for the existence of oh-my-opencode as a dependency.

Suggested change
if (npmCheck && npmCheck.includes("oh-my-opencode")) {
_omocState.detected = true;
}
if (npmCheck) {
try {
const npmInfo = JSON.parse(npmCheck);
if (npmInfo.dependencies?.['oh-my-opencode']) {
_omocState.detected = true;
}
} catch {
// Fallback for non-JSON output
if (npmCheck.includes("oh-my-opencode")) {
_omocState.detected = true;
}
}
}

}

// 4. Get OMOC version if detected
if (_omocState.detected) {
const version = run("npm view oh-my-opencode version 2>/dev/null", 5000);
if (version) {
_omocState.version = version;
}

// Default MCPs and hooks if not populated from config
if (_omocState.mcps.length === 0) {
_omocState.mcps = Object.keys(OMOC_MANAGED_MCPS);
}
if (_omocState.hooks.length === 0) {
_omocState.hooks = [...OMOC_HOOK_NAMES];
}

console.error(
`[aidevops] oh-my-opencode detected${_omocState.version ? ` (v${_omocState.version})` : ""}: ` +
`${_omocState.mcps.length} MCPs, ${_omocState.hooks.length} hooks active — ` +
`aidevops will complement (not duplicate) OMOC features`,
);
}

return _omocState;
}

Check notice on line 248 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

codefactor.io / CodeFactor

.agents/plugins/opencode-aidevops/index.mjs#L143-L248

Complex Method

/**
* Check if a specific MCP is managed by oh-my-opencode.
* @param {string} mcpName - aidevops MCP registry name
* @returns {boolean}
*/
function isMcpManagedByOmoc(mcpName) {
const omoc = detectOhMyOpenCode();
if (!omoc.detected) return false;

// Check if any OMOC MCP maps to this aidevops MCP name
for (const [omocName, aidevopsName] of Object.entries(OMOC_MANAGED_MCPS)) {
if (aidevopsName === mcpName && omoc.mcps.includes(omocName)) {
return true;
}
}
return false;
}

/**
* Check if a specific hook type is handled by oh-my-opencode.
* @param {string} hookName - OMOC hook name to check
* @returns {boolean}
*/
function isHookManagedByOmoc(hookName) {
const omoc = detectOhMyOpenCode();
if (!omoc.detected) return false;
return omoc.hooks.includes(hookName);
}

// ---------------------------------------------------------------------------
// Phase 1: Agent Loader
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -402,8 +586,9 @@
};

/**
* Oh-My-OpenCode tool patterns to disable globally.
* These MCPs may exist from old configs or OmO installations.
* Oh-My-OpenCode tool patterns to disable globally when OMOC is NOT detected.
* When OMOC IS detected, these are left alone (OMOC manages them).
* These MCPs may exist from old configs or stale OmO installations.
*/
const OMO_DISABLED_PATTERNS = ["grep_app_*", "websearch_*", "gh_grep_*"];

Expand All @@ -415,65 +600,75 @@
* @param {object} config - OpenCode Config object (mutable)
* @returns {number} Number of MCPs registered
*/
function registerMcpServers(config) {
if (!config.mcp) config.mcp = {};
if (!config.tools) config.tools = {};

const registry = getMcpRegistry();
let registered = 0;

for (const mcp of registry) {
// Skip MCPs managed by oh-my-opencode to avoid duplicates (t008.4)
if (isMcpManagedByOmoc(mcp.name)) {
console.error(`[aidevops] Skipping MCP '${mcp.name}' — managed by oh-my-opencode`);
continue;
}

// Skip macOS-only MCPs on other platforms
if (mcp.macOnly && !IS_MACOS) continue;

// Skip local MCPs whose binary isn't installed
if (mcp.requiresBinary) {
const binaryPath = run(`which ${mcp.requiresBinary}`);
if (!binaryPath) {
// Disable tools if binary not available
if (mcp.toolPattern) {
config.tools[mcp.toolPattern] = false;
}
continue;
}
}

// Register MCP server if not already configured (or if alwaysOverwrite)
if (!config.mcp[mcp.name] || mcp.alwaysOverwrite) {
if (mcp.type === "remote" && mcp.url) {
config.mcp[mcp.name] = {
type: "remote",
url: mcp.url,
enabled: mcp.eager,
};
} else {
config.mcp[mcp.name] = {
type: "local",
command: mcp.command,
enabled: mcp.eager,
};
}
registered++;
} else {
// Enforce loading policy on existing MCPs
config.mcp[mcp.name].enabled = mcp.eager;
}

// Set global tool permissions
if (mcp.toolPattern) {
config.tools[mcp.toolPattern] = mcp.globallyEnabled;
}
}

// Disable Oh-My-OpenCode tool patterns globally
for (const pattern of OMO_DISABLED_PATTERNS) {
if (!(pattern in config.tools)) {
config.tools[pattern] = false;
// Disable stale Oh-My-OpenCode tool patterns — but only when OMOC is NOT active.
// When OMOC is detected, it manages its own tool permissions.
const omoc = detectOhMyOpenCode();
if (!omoc.detected) {
for (const pattern of OMO_DISABLED_PATTERNS) {
if (!(pattern in config.tools)) {
config.tools[pattern] = false;
}
}
}

return registered;
}

Check notice on line 671 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

codefactor.io / CodeFactor

.agents/plugins/opencode-aidevops/index.mjs#L603-L671

Complex Method

/**
* Apply per-agent MCP tool permissions.
Expand Down Expand Up @@ -573,84 +768,84 @@
* @param {string} filePath
* @returns {{ violations: number, details: string[] }}
*/
function validateReturnStatements(filePath) {
const details = [];
let violations = 0;

try {
const content = readFileSync(filePath, "utf-8");
const lines = content.split("\n");

// Find function definitions and check for return statements
let inFunction = false;
let functionName = "";
let functionStart = 0;
let braceDepth = 0;
let hasReturn = false;

for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();

// Detect function definition: name() { or function name {
const funcMatch = trimmed.match(
/^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\)\s*\{/,
);
const funcMatch2 = trimmed.match(/^function\s+([a-zA-Z_][a-zA-Z0-9_]*)/);

if (funcMatch || funcMatch2) {
if (inFunction && !hasReturn) {
details.push(
` Line ${functionStart}: function '${functionName}' missing explicit return`,
);
violations++;
}
inFunction = true;
functionName = funcMatch ? funcMatch[1] : funcMatch2[1];
functionStart = i + 1;
braceDepth = trimmed.includes("{") ? 1 : 0;
hasReturn = false;
continue;
}

if (inFunction) {
// Track brace depth
for (const ch of trimmed) {
if (ch === "{") braceDepth++;
else if (ch === "}") braceDepth--;
}

// Check for return statement
if (/\breturn\s+[0-9]/.test(trimmed) || /\breturn\s*$/.test(trimmed)) {
hasReturn = true;
}

// Function ended
if (braceDepth <= 0) {
if (!hasReturn) {
details.push(
` Line ${functionStart}: function '${functionName}' missing explicit return`,
);
violations++;
}
inFunction = false;
}
}
}

// Handle last function if file ends inside it
if (inFunction && !hasReturn) {
details.push(
` Line ${functionStart}: function '${functionName}' missing explicit return`,
);
violations++;
}
} catch {
// File read error — skip validation
}

return { violations, details };
}

Check notice on line 848 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

codefactor.io / CodeFactor

.agents/plugins/opencode-aidevops/index.mjs#L771-L848

Complex Method

/**
* Validate positional parameter usage in shell scripts.
Expand Down Expand Up @@ -935,55 +1130,55 @@
* @param {object} input - { tool, sessionID, callID }
* @param {object} output - { title, output, metadata } (mutable)
*/
async function toolExecuteAfter(input, output) {
const toolName = input.tool || "";
const title = output.title || "";
const outputText = output.output || "";

// Track git operations for pattern recording
if (toolName === "Bash" || toolName === "bash") {
if (title.includes("git commit") || title.includes("git push")) {
console.error(`[aidevops] Git operation detected: ${title}`);
qualityLog("INFO", `Git operation: ${title}`);

// Record pattern if pattern-tracker-helper.sh is available
const patternTracker = join(SCRIPTS_DIR, "pattern-tracker-helper.sh");
if (existsSync(patternTracker)) {
const success = !outputText.includes("error") && !outputText.includes("fatal");
const patternType = success ? "SUCCESS_PATTERN" : "FAILURE_PATTERN";
run(
`bash "${patternTracker}" record "${patternType}" "git operation: ${title.substring(0, 100)}" --tag "quality-hook" 2>/dev/null`,
5000,
);
}
}

// Track ShellCheck/lint runs in Bash commands
if (
title.includes("shellcheck") ||
title.includes("linters-local")
) {
const passed = !outputText.includes("error") && !outputText.includes("violation");
qualityLog(
passed ? "INFO" : "WARN",
`Lint run: ${title} — ${passed ? "PASS" : "issues found"}`,
);
}
}

// Track Write/Edit operations for quality metrics
if (
toolName === "Write" ||
toolName === "Edit" ||
toolName === "write" ||
toolName === "edit"
) {
const filePath = output.metadata?.filePath || "";
if (filePath) {
qualityLog("INFO", `File modified: ${filePath} via ${toolName}`);
}
}
}

Check notice on line 1181 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

codefactor.io / CodeFactor

.agents/plugins/opencode-aidevops/index.mjs#L1133-L1181

Complex Method

// ---------------------------------------------------------------------------
// Phase 4: Shell Environment
Expand Down Expand Up @@ -1156,6 +1351,29 @@
].join("\n");
}

/**
* Get oh-my-opencode compatibility state for compaction context.
* @returns {string}
*/
function getOmocState() {
const omoc = detectOhMyOpenCode();
if (!omoc.detected) return "";

const lines = ["## oh-my-opencode Compatibility"];
lines.push(`oh-my-opencode detected${omoc.version ? ` (v${omoc.version})` : ""}`);
lines.push("aidevops complements OMOC — no duplicate MCPs or hooks.");

if (omoc.mcps.length > 0) {
lines.push(`OMOC-managed MCPs (skipped by aidevops): ${omoc.mcps.join(", ")}`);
}
if (omoc.hooks.length > 0) {
lines.push(`OMOC hooks active: ${omoc.hooks.join(", ")}`);
lines.push("aidevops hooks (ShellCheck, return-statements, secrets, MD031) are complementary.");
}

return lines.join("\n");
}

/**
* Compaction hook — inject aidevops context into compaction summary.
* @param {object} _input - { sessionID }
Expand All @@ -1170,6 +1388,7 @@
getRelevantMemories(directory),
getGitContext(directory),
getMailboxState(),
getOmocState(),
].filter(Boolean);

if (sections.length === 0) return;
Expand Down Expand Up @@ -1287,53 +1506,53 @@
aidevops_quality_check: {
description:
'Run quality checks on a file or the full pre-commit pipeline. Args: file (string, path to check) OR command "pre-commit" to run full pipeline on staged files',
async execute(args) {
const file = args.file || args.command || args;

// Full pre-commit pipeline
if (file === "pre-commit" || file === "staged") {
const hookScript = join(SCRIPTS_DIR, "pre-commit-hook.sh");
if (!existsSync(hookScript)) {
return "pre-commit-hook.sh not found — run aidevops update";
}
try {
const result = execSync(`bash "${hookScript}"`, {
encoding: "utf-8",
timeout: 30000,
stdio: ["pipe", "pipe", "pipe"],
});
return `Pre-commit quality checks PASSED:\n${result.trim()}`;
} catch (err) {
const cmdOutput = (err.stdout || "") + (err.stderr || "");
return `Pre-commit quality checks FAILED:\n${cmdOutput.trim()}`;
}
}

// Single file check
if (typeof file === "string" && file.endsWith(".sh")) {
const { totalViolations, report } = runShellQualityPipeline(file);
return totalViolations > 0
? `Quality check: ${totalViolations} issue(s) found:\n${report}`
: "Quality check: all checks passed.";
}

if (typeof file === "string" && file.endsWith(".md")) {
const { totalViolations, report } = runMarkdownQualityPipeline(file);
return totalViolations > 0
? `Markdown check: ${totalViolations} issue(s) found:\n${report}`
: "Markdown check: all checks passed.";
}

// Generic secrets scan
if (typeof file === "string" && existsSync(file)) {
const secretResult = scanForSecrets(file);
return secretResult.violations > 0
? `Secrets scan: ${secretResult.violations} potential issue(s):\n${secretResult.details.join("\n")}`
: "Secrets scan: no issues found.";
}

return `Usage: pass a file path (.sh or .md) or "pre-commit" for full pipeline`;
},

Check notice on line 1555 in .agents/plugins/opencode-aidevops/index.mjs

View check run for this annotation

codefactor.io / CodeFactor

.agents/plugins/opencode-aidevops/index.mjs#L1509-L1555

Complex Method
},

aidevops_install_hooks: {
Expand Down Expand Up @@ -1397,6 +1616,7 @@
* aidevops OpenCode Plugin
*
* Provides:
* 0. oh-my-opencode detection — detects OMOC presence and deduplicates (t008.4)
* 1. Config hook — dynamic agent loading + MCP server registration from ~/.aidevops/agents/
* 2. Custom tools — aidevops CLI, memory, pre-edit check, quality check, hook installer
* 3. Quality hooks — full pre-commit pipeline (ShellCheck, return statements,
Expand All @@ -1409,27 +1629,37 @@
* - Enforces eager/lazy loading policy (only osgrep starts at launch)
* - Sets global tool permissions and per-agent MCP tool enablement
* - Skips MCPs whose required binaries aren't installed
* - Skips MCPs managed by oh-my-opencode when OMOC is detected (t008.4)
* - Disables Oh-My-OpenCode tool patterns globally
* - Complements generate-opencode-agents.sh (shell script takes precedence)
*
* oh-my-opencode compatibility (Phase 0, t008.4):
* - Detects OMOC via OpenCode config, OMOC config files, and npm
* - Skips MCP registration for MCPs managed by OMOC (context7, websearch, grep_app)
* - Quality hooks are complementary (aidevops: ShellCheck, secrets; OMOC: comments, todos)
* - OMOC state injected into compaction context for session continuity
*
* @type {import('@opencode-ai/plugin').Plugin}
*/
export async function AidevopsPlugin({ directory }) {
// Phase 0: Detect oh-my-opencode early so all hooks can adapt
detectOhMyOpenCode(directory);

return {
// Phase 1+2: Dynamic agent and config injection
config: async (config) => configHook(config),

// Phase 1: Custom tools
tool: createTools(),

// Phase 3: Quality hooks
// Phase 3: Quality hooks (complementary to OMOC — no overlap)
"tool.execute.before": toolExecuteBefore,
"tool.execute.after": toolExecuteAfter,

// Phase 4: Shell environment
"shell.env": shellEnvHook,

// Compaction context (existing + improved)
// Compaction context (includes OMOC state when detected)
"experimental.session.compacting": async (input, output) =>
compactingHook(input, output, directory),
};
Expand Down
26 changes: 24 additions & 2 deletions .agents/tools/build-mcp/aidevops-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ tools:

## Quick Reference

- **Status**: Implemented (t008.1 PR #1138, t008.2 PR #1149)
- **Status**: Implemented (t008.1 PR #1138, t008.2 PR #1149, t008.3 PR #1150, t008.4 PR #1157)
- **Purpose**: Native OpenCode plugin wrapper for aidevops
- **Approach**: Single-file ESM plugin using hooks-based SDK pattern
- **Location**: `.agents/plugins/opencode-aidevops/index.mjs`
- **SDK**: `@opencode-ai/plugin` v1.1.56+
- **OMOC Compatibility**: Detects and complements oh-my-opencode when both installed

**Key Decision**: Plugin complements `generate-opencode-agents.sh` — the shell
script handles primary agent config, the plugin adds runtime hooks and tools.
Expand Down Expand Up @@ -109,6 +110,27 @@ interface Hooks {

### Hooks Implemented

#### 0. oh-my-opencode Detection (t008.4)

At plugin startup, detects whether oh-my-opencode (OMOC) is installed and active. Detection checks (in order):

1. OpenCode config (`opencode.json`) — looks for `"oh-my-opencode"` in the `plugin` array
2. OMOC config files — `.opencode/oh-my-opencode.json` (project) or `~/.config/opencode/oh-my-opencode.json` (user)
3. npm installation — `npm ls oh-my-opencode`

When OMOC is detected:

| Behaviour | Without OMOC | With OMOC |
|-----------|-------------|-----------|
| context7 MCP | Registered by aidevops | Skipped (OMOC manages it) |
| websearch/grep_app MCPs | N/A (not in aidevops registry) | Left to OMOC |
| Stale OMOC tool patterns | Disabled globally | Left alone (OMOC manages) |
| ShellCheck/secrets hooks | Active | Active (complementary) |
| Comment-checker/todo-enforcer | N/A | OMOC handles |
| Compaction context | Standard | Includes OMOC state |

Results are cached after first detection. The OMOC state is logged at startup and injected into compaction context for session continuity.

#### 1. Config Hook — Dynamic Agent Loading + MCP Registration

The config hook performs two complementary registrations:
Expand Down Expand Up @@ -226,7 +248,7 @@ Preserves operational state across context resets:
| Data-driven MCP registry over config file | Plugin needs runtime binary detection and platform-specific logic that a static JSON config cannot express |
| Only osgrep eager-loaded | All other MCPs lazy-load on demand to save ~7K+ tokens on session startup |
| Shell script takes precedence for MCPs | Plugin only registers MCPs not already configured; `generate-opencode-agents.sh` definitions win |
| Phase 4 (oh-my-opencode) skipped | oh-my-opencode is deprecated and actively removed by setup.sh |
| Complement oh-my-opencode, don't conflict | OMOC detected at startup; shared MCPs (context7) skipped; quality hooks are complementary (no overlap) |

## Future Enhancements

Expand Down
Loading