Skip to content

fix: make mex sync tool-agnostic#30

Merged
theDakshJaitly merged 3 commits intomainfrom
fix/29-sync-tool-agnostic
Apr 13, 2026
Merged

fix: make mex sync tool-agnostic#30
theDakshJaitly merged 3 commits intomainfrom
fix/29-sync-tool-agnostic

Conversation

@theDakshJaitly
Copy link
Copy Markdown
Owner

Summary

Closes #29

  • mex sync no longer hardcodes Claude Code as the only AI tool
  • Tool selection from mex setup is now persisted to .mex/config.json
  • Sync dispatches to the correct CLI based on saved config (claude, opencode run, codex)
  • Tools without a CLI (Cursor, Windsurf, Copilot) gracefully fall back to "show prompts" mode
  • If multiple CLI tools are configured, user is prompted to pick one

Test plan

  • All 94 existing + new tests pass
  • Build succeeds
  • Run mex setup and select OpenCode → verify .mex/config.json has "aiTools": ["opencode"]
  • Run mex sync → verify it shows "OpenCode" in the menu instead of "Claude"
  • Run mex setup with multiple tools → verify sync lets you pick between them
  • Run mex sync with no CLI tool configured → verify it falls back to prompts mode

Copilot AI review requested due to automatic review settings April 13, 2026 13:01
Closes #29

- Add AiTool type and AI_TOOLS registry with CLI metadata per tool
- Persist tool selection from setup to .mex/config.json
- Load saved aiTools in findConfig and pass through MexConfig
- Sync now dispatches to the correct CLI (claude, opencode run, codex)
- Tools without a CLI (Cursor, Windsurf, Copilot) fall back to prompts mode
- Replace hardcoded "Claude" strings in sync UI and cli help text
- Add tests for config persistence (save/load aiTools)
@theDakshJaitly theDakshJaitly force-pushed the fix/29-sync-tool-agnostic branch from af89487 to 3a53d7c Compare April 13, 2026 13:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR makes mex sync tool-agnostic by persisting the user’s AI tool selection from mex setup and dispatching sync to the correct CLI tool (or falling back to prompt-only mode when no supported CLI is available), addressing Issue #29.

Changes:

  • Add persisted AI tool selection (aiTools) to the mex config model and save/load it via .mex/config.json.
  • Update mex sync to select an installed CLI tool from the configured set and run it interactively, otherwise fall back to “show prompts”.
  • Add/extend tests for config persistence and update CLI help text to remove Claude-specific wording.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/config.test.ts Adds tests for loading/saving persisted aiTools selection.
src/types.ts Introduces AiTool/AI_TOOLS metadata and extends MexConfig with aiTools.
src/sync/index.ts Implements tool selection + dispatch to the selected tool’s CLI (or prompt fallback).
src/setup/index.ts Persists tool choice from setup into mex config.
src/config.ts Loads/saves aiTools from a persisted JSON config file.
src/cli.ts Updates command help text to be tool-agnostic (“AI” vs “Claude”).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config.ts Outdated
Comment on lines +59 to +67
aiTools?: AiTool[];
}

function loadAiTools(scaffoldRoot: string): AiTool[] {
const configPath = resolve(scaffoldRoot, CONFIG_FILE);
if (!existsSync(configPath)) return [];
try {
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as MexPersistedConfig;
return Array.isArray(raw.aiTools) ? raw.aiTools : [];
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

loadAiTools() only checks that aiTools is an array, but doesn’t validate each entry. If config.json contains an unknown string (or non-string), later code like AI_TOOLS[t] in sync can be undefined and cause a runtime crash when reading meta.cli. Consider filtering to known tools (e.g., Object.keys(AI_TOOLS)) and dropping invalid entries when loading.

Suggested change
aiTools?: AiTool[];
}
function loadAiTools(scaffoldRoot: string): AiTool[] {
const configPath = resolve(scaffoldRoot, CONFIG_FILE);
if (!existsSync(configPath)) return [];
try {
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as MexPersistedConfig;
return Array.isArray(raw.aiTools) ? raw.aiTools : [];
aiTools?: unknown;
}
function isAiTool(value: unknown): value is AiTool {
return typeof value === "string";
}
function loadAiTools(scaffoldRoot: string): AiTool[] {
const configPath = resolve(scaffoldRoot, CONFIG_FILE);
if (!existsSync(configPath)) return [];
try {
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as MexPersistedConfig;
return Array.isArray(raw.aiTools) ? raw.aiTools.filter(isAiTool) : [];

Copilot uses AI. Check for mistakes.
Comment thread src/config.ts Outdated
Comment on lines +56 to +66
const CONFIG_FILE = "config.json";

interface MexPersistedConfig {
aiTools?: AiTool[];
}

function loadAiTools(scaffoldRoot: string): AiTool[] {
const configPath = resolve(scaffoldRoot, CONFIG_FILE);
if (!existsSync(configPath)) return [];
try {
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as MexPersistedConfig;
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

Config persistence is resolved relative to scaffoldRoot (resolve(scaffoldRoot, "config.json")). When findScaffoldRoot() returns projectRoot (the context/ layout), this becomes <projectRoot>/config.json, which is a very generic filename and can easily collide with an app’s own config.json (and it also differs from the PR description of .mex/config.json). Consider always reading/writing under a mex-specific path (e.g. <projectRoot>/.mex/config.json or a hidden mex-specific filename) regardless of scaffold layout.

Copilot uses AI. Check for mistakes.
Comment thread src/config.ts
Comment on lines +73 to +83
export function saveAiTools(scaffoldRoot: string, tools: AiTool[]): void {
const configPath = resolve(scaffoldRoot, CONFIG_FILE);
let existing: MexPersistedConfig = {};
if (existsSync(configPath)) {
try {
existing = JSON.parse(readFileSync(configPath, "utf-8")) as MexPersistedConfig;
} catch { /* start fresh */ }
}
existing.aiTools = tools;
mkdirSync(dirname(configPath), { recursive: true });
writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

saveAiTools() assumes JSON.parse() returns an object. If config.json is valid JSON but not an object (e.g. an array/number/string), existing.aiTools = tools will throw at runtime. Consider guarding existing to a plain object (non-null, typeof object, not Array) before assigning, and also consider de-duplicating tools to avoid repeated entries from multi-select input.

Copilot uses AI. Check for mistakes.
Comment thread src/sync/index.ts
Comment on lines +19 to +26
function hasCliTool(cmd: string): boolean {
try {
execSync(`which ${cmd}`, { stdio: "ignore" });
return true;
} catch {
return false;
}
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

hasCliTool() uses execSync(which ${cmd}), which is shell-based and not portable (e.g. Windows typically uses where). Even though cmd currently comes from constants, using execFileSync/spawnSync with shell:false (and selecting which vs where by platform) would make this safer and more reliable across environments.

Copilot uses AI. Check for mistakes.
Comment thread src/setup/index.ts
Comment on lines +394 to 400
// Persist tool selection
if (selectedTools.length > 0 && !dryRun) {
const mexDir = resolve(projectRoot, ".mex");
saveAiTools(mexDir, selectedTools);
}

return selectedClaude;
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

MexConfig now requires aiTools, but later in this file the config object passed into runScan is still created as { projectRoot, scaffoldRoot: mexDir } (missing aiTools), which will fail npm run typecheck and makes the types inconsistent. Consider including aiTools there (e.g. selectedTools/[]) or changing runScan to accept a narrower config type if it only needs projectRoot.

Copilot uses AI. Check for mistakes.
@theDakshJaitly theDakshJaitly merged commit de1e9c5 into main Apr 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mex sync only uses claude

2 participants