import { describe, it, expect } from 'vitest'; import { weave, flattenForPost, type RepoEnvelopes } from '../src/lib/weave'; import type { Envelope } from '../src/types'; function env(uri: string, nodes: string[], triplets: Array<[string, string, string]>): Envelope { return { source: { uri, content_hash: uri }, extractor: { model: 'local-harvest', tier: 'shallow' }, nodes: nodes.map((n) => ({ name: n, embed: true })), triplets: triplets.map(([s, p, o]) => ({ subject: s, predicate: p, object: o })), }; } const repos: RepoEnvelopes[] = [ { repo: 'o/repoA', envelopes: [env('github:o/repoA@x.md', ['Arcrun', '餵食器'], [['Arcrun', '包含', '餵食器']])] }, { repo: 'o/repoB', envelopes: [env('github:o/repoB@y.md', ['Arcrun', '圖層'], [['Arcrun', '依賴', '圖層']])] }, ]; describe('weave', () => { it('偵測跨庫橋(同名節點跨 ≥2 repo)', () => { const r = weave(repos); const bridge = r.bridges.find((b) => b.node === 'Arcrun'); expect(bridge?.repos).toEqual(['o/repoA', 'o/repoB']); expect(r.totalTriplets).toBe(2); }); it('偵測跨庫異見(同 s/o 對、不同謂詞跨 repo)', () => { const diverge: RepoEnvelopes[] = [ { repo: 'o/repoA', envelopes: [env('github:o/repoA@x.md', ['X', 'Y'], [['X', '支持', 'Y']])] }, { repo: 'o/repoB', envelopes: [env('github:o/repoB@y.md', ['X', 'Y'], [['X', '反對', 'Y']])] }, ]; const r = weave(diverge); expect(r.divergences.length).toBe(1); expect(r.divergences[0].predicatesByRepo.map((p) => p.predicate).sort()).toEqual(['反對', '支持']); }); it('flattenForPost 攤平所有 envelope(順序穩定)', () => { expect(flattenForPost(repos).length).toBe(2); }); it('ingest 不算 bridge_score(橋只標 repos,無分數欄位)', () => { const r = weave(repos); expect(r.bridges[0]).not.toHaveProperty('bridge_score'); }); });