@miphamai/cli 0.68.0 → 0.70.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.
- package/package.json +1 -1
- package/src/commands/loop-scaffold.ts +15 -44
- package/src/config/loader.ts +56 -1
- package/src/core/claude-md-fix.ts +28 -0
- package/src/core/crsi-modify.ts +10 -1
- package/src/core/crsi-producer.ts +13 -0
- package/src/core/engine.ts +31 -0
- package/src/core/eval-harness.ts +94 -2
- package/src/core/fix-code.ts +167 -0
- package/src/core/fix.ts +176 -0
- package/src/core/hooks-config.ts +10 -5
- package/src/core/hooks-executor.ts +84 -5
- package/src/core/instructions.ts +2 -2
- package/src/core/memory/memory-manager.ts +205 -19
- package/src/core/post-flight-checker.ts +102 -0
- package/src/core/reward-fn.ts +3 -1
- package/src/core/session-log.ts +3 -1
- package/src/core/working-memory.ts +140 -0
- package/src/i18n-core/locales/en-US.json +30 -2
- package/src/i18n-core/locales/zh-CN.json +30 -2
- package/src/index.tsx +12 -0
- package/src/shared/package-info.ts +1 -1
- package/src/shared/types.ts +2 -0
- package/src/skills/community-registry.json +16 -0
- package/src/skills/marketplace.ts +233 -0
- package/src/skills/registry.ts +77 -63
- package/src/tools/agent/memory.ts +5 -1
- package/src/tools/exec/task.ts +21 -2
- package/src/ui/commands.ts +354 -34
package/src/ui/commands.ts
CHANGED
|
@@ -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 {
|
|
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,8 @@ 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'
|
|
55
|
+
import { getMemoryManager } from '../core/memory/memory-loader'
|
|
49
56
|
import { stripIndent } from './strip-indent.js'
|
|
50
57
|
import { createT } from '../i18n-core/t'
|
|
51
58
|
import type { TranslationMap } from '../i18n-core/types'
|
|
@@ -2358,9 +2365,9 @@ const installSkillCmd: CommandHandler = async (ctx, args) => {
|
|
|
2358
2365
|
let result: { success: boolean; name: string; message: string }
|
|
2359
2366
|
|
|
2360
2367
|
if (target.startsWith('http://') || target.startsWith('https://')) {
|
|
2361
|
-
result = installSkillFromUrl(target, marketplaceConfig)
|
|
2368
|
+
result = await installSkillFromUrl(target, marketplaceConfig)
|
|
2362
2369
|
} else {
|
|
2363
|
-
result = installSkill(target, marketplaceConfig)
|
|
2370
|
+
result = await installSkill(target, marketplaceConfig)
|
|
2364
2371
|
}
|
|
2365
2372
|
|
|
2366
2373
|
return {
|
|
@@ -2382,6 +2389,83 @@ const removeSkillCmd: CommandHandler = async (_ctx, args) => {
|
|
|
2382
2389
|
}
|
|
2383
2390
|
}
|
|
2384
2391
|
|
|
2392
|
+
const marketplaceCmd: CommandHandler = async (_ctx, args) => {
|
|
2393
|
+
const {
|
|
2394
|
+
readMarketplaces,
|
|
2395
|
+
addMarketplace,
|
|
2396
|
+
removeMarketplace,
|
|
2397
|
+
isValidMarketplaceRef,
|
|
2398
|
+
MARKETPLACES_PATH,
|
|
2399
|
+
} = await import('../skills/marketplace')
|
|
2400
|
+
const { readFileSync, writeFileSync, existsSync, mkdirSync } = await import('node:fs')
|
|
2401
|
+
const { dirname } = await import('node:path')
|
|
2402
|
+
|
|
2403
|
+
const readSources = () =>
|
|
2404
|
+
readMarketplaces((p) => (existsSync(p) ? readFileSync(p, 'utf-8') : null))
|
|
2405
|
+
|
|
2406
|
+
const sub = args[0]
|
|
2407
|
+
const ref = args[1]
|
|
2408
|
+
|
|
2409
|
+
if (!sub || sub === 'list') {
|
|
2410
|
+
const sources = readSources()
|
|
2411
|
+
const lines = ['── Marketplace Sources ──', '']
|
|
2412
|
+
for (const s of sources) lines.push(` ${s.owner}/${s.repo}`)
|
|
2413
|
+
lines.push(
|
|
2414
|
+
'',
|
|
2415
|
+
'Add: /marketplace add <owner>/<repo>',
|
|
2416
|
+
'Remove: /marketplace remove <owner>/<repo>',
|
|
2417
|
+
)
|
|
2418
|
+
return { content: lines.join('\n') }
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
if (sub === 'add' || sub === 'remove') {
|
|
2422
|
+
if (!ref) return { content: `Usage: /marketplace ${sub} <owner>/<repo>` }
|
|
2423
|
+
const [owner, repo] = ref.split('/')
|
|
2424
|
+
if (!owner || !repo || !isValidMarketplaceRef(owner, repo)) {
|
|
2425
|
+
return { content: `❌ Invalid marketplace ref "${ref}". Expected <owner>/<repo>.` }
|
|
2426
|
+
}
|
|
2427
|
+
const current = readSources()
|
|
2428
|
+
const result =
|
|
2429
|
+
sub === 'add' ? addMarketplace(current, owner, repo) : removeMarketplace(current, owner, repo)
|
|
2430
|
+
const changed = 'added' in result ? result.added : result.removed
|
|
2431
|
+
try {
|
|
2432
|
+
mkdirSync(dirname(MARKETPLACES_PATH), { recursive: true })
|
|
2433
|
+
writeFileSync(MARKETPLACES_PATH, JSON.stringify(result.sources, null, 2), 'utf-8')
|
|
2434
|
+
} catch (err) {
|
|
2435
|
+
return { content: `❌ Failed to save marketplaces: ${String(err)}` }
|
|
2436
|
+
}
|
|
2437
|
+
if (sub === 'add') {
|
|
2438
|
+
return {
|
|
2439
|
+
content: changed ? `✅ Added ${owner}/${repo}` : `ℹ ${owner}/${repo} already exists`,
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
return { content: changed ? `✅ Removed ${owner}/${repo}` : `ℹ ${owner}/${repo} not found` }
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
return { content: 'Usage: /marketplace add|list|remove [owner/repo]' }
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
const browseMarketplaceCmd: CommandHandler = async (_ctx) => {
|
|
2449
|
+
const { readMarketplaces, discoverSkills, downloadFile } = await import('../skills/marketplace')
|
|
2450
|
+
const { readFileSync, existsSync } = await import('node:fs')
|
|
2451
|
+
|
|
2452
|
+
const sources = readMarketplaces((p) => (existsSync(p) ? readFileSync(p, 'utf-8') : null))
|
|
2453
|
+
const lines = ['── Marketplace Skills ──', '']
|
|
2454
|
+
for (const source of sources) {
|
|
2455
|
+
lines.push(` ${source.owner}/${source.repo}:`)
|
|
2456
|
+
try {
|
|
2457
|
+
const skills = await discoverSkills(source, globalThis.fetch, downloadFile)
|
|
2458
|
+
if (skills.length === 0) lines.push(' (no skills found)')
|
|
2459
|
+
for (const s of skills) lines.push(` ${s.name.padEnd(24)} ${s.description}`)
|
|
2460
|
+
} catch {
|
|
2461
|
+
lines.push(' (failed to discover — check network / rate limit)')
|
|
2462
|
+
}
|
|
2463
|
+
lines.push('')
|
|
2464
|
+
}
|
|
2465
|
+
lines.push('Install: /install-skill <name>')
|
|
2466
|
+
return { content: lines.join('\n') }
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2385
2469
|
// ═══════════════════════════════════════════════════════════════
|
|
2386
2470
|
// Plugins — Community plugin marketplace + local management
|
|
2387
2471
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -3104,6 +3188,191 @@ const doctorCmd: CommandHandler = async (ctx) => {
|
|
|
3104
3188
|
return { content: lines.join('\n') }
|
|
3105
3189
|
}
|
|
3106
3190
|
|
|
3191
|
+
const fixCmd: CommandHandler = async (ctx, args) => {
|
|
3192
|
+
const t = resolveT(ctx)
|
|
3193
|
+
const { readFileSync, writeFileSync } = await import('node:fs')
|
|
3194
|
+
const { join } = await import('node:path')
|
|
3195
|
+
const { homedir } = await import('node:os')
|
|
3196
|
+
const { parse: parseYaml } = await import('yaml')
|
|
3197
|
+
|
|
3198
|
+
const target = args.find((a) => a === 'doctor' || a === 'config' || a === 'cache' || a === 'test')
|
|
3199
|
+
const dryRun = args.includes('--dry-run')
|
|
3200
|
+
const apply = args.includes('--apply')
|
|
3201
|
+
|
|
3202
|
+
const lines: string[] = [t('commands.fix.title')]
|
|
3203
|
+
if (dryRun) lines.push(t('commands.fix.dryrun_banner'))
|
|
3204
|
+
lines.push('')
|
|
3205
|
+
|
|
3206
|
+
const readSafe = (p: string): string | null => {
|
|
3207
|
+
try {
|
|
3208
|
+
return readFileSync(p, 'utf-8')
|
|
3209
|
+
} catch {
|
|
3210
|
+
return null
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
if (!target || target === 'doctor') {
|
|
3215
|
+
const loader = new InstructionsLoader()
|
|
3216
|
+
loader.loadAll(process.cwd())
|
|
3217
|
+
const files = selectRepoClaudeFiles(loader.list())
|
|
3218
|
+
const result = fixDoctor(files, {
|
|
3219
|
+
read: readSafe,
|
|
3220
|
+
write: (p, c) => {
|
|
3221
|
+
if (!dryRun) writeFileSync(p, c, 'utf-8')
|
|
3222
|
+
},
|
|
3223
|
+
})
|
|
3224
|
+
if (result.fixed.length === 0) {
|
|
3225
|
+
lines.push(t('commands.fix.doctor_clean'))
|
|
3226
|
+
} else {
|
|
3227
|
+
for (const f of result.fixed) {
|
|
3228
|
+
lines.push(t('commands.fix.doctor_fixed', { path: f.path, added: f.added.join(', ') }))
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
lines.push('')
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
if (!target || target === 'config') {
|
|
3235
|
+
const home = homedir()
|
|
3236
|
+
const configPaths = [
|
|
3237
|
+
join(process.cwd(), '.mipham', 'config.yml'),
|
|
3238
|
+
join(home, '.mipham', 'config.yml'),
|
|
3239
|
+
]
|
|
3240
|
+
const hookEngine = ctx.engine.getHookEngine?.()
|
|
3241
|
+
const result = fixConfig({
|
|
3242
|
+
configPaths,
|
|
3243
|
+
read: readSafe,
|
|
3244
|
+
parseYaml,
|
|
3245
|
+
restore: (p) => tryRestoreFromBackup(p),
|
|
3246
|
+
hookHealth: () =>
|
|
3247
|
+
hookEngine
|
|
3248
|
+
? hookEngine.getHookHealth().map((h) => ({ key: h.key, disabled: h.health.disabled }))
|
|
3249
|
+
: [],
|
|
3250
|
+
reEnableHook: (k) => (hookEngine ? hookEngine.reEnableHook(k) : false),
|
|
3251
|
+
dryRun,
|
|
3252
|
+
})
|
|
3253
|
+
if (result.corruptConfigs.length === 0 && result.disabledHooks.length === 0) {
|
|
3254
|
+
lines.push(t('commands.fix.config_clean'))
|
|
3255
|
+
} else {
|
|
3256
|
+
for (const p of result.restoredConfigs) {
|
|
3257
|
+
lines.push(t('commands.fix.config_restored', { path: p }))
|
|
3258
|
+
}
|
|
3259
|
+
for (const p of result.corruptConfigs.filter((p) => !result.restoredConfigs.includes(p))) {
|
|
3260
|
+
lines.push(t('commands.fix.config_corrupt', { path: p }))
|
|
3261
|
+
}
|
|
3262
|
+
for (const k of result.reenabledHooks) {
|
|
3263
|
+
lines.push(t('commands.fix.hook_reenabled', { key: k }))
|
|
3264
|
+
}
|
|
3265
|
+
for (const k of result.disabledHooks.filter((k) => !result.reenabledHooks.includes(k))) {
|
|
3266
|
+
lines.push(t('commands.fix.hook_disabled', { key: k }))
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
lines.push('')
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
if (!target || target === 'cache') {
|
|
3273
|
+
const crsiDir = join(homedir(), '.mipham', 'crsi')
|
|
3274
|
+
const cacheFiles = ['eval-scores.jsonl', 'improvements.jsonl', 'prose-proposals.jsonl'].map(
|
|
3275
|
+
(f) => join(crsiDir, f),
|
|
3276
|
+
)
|
|
3277
|
+
const result = fixCache(
|
|
3278
|
+
cacheFiles,
|
|
3279
|
+
{
|
|
3280
|
+
read: readSafe,
|
|
3281
|
+
write: (p, c) => writeFileSync(p, c, 'utf-8'),
|
|
3282
|
+
},
|
|
3283
|
+
apply && !dryRun,
|
|
3284
|
+
)
|
|
3285
|
+
if (result.files.length === 0) {
|
|
3286
|
+
lines.push(t('commands.fix.cache_clean'))
|
|
3287
|
+
} else {
|
|
3288
|
+
for (const f of result.files) {
|
|
3289
|
+
lines.push(
|
|
3290
|
+
t('commands.fix.cache_found', { path: f.path, count: String(f.corruptLines.length) }),
|
|
3291
|
+
)
|
|
3292
|
+
}
|
|
3293
|
+
if (apply && !dryRun) {
|
|
3294
|
+
for (const c of result.cleaned) {
|
|
3295
|
+
lines.push(t('commands.fix.cache_cleaned', { path: c.path, count: String(c.removed) }))
|
|
3296
|
+
}
|
|
3297
|
+
} else {
|
|
3298
|
+
lines.push(t('commands.fix.cache_hint'))
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
lines.push('')
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
if (!target || target === 'test') {
|
|
3305
|
+
const testFile = args.find(
|
|
3306
|
+
(a) =>
|
|
3307
|
+
!a.startsWith('--') && a !== 'test' && a !== 'doctor' && a !== 'config' && a !== 'cache',
|
|
3308
|
+
)
|
|
3309
|
+
if (!testFile) {
|
|
3310
|
+
lines.push(t('commands.fix.test_usage'))
|
|
3311
|
+
} else {
|
|
3312
|
+
const llm = ctx.engine.getLlm() ?? ctx.engine.getRegistry()
|
|
3313
|
+
if (!llm) {
|
|
3314
|
+
lines.push(t('commands.fix.test_no_llm'))
|
|
3315
|
+
} else {
|
|
3316
|
+
const { execSync } = await import('node:child_process')
|
|
3317
|
+
const { dirname, resolve } = await import('node:path')
|
|
3318
|
+
const result = await fixCodeTarget(
|
|
3319
|
+
{
|
|
3320
|
+
runVitest: (file) => {
|
|
3321
|
+
try {
|
|
3322
|
+
const output = execSync(`pnpm vitest run '${file}'`, {
|
|
3323
|
+
cwd: process.cwd(),
|
|
3324
|
+
encoding: 'utf-8',
|
|
3325
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
3326
|
+
})
|
|
3327
|
+
return { exitCode: 0, output }
|
|
3328
|
+
} catch (e) {
|
|
3329
|
+
const err = e as { stdout?: string; stderr?: string }
|
|
3330
|
+
return { exitCode: 1, output: `${err.stdout ?? ''}${err.stderr ?? ''}` }
|
|
3331
|
+
}
|
|
3332
|
+
},
|
|
3333
|
+
readFile: readSafe,
|
|
3334
|
+
writeFile: (p, c) => writeFileSync(p, c, 'utf-8'),
|
|
3335
|
+
generateFix: async (prompt) => {
|
|
3336
|
+
let text = ''
|
|
3337
|
+
for await (const chunk of llm.chat({
|
|
3338
|
+
model: '',
|
|
3339
|
+
messages: [{ role: 'user' as const, content: prompt }],
|
|
3340
|
+
temperature: 0,
|
|
3341
|
+
})) {
|
|
3342
|
+
if (chunk.type === 'text' && chunk.content) text += chunk.content
|
|
3343
|
+
}
|
|
3344
|
+
return stripCodeFences(text)
|
|
3345
|
+
},
|
|
3346
|
+
resolveSourceFile: (tf, specifier) => {
|
|
3347
|
+
const base = resolve(dirname(tf), specifier)
|
|
3348
|
+
return /\.[cm]?[jt]sx?$/.test(base) ? base : `${base}.ts`
|
|
3349
|
+
},
|
|
3350
|
+
},
|
|
3351
|
+
testFile,
|
|
3352
|
+
{ apply: apply && !dryRun, maxRetries: 3 },
|
|
3353
|
+
)
|
|
3354
|
+
if (result.fixed) {
|
|
3355
|
+
lines.push(
|
|
3356
|
+
t('commands.fix.test_fixed', {
|
|
3357
|
+
test: result.testFile,
|
|
3358
|
+
source: result.sourceFile ?? '-',
|
|
3359
|
+
attempts: String(result.attempts),
|
|
3360
|
+
}),
|
|
3361
|
+
)
|
|
3362
|
+
if (!apply) lines.push(t('commands.fix.test_hint'))
|
|
3363
|
+
} else {
|
|
3364
|
+
lines.push(
|
|
3365
|
+
t('commands.fix.test_failed', { test: result.testFile, detail: result.detail ?? '' }),
|
|
3366
|
+
)
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
lines.push('')
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
return { content: lines.join('\n').trimEnd() }
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3107
3376
|
// ═══════════════════════════════════════════════════════════════
|
|
3108
3377
|
// GitHub & Git Workflow Commands (Claude Code parity)
|
|
3109
3378
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -3421,42 +3690,44 @@ const cdCmd: CommandHandler = async (ctx, args) => {
|
|
|
3421
3690
|
|
|
3422
3691
|
const hooksCmd: CommandHandler = async (ctx) => {
|
|
3423
3692
|
const t = resolveT(ctx)
|
|
3424
|
-
const {
|
|
3425
|
-
const
|
|
3426
|
-
|
|
3427
|
-
const
|
|
3693
|
+
const { loadSettingsJson } = await import('../config/loader')
|
|
3694
|
+
const settingsJson = loadSettingsJson(process.cwd())
|
|
3695
|
+
|
|
3696
|
+
const hooks = settingsJson.hooks as Record<
|
|
3697
|
+
string,
|
|
3698
|
+
Array<{ matcher?: string; hooks: Array<{ type: string; command?: string }> }>
|
|
3699
|
+
>
|
|
3700
|
+
|
|
3701
|
+
const configured = Object.entries(hooks).filter(
|
|
3702
|
+
([, entries]) => Array.isArray(entries) && entries.length > 0,
|
|
3703
|
+
)
|
|
3428
3704
|
|
|
3429
|
-
if (
|
|
3705
|
+
if (configured.length === 0) {
|
|
3430
3706
|
return { content: t('commands.hooks.no_hooks') }
|
|
3431
3707
|
}
|
|
3432
3708
|
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
'',
|
|
3440
|
-
]
|
|
3709
|
+
const lines: string[] = [
|
|
3710
|
+
t('commands.hooks.title'),
|
|
3711
|
+
'',
|
|
3712
|
+
t('commands.hooks.location', { path: '.mipham/settings.json' }),
|
|
3713
|
+
'',
|
|
3714
|
+
]
|
|
3441
3715
|
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
lines.push(` 📄 ${f}`)
|
|
3716
|
+
let count = 0
|
|
3717
|
+
for (const [eventName, entries] of configured) {
|
|
3718
|
+
for (const entry of entries) {
|
|
3719
|
+
for (const hook of entry.hooks) {
|
|
3720
|
+
count++
|
|
3721
|
+
const matcher = entry.matcher || '*'
|
|
3722
|
+
const cmd = hook.type === 'command' && hook.command ? hook.command : hook.type
|
|
3723
|
+
lines.push(` ${eventName} [${matcher}] → ${cmd}`)
|
|
3451
3724
|
}
|
|
3452
3725
|
}
|
|
3453
|
-
|
|
3454
|
-
lines.push('')
|
|
3455
|
-
lines.push(t('commands.hooks.found', { count: String(files.length) }))
|
|
3456
|
-
return { content: lines.join('\n') }
|
|
3457
|
-
} catch {
|
|
3458
|
-
return { content: t('commands.hooks.error') }
|
|
3459
3726
|
}
|
|
3727
|
+
|
|
3728
|
+
lines.push('')
|
|
3729
|
+
lines.push(t('commands.hooks.found', { count: String(count) }))
|
|
3730
|
+
return { content: lines.join('\n') }
|
|
3460
3731
|
}
|
|
3461
3732
|
|
|
3462
3733
|
const hooksHealthCmd: CommandHandler = (ctx) => {
|
|
@@ -3766,7 +4037,7 @@ const saveCmd: CommandHandler = (ctx, args) => {
|
|
|
3766
4037
|
// Memory Management
|
|
3767
4038
|
// ═══════════════════════════════════════════════════════════════
|
|
3768
4039
|
|
|
3769
|
-
const memoryCmd: CommandHandler = async (ctx) => {
|
|
4040
|
+
const memoryCmd: CommandHandler = async (ctx, args) => {
|
|
3770
4041
|
const t = resolveT(ctx)
|
|
3771
4042
|
const { existsSync, readdirSync, readFileSync, statSync } = await import('node:fs')
|
|
3772
4043
|
const { join } = await import('node:path')
|
|
@@ -3774,6 +4045,47 @@ const memoryCmd: CommandHandler = async (ctx) => {
|
|
|
3774
4045
|
const home = process.env.HOME || '~'
|
|
3775
4046
|
const memoryDir = join(home, '.mipham', 'memory')
|
|
3776
4047
|
|
|
4048
|
+
// /memory gc — 记忆卫生:归档「0 召回 + 过期」的 auto-* 记忆(手写只报告)
|
|
4049
|
+
if (args[0]?.toLowerCase() === 'gc') {
|
|
4050
|
+
const mm = getMemoryManager()
|
|
4051
|
+
const { archived, candidates } = mm.gc()
|
|
4052
|
+
const lines: string[] = [t('commands.memories.gc_title'), '']
|
|
4053
|
+
if (archived.length === 0 && candidates.length === 0) {
|
|
4054
|
+
lines.push(t('commands.memories.gc_none'))
|
|
4055
|
+
} else {
|
|
4056
|
+
if (archived.length > 0) {
|
|
4057
|
+
lines.push(t('commands.memories.gc_archived', { count: String(archived.length) }))
|
|
4058
|
+
lines.push(...archived.map((n) => ` - ${n}`))
|
|
4059
|
+
lines.push('')
|
|
4060
|
+
}
|
|
4061
|
+
if (candidates.length > 0) {
|
|
4062
|
+
lines.push(t('commands.memories.gc_candidates', { count: String(candidates.length) }))
|
|
4063
|
+
lines.push(...candidates.map((n) => ` - ${n}`))
|
|
4064
|
+
lines.push('')
|
|
4065
|
+
}
|
|
4066
|
+
lines.push(t('commands.memories.gc_done'))
|
|
4067
|
+
}
|
|
4068
|
+
return { content: lines.join('\n') }
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
// /memory consolidate — 会话记忆合并:把 auto-* 聚簇成持久化 lesson-*
|
|
4072
|
+
if (args[0]?.toLowerCase() === 'consolidate') {
|
|
4073
|
+
const mm = getMemoryManager()
|
|
4074
|
+
const { merged, removed } = mm.consolidateAutoMemories()
|
|
4075
|
+
const lines: string[] = [t('commands.memories.consolidate_title'), '']
|
|
4076
|
+
if (removed === 0) {
|
|
4077
|
+
lines.push(t('commands.memories.consolidate_none'))
|
|
4078
|
+
} else {
|
|
4079
|
+
lines.push(
|
|
4080
|
+
t('commands.memories.consolidate_result', {
|
|
4081
|
+
removed: String(removed),
|
|
4082
|
+
merged: String(merged),
|
|
4083
|
+
}),
|
|
4084
|
+
)
|
|
4085
|
+
}
|
|
4086
|
+
return { content: lines.join('\n') }
|
|
4087
|
+
}
|
|
4088
|
+
|
|
3777
4089
|
if (!existsSync(memoryDir)) {
|
|
3778
4090
|
return { content: t('commands.memories.no_memories') }
|
|
3779
4091
|
}
|
|
@@ -4826,6 +5138,8 @@ const commandsListCmd: CommandHandler = () => {
|
|
|
4826
5138
|
'/reload-skills': 'Tools & Skills',
|
|
4827
5139
|
'/browse-skills': 'Tools & Skills',
|
|
4828
5140
|
'/install-skill': 'Tools & Skills',
|
|
5141
|
+
'/marketplace': 'Tools & Skills',
|
|
5142
|
+
'/browse-marketplace': 'Tools & Skills',
|
|
4829
5143
|
'/remove-skill': 'Tools & Skills',
|
|
4830
5144
|
'/mcp': 'Tools & Skills',
|
|
4831
5145
|
'/plugins': 'Plugins',
|
|
@@ -4986,6 +5300,8 @@ registry.set('/skills', skillsCmd)
|
|
|
4986
5300
|
registry.set('/reload-skills', reloadSkillsCmd)
|
|
4987
5301
|
registry.set('/browse-skills', browseSkillsCmd)
|
|
4988
5302
|
registry.set('/install-skill', installSkillCmd)
|
|
5303
|
+
registry.set('/marketplace', marketplaceCmd)
|
|
5304
|
+
registry.set('/browse-marketplace', browseMarketplaceCmd)
|
|
4989
5305
|
registry.set('/remove-skill', removeSkillCmd)
|
|
4990
5306
|
registry.set('/plugins', pluginsCmd)
|
|
4991
5307
|
registry.set('/browse-plugins', browsePluginsCmd)
|
|
@@ -5037,6 +5353,7 @@ registry.set('/pr-comments', prCommentsCmd)
|
|
|
5037
5353
|
|
|
5038
5354
|
// Session Management
|
|
5039
5355
|
registry.set('/doctor', doctorCmd)
|
|
5356
|
+
registry.set('/fix', fixCmd)
|
|
5040
5357
|
registry.set('/export', exportCmd)
|
|
5041
5358
|
registry.set('/resume', resumeCmd)
|
|
5042
5359
|
registry.set('/resume last', resumeLastCmd)
|
|
@@ -5141,6 +5458,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
5141
5458
|
'/save': 'Save conversation to Obsidian wiki (skill: save-to-wiki)',
|
|
5142
5459
|
'/export': 'Export conversation to file',
|
|
5143
5460
|
'/doctor': 'System diagnostics',
|
|
5461
|
+
'/fix': 'Deterministic self-repair: doctor/config/cache',
|
|
5144
5462
|
'/dream': 'Background memory consolidation',
|
|
5145
5463
|
'/constitution': 'View or reload constitutional principles',
|
|
5146
5464
|
'/bug-report': 'Generate diagnostic report for GitHub Issues',
|
|
@@ -5170,6 +5488,8 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
5170
5488
|
'/reload-skills': 'Reload all skills',
|
|
5171
5489
|
'/browse-skills': 'Browse community skill marketplace',
|
|
5172
5490
|
'/install-skill': 'Install a skill by name or URL',
|
|
5491
|
+
'/marketplace': 'Manage marketplace sources (add/list/remove)',
|
|
5492
|
+
'/browse-marketplace': 'Browse skills across all marketplace sources',
|
|
5173
5493
|
'/remove-skill': 'Remove an installed skill',
|
|
5174
5494
|
'/mcp': 'MCP server status',
|
|
5175
5495
|
'/plugins': 'List installed plugins',
|
|
@@ -5252,7 +5572,7 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
5252
5572
|
'/stats': 'Show session usage statistics',
|
|
5253
5573
|
'/summary': 'Generate session summary',
|
|
5254
5574
|
'/cd': 'Change session working directory',
|
|
5255
|
-
'/hooks': '
|
|
5575
|
+
'/hooks': 'List configured hooks from settings.json',
|
|
5256
5576
|
'/hooks health': 'Check hook health — see failures, disabled hooks, recovery status',
|
|
5257
5577
|
'/hooks enable': 'Manually re-enable a hook that was auto-disabled after repeated failures',
|
|
5258
5578
|
'/batch': 'Apply changes across multiple files',
|