@miphamai/cli 0.69.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,8 @@ import { McpClient } from '../mcp/client'
14
14
  import { buildCapabilityReport } from '../core/capability-inventory'
15
15
  import { InstructionsLoader } from '../core/instructions'
16
16
  import { findDerivableSections, DERIVABLE_HINTS } from '../core/claude-md-audit'
17
+ import { fixDoctor, fixConfig, fixCache, selectRepoClaudeFiles } from '../core/fix'
18
+ import { fixCodeTarget } from '../core/fix-code'
17
19
  import { runCrsiModification, approvePending, rejectPending, hasPending } from '../core/crsi-modify'
18
20
  import {
19
21
  produceCrsiProposal,
@@ -32,7 +34,11 @@ import {
32
34
  import { prefilterProposal } from '../core/proposal-guard'
33
35
  import { runEval, appendEvalScore } from '../core/eval-harness'
34
36
  import { listRewardFns } from '../core/reward-fn'
35
- import { runTaskPerformance, measureSkillDeltaRepeated } from '../core/task-performance'
37
+ import {
38
+ runTaskPerformance,
39
+ measureSkillDeltaRepeated,
40
+ stripCodeFences,
41
+ } from '../core/task-performance'
36
42
  import { randomUUID } from 'node:crypto'
37
43
  import {
38
44
  buildImprovementReport,
@@ -45,7 +51,7 @@ import {
45
51
  } from '../core/improvement-track'
46
52
  import { NPM_UPDATE_COMMAND, PACKAGE_VERSION, COAUTHOR_TRAILER } from '../shared/index.ts'
47
53
  import { getPreference } from '../config/preferences'
48
- import { loadCrossSessionConfig } from '../config/loader'
54
+ import { loadCrossSessionConfig, tryRestoreFromBackup } from '../config/loader'
49
55
  import { getMemoryManager } from '../core/memory/memory-loader'
50
56
  import { stripIndent } from './strip-indent.js'
51
57
  import { createT } from '../i18n-core/t'
@@ -394,11 +400,20 @@ const statusCmd: CommandHandler = async (ctx) => {
394
400
  const costCmd: CommandHandler = (ctx) => {
395
401
  const t = resolveT(ctx)
396
402
  const tokens = ctx.engine.getContext().getEstimatedTokens()
403
+ const cacheStatus = ctx.engine.getContext().getCacheStatus()
404
+ const cachedTokens = cacheStatus.cachedTokens
405
+ const hitRatio = tokens > 0 ? Math.min(1, cachedTokens / tokens) : 0
406
+ const uncachedTokens = Math.max(0, tokens - cachedTokens)
397
407
  return {
398
408
  content: stripIndent`
399
409
  ${t('commands.context_tokens.title')}
400
410
  ${t('commands.context_tokens.context_tokens')} ~${tokens.toLocaleString()} / 200,000
401
411
  ${t('commands.context_tokens.usage')} ${((tokens / 200_000) * 100).toFixed(1)}%
412
+ ${t('commands.context_tokens.prompt_cache', {
413
+ cached: cachedTokens.toLocaleString(),
414
+ ratio: (hitRatio * 100).toFixed(1),
415
+ uncached: uncachedTokens.toLocaleString(),
416
+ })}
402
417
 
403
418
  ${t('commands.context_tokens.footer')}
404
419
  `,
@@ -2359,9 +2374,9 @@ const installSkillCmd: CommandHandler = async (ctx, args) => {
2359
2374
  let result: { success: boolean; name: string; message: string }
2360
2375
 
2361
2376
  if (target.startsWith('http://') || target.startsWith('https://')) {
2362
- result = installSkillFromUrl(target, marketplaceConfig)
2377
+ result = await installSkillFromUrl(target, marketplaceConfig)
2363
2378
  } else {
2364
- result = installSkill(target, marketplaceConfig)
2379
+ result = await installSkill(target, marketplaceConfig)
2365
2380
  }
2366
2381
 
2367
2382
  return {
@@ -2383,6 +2398,83 @@ const removeSkillCmd: CommandHandler = async (_ctx, args) => {
2383
2398
  }
2384
2399
  }
2385
2400
 
2401
+ const marketplaceCmd: CommandHandler = async (_ctx, args) => {
2402
+ const {
2403
+ readMarketplaces,
2404
+ addMarketplace,
2405
+ removeMarketplace,
2406
+ isValidMarketplaceRef,
2407
+ MARKETPLACES_PATH,
2408
+ } = await import('../skills/marketplace')
2409
+ const { readFileSync, writeFileSync, existsSync, mkdirSync } = await import('node:fs')
2410
+ const { dirname } = await import('node:path')
2411
+
2412
+ const readSources = () =>
2413
+ readMarketplaces((p) => (existsSync(p) ? readFileSync(p, 'utf-8') : null))
2414
+
2415
+ const sub = args[0]
2416
+ const ref = args[1]
2417
+
2418
+ if (!sub || sub === 'list') {
2419
+ const sources = readSources()
2420
+ const lines = ['── Marketplace Sources ──', '']
2421
+ for (const s of sources) lines.push(` ${s.owner}/${s.repo}`)
2422
+ lines.push(
2423
+ '',
2424
+ 'Add: /marketplace add <owner>/<repo>',
2425
+ 'Remove: /marketplace remove <owner>/<repo>',
2426
+ )
2427
+ return { content: lines.join('\n') }
2428
+ }
2429
+
2430
+ if (sub === 'add' || sub === 'remove') {
2431
+ if (!ref) return { content: `Usage: /marketplace ${sub} <owner>/<repo>` }
2432
+ const [owner, repo] = ref.split('/')
2433
+ if (!owner || !repo || !isValidMarketplaceRef(owner, repo)) {
2434
+ return { content: `❌ Invalid marketplace ref "${ref}". Expected <owner>/<repo>.` }
2435
+ }
2436
+ const current = readSources()
2437
+ const result =
2438
+ sub === 'add' ? addMarketplace(current, owner, repo) : removeMarketplace(current, owner, repo)
2439
+ const changed = 'added' in result ? result.added : result.removed
2440
+ try {
2441
+ mkdirSync(dirname(MARKETPLACES_PATH), { recursive: true })
2442
+ writeFileSync(MARKETPLACES_PATH, JSON.stringify(result.sources, null, 2), 'utf-8')
2443
+ } catch (err) {
2444
+ return { content: `❌ Failed to save marketplaces: ${String(err)}` }
2445
+ }
2446
+ if (sub === 'add') {
2447
+ return {
2448
+ content: changed ? `✅ Added ${owner}/${repo}` : `ℹ ${owner}/${repo} already exists`,
2449
+ }
2450
+ }
2451
+ return { content: changed ? `✅ Removed ${owner}/${repo}` : `ℹ ${owner}/${repo} not found` }
2452
+ }
2453
+
2454
+ return { content: 'Usage: /marketplace add|list|remove [owner/repo]' }
2455
+ }
2456
+
2457
+ const browseMarketplaceCmd: CommandHandler = async (_ctx) => {
2458
+ const { readMarketplaces, discoverSkills, downloadFile } = await import('../skills/marketplace')
2459
+ const { readFileSync, existsSync } = await import('node:fs')
2460
+
2461
+ const sources = readMarketplaces((p) => (existsSync(p) ? readFileSync(p, 'utf-8') : null))
2462
+ const lines = ['── Marketplace Skills ──', '']
2463
+ for (const source of sources) {
2464
+ lines.push(` ${source.owner}/${source.repo}:`)
2465
+ try {
2466
+ const skills = await discoverSkills(source, globalThis.fetch, downloadFile)
2467
+ if (skills.length === 0) lines.push(' (no skills found)')
2468
+ for (const s of skills) lines.push(` ${s.name.padEnd(24)} ${s.description}`)
2469
+ } catch {
2470
+ lines.push(' (failed to discover — check network / rate limit)')
2471
+ }
2472
+ lines.push('')
2473
+ }
2474
+ lines.push('Install: /install-skill <name>')
2475
+ return { content: lines.join('\n') }
2476
+ }
2477
+
2386
2478
  // ═══════════════════════════════════════════════════════════════
2387
2479
  // Plugins — Community plugin marketplace + local management
2388
2480
  // ═══════════════════════════════════════════════════════════════
@@ -3105,6 +3197,191 @@ const doctorCmd: CommandHandler = async (ctx) => {
3105
3197
  return { content: lines.join('\n') }
3106
3198
  }
3107
3199
 
3200
+ const fixCmd: CommandHandler = async (ctx, args) => {
3201
+ const t = resolveT(ctx)
3202
+ const { readFileSync, writeFileSync } = await import('node:fs')
3203
+ const { join } = await import('node:path')
3204
+ const { homedir } = await import('node:os')
3205
+ const { parse: parseYaml } = await import('yaml')
3206
+
3207
+ const target = args.find((a) => a === 'doctor' || a === 'config' || a === 'cache' || a === 'test')
3208
+ const dryRun = args.includes('--dry-run')
3209
+ const apply = args.includes('--apply')
3210
+
3211
+ const lines: string[] = [t('commands.fix.title')]
3212
+ if (dryRun) lines.push(t('commands.fix.dryrun_banner'))
3213
+ lines.push('')
3214
+
3215
+ const readSafe = (p: string): string | null => {
3216
+ try {
3217
+ return readFileSync(p, 'utf-8')
3218
+ } catch {
3219
+ return null
3220
+ }
3221
+ }
3222
+
3223
+ if (!target || target === 'doctor') {
3224
+ const loader = new InstructionsLoader()
3225
+ loader.loadAll(process.cwd())
3226
+ const files = selectRepoClaudeFiles(loader.list())
3227
+ const result = fixDoctor(files, {
3228
+ read: readSafe,
3229
+ write: (p, c) => {
3230
+ if (!dryRun) writeFileSync(p, c, 'utf-8')
3231
+ },
3232
+ })
3233
+ if (result.fixed.length === 0) {
3234
+ lines.push(t('commands.fix.doctor_clean'))
3235
+ } else {
3236
+ for (const f of result.fixed) {
3237
+ lines.push(t('commands.fix.doctor_fixed', { path: f.path, added: f.added.join(', ') }))
3238
+ }
3239
+ }
3240
+ lines.push('')
3241
+ }
3242
+
3243
+ if (!target || target === 'config') {
3244
+ const home = homedir()
3245
+ const configPaths = [
3246
+ join(process.cwd(), '.mipham', 'config.yml'),
3247
+ join(home, '.mipham', 'config.yml'),
3248
+ ]
3249
+ const hookEngine = ctx.engine.getHookEngine?.()
3250
+ const result = fixConfig({
3251
+ configPaths,
3252
+ read: readSafe,
3253
+ parseYaml,
3254
+ restore: (p) => tryRestoreFromBackup(p),
3255
+ hookHealth: () =>
3256
+ hookEngine
3257
+ ? hookEngine.getHookHealth().map((h) => ({ key: h.key, disabled: h.health.disabled }))
3258
+ : [],
3259
+ reEnableHook: (k) => (hookEngine ? hookEngine.reEnableHook(k) : false),
3260
+ dryRun,
3261
+ })
3262
+ if (result.corruptConfigs.length === 0 && result.disabledHooks.length === 0) {
3263
+ lines.push(t('commands.fix.config_clean'))
3264
+ } else {
3265
+ for (const p of result.restoredConfigs) {
3266
+ lines.push(t('commands.fix.config_restored', { path: p }))
3267
+ }
3268
+ for (const p of result.corruptConfigs.filter((p) => !result.restoredConfigs.includes(p))) {
3269
+ lines.push(t('commands.fix.config_corrupt', { path: p }))
3270
+ }
3271
+ for (const k of result.reenabledHooks) {
3272
+ lines.push(t('commands.fix.hook_reenabled', { key: k }))
3273
+ }
3274
+ for (const k of result.disabledHooks.filter((k) => !result.reenabledHooks.includes(k))) {
3275
+ lines.push(t('commands.fix.hook_disabled', { key: k }))
3276
+ }
3277
+ }
3278
+ lines.push('')
3279
+ }
3280
+
3281
+ if (!target || target === 'cache') {
3282
+ const crsiDir = join(homedir(), '.mipham', 'crsi')
3283
+ const cacheFiles = ['eval-scores.jsonl', 'improvements.jsonl', 'prose-proposals.jsonl'].map(
3284
+ (f) => join(crsiDir, f),
3285
+ )
3286
+ const result = fixCache(
3287
+ cacheFiles,
3288
+ {
3289
+ read: readSafe,
3290
+ write: (p, c) => writeFileSync(p, c, 'utf-8'),
3291
+ },
3292
+ apply && !dryRun,
3293
+ )
3294
+ if (result.files.length === 0) {
3295
+ lines.push(t('commands.fix.cache_clean'))
3296
+ } else {
3297
+ for (const f of result.files) {
3298
+ lines.push(
3299
+ t('commands.fix.cache_found', { path: f.path, count: String(f.corruptLines.length) }),
3300
+ )
3301
+ }
3302
+ if (apply && !dryRun) {
3303
+ for (const c of result.cleaned) {
3304
+ lines.push(t('commands.fix.cache_cleaned', { path: c.path, count: String(c.removed) }))
3305
+ }
3306
+ } else {
3307
+ lines.push(t('commands.fix.cache_hint'))
3308
+ }
3309
+ }
3310
+ lines.push('')
3311
+ }
3312
+
3313
+ if (!target || target === 'test') {
3314
+ const testFile = args.find(
3315
+ (a) =>
3316
+ !a.startsWith('--') && a !== 'test' && a !== 'doctor' && a !== 'config' && a !== 'cache',
3317
+ )
3318
+ if (!testFile) {
3319
+ lines.push(t('commands.fix.test_usage'))
3320
+ } else {
3321
+ const llm = ctx.engine.getLlm() ?? ctx.engine.getRegistry()
3322
+ if (!llm) {
3323
+ lines.push(t('commands.fix.test_no_llm'))
3324
+ } else {
3325
+ const { execSync } = await import('node:child_process')
3326
+ const { dirname, resolve } = await import('node:path')
3327
+ const result = await fixCodeTarget(
3328
+ {
3329
+ runVitest: (file) => {
3330
+ try {
3331
+ const output = execSync(`pnpm vitest run '${file}'`, {
3332
+ cwd: process.cwd(),
3333
+ encoding: 'utf-8',
3334
+ stdio: ['ignore', 'pipe', 'pipe'],
3335
+ })
3336
+ return { exitCode: 0, output }
3337
+ } catch (e) {
3338
+ const err = e as { stdout?: string; stderr?: string }
3339
+ return { exitCode: 1, output: `${err.stdout ?? ''}${err.stderr ?? ''}` }
3340
+ }
3341
+ },
3342
+ readFile: readSafe,
3343
+ writeFile: (p, c) => writeFileSync(p, c, 'utf-8'),
3344
+ generateFix: async (prompt) => {
3345
+ let text = ''
3346
+ for await (const chunk of llm.chat({
3347
+ model: '',
3348
+ messages: [{ role: 'user' as const, content: prompt }],
3349
+ temperature: 0,
3350
+ })) {
3351
+ if (chunk.type === 'text' && chunk.content) text += chunk.content
3352
+ }
3353
+ return stripCodeFences(text)
3354
+ },
3355
+ resolveSourceFile: (tf, specifier) => {
3356
+ const base = resolve(dirname(tf), specifier)
3357
+ return /\.[cm]?[jt]sx?$/.test(base) ? base : `${base}.ts`
3358
+ },
3359
+ },
3360
+ testFile,
3361
+ { apply: apply && !dryRun, maxRetries: 3 },
3362
+ )
3363
+ if (result.fixed) {
3364
+ lines.push(
3365
+ t('commands.fix.test_fixed', {
3366
+ test: result.testFile,
3367
+ source: result.sourceFile ?? '-',
3368
+ attempts: String(result.attempts),
3369
+ }),
3370
+ )
3371
+ if (!apply) lines.push(t('commands.fix.test_hint'))
3372
+ } else {
3373
+ lines.push(
3374
+ t('commands.fix.test_failed', { test: result.testFile, detail: result.detail ?? '' }),
3375
+ )
3376
+ }
3377
+ }
3378
+ }
3379
+ lines.push('')
3380
+ }
3381
+
3382
+ return { content: lines.join('\n').trimEnd() }
3383
+ }
3384
+
3108
3385
  // ═══════════════════════════════════════════════════════════════
3109
3386
  // GitHub & Git Workflow Commands (Claude Code parity)
3110
3387
  // ═══════════════════════════════════════════════════════════════
@@ -3422,42 +3699,44 @@ const cdCmd: CommandHandler = async (ctx, args) => {
3422
3699
 
3423
3700
  const hooksCmd: CommandHandler = async (ctx) => {
3424
3701
  const t = resolveT(ctx)
3425
- const { existsSync, readdirSync } = await import('node:fs')
3426
- const { join } = await import('node:path')
3427
- const cwd = process.cwd()
3428
- const hooksDir = join(cwd, '.mipham', 'hooks')
3702
+ const { loadSettingsJson } = await import('../config/loader')
3703
+ const settingsJson = loadSettingsJson(process.cwd())
3704
+
3705
+ const hooks = settingsJson.hooks as Record<
3706
+ string,
3707
+ Array<{ matcher?: string; hooks: Array<{ type: string; command?: string }> }>
3708
+ >
3709
+
3710
+ const configured = Object.entries(hooks).filter(
3711
+ ([, entries]) => Array.isArray(entries) && entries.length > 0,
3712
+ )
3429
3713
 
3430
- if (!existsSync(hooksDir)) {
3714
+ if (configured.length === 0) {
3431
3715
  return { content: t('commands.hooks.no_hooks') }
3432
3716
  }
3433
3717
 
3434
- try {
3435
- const files = readdirSync(hooksDir).filter((f) => f.endsWith('.sh'))
3436
- const lines: string[] = [
3437
- t('commands.hooks.title'),
3438
- '',
3439
- t('commands.hooks.location', { path: hooksDir }),
3440
- '',
3441
- ]
3718
+ const lines: string[] = [
3719
+ t('commands.hooks.title'),
3720
+ '',
3721
+ t('commands.hooks.location', { path: '.mipham/settings.json' }),
3722
+ '',
3723
+ ]
3442
3724
 
3443
- for (const f of files) {
3444
- try {
3445
- const stat = (await import('node:fs')).statSync(join(hooksDir, f))
3446
- const isExec = (stat.mode & 0o111) !== 0
3447
- lines.push(
3448
- ` ${isExec ? '✅' : '⚠️'} ${f}${isExec ? '' : ' (not executable — run chmod +x)'}`,
3449
- )
3450
- } catch {
3451
- lines.push(` 📄 ${f}`)
3725
+ let count = 0
3726
+ for (const [eventName, entries] of configured) {
3727
+ for (const entry of entries) {
3728
+ for (const hook of entry.hooks) {
3729
+ count++
3730
+ const matcher = entry.matcher || '*'
3731
+ const cmd = hook.type === 'command' && hook.command ? hook.command : hook.type
3732
+ lines.push(` ${eventName} [${matcher}] → ${cmd}`)
3452
3733
  }
3453
3734
  }
3454
-
3455
- lines.push('')
3456
- lines.push(t('commands.hooks.found', { count: String(files.length) }))
3457
- return { content: lines.join('\n') }
3458
- } catch {
3459
- return { content: t('commands.hooks.error') }
3460
3735
  }
3736
+
3737
+ lines.push('')
3738
+ lines.push(t('commands.hooks.found', { count: String(count) }))
3739
+ return { content: lines.join('\n') }
3461
3740
  }
3462
3741
 
3463
3742
  const hooksHealthCmd: CommandHandler = (ctx) => {
@@ -3816,6 +4095,23 @@ const memoryCmd: CommandHandler = async (ctx, args) => {
3816
4095
  return { content: lines.join('\n') }
3817
4096
  }
3818
4097
 
4098
+ // /memory dedup — 只读近重复报告:列出 TF-IDF 余弦 > 阈值的近重复对,用户决定是否合并
4099
+ if (args[0]?.toLowerCase() === 'dedup') {
4100
+ const mm = getMemoryManager()
4101
+ const pairs = mm.listNearDuplicates()
4102
+ const lines: string[] = [t('commands.memories.dedup_title'), '']
4103
+ if (pairs.length === 0) {
4104
+ lines.push(t('commands.memories.dedup_none'))
4105
+ } else {
4106
+ for (const { a, b, similarity } of pairs) {
4107
+ lines.push(` ${a} ↔ ${b} (${(similarity * 100).toFixed(0)}%)`)
4108
+ }
4109
+ lines.push('')
4110
+ lines.push(t('commands.memories.dedup_footer'))
4111
+ }
4112
+ return { content: lines.join('\n') }
4113
+ }
4114
+
3819
4115
  if (!existsSync(memoryDir)) {
3820
4116
  return { content: t('commands.memories.no_memories') }
3821
4117
  }
@@ -4225,6 +4521,14 @@ const mcpCmd: CommandHandler = async (ctx, args) => {
4225
4521
  }
4226
4522
  }
4227
4523
  const via = config.url ? 'HTTP' : 'stdio'
4524
+ if (config.url) {
4525
+ const headerKeys = config.headers ? Object.keys(config.headers) : []
4526
+ const headerLine = headerKeys.length > 0 ? `\nHeaders to send: ${headerKeys.join(', ')}` : ''
4527
+ return {
4528
+ content: `── MCP Connect: ${name} ──\n\nConnecting via HTTP\nURL: ${config.url}${headerLine}\n\n⚠️ Verify the URL — any configured headers (e.g. Authorization) will be sent to this server.`,
4529
+ forwardToAI: `Connect to MCP server "${name}" using McpClient.getInstance().connect(config), then register its tools. Report the result.`,
4530
+ }
4531
+ }
4228
4532
  return {
4229
4533
  content: `── MCP Connect: ${name} ──\n\nConnecting via ${via}...`,
4230
4534
  forwardToAI: `Connect to MCP server "${name}" using McpClient.getInstance().connect(config), then register its tools. Report the result.`,
@@ -4868,6 +5172,8 @@ const commandsListCmd: CommandHandler = () => {
4868
5172
  '/reload-skills': 'Tools & Skills',
4869
5173
  '/browse-skills': 'Tools & Skills',
4870
5174
  '/install-skill': 'Tools & Skills',
5175
+ '/marketplace': 'Tools & Skills',
5176
+ '/browse-marketplace': 'Tools & Skills',
4871
5177
  '/remove-skill': 'Tools & Skills',
4872
5178
  '/mcp': 'Tools & Skills',
4873
5179
  '/plugins': 'Plugins',
@@ -5028,6 +5334,8 @@ registry.set('/skills', skillsCmd)
5028
5334
  registry.set('/reload-skills', reloadSkillsCmd)
5029
5335
  registry.set('/browse-skills', browseSkillsCmd)
5030
5336
  registry.set('/install-skill', installSkillCmd)
5337
+ registry.set('/marketplace', marketplaceCmd)
5338
+ registry.set('/browse-marketplace', browseMarketplaceCmd)
5031
5339
  registry.set('/remove-skill', removeSkillCmd)
5032
5340
  registry.set('/plugins', pluginsCmd)
5033
5341
  registry.set('/browse-plugins', browsePluginsCmd)
@@ -5079,6 +5387,7 @@ registry.set('/pr-comments', prCommentsCmd)
5079
5387
 
5080
5388
  // Session Management
5081
5389
  registry.set('/doctor', doctorCmd)
5390
+ registry.set('/fix', fixCmd)
5082
5391
  registry.set('/export', exportCmd)
5083
5392
  registry.set('/resume', resumeCmd)
5084
5393
  registry.set('/resume last', resumeLastCmd)
@@ -5183,6 +5492,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
5183
5492
  '/save': 'Save conversation to Obsidian wiki (skill: save-to-wiki)',
5184
5493
  '/export': 'Export conversation to file',
5185
5494
  '/doctor': 'System diagnostics',
5495
+ '/fix': 'Deterministic self-repair: doctor/config/cache',
5186
5496
  '/dream': 'Background memory consolidation',
5187
5497
  '/constitution': 'View or reload constitutional principles',
5188
5498
  '/bug-report': 'Generate diagnostic report for GitHub Issues',
@@ -5212,6 +5522,8 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
5212
5522
  '/reload-skills': 'Reload all skills',
5213
5523
  '/browse-skills': 'Browse community skill marketplace',
5214
5524
  '/install-skill': 'Install a skill by name or URL',
5525
+ '/marketplace': 'Manage marketplace sources (add/list/remove)',
5526
+ '/browse-marketplace': 'Browse skills across all marketplace sources',
5215
5527
  '/remove-skill': 'Remove an installed skill',
5216
5528
  '/mcp': 'MCP server status',
5217
5529
  '/plugins': 'List installed plugins',
@@ -5294,7 +5606,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
5294
5606
  '/stats': 'Show session usage statistics',
5295
5607
  '/summary': 'Generate session summary',
5296
5608
  '/cd': 'Change session working directory',
5297
- '/hooks': 'Manage lifecycle hook scripts',
5609
+ '/hooks': 'List configured hooks from settings.json',
5298
5610
  '/hooks health': 'Check hook health — see failures, disabled hooks, recovery status',
5299
5611
  '/hooks enable': 'Manually re-enable a hook that was auto-disabled after repeated failures',
5300
5612
  '/batch': 'Apply changes across multiple files',