kimaki 0.4.33 → 0.4.35
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/cli.js +10 -1
- package/dist/commands/session.js +55 -0
- package/dist/logger.js +13 -6
- package/dist/opencode.js +1 -2
- package/dist/session-handler.js +53 -15
- package/dist/system-message.js +13 -0
- package/dist/unnest-code-blocks.js +10 -3
- package/dist/unnest-code-blocks.test.js +231 -12
- package/package.json +5 -6
- package/src/cli.ts +11 -1
- package/src/commands/session.ts +68 -0
- package/src/logger.ts +20 -12
- package/src/opencode.ts +1 -2
- package/src/session-handler.ts +61 -19
- package/src/system-message.ts +13 -0
- package/src/unnest-code-blocks.test.ts +242 -12
- package/src/unnest-code-blocks.ts +10 -3
|
@@ -12,8 +12,7 @@ test('basic - single item with code block', () => {
|
|
|
12
12
|
|
|
13
13
|
\`\`\`js
|
|
14
14
|
const x = 1
|
|
15
|
-
\`\`\`
|
|
16
|
-
"
|
|
15
|
+
\`\`\`"
|
|
17
16
|
`)
|
|
18
17
|
})
|
|
19
18
|
|
|
@@ -53,8 +52,7 @@ test('multiple code blocks in one item', () => {
|
|
|
53
52
|
\`\`\`
|
|
54
53
|
\`\`\`python
|
|
55
54
|
b = 2
|
|
56
|
-
\`\`\`
|
|
57
|
-
"
|
|
55
|
+
\`\`\`"
|
|
58
56
|
`)
|
|
59
57
|
})
|
|
60
58
|
|
|
@@ -132,8 +130,7 @@ test('mixed - some items have code, some dont', () => {
|
|
|
132
130
|
|
|
133
131
|
\`\`\`python
|
|
134
132
|
y = 2
|
|
135
|
-
\`\`\`
|
|
136
|
-
"
|
|
133
|
+
\`\`\`"
|
|
137
134
|
`)
|
|
138
135
|
})
|
|
139
136
|
|
|
@@ -150,8 +147,7 @@ test('text before and after code in same item', () => {
|
|
|
150
147
|
\`\`\`js
|
|
151
148
|
const x = 1
|
|
152
149
|
\`\`\`
|
|
153
|
-
- End text
|
|
154
|
-
"
|
|
150
|
+
- End text"
|
|
155
151
|
`)
|
|
156
152
|
})
|
|
157
153
|
|
|
@@ -178,7 +174,6 @@ More text after.`
|
|
|
178
174
|
const x = 1
|
|
179
175
|
\`\`\`
|
|
180
176
|
|
|
181
|
-
|
|
182
177
|
More text after."
|
|
183
178
|
`)
|
|
184
179
|
})
|
|
@@ -206,8 +201,7 @@ test('handles code block without language', () => {
|
|
|
206
201
|
|
|
207
202
|
\`\`\`
|
|
208
203
|
plain code
|
|
209
|
-
\`\`\`
|
|
210
|
-
"
|
|
204
|
+
\`\`\`"
|
|
211
205
|
`)
|
|
212
206
|
})
|
|
213
207
|
|
|
@@ -219,7 +213,243 @@ test('handles empty list item with code', () => {
|
|
|
219
213
|
expect(result).toMatchInlineSnapshot(`
|
|
220
214
|
"\`\`\`js
|
|
221
215
|
const x = 1
|
|
216
|
+
\`\`\`"
|
|
217
|
+
`)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('numbered list with text after code block', () => {
|
|
221
|
+
const input = `1. First item
|
|
222
|
+
\`\`\`js
|
|
223
|
+
const a = 1
|
|
224
|
+
\`\`\`
|
|
225
|
+
Text after the code
|
|
226
|
+
2. Second item`
|
|
227
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
228
|
+
expect(result).toMatchInlineSnapshot(`
|
|
229
|
+
"1. First item
|
|
230
|
+
|
|
231
|
+
\`\`\`js
|
|
232
|
+
const a = 1
|
|
233
|
+
\`\`\`
|
|
234
|
+
- Text after the code
|
|
235
|
+
2. Second item"
|
|
236
|
+
`)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('numbered list with multiple code blocks and text between', () => {
|
|
240
|
+
const input = `1. First item
|
|
241
|
+
\`\`\`js
|
|
242
|
+
const a = 1
|
|
243
|
+
\`\`\`
|
|
244
|
+
Middle text
|
|
245
|
+
\`\`\`python
|
|
246
|
+
b = 2
|
|
247
|
+
\`\`\`
|
|
248
|
+
Final text
|
|
249
|
+
2. Second item`
|
|
250
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
251
|
+
expect(result).toMatchInlineSnapshot(`
|
|
252
|
+
"1. First item
|
|
253
|
+
|
|
254
|
+
\`\`\`js
|
|
255
|
+
const a = 1
|
|
256
|
+
\`\`\`
|
|
257
|
+
- Middle text
|
|
258
|
+
|
|
259
|
+
\`\`\`python
|
|
260
|
+
b = 2
|
|
261
|
+
\`\`\`
|
|
262
|
+
- Final text
|
|
263
|
+
2. Second item"
|
|
264
|
+
`)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('unordered list with multiple code blocks and text between', () => {
|
|
268
|
+
const input = `- First item
|
|
269
|
+
\`\`\`js
|
|
270
|
+
const a = 1
|
|
271
|
+
\`\`\`
|
|
272
|
+
Middle text
|
|
273
|
+
\`\`\`python
|
|
274
|
+
b = 2
|
|
275
|
+
\`\`\`
|
|
276
|
+
Final text
|
|
277
|
+
- Second item`
|
|
278
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
279
|
+
expect(result).toMatchInlineSnapshot(`
|
|
280
|
+
"- First item
|
|
281
|
+
|
|
282
|
+
\`\`\`js
|
|
283
|
+
const a = 1
|
|
284
|
+
\`\`\`
|
|
285
|
+
- Middle text
|
|
286
|
+
|
|
287
|
+
\`\`\`python
|
|
288
|
+
b = 2
|
|
289
|
+
\`\`\`
|
|
290
|
+
- Final text
|
|
291
|
+
- Second item"
|
|
292
|
+
`)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('numbered list starting from 5', () => {
|
|
296
|
+
const input = `5. Fifth item
|
|
297
|
+
\`\`\`js
|
|
298
|
+
code
|
|
299
|
+
\`\`\`
|
|
300
|
+
Text after
|
|
301
|
+
6. Sixth item`
|
|
302
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
303
|
+
expect(result).toMatchInlineSnapshot(`
|
|
304
|
+
"5. Fifth item
|
|
305
|
+
|
|
306
|
+
\`\`\`js
|
|
307
|
+
code
|
|
308
|
+
\`\`\`
|
|
309
|
+
- Text after
|
|
310
|
+
6. Sixth item"
|
|
311
|
+
`)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('deeply nested list with code', () => {
|
|
315
|
+
const input = `- Level 1
|
|
316
|
+
- Level 2
|
|
317
|
+
- Level 3
|
|
318
|
+
\`\`\`js
|
|
319
|
+
deep code
|
|
320
|
+
\`\`\`
|
|
321
|
+
Text after deep code
|
|
322
|
+
- Another level 3
|
|
323
|
+
- Back to level 2`
|
|
324
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
325
|
+
expect(result).toMatchInlineSnapshot(`
|
|
326
|
+
"- Level 1
|
|
327
|
+
- Level 2
|
|
328
|
+
- Level 3
|
|
329
|
+
|
|
330
|
+
\`\`\`js
|
|
331
|
+
deep code
|
|
332
|
+
\`\`\`
|
|
333
|
+
- Text after deep code
|
|
334
|
+
- Another level 3
|
|
335
|
+
- Back to level 2"
|
|
336
|
+
`)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
test('nested numbered list inside unordered with code', () => {
|
|
340
|
+
const input = `- Unordered item
|
|
341
|
+
1. Nested numbered
|
|
342
|
+
\`\`\`js
|
|
343
|
+
code
|
|
344
|
+
\`\`\`
|
|
345
|
+
Text after
|
|
346
|
+
2. Second nested
|
|
347
|
+
- Another unordered`
|
|
348
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
349
|
+
expect(result).toMatchInlineSnapshot(`
|
|
350
|
+
"- Unordered item
|
|
351
|
+
1. Nested numbered
|
|
352
|
+
|
|
353
|
+
\`\`\`js
|
|
354
|
+
code
|
|
355
|
+
\`\`\`
|
|
356
|
+
- Text after
|
|
357
|
+
2. Second nested
|
|
358
|
+
- Another unordered"
|
|
359
|
+
`)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
test('code block at end of numbered item no text after', () => {
|
|
363
|
+
const input = `1. First with text
|
|
364
|
+
\`\`\`js
|
|
365
|
+
code here
|
|
366
|
+
\`\`\`
|
|
367
|
+
2. Second item
|
|
368
|
+
3. Third item`
|
|
369
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
370
|
+
expect(result).toMatchInlineSnapshot(`
|
|
371
|
+
"1. First with text
|
|
372
|
+
|
|
373
|
+
\`\`\`js
|
|
374
|
+
code here
|
|
375
|
+
\`\`\`
|
|
376
|
+
2. Second item
|
|
377
|
+
3. Third item"
|
|
378
|
+
`)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
test('multiple items each with code and text after', () => {
|
|
382
|
+
const input = `1. First
|
|
383
|
+
\`\`\`js
|
|
384
|
+
code1
|
|
385
|
+
\`\`\`
|
|
386
|
+
After first
|
|
387
|
+
2. Second
|
|
388
|
+
\`\`\`python
|
|
389
|
+
code2
|
|
390
|
+
\`\`\`
|
|
391
|
+
After second
|
|
392
|
+
3. Third no code`
|
|
393
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
394
|
+
expect(result).toMatchInlineSnapshot(`
|
|
395
|
+
"1. First
|
|
396
|
+
|
|
397
|
+
\`\`\`js
|
|
398
|
+
code1
|
|
399
|
+
\`\`\`
|
|
400
|
+
- After first
|
|
401
|
+
2. Second
|
|
402
|
+
|
|
403
|
+
\`\`\`python
|
|
404
|
+
code2
|
|
405
|
+
\`\`\`
|
|
406
|
+
- After second
|
|
407
|
+
3. Third no code"
|
|
408
|
+
`)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
test('code block immediately after list marker', () => {
|
|
412
|
+
const input = `1. \`\`\`js
|
|
413
|
+
immediate code
|
|
414
|
+
\`\`\`
|
|
415
|
+
2. Normal item`
|
|
416
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
417
|
+
expect(result).toMatchInlineSnapshot(`
|
|
418
|
+
"\`\`\`js
|
|
419
|
+
immediate code
|
|
420
|
+
\`\`\`
|
|
421
|
+
2. Normal item"
|
|
422
|
+
`)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test('code block with filename metadata', () => {
|
|
426
|
+
const input = `- Item with code
|
|
427
|
+
\`\`\`tsx filename=example.tsx
|
|
428
|
+
const x = 1
|
|
429
|
+
\`\`\``
|
|
430
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
431
|
+
expect(result).toMatchInlineSnapshot(`
|
|
432
|
+
"- Item with code
|
|
433
|
+
|
|
434
|
+
\`\`\`tsx filename=example.tsx
|
|
435
|
+
const x = 1
|
|
436
|
+
\`\`\`"
|
|
437
|
+
`)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
test('numbered list with filename metadata code block', () => {
|
|
441
|
+
const input = `1. First item
|
|
442
|
+
\`\`\`tsx filename=app.tsx
|
|
443
|
+
export default function App() {}
|
|
444
|
+
\`\`\`
|
|
445
|
+
2. Second item`
|
|
446
|
+
const result = unnestCodeBlocksFromLists(input)
|
|
447
|
+
expect(result).toMatchInlineSnapshot(`
|
|
448
|
+
"1. First item
|
|
449
|
+
|
|
450
|
+
\`\`\`tsx filename=app.tsx
|
|
451
|
+
export default function App() {}
|
|
222
452
|
\`\`\`
|
|
223
|
-
"
|
|
453
|
+
2. Second item"
|
|
224
454
|
`)
|
|
225
455
|
})
|
|
@@ -41,11 +41,15 @@ function processListToken(list: Tokens.List): Segment[] {
|
|
|
41
41
|
function processListItem(item: Tokens.ListItem, prefix: string): Segment[] {
|
|
42
42
|
const segments: Segment[] = []
|
|
43
43
|
let currentText: string[] = []
|
|
44
|
+
// Track if we've seen a code block - text after code uses continuation prefix
|
|
45
|
+
let seenCodeBlock = false
|
|
44
46
|
|
|
45
47
|
const flushText = (): void => {
|
|
46
48
|
const text = currentText.join('').trim()
|
|
47
49
|
if (text) {
|
|
48
|
-
|
|
50
|
+
// After a code block, use '-' as continuation prefix to avoid repeating numbers
|
|
51
|
+
const effectivePrefix = seenCodeBlock ? '- ' : prefix
|
|
52
|
+
segments.push({ type: 'list-item', prefix: effectivePrefix, content: text })
|
|
49
53
|
}
|
|
50
54
|
currentText = []
|
|
51
55
|
}
|
|
@@ -59,6 +63,7 @@ function processListItem(item: Tokens.ListItem, prefix: string): Segment[] {
|
|
|
59
63
|
type: 'code',
|
|
60
64
|
content: '```' + lang + '\n' + codeToken.text + '\n```\n',
|
|
61
65
|
})
|
|
66
|
+
seenCodeBlock = true
|
|
62
67
|
} else if (token.type === 'list') {
|
|
63
68
|
flushText()
|
|
64
69
|
// Recursively process nested list - segments bubble up
|
|
@@ -118,10 +123,12 @@ function renderSegments(segments: Segment[]): string {
|
|
|
118
123
|
result.push(segment.prefix + segment.content + '\n')
|
|
119
124
|
} else {
|
|
120
125
|
// Raw content (no prefix means it's original raw)
|
|
121
|
-
|
|
126
|
+
// Ensure raw ends with newline for proper separation from next segment
|
|
127
|
+
const raw = segment.content.trimEnd()
|
|
128
|
+
result.push(raw + '\n')
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
return result.join('')
|
|
133
|
+
return result.join('').trimEnd()
|
|
127
134
|
}
|