Skip to content

Commit 13ffb65

Browse files
authored
feat(config): standardize Systematic branding in descriptions (#54)
Add toTitleCase() and formatAgentDescription() helpers to consistently format component descriptions with title-cased names and Systematic branding. - Agent descriptions: "{desc} ({Title-Name} - Systematic)" - Command prefix: "(Systematic)" instead of "(systematic)" - Skill prefix: "(Systematic - Skill)" instead of "(systematic - Skill)"
1 parent 13f50c3 commit 13ffb65

5 files changed

Lines changed: 108 additions & 18 deletions

File tree

src/lib/config-handler.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,29 @@ export interface ConfigHandlerDeps {
1616

1717
type CommandConfig = NonNullable<Config['command']>[string]
1818

19+
export function toTitleCase(name: string): string {
20+
return name
21+
.split('-')
22+
.map((segment) =>
23+
segment.length > 0
24+
? segment.charAt(0).toUpperCase() + segment.slice(1)
25+
: segment,
26+
)
27+
.join('-')
28+
}
29+
30+
export function formatAgentDescription(
31+
name: string,
32+
description: string | undefined,
33+
): string {
34+
const baseDescription = description || `${name} agent`
35+
const suffix = `(${toTitleCase(name)} - Systematic)`
36+
if (baseDescription.endsWith(suffix)) {
37+
return baseDescription
38+
}
39+
return `${baseDescription} ${suffix}`
40+
}
41+
1942
function loadAgentAsConfig(agentInfo: {
2043
name: string
2144
file: string
@@ -41,7 +64,7 @@ function loadAgentAsConfig(agentInfo: {
4164
} = extractAgentFrontmatter(converted)
4265

4366
const config: AgentConfig = {
44-
description: description || `${agentInfo.name} agent`,
67+
description: formatAgentDescription(agentInfo.name, description),
4568
prompt,
4669
}
4770

@@ -80,7 +103,7 @@ function loadCommandAsConfig(commandInfo: {
80103

81104
const config: CommandConfig = {
82105
template: body.trim(),
83-
description: `(systematic) ${baseDescription}`,
106+
description: `(Systematic) ${baseDescription}`,
84107
}
85108

86109
if (agent !== undefined) config.agent = agent

src/lib/skill-loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { parseFrontmatter } from './frontmatter.js'
44
import type { SkillInfo } from './skills.js'
55

66
const SKILL_PREFIX = 'systematic:'
7-
const SKILL_DESCRIPTION_PREFIX = '(systematic - Skill) '
7+
const SKILL_DESCRIPTION_PREFIX = '(Systematic - Skill) '
88

99
export interface LoadedSkill {
1010
name: string

tests/integration/opencode.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ Integration test content.`,
185185
expect(commandNames).not.toContain('test-skill')
186186
})
187187

188-
test('adds (systematic - Skill) prefix to skill descriptions', async () => {
188+
test('adds (Systematic - Skill) prefix to skill descriptions', async () => {
189189
const handler = createConfigHandler({
190190
directory: testEnv.projectDir,
191191
bundledSkillsDir: path.join(testEnv.bundledDir, 'skills'),
@@ -197,9 +197,9 @@ Integration test content.`,
197197
await handler(config)
198198

199199
const skillCommand = config.command?.['systematic:test-skill']
200-
expect(skillCommand?.description).toMatch(/^\(systematic - Skill\) /)
200+
expect(skillCommand?.description).toMatch(/^\(Systematic - Skill\) /)
201201
expect(skillCommand?.description).toBe(
202-
'(systematic - Skill) A skill for integration testing',
202+
'(Systematic - Skill) A skill for integration testing',
203203
)
204204
})
205205

tests/unit/config-handler.test.ts

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import fs from 'node:fs'
33
import os from 'node:os'
44
import path from 'node:path'
55
import type { Config } from '@opencode-ai/sdk'
6-
import { createConfigHandler } from '../../src/lib/config-handler.ts'
6+
import {
7+
createConfigHandler,
8+
formatAgentDescription,
9+
toTitleCase,
10+
} from '../../src/lib/config-handler.ts'
711
import { formatFrontmatter } from '../../src/lib/frontmatter.ts'
812

913
describe('config-handler', () => {
@@ -78,6 +82,67 @@ Command template for ${name}.`,
7882
)
7983
}
8084

85+
describe('toTitleCase', () => {
86+
test('converts kebab-case to Title-Case', () => {
87+
expect(toTitleCase('architecture-strategist')).toBe(
88+
'Architecture-Strategist',
89+
)
90+
})
91+
92+
test('handles single word', () => {
93+
expect(toTitleCase('oracle')).toBe('Oracle')
94+
})
95+
96+
test('handles empty string', () => {
97+
expect(toTitleCase('')).toBe('')
98+
})
99+
100+
test('handles single-character segments', () => {
101+
expect(toTitleCase('a-b-c')).toBe('A-B-C')
102+
})
103+
104+
test('handles numbers in segments', () => {
105+
expect(toTitleCase('v2-api-agent')).toBe('V2-Api-Agent')
106+
})
107+
108+
test('preserves already-capitalized characters after first', () => {
109+
expect(toTitleCase('REST-API')).toBe('REST-API')
110+
expect(toTitleCase('AI-reviewer')).toBe('AI-Reviewer')
111+
})
112+
})
113+
114+
describe('formatAgentDescription', () => {
115+
test('appends branding suffix with title-cased name', () => {
116+
expect(
117+
formatAgentDescription(
118+
'code-simplicity-reviewer',
119+
'Reviews code for simplicity',
120+
),
121+
).toBe(
122+
'Reviews code for simplicity (Code-Simplicity-Reviewer - Systematic)',
123+
)
124+
})
125+
126+
test('uses fallback when description is undefined', () => {
127+
expect(formatAgentDescription('test-agent', undefined)).toBe(
128+
'test-agent agent (Test-Agent - Systematic)',
129+
)
130+
})
131+
132+
test('uses fallback when description is empty', () => {
133+
expect(formatAgentDescription('test-agent', '')).toBe(
134+
'test-agent agent (Test-Agent - Systematic)',
135+
)
136+
})
137+
138+
test('does not double-brand if suffix already present', () => {
139+
const alreadyBranded = 'Some description (Test-Agent - Systematic)'
140+
expect(formatAgentDescription('test-agent', alreadyBranded)).toBe(
141+
alreadyBranded,
142+
)
143+
})
144+
})
145+
81146
describe('createConfigHandler', () => {
82147
test('returns a function', () => {
83148
const handler = createConfigHandler({
@@ -104,7 +169,9 @@ Command template for ${name}.`,
104169

105170
expect(config.agent).toBeDefined()
106171
expect(config.agent?.['test-agent']).toBeDefined()
107-
expect(config.agent?.['test-agent']?.description).toBe('A test agent')
172+
expect(config.agent?.['test-agent']?.description).toBe(
173+
'A test agent (Test-Agent - Systematic)',
174+
)
108175
})
109176

110177
test('collects bundled commands into config', async () => {
@@ -127,7 +194,7 @@ Command template for ${name}.`,
127194
expect(config.command).toBeDefined()
128195
expect(config.command?.['systematic:test-command']).toBeDefined()
129196
expect(config.command?.['systematic:test-command']?.description).toBe(
130-
'(systematic) A test command',
197+
'(Systematic) A test command',
131198
)
132199
expect(config.command?.['systematic:test-command']?.template).toContain(
133200
'Command template for test-command',
@@ -150,7 +217,7 @@ Command template for ${name}.`,
150217
expect(config.command).toBeDefined()
151218
expect(config.command?.['systematic:test-skill']).toBeDefined()
152219
expect(config.command?.['systematic:test-skill']?.description).toBe(
153-
'(systematic - Skill) A test skill',
220+
'(Systematic - Skill) A test skill',
154221
)
155222
expect(config.command?.['systematic:test-skill']?.template).toContain(
156223
'<skill-instruction>',
@@ -352,7 +419,7 @@ Command template for ${name}.`,
352419

353420
const agent = config.agent?.['full-agent']
354421
expect(agent).toBeDefined()
355-
expect(agent?.description).toBe('A full agent')
422+
expect(agent?.description).toBe('A full agent (Full-Agent - Systematic)')
356423
expect(agent?.model).toBe('openai/gpt-4')
357424
expect(agent?.temperature).toBe(0.7)
358425
expect(agent?.top_p).toBe(1)
@@ -460,7 +527,7 @@ Full command template.`,
460527

461528
const command = config.command?.['systematic:full-command']
462529
expect(command).toBeDefined()
463-
expect(command?.description).toBe('(systematic) A full command')
530+
expect(command?.description).toBe('(Systematic) A full command')
464531
expect(command?.agent).toBe('oracle')
465532
expect(command?.model).toBe('openai/gpt-4')
466533
expect(command?.subtask).toBe(true)

tests/unit/skill-loader.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,21 @@ describe('skill-loader', () => {
3131
})
3232

3333
describe('formatSkillDescription', () => {
34-
test('adds (systematic - Skill) prefix to description', () => {
34+
test('adds (Systematic - Skill) prefix to description', () => {
3535
expect(formatSkillDescription('A test skill', 'test')).toBe(
36-
'(systematic - Skill) A test skill',
36+
'(Systematic - Skill) A test skill',
3737
)
3838
})
3939

4040
test('does not double-prefix already prefixed description', () => {
4141
expect(
42-
formatSkillDescription('(systematic - Skill) A test skill', 'test'),
43-
).toBe('(systematic - Skill) A test skill')
42+
formatSkillDescription('(Systematic - Skill) A test skill', 'test'),
43+
).toBe('(Systematic - Skill) A test skill')
4444
})
4545

4646
test('uses fallback name when description is empty', () => {
4747
expect(formatSkillDescription('', 'my-skill')).toBe(
48-
'(systematic - Skill) my-skill skill',
48+
'(Systematic - Skill) my-skill skill',
4949
)
5050
})
5151
})
@@ -162,7 +162,7 @@ description: A test skill
162162

163163
expect(loaded.name).toBe('test-skill')
164164
expect(loaded.prefixedName).toBe('systematic:test-skill')
165-
expect(loaded.description).toBe('(systematic - Skill) A test skill')
165+
expect(loaded.description).toBe('(Systematic - Skill) A test skill')
166166
expect(loaded.wrappedTemplate).toContain('<skill-instruction>')
167167
expect(loaded.wrappedTemplate).toContain('# Test Content')
168168
})

0 commit comments

Comments
 (0)