2026-07-04 22:53:10 +00:00
#!/usr/bin/env node
feat: content-pipeline/ → agents/ — formalize the agent system in the seed
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
2026-07-11 20:33:48 +00:00
/ * *
* @ agent - manifest
* name : reviser
* title : Draft Reviser
* class : content
* trigger : manual < slug > ( auto - invoked when the SEO gate fails and autoRevise is on )
* description : Feed the SEO audit ' s fixes back to the writer and re - audit until the draft passes or attempts run out .
* model : writer = claude - sonnet - 4 - 6
* prompt : prompts / reviser . system . md
* skills : -
* tools : lib / claude . mjs
* reads : -
* writes : -
* created : 2026 - 07 - 04
* @ end
* /
2026-07-04 22:53:10 +00:00
// Auto-revise loop: feed the SEO audit's fixes back to the writer, re-audit, repeat until the
// draft passes the gate or maxReviseAttempts is reached.
feat: content-pipeline/ → agents/ — formalize the agent system in the seed
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
2026-07-11 20:33:48 +00:00
// Usage: node agents/scripts/revise.mjs <slug>
2026-07-04 22:53:10 +00:00
import { existsSync } from "node:fs" ;
import { dirname , resolve , join } from "node:path" ;
import { fileURLToPath } from "node:url" ;
import { runClaude } from "./lib/claude.mjs" ;
import { loadJson } from "./lib/util.mjs" ;
import { runSeoReview } from "./seo-review.mjs" ;
const HERE = dirname ( fileURLToPath ( import . meta . url ) ) ;
const PIPELINE = resolve ( HERE , ".." ) ;
const cfg = loadJson ( join ( PIPELINE , "config.json" ) ) ;
const root = cfg . paths . projectRoot ;
const failing = ( audit , minScore ) =>
! audit || audit . verdict === "fail" || ( audit . overall ? ? 0 ) < minScore ;
/ * *
* Revise a draft until it passes the SEO gate ( or attempts run out ) .
* @ returns { Promise < { audit : object | null , attempts : number } > }
* /
export async function runRevise ( slug , { maxAttempts } = { } ) {
const max = maxAttempts ? ? cfg . seo ? . maxReviseAttempts ? ? 2 ;
const minScore = cfg . seo ? . minScore ? ? 85 ;
const draftDir = join ( root , cfg . paths . draftsDir , slug ) ;
const mdxPath = join ( draftDir , "index.mdx" ) ;
const auditPath = join ( draftDir , "seo-review.json" ) ;
if ( ! existsSync ( mdxPath ) ) {
console . error ( ` [revise] no draft at ${ mdxPath } ` ) ;
return { audit : null , attempts : 0 } ;
}
const calPathForEntry = join ( root , cfg . paths . calendar ) ;
const calEntry = existsSync ( calPathForEntry )
? loadJson ( calPathForEntry ) . find ( ( e ) => e . slug === slug )
: null ;
const firsthandNote = calEntry ? . authorFirsthand
? ` \n \n IMPORTANT — authorFirsthand piece: a real, named author ( ${ cfg . author ? . name || "the site author" } ) wrote this personally. Do NOT strip or neutralize the first-person voice and subjective opinions — that is authentic Experience and must be kept. Only add citations for objective facts (addresses, prices, dates, hours) or frame them honestly. `
: "" ;
let audit = existsSync ( auditPath ) ? loadJson ( auditPath ) : await runSeoReview ( slug , { published : false } ) ;
let attempts = 0 ;
while ( failing ( audit , minScore ) && attempts < max ) {
attempts ++ ;
const keyword = audit ? . primaryKeyword || slug . replace ( /-/g , " " ) ;
console . log ( ` [revise] attempt ${ attempts } / ${ max } on ${ slug } (current: ${ audit ? . verdict } ${ audit ? . overall } ) ` ) ;
const prompt = ` Revise this draft to pass the SEO/EEAT gate (target ≥ ${ minScore } ).
- Draft : $ { mdxPath }
- Citations file : $ { join ( draftDir , "sources.json" ) }
- Audit to address ( work through blocking + topFixes ) : $ { auditPath }
- Primary keyword : $ { keyword }
- Existing published posts ( for internal links ) : $ { join ( root , cfg . paths . blogContentDir ) } $ { firsthandNote }
Follow the reviser instructions in your system prompt exactly . Edit the files in place . ` ;
await runClaude ( {
prompt ,
systemPromptFile : join ( PIPELINE , "prompts/reviser.system.md" ) ,
model : cfg . models . writer ,
allowedTools : "Read Write Edit Glob Grep WebSearch" ,
addDirs : [ root ] ,
cwd : root ,
logFile : join ( root , cfg . paths . logsDir , ` revise- ${ slug } .log ` ) ,
} ) ;
audit = await runSeoReview ( slug , { published : false } ) ;
}
const ok = ! failing ( audit , minScore ) ;
console . log (
` [revise] ${ slug } : ${ ok ? "PASS" : "still failing" } after ${ attempts } attempt(s) — ` +
` ${ audit ? . verdict } ${ audit ? . overall } `
) ;
return { audit , attempts } ;
}
// CLI
if ( import . meta . url === ` file:// ${ process . argv [ 1 ] } ` ) {
const slug = process . argv [ 2 ] ;
if ( ! slug ) {
console . error ( "Usage: revise.mjs <slug>" ) ;
process . exit ( 1 ) ;
}
const { audit } = await runRevise ( slug ) ;
process . exit ( audit && audit . verdict === "pass" ? 0 : 2 ) ;
}