diff --git a/src/agent/kpi-record.test.ts b/src/agent/kpi-record.test.ts index c98d3ec..d4d6b99 100644 --- a/src/agent/kpi-record.test.ts +++ b/src/agent/kpi-record.test.ts @@ -4,8 +4,6 @@ import { toKpiRecord } from './kpi-record.ts' const NOW = '2026-07-31T00:00:00.000Z' -// The real tool hardcodes summary: '' and the agent is told not to touch it, so -// this is what every GitHub-only day actually looks like. test('describes the activity when the agent supplied no summary', () => { const record = toKpiRecord(JSON.stringify({ summary: '', commits: [1, 2, 3, 4], pullRequests: [1, 2, 3, 4, 5, 6] }), NOW) @@ -36,9 +34,6 @@ test('maps a complete GitHub payload', () => { assert.equal(record.updated_at, NOW) }) -// The GitHub tool output is relayed through an LLM, so any field can go missing. -// Zeros are the right answer here — but only if they come from genuinely absent -// data, which is what these cases pin down. test('defaults every missing field rather than throwing', () => { const record = toKpiRecord(JSON.stringify({}), NOW) @@ -76,8 +71,6 @@ test('counts empty arrays as zero, not missing', () => { assert.equal(record.prs_count, 0) }) -// A real 0-commit day and a broken payload both produce zeros, so the summary is -// the only thing distinguishing them downstream. Worth keeping honest. test('keeps the summary when counts are legitimately zero', () => { const record = toKpiRecord(JSON.stringify({ summary: 'no code today', commits: [], pullRequests: [] }), NOW) diff --git a/src/agent/llm.ts b/src/agent/llm.ts index e07936f..abf8d03 100644 --- a/src/agent/llm.ts +++ b/src/agent/llm.ts @@ -32,8 +32,6 @@ export const createLlm = (temperature = 0) => { model, apiKey, temperature, - // OpenRouter routes to the next model when one errors or is at capacity. - // It rejects more than 3 entries, primary included. modelKwargs: { models: [model, ...LLM_FALLBACK_MODELS.filter((m) => m !== model)].slice(0, 3) }, configuration: { baseURL: process.env.LLM_BASE_URL || DEFAULT_LLM_BASE_URL, diff --git a/src/agent/notify-error.test.ts b/src/agent/notify-error.test.ts index 31500fb..7df409a 100644 --- a/src/agent/notify-error.test.ts +++ b/src/agent/notify-error.test.ts @@ -21,9 +21,6 @@ afterEach(() => { globalThis.fetch = realFetch }) -// Telegram's HTML mode rejects any tag outside its whitelist with a 400, and the -// alert is then never delivered. Real errors carry markup: gateway 502 bodies are -// HTML, and reasoning models emit . test('escapes markup in the error so Telegram can parse the alert', async () => { await notifyError('AI news search', new Error('Tavily API error 502: upstream connect error')) @@ -55,8 +52,6 @@ test('keeps an oversized error inside Telegram limit', async () => { assert.ok(sent!.text.length <= 4096, `message was ${sent!.text.length} chars`) }) -// Cutting an escaped string can land mid-entity ("&qu"), which Telegram rejects. -// Cutting first and escaping after makes that impossible. test('never emits a half-escaped entity', async () => { await notifyError('AI news search', new Error('"'.repeat(5000))) diff --git a/src/constants/index.ts b/src/constants/index.ts index a1cbc21..a5656fe 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -11,8 +11,7 @@ export const TRENDING_TAG_MAX = 24 export const COMPANY_CLEANUP_TABLE = 'mockTestUsers' export const COMPANY_CLEANUP_THRESHOLD_DAYS = '30' // ── AI news digest (Tavily) ────────────────────────────────────────────────── -// How many stories make the digest. -export const AI_NEWS_TOP_N = 4 +export const AI_NEWS_TOP_N = 4 // How many stories make the digest. export const AI_NEWS_FETCH_N = 10 export const AI_NEWS_LOOKBACK_DAYS = 1 export const AI_NEWS_SNIPPET_MAX = 200 diff --git a/src/index.ts b/src/index.ts index 4c36b7f..ae7bd7f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,8 +39,6 @@ async function main(): Promise { process.exit(0) } - // No recognized args — scheduling is handled by GitHub Actions, not an - // in-process daemon. Point the user at the actual options instead. logger.info('No job specified. Use --job= to run one, or --list-jobs to see all.') printJobs() process.exit(0) diff --git a/src/tools/ai-news-search.tool.ts b/src/tools/ai-news-search.tool.ts index f8cf3eb..cc00b62 100644 --- a/src/tools/ai-news-search.tool.ts +++ b/src/tools/ai-news-search.tool.ts @@ -7,8 +7,6 @@ import type { AiNewsItem } from '../schemas/index.js' const TAVILY_SEARCH_URL = 'https://api.tavily.com/search' -// Only the fields the digest actually uses. `score` and `raw_content` come back -// too and are deliberately dropped — see selectUnseen for why score is unused. type TavilyResult = { title?: string url?: string @@ -25,8 +23,6 @@ function hostnameOf(url: string): string { } } -// Tavily returns RFC-1123 ("Fri, 31 Jul 2026 04:00:00 GMT"). Postgres accepts it, -// but normalising here means the stored value and the message agree. function toIsoDate(raw: string | null | undefined): string | null { if (!raw) return null @@ -35,9 +31,6 @@ function toIsoDate(raw: string | null | undefined): string | null { return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString() } -// The HTTP half: credentials, the call, and the error surface. Throws rather -// than returning empty, so a dead key or a rate limit reaches notifyError -// instead of looking like a quiet news day. async function searchTavily(body: Record): Promise { const apiKey = process.env.TAVILY_API_KEY ?? '' @@ -58,17 +51,10 @@ async function searchTavily(body: Record): Promise, topN: number): AiNewsItem[] { return items.filter((item) => !seenUrls.has(item.url)).slice(0, topN) } -// Tavily sometimes returns a blank or padded title; the url is the last resort -// so a story is never listed with no name at all. function titleOf(r: TavilyResult): string { return r.title?.trim() || String(r.url) } diff --git a/src/tools/ai-news-telegram.test.ts b/src/tools/ai-news-telegram.test.ts index 6f9832a..261b1c3 100644 --- a/src/tools/ai-news-telegram.test.ts +++ b/src/tools/ai-news-telegram.test.ts @@ -75,10 +75,6 @@ test('counts stories with the right plural', () => { assert.match(buildAiNewsMessage([story(), story()], '2026-07-31'), /📊 2 stories /) }) -// Telegram rejects the whole message past 4096, so a digest that overflows is -// not truncated — it is lost. Title and snippet are both bounded now, so this -// really is the worst case, and it fails if TOP_N is raised past what the -// format can carry. test('a worst-case digest stays inside the 4096-char limit Telegram hard-fails at', () => { const worst = Array.from({ length: AI_NEWS_TOP_N }, () => story({ diff --git a/src/tools/manual-kpi.tool.ts b/src/tools/manual-kpi.tool.ts index 3efbf59..ab0871b 100644 --- a/src/tools/manual-kpi.tool.ts +++ b/src/tools/manual-kpi.tool.ts @@ -31,9 +31,6 @@ async function collectActivities(): Promise { return activities } - // Local mode: interactive readline prompt. These go straight to stdout — a - // timestamped, levelled log line would not read as a question, and pino's - // stream does not interleave predictably with readline's own output. prompt('\n──────────────────────────────────────────') prompt('📝 Anything else you did today?') prompt('📝 (Enter each activity on a new line)') diff --git a/src/tools/news-telegram.test.ts b/src/tools/news-telegram.test.ts index ed299ed..f50135d 100644 --- a/src/tools/news-telegram.test.ts +++ b/src/tools/news-telegram.test.ts @@ -61,9 +61,6 @@ test('omits the tag line entirely when the curator returned none', () => { assert.match(message, /⭐ 238,000/) // the rest of the entry is intact }) -// The bug this file was written for: tags were uncapped, so one verbose entry -// pushed the digest past Telegram's limit and Telegram rejected the WHOLE -// message — losing seven perfectly normal repos along with the one bad one. test('one repo with a pathological tags array cannot cost the whole digest', () => { const normal = Array.from({ length: TRENDING_TOP_N - 1 }, () => repo()) const badApple = repo({ tags: Array.from({ length: 40 }, (_, i) => `machine-learning-infrastructure-tag-${i}`) }) @@ -87,8 +84,6 @@ test('a worst-case digest stays inside the limit Telegram hard-fails at', () => assert.ok(buildTrendingMessage(worst, '2026-08-01').length <= TELEGRAM_MAX_CHARS) }) -// Escaping expands text after the per-field caps run (`&` becomes `&`), so -// the caps alone cannot guarantee the invariant — the trim guard does. test('drops trailing repos rather than emitting a message Telegram will reject', () => { const bomb = Array.from({ length: TRENDING_TOP_N }, () => repo({ summary: '&'.repeat(TRENDING_SUMMARY_MAX) })) const message = buildTrendingMessage(bomb, '2026-08-01') diff --git a/src/tools/telegram.test.ts b/src/tools/telegram.test.ts index a9e0939..1484103 100644 --- a/src/tools/telegram.test.ts +++ b/src/tools/telegram.test.ts @@ -2,10 +2,6 @@ import { test } from 'node:test' import assert from 'node:assert/strict' import { sendTelegramMessage } from './telegram.ts' -// Both digests deliver through this, so its failure modes are the ones that -// decide whether a digest arrives or vanishes. -// Restores process.env wholesale rather than key by key — the branchier version -// of this helper was itself complex enough to trip the CRAP gate. async function withEnvAndFetch(env: NodeJS.ProcessEnv, stub: typeof fetch, fn: () => Promise): Promise { const savedEnv = { ...process.env } const realFetch = globalThis.fetch diff --git a/src/tools/telegram.ts b/src/tools/telegram.ts index 5b88dfb..da35a7f 100644 --- a/src/tools/telegram.ts +++ b/src/tools/telegram.ts @@ -1,15 +1,5 @@ import { logger } from '../utils/logger.js' -/** - * Posts an HTML message to the configured Telegram chat. - * - * Both digests reached Telegram through the same twenty lines — read two env - * vars, validate them, POST, check the status, log. This is that, once. - * Message building stays in the tools; only delivery lives here. - * - * Throws on missing credentials and on any non-2xx, so callers can report the - * failure through notifyError rather than guessing. - */ export async function sendTelegramMessage(html: string, label: string): Promise { const botToken = process.env.TELEGRAM_BOT_TOKEN ?? '' const chatId = process.env.TELEGRAM_CHAT_ID ?? '' diff --git a/src/tools/trending-scrape.tool.ts b/src/tools/trending-scrape.tool.ts index 1707d75..e8ac97f 100644 --- a/src/tools/trending-scrape.tool.ts +++ b/src/tools/trending-scrape.tool.ts @@ -24,8 +24,7 @@ function parseTrendingHtml(html: string): TrendingRepo[] { const langMatch = block.match(/([\s\S]*?)<\/span>/) const language = langMatch ? langMatch[1].trim() : '' - // Total stars — the count sits after the star icon inside the stargazers link, - // so we have to skip the icon markup rather than read straight after the opening tag. + // Total stars const starsMatch = block.match(/href="\/[^"]*\/stargazers"[\s\S]*?<\/svg>\s*([\d,]+)/) const stars = starsMatch ? parseInt(starsMatch[1].replace(/,/g, ''), 10) : 0