From c4e074e852eb14fe47296d5053fa3650c503d7b9 Mon Sep 17 00:00:00 2001 From: damengrandom Date: Thu, 30 Jul 2026 21:12:47 +1000 Subject: [PATCH] feat(T24): remove node-cron daemon and local crontab automation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions is the only automation path actually running in production (every workflow calls a single job via --job=, never `pnpm dev` with no args). The in-process node-cron daemon duplicated schedule definitions already living in the workflow YAML and required a persistently-running process with no supervisor — strictly weaker than both GitHub Actions and the macOS crontab docs it sat alongside. - Remove src/jobs/scheduler.ts and the node-cron/@types/node-cron deps - No-args CLI invocation now prints usage + job list instead of starting a daemon - Keep Job.schedule/timezone on the registry — still used by `pnpm jobs` for informational display - Update README (Commands, Project structure) and stale comments referencing the deleted scheduler Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CtRxtJgwNeyMyTnYxuxoH6 --- README.md | 19 ++---------------- package.json | 2 -- pnpm-lock.yaml | 17 ---------------- src/agent/curator-graph.test.ts | 2 +- src/agent/curator.graph.ts | 6 +++--- src/agent/index.ts | 6 +++--- src/index.ts | 7 +++++-- src/jobs/registry.ts | 5 +++-- src/jobs/scheduler.ts | 33 ------------------------------- src/schemas/index.ts | 16 +++++++++++---- src/tools/trending-scrape.tool.ts | 10 +--------- 11 files changed, 30 insertions(+), 93 deletions(-) delete mode 100644 src/jobs/scheduler.ts diff --git a/README.md b/README.md index ba7742f..71c6375 100644 --- a/README.md +++ b/README.md @@ -79,20 +79,6 @@ Trigger manually: **Actions tab → select workflow → Run workflow**. The Dail --- -## Automate locally (macOS crontab) - -```bash -crontab -e -``` -``` -PATH=/Users/yourname/.nvm/versions/node/v22.14.0/bin:/opt/homebrew/bin:/usr/bin:/bin -TZ=Australia/Sydney -0 17 * * * /opt/homebrew/bin/pnpm --prefix /path/to/project cleanup >> /path/to/project/data/cron.log 2>&1 -``` -> Mac must be awake at 5pm — GitHub Actions is more reliable for unattended runs. - ---- - ## Commands | Command | What it does | @@ -103,7 +89,6 @@ TZ=Australia/Sydney | `pnpm news` | Scrape, curate, send via Telegram — alias for `--job=news` | | `pnpm jobs` | List every registered job with its cron schedule | | `pnpm run dev --job=` | Run any registered job by name | -| `pnpm dev` | Long-running daemon (node-cron) | | `pnpm seed-mock` | Seed expired mock users into company DB | | `pnpm test` | Run the unit test suite (`node:test` via `tsx`) | | `pnpm tsc` | TypeScript type check | @@ -124,7 +109,7 @@ src/ │ ├── curator-graph.test.ts # Unit tests for the curator retry graph │ └── *.agent.ts # One focused agent per task ├── tools/ # DynamicStructuredTool implementations -├── jobs/ # registry.ts (add jobs here) + scheduler.ts +├── jobs/registry.ts # Job definitions + CLI dispatch (add jobs here) ├── storage/ # PostgreSQL queries (own-db + company-db) ├── schemas/index.ts # Zod schemas + shared types (TrendingRepo, CuratedRepo, ...) └── index.ts # Entry point + CLI flags @@ -139,7 +124,7 @@ src/ |---|---| | `kpi` | Daily GitHub activity records | | `diary` | AI-generated daily KPI reports | -| `cleanup_log` | Company DB cleanup history | +| `cleanup_log` | Company DB cleanup history (Functionality no longer supported) | | `github_trending` | Trending repos with summaries, tags, sent status | --- diff --git a/package.json b/package.json index 87476bd..5716719 100644 --- a/package.json +++ b/package.json @@ -47,13 +47,11 @@ "@octokit/rest": "^22.0.1", "dotenv": "^17.3.1", "langchain": "^1.2.37", - "node-cron": "^4.2.1", "pg": "^8.20.0", "zod": "^4.3.6" }, "devDependencies": { "@types/node": "^25.5.0", - "@types/node-cron": "^3.0.11", "@types/pg": "^8.20.0", "prettier": "^3.8.1", "ts-node": "^10.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 899ca0c..f1147ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: langchain: specifier: ^1.2.37 version: 1.4.4(@langchain/core@1.2.3(openai@6.49.0(zod@4.4.3)))(openai@6.49.0(zod@4.4.3)) - node-cron: - specifier: ^4.2.1 - version: 4.2.1 pg: specifier: ^8.20.0 version: 8.21.0 @@ -39,9 +36,6 @@ importers: '@types/node': specifier: ^25.5.0 version: 25.9.2 - '@types/node-cron': - specifier: ^3.0.11 - version: 3.0.11 '@types/pg': specifier: ^8.20.0 version: 8.20.0 @@ -353,9 +347,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node-cron@3.0.11': - resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} - '@types/node@25.9.2': resolution: {integrity: sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==} @@ -454,10 +445,6 @@ packages: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true - node-cron@4.2.1: - resolution: {integrity: sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==} - engines: {node: '>=6.0.0'} - openai@6.49.0: resolution: {integrity: sha512-aYCc0C6L864eR6WSYIwQGyXriw/nIyZx0ObvhzOEVuk0zoBDpynjSbrionWI7q65B5H8jJX0DXR9snEzM6bfPg==} peerDependencies: @@ -840,8 +827,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node-cron@3.0.11': {} - '@types/node@25.9.2': dependencies: undici-types: 7.24.6 @@ -944,8 +929,6 @@ snapshots: mustache@4.2.0: {} - node-cron@4.2.1: {} - openai@6.49.0(zod@4.4.3): optionalDependencies: zod: 4.4.3 diff --git a/src/agent/curator-graph.test.ts b/src/agent/curator-graph.test.ts index 878428a..6e511ad 100644 --- a/src/agent/curator-graph.test.ts +++ b/src/agent/curator-graph.test.ts @@ -1,7 +1,7 @@ import { test } from 'node:test' import assert from 'node:assert/strict' import { runCuratorGraph } from './curator.graph.ts' -import { TrendingRepo } from '../tools/trending-scrape.tool.ts' +import { TrendingRepo } from '../schemas/index.ts' const sampleRepos: TrendingRepo[] = [ { name: 'foo/bar', url: 'https://github.com/foo/bar', description: 'test repo', language: 'typescript', stars: 100, todayStars: 5 }, diff --git a/src/agent/curator.graph.ts b/src/agent/curator.graph.ts index e99a66b..c83d772 100644 --- a/src/agent/curator.graph.ts +++ b/src/agent/curator.graph.ts @@ -1,11 +1,11 @@ import { StateGraph, StateSchema, START, END } from '@langchain/langgraph' import { z } from 'zod' -import { TrendingRepoSchema, type CuratedRepo } from '../schemas/index.js' +import { TrendingRepoOutputSchema, type CuratedRepo } from '../schemas/index.js' import { parseJson } from './utils.ts' -import { TrendingRepo } from '../tools/trending-scrape.tool.ts' +import { TrendingRepo } from '../schemas/index.ts' -const CuratedRepoOutputSchema = z.object({ repos: z.array(TrendingRepoSchema) }) +const CuratedRepoOutputSchema = z.object({ repos: z.array(TrendingRepoOutputSchema) }) export type CurateFn = (repos: TrendingRepo[], feedback?: string) => Promise export type CuratorResult = { curated: CuratedRepo[] | null; error: string | null } diff --git a/src/agent/index.ts b/src/agent/index.ts index 2465709..0ab117e 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -4,11 +4,11 @@ import { manualKpiAgent } from './manual-kpi.agent.js' import { diaryAgent } from './diary.agent.js' import { trendingCuratorAgent } from './news-curator.agent.js' import { trendingTelegramAgent } from './news-telegram.agent.js' -import { trendingScrapeTool, type TrendingRepo } from '../tools/trending-scrape.tool.js' +import { trendingScrapeTool } from '../tools/trending-scrape.tool.js' import { saveKpiRecord, saveTrendingRepos, getRecentRepoNames } from '../storage/own-db.js' import { sectionLogger } from '../utils/logger.js' import { notifyError, parseJson, toolOutput } from './utils.ts' -import { AgentResult, CuratedRepo } from '../schemas/index.ts' +import { AgentResult, CuratedRepo, TrendingRepo } from '../schemas/index.ts' import { runCuratorGraph } from './curator.graph.ts' export class WorkCoordinator { @@ -315,5 +315,5 @@ export class WorkCoordinator { } } -// ── Named exports for backwards compatibility with index.ts and scheduler.ts ─── +// ── Named exports for backwards compatibility with index.ts and registry.ts ─── export const { runCleanup, runDailyJobs, runNewsAgent } = WorkCoordinator diff --git a/src/index.ts b/src/index.ts index 8732de0..cbb6be3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ import 'dotenv/config' import { initDb } from './storage/own-db.js' -import { startScheduler } from './jobs/scheduler.js' import { jobs, findJobByCliArg } from './jobs/registry.js' function printJobs(): void { @@ -38,7 +37,11 @@ async function main(): Promise { process.exit(0) } - startScheduler() + // No recognized args — scheduling is handled by GitHub Actions, not an + // in-process daemon. Point the user at the actual options instead. + console.log('No job specified. Use --job= to run one, or --list-jobs to see all.\n') + printJobs() + process.exit(0) } main().catch((err) => { diff --git a/src/jobs/registry.ts b/src/jobs/registry.ts index afd0ba8..ea1c784 100644 --- a/src/jobs/registry.ts +++ b/src/jobs/registry.ts @@ -6,8 +6,9 @@ import { DEFAULT_CRONJOB_TIME, DEFAULT_CRONJOB_TIMEZONE, NEWS_CRON_TIME } from ' * * - `name` stable identifier — used by CLI (`--job=`) and logs * - `description` one-line human-readable summary shown by `--list-jobs` - * - `schedule` cron expression for the in-process scheduler. Omit for - * interactive/manual-only jobs that are never auto-scheduled + * - `schedule` cron expression, shown by `--list-jobs` — informational only; + * the actual schedule lives in .github/workflows/*.yml. Omit + * for interactive/manual-only jobs that are never scheduled * - `timezone` IANA timezone name (defaults to DEFAULT_CRONJOB_TIMEZONE) * - `run` the actual pipeline — hand-written, not generalized */ diff --git a/src/jobs/scheduler.ts b/src/jobs/scheduler.ts deleted file mode 100644 index 6a99337..0000000 --- a/src/jobs/scheduler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import cron from 'node-cron' -import { DEFAULT_CRONJOB_TIMEZONE } from '../constants/index.js' -import { jobs, type Job } from './registry.js' - -/** - * Register every job in the registry that declares a `schedule` with - * node-cron. Jobs without a schedule (e.g. the interactive daily-kpi job) - * are skipped here and only run on-demand via the CLI. In production, - * GitHub Actions drives the same jobs directly via `pnpm dev --job=`. - */ -export function startScheduler(): void { - const scheduled: Job[] = jobs.filter((j) => !!j.schedule) - - for (const job of scheduled) { - if (!job.schedule) continue - - cron.schedule( - job.schedule, - async () => { - try { - await job.run() - } catch (err) { - console.error(`❌ Job "${job.name}" failed:`, err) - } - }, - { timezone: job.timezone ?? DEFAULT_CRONJOB_TIMEZONE } - ) - } - - const summary = scheduled.map((j) => `${j.name}@${j.schedule} (${j.timezone ?? DEFAULT_CRONJOB_TIMEZONE})`).join(', ') - - console.log(`⏰ Scheduler started — ${summary}`) -} diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 005b786..053633d 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -61,7 +61,7 @@ export const CleanupResultSchema = z.object({ }) // --- GitHub Trending --- -export const TrendingRepoSchema = z.object({ +export const TrendingRepoOutputSchema = z.object({ repo_name: z.string(), url: z.string(), description: z.string(), @@ -72,22 +72,30 @@ export const TrendingRepoSchema = z.object({ tags: z.array(z.string()), }) -export const TrendingRepoLogSchema = TrendingRepoSchema.extend({ +export const TrendingRepoLogSchema = TrendingRepoOutputSchema.extend({ sent: z.boolean(), created_at: z.string(), updated_at: z.string(), }) // TypeScript types inferred from schemas +export interface TrendingRepo { + name: string // e.g. "owner/repo" + url: string + description: string + language: string + stars: number + todayStars: number +} + export type GitHubDigest = z.infer export type GithubKpiInput = z.infer export type ManualKpiInput = z.infer export type KpiRecord = z.infer export type DiaryEntry = z.infer export type CleanupResult = z.infer -export type TrendingRepo = z.infer export type TrendingRepoLog = z.infer -export type CuratedRepo = z.infer +export type CuratedRepo = z.infer export type AgentResult = { messages: Array<{ _getType?: () => string; content: unknown }> } diff --git a/src/tools/trending-scrape.tool.ts b/src/tools/trending-scrape.tool.ts index a1ea9c8..6698b45 100644 --- a/src/tools/trending-scrape.tool.ts +++ b/src/tools/trending-scrape.tool.ts @@ -1,14 +1,6 @@ import { DynamicStructuredTool } from '@langchain/core/tools' import { z } from 'zod' - -export interface TrendingRepo { - name: string // e.g. "owner/repo" - url: string - description: string - language: string - stars: number - todayStars: number -} +import { TrendingRepo } from '../schemas/index.ts' function parseTrendingHtml(html: string): TrendingRepo[] { const repos: TrendingRepo[] = []