feat(providers): multi-provider AI architecture#171
Conversation
- Add PROVIDER_ID = 'claude' constant - Rename assertClaudeAvailable -> assertAvailable (generic contract name) - Rename invokeClaude -> invoke (generic contract name) - Keep getCommandsDir unchanged - Add provider interface contract JSDoc block - Pure rename/restructure pass, no logic changes
- Re-exports all from lib/provider-claude.cjs via spread - Adds legacy aliases assertClaudeAvailable and invokeClaude - bin/mgw.cjs and lib/index.cjs continue to work without changes
- Created 32-01-SUMMARY.md documenting plan completion - 2 tasks executed, 2 files modified (provider-claude.cjs created, claude.cjs shimmed)
…solution - Registry maps PROVIDER_ID strings to provider modules - getProvider(id) defaults to 'claude', throws on unknown with helpful message - listProviders() returns array of registered IDs - Pre-populated with claude provider
- Replace direct lib/claude.cjs import in bin/mgw.cjs with ProviderManager - Add --provider global flag to mgw CLI for future provider selection - Route all provider calls through provider.assertAvailable(), provider.invoke(), provider.getCommandsDir() - Fix help command getCommandsDir() call to route through ProviderManager.getProvider() - Add ProviderManager to lib/index.cjs barrel export [Rule 1 - Bug] Fixed help command calling bare getCommandsDir() which was no longer imported after switching to ProviderManager import. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 32-02-SUMMARY.md documents ProviderManager creation and bin/mgw.cjs wiring - Phase 32 plans 01 and 02 both complete with all success criteria met - Provider interface abstraction layer fully operational Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ementation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PROVIDER_ID='gemini' - assertAvailable() checks gemini binary via execSync; exits with install instructions on ENOENT - getCommandsDir() returns ~/.gemini/commands; throws clear message if missing - invoke() prepends commandFile contents as <system> block (no --system-prompt-file in gemini CLI) - invoke() supports --model flag, quiet buffering, dry-run mode - opts.json ignored (gemini CLI has no --output-format json equivalent)
- PROVIDER_ID='opencode' - assertAvailable() checks opencode binary via execSync; exits with install instructions on ENOENT - getCommandsDir() returns ~/.opencode/commands; throws clear message if missing - invoke() uses 'opencode run' subcommand as non-interactive mode - invoke() passes commandFile via --system-prompt flag (opencode native support) - invoke() supports --model flag, quiet buffering, dry-run mode - opts.json ignored (opencode CLI has no --output-format json equivalent)
…Manager registry
- Added gemini: require('./provider-gemini.cjs') to registry dict
- Added opencode: require('./provider-opencode.cjs') to registry dict
- Updated JSDoc to list all three provider IDs
- getProvider('unknown') error message auto-lists all three providers via Object.keys()
- ProviderManager.listProviders() now returns ['claude', 'gemini', 'opencode']
…summaries - 33-01-SUMMARY.md: GeminiProvider with inline system-prompt injection - 33-02-SUMMARY.md: OpenCodeProvider with native --system-prompt file support - 33-03-SUMMARY.md: ProviderManager registry updated with all three providers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add resolveGsdRoot() with tier 1 (GSD_TOOLS_PATH env), tier 2 (.mgw/config.json gsd_path), tier 3 (default ~/.claude/get-shit-done/) - Update getGsdToolsPath() to use resolveGsdRoot() internally - Error message now lists all checked paths when binary not found - Export resolveGsdRoot for callers needing workflow path construction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-detects active AI CLI binary (claude > gemini > opencode priority) - --provider flag overrides auto-detection (both = and space forms) - PROVIDER_TARGETS map routes to ~/.claude/commands/mgw/, ~/.gemini/commands/mgw/, ~/.opencode/commands/ - Reads/writes ~/.mgw-install-state.json to detect provider switches - Removes old provider install dir when switching providers (fs.rmSync) - Prints skip message and exits 0 when no CLI found or base dir missing - Invalid --provider value exits 1 with list of valid options
Testing ProceduresStep-by-step instructions to verify the multi-provider AI architecture. Prerequisites# Clone/navigate to the repo
cd /path/to/mgw
# Ensure dependencies are installed
npm install1. Provider Manager — Unit Verificationnode -e "
const { ProviderManager } = require('./lib/provider-manager.cjs');
const pm = new ProviderManager();
console.log('Registered providers:', pm.listProviders());
// Expected: ['claude', 'gemini', 'opencode']
const p = pm.getProvider('claude');
console.log('Claude provider:', p.name);
"2. Backward Compatibility Shimnode -e "
const claude = require('./lib/claude.cjs');
console.log('invoke exported:', typeof claude.invoke);
console.log('assertAvailable exported:', typeof claude.assertAvailable);
// Both should print 'function'
"3. GSD Adapter — resolveGsdRoot() 3-Tier Lookup# Tier 1: env var
GSD_TOOLS_PATH=/tmp/custom-gsd node -e "
const { resolveGsdRoot } = require('./lib/gsd-adapter.cjs');
console.log(resolveGsdRoot({}));
// Expected: /tmp/custom-gsd
"
# Tier 2: config value (unset env var)
node -e "
const { resolveGsdRoot } = require('./lib/gsd-adapter.cjs');
console.log(resolveGsdRoot({ gsd_path: '/tmp/config-gsd' }));
// Expected: /tmp/config-gsd
"
# Tier 3: default fallback
node -e "
const { resolveGsdRoot } = require('./lib/gsd-adapter.cjs');
console.log(resolveGsdRoot({}));
// Expected: ~/.claude/get-shit-done (expanded path)
"4. mgw-install — Auto-Detection# Dry run with Claude (assuming claude CLI is installed)
node bin/mgw-install.cjs --dry-run
# Expected: detects 'claude', target dir is ~/.claude/commands/mgw/
# Force a specific provider
node bin/mgw-install.cjs --provider gemini --dry-run
# Expected: target dir is ~/.gemini/commands/mgw/
node bin/mgw-install.cjs --provider opencode --dry-run
# Expected: target dir is ~/.opencode/commands/mgw/5. Claude Provider — Existing Workflow (Regression)# Verify mgw still works with Claude (default)
node bin/mgw.cjs help
# Expected: help output displayed, no errors6. Human-Required: Gemini CLI End-to-End
node bin/mgw.cjs --provider gemini mgw:help
# Expected: Gemini CLI executes with system block injected inline
# Verify in Gemini CLI output that MGW context appears at the start of the prompt7. Human-Required: OpenCode End-to-End
node bin/mgw.cjs --provider opencode mgw:help
# Expected: opencode run --system-prompt <mgw-context> is invoked
# Verify in process args / opencode logs that --system-prompt flag was passed8. Human-Required: Full mgw:run Pipeline on Gemini
# Set provider and run against a test issue
node bin/mgw.cjs --provider gemini mgw:run --issue <TEST_ISSUE_NUMBER>
# Expected: full triage → planning → execution → PR flow completes
# Check .mgw/active/<issue>.json transitions through pipeline stagesPass Criteria
|
- Gemini getCommandsDir() returned ~/.gemini/commands/ but installer copies to ~/.gemini/commands/mgw/ — commands would never be found at runtime. Fixed to return ~/.gemini/commands/mgw/. - OpenCode install target lacked mgw/ namespace — commands were mixed into ~/.opencode/commands/ and provider-switch cleanup (rmSync) would destroy ALL opencode commands. Added mgw/ subdirectory to both the install target and getCommandsDir(). - mgw-install.cjs parent dir check used path.dirname(targetDir) which resolved to ~/.claude/commands/ for claude (too strict vs original ~/.claude/ check). Now checks ~/.<provider>/ directly, matching the skip message. - Fixed var → const/let inconsistency in provider-opencode.cjs invoke(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Fixes —
|
…ini dry-run dumps prompt 1. help command: ProviderManager.getProvider() was called with no argument, always defaulting to claude even when --provider flag was passed. Now reads this.optsWithGlobals().provider and forwards it. 2. Gemini/OpenCode getCommandsDir() error messages referenced a non-existent 'mgw install-commands' subcommand. Fixed to reference the actual installer script: node bin/mgw-install.cjs --provider <id>. 3. Gemini invoke() dry-run check ran AFTER reading the command file and embedding its full contents into the prompt args. Dry-run output would dump the entire system prompt (potentially many KB). Moved dry-run check before file I/O; dry-run now shows the file path reference. 4. Stale comments in bin/mgw.cjs still referenced assertClaudeAvailable() and 'claude -p' after the provider refactor. Updated to generic terms. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Findings — Patched in 1158cd2Thorough review of all 19 changed files. Four bugs found and fixed: 1.
|
1. mgw-install.cjs: Move parent-dir guard BEFORE old-dir cleanup. Previously, switching providers (e.g. --provider gemini) when the new provider's home dir didn't exist would delete old commands (rmSync) then exit 0 at the guard — leaving zero commands installed for any provider. 2. claude.cjs shim: Replace ...provider spread with explicit exports. The spread leaked PROVIDER_ID, assertAvailable, invoke into the shim module (and therefore the barrel). These generic names weren't in the original API surface and could shadow or be shadowed by future barrel modules. Shim now exports only the original three: assertClaudeAvailable, invokeClaude, getCommandsDir. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Pass 3 — Patched in e4ac26bReviewed all 19 changed files line-by-line. Two code bugs and one false claim found. 1. Installer cleanup-before-guard ordering (bug)File: Old-dir cleanup (
Fix: Swapped the two blocks — parent-dir guard runs first, cleanup only runs after we've confirmed the new target is viable. 2. Shim namespace leak into barrel (bug)File: The backward-compat shim used Why this matters:
Fix: Replaced 3. PR description mentions non-existent
|
Merge main into issue/157 branch, resolving conflicts in 4 files: - lib/claude.cjs: keep shim pattern, delegate to provider-claude.cjs - lib/provider-claude.cjs: add error classes, timeouts, SIGINT handler from main - lib/gsd-adapter.cjs: auto-merged (resolveGsdRoot + error types + STAGES) - lib/index.cjs: auto-merged (provider-manager + pipeline/errors/logger + lazy TUI) - bin/mgw.cjs: auto-merged (ProviderManager + startTimer/logger) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
lib/claude.cjsintolib/provider-claude.cjswith a backward-compat shimGeminiProviderandOpenCodeProviderwith CLI-specific invocation and commands directory conventionslib/gsd-adapter.cjswith a 3-tierresolveGsdRoot()lookup (env var > config > default)bin/mgw-install.cjsto auto-detect installed AI CLIs and support--provideroverrideCloses #157
Changes
Phase 32 — Provider Interface Design
lib/provider-claude.cjs: New canonical Claude CLI provider withassertAvailable(),invoke(), andgetCommandsDir()lib/claude.cjs: Reduced to backward-compat shim re-exporting all legacy aliaseslib/provider-manager.cjs: NewProviderManagerregistry withgetProvider(),listProviders(), andresolveDefault()bin/mgw.cjs/lib/index.cjs: Wired throughProviderManagerwith--providerflag supportPhase 33 — Gemini + OpenCode Providers
lib/provider-gemini.cjs: Gemini CLI provider — inline system block injection,~/.gemini/commands/directorylib/provider-opencode.cjs: OpenCode provider —opencode runwith--system-promptflag,~/.opencode/commands/directorylib/provider-manager.cjs: All three providers registered (claude,gemini,opencode)Phase 34 — GSD Adapter Generalization
lib/gsd-adapter.cjs: AddedresolveGsdRoot()with 3-tier path resolution:GSD_TOOLS_PATHenv var →config.gsd_path→ default~/.claude/get-shit-donePhase 35 — Command Installation Multi-CLI Support
bin/mgw-install.cjs: Auto-detect installed CLI (claude → gemini → opencode priority),--providerflag override, old commands directory cleanup on provider switchTest Plan
node bin/mgw.cjs --provider claudeinvokes Claude CLI correctlynode bin/mgw.cjs --provider geminiinvokes Gemini CLI correctlynode bin/mgw.cjs --provider opencodeinvokes OpenCode CLI correctlynode bin/mgw-install.cjsauto-detects installed CLI and installs to correct commands dirnode bin/mgw-install.cjs --provider geminioverrides auto-detectionGSD_TOOLS_PATH=/custom/path node bin/mgw.cjsresolves GSD root from env varresolveGsdRoot()falls back to configgsd_pathwhen env var unsetresolveGsdRoot()falls back to~/.claude/get-shit-donedefaultlib/provider-manager.cjs listProviders()returns all three registered providersHuman Verification Required
The following Phase 33 items require real CLI binaries and cannot be verified in automated tests:
geminiCLI, runnode bin/mgw.cjs --provider geminiand verify a real command executes with the correct inline system block injectionopencodeCLI, runnode bin/mgw.cjs --provider opencodeand verifyopencode run --system-promptis invoked correctly with MGW contextmgw:runpipeline with--provider geminiagainst a test issue to confirm the triage → execute → PR flow works end-to-end with Gemini