Phase 3.
- server.mjs loads only site.agents.enabled, passing per-agent settings
- each console agent exports a manifest + register(host, settings); QA reads
heartbeatMin/autofix from settings (env fallback)
- catalog.mjs folds the runtime (console) agents into agents.json/AGENTS.md via
their manifest exports — one registry for pipeline + runtime agents
Verified: disabling 'qa' in the roster removes /qa/run + the heartbeat.
Phase 2. host + agent modules no longer hardcode identity:
- site.mjs derives name/url/host/model/agentUser/home/role/qaEmail from
site.config.json + astroagent.config.json
- host uses site.model/site.tools/site.home; QA uses site.url/host/qaEmail/routes
- personas templated from site.*; site-specific brand wording (sumi-e, .ca-*,
'law firms') dropped — brand now comes only from brand/BRAND.md + the brand skill
Core (host.mjs + agents/*) is now site-agnostic; identity lives in config.
- qa-autofix.php queues Web Designer tasks for safe findings (alt, internal
links) with 6h dedup; runQaFlow runs it after each pass and kicks the queue
(QA_AUTOFIX env, on by default)
- adminqa fix() accepts optional human instructions; /admin/qa adds an
instructions input per finding for the judgment calls
An empty alt is valid only for purely decorative images; a content image
(photo/cover/screenshot) with empty alt is a real a11y gap. QA now surfaces
empty and missing alt as distinct findings.
- sitemap.xml.js now includes services, projects (+ DB-driven project pages),
resume, changelog, faq (was blog-only) — clears 12 QA sitemap warnings
- runQa a11y check treats a bare/empty alt as present (valid decorative image),
fixing a false positive on the blog cover
- runner: QA_HEARTBEAT_MIN (default 60) in-process timer runs a scheduled QA
pass; one pulse ~90s after boot. Triage (LLM) skipped on clean scheduled runs
to save tokens; only manual runs or runs with errors get a written summary.
- qa-finish.php prunes to the 50 most recent runs.
- systemd unit exposes QA_HEARTBEAT_MIN for tuning.
- migration 012: cja_changelog.actor column
- seed-changelog: optional 5th 'by' element (defaults to Carlos Arias);
display shows 'Added · time · by <actor>'
- changelog skill documents the actor field + attribution logic (agents name
themselves, human/CLI edits are Carlos Arias)
- Web Designer prompt now logs each change to the changelog via the skill,
attributed to 'Website Designer Agent'; runner reseeds cja_changelog when the
seed file changed (the agent has no shell)
- .panel's display:flex was overriding the [hidden] attribute, so the Sign-in
form and 'You're in' panel showed at once regardless of session. Force
[hidden] { display:none !important } so they toggle.
- 'You're in' now links to Web Designer, Manage projects, and the site.
Native <option> popups used the browser default (white) background; in dark
mode the var(--ink) text rendered near-white on it. Give the select a themed
background and style option explicitly. Fixes the Web Designer Page picker and
the project editor status dropdown.
- /admin/projects list + /admin/projects/edit gallery editor (AdminLayout chrome,
session-guarded). Upload/replace images per slot, captions, add/remove,
Instagram reel URL. Save -> cja_projects -> rebuild -> live.
- adminprojects API (list/get/save); media path validation (local paths only).
- runner /rebuild endpoint: build public/ + commit after structured edits.
- Ownership: app/ + public/ now owned by carlos-arias-agent:caweb so the agent
can rebuild its own output; www serves via the caweb group. Documented in
SETUP.md — never build as root.
Verified end to end: upload image -> save to gallery -> rebuild -> image live on
the project page and served.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
- cja_media table; POST /api/media/upload (admin-gated, multipart).
- Images optimised with GD (downscale to 1600px, re-encode; WebP for
transparency, JPG for photos). Video via ffmpeg (scale, compress, drop audio).
- Stored in app/public/media/ (source tree) so uploads survive rebuilds and are
copied into public/ on build.
- requireAdmin() moved to PublicController base (fixes a static/non-static
clash with AdminAuth). Type detection uses getimagesize (fileinfo ext absent).
Note: php-fpm must be in the caweb group to write app/public/media (restart
after adding www to caweb).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
- /admin login page (public, noindex): password form + signed-in panel that
activates the in-page console. Verified in-browser: login -> panel -> the
astroagent handle appears on other pages.
- Secret-link login: visit /admin/<token> to sign in without a password.
adminauth token() validates a bcrypt-hashed token, sets the session, 302s to
/admin. nginx routes the token path to PHP. set-admin-token.php generates it.
- Password login kept as a fallback.
Verified: token link -> session -> console access; bad token -> /admin?e=1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
Records the operational setup (agents/console/SETUP.md). Server-side, not in git:
- confined carlos-arias-agent user (nologin, no sudo) runs headless Claude
- claude relocated to /usr/local/bin (root's install was unreachable)
- caweb shared group: agent edits+builds, www serves, no chown-to-www needed
- valid setup-token stored in agents/.env (git-ignored)
Verified: agent reads repo, writes source, runs isolated preview build, output
served by nginx. Full edit->preview->serve loop works end to end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
- cja_admin + cja_admin_log tables (dedicated single-admin tier + audit trail)
- AdminAuth controller: /api/adminauth login/logout/me, session-based,
rate-limited, every attempt logged
- set-admin-password CLI (bcrypt, run manually so the password never enters
an agent context)
- requireAdmin() guard for future privileged console endpoints
- agent env scaffold (git-ignored)
Foundation only — no agent runner or features yet. Preview/publish, media,
and the dashboard come in phases 1-3 per agents/console/PLAN.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
Establishes the deploy baseline on main so the admin agent's publish/rollback
has a clean starting point. Everything built to date: sumi-e brand system,
homepage, projects (DB-driven case studies), resume, about, services +
website-design detail, contact form + DB, changelog, favicon + share card.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
Adopt the agents/ architecture proven on medellin.co (reference impl):
- Move the content engine to a top-level agents/ dir: orchestrators, prompts,
config, run.sh, admin console, shared libs. All content-pipeline literals
repointed (config paths, scripts, admin, LLM-facing prompts/image.md string,
configure.mjs, new-site.sh, astroagent tokenFile, .gitignore runtime block).
- Every script carries a parseable @agent-manifest header: name, title, class
(content|operational|runtime|plumbing), trigger, model, prompts, skills (MCP),
tools, reads/writes tables. 5 content agents + 3 plumbing scripts.
- New agents/catalog.mjs generates the catalog from the headers:
agents/AGENTS.md (human, grouped by class) + agents/agents.json (machine
manifest — a clone diffs it against a source to find missing tools/tables/MCP
before running). configure.mjs regenerates the catalog on every identity
stamp. No DB table, no watcher.
- config.json gains paths.stateDir/newsDir; publish-tick, write-daily, and
news-radar read them instead of hardcoding.
- Full cut: content-pipeline/ deleted (the seed has no live crons, so no
hybrid period needed). Docs updated (AGENTS.md structure + pipeline section,
README paths).
Clones migrating from content-pipeline/: see medellin.co's
.memory/handoffs/agents-directory-migration.md for the cutover playbook
(one cron set active at a time; migrate drafts/state after repointing cron).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
Structure-only export from a live SeedProject site (68 tables: the sp_ framework — users,
oauth, tokens, settings, roles/permissions, api_requests, orgs, saas/billing, menus, kathe —
plus the mde_ content model: content, directory, events, comments, reactions, categories…).
Every table is CREATE TABLE IF NOT EXISTS, so it composes with the existing 001/002 migrations
and only fills in what's missing. This modernizes the base off the legacy `usergen`/`api_requests`
schema and is the prerequisite for porting the auth system, Mailer, and resource registry upstream.
No data included.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
Two self-contained framework fixes, no schema dependencies:
- throttle(): exempt trusted callers via new Functions::isTrustedIp() — loopback, the
server's own IP, and an optional TRUSTED_IPS config allowlist (IPs/CIDRs). Fixes the SSG
build (which fetches the read API from the box thousands of times per build) tripping the
public rate limit and baking empty data into the deploy. Public client IPs stay limited.
- public/index.php: set secure session cookie params (httponly, SameSite=Lax, secure on
https) before session_start, so session/login state rides on a hardened cookie.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
Pick an element on the rendered site, comment, and the agent edits the
source. Includes the discoverability fixes (SVG crosshair, pick-mode
banner) and publish auto-reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RCfiiNfHmDRj4ZZiFM535z
Three bugs found deploying a fresh clone to seedproject.com, each fatal
to the documented "spin up a new site" flow:
- .gitignore: the unanchored `public/` pattern (meant for the root build
output) also ignored api/public/ (the framework's front controller,
controllers, models, views) and app/public/ (theme static assets:
fonts, avatar placeholder). Neither was ever committed, so every fresh
clone 500'd on all /api routes and 404'd on theme assets. Anchor the
pattern to /public/ and commit both directories.
- api/install/dump.sql: stray `CREATE DATABASE ochenta80_db123` (SQLyog
export artifact) aborted `php console app:install` for any
non-privileged DB user. The schema must import into whatever database
the installer connects to.
- PluginManager::boot() queries sp_plugins on every request, but no
shipped schema creates it — even a successful install 500'd on every
endpoint. Add migration 002_create_sp_plugins.sql matching the columns
PluginManager reads/writes.
Also empty api/system/errors.json, which shipped with stale error logs
from an unrelated project.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3JqxTe7TKaR7xufcMr7Ds
Decision: the JSON-based content-pipeline is optional and does not block cloning/
building a site. Rather than genericize the current JSON prompts, it will be
re-architected DB-managed via /api in a future session, with prompt genericization
folded into that. AGENTS.md now tells future agents not to genericize it prematurely.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn