Phase 2: analysis layer (analyze.js), cache guard, log hygiene

- analyze.js: burn rate, weekly reconstruction, cycle stagger, rotation
  rank, underspend alerts, log prune with weekly archive
- logger.js: getCachedRun(maxAgeMinutes) — skip probing if recent data exists
- monitor.js: cache guard at wake — 20-min dedup, zero extra API calls
- test.js: fix type assertion for gemini-api/xai-api providers (+5 passing);
  add 14 new tests for cache guard and analyze.js (162 total, all green)
- docs/analyze.md: usage reference

Co-authored-by: Hannibal Smith <hannibal@trentuna.com>
This commit is contained in:
Hannibal Smith 2026-04-05 04:49:05 +00:00
parent 1b4e299461
commit 34898b1196
Signed by: hannibal
GPG key ID: 6EB37F7E6190AF1C
6 changed files with 745 additions and 2 deletions

49
test.js
View file

@ -99,7 +99,7 @@ assert('api-ateam in registry', names.includes('api-ateam'));
assert('zai NOT in registry (not anthropic-messages)', !names.includes('zai'));
for (const [name, p] of Object.entries(providers)) {
assert(`${name} has baseUrl`, typeof p.baseUrl === 'string' && p.baseUrl.length > 0);
assert(`${name} has type`, ['teams-direct', 'shelley-proxy', 'api-direct'].includes(p.type));
assert(`${name} has type`, ['teams-direct', 'shelley-proxy', 'api-direct', 'gemini-api', 'xai-api'].includes(p.type));
}
// ── 5. Teams header parser ───────────────────────────────────────────────────
@ -415,6 +415,53 @@ assert('xai 429: severity = critical', x429.severity === 'critical');
const x401 = parseXaiHeaders(null, 401, 'xai-key');
assert('xai 401: status = invalid_key', x401.status === 'invalid_key');
// ── 13. Cache guard (getCachedRun) ─────────────────────────────────────────
console.log('\n── 13. Cache guard ─────────────────────────────────────────────');
const { getCachedRun } = await import('./logger.js');
assert('getCachedRun(0) returns null (zero-minute threshold)', getCachedRun(0) === null);
const cached20 = getCachedRun(20);
assert('getCachedRun(20) returns null or valid object',
cached20 === null || (typeof cached20 === 'object' && typeof cached20.ts === 'string'));
// ── 14. analyze.js smoke tests ───────────────────────────────────────────────
console.log('\n── 14. analyze.js ──────────────────────────────────────────────');
assert('analyze.js exists', existsSync(join(root, 'analyze.js')));
assert('docs/analyze.md exists', existsSync(join(root, 'docs', 'analyze.md')));
const analyzeResult = runSafe('node analyze.js');
assert('analyze.js exits 0', analyzeResult.code === 0,
`exit code: ${analyzeResult.code}\n${analyzeResult.stderr}`);
assert('analyze.js produces output', analyzeResult.stdout.length > 0);
const analyzeJson = runSafe('node analyze.js --json');
assert('analyze.js --json exits 0', analyzeJson.code === 0,
`exit code: ${analyzeJson.code}\n${analyzeJson.stderr}`);
let analyzeData;
try {
analyzeData = JSON.parse(analyzeJson.stdout);
assert('analyze.js --json is valid JSON', true);
} catch (e) {
assert('analyze.js --json is valid JSON', false, e.message);
}
if (analyzeData) {
assert('analyze.js --json has burn_rates', 'burn_rates' in analyzeData);
assert('analyze.js --json has stagger', 'stagger' in analyzeData);
assert('analyze.js --json has rotation', 'rotation' in analyzeData);
assert('analyze.js --json has weekly', 'weekly' in analyzeData);
assert('analyze.js --json stagger is array', Array.isArray(analyzeData.stagger));
assert('analyze.js --json rotation is array', Array.isArray(analyzeData.rotation));
}
const analyzeRotation = runSafe('node analyze.js --rotation');
assert('analyze.js --rotation exits 0', analyzeRotation.code === 0,
`exit code: ${analyzeRotation.code}\n${analyzeRotation.stderr}`);
const analyzePruneDry = runSafe('node analyze.js --prune --dry-run');
assert('analyze.js --prune --dry-run exits 0', analyzePruneDry.code === 0,
`exit code: ${analyzePruneDry.code}\n${analyzePruneDry.stderr}`);
// ── Results ──────────────────────────────────────────────────────────────────
console.log('\n' + '═'.repeat(50));
console.log(`Tests: ${passed + failed} | Passed: ${passed} | Failed: ${failed}`);