@hypercarrier/rarebit 0.5.1 → 0.5.2

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,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 — 2026-10-15
4
+
5
+ - Add a development-only simulator lane (`npm run test:sim`) that runs real
6
+ Rarebit selection, materialization, Recall, compaction and tree navigation on
7
+ the shared HyperCarrier Pi simulator. It needs the HyperCarrier workspace.
8
+ `sim/` is not in the npm package. The lane found no Rarebit product bug.
9
+ - Move the selection monitor tests into `test/`.
10
+
3
11
  ## 0.5.1 — 2026-10-12
4
12
 
5
13
  - Document the release privacy gate as the baseline range scan in `RUNBOOK.md`
package/README.md CHANGED
@@ -400,6 +400,16 @@ Use [GitHub Issues](https://github.com/deephbz/rarebit/issues) for normal
400
400
  support. Include no Session prose, credentials, or Recall files in public
401
401
  reports.
402
402
 
403
+ ## Development verification
404
+
405
+ `npm ci && npm test` runs standalone unit and selection checks. The private
406
+ R&D simulator lane uses HC's shared `@hypercarrier/pi-sim`; it does not enter
407
+ Rarebit's root install or published artifact. Run `npm run test:sim` in the HC
408
+ workspace or set `HC_PI_SIM_PATH` to an installed simulator package root.
409
+ Run `npm run test:sim:campaign` for the longer seeded campaign.
410
+ [Simulator scope, setup, properties, replay, and results](sim/README.md) remain
411
+ repository-only development artifacts. No real provider credentials are needed.
412
+
403
413
  ## Release and source history
404
414
 
405
415
  Rarebit is released from an immutable version tag by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypercarrier/rarebit",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Sparse, provenance-bearing conversational evidence recovery for Pi sessions.",
5
5
  "license": "MIT",
6
6
  "author": "HyperCarrier contributors",
@@ -29,6 +29,9 @@
29
29
  },
30
30
  "scripts": {
31
31
  "test": "node --test test/*.test.mjs",
32
+ "test:sim": "node --test sim/*.test.mjs",
33
+ "test:sim:campaign": "RAREBIT_SIM_RUNS=100 node --test sim/*.test.mjs",
34
+ "test:sim:replay": "node sim/replay.mjs",
32
35
  "test:e2e:tui": "node scripts/e2e-v020-pi.mjs",
33
36
  "test:e2e:summary-cache": "node scripts/e2e-summary-cache.mjs",
34
37
  "test:e2e:recall": "node scripts/e2e-recall-pi0842.mjs",
@@ -55,6 +58,7 @@
55
58
  "@earendil-works/pi-coding-agent": "0.84.2",
56
59
  "@earendil-works/pi-tui": "0.84.2",
57
60
  "ajv": "^6.15.0",
61
+ "fast-check": "^4.10.2",
58
62
  "typescript": "^5.9.3"
59
63
  },
60
64
  "pi": {
@@ -0,0 +1,10 @@
1
+ // Independent observation of the prose-selection contract; no simulator needed.
2
+ import assert from 'node:assert/strict';
3
+ export function expectedProse(branch) {
4
+ return branch.filter(e => e.type === 'message' && (e.message.role === 'user' || (e.message.role === 'assistant' && ['stop', 'toolUse'].includes(e.message.stopReason))))
5
+ .map(e => ({ id: e.id ?? e.message.id ?? null, text: typeof e.message.content === 'string' ? e.message.content : (e.message.content ?? []).filter(b => b.type === 'text' && typeof b.text === 'string').map(b => b.text).join('\n'), readable: typeof e.message.content === 'string' || (e.message.content ?? []).some(b => b.type === 'text' && typeof b.text === 'string') }))
6
+ .filter(e => e.readable).map(({ id, text }) => ({ id, text }));
7
+ }
8
+ export function checkSelection(selection, branch) {
9
+ assert.deepEqual(selection.occurrences.map(o => ({ id: o.sourceEntryId, text: o.text })), expectedProse(branch), 'R1 selection contract');
10
+ }
@@ -0,0 +1,31 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fc from 'fast-check';
4
+ import { selectRarebits } from '../src/rarebit-core.mjs';
5
+ import { parseNativeSession, resolveActiveBranch } from '../src/rarebit-session.mjs';
6
+ import { checkSelection } from './helpers/selection-monitor.mjs';
7
+
8
+ test('R1 exhaustive role/outcome/content pairs; R8 old and current native formats', () => {
9
+ const entries = [];
10
+ for (const role of ['user', 'assistant', 'toolResult']) for (const stopReason of ['stop', 'toolUse', 'error', 'aborted', 'length', undefined]) for (const content of ['prose', [{ type: 'text', text: 'prose' }, { type: 'thinking', thinking: 'private reasoning' }], [{ type: 'toolCall', id: 'synthetic', name: 'read', arguments: {} }], []]) entries.push({ type: 'message', message: { role, stopReason, content } });
11
+ let count = 0;
12
+ for (const a of entries) for (const b of entries) {
13
+ const branch = [a, b].map((e, i) => ({ ...e, id: `e${i}`, parentId: i ? 'e0' : null }));
14
+ checkSelection(selectRarebits(branch), branch); count++;
15
+ }
16
+ assert.equal(count, 5184);
17
+ for (const version of [1, 2, 3]) {
18
+ const branch = entries.map((e, i) => ({ ...e, id: `e${i}`, parentId: i ? `e${i-1}` : null, legacyExtra: 'retained' }));
19
+ const original = JSON.stringify([{ type: 'session', id: 'synthetic-old', version }, ...branch]);
20
+ const parsed = parseNativeSession(JSON.parse(original).map(e => JSON.stringify(e)).join('\n'));
21
+ checkSelection(selectRarebits(resolveActiveBranch(parsed)), branch);
22
+ assert.equal(JSON.stringify([parsed.header, ...parsed.entries]), original, 'R8 no read-time migration');
23
+ }
24
+ });
25
+
26
+ test('R1/R8 fast-check mixed native evidence with shrinking', () => {
27
+ fc.assert(fc.property(fc.array(fc.record({ role: fc.constantFrom('user', 'assistant', 'toolResult'), stopReason: fc.constantFrom('stop', 'toolUse', 'error', 'aborted'), text: fc.string({ maxLength: 100 }) }), { maxLength: 30 }), records => {
28
+ const branch = records.map((r, i) => ({ type: 'message', id: `e${i}`, parentId: i ? `e${i-1}` : null, message: { role: r.role, stopReason: r.stopReason, content: [{ type: 'text', text: r.text }] } }));
29
+ checkSelection(selectRarebits(branch), branch);
30
+ }), { seed: 20261015, numRuns: 100 });
31
+ });