seedproject-web/content-pipeline
Carlos Arias fc84d095b8 feat: config-driven identity (site.config.json) + buildable sample content
- site.config.json  single source of identity (name/url/description/author/social)
- scripts/configure.mjs  stamps it into app, content-pipeline, and astroagent configs
- app/src/lib/blog-data.js  SITE + author now read from app/src/config/site.json
- astro.config.mjs  default site URL from config
- index.astro / Footer.astro  neutralized hardcoded title, hero alt, social link
- sample 'welcome' post + placeholder hero/avatar so the site builds out of the box

Verified: cd app && npm ci && npm run build -> 13 pages, Complete.
Remaining comiida demo prose in about/faq/services/contact + Newsletter (sample content).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
2026-07-04 23:06:23 +00:00
..
admin chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00
drafts chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00
logs chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00
prompts chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00
scripts chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00
state chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00
config.json feat: config-driven identity (site.config.json) + buildable sample content 2026-07-04 23:06:23 +00:00
README.md chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00
run.sh chore: scaffold SeedProject base (Phase 1) 2026-07-04 22:53:10 +00:00

Comiida Content Pipeline

An agentic pipeline that plans a 3-month editorial calendar and drafts one SEO/EEAT restaurant article per day (English, for Medellín expats/tourists) with an AI cover image, queued for human approval before going live on the Astro site.

Engine: headless Claude Code (claude -p) + Higgsfield image MCP (authenticated at the claude.ai level — reachable headless, confirmed). See the design at /root/.claude/plans/init-elegant-giraffe.md.

Layout

  • config.json — site, author, models, image, editorial mix/word-counts.
  • .env — optional secrets (git-ignored). None required today (Higgsfield auth is global).
  • prompts/ — system prompts: research.system.md, writer.system.md, image.md.
  • scripts/research.mjs, write-daily.mjs, list-drafts.mjs, approve.mjs, lib/.
  • calendar.json — the generated editorial calendar (created by research.mjs).
  • drafts/<slug>/ — pending drafts (index.mdx, cover.jpg, sources.json).
  • run.sh — cron-safe wrapper (sets PATH, loads .env).

Setup (one-time)

  1. Add the real author to app/src/lib/blog-data.js and set author.slug/name in config.json to match (EEAT requires a real byline).
  2. Image generation needs no key — it uses the Higgsfield claude.ai MCP. Check credits with the balance tool if generations start failing.

Usage

# Plan ~3 months (writes calendar.json). Default days = config.editorial.calendarDays.
./run.sh research.mjs [days]

# Draft the next due article into drafts/<slug>/ (auto-runs the SEO audit after).
./run.sh write-daily.mjs [slug]

# SEO Specialist audit (EEAT / spam-policy / on-page / AEO / readability).
# Writes <postDir>/seo-review.json. Default audits the draft; --published audits the live post.
./run.sh seo-review.mjs <slug> [--published]

# Auto-revise a failing draft until it passes the SEO gate (or maxReviseAttempts).
./run.sh revise.mjs <slug>

# News radar — discover timely Medellín food news/events → news-queue.json.
# (also runs automatically inside write-daily, once per day)
./run.sh news-radar.mjs

# Research — top up the evergreen backlog (additive merge into calendar.json).
# (also runs automatically inside write-daily when the planned backlog is low)
./run.sh research.mjs [count]

# Review the queue, then publish one manually (SEO-gated; --force overrides).
node scripts/list-drafts.mjs
./run.sh approve.mjs <slug> [--force]   # moves into the blog, builds, goes live

# Randomized auto-publisher (cron). Publishes ONE SEO-passing draft per day at a random time.
./run.sh publish-tick.mjs [--now]       # --now ignores the time gate (for testing)

Scheduling (managed in the aaPanel Cron UI; server clock is UTC)

Only two jobs are scheduled — research is now demand-driven (see below), not a cron.

0 11 * * *   run.sh write-daily.mjs    # daily 11:00 UTC (06:00 Bogotá) — news scan + refill + draft + SEO audit
*/15 * * * * run.sh publish-tick.mjs   # every 15m — publish 1 passing draft in the morning (random timestamp)

(aaPanel runs these in UTC; publish-tick computes its Bogotá floor time internally.)

Evergreen backlog is demand-driven (no monthly cron)

research.mjs is additive — it proposes evergreen topics and merges new ones into calendar.json (deduped, dates auto-assigned). write-daily auto-refills when the planned backlog drops below editorial.refillThreshold (guarded once/day), so the calendar stays stocked without thinking in "months." Run manually any time: ./run.sh research.mjs [count].

Concurrency safety

All calendar.json writes go through lib/calendar.mjs — an exclusive lockfile plus atomic read-modify-write (updateEntry/addEntry/mergeEntries). This prevents the lost-update race where a long write-daily run could clobber concurrent publish-tick / research writes.

Auto-publish: morning go-live, randomized timestamp (config.jsonpublish)

Posts go live in the morning (not a random hour all day), but each is stamped with a random earlier-today timestamp so published times aren't a fixed-minute fingerprint:

  • 06:00 write-daily.mjs drafts the day's topic, runs the SEO audit, and auto-revises.
  • publish-tick.mjs (every 15 min) publishes one SEO-passing draft on the first tick after the publish.notBefore floor (default 07:00 Bogotá — a buffer so the morning's fresh/news article is the one that goes live). The post's date is set to a random time between midnight and the publish moment (randomTimestampTodaySoFar — varied but never future). News drafts fast-track ahead of evergreen. One publish/day; idempotent; SEO-failing drafts wait for manual review. State in state/publish-YYYY-MM-DD.json.
  • Set publish.auto=false to disable auto-publish and use manual approve.mjs.

Admin dashboard (read-only)

A small Node service (admin/server.mjs, no deps) shows the week's scheduled topics, the draft queue (with cover + preview), and the full calendar.

  • Served at https://comiida.com/admin?key=<ADMIN_TOKEN> (Apache proxies /admin127.0.0.1:3010; token is in .env). A valid ?key= sets a 30-day cookie.
  • Kept alive by supervisor: program comiidaAdmin (/www/server/panel/plugin/supervisor/profile/comiidaAdmin.ini). Manage with: supervisorctl -c /etc/supervisor/supervisord.conf {status|restart|stop} comiidaAdmin:comiidaAdmin_00.
  • Read-only by design — approving still happens via ./run.sh approve.mjs <slug>.

Daily news radar (timely news/events)

scripts/news-radar.mjs + prompts/news-radar.system.md discover time-sensitive Medellín food items (openings/closings, festivals/events, awards, press) via WebSearch → news-queue.json (deduped vs published + calendar; stale fresh items pruned past news.recencyDays).

  • Runs automatically at the start of write-daily.mjs, guarded once/day via state/news-YYYY-MM-DD.json (so the hourly test cron doesn't re-scan).
  • News-first selection: the writer prefers the freshest queue item (by freshnessScore), injects it into calendar.json as a news:true entry, and drafts it; on a quiet day it falls back to the next evergreen calendar topic.
  • Fast-track publishing: publish-tick.mjs releases news:true drafts before evergreen ones (still 1/day, still SEO-gated, still randomized timestamp) — so timely pieces go live the same day.
  • Config: config.json → news { enabled, recencyDays, maxPerScan }.

Auto-revise loop

When a fresh draft fails the SEO gate, write-daily automatically runs scripts/revise.mjs: it feeds the audit's blocking + topFixes back to the writer (prompts/reviser.system.md), which edits the draft in place (finding real sources via WebSearch for uncited claims — never fabricating), then re-audits. Repeats until pass or seo.maxReviseAttempts (config). Config: seo.autoRevise, seo.maxReviseAttempts. Verified: a draft at fail/82 → pass/90 in one pass.

SEO Specialist agent

scripts/seo-review.mjs + prompts/seo-review.system.md audit an article across five weighted dimensions — EEAT (30), spam-policy compliance (20), on-page SEO (25), AEO/answer- engine (15), readability (10) — and write seo-review.json with per-dimension scores, a verdict (pass/revise/fail), blocking issues, and a ranked fix list.

  • Runs automatically at the end of write-daily.mjs (verdict also stored on the calendar entry).
  • Gates approve.mjs: approval is blocked when verdict==="fail" or overall < seo.minScore (config; default 85). Override with approve.mjs <slug> --force.
  • Verdict + scores + fixes show in the /admin dashboard (draft cards and the draft preview).
  • Reviewer model: models.reviewer in config.json.

EEAT guardrails (enforced in prompts)

Data-driven only; every claim cited; no fabricated first-person dining; real author byline; honest "AI-generated illustration" image credit.