moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,425 @@
#!/usr/bin/env tsx
/**
* Phase 5 §0.3 — Cost projection probe.
*
* Per PM ratification 2026-04-29 Phase 5 §0 preflight Ask #2: AUTHORIZED.
* Per brief §0.3 #3 + §5.4 cost ceiling computation.
*
* Sends 5 prompts of varying complexity to each Phase 5 deployed variant via
* the existing LiteLLM proxy (matches Faza 1 runner pattern):
* - claude::gen1-v1 model alias = claude-opus-4-7
* - qwen-thinking::gen1-v1 model alias = qwen3.6-35b-a3b-via-dashscope-direct
*
* Records actual input/output tokens × pricing snapshot 2026-04-29.
* Emits per-request JSONL + computes p50/p95/max.
*
* Validates canary_cost_p95_ceiling = 740 × max(p95) × 1.20 ≤ $20.
*
* Pricing snapshot (2026-04-29 — public docs URL):
* - Claude Opus 4.7: $5/M input, $25/M output (NEW Opus 4.7 pricing per
* platform.claude.com/docs/en/docs/about-claude/pricing 2026-04-29)
* - DashScope Qwen 35B-A3B intl thinking: $0.25/M input, $2/M output
* (alibabacloud.com/help/en/model-studio/billing-for-model-studio 2026-04-29)
*
* NOTE on Faza 1 pricing reference: run-checkpoint-c.ts uses different
* historical pricing ($15/$75 for Opus, $0.20/$0.80 for Qwen). Phase 5
* uses the 2026-04-29 snapshot above (Opus 4.7 reduced rate; Qwen intl
* thinking-mode rate). Probe pricing is the binding source for §0.3.
*
* Usage: npx tsx gepa-phase-5/scripts/cost-probe.ts
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { claudeGen1V1Shape } from '../../packages/agent/src/prompt-shapes/gepa-evolved/claude-gen1-v1.js';
import { qwenThinkingGen1V1Shape } from '../../packages/agent/src/prompt-shapes/gepa-evolved/qwen-thinking-gen1-v1.js';
// Manual .env load (no dotenv dependency; keep probe self-contained)
const envPath = path.resolve(process.cwd(), '.env');
if (fs.existsSync(envPath)) {
const raw = fs.readFileSync(envPath, 'utf-8');
for (const line of raw.split(/\r?\n/)) {
const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line.trim());
if (m && !process.env[m[1]]) {
let v = m[2];
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
v = v.slice(1, -1);
}
process.env[m[1]] = v;
}
}
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '../..');
const OUT_JSONL = path.join(REPO_ROOT, 'gepa-phase-5/cost-probe-2026-04-29.jsonl');
const OUT_SUMMARY = path.join(REPO_ROOT, 'gepa-phase-5/cost-probe-2026-04-29-summary.md');
const LITELLM_URL = process.env.LITELLM_URL ?? 'http://localhost:4000';
const LITELLM_MASTER_KEY = process.env.LITELLM_MASTER_KEY;
// 2026-04-29 pricing snapshot (Phase 5 binding)
const PRICING = {
'claude::gen1-v1': { in_per_m: 5.0, out_per_m: 25.0, model_alias: 'claude-opus-4-7' },
'qwen-thinking::gen1-v1': {
in_per_m: 0.25,
out_per_m: 2.0,
model_alias: 'qwen3.6-35b-a3b-via-dashscope-direct',
},
} as const;
interface ProbePrompt {
complexity: 'trivial' | 'medium-1' | 'medium-2' | 'complex' | 'stretch';
persona: string;
materials: string;
question: string;
max_tokens: number;
}
const PROMPTS: ProbePrompt[] = [
{
complexity: 'trivial',
persona: 'a concise factual analyst',
materials: 'The number two added to the number two equals four. This is elementary arithmetic.',
question: 'What is 2+2?',
max_tokens: 200,
},
{
complexity: 'medium-1',
persona: 'a CFO advisor at a mid-stage SaaS startup',
materials: [
'Q3 metrics for Acme SaaS (Series B, $12M ARR):',
'- ARR growth slowed from 18% QoQ to 9% QoQ.',
'- Gross margin held at 78%.',
'- Net dollar retention dropped from 121% to 108%.',
'- Churn ticked up in SMB segment from 1.4% to 2.1% monthly.',
'- Sales cycle elongated 22% in mid-market.',
'- Two competitors raised Series C at higher valuations.',
].join('\n'),
question: 'List the 3 highest-priority risks the CEO should brief the board on, in order.',
max_tokens: 600,
},
{
complexity: 'medium-2',
persona: 'a software architect advising on legacy modernization',
materials: [
'Context: 8-year-old Rails monolith. ~400k LOC. Critical revenue path.',
'Team: 12 backend engineers, 4 platform engineers. No prior modernization experience.',
'Pressure: Sales reports the monolith blocks integration deals worth ~$8M ARR.',
'Constraints: 9-month runway-extension clock; CEO wants visible progress every 60 days.',
].join('\n'),
question: 'Compare strangler-fig vs big-bang refactor for this team. Recommend one with explicit risk acknowledgment.',
max_tokens: 800,
},
{
complexity: 'complex',
persona: 'a strategic operations consultant',
materials: [
'NorthLane Retail (mid-market apparel chain):',
'- Q3 SSS missed plan by 8% (-3.2% vs +5% planned).',
'- Macro: discretionary spend down 4% sector-wide; 30y yield up 80bps in quarter.',
'- Competitive: Aritzia opened 4 new doors in core trade areas; Old Navy ran 30% friends-and-family promo for 6 weeks.',
'- Internal: New POS system rollout caused 6% transaction-error rate for 3 weeks; e-commerce site speed regressed (LCP 2.4s -> 4.1s).',
'- Brand: Latest TikTok creator partnership underperformed engagement targets by 60%; Brand consideration score flat.',
'- Inventory: $42M aged 90+ days; merch margin compressed 180bps to fund clearance.',
].join('\n'),
question: 'Diagnose root causes across 4 dimensions (macro / competitive / internal execution / brand). For each, attribute approximate share of the 8% miss + propose one targeted Q4 intervention.',
max_tokens: 1200,
},
{
complexity: 'stretch',
persona: 'an M&A analyst preparing an investment memo for the deal committee',
materials: [
'Target: Cobalt Cloud (Series D enterprise SaaS, governance + audit logging).',
'- ARR: $50M trailing; +52% YoY; 110% NDR.',
'- Customers: 320 logos, top-10 = 28% of ARR. Net new logos +18% YoY.',
'- Gross margin: 76% (best-in-class for category 73-78%).',
'- Sales efficiency (Magic Number): 1.1 (LTM); CAC payback 16 months.',
'- Burn: $4M/quarter, 18 months runway.',
'- Last round: $750M post on $35M ARR (21x), 2.5 years ago.',
'- Comps: Drata (~25x ARR), Vanta (~22x ARR), Hyperproof (~14x ARR), AuditBoard (public, 8x).',
'- Recent context: 2 strategic offers verbal at 12-15x ARR. Founder open to strategic at 15x+.',
].join('\n'),
question: [
'Walk through three valuation methods for Cobalt Cloud:',
'1) DCF — state assumptions explicit (revenue growth fade, margin trajectory, discount rate, terminal multiple).',
'2) Trading comps — apply weighted multiple from 4 comps to next-twelve-months revenue.',
'3) LBO — assume Sponsor offer at $750M EV, 6.5x leverage, 5y hold, exit at 12x EBITDA. State minimum revenue CAGR for 25% IRR.',
'Reconcile any gap > 25% between the three methods + recommend offer band.',
].join('\n'),
max_tokens: 1500,
},
];
interface ProbeResult {
variant: 'claude::gen1-v1' | 'qwen-thinking::gen1-v1';
complexity: ProbePrompt['complexity'];
model_alias: string;
input_tokens: number;
output_tokens: number;
cost_usd: number;
latency_ms: number;
ts: string;
error?: string;
}
function appendJsonl(file: string, row: ProbeResult) {
fs.appendFileSync(file, JSON.stringify(row) + '\n');
}
async function callViaLitellm(model: string, system: string, user: string, max_tokens: number) {
if (!LITELLM_MASTER_KEY) throw new Error('LITELLM_MASTER_KEY not set');
const payload = {
model,
messages: [
{ role: 'system', content: system },
{ role: 'user', content: user },
],
max_tokens,
};
const t0 = Date.now();
const resp = await fetch(`${LITELLM_URL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${LITELLM_MASTER_KEY}`,
},
body: JSON.stringify(payload),
});
const latency_ms = Date.now() - t0;
const data = (await resp.json()) as Record<string, unknown>;
if (!resp.ok || 'error' in data) {
const errMsg =
(data as { error?: { message?: string } }).error?.message ??
`HTTP ${resp.status}: ${JSON.stringify(data).slice(0, 200)}`;
return { ok: false as const, error: String(errMsg), latency_ms };
}
const choices = data.choices as Array<{ message: { content: string } }> | undefined;
const usage = data.usage as { prompt_tokens?: number; completion_tokens?: number } | undefined;
return {
ok: true as const,
content: choices?.[0]?.message?.content ?? '',
input_tokens: usage?.prompt_tokens ?? 0,
output_tokens: usage?.completion_tokens ?? 0,
latency_ms,
};
}
async function probe(
variant: 'claude::gen1-v1' | 'qwen-thinking::gen1-v1',
prompt: ProbePrompt,
): Promise<ProbeResult> {
const shape = variant === 'claude::gen1-v1' ? claudeGen1V1Shape : qwenThinkingGen1V1Shape;
const config = PRICING[variant];
const sysPrompt = shape.systemPrompt({
persona: prompt.persona,
question: prompt.question,
isMultiStep: false,
});
const userPrompt = shape.soloUserPrompt({
persona: prompt.persona,
materials: prompt.materials,
question: prompt.question,
});
const result = await callViaLitellm(config.model_alias, sysPrompt, userPrompt, prompt.max_tokens);
if (!result.ok) {
return {
variant,
complexity: prompt.complexity,
model_alias: config.model_alias,
input_tokens: 0,
output_tokens: 0,
cost_usd: 0,
latency_ms: result.latency_ms,
ts: new Date().toISOString(),
error: result.error,
};
}
const cost_usd =
(result.input_tokens * config.in_per_m) / 1e6 +
(result.output_tokens * config.out_per_m) / 1e6;
return {
variant,
complexity: prompt.complexity,
model_alias: config.model_alias,
input_tokens: result.input_tokens,
output_tokens: result.output_tokens,
cost_usd,
latency_ms: result.latency_ms,
ts: new Date().toISOString(),
};
}
function percentile(values: number[], p: number): number {
if (values.length === 0) return 0;
const sorted = [...values].sort((a, b) => a - b);
const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
return sorted[idx];
}
interface VariantSummary {
variant: string;
count: number;
errors: number;
p50: number;
p95: number;
max: number;
mean: number;
total: number;
}
function summarize(results: ProbeResult[], variant: string): VariantSummary {
const ok = results.filter((r) => r.variant === variant && !r.error);
const errors = results.filter((r) => r.variant === variant && r.error).length;
if (ok.length === 0) {
return {
variant,
count: 0,
errors,
p50: NaN,
p95: NaN,
max: NaN,
mean: NaN,
total: 0,
};
}
const costs = ok.map((r) => r.cost_usd);
const total = costs.reduce((a, b) => a + b, 0);
return {
variant,
count: ok.length,
errors,
p50: percentile(costs, 50),
p95: percentile(costs, 95),
max: Math.max(...costs),
mean: total / ok.length,
total,
};
}
async function main() {
fs.writeFileSync(OUT_JSONL, '');
console.log('Phase 5 §0.3 cost probe — start');
console.log(`LiteLLM: ${LITELLM_URL}`);
console.log(`Output JSONL: ${OUT_JSONL}`);
const results: ProbeResult[] = [];
for (const variant of ['claude::gen1-v1', 'qwen-thinking::gen1-v1'] as const) {
for (const prompt of PROMPTS) {
console.log(` ${variant} <- ${prompt.complexity}`);
const r = await probe(variant, prompt);
results.push(r);
appendJsonl(OUT_JSONL, r);
if (r.error) console.log(` ERROR: ${r.error}`);
else
console.log(
` in=${r.input_tokens} out=${r.output_tokens} cost=$${r.cost_usd.toFixed(4)} latency=${r.latency_ms}ms`,
);
}
}
const claudeSummary = summarize(results, 'claude::gen1-v1');
const qwenSummary = summarize(results, 'qwen-thinking::gen1-v1');
const validP95s = [claudeSummary.p95, qwenSummary.p95].filter(
(v) => Number.isFinite(v) && v > 0,
);
const max_p95 = validP95s.length > 0 ? Math.max(...validP95s) : NaN;
const VOLUME = 740;
const BUFFER = 1.2;
const canary_cost_p95_ceiling = Number.isFinite(max_p95) ? VOLUME * max_p95 * BUFFER : NaN;
const HALT = 20;
const HARD_CAP = 25;
const lines: string[] = [];
lines.push('# Phase 5 §0.3 Cost Probe Summary');
lines.push('');
lines.push(`**Date:** ${new Date().toISOString()}`);
lines.push(`**Pricing snapshot:** 2026-04-29`);
lines.push(`**Branch:** phase-5-deployment-v2`);
lines.push(`**Endpoint:** ${LITELLM_URL} (LiteLLM proxy, matches Faza 1 runner pattern)`);
lines.push('');
lines.push('## Per-variant statistics');
lines.push('');
lines.push('| Variant | Model alias | OK | Errors | p50 | p95 | max | mean | total |');
lines.push('|---|---|---|---|---|---|---|---|---|');
for (const s of [claudeSummary, qwenSummary]) {
const config = PRICING[s.variant as keyof typeof PRICING];
if (s.count === 0) {
lines.push(`| ${s.variant} | ${config.model_alias} | 0 | ${s.errors} | n/a | n/a | n/a | n/a | n/a |`);
} else {
lines.push(
`| ${s.variant} | ${config.model_alias} | ${s.count} | ${s.errors} | $${s.p50.toFixed(4)} | $${s.p95.toFixed(4)} | $${s.max.toFixed(4)} | $${s.mean.toFixed(4)} | $${s.total.toFixed(4)} |`,
);
}
}
lines.push('');
lines.push('## Canary cost ceiling (per brief §5.4)');
lines.push('');
lines.push(
`Formula: \`canary_cost_p95_ceiling = ${VOLUME} requests × max(p95) × ${BUFFER} (buffer)\``,
);
lines.push('');
if (Number.isFinite(max_p95)) {
lines.push(`max(p95) = $${max_p95.toFixed(4)}`);
lines.push(
`canary_cost_p95_ceiling = ${VOLUME} × $${max_p95.toFixed(4)} × ${BUFFER} = **$${canary_cost_p95_ceiling.toFixed(2)}**`,
);
} else {
lines.push(`max(p95) = NaN (no successful samples)`);
lines.push(`canary_cost_p95_ceiling = INDETERMINATE`);
}
lines.push('');
lines.push('## Verdict');
lines.push('');
if (Number.isFinite(canary_cost_p95_ceiling)) {
if (canary_cost_p95_ceiling <= HALT) {
lines.push(
`**PASS** — ceiling $${canary_cost_p95_ceiling.toFixed(2)} <= halt trigger $${HALT}.`,
);
} else if (canary_cost_p95_ceiling <= HARD_CAP) {
lines.push(
`**HALT-AND-PM** — ceiling $${canary_cost_p95_ceiling.toFixed(2)} > halt trigger $${HALT}, but <= hard cap $${HARD_CAP}.`,
);
} else {
lines.push(
`**HARD-CAP-EXCEED** — ceiling $${canary_cost_p95_ceiling.toFixed(2)} > hard cap $${HARD_CAP}.`,
);
}
} else {
lines.push(`**INDETERMINATE** — no valid p95.`);
}
lines.push('');
lines.push('## Probe spend (this script)');
const totalSpend = (claudeSummary.total ?? 0) + (qwenSummary.total ?? 0);
lines.push('');
lines.push(`Total: **$${totalSpend.toFixed(4)}**`);
lines.push(`Brief §5.4 probe budget: $0.30-$0.50.`);
lines.push('');
lines.push('## JSONL anchor');
lines.push('');
lines.push('Per-request rows: `gepa-phase-5/cost-probe-2026-04-29.jsonl`');
const summaryText = lines.join('\n');
fs.writeFileSync(OUT_SUMMARY, summaryText);
console.log('');
console.log('==========================================');
console.log(summaryText);
console.log('==========================================');
console.log(`Summary: ${OUT_SUMMARY}`);
}
main().catch((err) => {
console.error('FATAL:', err);
process.exit(1);
});

View File

@@ -0,0 +1,244 @@
#!/usr/bin/env tsx
/**
* Phase 5 §3.3 — Daily monitoring summary aggregator.
*
* Reads JSONL emit files from `gepa-phase-5/monitoring/<ISO_date>/<variant>.jsonl`
* and alert log from `gepa-phase-5/phase-5-alerts/<ISO_date>.jsonl`, then
* writes a markdown summary to `gepa-phase-5/phase-5-daily-summary/<ISO_date>.md`.
*
* Per brief §3.3 reading cadence: PM-side reading is 1×/day during canary
* Day 0-5, then 2×/week. This script is intended to run once per UTC day.
*
* Per brief §3.4 Stage 1 deliverable: JSONL files + daily markdown summary
* (no UI). Stage 2 dashboard deferred post-launch.
*
* Usage:
* npx tsx gepa-phase-5/scripts/phase-5-daily-summary.ts # default = today UTC
* npx tsx gepa-phase-5/scripts/phase-5-daily-summary.ts 2026-04-30
*
* Audit: gepa-phase-5/manifest.yaml § halt_and_pm_triggers, § rollback_triggers.
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
// gepa-phase-5/scripts/phase-5-daily-summary.ts → repo root
const REPO_ROOT = path.resolve(path.dirname(__filename), '../..');
const MONITORING_DIR = path.join(REPO_ROOT, 'gepa-phase-5', 'monitoring');
const ALERTS_DIR = path.join(REPO_ROOT, 'gepa-phase-5', 'phase-5-alerts');
const SUMMARY_DIR = path.join(REPO_ROOT, 'gepa-phase-5', 'phase-5-daily-summary');
interface MetricEntry {
ts: string;
variant: string;
request_id: string;
metric_name: string;
metric_value: number;
baseline_comparison?: { baseline_value: number; delta?: number };
error_type?: string;
}
interface AlertEntry {
ts: string;
trigger_id: string;
variant: string;
metric_name: string;
observed_value: number;
threshold: number;
is_rollback_trigger: boolean;
diagnostic?: string;
}
function todayIsoUtc(): string {
return new Date().toISOString().slice(0, 10);
}
function readJsonlSafe<T>(filePath: string): T[] {
if (!fs.existsSync(filePath)) return [];
const raw = fs.readFileSync(filePath, 'utf-8');
const lines = raw.split('\n').filter((l) => l.trim().length > 0);
return lines.map((l) => JSON.parse(l) as T);
}
function p50(values: number[]): number | null {
if (values.length === 0) return null;
const sorted = [...values].sort((a, b) => a - b);
return sorted[Math.floor(sorted.length / 2)] ?? null;
}
function p95(values: number[]): number | null {
if (values.length === 0) return null;
const sorted = [...values].sort((a, b) => a - b);
return sorted[Math.floor(sorted.length * 0.95)] ?? null;
}
function mean(values: number[]): number | null {
if (values.length === 0) return null;
return values.reduce((a, b) => a + b, 0) / values.length;
}
function ensureDir(dir: string): void {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
interface VariantSummary {
variant: string;
totalEvents: number;
perMetric: Record<
string,
{
n: number;
mean?: number;
p50?: number;
p95?: number;
}
>;
errorBreakdown?: Record<string, number>;
}
function summarizeVariant(variant: string, entries: MetricEntry[]): VariantSummary {
const summary: VariantSummary = {
variant,
totalEvents: entries.length,
perMetric: {},
};
const byMetric = new Map<string, number[]>();
const errorTypes = new Map<string, number>();
for (const e of entries) {
if (!byMetric.has(e.metric_name)) byMetric.set(e.metric_name, []);
byMetric.get(e.metric_name)!.push(e.metric_value);
if (e.metric_name === 'error' && e.error_type) {
errorTypes.set(e.error_type, (errorTypes.get(e.error_type) ?? 0) + 1);
}
}
for (const [metric, vals] of byMetric.entries()) {
summary.perMetric[metric] = {
n: vals.length,
mean: mean(vals) ?? undefined,
p50: p50(vals) ?? undefined,
p95: p95(vals) ?? undefined,
};
}
if (errorTypes.size > 0) {
summary.errorBreakdown = Object.fromEntries(errorTypes.entries());
}
return summary;
}
function fmtNum(v: number | undefined, decimals = 4): string {
if (v === undefined || !Number.isFinite(v)) return '-';
return v.toFixed(decimals);
}
function formatMarkdown(date: string, summaries: VariantSummary[], alerts: AlertEntry[]): string {
const lines: string[] = [];
lines.push(`# Phase 5 Daily Summary — ${date}`);
lines.push('');
lines.push(`**Generated:** ${new Date().toISOString()}`);
lines.push(`**Source:** \`gepa-phase-5/monitoring/${date}/*.jsonl\` + \`gepa-phase-5/phase-5-alerts/${date}.jsonl\``);
lines.push(`**Manifest:** \`gepa-phase-5/manifest.yaml\``);
lines.push('');
// Alerts first (top of summary so PM sees rollback triggers immediately).
lines.push('## Alerts');
lines.push('');
if (alerts.length === 0) {
lines.push('_No alerts._');
} else {
const rollbackAlerts = alerts.filter((a) => a.is_rollback_trigger);
const informational = alerts.filter((a) => !a.is_rollback_trigger);
if (rollbackAlerts.length > 0) {
lines.push(`### 🚨 ROLLBACK TRIGGERS (${rollbackAlerts.length})`);
lines.push('');
for (const a of rollbackAlerts) {
lines.push(`- **${a.trigger_id}** — ${a.variant}\`${a.metric_name}\` observed=${fmtNum(a.observed_value)} threshold=${fmtNum(a.threshold)}`);
if (a.diagnostic) lines.push(` - ${a.diagnostic}`);
}
lines.push('');
}
if (informational.length > 0) {
lines.push(`### Informational alerts (${informational.length})`);
lines.push('');
for (const a of informational) {
lines.push(`- ${a.trigger_id}${a.variant} — observed=${fmtNum(a.observed_value)}`);
}
lines.push('');
}
}
lines.push('');
// Per-variant aggregates.
lines.push('## Per-variant metrics');
lines.push('');
if (summaries.length === 0) {
lines.push('_No metric events recorded._');
} else {
for (const s of summaries) {
lines.push(`### ${s.variant} (events: ${s.totalEvents})`);
lines.push('');
lines.push('| Metric | n | mean | p50 | p95 |');
lines.push('|---|---|---|---|---|');
for (const [metric, agg] of Object.entries(s.perMetric)) {
lines.push(
`| ${metric} | ${agg.n} | ${fmtNum(agg.mean)} | ${fmtNum(agg.p50)} | ${fmtNum(agg.p95)} |`,
);
}
if (s.errorBreakdown) {
lines.push('');
lines.push('**Error breakdown:**');
for (const [type, count] of Object.entries(s.errorBreakdown)) {
lines.push(`- ${type}: ${count}`);
}
}
lines.push('');
}
}
lines.push('---');
lines.push('');
lines.push('**Cross-references:**');
lines.push('- Manifest: `gepa-phase-5/manifest.yaml` (LOCKED scope + thresholds)');
lines.push('- Brief: `D:/Projects/PM-Waggle-OS/briefs/2026-04-29-phase-5-deployment-brief-v1.md` §3 monitoring + §4 exit criteria');
lines.push('- Cost amendment: `D:/Projects/PM-Waggle-OS/decisions/2026-04-30-phase-5-cost-amendment-LOCKED.md`');
lines.push('');
return lines.join('\n');
}
function main(): void {
const date = process.argv[2] ?? todayIsoUtc();
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
process.stderr.write(`Invalid date format: ${date}. Expected YYYY-MM-DD.\n`);
process.exit(2);
}
const monitoringDateDir = path.join(MONITORING_DIR, date);
const summaries: VariantSummary[] = [];
if (fs.existsSync(monitoringDateDir)) {
const files = fs.readdirSync(monitoringDateDir).filter((f) => f.endsWith('.jsonl'));
for (const file of files) {
const variantSafe = file.replace(/\.jsonl$/, '');
const entries = readJsonlSafe<MetricEntry>(path.join(monitoringDateDir, file));
// Recover real variant name from entries[0] (sanitize is one-way).
const realVariant = entries[0]?.variant ?? variantSafe;
summaries.push(summarizeVariant(realVariant, entries));
}
}
const alertsFile = path.join(ALERTS_DIR, `${date}.jsonl`);
const alerts = readJsonlSafe<AlertEntry>(alertsFile);
const markdown = formatMarkdown(date, summaries, alerts);
ensureDir(SUMMARY_DIR);
const outFile = path.join(SUMMARY_DIR, `${date}.md`);
fs.writeFileSync(outFile, markdown, 'utf-8');
process.stdout.write(`Wrote: ${outFile}\n`);
process.stdout.write(`Variants summarized: ${summaries.length}\n`);
process.stdout.write(`Alerts: ${alerts.length} (rollback triggers: ${alerts.filter((a) => a.is_rollback_trigger).length})\n`);
}
main();