diff --git a/src/lib/config-handler.ts b/src/lib/config-handler.ts index 2e5947e6..d2330c58 100644 --- a/src/lib/config-handler.ts +++ b/src/lib/config-handler.ts @@ -16,6 +16,29 @@ export interface ConfigHandlerDeps { type CommandConfig = NonNullable[string] +export function toTitleCase(name: string): string { + return name + .split('-') + .map((segment) => + segment.length > 0 + ? segment.charAt(0).toUpperCase() + segment.slice(1) + : segment, + ) + .join('-') +} + +export function formatAgentDescription( + name: string, + description: string | undefined, +): string { + const baseDescription = description || `${name} agent` + const suffix = `(${toTitleCase(name)} - Systematic)` + if (baseDescription.endsWith(suffix)) { + return baseDescription + } + return `${baseDescription} ${suffix}` +} + function loadAgentAsConfig(agentInfo: { name: string file: string @@ -41,7 +64,7 @@ function loadAgentAsConfig(agentInfo: { } = extractAgentFrontmatter(converted) const config: AgentConfig = { - description: description || `${agentInfo.name} agent`, + description: formatAgentDescription(agentInfo.name, description), prompt, } @@ -80,7 +103,7 @@ function loadCommandAsConfig(commandInfo: { const config: CommandConfig = { template: body.trim(), - description: `(systematic) ${baseDescription}`, + description: `(Systematic) ${baseDescription}`, } if (agent !== undefined) config.agent = agent diff --git a/src/lib/skill-loader.ts b/src/lib/skill-loader.ts index 97d79d12..f616ae5d 100644 --- a/src/lib/skill-loader.ts +++ b/src/lib/skill-loader.ts @@ -4,7 +4,7 @@ import { parseFrontmatter } from './frontmatter.js' import type { SkillInfo } from './skills.js' const SKILL_PREFIX = 'systematic:' -const SKILL_DESCRIPTION_PREFIX = '(systematic - Skill) ' +const SKILL_DESCRIPTION_PREFIX = '(Systematic - Skill) ' export interface LoadedSkill { name: string diff --git a/tests/integration/opencode.test.ts b/tests/integration/opencode.test.ts index b19ec516..21fe344b 100644 --- a/tests/integration/opencode.test.ts +++ b/tests/integration/opencode.test.ts @@ -185,7 +185,7 @@ Integration test content.`, expect(commandNames).not.toContain('test-skill') }) - test('adds (systematic - Skill) prefix to skill descriptions', async () => { + test('adds (Systematic - Skill) prefix to skill descriptions', async () => { const handler = createConfigHandler({ directory: testEnv.projectDir, bundledSkillsDir: path.join(testEnv.bundledDir, 'skills'), @@ -197,9 +197,9 @@ Integration test content.`, await handler(config) const skillCommand = config.command?.['systematic:test-skill'] - expect(skillCommand?.description).toMatch(/^\(systematic - Skill\) /) + expect(skillCommand?.description).toMatch(/^\(Systematic - Skill\) /) expect(skillCommand?.description).toBe( - '(systematic - Skill) A skill for integration testing', + '(Systematic - Skill) A skill for integration testing', ) }) diff --git a/tests/unit/config-handler.test.ts b/tests/unit/config-handler.test.ts index 72461238..1ebbb69e 100644 --- a/tests/unit/config-handler.test.ts +++ b/tests/unit/config-handler.test.ts @@ -3,7 +3,11 @@ import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import type { Config } from '@opencode-ai/sdk' -import { createConfigHandler } from '../../src/lib/config-handler.ts' +import { + createConfigHandler, + formatAgentDescription, + toTitleCase, +} from '../../src/lib/config-handler.ts' import { formatFrontmatter } from '../../src/lib/frontmatter.ts' describe('config-handler', () => { @@ -78,6 +82,67 @@ Command template for ${name}.`, ) } + describe('toTitleCase', () => { + test('converts kebab-case to Title-Case', () => { + expect(toTitleCase('architecture-strategist')).toBe( + 'Architecture-Strategist', + ) + }) + + test('handles single word', () => { + expect(toTitleCase('oracle')).toBe('Oracle') + }) + + test('handles empty string', () => { + expect(toTitleCase('')).toBe('') + }) + + test('handles single-character segments', () => { + expect(toTitleCase('a-b-c')).toBe('A-B-C') + }) + + test('handles numbers in segments', () => { + expect(toTitleCase('v2-api-agent')).toBe('V2-Api-Agent') + }) + + test('preserves already-capitalized characters after first', () => { + expect(toTitleCase('REST-API')).toBe('REST-API') + expect(toTitleCase('AI-reviewer')).toBe('AI-Reviewer') + }) + }) + + describe('formatAgentDescription', () => { + test('appends branding suffix with title-cased name', () => { + expect( + formatAgentDescription( + 'code-simplicity-reviewer', + 'Reviews code for simplicity', + ), + ).toBe( + 'Reviews code for simplicity (Code-Simplicity-Reviewer - Systematic)', + ) + }) + + test('uses fallback when description is undefined', () => { + expect(formatAgentDescription('test-agent', undefined)).toBe( + 'test-agent agent (Test-Agent - Systematic)', + ) + }) + + test('uses fallback when description is empty', () => { + expect(formatAgentDescription('test-agent', '')).toBe( + 'test-agent agent (Test-Agent - Systematic)', + ) + }) + + test('does not double-brand if suffix already present', () => { + const alreadyBranded = 'Some description (Test-Agent - Systematic)' + expect(formatAgentDescription('test-agent', alreadyBranded)).toBe( + alreadyBranded, + ) + }) + }) + describe('createConfigHandler', () => { test('returns a function', () => { const handler = createConfigHandler({ @@ -104,7 +169,9 @@ Command template for ${name}.`, expect(config.agent).toBeDefined() expect(config.agent?.['test-agent']).toBeDefined() - expect(config.agent?.['test-agent']?.description).toBe('A test agent') + expect(config.agent?.['test-agent']?.description).toBe( + 'A test agent (Test-Agent - Systematic)', + ) }) test('collects bundled commands into config', async () => { @@ -127,7 +194,7 @@ Command template for ${name}.`, expect(config.command).toBeDefined() expect(config.command?.['systematic:test-command']).toBeDefined() expect(config.command?.['systematic:test-command']?.description).toBe( - '(systematic) A test command', + '(Systematic) A test command', ) expect(config.command?.['systematic:test-command']?.template).toContain( 'Command template for test-command', @@ -150,7 +217,7 @@ Command template for ${name}.`, expect(config.command).toBeDefined() expect(config.command?.['systematic:test-skill']).toBeDefined() expect(config.command?.['systematic:test-skill']?.description).toBe( - '(systematic - Skill) A test skill', + '(Systematic - Skill) A test skill', ) expect(config.command?.['systematic:test-skill']?.template).toContain( '', @@ -352,7 +419,7 @@ Command template for ${name}.`, const agent = config.agent?.['full-agent'] expect(agent).toBeDefined() - expect(agent?.description).toBe('A full agent') + expect(agent?.description).toBe('A full agent (Full-Agent - Systematic)') expect(agent?.model).toBe('openai/gpt-4') expect(agent?.temperature).toBe(0.7) expect(agent?.top_p).toBe(1) @@ -460,7 +527,7 @@ Full command template.`, const command = config.command?.['systematic:full-command'] expect(command).toBeDefined() - expect(command?.description).toBe('(systematic) A full command') + expect(command?.description).toBe('(Systematic) A full command') expect(command?.agent).toBe('oracle') expect(command?.model).toBe('openai/gpt-4') expect(command?.subtask).toBe(true) diff --git a/tests/unit/skill-loader.test.ts b/tests/unit/skill-loader.test.ts index c89120db..46e78f23 100644 --- a/tests/unit/skill-loader.test.ts +++ b/tests/unit/skill-loader.test.ts @@ -31,21 +31,21 @@ describe('skill-loader', () => { }) describe('formatSkillDescription', () => { - test('adds (systematic - Skill) prefix to description', () => { + test('adds (Systematic - Skill) prefix to description', () => { expect(formatSkillDescription('A test skill', 'test')).toBe( - '(systematic - Skill) A test skill', + '(Systematic - Skill) A test skill', ) }) test('does not double-prefix already prefixed description', () => { expect( - formatSkillDescription('(systematic - Skill) A test skill', 'test'), - ).toBe('(systematic - Skill) A test skill') + formatSkillDescription('(Systematic - Skill) A test skill', 'test'), + ).toBe('(Systematic - Skill) A test skill') }) test('uses fallback name when description is empty', () => { expect(formatSkillDescription('', 'my-skill')).toBe( - '(systematic - Skill) my-skill skill', + '(Systematic - Skill) my-skill skill', ) }) }) @@ -162,7 +162,7 @@ description: A test skill expect(loaded.name).toBe('test-skill') expect(loaded.prefixedName).toBe('systematic:test-skill') - expect(loaded.description).toBe('(systematic - Skill) A test skill') + expect(loaded.description).toBe('(Systematic - Skill) A test skill') expect(loaded.wrappedTemplate).toContain('') expect(loaded.wrappedTemplate).toContain('# Test Content') })