@yeseh/cortex-cli 0.6.7 → 0.6.9
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/dist/program.js +1538 -5
- package/dist/program.js.map +32 -3
- package/dist/run.d.ts +0 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -4
- package/dist/run.js.map +3 -3
- package/package.json +4 -6
- package/dist/chunk-tgrm2cc9.js +0 -1543
- package/dist/chunk-tgrm2cc9.js.map +0 -38
- package/src/category/commands/create.spec.ts +0 -139
- package/src/category/commands/create.ts +0 -119
- package/src/category/index.ts +0 -24
- package/src/commands/init.spec.ts +0 -203
- package/src/commands/init.ts +0 -301
- package/src/context.spec.ts +0 -60
- package/src/context.ts +0 -170
- package/src/errors.spec.ts +0 -264
- package/src/errors.ts +0 -105
- package/src/memory/commands/add.spec.ts +0 -169
- package/src/memory/commands/add.ts +0 -158
- package/src/memory/commands/definitions.spec.ts +0 -80
- package/src/memory/commands/list.spec.ts +0 -123
- package/src/memory/commands/list.ts +0 -269
- package/src/memory/commands/move.spec.ts +0 -85
- package/src/memory/commands/move.ts +0 -119
- package/src/memory/commands/remove.spec.ts +0 -79
- package/src/memory/commands/remove.ts +0 -108
- package/src/memory/commands/show.spec.ts +0 -71
- package/src/memory/commands/show.ts +0 -165
- package/src/memory/commands/test-helpers.spec.ts +0 -127
- package/src/memory/commands/update.spec.ts +0 -86
- package/src/memory/commands/update.ts +0 -230
- package/src/memory/index.spec.ts +0 -59
- package/src/memory/index.ts +0 -44
- package/src/memory/parsing.spec.ts +0 -105
- package/src/memory/parsing.ts +0 -22
- package/src/observability.spec.ts +0 -126
- package/src/observability.ts +0 -82
- package/src/output.spec.ts +0 -835
- package/src/output.ts +0 -119
- package/src/program.spec.ts +0 -46
- package/src/program.ts +0 -75
- package/src/run.spec.ts +0 -31
- package/src/run.ts +0 -9
- package/src/store/commands/add.spec.ts +0 -131
- package/src/store/commands/add.ts +0 -231
- package/src/store/commands/init.spec.ts +0 -220
- package/src/store/commands/init.ts +0 -272
- package/src/store/commands/list.spec.ts +0 -175
- package/src/store/commands/list.ts +0 -102
- package/src/store/commands/prune.spec.ts +0 -120
- package/src/store/commands/prune.ts +0 -152
- package/src/store/commands/reindexs.spec.ts +0 -94
- package/src/store/commands/reindexs.ts +0 -97
- package/src/store/commands/remove.spec.ts +0 -97
- package/src/store/commands/remove.ts +0 -189
- package/src/store/index.spec.ts +0 -60
- package/src/store/index.ts +0 -49
- package/src/store/utils/resolve-store-name.spec.ts +0 -62
- package/src/store/utils/resolve-store-name.ts +0 -79
- package/src/test-helpers.spec.ts +0 -430
- package/src/tests/cli.integration.spec.ts +0 -1306
- package/src/toon.spec.ts +0 -183
- package/src/toon.ts +0 -462
- package/src/utils/git.spec.ts +0 -95
- package/src/utils/git.ts +0 -51
- package/src/utils/input.spec.ts +0 -326
- package/src/utils/input.ts +0 -150
- package/src/utils/paths.spec.ts +0 -235
- package/src/utils/paths.ts +0 -75
- package/src/utils/prompts.spec.ts +0 -23
- package/src/utils/prompts.ts +0 -88
- package/src/utils/resolve-default-store.spec.ts +0 -135
- package/src/utils/resolve-default-store.ts +0 -74
package/src/output.spec.ts
DELETED
|
@@ -1,835 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test';
|
|
2
|
-
import { serializeOutput } from './output.ts';
|
|
3
|
-
import type {
|
|
4
|
-
OutputCategory,
|
|
5
|
-
OutputInit,
|
|
6
|
-
OutputMemory,
|
|
7
|
-
OutputStore,
|
|
8
|
-
OutputStoreInit,
|
|
9
|
-
OutputStoreRegistry,
|
|
10
|
-
} from './output.ts';
|
|
11
|
-
|
|
12
|
-
// Sample test data
|
|
13
|
-
const sampleMemory: OutputMemory = {
|
|
14
|
-
path: 'global/persona/test-memory',
|
|
15
|
-
metadata: {
|
|
16
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
17
|
-
updatedAt: new Date('2024-01-16T14:30:00Z'),
|
|
18
|
-
tags: [
|
|
19
|
-
'test', 'example',
|
|
20
|
-
],
|
|
21
|
-
source: 'unit-test',
|
|
22
|
-
tokenEstimate: 42,
|
|
23
|
-
},
|
|
24
|
-
content: 'This is test content.',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const sampleCategory: OutputCategory = {
|
|
28
|
-
path: 'global/persona',
|
|
29
|
-
memories: [
|
|
30
|
-
{ path: 'global/persona/mem1', tokenEstimate: 10, summary: 'First memory' },
|
|
31
|
-
{ path: 'global/persona/mem2', tokenEstimate: 20 },
|
|
32
|
-
],
|
|
33
|
-
subcategories: [{ path: 'global/persona/sub1', memoryCount: 5 }],
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Helper to serialize with TOON format
|
|
37
|
-
const toonSerialize = <T>(kind: string, value: T) =>
|
|
38
|
-
serializeOutput({ kind, value } as Parameters<typeof serializeOutput>[0], 'toon');
|
|
39
|
-
|
|
40
|
-
describe('serializeMemoryToon', () => {
|
|
41
|
-
it('should encodes memory with all metadata fields', () => {
|
|
42
|
-
const result = toonSerialize('memory', sampleMemory);
|
|
43
|
-
|
|
44
|
-
expect(result.ok()).toBe(true);
|
|
45
|
-
if (result.ok()) {
|
|
46
|
-
// TOON outputs YAML-like format with spaces after colons and nested structure
|
|
47
|
-
expect(result.value).toContain('path: global/persona/test-memory');
|
|
48
|
-
// Timestamps are quoted because they contain colons, nested under metadata
|
|
49
|
-
expect(result.value).toContain('createdAt: "2024-01-15T10:00:00.000Z"');
|
|
50
|
-
expect(result.value).toContain('updatedAt: "2024-01-16T14:30:00.000Z"');
|
|
51
|
-
expect(result.value).toContain('tags[2');
|
|
52
|
-
expect(result.value).toContain('source: unit-test');
|
|
53
|
-
expect(result.value).toContain('tokenEstimate: 42');
|
|
54
|
-
expect(result.value).toContain('content: This is test content.');
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should uses nested structure for metadata (not key folding)', () => {
|
|
59
|
-
const memory: OutputMemory = {
|
|
60
|
-
path: 'test/path',
|
|
61
|
-
metadata: {
|
|
62
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
63
|
-
tags: ['tag1'],
|
|
64
|
-
},
|
|
65
|
-
content: 'Content',
|
|
66
|
-
};
|
|
67
|
-
const result = toonSerialize('memory', memory);
|
|
68
|
-
|
|
69
|
-
expect(result.ok()).toBe(true);
|
|
70
|
-
if (result.ok()) {
|
|
71
|
-
// TOON produces nested YAML-like structure, not dotted notation
|
|
72
|
-
expect(result.value).toContain('metadata:');
|
|
73
|
-
expect(result.value).toContain('createdAt:');
|
|
74
|
-
expect(result.value).toContain('tags[1');
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should quotes multiline content', () => {
|
|
79
|
-
const memory: OutputMemory = {
|
|
80
|
-
path: 'test/multiline',
|
|
81
|
-
metadata: {
|
|
82
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
83
|
-
tags: [],
|
|
84
|
-
},
|
|
85
|
-
content: 'Line 1\nLine 2\nLine 3',
|
|
86
|
-
};
|
|
87
|
-
const result = toonSerialize('memory', memory);
|
|
88
|
-
|
|
89
|
-
expect(result.ok()).toBe(true);
|
|
90
|
-
if (result.ok()) {
|
|
91
|
-
// Multiline content should be quoted (JSON-style)
|
|
92
|
-
expect(result.value).toContain('"Line 1\\nLine 2\\nLine 3"');
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should handles missing optional fields (updatedAt, source, tokenEstimate, expiresAt)', () => {
|
|
97
|
-
const memory: OutputMemory = {
|
|
98
|
-
path: 'test/minimal',
|
|
99
|
-
metadata: {
|
|
100
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
101
|
-
tags: ['minimal'],
|
|
102
|
-
// No updatedAt, source, tokenEstimate, expiresAt
|
|
103
|
-
},
|
|
104
|
-
content: 'Minimal content',
|
|
105
|
-
};
|
|
106
|
-
const result = toonSerialize('memory', memory);
|
|
107
|
-
|
|
108
|
-
expect(result.ok()).toBe(true);
|
|
109
|
-
if (result.ok()) {
|
|
110
|
-
expect(result.value).toContain('path: test/minimal');
|
|
111
|
-
expect(result.value).toContain('createdAt:');
|
|
112
|
-
expect(result.value).toContain('content: Minimal content');
|
|
113
|
-
// Should NOT contain optional fields that weren't set
|
|
114
|
-
expect(result.value).not.toContain('updatedAt');
|
|
115
|
-
expect(result.value).not.toContain('source');
|
|
116
|
-
expect(result.value).not.toContain('tokenEstimate');
|
|
117
|
-
expect(result.value).not.toContain('expiresAt');
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should serializes whitespace path without error (no validation)', () => {
|
|
122
|
-
// The output module does NOT validate - it just serializes
|
|
123
|
-
const memory: OutputMemory = {
|
|
124
|
-
path: ' ', // Whitespace path - serializes as-is
|
|
125
|
-
metadata: {
|
|
126
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
127
|
-
tags: [],
|
|
128
|
-
},
|
|
129
|
-
content: 'Content',
|
|
130
|
-
};
|
|
131
|
-
const result = toonSerialize('memory', memory);
|
|
132
|
-
|
|
133
|
-
// Serialization succeeds - validation is at object construction time
|
|
134
|
-
expect(result.ok()).toBe(true);
|
|
135
|
-
if (result.ok()) {
|
|
136
|
-
expect(result.value).toContain('path:');
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should serializes Invalid Date as SERIALIZE_FAILED', () => {
|
|
141
|
-
const memory: OutputMemory = {
|
|
142
|
-
path: 'test/path',
|
|
143
|
-
metadata: {
|
|
144
|
-
createdAt: new Date('invalid date'),
|
|
145
|
-
tags: [],
|
|
146
|
-
},
|
|
147
|
-
content: 'Content',
|
|
148
|
-
};
|
|
149
|
-
const result = toonSerialize('memory', memory);
|
|
150
|
-
|
|
151
|
-
// Invalid Date causes serialization to fail
|
|
152
|
-
expect(result.ok()).toBe(false);
|
|
153
|
-
if (!result.ok()) {
|
|
154
|
-
expect(result.error.code).toBe('SERIALIZE_FAILED');
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe('serializeCategoryToon', () => {
|
|
160
|
-
it('should encodes category with memories and subcategories', () => {
|
|
161
|
-
const result = toonSerialize('category', sampleCategory);
|
|
162
|
-
|
|
163
|
-
expect(result.ok()).toBe(true);
|
|
164
|
-
if (result.ok()) {
|
|
165
|
-
expect(result.value).toContain('path: global/persona');
|
|
166
|
-
// Memories array with TOON array format
|
|
167
|
-
expect(result.value).toContain('memories[2');
|
|
168
|
-
// Subcategories use tabular format
|
|
169
|
-
expect(result.value).toContain('subcategories[1');
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should uses tabular format for uniform memories array', () => {
|
|
174
|
-
// Create a uniform array (all items have same keys)
|
|
175
|
-
const category: OutputCategory = {
|
|
176
|
-
path: 'test/uniform-memories',
|
|
177
|
-
memories: [
|
|
178
|
-
{ path: 'test/mem1', tokenEstimate: 10 },
|
|
179
|
-
{ path: 'test/mem2', tokenEstimate: 20 },
|
|
180
|
-
],
|
|
181
|
-
subcategories: [],
|
|
182
|
-
};
|
|
183
|
-
const result = toonSerialize('category', category);
|
|
184
|
-
|
|
185
|
-
expect(result.ok()).toBe(true);
|
|
186
|
-
if (result.ok()) {
|
|
187
|
-
// Tabular format with tab-separated header
|
|
188
|
-
expect(result.value).toMatch(/memories\[2\t\]\{path\ttokenEstimate\}:/);
|
|
189
|
-
// Should contain memory paths in the rows
|
|
190
|
-
expect(result.value).toContain('test/mem1');
|
|
191
|
-
expect(result.value).toContain('test/mem2');
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should uses tabular format for subcategories array', () => {
|
|
196
|
-
const result = toonSerialize('category', sampleCategory);
|
|
197
|
-
|
|
198
|
-
expect(result.ok()).toBe(true);
|
|
199
|
-
if (result.ok()) {
|
|
200
|
-
// Tabular format for subcategories with tab-separated format
|
|
201
|
-
expect(result.value).toMatch(/subcategories\[1\t\]\{path\tmemoryCount\}:/);
|
|
202
|
-
expect(result.value).toContain('global/persona/sub1');
|
|
203
|
-
expect(result.value).toContain('5'); // memory count
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('should handles empty memories array', () => {
|
|
208
|
-
const category: OutputCategory = {
|
|
209
|
-
path: 'test/empty-memories',
|
|
210
|
-
memories: [],
|
|
211
|
-
subcategories: [{ path: 'test/sub', memoryCount: 3 }],
|
|
212
|
-
};
|
|
213
|
-
const result = toonSerialize('category', category);
|
|
214
|
-
|
|
215
|
-
expect(result.ok()).toBe(true);
|
|
216
|
-
if (result.ok()) {
|
|
217
|
-
// Empty arrays in TOON format show count of 0
|
|
218
|
-
expect(result.value).toContain('memories[0');
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('should handles empty subcategories array', () => {
|
|
223
|
-
const category: OutputCategory = {
|
|
224
|
-
path: 'test/empty-subcategories',
|
|
225
|
-
memories: [{ path: 'test/mem1', tokenEstimate: 10 }],
|
|
226
|
-
subcategories: [],
|
|
227
|
-
};
|
|
228
|
-
const result = toonSerialize('category', category);
|
|
229
|
-
|
|
230
|
-
expect(result.ok()).toBe(true);
|
|
231
|
-
if (result.ok()) {
|
|
232
|
-
// Empty arrays in TOON format show count of 0
|
|
233
|
-
expect(result.value).toContain('subcategories[0');
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('should serializes empty path without error (no validation)', () => {
|
|
238
|
-
// The output module does NOT validate - it just serializes
|
|
239
|
-
const category: OutputCategory = {
|
|
240
|
-
path: '', // Empty path - serializes as-is
|
|
241
|
-
memories: [],
|
|
242
|
-
subcategories: [],
|
|
243
|
-
};
|
|
244
|
-
const result = toonSerialize('category', category);
|
|
245
|
-
|
|
246
|
-
// Serialization succeeds - validation is at object construction time
|
|
247
|
-
expect(result.ok()).toBe(true);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe('serializeStoreToon', () => {
|
|
252
|
-
it('should encodes store with name and path', () => {
|
|
253
|
-
const store: OutputStore = {
|
|
254
|
-
name: 'my-store',
|
|
255
|
-
path: '/data/my-store',
|
|
256
|
-
};
|
|
257
|
-
const result = toonSerialize('store', store);
|
|
258
|
-
|
|
259
|
-
expect(result.ok()).toBe(true);
|
|
260
|
-
if (result.ok()) {
|
|
261
|
-
// TOON outputs YAML-like format with spaces after colons
|
|
262
|
-
expect(result.value).toContain('name: my-store');
|
|
263
|
-
expect(result.value).toContain('path: /data/my-store');
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it('should serializes invalid store name without error (no validation)', () => {
|
|
268
|
-
// The output module does NOT validate - it just serializes
|
|
269
|
-
const store: OutputStore = {
|
|
270
|
-
name: 'Invalid Name!', // Not lowercase slug - serializes as-is
|
|
271
|
-
path: '/data/store',
|
|
272
|
-
};
|
|
273
|
-
const result = toonSerialize('store', store);
|
|
274
|
-
|
|
275
|
-
// Serialization succeeds - validation is at object construction time
|
|
276
|
-
expect(result.ok()).toBe(true);
|
|
277
|
-
if (result.ok()) {
|
|
278
|
-
expect(result.value).toContain('name: Invalid Name!');
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('should serializes whitespace store path without error (no validation)', () => {
|
|
283
|
-
// The output module does NOT validate - it just serializes
|
|
284
|
-
const store: OutputStore = {
|
|
285
|
-
name: 'valid-name',
|
|
286
|
-
path: ' ', // Whitespace path - serializes as-is
|
|
287
|
-
};
|
|
288
|
-
const result = toonSerialize('store', store);
|
|
289
|
-
|
|
290
|
-
// Serialization succeeds - validation is at object construction time
|
|
291
|
-
expect(result.ok()).toBe(true);
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
describe('serializeStoreRegistryToon', () => {
|
|
296
|
-
it('should encodes registry with multiple stores', () => {
|
|
297
|
-
const registry: OutputStoreRegistry = {
|
|
298
|
-
stores: [
|
|
299
|
-
{ name: 'store-one', path: '/data/one' },
|
|
300
|
-
{ name: 'store-two', path: '/data/two' },
|
|
301
|
-
],
|
|
302
|
-
};
|
|
303
|
-
const result = toonSerialize('store-registry', registry);
|
|
304
|
-
|
|
305
|
-
expect(result.ok()).toBe(true);
|
|
306
|
-
if (result.ok()) {
|
|
307
|
-
expect(result.value).toContain('stores[2');
|
|
308
|
-
expect(result.value).toContain('store-one');
|
|
309
|
-
expect(result.value).toContain('store-two');
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('should uses tabular format for stores array', () => {
|
|
314
|
-
const registry: OutputStoreRegistry = {
|
|
315
|
-
stores: [
|
|
316
|
-
{ name: 'alpha', path: '/a' },
|
|
317
|
-
{ name: 'beta', path: '/b' },
|
|
318
|
-
],
|
|
319
|
-
};
|
|
320
|
-
const result = toonSerialize('store-registry', registry);
|
|
321
|
-
|
|
322
|
-
expect(result.ok()).toBe(true);
|
|
323
|
-
if (result.ok()) {
|
|
324
|
-
// Tabular format: header with count and field names (tab-separated)
|
|
325
|
-
expect(result.value).toMatch(/stores\[2\t\]\{name\tpath\}:/);
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('should handles empty stores array', () => {
|
|
330
|
-
const registry: OutputStoreRegistry = {
|
|
331
|
-
stores: [],
|
|
332
|
-
};
|
|
333
|
-
const result = toonSerialize('store-registry', registry);
|
|
334
|
-
|
|
335
|
-
expect(result.ok()).toBe(true);
|
|
336
|
-
if (result.ok()) {
|
|
337
|
-
// Empty arrays in TOON format show count of 0
|
|
338
|
-
expect(result.value).toContain('stores[0');
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('should serializes invalid store in array without error (no validation)', () => {
|
|
343
|
-
// The output module does NOT validate - it just serializes
|
|
344
|
-
const registry: OutputStoreRegistry = {
|
|
345
|
-
stores: [
|
|
346
|
-
{ name: 'valid-store', path: '/valid' },
|
|
347
|
-
{ name: 'INVALID STORE', path: '/invalid' }, // Invalid name - serializes as-is
|
|
348
|
-
],
|
|
349
|
-
};
|
|
350
|
-
const result = toonSerialize('store-registry', registry);
|
|
351
|
-
|
|
352
|
-
// Serialization succeeds - validation is at object construction time
|
|
353
|
-
expect(result.ok()).toBe(true);
|
|
354
|
-
if (result.ok()) {
|
|
355
|
-
expect(result.value).toContain('INVALID STORE');
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
describe('serializeStoreInitToon', () => {
|
|
361
|
-
it('should encodes store init with path and name', () => {
|
|
362
|
-
const storeInit: OutputStoreInit = {
|
|
363
|
-
path: '/initialized/store/path',
|
|
364
|
-
name: 'my-store',
|
|
365
|
-
};
|
|
366
|
-
const result = toonSerialize('store-init', storeInit);
|
|
367
|
-
|
|
368
|
-
expect(result.ok()).toBe(true);
|
|
369
|
-
if (result.ok()) {
|
|
370
|
-
// TOON outputs YAML-like format with spaces after colons
|
|
371
|
-
expect(result.value).toContain('path: /initialized/store/path');
|
|
372
|
-
expect(result.value).toContain('name: my-store');
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it('should serializes empty path without error (no validation)', () => {
|
|
377
|
-
// The output module does NOT validate - it just serializes
|
|
378
|
-
const storeInit: OutputStoreInit = {
|
|
379
|
-
path: '',
|
|
380
|
-
name: 'my-store',
|
|
381
|
-
};
|
|
382
|
-
const result = toonSerialize('store-init', storeInit);
|
|
383
|
-
|
|
384
|
-
// Serialization succeeds - validation is at object construction time
|
|
385
|
-
expect(result.ok()).toBe(true);
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
describe('serializeInitToon', () => {
|
|
390
|
-
it('should encodes init with path and categories', () => {
|
|
391
|
-
const init: OutputInit = {
|
|
392
|
-
path: '/project/root',
|
|
393
|
-
categories: [
|
|
394
|
-
'persona',
|
|
395
|
-
'project',
|
|
396
|
-
'domain',
|
|
397
|
-
],
|
|
398
|
-
};
|
|
399
|
-
const result = toonSerialize('init', init);
|
|
400
|
-
|
|
401
|
-
expect(result.ok()).toBe(true);
|
|
402
|
-
if (result.ok()) {
|
|
403
|
-
// TOON outputs YAML-like format with spaces after colons
|
|
404
|
-
expect(result.value).toContain('path: /project/root');
|
|
405
|
-
expect(result.value).toContain('categories[3');
|
|
406
|
-
// Categories array
|
|
407
|
-
expect(result.value).toContain('persona');
|
|
408
|
-
expect(result.value).toContain('project');
|
|
409
|
-
expect(result.value).toContain('domain');
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it('should handles empty categories array', () => {
|
|
414
|
-
const init: OutputInit = {
|
|
415
|
-
path: '/project/empty',
|
|
416
|
-
categories: [],
|
|
417
|
-
};
|
|
418
|
-
const result = toonSerialize('init', init);
|
|
419
|
-
|
|
420
|
-
expect(result.ok()).toBe(true);
|
|
421
|
-
if (result.ok()) {
|
|
422
|
-
expect(result.value).toContain('path: /project/empty');
|
|
423
|
-
// Empty arrays in TOON format show count of 0
|
|
424
|
-
expect(result.value).toContain('categories[0');
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('should serializes whitespace path without error (no validation)', () => {
|
|
429
|
-
// The output module does NOT validate - it just serializes
|
|
430
|
-
const init: OutputInit = {
|
|
431
|
-
path: ' ',
|
|
432
|
-
categories: ['test'],
|
|
433
|
-
};
|
|
434
|
-
const result = toonSerialize('init', init);
|
|
435
|
-
|
|
436
|
-
// Serialization succeeds - validation is at object construction time
|
|
437
|
-
expect(result.ok()).toBe(true);
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
describe('TOON edge cases', () => {
|
|
442
|
-
it('should handles special characters in content', () => {
|
|
443
|
-
const memory: OutputMemory = {
|
|
444
|
-
path: 'test/special-chars',
|
|
445
|
-
metadata: {
|
|
446
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
447
|
-
tags: [],
|
|
448
|
-
},
|
|
449
|
-
content: 'Content with "quotes" and : colons',
|
|
450
|
-
};
|
|
451
|
-
const result = toonSerialize('memory', memory);
|
|
452
|
-
|
|
453
|
-
expect(result.ok()).toBe(true);
|
|
454
|
-
if (result.ok()) {
|
|
455
|
-
// Content with special chars should be quoted
|
|
456
|
-
expect(result.value).toContain('"Content with \\"quotes\\" and : colons"');
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
it('should handles tab characters in strings', () => {
|
|
461
|
-
const memory: OutputMemory = {
|
|
462
|
-
path: 'test/tabs',
|
|
463
|
-
metadata: {
|
|
464
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
465
|
-
tags: [],
|
|
466
|
-
},
|
|
467
|
-
content: 'Column1\tColumn2\tColumn3',
|
|
468
|
-
};
|
|
469
|
-
const result = toonSerialize('memory', memory);
|
|
470
|
-
|
|
471
|
-
expect(result.ok()).toBe(true);
|
|
472
|
-
if (result.ok()) {
|
|
473
|
-
// Tab characters should be quoted/escaped
|
|
474
|
-
expect(result.value).toContain('"Column1\\tColumn2\\tColumn3"');
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
it('should handles newlines in strings', () => {
|
|
479
|
-
const memory: OutputMemory = {
|
|
480
|
-
path: 'test/newlines',
|
|
481
|
-
metadata: {
|
|
482
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
483
|
-
tags: [],
|
|
484
|
-
},
|
|
485
|
-
content: 'First line\r\nSecond line',
|
|
486
|
-
};
|
|
487
|
-
const result = toonSerialize('memory', memory);
|
|
488
|
-
|
|
489
|
-
expect(result.ok()).toBe(true);
|
|
490
|
-
if (result.ok()) {
|
|
491
|
-
// Newlines should be quoted/escaped
|
|
492
|
-
expect(result.value).toContain('\\r\\n');
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it('should handles empty strings', () => {
|
|
497
|
-
const memory: OutputMemory = {
|
|
498
|
-
path: 'test/empty-content',
|
|
499
|
-
metadata: {
|
|
500
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
501
|
-
tags: [],
|
|
502
|
-
},
|
|
503
|
-
content: '',
|
|
504
|
-
};
|
|
505
|
-
const result = toonSerialize('memory', memory);
|
|
506
|
-
|
|
507
|
-
expect(result.ok()).toBe(true);
|
|
508
|
-
if (result.ok()) {
|
|
509
|
-
// Empty content should be represented
|
|
510
|
-
expect(result.value).toContain('content:');
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
it('should handles unicode characters', () => {
|
|
515
|
-
const memory: OutputMemory = {
|
|
516
|
-
path: 'test/unicode',
|
|
517
|
-
metadata: {
|
|
518
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
519
|
-
tags: [
|
|
520
|
-
'emoji', 'unicode',
|
|
521
|
-
],
|
|
522
|
-
},
|
|
523
|
-
content: 'Hello, world! Bonjour! Hola!',
|
|
524
|
-
};
|
|
525
|
-
const result = toonSerialize('memory', memory);
|
|
526
|
-
|
|
527
|
-
expect(result.ok()).toBe(true);
|
|
528
|
-
if (result.ok()) {
|
|
529
|
-
expect(result.value).toContain('Hello, world! Bonjour! Hola!');
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it('should handles memory with expiresAt field', () => {
|
|
534
|
-
const memory: OutputMemory = {
|
|
535
|
-
path: 'test/expires',
|
|
536
|
-
metadata: {
|
|
537
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
538
|
-
tags: [],
|
|
539
|
-
expiresAt: new Date('2025-01-15T10:00:00Z'),
|
|
540
|
-
},
|
|
541
|
-
content: 'Expiring content',
|
|
542
|
-
};
|
|
543
|
-
const result = toonSerialize('memory', memory);
|
|
544
|
-
|
|
545
|
-
expect(result.ok()).toBe(true);
|
|
546
|
-
if (result.ok()) {
|
|
547
|
-
// Timestamps are quoted, nested under metadata (not dotted)
|
|
548
|
-
expect(result.value).toContain('expiresAt: "2025-01-15T10:00:00.000Z"');
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
it('should handles category memory with summary containing special characters', () => {
|
|
553
|
-
const category: OutputCategory = {
|
|
554
|
-
path: 'test/special-summary',
|
|
555
|
-
memories: [{
|
|
556
|
-
path: 'test/mem1',
|
|
557
|
-
tokenEstimate: 10,
|
|
558
|
-
summary: 'Summary with "quotes" and: colons',
|
|
559
|
-
}],
|
|
560
|
-
subcategories: [],
|
|
561
|
-
};
|
|
562
|
-
const result = toonSerialize('category', category);
|
|
563
|
-
|
|
564
|
-
expect(result.ok()).toBe(true);
|
|
565
|
-
if (result.ok()) {
|
|
566
|
-
// Summary with special chars should be quoted
|
|
567
|
-
expect(result.value).toContain('Summary with');
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
it('should handles tags array in memory', () => {
|
|
572
|
-
const memory: OutputMemory = {
|
|
573
|
-
path: 'test/tags',
|
|
574
|
-
metadata: {
|
|
575
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
576
|
-
tags: [
|
|
577
|
-
'tag1',
|
|
578
|
-
'tag-two',
|
|
579
|
-
'tag_three',
|
|
580
|
-
],
|
|
581
|
-
},
|
|
582
|
-
content: 'Tagged content',
|
|
583
|
-
};
|
|
584
|
-
const result = toonSerialize('memory', memory);
|
|
585
|
-
|
|
586
|
-
expect(result.ok()).toBe(true);
|
|
587
|
-
if (result.ok()) {
|
|
588
|
-
// Nested tags under metadata
|
|
589
|
-
expect(result.value).toContain('tags[3');
|
|
590
|
-
expect(result.value).toContain('tag1');
|
|
591
|
-
expect(result.value).toContain('tag-two');
|
|
592
|
-
expect(result.value).toContain('tag_three');
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
it('should handles empty tags array', () => {
|
|
597
|
-
const memory: OutputMemory = {
|
|
598
|
-
path: 'test/no-tags',
|
|
599
|
-
metadata: {
|
|
600
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
601
|
-
tags: [],
|
|
602
|
-
},
|
|
603
|
-
content: 'No tags content',
|
|
604
|
-
};
|
|
605
|
-
const result = toonSerialize('memory', memory);
|
|
606
|
-
|
|
607
|
-
expect(result.ok()).toBe(true);
|
|
608
|
-
if (result.ok()) {
|
|
609
|
-
// Empty tags array in TOON format shows count of 0
|
|
610
|
-
expect(result.value).toContain('tags[0');
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
it('should handles large token estimate values', () => {
|
|
615
|
-
const memory: OutputMemory = {
|
|
616
|
-
path: 'test/large-tokens',
|
|
617
|
-
metadata: {
|
|
618
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
619
|
-
tags: [],
|
|
620
|
-
tokenEstimate: 999999,
|
|
621
|
-
},
|
|
622
|
-
content: 'Large token estimate',
|
|
623
|
-
};
|
|
624
|
-
const result = toonSerialize('memory', memory);
|
|
625
|
-
|
|
626
|
-
expect(result.ok()).toBe(true);
|
|
627
|
-
if (result.ok()) {
|
|
628
|
-
// Nested under metadata (not dotted)
|
|
629
|
-
expect(result.value).toContain('tokenEstimate: 999999');
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
it('should serializes negative token estimate without error (no validation)', () => {
|
|
634
|
-
// The output module does NOT validate - it just serializes
|
|
635
|
-
const memory: OutputMemory = {
|
|
636
|
-
path: 'test/negative-tokens',
|
|
637
|
-
metadata: {
|
|
638
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
639
|
-
tags: [],
|
|
640
|
-
tokenEstimate: -5,
|
|
641
|
-
},
|
|
642
|
-
content: 'Invalid token estimate',
|
|
643
|
-
};
|
|
644
|
-
const result = toonSerialize('memory', memory);
|
|
645
|
-
|
|
646
|
-
// Serialization succeeds - validation is at object construction time
|
|
647
|
-
expect(result.ok()).toBe(true);
|
|
648
|
-
if (result.ok()) {
|
|
649
|
-
expect(result.value).toContain('-5');
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
it('should serializes Infinity token estimate without error (no validation)', () => {
|
|
654
|
-
// The output module does NOT validate - it just serializes
|
|
655
|
-
const memory: OutputMemory = {
|
|
656
|
-
path: 'test/infinite-tokens',
|
|
657
|
-
metadata: {
|
|
658
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
659
|
-
tags: [],
|
|
660
|
-
tokenEstimate: Number.POSITIVE_INFINITY,
|
|
661
|
-
},
|
|
662
|
-
content: 'Infinite token estimate',
|
|
663
|
-
};
|
|
664
|
-
const result = toonSerialize('memory', memory);
|
|
665
|
-
|
|
666
|
-
// Serialization succeeds - validation is at object construction time
|
|
667
|
-
expect(result.ok()).toBe(true);
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
it('should serializes invalid updatedAt date as SERIALIZE_FAILED', () => {
|
|
671
|
-
const memory: OutputMemory = {
|
|
672
|
-
path: 'test/invalid-updated',
|
|
673
|
-
metadata: {
|
|
674
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
675
|
-
updatedAt: new Date('not a date'),
|
|
676
|
-
tags: [],
|
|
677
|
-
},
|
|
678
|
-
content: 'Invalid updated date',
|
|
679
|
-
};
|
|
680
|
-
const result = toonSerialize('memory', memory);
|
|
681
|
-
|
|
682
|
-
// Invalid Date causes serialization to fail
|
|
683
|
-
expect(result.ok()).toBe(false);
|
|
684
|
-
if (!result.ok()) {
|
|
685
|
-
expect(result.error.code).toBe('SERIALIZE_FAILED');
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
it('should serializes invalid expiresAt date as SERIALIZE_FAILED', () => {
|
|
690
|
-
const memory: OutputMemory = {
|
|
691
|
-
path: 'test/invalid-expires',
|
|
692
|
-
metadata: {
|
|
693
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
694
|
-
tags: [],
|
|
695
|
-
expiresAt: new Date('invalid'),
|
|
696
|
-
},
|
|
697
|
-
content: 'Invalid expires date',
|
|
698
|
-
};
|
|
699
|
-
const result = toonSerialize('memory', memory);
|
|
700
|
-
|
|
701
|
-
// Invalid Date causes serialization to fail
|
|
702
|
-
expect(result.ok()).toBe(false);
|
|
703
|
-
if (!result.ok()) {
|
|
704
|
-
expect(result.error.code).toBe('SERIALIZE_FAILED');
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
it('should handles subcategory with zero memory count', () => {
|
|
709
|
-
const category: OutputCategory = {
|
|
710
|
-
path: 'test/zero-count',
|
|
711
|
-
memories: [],
|
|
712
|
-
subcategories: [{ path: 'test/empty-sub', memoryCount: 0 }],
|
|
713
|
-
};
|
|
714
|
-
const result = toonSerialize('category', category);
|
|
715
|
-
|
|
716
|
-
expect(result.ok()).toBe(true);
|
|
717
|
-
if (result.ok()) {
|
|
718
|
-
expect(result.value).toContain('test/empty-sub');
|
|
719
|
-
expect(result.value).toContain('0');
|
|
720
|
-
}
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
it('should serializes negative memory count without error (no validation)', () => {
|
|
724
|
-
// The output module does NOT validate - it just serializes
|
|
725
|
-
const category: OutputCategory = {
|
|
726
|
-
path: 'test/negative-count',
|
|
727
|
-
memories: [],
|
|
728
|
-
subcategories: [{ path: 'test/invalid-sub', memoryCount: -1 }],
|
|
729
|
-
};
|
|
730
|
-
const result = toonSerialize('category', category);
|
|
731
|
-
|
|
732
|
-
// Serialization succeeds - validation is at object construction time
|
|
733
|
-
expect(result.ok()).toBe(true);
|
|
734
|
-
if (result.ok()) {
|
|
735
|
-
expect(result.value).toContain('-1');
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
it('should serializes whitespace memory path in category without error (no validation)', () => {
|
|
740
|
-
// The output module does NOT validate - it just serializes
|
|
741
|
-
const category: OutputCategory = {
|
|
742
|
-
path: 'test/valid-path',
|
|
743
|
-
memories: [{ path: ' ', tokenEstimate: 10 }], // Whitespace path - serializes as-is
|
|
744
|
-
subcategories: [],
|
|
745
|
-
};
|
|
746
|
-
const result = toonSerialize('category', category);
|
|
747
|
-
|
|
748
|
-
// Serialization succeeds - validation is at object construction time
|
|
749
|
-
expect(result.ok()).toBe(true);
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
it('should serializes empty subcategory path without error (no validation)', () => {
|
|
753
|
-
// The output module does NOT validate - it just serializes
|
|
754
|
-
const category: OutputCategory = {
|
|
755
|
-
path: 'test/valid-path',
|
|
756
|
-
memories: [],
|
|
757
|
-
subcategories: [{ path: '', memoryCount: 5 }], // Empty path - serializes as-is
|
|
758
|
-
};
|
|
759
|
-
const result = toonSerialize('category', category);
|
|
760
|
-
|
|
761
|
-
// Serialization succeeds - validation is at object construction time
|
|
762
|
-
expect(result.ok()).toBe(true);
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
it('should serializes negative token estimate in category memory without error (no validation)', () => {
|
|
766
|
-
// The output module does NOT validate - it just serializes
|
|
767
|
-
const category: OutputCategory = {
|
|
768
|
-
path: 'test/valid-path',
|
|
769
|
-
memories: [{ path: 'test/mem', tokenEstimate: -10 }],
|
|
770
|
-
subcategories: [],
|
|
771
|
-
};
|
|
772
|
-
const result = toonSerialize('category', category);
|
|
773
|
-
|
|
774
|
-
// Serialization succeeds - validation is at object construction time
|
|
775
|
-
expect(result.ok()).toBe(true);
|
|
776
|
-
if (result.ok()) {
|
|
777
|
-
expect(result.value).toContain('-10');
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
describe('TOON format output structure', () => {
|
|
783
|
-
it('should output uses newlines between top-level key-value pairs', () => {
|
|
784
|
-
const store: OutputStore = {
|
|
785
|
-
name: 'test-store',
|
|
786
|
-
path: '/test/path',
|
|
787
|
-
};
|
|
788
|
-
const result = toonSerialize('store', store);
|
|
789
|
-
|
|
790
|
-
expect(result.ok()).toBe(true);
|
|
791
|
-
if (result.ok()) {
|
|
792
|
-
// Newline between fields for YAML-like output
|
|
793
|
-
expect(result.value).toContain('\n');
|
|
794
|
-
expect(result.value).toContain('name: test-store');
|
|
795
|
-
expect(result.value).toContain('path: /test/path');
|
|
796
|
-
}
|
|
797
|
-
});
|
|
798
|
-
|
|
799
|
-
it('should output uses nested structure for metadata (not key folding)', () => {
|
|
800
|
-
const memory: OutputMemory = {
|
|
801
|
-
path: 'test/folding',
|
|
802
|
-
metadata: {
|
|
803
|
-
createdAt: new Date('2024-01-15T10:00:00Z'),
|
|
804
|
-
tags: ['test'],
|
|
805
|
-
},
|
|
806
|
-
content: 'Test folding',
|
|
807
|
-
};
|
|
808
|
-
const result = toonSerialize('memory', memory);
|
|
809
|
-
|
|
810
|
-
expect(result.ok()).toBe(true);
|
|
811
|
-
if (result.ok()) {
|
|
812
|
-
// TOON outputs nested YAML-like format, not inline object braces
|
|
813
|
-
expect(result.value).not.toMatch(/metadata:\{/);
|
|
814
|
-
// Should have nested indented structure
|
|
815
|
-
expect(result.value).toContain('metadata:');
|
|
816
|
-
}
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
it('should tabular format uses correct header syntax with tab separators', () => {
|
|
820
|
-
const registry: OutputStoreRegistry = {
|
|
821
|
-
stores: [
|
|
822
|
-
{ name: 'a', path: '/a' },
|
|
823
|
-
{ name: 'b', path: '/b' },
|
|
824
|
-
{ name: 'c', path: '/c' },
|
|
825
|
-
],
|
|
826
|
-
};
|
|
827
|
-
const result = toonSerialize('store-registry', registry);
|
|
828
|
-
|
|
829
|
-
expect(result.ok()).toBe(true);
|
|
830
|
-
if (result.ok()) {
|
|
831
|
-
// Header format with tab-separated count: key[count\t]{fields}:
|
|
832
|
-
expect(result.value).toMatch(/stores\[3\t\]\{name\tpath\}:/);
|
|
833
|
-
}
|
|
834
|
-
});
|
|
835
|
-
});
|