Skip to content

Commit 0c3fe79

Browse files
committed
Hide bundled skills from human skills list
The default skills list is meant for skills users can inspect and manage in the current environment. Bundled skills remain available internally and in JSON metadata, but the human table now omits bundled rows and removes the Source column. Constraint: --json remains machine-readable with full source metadata. Confidence: high Scope-risk: narrow Tested: bun test src/cli/handlers/skills.test.ts src/skills/loadSkillsDir.test.ts src/commands.test.ts Tested: bun run build Tested: node dist/cli.mjs skills list Tested: node dist/cli.mjs skills list --json Tested: git diff --check Tested: bun run smoke
1 parent 361a4ac commit 0c3fe79

2 files changed

Lines changed: 70 additions & 21 deletions

File tree

src/cli/handlers/skills.test.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,29 +102,73 @@ test('formats skills list as an aligned human table', () => {
102102
skill(
103103
'batch',
104104
'Research and plan a large-scale change, then execute it in parallel across 5–30 isolated worktree agents that each open a PR.',
105+
'projectSettings',
106+
),
107+
skill(
108+
'debug',
109+
'Enable debug logging for this session and help diagnose issues.',
110+
'userSettings',
105111
),
106-
skill('debug', 'Enable debug logging for this session and help diagnose issues.'),
107112
skill(
108113
'loop',
109114
'Run a prompt on a fixed interval or dynamically reschedule it, including bare maintenance-mode loops.',
115+
'projectSettings',
110116
),
111117
skill(
112118
'simplify',
113119
'Review changed code for reuse, quality, and efficiency, then fix any issues found.',
120+
'projectSettings',
114121
),
115122
skill(
116123
'update-config',
117124
'Use this skill to configure the Claude Code harness via settings.json. Automated behaviors require hooks.',
125+
'projectSettings',
118126
),
119127
],
120128
80,
121129
)
122130

123131
assert.match(output, /^Skills: 5 enabled/)
124-
assert.match(output, /Name\s+Status\s+Source\s+Description/)
132+
assert.match(output, /Name\s+Status\s+Description/)
133+
assert.doesNotMatch(output, /\bSource\b/)
125134
assert.doesNotMatch(output, /source: bundled \| trust:/)
126-
assert.match(output, /batch\s+enabled\s+bundled\s+Research and plan/)
127-
assert.match(output, /update-config\s+enabled\s+bundled\s+Configure the Claude Code harness via/)
135+
assert.doesNotMatch(output, /\bbundled\b/)
136+
assert.match(output, /batch\s+enabled\s+Research and plan/)
137+
assert.match(output, /update-config\s+enabled\s+Configure the Claude Code harness via/)
138+
})
139+
140+
test('omits source column while preserving installed rows', () => {
141+
const output = formatSkillsListForDisplay(
142+
[
143+
skill('docs-writer', 'Writes project documentation.', 'projectSettings'),
144+
skill('pr-review', 'Reviews pull requests.', 'userSettings'),
145+
skill('debug', 'Enable debug logging.', 'bundled'),
146+
],
147+
100,
148+
)
149+
150+
assert.doesNotMatch(output, /\bSource\b/)
151+
assert.doesNotMatch(output, /docs-writer\s+enabled\s+project\s+/)
152+
assert.doesNotMatch(output, /pr-review\s+enabled\s+user\s+/)
153+
assert.match(output, /docs-writer\s+enabled\s+Writes project documentation\./)
154+
assert.match(output, /pr-review\s+enabled\s+Reviews pull requests\./)
155+
assert.doesNotMatch(output, /\bdebug\b/)
156+
assert.doesNotMatch(output, /Enable debug logging/)
157+
})
158+
159+
test('omits bundled skills from the human table', () => {
160+
const output = formatSkillsListForDisplay(
161+
[
162+
skill('debug', 'Enable debug logging.', 'bundled'),
163+
skill('docs-writer', 'Writes project documentation.', 'projectSettings'),
164+
],
165+
100,
166+
)
167+
168+
assert.match(output, /^Skills: 1 enabled/)
169+
assert.doesNotMatch(output, /\bdebug\b/)
170+
assert.doesNotMatch(output, /Enable debug logging/)
171+
assert.match(output, /docs-writer\s+enabled\s+Writes project documentation\./)
128172
})
129173

130174
test('wraps description continuations under the Description column', () => {
@@ -133,16 +177,17 @@ test('wraps description continuations under the Description column', () => {
133177
skill(
134178
'batch',
135179
'Research and plan a large-scale change, then execute it in parallel across 5–30 isolated worktree agents that each open a PR.',
180+
'projectSettings',
136181
),
137182
],
138-
70,
183+
45,
139184
)
140185
const lines = output.split('\n')
141186
const header = lines.find(line => line.includes('Description'))
142187
assert.ok(header)
143188
const descriptionColumn = header.indexOf('Description')
144189
const continuation = lines.find(line =>
145-
line.trim().startsWith('then execute'),
190+
line.trim().startsWith('large-scale change'),
146191
)
147192
assert.ok(continuation)
148193
assert.equal(continuation.search(/\S/), descriptionColumn)
@@ -155,6 +200,16 @@ test('formats empty skills list cleanly', () => {
155200
)
156201
})
157202

203+
test('formats all-bundled skills as empty in the human table', () => {
204+
assert.equal(
205+
formatSkillsListForDisplay(
206+
[skill('debug', 'Enable debug logging.', 'bundled')],
207+
100,
208+
),
209+
'Skills: 0 enabled\n\nNo skills found.',
210+
)
211+
})
212+
158213
test('formats skills list json as machine-readable metadata', () => {
159214
const description = 'Full description should remain in JSON. Extra sentence stays.'
160215
const parsed = JSON.parse(

src/cli/handlers/skillsListFormat.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,21 +100,18 @@ function formatSkillListRow({
100100
state,
101101
nameWidth,
102102
statusWidth,
103-
sourceWidth,
104103
descriptionWidth,
105104
descriptionIndent,
106105
}: {
107106
skill: SkillListCommand
108107
state: string | undefined
109108
nameWidth: number
110109
statusWidth: number
111-
sourceWidth: number
112110
descriptionWidth: number
113111
descriptionIndent: string
114112
}): string {
115113
const status = state ?? 'enabled'
116-
const source = sourceLabel(skill)
117-
const prefix = `${getCommandName(skill).padEnd(nameWidth)} ${status.padEnd(statusWidth)} ${source.padEnd(sourceWidth)} `
114+
const prefix = `${getCommandName(skill).padEnd(nameWidth)} ${status.padEnd(statusWidth)} `
118115
const descriptionLines = wrapSkillDescription(
119116
descriptionSummary(skill.description),
120117
descriptionWidth,
@@ -166,11 +163,14 @@ export function formatSkillsListForDisplay(
166163
): string {
167164
const sortedSkills = skills
168165
.slice()
166+
.filter(skill => sourceLabel(skill) !== 'bundled')
169167
.sort((a, b) => getCommandName(a).localeCompare(getCommandName(b)))
170168
const states = getResolutionState(skills)
171-
const enabledCount = [...states.values()].filter(s => s === 'enabled').length
169+
const enabledCount = sortedSkills.filter(
170+
skill => states.get(skill) === 'enabled',
171+
).length
172172

173-
if (skills.length === 0) {
173+
if (sortedSkills.length === 0) {
174174
return ['Skills: 0 enabled', '', 'No skills found.'].join('\n')
175175
}
176176

@@ -182,23 +182,17 @@ export function formatSkillsListForDisplay(
182182
'Status'.length,
183183
...sortedSkills.map(skill => (states.get(skill) ?? 'enabled').length),
184184
)
185-
const sourceWidth = Math.max(
186-
'Source'.length,
187-
...sortedSkills.map(skill => sourceLabel(skill).length),
188-
)
189-
const descriptionStart =
190-
nameWidth + 2 + statusWidth + 3 + sourceWidth + 3
185+
const descriptionStart = nameWidth + 2 + statusWidth + 3
191186
const descriptionWidth = Math.max(20, columns - descriptionStart)
192187
const descriptionIndent = ' '.repeat(descriptionStart)
193-
const header = `${'Name'.padEnd(nameWidth)} ${'Status'.padEnd(statusWidth)} ${'Source'.padEnd(sourceWidth)} Description`
194-
const rule = `${separator(nameWidth)} ${separator(statusWidth)} ${separator(sourceWidth)} ${separator(descriptionWidth)}`
188+
const header = `${'Name'.padEnd(nameWidth)} ${'Status'.padEnd(statusWidth)} Description`
189+
const rule = `${separator(nameWidth)} ${separator(statusWidth)} ${separator(descriptionWidth)}`
195190
const rows = sortedSkills.map(skill =>
196191
formatSkillListRow({
197192
skill,
198193
state: states.get(skill),
199194
nameWidth,
200195
statusWidth,
201-
sourceWidth,
202196
descriptionWidth,
203197
descriptionIndent,
204198
}),

0 commit comments

Comments
 (0)