@hypercarrier/rarebit 0.1.0-alpha.1 → 0.1.0-alpha.3

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/CHANGELOG.md CHANGED
@@ -1,10 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-alpha.3 — 2026-08-02
4
+
5
+ - Recall now sends one human-readable Markdown user message with the exact request and local private JSON evidence references. Idle and busy steer behavior remains unchanged, and JSON files remain machine authority.
6
+
7
+ ## 0.1.0-alpha.2 — 2026-08-02
8
+
9
+ First promoted OIDC prerelease candidate. This corrective release has no
10
+ product or runtime behavior change from alpha.1. It prepares the standalone
11
+ package for non-destructive trusted publication.
12
+
3
13
  ## 0.1.0-alpha.1 — 2026-08-02
4
14
 
5
- First standalone public-alpha candidate. It preserves the committed Rarebit
6
- source from HyperCarrier commit `945533d41e6c1f88718d12b744bc96202afbac21`.
7
- The package name is `@hypercarrier/rarebit` and the CLI is `rarebit`.
15
+ Bootstrap registry evidence. It preserves the committed Rarebit source from
16
+ HyperCarrier commit `945533d41e6c1f88718d12b744bc96202afbac21`. The package
17
+ name is `@hypercarrier/rarebit` and the CLI is `rarebit`.
8
18
 
9
19
  This is an unstable alpha. Public APIs, CLI output, sidecar details, and visual
10
20
  marks can change before a stable release.
package/README.md CHANGED
@@ -15,7 +15,7 @@ authority.
15
15
  Use Node 22 or later and Pi 0.83 or later. Install it in Pi from npm:
16
16
 
17
17
  ```sh
18
- pi install npm:@hypercarrier/rarebit@0.1.0-alpha.1
18
+ pi install npm:@hypercarrier/rarebit@0.1.0-alpha.3
19
19
  pi list
20
20
  ```
21
21
 
@@ -34,7 +34,7 @@ To use the CLI in a normal Node project, install it and invoke the local bin
34
34
  with npm:
35
35
 
36
36
  ```sh
37
- npm install @hypercarrier/rarebit@0.1.0-alpha.1
37
+ npm install @hypercarrier/rarebit@0.1.0-alpha.3
38
38
  npm exec -- rarebit --help
39
39
  ```
40
40
 
@@ -101,8 +101,8 @@ remain until you remove them. Session JSONL retention is controlled by Pi.
101
101
 
102
102
  Run `/rarebit recall <prompt>` in a Pi Session. Rarebit writes the exact active
103
103
  branch selection to two private files: a conversation view and detailed
104
- lineage evidence. It then sends one atomic user message. The message contains
105
- your exact request and absolute pointers to both files.
104
+ lineage evidence. It then sends one atomic, human-readable Markdown user
105
+ message containing your exact request and absolute pointers to both files.
106
106
 
107
107
  When Pi is idle, that message starts one turn. When Pi is busy, it queues one
108
108
  steering message. Recall does not send a second follow-up, add a custom Session
@@ -110,7 +110,7 @@ entry, or create a durable Recall receipt.
110
110
 
111
111
  The temporary directory is mode 0700 and its JSON files are mode 0600. The
112
112
  files remain after the request so Pi can read them; delete the directory after
113
- the turn no longer needs it. Both files and the persisted Pi envelope are
113
+ the turn no longer needs it. Both files and the persisted Pi message are
114
114
  sensitive. Do not put their paths or content in a ticket.
115
115
 
116
116
  ## Update, uninstall, and rollback
@@ -118,7 +118,7 @@ sensitive. Do not put their paths or content in a ticket.
118
118
  Pin alpha versions for reproducible installs:
119
119
 
120
120
  ```sh
121
- pi install npm:@hypercarrier/rarebit@0.1.0-alpha.1
121
+ pi install npm:@hypercarrier/rarebit@0.1.0-alpha.3
122
122
  pi update npm:@hypercarrier/rarebit
123
123
  pi remove npm:@hypercarrier/rarebit
124
124
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypercarrier/rarebit",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Sparse, provenance-bearing conversational evidence recovery for Pi sessions.",
5
5
  "license": "MIT",
6
6
  "author": "HyperCarrier contributors",
@@ -15,6 +15,55 @@ async function waitFor(predicate, label, timeout = 2_000) {
15
15
  }
16
16
  }
17
17
 
18
+ function expectedRecallMarkdown(conversationPath, detailedPath, request) {
19
+ return `# Rarebit Recall
20
+
21
+ *Use these local private evidence files to recover historical context for this turn.*
22
+
23
+ ## How to use this bundle
24
+
25
+ 1. Treat **Current request** below as the exact current user request.
26
+ 2. Read **Conversation** first for meaning.
27
+ 3. Use **Detailed evidence** only for source, Session, branch, or lineage facts.
28
+ 4. Answer the current request using this evidence.
29
+
30
+ ## Local private evidence files
31
+
32
+ **Conversation** — \`rarebit_conversation/v1\`
33
+
34
+ \`\`\`text
35
+ ${conversationPath}
36
+ \`\`\`
37
+
38
+ **Detailed evidence** — \`rarebit_message_recall/v1\`
39
+
40
+ \`\`\`text
41
+ ${detailedPath}
42
+ \`\`\`
43
+
44
+ ## Current request
45
+
46
+ \`\`\`text
47
+ ${request}
48
+ \`\`\``;
49
+ }
50
+
51
+ function conversationPathFrom(message) {
52
+ const match = message.match(
53
+ /\*\*Conversation\*\* — `rarebit_conversation\/v1`\n\n```text\n([^\n]*rarebit-conversation\.json)\n```/,
54
+ );
55
+ assert.ok(match, "Recall Markdown must contain the fenced conversation path");
56
+ return match[1];
57
+ }
58
+
59
+ function detailedPathFrom(message) {
60
+ const match = message.match(
61
+ /\*\*Detailed evidence\*\* — `rarebit_message_recall\/v1`\n\n```text\n([^\n]*rarebit-evidence\.json)\n```/,
62
+ );
63
+ assert.ok(match, "Recall Markdown must contain the fenced detailed path");
64
+ return match[1];
65
+ }
66
+
18
67
  const root =
19
68
  process.env.PI_CODING_AGENT_ROOT ??
20
69
  resolve(
@@ -107,11 +156,20 @@ try {
107
156
  .filter((entry) => JSON.stringify(entry).includes("exact idle request"));
108
157
  assert.equal(userEntries.length, 1);
109
158
  const message = userEntries[0].message ?? userEntries[0];
110
- const envelope = JSON.parse(message.content[0].text);
111
- recallDirectories.push(dirname(envelope.files.conversation.path));
112
- assert.equal(envelope.request.text, "exact idle request");
113
- await access(envelope.files.conversation.path);
114
- await access(envelope.files.detailedEvidence.path);
159
+ const idleMessageText = message.content[0].text;
160
+ const idleConversationPath = conversationPathFrom(idleMessageText);
161
+ const idleDetailedPath = detailedPathFrom(idleMessageText);
162
+ assert.equal(
163
+ idleMessageText,
164
+ expectedRecallMarkdown(
165
+ idleConversationPath,
166
+ idleDetailedPath,
167
+ "exact idle request",
168
+ ),
169
+ );
170
+ recallDirectories.push(dirname(idleConversationPath));
171
+ await access(idleConversationPath);
172
+ await access(idleDetailedPath);
115
173
  assert.equal(
116
174
  manager
117
175
  .getBranch()
@@ -141,29 +199,29 @@ try {
141
199
  assert.equal(queued.steering.length, 1);
142
200
  assert.equal(queued.followUp?.length ?? 0, 0);
143
201
  const queuedMessage = queued.steering[0];
144
- const busyEnvelope = JSON.parse(
202
+ const busyMessageText =
145
203
  typeof queuedMessage === "string"
146
204
  ? queuedMessage
147
- : (queuedMessage.content?.[0]?.text ?? queuedMessage.content),
205
+ : (queuedMessage.content?.[0]?.text ?? queuedMessage.content);
206
+ const busyConversationPath = conversationPathFrom(busyMessageText);
207
+ const busyDetailedPath = detailedPathFrom(busyMessageText);
208
+ assert.equal(
209
+ busyMessageText,
210
+ expectedRecallMarkdown(
211
+ busyConversationPath,
212
+ busyDetailedPath,
213
+ "exact busy request",
214
+ ),
148
215
  );
149
- recallDirectories.push(dirname(busyEnvelope.files.conversation.path));
150
- await access(busyEnvelope.files.conversation.path);
151
- await access(busyEnvelope.files.detailedEvidence.path);
152
- assert.equal(busyEnvelope.request.text, "exact busy request");
216
+ recallDirectories.push(dirname(busyConversationPath));
217
+ await access(busyConversationPath);
218
+ await access(busyDetailedPath);
153
219
  releaseBusy();
154
220
  await busyRun;
155
221
  await session.agent.waitForIdle();
156
222
  assert.equal(provider.state.callCount, 4);
157
223
  const recallCount = (texts) =>
158
- texts
159
- .map((text) => {
160
- try {
161
- return JSON.parse(text);
162
- } catch {
163
- return null;
164
- }
165
- })
166
- .filter((value) => value?.request?.text === "exact busy request").length;
224
+ texts.filter((text) => text === busyMessageText).length;
167
225
  const fourthUserTexts = (fourthContext?.messages ?? [])
168
226
  .filter((message) => message.role === "user")
169
227
  .map((message) => message.content?.[0]?.text ?? "");
@@ -184,7 +242,7 @@ try {
184
242
  false,
185
243
  );
186
244
  console.log(
187
- `Pi ${packageJson.version} Recall E2E passed: idle and busy steer envelopes; no custom receipt.`,
245
+ `Pi ${packageJson.version} Recall E2E passed: idle and busy steer Markdown messages; no custom receipt.`,
188
246
  );
189
247
  } finally {
190
248
  releaseBusy?.();
@@ -22,6 +22,7 @@ const forbiddenPath = /(^|\/)(\.git|node_modules|\.github|hc-rarebit\.mjs)(\/|$)
22
22
  const allowedBare = new Set(["@earendil-works/pi-ai"]);
23
23
  const packageFor = (specifier) => specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
24
24
  const packageJson = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
25
+ assert.equal(packageJson.version, "0.1.0-alpha.3", "release version mismatch");
25
26
 
26
27
  const packed = JSON.parse(npm(["pack", "--json", "--ignore-scripts"]))[0];
27
28
  const tarball = join(root, packed.filename);
package/src/extension.mjs CHANGED
@@ -381,22 +381,42 @@ export default function registerPiRarebit(pi, config = {}) {
381
381
  );
382
382
  };
383
383
 
384
- const recallEnvelope = (recall, requestText) =>
385
- JSON.stringify({
386
- instructions:
387
- "Treat request.text as the current user request. Use the two private Rarebit files as historical evidence. Read the conversation file first for meaning. Use detailed evidence only for source, Session, branch, or lineage facts. Answer request.text using this evidence.",
388
- files: {
389
- conversation: {
390
- path: recall.conversationPath,
391
- schema: `rarebit_conversation/v${RAREBIT_CONVERSATION_SCHEMA_VERSION}`,
392
- },
393
- detailedEvidence: {
394
- path: recall.detailedPath,
395
- schema: `rarebit_message_recall/v${RAREBIT_RECALL_SCHEMA_VERSION}`,
396
- },
397
- },
398
- request: { text: requestText },
399
- });
384
+ const fence = (value) => {
385
+ const safeLength = (marker) => {
386
+ const runs = value.match(new RegExp(`${marker}+`, "g")) ?? [];
387
+ return Math.max(3, ...runs.map((run) => run.length + 1));
388
+ };
389
+ const backtickLength = safeLength("`");
390
+ const tildeLength = safeLength("~");
391
+ const marker = backtickLength <= tildeLength ? "`" : "~";
392
+ const length = marker === "`" ? backtickLength : tildeLength;
393
+ const boundary = marker.repeat(length);
394
+ return `${boundary}text\n${value}\n${boundary}`;
395
+ };
396
+ const recallMessage = (recall, requestText) => `# Rarebit Recall
397
+
398
+ *Use these local private evidence files to recover historical context for this turn.*
399
+
400
+ ## How to use this bundle
401
+
402
+ 1. Treat **Current request** below as the exact current user request.
403
+ 2. Read **Conversation** first for meaning.
404
+ 3. Use **Detailed evidence** only for source, Session, branch, or lineage facts.
405
+ 4. Answer the current request using this evidence.
406
+
407
+ ## Local private evidence files
408
+
409
+ **Conversation** — \`rarebit_conversation/v${RAREBIT_CONVERSATION_SCHEMA_VERSION}\`
410
+
411
+ ${fence(recall.conversationPath)}
412
+
413
+ **Detailed evidence** — \`rarebit_message_recall/v${RAREBIT_RECALL_SCHEMA_VERSION}\`
414
+
415
+ ${fence(recall.detailedPath)}
416
+
417
+ ## Current request
418
+
419
+ ${fence(requestText)}`;
400
420
 
401
421
  pi.registerCommand?.("rarebit", {
402
422
  description: rarebitCommandDescription(),
@@ -432,7 +452,7 @@ export default function registerPiRarebit(pi, config = {}) {
432
452
  "the active Pi Session or branch changed during materialization",
433
453
  );
434
454
  }
435
- pi.sendUserMessage(recallEnvelope(recall, prompt), {
455
+ pi.sendUserMessage(recallMessage(recall, prompt), {
436
456
  deliverAs: "steer",
437
457
  });
438
458
  notify(ctx, "Rarebit recall bundle prepared; turn requested", "info");
@@ -303,36 +303,120 @@ function extensionHarness({ recallMaterializer, sendUserMessage } = {}) {
303
303
  return { commands, handlers, pi, sent, tools };
304
304
  }
305
305
 
306
- test("recall sends one self-contained steer envelope in idle and busy contexts", async () => {
306
+ test("recall sends one exact fenced Markdown steer message in idle and busy contexts", async () => {
307
307
  const sessionFile = "/tmp/session-recall.jsonl";
308
308
  const recall = {
309
- conversationPath: "/tmp/hc-rarebit-recall-test/rarebit-conversation.json",
310
- detailedPath: "/tmp/hc-rarebit-recall-test/rarebit-evidence.json",
309
+ conversationPath:
310
+ "/tmp/Rare bit [会话] (one) **/tick`` and ~~\nnext/rarebit-conversation.json",
311
+ detailedPath:
312
+ "/tmp/Detailed [证据] (two) **/tick```` and ~~~\n~~~line-start/rarebit-evidence.json",
311
313
  sessionId: "session-recall",
312
314
  sessionFile,
313
315
  branchLeafId: "stop",
314
316
  };
317
+ const prompt =
318
+ "\n# Heading-looking request\n- list-looking item\n```text\ninside backticks\n```\n~~~~\ninside tildes\n~~~~\nTrailing request\n";
319
+ const expectedMessage = `# Rarebit Recall
320
+
321
+ *Use these local private evidence files to recover historical context for this turn.*
322
+
323
+ ## How to use this bundle
324
+
325
+ 1. Treat **Current request** below as the exact current user request.
326
+ 2. Read **Conversation** first for meaning.
327
+ 3. Use **Detailed evidence** only for source, Session, branch, or lineage facts.
328
+ 4. Answer the current request using this evidence.
329
+
330
+ ## Local private evidence files
331
+
332
+ **Conversation** — \`rarebit_conversation/v1\`
333
+
334
+ \`\`\`text
335
+ ${recall.conversationPath}
336
+ \`\`\`
337
+
338
+ **Detailed evidence** — \`rarebit_message_recall/v1\`
339
+
340
+ ~~~~text
341
+ ${recall.detailedPath}
342
+ ~~~~
343
+
344
+ ## Current request
345
+
346
+ \`\`\`\`text
347
+ ${prompt}
348
+ \`\`\`\``;
349
+
315
350
  for (const busy of [false, true]) {
316
351
  const notices = [];
317
352
  const harness = extensionHarness({ recallMaterializer: async () => recall });
318
353
  const ctx = contextFor(branch(), sessionFile, notices);
319
354
  ctx.isIdle = () => !busy;
320
- const prompt = "Use \"this\" evidence\\path\nwith spacing. ";
321
355
  await harness.commands.get("rarebit").handler(`recall ${prompt}`, ctx);
322
356
  assert.equal(harness.sent.length, 1);
357
+ assert.equal(harness.sent[0].args[0], expectedMessage);
323
358
  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);
359
+ assert.equal(expectedMessage.includes(recall.conversationPath), true);
360
+ assert.equal(expectedMessage.includes(recall.detailedPath), true);
361
+ assert.equal(expectedMessage.includes(prompt), true);
332
362
  assert.match(notices.at(-1).text, /bundle prepared; turn requested/i);
333
363
  }
334
364
  });
335
365
 
366
+ test("long delimiter runs ending at value boundaries use the shortest safe fences", async () => {
367
+ const pathBackticks = "`".repeat(80);
368
+ const pathTildes = "~".repeat(47);
369
+ const conversationPath = `/tmp/long-runs/${pathBackticks}/${pathTildes}`;
370
+ const requestTildes = "~".repeat(96);
371
+ const requestBackticks = "`".repeat(63);
372
+ const request = `Boundary ${requestTildes} ${requestBackticks}`;
373
+ const harness = extensionHarness({
374
+ recallMaterializer: async () => ({
375
+ conversationPath,
376
+ detailedPath: "/tmp/rarebit-evidence.json",
377
+ sessionId: "session-recall",
378
+ sessionFile: "/tmp/session-recall.jsonl",
379
+ branchLeafId: "stop",
380
+ }),
381
+ });
382
+
383
+ await harness.commands
384
+ .get("rarebit")
385
+ .handler(
386
+ `recall ${request}`,
387
+ contextFor(branch(), "/tmp/session-recall.jsonl"),
388
+ );
389
+
390
+ assert.equal(harness.sent.length, 1);
391
+ const message = harness.sent[0].args[0];
392
+ const conversationBlock = message.match(
393
+ /\*\*Conversation\*\* — `rarebit_conversation\/v1`\n\n([`~]+)text\n([^\n]*)\n\1\n\n\*\*Detailed evidence\*\*/,
394
+ );
395
+ assert.ok(conversationBlock);
396
+ assert.equal(conversationBlock[2], conversationPath);
397
+ assert.equal(conversationBlock[2].endsWith(pathTildes), true);
398
+ const conversationFence = conversationBlock[1];
399
+ assert.equal(conversationFence[0], "~");
400
+ assert.ok(conversationFence.length >= 3);
401
+ assert.equal(conversationFence.length, pathTildes.length + 1);
402
+ assert.ok(conversationFence.length > pathTildes.length);
403
+ assert.ok(conversationFence.length < pathBackticks.length + 1);
404
+
405
+ const requestBlock = message.match(
406
+ /## Current request\n\n([`~]+)text\n([\s\S]*)\n\1$/,
407
+ );
408
+ assert.ok(requestBlock);
409
+ assert.equal(requestBlock[2], request);
410
+ assert.equal(requestBlock[2].endsWith(requestBackticks), true);
411
+ const requestFence = requestBlock[1];
412
+ assert.equal(requestFence[0], "`");
413
+ assert.ok(requestFence.length >= 3);
414
+ assert.equal(requestFence.length, requestBackticks.length + 1);
415
+ assert.ok(requestFence.length > requestBackticks.length);
416
+ assert.ok(requestFence.length < requestTildes.length + 1);
417
+ assert.equal(message.endsWith(`${request}\n${requestFence}`), true);
418
+ });
419
+
336
420
  test("single-send failure discards the prepared Recall bundle", async () => {
337
421
  const notices = [];
338
422
  let discarded = 0;