test(explore): add the factory-closure fixture and its selection probe (CG-27)

A file whose top-level symbol spans almost all of it — createFoo() returning
an object of closures — is how Svelte 5 rune stores, React custom-hook modules,
IIFE module-pattern JS and Zustand's create((set,get)=>({…})) are all written.
probe-factory-closure.mjs measures what such a file DELIVERS from within: which
inner symbols' definitions reach the agent, not how many bytes did.
This commit is contained in:
Colby McHenry
2026-08-06 13:58:36 -05:00
parent dc4fd755ef
commit d49265043c
11 changed files with 992 additions and 0 deletions
@@ -0,0 +1,25 @@
/** Minimal fetch helpers the dashboard store depends on. */
export interface RequestOptions {
retries: number;
timeoutMs: number;
}
export const defaultRequestOptions: RequestOptions = { retries: 2, timeoutMs: 5_000 };
/** Build a query string from a plain record, skipping empty values. */
export function toQueryString(params: Record<string, string | number | undefined>): string {
const parts: string[] = [];
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === '') continue;
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
}
return parts.length > 0 ? `?${parts.join('&')}` : '';
}
/** Join a base path and a resource path without doubling the separator. */
export function joinPath(base: string, resource: string): string {
if (base.endsWith('/') && resource.startsWith('/')) return base + resource.slice(1);
if (!base.endsWith('/') && !resource.startsWith('/')) return `${base}/${resource}`;
return base + resource;
}
@@ -0,0 +1,37 @@
import type { MetricSample } from '../stores/types';
/** Statistics helpers shared by the store and the panel. */
export function meanOf(samples: readonly MetricSample[]): number {
if (samples.length === 0) return 0;
let total = 0;
for (const sample of samples) total += sample.value;
return total / samples.length;
}
export function medianOf(samples: readonly MetricSample[]): number {
if (samples.length === 0) return 0;
const values = samples.map((s) => s.value).sort((a, b) => a - b);
const mid = Math.floor(values.length / 2);
return values.length % 2 === 0 ? (values[mid - 1]! + values[mid]!) / 2 : values[mid]!;
}
export function rateOfChange(samples: readonly MetricSample[]): number {
if (samples.length < 2) return 0;
const ordered = samples.slice().sort((a, b) => a.at - b.at);
const first = ordered[0]!;
const last = ordered[ordered.length - 1]!;
const elapsed = last.at - first.at;
return elapsed > 0 ? (last.value - first.value) / elapsed : 0;
}
export function bucketByHour(samples: readonly MetricSample[]): Map<number, MetricSample[]> {
const buckets = new Map<number, MetricSample[]>();
for (const sample of samples) {
const hour = Math.floor(sample.at / 3_600_000);
const bucket = buckets.get(hour);
if (bucket) bucket.push(sample);
else buckets.set(hour, [sample]);
}
return buckets;
}