A conversation-management approach is useful when it retains what you need, makes it retrievable, and supports checking results after a tool change. The original ranked four tools for portability and search. Without current versions and a common sample, those rankings are not evidence. This article keeps the four-way comparison while using pinned adapter inputs and executable acceptance checks.
Specify the layer being compared
Raw copies preserve upstream storage evidence. Readable exports support inspection. Normalized conversations support cross-source lookup. A summary knowledge base supports reuse, with information changes from selection and generation. These layers can coexist; a summary cannot replace a raw backup.
Record the tool version, OS, source, read or export method, and date. History browsing, session resumption, content search, cross-project search, and semantic retrieval are distinct capabilities. The Claude Code workflows document session resumption and its picker; VS Code session documentation documents JSON export. A documented export does not establish compatibility with this project’s adapter.
Four entry points impose different retention work
This comparison concerns shapes readable by the pinned ChatCrystal implementation, rather than every current UI, cloud-storage mode, or performance property of these tools.
| Source | Pinned adapter entry point | Evidence needed during migration |
|---|---|---|
| Claude Code | Project JSONL in Claude adapter | The copy can be reread; block, tool, and thought selection is recorded |
| Cursor | Workspace index and global bubbles in Cursor adapter | Both samples belong to the intended scope; orphans and version changes are explainable |
| Copilot | Workspace/empty-window locations, first JSONL snapshot or legacy JSON in Copilot adapter | Later patches are not mistaken for a latest full snapshot; timestamp and response retention is evidenced |
| Trae | SQLite KV and nested task content in Trae adapter | sessionId lookup, content precedence, and synthetic time are not mistaken for original timestamps |
JSONL can be inspected with text tools, while versions and record meaning still matter. SQLite supports queries, while consistent copies, actual tables and keys, and JSON decoding still matter. Either container may hold sensitive project material; establish retention and sharing scope for your migration samples. These are data-handling requirements rather than format or ecosystem rankings.
Apply the same acceptance manifest to four candidates
Label source, conversation ID, project, message order, content, tool flags, and thoughts in controlled samples, then compare migrated output. Counting two messages can miss source-ID collisions, mismatched content, or missing thoughts. Add original timestamps, parent references, attachments, and full tool traces to real acceptance separately.
The program constructs the same teaching manifest for four sources. It does not read their storage or run their clients or ChatCrystal, and it produces no actual product ranking. Save lesson.mjs and run Node 24+, without npm dependencies.
import assert from 'node:assert/strict';
const sources = ['claude-code', 'cursor', 'copilot', 'trae'];
const expected = sources.map((source) => ({
source,
id: 'shared-7',
project: 'fixture-project',
messages: [
{ id: 'u1', role: 'user', content: 'fixture question', thinking: null, hasToolUse: false },
{
id: 'a1',
role: 'assistant',
content: 'fixture answer',
thinking: 'fixture thought',
hasToolUse: true,
},
],
}));
function audit(actual) {
const key = (item) => item.source + ':' + item.id;
const found = new Map();
const issues = [];
for (const item of actual) {
const id = key(item);
if (found.has(id)) issues.push(id + ':duplicate');
found.set(id, item);
}
for (const reference of expected) {
const id = key(reference);
const item = found.get(id);
if (!item) {
issues.push(id + ':missing');
continue;
}
if (item.project !== reference.project) issues.push(id + ':project');
if (item.messages.length !== reference.messages.length) issues.push(id + ':message-count');
reference.messages.forEach((message, index) => {
for (const field of ['id', 'role', 'content', 'thinking', 'hasToolUse']) {
if (item.messages[index]?.[field] !== message[field])
issues.push(id + ':' + index + ':' + field);
}
});
found.delete(id);
}
for (const id of found.keys()) issues.push(id + ':unexpected');
return issues;
}
const intact = structuredClone(expected);
assert.deepEqual(audit(intact), []);
const lossy = structuredClone(expected);
lossy[0].messages[1].thinking = null;
assert.ok(audit(lossy).includes('claude-code:shared-7:1:thinking'));
const missing = expected.filter((item) => item.source !== 'cursor');
assert.ok(audit(missing).includes('cursor:shared-7:missing'));
const reordered = structuredClone(expected);
reordered[2].messages.reverse();
assert.ok(audit(reordered).includes('copilot:shared-7:0:role'));
assert.ok(
audit([...intact, structuredClone(intact[0])]).includes('claude-code:shared-7:duplicate')
);
console.log('four-source fixture acceptance passes; no product runtime is compared');
Intact manifests pass. Missing thinking, an omitted Cursor conversation, reversed Copilot messages, and a repeated same-source ID produce specific findings. A shared id does not merge different sources because the key includes source.
Remove thinking from the field loop and the thought-loss assertion fails. Use only id as the matching key and the intact-manifest assertion fails. Restore each to pass. This verifies selected-field acceptance, not complete real exports or resolution of every cross-source ID collision in ChatCrystal’s local import.
Choose a combination for the task
For debugging evidence, obtain a verifiable raw copy and document its consistency conditions. For human review, add a readable export. For cross-source lookup, check normalized identity and fields. For reusable lessons, then verify summaries, citations, and retrieval hits. Combine these steps for the purpose while retaining a path to the source.
ChatCrystal normalizes selected output through SourceAdapter; summaries, embeddings, and search have separate failure boundaries. This does not establish lossless upstream conversion. Unknown versions, missing sources, and empty results need investigation rather than a single import-success label.
A comparison should leave expected samples, actual retention and omissions, failure cases, and recovery steps. Define search tasks and expected results before measuring local latency and relevance. This article reports no actual speed, quality, cost, or product ranking. Continue with storage lookup and import identity and hashing to apply the manifest to specific paths.