diff --git a/bin/install.js b/bin/install.js
index 9a5f39a22..8d5543107 100755
--- a/bin/install.js
+++ b/bin/install.js
@@ -589,6 +589,27 @@ function writeSettings(settingsPath, settings) {
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
}
+/**
+ * Read model_overrides from ~/.gsd/defaults.json at install time.
+ * Returns an object mapping agent names to model IDs, or null if the file
+ * doesn't exist or has no model_overrides entry.
+ * Used by Codex TOML and OpenCode agent file generators to embed per-agent
+ * model assignments so that model_overrides is respected on non-Claude runtimes (#2256).
+ */
+function readGsdGlobalModelOverrides() {
+ try {
+ const defaultsPath = path.join(os.homedir(), '.gsd', 'defaults.json');
+ if (!fs.existsSync(defaultsPath)) return null;
+ const raw = fs.readFileSync(defaultsPath, 'utf-8');
+ const parsed = JSON.parse(raw);
+ const overrides = parsed.model_overrides;
+ if (!overrides || typeof overrides !== 'object') return null;
+ return overrides;
+ } catch {
+ return null;
+ }
+}
+
// Cache for attribution settings (populated once per runtime during install)
const attributionCache = new Map();
@@ -1748,7 +1769,7 @@ purpose: ${toSingleLine(description)}
* Sets required agent metadata, sandbox_mode, and developer_instructions
* from the agent markdown content.
*/
-function generateCodexAgentToml(agentName, agentContent) {
+function generateCodexAgentToml(agentName, agentContent, modelOverrides = null) {
const sandboxMode = CODEX_AGENT_SANDBOX[agentName] || 'read-only';
const { frontmatter, body } = extractFrontmatterAndBody(agentContent);
const frontmatterText = frontmatter || '';
@@ -1762,12 +1783,22 @@ function generateCodexAgentToml(agentName, agentContent) {
`name = ${JSON.stringify(resolvedName)}`,
`description = ${JSON.stringify(resolvedDescription)}`,
`sandbox_mode = "${sandboxMode}"`,
- // Agent prompts contain raw backslashes in regexes and shell snippets.
- // TOML literal multiline strings preserve them without escape parsing.
- `developer_instructions = '''`,
- instructions,
- `'''`,
];
+
+ // Embed model override when configured in ~/.gsd/defaults.json so that
+ // model_overrides is respected on Codex (which uses static TOML, not inline
+ // Task() model parameters). See #2256.
+ const modelOverride = modelOverrides?.[resolvedName] || modelOverrides?.[agentName];
+ if (modelOverride) {
+ lines.push(`model = ${JSON.stringify(modelOverride)}`);
+ }
+
+ // Agent prompts contain raw backslashes in regexes and shell snippets.
+ // TOML literal multiline strings preserve them without escape parsing.
+ lines.push(`developer_instructions = '''`);
+ lines.push(instructions);
+ lines.push(`'''`);
+
return lines.join('\n') + '\n';
}
@@ -2991,7 +3022,10 @@ function installCodexConfig(targetDir, agentsSrc) {
agents.push({ name, description: toSingleLine(description) });
- const tomlContent = generateCodexAgentToml(name, content);
+ // Pass model overrides from ~/.gsd/defaults.json so Codex TOML files
+ // embed the configured model — Codex cannot receive model inline (#2256).
+ const modelOverrides = readGsdGlobalModelOverrides();
+ const tomlContent = generateCodexAgentToml(name, content, modelOverrides);
fs.writeFileSync(path.join(agentsTomlDir, `${name}.toml`), tomlContent);
}
@@ -3145,7 +3179,7 @@ function convertClaudeToGeminiAgent(content) {
return `---\n${newFrontmatter}\n---${stripSubTags(neutralBody)}`;
}
-function convertClaudeToOpencodeFrontmatter(content, { isAgent = false } = {}) {
+function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOverride = null } = {}) {
// Replace tool name references in content (applies to all files)
let convertedContent = content;
convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
@@ -3280,6 +3314,12 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false } = {}) {
// use its default model for subagents. See #1156.
if (isAgent) {
newLines.push('mode: subagent');
+ // Embed model override from ~/.gsd/defaults.json so model_overrides is
+ // respected on OpenCode (which uses static agent frontmatter, not inline
+ // Task() model parameters). See #2256.
+ if (modelOverride) {
+ newLines.push(`model: ${modelOverride}`);
+ }
}
// For commands: add tools object if we had allowed-tools or tools
@@ -5682,7 +5722,11 @@ function install(isGlobal, runtime = 'claude') {
content = processAttribution(content, getCommitAttribution(runtime));
// Convert frontmatter for runtime compatibility (agents need different handling)
if (isOpencode) {
- content = convertClaudeToOpencodeFrontmatter(content, { isAgent: true });
+ // Resolve per-agent model override from ~/.gsd/defaults.json (#2256)
+ const _ocAgentName = entry.name.replace(/\.md$/, '');
+ const _ocModelOverrides = readGsdGlobalModelOverrides();
+ const _ocModelOverride = _ocModelOverrides?.[_ocAgentName] || null;
+ content = convertClaudeToOpencodeFrontmatter(content, { isAgent: true, modelOverride: _ocModelOverride });
} else if (isKilo) {
content = convertClaudeToKiloFrontmatter(content, { isAgent: true });
} else if (isGemini) {
diff --git a/commands/gsd/inbox.md b/commands/gsd/inbox.md
new file mode 100644
index 000000000..a57684fe2
--- /dev/null
+++ b/commands/gsd/inbox.md
@@ -0,0 +1,38 @@
+---
+name: gsd:inbox
+description: Triage and review all open GitHub issues and PRs against project templates and contribution guidelines
+argument-hint: "[--issues] [--prs] [--label] [--close-incomplete] [--repo owner/repo]"
+allowed-tools:
+ - Read
+ - Bash
+ - Write
+ - Grep
+ - Glob
+ - AskUserQuestion
+---
+
+One-command triage of the project's GitHub inbox. Fetches all open issues and PRs,
+reviews each against the corresponding template requirements (feature, enhancement,
+bug, chore, fix PR, enhancement PR, feature PR), reports completeness and compliance,
+and optionally applies labels or closes non-compliant submissions.
+
+**Flow:** Detect repo → Fetch open issues + PRs → Classify each by type → Review against template → Report findings → Optionally act (label, comment, close)
+
+
+
+@~/.claude/get-shit-done/workflows/inbox.md
+
+
+
+**Flags:**
+- `--issues` — Review only issues (skip PRs)
+- `--prs` — Review only PRs (skip issues)
+- `--label` — Auto-apply recommended labels after review
+- `--close-incomplete` — Close issues/PRs that fail template compliance (with comment explaining why)
+- `--repo owner/repo` — Override auto-detected repository (defaults to current git remote)
+
+
+
+Execute the inbox workflow from @~/.claude/get-shit-done/workflows/inbox.md end-to-end.
+Parse flags from arguments and pass to workflow.
+
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 6f04657dd..5d7c03d48 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -113,7 +113,7 @@ User-facing entry points. Each file contains YAML frontmatter (name, description
- **Copilot:** Slash commands (`/gsd-command-name`)
- **Antigravity:** Skills
-**Total commands:** 73
+**Total commands:** 74
### Workflows (`get-shit-done/workflows/*.md`)
@@ -409,7 +409,7 @@ UI-SPEC.md (per phase) ───────────────────
```
~/.claude/ # Claude Code (global install)
-├── commands/gsd/*.md # 73 slash commands
+├── commands/gsd/*.md # 74 slash commands
├── get-shit-done/
│ ├── bin/gsd-tools.cjs # CLI utility
│ ├── bin/lib/*.cjs # 19 domain modules
diff --git a/tests/codex-config.test.cjs b/tests/codex-config.test.cjs
index f02db36f5..60f863dea 100644
--- a/tests/codex-config.test.cjs
+++ b/tests/codex-config.test.cjs
@@ -320,6 +320,35 @@ tools: Read, Grep, Glob
const result = generateCodexAgentToml('gsd-unknown', sampleAgent);
assert.ok(result.includes('sandbox_mode = "read-only"'), 'defaults to read-only');
});
+
+ // ─── #2256: model_overrides support ───────────────────────────────────────
+
+ test('emits model field when modelOverrides contains an entry for the agent (#2256)', () => {
+ const overrides = { 'gsd-executor': 'gpt-5.3-codex' };
+ const result = generateCodexAgentToml('gsd-executor', sampleAgent, overrides);
+ assert.ok(result.includes('model = "gpt-5.3-codex"'), 'model field must be present in TOML');
+ });
+
+ test('does not emit model field when modelOverrides is null (#2256)', () => {
+ const result = generateCodexAgentToml('gsd-executor', sampleAgent, null);
+ assert.ok(!result.includes('model ='), 'model field must be absent when no override');
+ });
+
+ test('does not emit model field when modelOverrides has no entry for this agent (#2256)', () => {
+ const overrides = { 'gsd-planner': 'gpt-5.4' };
+ const result = generateCodexAgentToml('gsd-executor', sampleAgent, overrides);
+ assert.ok(!result.includes('model ='), 'model field must be absent for agents not in overrides');
+ });
+
+ test('model field appears before developer_instructions (#2256)', () => {
+ const overrides = { 'gsd-executor': 'gpt-5.3-codex' };
+ const result = generateCodexAgentToml('gsd-executor', sampleAgent, overrides);
+ const modelIdx = result.indexOf('model = "gpt-5.3-codex"');
+ const instrIdx = result.indexOf("developer_instructions = '''");
+ assert.ok(modelIdx !== -1, 'model field present');
+ assert.ok(instrIdx !== -1, 'developer_instructions present');
+ assert.ok(modelIdx < instrIdx, 'model field must appear before developer_instructions');
+ });
});
// ─── CODEX_AGENT_SANDBOX mapping ────────────────────────────────────────────────
diff --git a/tests/runtime-converters.test.cjs b/tests/runtime-converters.test.cjs
index a8803c598..dcffa9924 100644
--- a/tests/runtime-converters.test.cjs
+++ b/tests/runtime-converters.test.cjs
@@ -184,6 +184,48 @@ Fallback skills live in .agents/skills/.`;
assert.ok(frontmatter.includes('description:'), 'description should be kept');
});
});
+
+ // ─── #2256: model_overrides support for OpenCode/Kilo agents ────────────────
+ // Only test OpenCode — Kilo uses the same converter but model override injection
+ // is wired only for OpenCode at the call site in install().
+ if (label === 'OpenCode') {
+ describe('OpenCode agent model override (modelOverride option) (#2256)', () => {
+ test('adds model: field when modelOverride is provided', () => {
+ const result = convert(SAMPLE_AGENT, { isAgent: true, modelOverride: 'gpt-5.3-codex' });
+ const frontmatter = result.split('---')[1];
+ assert.ok(frontmatter.includes('model: gpt-5.3-codex'), 'model: field must be added with override value');
+ });
+
+ test('does not add model: field when modelOverride is null', () => {
+ const result = convert(SAMPLE_AGENT, { isAgent: true, modelOverride: null });
+ const frontmatter = result.split('---')[1];
+ assert.ok(!frontmatter.includes('model:'), 'model: field must be absent when no override');
+ });
+
+ test('does not add model: field when modelOverride is omitted', () => {
+ const result = convert(SAMPLE_AGENT, { isAgent: true });
+ const frontmatter = result.split('---')[1];
+ assert.ok(!frontmatter.includes('model:'), 'model: field must be absent when option omitted');
+ });
+
+ test('model: field appears after mode: subagent', () => {
+ const result = convert(SAMPLE_AGENT, { isAgent: true, modelOverride: 'o4-mini' });
+ const frontmatter = result.split('---')[1];
+ const modeIdx = frontmatter.indexOf('mode: subagent');
+ const modelIdx = frontmatter.indexOf('model: o4-mini');
+ assert.ok(modeIdx !== -1, 'mode: subagent must be present');
+ assert.ok(modelIdx !== -1, 'model: field must be present');
+ assert.ok(modelIdx > modeIdx, 'model: must appear after mode: subagent');
+ });
+
+ test('model override does not affect command conversion', () => {
+ // modelOverride has no effect when isAgent is false (commands)
+ const result = convert(SAMPLE_COMMAND, { modelOverride: 'gpt-5.4' });
+ const frontmatter = result.split('---')[1];
+ assert.ok(!frontmatter.includes('model:'), 'model: must not appear in command output');
+ });
+ });
+ }
}
// ─────────────────────────────────────────────────────────────────────────────