@hypercarrier/rarebit 0.1.0-alpha.1

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.
Files changed (50) hide show
  1. package/AGENTS.md +14 -0
  2. package/CHANGELOG.md +10 -0
  3. package/LICENSE +21 -0
  4. package/README.md +142 -0
  5. package/RUNBOOK.md +36 -0
  6. package/SECURITY.md +11 -0
  7. package/VISUAL-LANGUAGE.md +89 -0
  8. package/bin/rarebit.mjs +20 -0
  9. package/package.json +96 -0
  10. package/schemas/rarebit.schema.json +618 -0
  11. package/scripts/e2e-recall-pi083.mjs +199 -0
  12. package/scripts/e2e-summary-cache.mjs +257 -0
  13. package/scripts/verify-package.mjs +81 -0
  14. package/src/automatic-summary-policy.mjs +210 -0
  15. package/src/extension.mjs +516 -0
  16. package/src/herdr-activity.mjs +111 -0
  17. package/src/index.mjs +13 -0
  18. package/src/lifecycle.mjs +322 -0
  19. package/src/rarebit-activity.mjs +48 -0
  20. package/src/rarebit-artifact-state.mjs +839 -0
  21. package/src/rarebit-cli.mjs +305 -0
  22. package/src/rarebit-command.mjs +281 -0
  23. package/src/rarebit-core.mjs +525 -0
  24. package/src/rarebit-model.mjs +226 -0
  25. package/src/rarebit-recall.mjs +182 -0
  26. package/src/rarebit-service.mjs +579 -0
  27. package/src/rarebit-session.mjs +227 -0
  28. package/src/rarebit-settings.mjs +79 -0
  29. package/src/rarebit-store.mjs +771 -0
  30. package/src/rarebit-visual-language.mjs +110 -0
  31. package/src/types.d.ts +601 -0
  32. package/test/automatic-summary-policy.test.mjs +476 -0
  33. package/test/extension-activity.test.mjs +73 -0
  34. package/test/fixtures/branch-mixed.json +117 -0
  35. package/test/fixtures/conformance-corpus.v1.json +306 -0
  36. package/test/fixtures/truncated-mixed.jsonl +2 -0
  37. package/test/herdr-activity.test.mjs +93 -0
  38. package/test/lifecycle-contract-independent.test.mjs +627 -0
  39. package/test/rarebit-activity.test.mjs +82 -0
  40. package/test/rarebit-artifact-state.test.mjs +440 -0
  41. package/test/rarebit-cli.test.mjs +222 -0
  42. package/test/rarebit-command.test.mjs +127 -0
  43. package/test/rarebit-core.test.mjs +461 -0
  44. package/test/rarebit-cross-consumer-conformance.test.mjs +150 -0
  45. package/test/rarebit-independent-contract.test.mjs +317 -0
  46. package/test/rarebit-recall.test.mjs +434 -0
  47. package/test/rarebit-sidecar-protocol.test.mjs +538 -0
  48. package/test/rarebit-visual-language.test.mjs +75 -0
  49. package/test/summary-attention-independent.test.mjs +172 -0
  50. package/test/types-history.ts +13 -0
@@ -0,0 +1,434 @@
1
+ import assert from "node:assert/strict";
2
+ import {
3
+ mkdir,
4
+ readFile,
5
+ readdir,
6
+ rm,
7
+ stat,
8
+ writeFile,
9
+ } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import test from "node:test";
13
+
14
+ import registerPiRarebit from "../src/extension.mjs";
15
+ import {
16
+ RAREBIT_RECALL_CONVERSATION_FILENAME,
17
+ RAREBIT_RECALL_DETAILED_FILENAME,
18
+ materializeRarebitRecall,
19
+ } from "../src/rarebit-recall.mjs";
20
+
21
+ const entry = (id, message, timestamp = "2026-07-24T01:02:03.000Z") => ({
22
+ type: "message",
23
+ id,
24
+ timestamp,
25
+ message,
26
+ });
27
+
28
+ const branch = () => [
29
+ entry("owner", { role: "user", content: "Exact owner evidence." }),
30
+ entry(
31
+ "tool",
32
+ { role: "toolResult", content: "private tool output" },
33
+ "2026-07-24T01:59:59.000Z",
34
+ ),
35
+ entry(
36
+ "continue",
37
+ {
38
+ role: "assistant",
39
+ stopReason: "toolUse",
40
+ content: [{ type: "text", text: "Exact continuation evidence." }],
41
+ },
42
+ "2026-07-24T02:00:00.000Z",
43
+ ),
44
+ entry(
45
+ "stop",
46
+ {
47
+ role: "assistant",
48
+ stopReason: "stop",
49
+ content: "Exact stop evidence.",
50
+ },
51
+ "2026-07-24T02:45:00.000Z",
52
+ ),
53
+ ];
54
+
55
+ function contextFor(activeBranch, sessionFile, notices = []) {
56
+ return {
57
+ hasUI: true,
58
+ isIdle: () => true,
59
+ ui: {
60
+ notify: (text, level) => notices.push({ text, level }),
61
+ },
62
+ sessionManager: {
63
+ getHeader: () => ({ id: "session-recall" }),
64
+ getSessionFile: () => sessionFile,
65
+ getBranch: () => activeBranch,
66
+ },
67
+ };
68
+ }
69
+
70
+ async function fixtureRoot(name) {
71
+ const root = join(
72
+ tmpdir(),
73
+ `hc-rarebit-recall-test-${name}-${process.pid}-${Date.now()}`,
74
+ );
75
+ await mkdir(root, { recursive: true, mode: 0o700 });
76
+ return root;
77
+ }
78
+
79
+ test("recall materialization writes private detailed evidence and a chronological low-context conversation view", async () => {
80
+ const root = await fixtureRoot("materialize");
81
+ const sessionFile = join(root, "session.jsonl");
82
+ await writeFile(sessionFile, "{}\n");
83
+ const result = await materializeRarebitRecall(
84
+ contextFor(branch(), sessionFile),
85
+ {
86
+ tempRoot: root,
87
+ now: () => new Date("2026-07-24T03:04:05.000Z"),
88
+ },
89
+ );
90
+
91
+ try {
92
+ assert.equal(
93
+ result.conversationPath,
94
+ join(result.directory, RAREBIT_RECALL_CONVERSATION_FILENAME),
95
+ );
96
+ assert.equal(
97
+ result.detailedPath,
98
+ join(result.directory, RAREBIT_RECALL_DETAILED_FILENAME),
99
+ );
100
+ assert.notEqual(result.directory, root);
101
+ assert.equal((await stat(result.directory)).mode & 0o777, 0o700);
102
+ assert.equal((await stat(result.conversationPath)).mode & 0o777, 0o600);
103
+ assert.equal((await stat(result.detailedPath)).mode & 0o777, 0o600);
104
+
105
+ const detailed = JSON.parse(await readFile(result.detailedPath, "utf8"));
106
+ assert.equal(detailed.createdAt, "2026-07-24T03:04:05.000Z");
107
+ assert.deepEqual(detailed.evidenceAuthority, {
108
+ type: "pi_session_jsonl",
109
+ path: sessionFile,
110
+ });
111
+ assert.equal(detailed.session.id, "session-recall");
112
+ assert.equal(detailed.session.activeBranch.leafEntryId, "stop");
113
+ assert.deepEqual(detailed.session.activeBranch.entryIds, [
114
+ "owner",
115
+ "tool",
116
+ "continue",
117
+ "stop",
118
+ ]);
119
+ assert.equal(detailed.selection.occurrenceCount, 3);
120
+ assert.deepEqual(
121
+ detailed.selection.occurrences.map(({ sourceEntryId, text }) => ({
122
+ sourceEntryId,
123
+ text,
124
+ })),
125
+ [
126
+ { sourceEntryId: "owner", text: "Exact owner evidence." },
127
+ {
128
+ sourceEntryId: "continue",
129
+ text: "Exact continuation evidence.",
130
+ },
131
+ { sourceEntryId: "stop", text: "Exact stop evidence." },
132
+ ],
133
+ );
134
+ assert.doesNotMatch(
135
+ await readFile(result.detailedPath, "utf8"),
136
+ /private tool output/,
137
+ );
138
+
139
+ const conversation = JSON.parse(
140
+ await readFile(result.conversationPath, "utf8"),
141
+ );
142
+ assert.deepEqual(conversation, {
143
+ schemaVersion: 1,
144
+ type: "rarebit_conversation",
145
+ timeZone: "UTC",
146
+ hours: [
147
+ {
148
+ hour: "2026-07-24T01:00Z",
149
+ messages: [{ role: "user", content: "Exact owner evidence." }],
150
+ },
151
+ {
152
+ hour: "2026-07-24T02:00Z",
153
+ messages: [
154
+ { role: "agent", content: "Exact continuation evidence." },
155
+ { role: "agent", content: "Exact stop evidence." },
156
+ ],
157
+ },
158
+ ],
159
+ });
160
+ const conversationText = await readFile(result.conversationPath, "utf8");
161
+ assert.doesNotMatch(
162
+ conversationText,
163
+ /sourceEntryId|occurrenceId|manifestHash|contentHash|timestamp|session/i,
164
+ );
165
+ } finally {
166
+ await result.discard();
167
+ await rm(root, { recursive: true, force: true });
168
+ }
169
+ });
170
+
171
+ test("conversation buckets sort chronologically and treat producer-tagged input as a normal user message", async () => {
172
+ const root = await fixtureRoot("bucket-order");
173
+ const sessionFile = join(root, "session.jsonl");
174
+ await writeFile(sessionFile, "{}\n");
175
+ const activeBranch = [
176
+ {
177
+ ...entry(
178
+ "rpc-user",
179
+ { role: "user", content: "Unknown-origin user evidence." },
180
+ "2026-07-24T02:10:00.000Z",
181
+ ),
182
+ producer: "rpc",
183
+ },
184
+ entry(
185
+ "earlier-agent",
186
+ {
187
+ role: "assistant",
188
+ stopReason: "stop",
189
+ content: "Earlier agent evidence.",
190
+ },
191
+ "2026-07-24T01:10:00.000Z",
192
+ ),
193
+ entry(
194
+ "unknown-time",
195
+ {
196
+ role: "assistant",
197
+ stopReason: "stop",
198
+ content: "Unknown-time agent evidence.",
199
+ },
200
+ null,
201
+ ),
202
+ entry(
203
+ "later-agent",
204
+ {
205
+ role: "assistant",
206
+ stopReason: "stop",
207
+ content: "Later agent evidence.",
208
+ },
209
+ "2026-07-24T02:20:00.000Z",
210
+ ),
211
+ ];
212
+ const result = await materializeRarebitRecall(
213
+ contextFor(activeBranch, sessionFile),
214
+ { tempRoot: root },
215
+ );
216
+
217
+ try {
218
+ const conversation = JSON.parse(
219
+ await readFile(result.conversationPath, "utf8"),
220
+ );
221
+ assert.deepEqual(conversation.hours, [
222
+ {
223
+ hour: "2026-07-24T01:00Z",
224
+ messages: [{ role: "agent", content: "Earlier agent evidence." }],
225
+ },
226
+ {
227
+ hour: "2026-07-24T02:00Z",
228
+ messages: [
229
+ { role: "user", content: "Unknown-origin user evidence." },
230
+ { role: "agent", content: "Later agent evidence." },
231
+ ],
232
+ },
233
+ {
234
+ hour: null,
235
+ messages: [{ role: "agent", content: "Unknown-time agent evidence." }],
236
+ },
237
+ ]);
238
+ assert.doesNotMatch(
239
+ await readFile(result.conversationPath, "utf8"),
240
+ /human|producer|rpc/,
241
+ );
242
+ const detailed = JSON.parse(await readFile(result.detailedPath, "utf8"));
243
+ assert.equal(
244
+ detailed.selection.occurrences.every(
245
+ (occurrence) => !Object.hasOwn(occurrence, "producer"),
246
+ ),
247
+ true,
248
+ );
249
+ } finally {
250
+ await result.discard();
251
+ await rm(root, { recursive: true, force: true });
252
+ }
253
+ });
254
+
255
+ test("a second-file write failure removes the partial invocation directory", async () => {
256
+ const root = await fixtureRoot("partial-write");
257
+ const sessionFile = join(root, "session.jsonl");
258
+ await writeFile(sessionFile, "{}\n");
259
+ let writes = 0;
260
+
261
+ await assert.rejects(
262
+ materializeRarebitRecall(contextFor(branch(), sessionFile), {
263
+ tempRoot: root,
264
+ writeJson: async (path, document) => {
265
+ writes += 1;
266
+ if (writes === 2) throw new Error("injected detailed write failure");
267
+ await writeFile(path, `${JSON.stringify(document)}\n`, {
268
+ flag: "wx",
269
+ mode: 0o600,
270
+ });
271
+ },
272
+ }),
273
+ /injected detailed write failure/,
274
+ );
275
+ assert.equal(writes, 2);
276
+ assert.deepEqual(await readdir(root), ["session.jsonl"]);
277
+ await rm(root, { recursive: true, force: true });
278
+ });
279
+
280
+ function extensionHarness({ recallMaterializer, sendUserMessage } = {}) {
281
+ const handlers = new Map();
282
+ const commands = new Map();
283
+ const sent = [];
284
+ const tools = [];
285
+ const pi = {
286
+ on: (event, handler) => handlers.set(event, handler),
287
+ registerCommand: (name, command) => commands.set(name, command),
288
+ registerTool: (...args) => tools.push(args),
289
+ sendMessage: () => {
290
+ throw new Error("Recall must not call sendMessage");
291
+ },
292
+ appendEntry: () => {
293
+ throw new Error("Recall must not append an entry");
294
+ },
295
+ sendUserMessage: (...args) => {
296
+ sent.push({ kind: "user", args });
297
+ return sendUserMessage?.(...args);
298
+ },
299
+ };
300
+ registerPiRarebit(pi, {
301
+ ...(recallMaterializer ? { recallMaterializer } : {}),
302
+ });
303
+ return { commands, handlers, pi, sent, tools };
304
+ }
305
+
306
+ test("recall sends one self-contained steer envelope in idle and busy contexts", async () => {
307
+ const sessionFile = "/tmp/session-recall.jsonl";
308
+ const recall = {
309
+ conversationPath: "/tmp/hc-rarebit-recall-test/rarebit-conversation.json",
310
+ detailedPath: "/tmp/hc-rarebit-recall-test/rarebit-evidence.json",
311
+ sessionId: "session-recall",
312
+ sessionFile,
313
+ branchLeafId: "stop",
314
+ };
315
+ for (const busy of [false, true]) {
316
+ const notices = [];
317
+ const harness = extensionHarness({ recallMaterializer: async () => recall });
318
+ const ctx = contextFor(branch(), sessionFile, notices);
319
+ ctx.isIdle = () => !busy;
320
+ const prompt = "Use \"this\" evidence\\path\nwith spacing. ";
321
+ await harness.commands.get("rarebit").handler(`recall ${prompt}`, ctx);
322
+ assert.equal(harness.sent.length, 1);
323
+ assert.deepEqual(harness.sent[0].args[1], { deliverAs: "steer" });
324
+ const envelope = JSON.parse(harness.sent[0].args[0]);
325
+ assert.equal(envelope.request.text, prompt);
326
+ assert.match(envelope.instructions, /request\.text as the current user request/i);
327
+ assert.equal(envelope.files.conversation.path, recall.conversationPath);
328
+ assert.equal(envelope.files.conversation.schema, "rarebit_conversation/v1");
329
+ assert.equal(envelope.files.detailedEvidence.path, recall.detailedPath);
330
+ assert.equal(envelope.files.detailedEvidence.schema, "rarebit_message_recall/v1");
331
+ assert.match(envelope.instructions, /conversation file first/i);
332
+ assert.match(notices.at(-1).text, /bundle prepared; turn requested/i);
333
+ }
334
+ });
335
+
336
+ test("single-send failure discards the prepared Recall bundle", async () => {
337
+ const notices = [];
338
+ let discarded = 0;
339
+ const harness = extensionHarness({
340
+ recallMaterializer: async () => ({
341
+ conversationPath: "/tmp/rarebit-conversation.json",
342
+ detailedPath: "/tmp/rarebit-evidence.json",
343
+ sessionId: "session-recall",
344
+ sessionFile: "/tmp/session-recall.jsonl",
345
+ branchLeafId: "stop",
346
+ selectedMessageCount: 3,
347
+ discard: async () => {
348
+ discarded += 1;
349
+ },
350
+ }),
351
+ sendUserMessage: () => {
352
+ throw new Error("synchronous prompt invocation failure");
353
+ },
354
+ });
355
+
356
+ await harness.commands
357
+ .get("rarebit")
358
+ .handler(
359
+ "recall Continue from the evidence",
360
+ contextFor(branch(), "/tmp/session-recall.jsonl", notices),
361
+ );
362
+
363
+ assert.equal(harness.sent.length, 1);
364
+ assert.equal(harness.sent[0].kind, "user");
365
+ assert.equal(discarded, 1);
366
+ assert.match(notices.at(-1).text, /recall failed/i);
367
+ });
368
+
369
+ test("missing prompt performs no extraction or send and shows concise usage", async () => {
370
+ let materializations = 0;
371
+ const notices = [];
372
+ const harness = extensionHarness({
373
+ recallMaterializer: async () => {
374
+ materializations += 1;
375
+ },
376
+ });
377
+
378
+ await harness.commands
379
+ .get("rarebit")
380
+ .handler(
381
+ "recall",
382
+ contextFor(branch(), "/tmp/session-recall.jsonl", notices),
383
+ );
384
+
385
+ assert.equal(materializations, 0);
386
+ assert.deepEqual(harness.sent, []);
387
+ assert.equal(
388
+ notices.at(-1).text,
389
+ "Usage: /rarebit recall <prompt...>",
390
+ );
391
+ });
392
+
393
+ test("materialization failure is actionable and sends neither context nor prompt", async () => {
394
+ const notices = [];
395
+ const harness = extensionHarness({
396
+ recallMaterializer: async () => {
397
+ throw new Error("temp directory is read-only");
398
+ },
399
+ });
400
+
401
+ await harness.commands
402
+ .get("rarebit")
403
+ .handler(
404
+ "recall Continue from the evidence",
405
+ contextFor(branch(), "/tmp/session-recall.jsonl", notices),
406
+ );
407
+
408
+ assert.deepEqual(harness.sent, []);
409
+ assert.match(notices.at(-1).text, /temp directory is read-only/);
410
+ assert.match(notices.at(-1).text, /Verify the current Session/i);
411
+ assert.equal(notices.at(-1).level, "error");
412
+ });
413
+
414
+ test("exact-leaf changes cancel before sending and discard the prepared bundle", async () => {
415
+ const notices = [];
416
+ let discarded = 0;
417
+ const harness = extensionHarness({
418
+ recallMaterializer: async () => ({
419
+ conversationPath: "/tmp/rarebit-conversation.json",
420
+ detailedPath: "/tmp/rarebit-evidence.json",
421
+ sessionId: "session-recall",
422
+ sessionFile: "/tmp/session-recall.jsonl",
423
+ branchLeafId: "different-leaf",
424
+ discard: async () => { discarded += 1; },
425
+ }),
426
+ });
427
+ await harness.commands.get("rarebit").handler(
428
+ "recall Second",
429
+ contextFor(branch(), "/tmp/session-recall.jsonl", notices),
430
+ );
431
+ assert.deepEqual(harness.sent, []);
432
+ assert.equal(discarded, 1);
433
+ assert.match(notices.at(-1).text, /Session or branch changed/);
434
+ });