mini-coder 0.5.10 → 0.5.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mini-coder",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "A small, fast CLI coding agent",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
package/src/agent.ts CHANGED
@@ -623,6 +623,10 @@ function getTodoReminderSignature(
623
623
  );
624
624
  }
625
625
 
626
+ function wrapSystemReminder(content: string): string {
627
+ return `<system_reminder>\n${content}\n</system_reminder>`;
628
+ }
629
+
626
630
  function createTodoReminderMessage(
627
631
  messages: readonly Message[],
628
632
  remindedTodoSignatures: Set<string>,
@@ -651,7 +655,7 @@ function createTodoReminderMessage(
651
655
 
652
656
  return {
653
657
  role: "user",
654
- content: lines.join("\n"),
658
+ content: wrapSystemReminder(lines.join("\n")),
655
659
  timestamp: Date.now(),
656
660
  };
657
661
  }
@@ -2,40 +2,19 @@ import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { mkdtempSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import type { ContainerNode, Node } from "@cel-tui/types";
5
+ import type { ContainerNode } from "@cel-tui/types";
6
6
  import { registerFauxProvider } from "@mariozechner/pi-ai";
7
7
  import type { AppState } from "../index.ts";
8
- import { COMMANDS } from "../input.ts";
9
8
  import {
10
- appendPromptHistory,
11
9
  computeContextTokens,
12
10
  createSession,
13
11
  openDatabase,
14
12
  } from "../session.ts";
15
13
  import { loadSettings } from "../settings.ts";
16
14
  import { DEFAULT_THEME } from "../theme.ts";
17
- import {
18
- createCommandController,
19
- formatPromptHistoryLabel,
20
- formatPromptHistoryPreview,
21
- formatRelativeDate,
22
- formatSessionLabel,
23
- } from "./commands.ts";
15
+ import { createCommandController } from "./commands.ts";
24
16
  import type { ActiveOverlay } from "./overlay.ts";
25
17
 
26
- function collectText(node: Node | null): string[] {
27
- if (!node) {
28
- return [];
29
- }
30
- if (node.type === "text") {
31
- return [node.content];
32
- }
33
- if (node.type === "textinput") {
34
- return [];
35
- }
36
- return node.children.flatMap((child) => collectText(child));
37
- }
38
-
39
18
  function expectOverlay(overlay: ActiveOverlay | null): ActiveOverlay {
40
19
  if (!overlay) {
41
20
  throw new Error("Expected an active overlay");
@@ -107,200 +86,6 @@ afterEach(() => {
107
86
  });
108
87
 
109
88
  describe("ui/commands", () => {
110
- test("showCommandAutocomplete clears the current draft and opens the commands overlay", () => {
111
- const state = createTestState();
112
- const runtimeState = { overlay: null as ActiveOverlay | null };
113
- let inputValue = "draft";
114
- const controller = createCommandController({
115
- openOverlay: (nextOverlay) => {
116
- runtimeState.overlay = nextOverlay;
117
- },
118
- dismissOverlay: () => {
119
- runtimeState.overlay = null;
120
- },
121
- setInputValue: (value) => {
122
- inputValue = value;
123
- },
124
- appendInfoMessage: () => {},
125
- appendTodoMessage: () => {},
126
- scrollConversationToBottom: () => {},
127
- render: () => {},
128
- reloadPromptContext: async () => {},
129
- openInBrowser: () => {},
130
- });
131
-
132
- try {
133
- controller.showCommandAutocomplete(state);
134
-
135
- const overlay = expectOverlay(runtimeState.overlay);
136
-
137
- expect(inputValue).toBe("");
138
- expect(overlay.title).toBe("Commands");
139
-
140
- const text = collectText(overlay.select());
141
- for (const command of COMMANDS) {
142
- expect(text.some((line) => line.includes(`/${command}`))).toBe(true);
143
- }
144
- } finally {
145
- state.db.close();
146
- }
147
- });
148
-
149
- test("showInputHistoryOverlay restores the selected raw prompt", () => {
150
- const state = createTestState();
151
- const runtimeState = { overlay: null as ActiveOverlay | null };
152
- let inputValue = "draft";
153
- const rawPrompt = "first line\nsecond line";
154
- const controller = createCommandController({
155
- openOverlay: (nextOverlay) => {
156
- runtimeState.overlay = nextOverlay;
157
- },
158
- dismissOverlay: () => {
159
- runtimeState.overlay = null;
160
- },
161
- setInputValue: (value) => {
162
- inputValue = value;
163
- },
164
- appendInfoMessage: () => {},
165
- appendTodoMessage: () => {},
166
- scrollConversationToBottom: () => {},
167
- render: () => {},
168
- reloadPromptContext: async () => {},
169
- openInBrowser: () => {},
170
- });
171
-
172
- try {
173
- appendPromptHistory(state.db, {
174
- text: "older prompt",
175
- cwd: "/tmp/older",
176
- });
177
- appendPromptHistory(state.db, { text: rawPrompt, cwd: state.cwd });
178
-
179
- controller.showInputHistoryOverlay(state);
180
-
181
- const overlay = expectOverlay(runtimeState.overlay);
182
-
183
- expect(overlay.title).toBe("Input history");
184
-
185
- const selectNode = renderSelect(overlay);
186
-
187
- selectNode.props.onKeyPress?.("enter");
188
-
189
- expect(runtimeState.overlay).toBeNull();
190
- expect(inputValue).toBe(rawPrompt);
191
- } finally {
192
- state.db.close();
193
- }
194
- });
195
-
196
- test("showInputHistoryOverlay dismissal leaves the current draft unchanged", () => {
197
- const state = createTestState();
198
- const runtimeState = { overlay: null as ActiveOverlay | null };
199
- let inputValue = "draft prompt";
200
- const controller = createCommandController({
201
- openOverlay: (nextOverlay) => {
202
- runtimeState.overlay = nextOverlay;
203
- },
204
- dismissOverlay: () => {
205
- runtimeState.overlay = null;
206
- },
207
- setInputValue: (value) => {
208
- inputValue = value;
209
- },
210
- appendInfoMessage: () => {},
211
- appendTodoMessage: () => {},
212
- scrollConversationToBottom: () => {},
213
- render: () => {},
214
- reloadPromptContext: async () => {},
215
- openInBrowser: () => {},
216
- });
217
-
218
- try {
219
- appendPromptHistory(state.db, {
220
- text: "saved prompt",
221
- cwd: state.cwd,
222
- });
223
-
224
- controller.showInputHistoryOverlay(state);
225
-
226
- const overlay = expectOverlay(runtimeState.overlay);
227
- const selectNode = renderSelect(overlay);
228
-
229
- selectNode.props.onBlur?.();
230
-
231
- expect(runtimeState.overlay).toBeNull();
232
- expect(inputValue).toBe("draft prompt");
233
- } finally {
234
- state.db.close();
235
- }
236
- });
237
-
238
- test("handleCommand('session') overlay includes the first user preview for disambiguation", () => {
239
- const state = createTestState();
240
- const runtimeState = { overlay: null as ActiveOverlay | null };
241
- const controller = createCommandController({
242
- openOverlay: (nextOverlay) => {
243
- runtimeState.overlay = nextOverlay;
244
- },
245
- dismissOverlay: () => {
246
- runtimeState.overlay = null;
247
- },
248
- setInputValue: () => {},
249
- appendInfoMessage: () => {},
250
- appendTodoMessage: () => {},
251
- scrollConversationToBottom: () => {},
252
- render: () => {},
253
- reloadPromptContext: async () => {},
254
- openInBrowser: () => {},
255
- });
256
- const session = createSession(state.db, {
257
- cwd: state.canonicalCwd,
258
- model: "test/beta",
259
- effort: "high",
260
- });
261
-
262
- state.db.run(
263
- "INSERT INTO messages (session_id, turn, data, created_at) VALUES (?, ?, ?, ?)",
264
- [
265
- session.id,
266
- null,
267
- JSON.stringify({
268
- role: "ui",
269
- kind: "info",
270
- content: "Help output",
271
- timestamp: 1,
272
- }),
273
- 1,
274
- ],
275
- );
276
- state.db.run(
277
- "INSERT INTO messages (session_id, turn, data, created_at) VALUES (?, ?, ?, ?)",
278
- [
279
- session.id,
280
- 1,
281
- JSON.stringify({
282
- role: "user",
283
- content: "Investigate\nthis session label",
284
- timestamp: 2,
285
- }),
286
- 2,
287
- ],
288
- );
289
-
290
- try {
291
- expect(controller.handleCommand("session", state)).toBe(true);
292
-
293
- const overlay = expectOverlay(runtimeState.overlay);
294
- const text = collectText(renderSelect(overlay));
295
-
296
- expect(
297
- text.some((line) => line.includes("Investigate this session")),
298
- ).toBe(true);
299
- } finally {
300
- state.db.close();
301
- }
302
- });
303
-
304
89
  test("handleCommand('session') restores the selected session and recomputes stats", () => {
305
90
  const state = createTestState();
306
91
  const runtimeState = { overlay: null as ActiveOverlay | null };
@@ -391,27 +176,6 @@ describe("ui/commands", () => {
391
176
  }
392
177
  });
393
178
 
394
- test("handleCommand returns false for unknown commands", () => {
395
- const state = createTestState();
396
- const controller = createCommandController({
397
- openOverlay: () => {},
398
- dismissOverlay: () => {},
399
- setInputValue: () => {},
400
- appendInfoMessage: () => {},
401
- appendTodoMessage: () => {},
402
- scrollConversationToBottom: () => {},
403
- render: () => {},
404
- reloadPromptContext: async () => {},
405
- openInBrowser: () => {},
406
- });
407
-
408
- try {
409
- expect(controller.handleCommand("unknown", state)).toBe(false);
410
- } finally {
411
- state.db.close();
412
- }
413
- });
414
-
415
179
  test("/new clears the active session state and reloads prompt context", async () => {
416
180
  const state = createTestState();
417
181
  let reloadCount = 0;
@@ -674,55 +438,4 @@ describe("ui/commands", () => {
674
438
  state.db.close();
675
439
  }
676
440
  });
677
-
678
- test("formatRelativeDate accepts an explicit clock for deterministic output", () => {
679
- const now = new Date("2026-04-07T12:00:00Z");
680
-
681
- expect(formatRelativeDate(new Date("2026-04-07T11:59:45Z"), now)).toBe(
682
- "just now",
683
- );
684
- expect(formatRelativeDate(new Date("2026-04-07T11:50:00Z"), now)).toBe(
685
- "10m ago",
686
- );
687
- expect(formatRelativeDate(new Date("2026-04-07T10:00:00Z"), now)).toBe(
688
- "2h ago",
689
- );
690
- expect(formatRelativeDate(new Date("2026-04-04T12:00:00Z"), now)).toBe(
691
- "3d ago",
692
- );
693
- });
694
-
695
- test("formatPromptHistoryPreview collapses whitespace into one line", () => {
696
- expect(formatPromptHistoryPreview(" first\n\n second\tthird ")).toBe(
697
- "first second third",
698
- );
699
- });
700
-
701
- test("formatPromptHistoryLabel_longPromptAndCwd_returnsATruncatedSingleLineLabel", () => {
702
- expect(
703
- formatPromptHistoryLabel(
704
- " Investigate\n\n this prompt history row because it is much too wide for the overlay ",
705
- "/tmp/projects/very/deeply/nested/mini-coder-audit",
706
- "5m ago",
707
- ),
708
- ).toBe(
709
- "Investigate this prompt history… · …/mini-coder-audit · 5m ago",
710
- );
711
- });
712
-
713
- test("formatSessionLabel_longPreviewAndModel_returnsATruncatedReadableLabel", () => {
714
- expect(
715
- formatSessionLabel(
716
- {
717
- model: "openai-codex/gpt-5.4-super-long-variant",
718
- firstUserPreview:
719
- "Audit the session selector because every entry looks identical in real usage",
720
- },
721
- "just now",
722
- true,
723
- ),
724
- ).toBe(
725
- "Audit the session selector… · openai-codex/gpt… · just now · current",
726
- );
727
- });
728
441
  });
@@ -1,49 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { isEmptyUserContent, stripSkillFrontmatter } from "./agent.ts";
3
-
4
- describe("ui/agent", () => {
5
- test("stripSkillFrontmatter removes frontmatter and keeps the skill body", () => {
6
- const content = [
7
- "---",
8
- "name: code-review",
9
- 'description: "Review code for issues"',
10
- "---",
11
- "# Review Checklist",
12
- "- Find bugs",
13
- "",
14
- ].join("\n");
15
-
16
- expect(stripSkillFrontmatter(content)).toBe(
17
- "# Review Checklist\n- Find bugs\n",
18
- );
19
- });
20
-
21
- test("stripSkillFrontmatter leaves content without frontmatter unchanged", () => {
22
- expect(stripSkillFrontmatter("# Skill\nUse this carefully\n")).toBe(
23
- "# Skill\nUse this carefully\n",
24
- );
25
- });
26
-
27
- test("isEmptyUserContent returns true for empty text-only content", () => {
28
- expect(isEmptyUserContent(" \n\t")).toBe(true);
29
- expect(
30
- isEmptyUserContent([
31
- { type: "text", text: " " },
32
- { type: "text", text: "\n" },
33
- ]),
34
- ).toBe(true);
35
- });
36
-
37
- test("isEmptyUserContent returns false when multipart content includes an image", () => {
38
- expect(
39
- isEmptyUserContent([
40
- { type: "text", text: " " },
41
- {
42
- type: "image",
43
- data: Buffer.from("fake-png-data").toString("base64"),
44
- mimeType: "image/png",
45
- },
46
- ]),
47
- ).toBe(false);
48
- });
49
- });