kimi-code/packages/migration-legacy/test/report.test.ts
Kai 475ebadc20
fix: copy user skills and clean up OAuth UX (#31)
* feat(migration-legacy): migrate user skills from kimi-cli

The first-launch migration previously left ~/.kimi/skills/ behind: the
new scanner only reads ~/.kimi-code/skills/ and ~/.agents/skills/, so any
custom skills authored against kimi-cli silently disappeared after the
upgrade. Adds a skills step that copies top-level entries from
~/.kimi/skills/ into ~/.kimi-code/skills/ with skip-existing semantics,
wires it into the existing run-migration pipeline and result screen, and
surfaces the count alongside config/mcp/REPL-history.

* refactor(migration): hide OAuth from migration UX

OAuth credentials are deliberately never migrated (refresh tokens
rotate server-side, so two installs sharing one token would fight over
who gets refreshed). The previous UX framed this as a limitation: the
result screen carried a yellow ⚠ "kimi-cli login not migrated — run
/login" line, and the pre-migration summary listed "kimi-cli login
(needs /login)" alongside real migratable data classes, making users
think a login was about to be transferred and only the last step had
failed.

Drops both surfaces and short-circuits the pre-migration screen when
the only legacy data is `credentials/*.json`. kimi-code's own /login
flow handles re-auth on first use, so a dedicated migration notice is
redundant. The `report.notices.oauthLoginsRequiringRelogin` JSON field
is left intact for debugging.

* chore: changeset for skills migration and OAuth UX cleanup
2026-05-25 21:16:36 +08:00

69 lines
2.3 KiB
TypeScript

import { describe, expect, it, beforeEach, afterEach } from 'vitest';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { writeReport } from '../src/report.js';
import type { MigrationReport } from '../src/types.js';
let tgt: string;
beforeEach(async () => {
tgt = await mkdtemp(join(tmpdir(), 'rpt-'));
});
afterEach(async () => {
await rm(tgt, { recursive: true, force: true });
});
describe('writeReport', () => {
it('serializes the report at <target>/migration-report.json', async () => {
const report: MigrationReport = {
startedAt: 's',
completedAt: 'e',
migratorVersion: '0.1.1',
source: '/x',
target: tgt,
summary: {
config: {
migrated: false,
tuiExtracted: false,
droppedProviders: [],
droppedModels: [],
droppedKeys: [],
configConflicts: [],
wroteSiblingDueToConflict: false,
wroteTuiSibling: false,
migratedHooks: 0,
droppedHooks: 0,
siblingContents: { providers: [], models: [], hooks: 0 },
},
mcp: { mergedServers: [], keptNewForConflicts: [], droppedServers: [], wroteSiblingDueToConflict: false },
userHistory: { copied: 0, skippedExisting: 0 },
skills: { copied: 0, skippedExisting: 0 },
sessions: {
scope: 'all',
bucketsScanned: 0,
bucketsSkippedNonlocalKaos: 0,
bucketsSkippedNoWorkdirFound: 0,
sessionsAttempted: 0,
sessionsMigrated: 0,
sessionsAlreadyMigrated: 0,
sessionsSkippedPlaceholder: 0,
sessionsSkippedEmpty: 0,
sessionsSkippedMalformed: 0,
sessionsFailed: [],
sessionsConflicts: [],
},
},
notices: {
mcpOauthServersRequiringReauth: [],
oauthLoginsRequiringRelogin: [],
detectedPlugins: [],
configConflictNotice: null,
tuiConflictNotice: null,
},
};
await writeReport(tgt, report);
const text = await readFile(join(tgt, 'migration-report.json'), 'utf-8');
const parsed: unknown = JSON.parse(text);
expect((parsed as { migratorVersion: string }).migratorVersion).toBe('0.1.1');
});
});