/** * activity-ledger — the ingestion + aggregation side of the Eddie-reporter * feedback loop (companion to the client in `@brad-frost-web/eddie-reporter`). * * Downstream Eddie products statically scan themselves for Eddie asset usage * and POST a usage report to the brain. This module normalizes those reports, * appends them to an append-only event log, and maintains a per-product * roll-up, all persisted to `.eddie-brain/activity.json`. * * DURABILITY / EXEMPTION: like `learning.json` (the correction flywheel) and * `adoption.json` (the org dependency snapshot), `activity.json` is RUNTIME * STATE. It accrues from usage over time rather than being derived from source, * so it is preserved across `brain init` regenerations and is exempt from the * CI graph-freshness gate. Recording activity must never force a graph rebuild. * * ADOPTION vs ACTIVITY: `adoption.json` answers "which repos declare an Eddie * dependency" (coarse, from package.json). This ledger answers "which assets * are actually used, how heavily, and how recently" (fine-grained, from a * static source scan). Together they turn a static catalog into a living, * usage-weighted picture of the system. */ /** Current on-disk schema version for activity.json. */ export declare const ACTIVITY_SCHEMA_VERSION = 1; /** A per-bucket map of asset name → occurrence count. */ export type UsageMap = Record; /** The usage portion of an incoming report. */ export interface ReportUsage { components: UsageMap; recipes: UsageMap; pages: UsageMap; tokens: UsageMap; filesScanned: number; filesMatched: number; } /** The shape a reporter POSTs. Mirrors `eddie-reporter`'s UsageReport, but the * brain treats every field defensively — reporters in the wild may be older. */ export interface IncomingReport { schema?: string; reporterVersion?: string; generatedAt?: string; event?: string; product?: { id?: string; name?: string; repo?: string; branch?: string; commit?: string; environment?: string; }; eddie?: { declared?: Record; packages?: string[]; }; usage?: Partial; } /** A normalized, stored activity event. */ export interface ActivityEvent { id: string; receivedAt: string; generatedAt: string; event: string; reporterVersion: string; product: { id: string; name?: string; repo?: string; branch?: string; commit?: string; environment: string; }; eddie: { declared: Record; packages: string[]; }; usage: ReportUsage; } /** Rolled-up state for a single product (its latest known usage). */ export interface ProductRollup { id: string; name?: string; repo?: string; environment: string; firstSeen: string; lastSeen: string; lastEvent: string; lastCommit?: string; reportCount: number; packages: string[]; declared: Record; latestUsage: ReportUsage; } export interface ActivitySnapshot { version: number; updatedAt: string; events: ActivityEvent[]; products: Record; } /** Per-asset aggregate across products. */ export interface AssetUsage { asset: string; products: number; occurrences: number; } export interface ActivityStats { updatedAt: string; totals: { products: number; activeProducts: number; staleProducts: number; events: number; }; /** Assets sorted by how many products use them (desc), then occurrences. */ components: AssetUsage[]; recipes: AssetUsage[]; pages: AssetUsage[]; tokens: AssetUsage[]; /** * Tags a product reported that are not in the catalog (#1845). * * The reporter is a zero-dependency static scanner running on the consumer's * machine; it has no catalog to check against, so `` written in a blog * post's prose reaches the ledger looking exactly like a real component. The * first production beacon carried one such phantom among 36 distinct tags, * and `eddie_get_activity` duly reported it as a used component. * * These are partitioned out of the buckets above rather than dropped: an * unrecognised tag is either noise (report it so the regex can be tightened) * or a component this Brain's graph is behind on (report it so someone * notices). Silently discarding it would hide both. Only populated when * `getStats` is given a catalog — with no catalog, nothing is unrecognised * and the buckets behave exactly as before. */ unrecognised: { components: AssetUsage[]; recipes: AssetUsage[]; pages: AssetUsage[]; }; staleProducts: { id: string; lastSeen: string; }[]; } /** Options for {@link ActivityLedger.getStats}. */ export interface GetStatsOptions { /** * Lowercased tag names the catalog knows about. When supplied, reported tags * outside this set are moved to `unrecognised` instead of counting as usage. * Omit it and every reported tag is taken at face value (previous behaviour). */ knownTags?: Set; } /** * Normalize an untrusted incoming report into a stored ActivityEvent. Returns * null when the report has no usable product identity — the one field we can't * synthesize. `idFactory`/`now` are injectable so this is deterministic in * tests. */ export declare function normalizeReport(raw: IncomingReport, opts?: { now?: string; id?: string; }): ActivityEvent | null; /** * ActivityLedger — load, record into, and query `.eddie-brain/activity.json`. * Append-only event log plus a derived per-product roll-up; both live in one * file for atomic save. */ export declare class ActivityLedger { private snapshot; constructor(snapshot?: ActivitySnapshot); /** Load from disk, tolerating a missing/corrupt file (starts empty). */ static load(filePath: string): ActivityLedger; /** * Build a ledger from an already-parsed snapshot (a file, or the blob the * flush publishes — see activity-source.ts). Null when the shape is not a * ledger at all; missing containers are backfilled. */ static fromSnapshot(data: ActivitySnapshot | null | undefined): ActivityLedger | null; /** Record a normalized event: append to the log and update the product roll-up. */ record(event: ActivityEvent): void; /** Convenience: normalize + record in one call. Returns the stored event, or null if unusable. */ ingest(raw: IncomingReport, opts?: { now?: string; id?: string; }): ActivityEvent | null; getSnapshot(): ActivitySnapshot; getProducts(): ProductRollup[]; getProduct(id: string): ProductRollup | undefined; getRecentEvents(limit?: number): ActivityEvent[]; /** * Aggregate the latest usage across every product into per-asset stats: * how many products use each asset and total occurrences. Uses each * product's most-recent report (its roll-up), so a product is counted once * regardless of how many times it reported. */ getStats(now?: string, opts?: GetStatsOptions): ActivityStats; /** Persist to disk (pretty-printed for reviewable diffs). */ save(filePath: string): void; } //# sourceMappingURL=activity-ledger.d.ts.map