Ctrl K
ComunidadGuías…

__tests__/people.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * People memory across posts and outcome learning: exchanges land on the right person, promises /
 * asks / commercial signals are picked out, the drafter's profile reads right and excludes the current
 * post, outcomes classify as-is / edited / rewritten and keep lessons, leads list. Temp PROFILE_DIR.
 */
import { describe, it, expect, beforeAll } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

let people;
beforeAll(async () => { process.env.PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'ppl-')); people = await import('../src/people.js'); });

const me = 'Wesley Stoep';
const isMe = (n) => String(n.author || '').toLowerCase() === me.toLowerCase();
const node = (id, author, text, extra = {}) => ({ id, author, text, isReply: !!extra.replyTo, replyTo: extra.replyTo || '', when: extra.when || '', i: Number(id.replace(/\D/g, '')) });
function entriesOf(branch) { return branch.map((n) => ({ node: n, branch, isMe, needsReply: !isMe(n) && (!n.isReply || n.replyTo === me) })); }

describe('people memory', () => {
  it('remembers exchanges per person with promises, asks and commercial signals, across posts', () => {
    const b1 = [node('c1', 'Ilya Elbert', 'Which stack do you use for auth? Entra was brutal'), node('c2', me, "I'll put together a walkthrough on the Entra part", { replyTo: 'Ilya Elbert' }), node('c3', 'Ilya Elbert', 'i was so angry at entra with all the policies lol', { replyTo: me })];
    const touched = people.remember('facebook', { postId: 'p1', postText: 'Vibe coding post', nodes: b1 }, entriesOf(b1), { now: 1000, urlOf: (n) => 'https://fb/' + n.id });
    expect(touched).toEqual(['Ilya Elbert']);
    const b2 = [node('c9', 'Ilya Elbert', 'what does it cost to have you build this for us?')];
    people.remember('facebook', { postId: 'p2', postText: 'Second post', nodes: b2 }, entriesOf(b2), { now: 2000 });
    const r = people.load('facebook', 'Ilya Elbert');
    expect(r.exchanges.map((x) => x.who)).toEqual(['them', 'you', 'them', 'them']);
    expect(Object.keys(r.posts)).toEqual(['p1', 'p2']);
    expect(r.promises[0].text).toMatch(/put together a walkthrough/);
    expect(r.asks[0].text).toMatch(/Which stack/);
    expect(r.signals[0].text).toMatch(/what does it cost/); expect(r.signals[0].postId).toBe('p2');
    expect(people.isLead('facebook', 'Ilya Elbert')).toBe(true);
    // c3 came after the owner's c2 in the same branch: he came back — the outcome a reply is for
    expect(r.outcomes.repliedBack).toBe(1);
    expect(people.worthOf(r)).toBeGreaterThanOrEqual(60); expect(people.worthOf(null)).toBe(0);
    // a second pass over the same branch adds nothing twice
    people.remember('facebook', { postId: 'p1', postText: 'Vibe coding post', nodes: b1 }, entriesOf(b1), { now: 3000 });
    expect(people.load('facebook', 'Ilya Elbert').exchanges.length).toBe(4);
  });

  it('leaves side conversations out and starts a record only for people the owner talks with', () => {
    const b = [node('d1', 'Peter', 'nice game'), node('d2', 'Dennis', 'meh', { replyTo: 'Peter' })];
    const entries = [{ node: b[0], branch: b, isMe, needsReply: true }, { node: b[1], branch: b, isMe, needsReply: false }];
    people.remember('facebook', { postId: 'p3', postText: 'x', nodes: b }, entries, { now: 4000 });
    expect(people.load('facebook', 'Peter').exchanges.length).toBe(1);
    // Dennis talked to Peter, not to the owner — but the branch holds no owner reply, so Dennis is "them" on a root-less…
    // …no: he is a reply not to the owner in a branch without the owner: not remembered
    expect(people.load('facebook', 'Dennis')).toBeNull();
  });

  it('gives the drafter a profile that excludes the current post and carries the promise and the signal', () => {
    const p = people.profileOf('facebook', 'Ilya Elbert', { exceptPostId: 'p2' });
    expect(p).toMatch(/PERSON MEMORY — Ilya Elbert: 4 exchange/); expect(p).toMatch(/"Vibe coding post"/); expect(p).not.toMatch(/"Second post"/);
    expect(p).toMatch(/You promised them/); expect(p).toMatch(/COMMERCIAL SIGNAL/); expect(p).toMatch(/never re-promise/);
    expect(people.profileOf('facebook', 'Nobody')).toBe('');
  });

  it('classifies outcomes and keeps rewrites as lessons the drafter reads', () => {
    expect(people.recordOutcome('facebook', 'Ilya Elbert', { draft: 'Thanks Ilya, that is fair.', posted: 'Thanks Ilya, that is fair.' }).kind).toBe('asIs');
    expect(people.recordOutcome('facebook', 'Ilya Elbert', { draft: 'Thanks Ilya, that is a fair point about Entra and the keys.', posted: 'Thanks Ilya, that is a fair point about Entra and keys!' }).kind).toBe('edited');
    expect(people.recordOutcome('facebook', 'Ilya Elbert', { draft: 'Great question! Absolutely, I would be happy to elaborate on that.', posted: 'ha yeah entra is a pain' }).kind).toBe('rewritten');
    const o = people.outcomes(); expect(o).toMatchObject({ drafted: 3, asIs: 1, edited: 1, rewritten: 1, asIsRate: 33 });
    expect(people.editLessons()).toMatch(/drafted: "Great question!/); expect(people.editLessons()).toMatch(/posted:  "ha yeah entra/);
    expect(people.load('facebook', 'Ilya Elbert').outcomes).toMatchObject({ drafted: 3, asIs: 1, edited: 1, rewritten: 1, repliedBack: 1 });
  });

  it("on someone else's post, their post and the owner's root comment land on their record", () => {
    const b = [node('e0', me, 'super clean stack'), node('e1', 'Marta', 'thanks! which db?', { replyTo: me })];
    const entries = b.map((n) => ({ node: n, branch: b, isMe, needsReply: !isMe(n) }));
    people.remember('facebook', { postId: 'p9', postText: 'my auth stack is entra + supabase', postAuthor: 'Marta', nodes: b }, entries, { now: 9000 });
    const r = people.load('facebook', 'Marta');
    expect(r.exchanges.map((x) => [x.who, x.kind || '', x.text.slice(0, 12)])).toEqual([['them', 'post', 'my auth stac'], ['you', '', 'super clean '], ['them', '', 'thanks! whic']]);
    expect(r.posts.p9.theirs).toBe(true);
    expect(people.profileOf('facebook', 'Marta')).toMatch(/Marta POSTED: my auth stack/);
    expect(people.profileOf('facebook', 'Marta')).toMatch(/YOU: super clean stack/);
    expect(r.outcomes.repliedBack).toBe(1);   // she answered the owner's comment
  });

  it('lists people newest first and leads only on request', () => {
    const all = people.list('facebook'); expect(all.map((p) => p.name)).toEqual(['Marta', 'Peter', 'Ilya Elbert']);
    const leads = people.list('facebook', { leadsOnly: true }); expect(leads.map((p) => p.name)).toEqual(['Ilya Elbert']); expect(leads[0].signals[0]).toMatch(/cost/);
  });
});