Ctrl K
ComunidadGuías…

__tests__/playbook.test.js

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

Archivo original ↗Preguntar a KiBot
/**
 * What this account has learned about where leads are.
 *
 * Every run used to start from nothing: rediscovering that Facebook has a post search, retrying
 * phrasings somebody tried last week, sweeping a group that has produced nothing in a month. A
 * prompt cannot fix that — telling a model "you know Facebook" does not tell it that "programmeur
 * gezocht" returns recruiters while "wie kan mij helpen met een webshop" returns customers.
 *
 * The rule this file exists to enforce: the score is EARNED, never claimed. Nothing here asks the
 * agent how it did.
 */
import { describe, it, expect, beforeEach } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

process.env.PROFILE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'gb-pb-'));
const pb = await import('../src/playbook.js');

const search = (q) => ({ kind: 'search', what: q });

describe('keeping score', () => {
  beforeEach(() => pb.forget('facebook'));

  it('records what a place returned, not what anyone thought of it', () => {
    pb.recordSweep('facebook', search('webshop bouwen'), { posts: 12, tooOld: 3 });
    const e = pb.summary('facebook').places[0];
    expect(e).toMatchObject({ what: 'webshop bouwen', posts: 12, tooOld: 3, sweeps: 1, leads: 0 });
  });

  it('credits a lead to the place it came from', () => {
    pb.recordSweep('facebook', search('webshop bouwen'), { posts: 10 });
    pb.recordLead('facebook', search('webshop bouwen'));
    expect(pb.summary('facebook').places[0].leads).toBe(1);
  });

  it('accumulates across runs, which is the whole point', () => {
    for (let i = 0; i < 3; i++) pb.recordSweep('facebook', search('x'), { posts: 5 });
    expect(pb.summary('facebook').places[0]).toMatchObject({ sweeps: 3, posts: 15 });
  });

  it('will not credit a lead to a place it has never swept', () => {
    expect(pb.recordLead('facebook', search('never seen'))).toBeNull();
  });

  it('ignores a place it cannot name', () => {
    expect(pb.recordSweep('facebook', { kind: 'search', what: '  ' }, { posts: 3 })).toBeNull();
  });
});

describe('deciding what is worth trying again', () => {
  beforeEach(() => pb.forget('facebook'));

  /* Writing somewhere off after one quiet afternoon is how you lose the group that produces a
     customer a month. It takes real evidence. */
  it('does not write a place off on thin evidence', () => {
    pb.recordSweep('facebook', search('quiet'), { posts: 4 });
    expect(pb.summary('facebook').places[0].dead).toBe(false);
  });

  it('writes one off after enough sweeps and enough posts with nothing to show', () => {
    for (let i = 0; i < 4; i++) pb.recordSweep('facebook', search('dead end'), { posts: 20 });
    expect(pb.summary('facebook').places[0].dead).toBe(true);
  });

  it('never writes off a place that has produced somebody', () => {
    for (let i = 0; i < 9; i++) pb.recordSweep('facebook', search('slow but real'), { posts: 40 });
    pb.recordLead('facebook', search('slow but real'));
    expect(pb.summary('facebook').places[0].dead).toBe(false);
  });
});

describe('what the agent is handed', () => {
  beforeEach(() => pb.forget('facebook'));

  it('is empty before anything has been learned, rather than pretending', () => {
    expect(pb.asContext('facebook')).toBe('');
  });

  it('names what worked and what to stop trying', () => {
    pb.recordSweep('facebook', search('goede webbouwer'), { posts: 8 });
    pb.recordLead('facebook', search('goede webbouwer'));
    for (let i = 0; i < 4; i++) pb.recordSweep('facebook', search('software development'), { posts: 30 });

    const ctx = pb.asContext('facebook');
    expect(ctx).toMatch(/WHAT HAS WORKED BEFORE/);
    expect(ctx).toMatch(/goede webbouwer/);
    expect(ctx).toMatch(/NEVER PRODUCED ANYONE/);
    expect(ctx).toMatch(/software development/);
  });

  /* A search tried once that found two is a better bet for the next twenty minutes than one tried
     twenty times that found three. Ordering by total would say the opposite. */
  it('puts the best bet first, by rate rather than by total', () => {
    pb.recordSweep('facebook', search('rare gem'), { posts: 5 });
    pb.recordLead('facebook', search('rare gem'));
    pb.recordLead('facebook', search('rare gem'));
    for (let i = 0; i < 20; i++) pb.recordSweep('facebook', search('grind'), { posts: 50 });
    for (let i = 0; i < 3; i++) pb.recordLead('facebook', search('grind'));

    const ctx = pb.asContext('facebook');
    expect(ctx.indexOf('rare gem')).toBeLessThan(ctx.indexOf('grind'));
  });

  /* An average alone cannot tell "nothing here yet" from "thirty posts and never a lead", and only
     one of those deserves another try. */
  it('keeps the raw counts, so the history is readable', () => {
    pb.recordSweep('facebook', search('x'), { posts: 8 });
    pb.recordLead('facebook', search('x'));
    expect(pb.asContext('facebook')).toMatch(/1 lead\(s\) from 8 post\(s\) over 1 sweep\(s\)/);
  });
});