Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
**An SEO content engine that uses RSS as raw material.**

Aggregate It imports RSS/Atom feeds, faithfully rewrites the content with AI (no
fact changes — news stays news), deduplicates stories semantically, and maintains
fact changes, news stays news), deduplicates stories semantically, and maintains
*living* topic pages that stay up to date as new sources arrive. It automatically
extracts entities (companies, people, products…), resolves them against your
custom post types, creates researched hub pages, and builds an internal-link graph
Expand All @@ -15,30 +15,30 @@ fuel happens to be RSS.
- **License:** GPL-2.0-or-later
- **Distribution:** GitHub releases + Plugin Update Checker self-update (matches `crm-connect`); WordPress.org listing optional later. Freemium add-ons via provider seams.
- **Slug:** `aggregate-it`
- **Conventions:** follows the `crm-connect` house style — see [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md)
- **Status:** planning / pre-implementation
- **Conventions:** follows the `crm-connect` house style, see [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md)
- **Status:** implemented (phases 0-4 built, see [`docs/PLAN.md`](docs/PLAN.md))

## What makes it different

| Capability | Why it matters for SEO |
|---|---|
| **Living canonical posts** — one authoritative URL per story, updated over time | Concentrates link equity + sends `dateModified` freshness signals instead of splitting equity across duplicates |
| **Faithful rewrite** — reword + strip promo junk, never alter facts | Original prose without the hallucination/legal risk; a documented selling point |
| **Novelty gate** — only update when genuinely new facts arrive | Avoids the thin-rewrite pattern that triggers Google's Scaled Content Abuse policy |
| **Entity hub engine** — auto-match/create CPT entities + contextual backlinks | Builds hub-and-spoke topical authority + machine-readable `about`/`sameAs` schema |
| **Native-rich schema** — Article/NewsArticle + entity + citation graph | The durable moat thin-rewrite competitors can't replicate |
| **Living canonical posts**: one authoritative URL per story, updated over time | Concentrates link equity + sends `dateModified` freshness signals instead of splitting equity across duplicates |
| **Faithful rewrite**: reword + strip promo junk, never alter facts | Original prose without the hallucination/legal risk; a documented selling point |
| **Novelty gate**: only update when genuinely new facts arrive | Avoids the thin-rewrite pattern that triggers Google's Scaled Content Abuse policy |
| **Entity hub engine**: auto-match/create CPT entities + contextual backlinks | Builds hub-and-spoke topical authority + machine-readable `about`/`sameAs` schema |
| **Native-rich schema**: Article/NewsArticle + entity + citation graph | The durable moat thin-rewrite competitors can't replicate |

## Documentation

- [`docs/DECISIONS.md`](docs/DECISIONS.md) — the resolved design decisions and their rationale
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — data model, pipeline, subsystems, config schema, extension API
- [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md) — engineering house style, ported from `crm-connect`
- [`docs/EXTENDING.md`](docs/EXTENDING.md) — provider/hook seams for premium add-ons
- [`docs/PLAN.md`](docs/PLAN.md) — phased implementation plan (phases 0–4 built)
- [`docs/DECISIONS.md`](docs/DECISIONS.md): the resolved design decisions and their rationale
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md): data model, pipeline, subsystems, config schema, extension API
- [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md): engineering house style, ported from `crm-connect`
- [`docs/EXTENDING.md`](docs/EXTENDING.md): provider/hook seams for premium add-ons
- [`docs/PLAN.md`](docs/PLAN.md): phased implementation plan (phases 0-4 built)

## Operating profile (chosen constraints)

- **Scale:** single site, moderate volume — Action Scheduler, no external worker
- **Scale:** single site, moderate volume. A claim-based database queue driven by WP-Cron, no external worker
- **Editorial:** fully automatic publish; manual edits after the fact
- **AI:** cheapest-viable models behind a swappable, bring-your-own-key adapter
- **Content stance:** full faithful rewrite, facts preserved, promo stripped
Expand Down
4 changes: 2 additions & 2 deletions aggregate-it.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
/**
* Plugin Name: Aggregate It
* Plugin URI: https://github.com/nmbrthirteen/aggregate-it
* Description: An SEO content engine that uses RSS as raw material — it imports feeds, faithfully rewrites them with AI, deduplicates stories into living topic pages, and auto-builds entity hubs with an internal-link graph.
* Description: An SEO content engine that uses RSS as raw material. It imports feeds, faithfully rewrites them with AI, deduplicates stories into living topic pages, and auto-builds entity hubs with an internal-link graph.
*
* Features:
* - Import RSS/Atom and JSON feeds on a per-feed schedule, with categories, tags, include/exclude keywords, and per-feed length and publish overrides.
* - Bulk-add feeds, OPML import (paste or upload), and a global keyword/domain blacklist.
* - Faithful AI rewrite that keeps the facts and strips promo — choose Google Gemini, OpenAI, or Claude with your own key.
* - Faithful AI rewrite that keeps the facts and strips promo, choose Google Gemini, OpenAI, or Claude with your own key.
* - Model, article-length, and writing-style controls, a daily spend cap, and a one-click connection test.
* - Semantic deduplication into one canonical post per story, a novelty gate, and dated "In the news" timelines.
* - Automatic entity hub pages with schema.org types, sameAs links, optional Wikipedia enrichment, an internal-link graph, and related articles.
Expand Down
2 changes: 0 additions & 2 deletions assets/css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,6 @@
display: block;
color: #646970;
font-size: 11px;
text-transform: uppercase;
letter-spacing: .02em;
}

.aggregate-it .ai-hub-feed-link {
Expand Down
2 changes: 1 addition & 1 deletion assets/css/hub.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Aggregate It — entity hub front-end styles. Neutral + theme-agnostic. */
/* Aggregate It entity hub front-end styles. Neutral + theme-agnostic. */
.aggregate-it-details {
border-collapse: collapse;
width: 100%;
Expand Down
2 changes: 1 addition & 1 deletion assets/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
}
tbody.innerHTML = '';
if ( ! rows.length ) {
tbody.innerHTML = '<tr><td colspan="4" class="ai-empty">No articles yet — add a feed to get started.</td></tr>';
tbody.innerHTML = '<tr><td colspan="4" class="ai-empty">No articles yet. Add a feed to get started.</td></tr>';
return;
}
rows.forEach( function ( r ) {
Expand Down
60 changes: 30 additions & 30 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
How Aggregate It is put together: the pipeline, the data model, the subsystems, the
configuration schema, and the extension API that the freemium seams depend on.

Read [`DECISIONS.md`](DECISIONS.md) first — this document assumes those choices.
Read [`DECISIONS.md`](DECISIONS.md) first; this document assumes those choices.

---

Expand Down Expand Up @@ -32,7 +32,7 @@ Read [`DECISIONS.md`](DECISIONS.md) first — this document assumes those choice
Every stage is a job on the custom claim-based queue (the crm-connect pattern).
```

The plugin is the runtime *and* the CMS — no external worker. A **custom claim-based DB
The plugin is the runtime *and* the CMS: no external worker. A **custom claim-based DB
queue** (see [`CONVENTIONS.md`](CONVENTIONS.md) §5) drives every stage: atomic
`claim_token` claiming, stale-claim recovery, exponential backoff, dead-letter,
auto-pause, and a non-blocking "nudge" for low latency. The only outbound calls are: AI
Expand All @@ -42,7 +42,7 @@ provider, optional research/keyword providers, source-site fetches, and IndexNow

## 2. The pipeline (item state machine)

Each row in `ai_items` advances through explicit, **idempotent, resumable** states —
Each row in `ai_items` advances through explicit, **idempotent, resumable** states,
**one stage per queue claim**, never the whole pipeline in a single request. A
monolithic "import + rewrite" call is the anti-pattern we are avoiding.

Expand All @@ -56,14 +56,14 @@ monolithic "import + rewrite" call is the anti-pattern we are avoiding.
| ↳ `updated` | Processing (matched cluster) | Novelty gate; if new facts → append dated section, bump `dateModified` | cheap |
| `entity_linked` | Entity | Resolve/create entities, inject first-mention links, write relationships | optional paid |
| `published` | Publish | Slug, meta via adapter, JSON-LD, IndexNow ping | free |
| `failed` | — | Retried w/ exponential backoff; permanent failures → dead-letter + log | — |
| `failed` | - | Retried w/ exponential backoff; permanent failures → dead-letter + log | - |

**Idempotency:** every stage keys off `content_hash`; re-running a stage is safe.
**Re-runs:** a post can be re-rewritten (new prompt/model) without re-importing.
**Pause:** when the daily spend cap trips, the queue's transient auto-pause halts paid
stages (mirrors crm-connect's auto-pause); free stages continue.
**Batch API:** the rewrite stage may submit a batch job and set `next_attempt_at` to a
poll time — the claim loop polls until ready, then advances. No long blocking calls.
poll time; the claim loop polls until ready, then advances. No long blocking calls.

---

Expand Down Expand Up @@ -172,7 +172,7 @@ CREATE TABLE {prefix}_ai_log (
At single-site moderate volume the live vector set is thousands, not millions.
Cosine over a few thousand packed float32 rows in PHP is sub-millisecond-per-compare
and runs inside a queue job, not a web request. External vector DBs
(Pinecone/Qdrant) are explicitly avoided — they'd break "activate and it just works"
(Pinecone/Qdrant) are explicitly avoided; they'd break "activate and it just works"
and the OSS no-paid-dependency rule. A `pgvector`/scale path can be added behind the
same `VectorStore` interface later.

Expand All @@ -184,51 +184,51 @@ same `VectorStore` interface later.

- Feed parsing via WordPress core `SimplePie` (already bundled) for RSS/Atom; JSON
Feed handled separately.
- **Full-content extraction:** `ContentExtractor` — feed-content-if-full-enough, else
- **Full-content extraction:** `ContentExtractor`: feed-content-if-full-enough, else
fetch + GPL-compatible readability port (no JS). `HttpFetcher` enforces `robots.txt`,
per-domain rate limit, honest UA, response caching.
- Syntactic dedup by `(source_id, guid)` unique key + `content_hash`.

### 4.2 Processing

- `Embedder` (provider adapter) → `ai_vectors`.
- `Clusterer` — 3-signal: cosine ≥ threshold **∧** shared primary entity **∧** within
- `Clusterer`: 3-signal: cosine ≥ threshold **∧** shared primary entity **∧** within
`window_until`. Borderline (near threshold) → log, treat as new cluster.
- `Rewriter` — the **single structured call**. Strict JSON schema output. Prompt is
- `Rewriter`: the **single structured call**. Strict JSON schema output. Prompt is
template-driven (per-feed overridable), enforces faithful-rewrite + strip-promo +
keyword-natural rules.
- `FactsGuard` — extract numbers/dates/proper-nouns from input & output; any in output
- `FactsGuard`: extract numbers/dates/proper-nouns from input & output; any in output
not in input → flag on the item (publishes, but visible in the dashboard).
- `NoveltyGate` — diff new item's facts against `cluster.fact_set`; no new facts →
- `NoveltyGate`: diff new item's facts against `cluster.fact_set`; no new facts →
suppress; new facts → append + merge into `fact_set`.

### 4.3 Entity engine

- `EntityExtractor` — comes from the rewrite call's `entities[]` (no separate NER call).
- `EntityResolver` — normalize → exact/alias → type-constrained fuzzy+embedding →
- `EntityExtractor`: comes from the rewrite call's `entities[]` (no separate NER call).
- `EntityResolver`: normalize → exact/alias → type-constrained fuzzy+embedding →
confidence band (link / create / skip-and-log). Batch-dedup guard via a per-run
pending-creation set.
- `EntityResearcher` — pluggable. Default: stub from in-article context (zero deps).
- `EntityResearcher`: pluggable. Default: stub from in-article context (zero deps).
With a research provider: cited external facts + `sameAs`.
- `EntityLinker` — first-mention contextual link, capped per post, writes the
- `EntityLinker`: first-mention contextual link, capped per post, writes the
relationship taxonomy.
- `HubRenderer` — entity CPT template: description + cited facts + `sameAs` +
- `HubRenderer`: entity CPT template: description + cited facts + `sameAs` +
auto-growing related-posts list + Organization/Person/Product schema.
- `EntityMerge` — admin tool: merge B→A, repoint links/aliases/relationships/schema.
- `EntityMerge`: admin tool: merge B→A, repoint links/aliases/relationships/schema.

### 4.4 Publish / SEO

- `SlugGenerator` — from locked primary keyword.
- `SeoAdapter` — strategy per active plugin (Yoast / Rank Math / SEOPress / native
- `SlugGenerator`: from locked primary keyword.
- `SeoAdapter`: strategy per active plugin (Yoast / Rank Math / SEOPress / native
fallback): writes title + meta + focus keyword; **suppresses the plugin's schema** on
our objects.
- `SchemaGraph` — builds the native JSON-LD `@graph`.
- `IndexNow` — pings on publish and on novelty update; bumps `dateModified`.
- `SchemaGraph`: builds the native JSON-LD `@graph`.
- `IndexNow`: pings on publish and on novelty update; bumps `dateModified`.

### 4.5 Cost & observability

- `CostMeter` — per call/stage tokens + USD → `ai_log`; aggregates per feed/day.
- `SpendCap` — hard daily ceiling; trips → paid stages pause, admin notice.
- `CostMeter`: per call/stage tokens + USD → `ai_log`; aggregates per feed/day.
- `SpendCap`: hard daily ceiling; trips → paid stages pause, admin notice.
- Dashboard: pipeline funnel, per-feed costs, facts-guard flags, ambiguous-entity
queue, original-vs-rewrite diff, feed-health.

Expand Down Expand Up @@ -280,7 +280,7 @@ Adding support for a new post type = adding a rule. No code.
## 6. Extension API (the freemium seams)

Free core must expose clean hooks so premium add-ons (and the community) extend without
forking. Retrofitting these later is painful — they go in from phase 1.
forking. Retrofitting these later is painful; they go in from phase 1.

**Provider interfaces** (premium add-ons register implementations):

Expand All @@ -304,11 +304,11 @@ interface Seo_Adapter { // one per SEO plugin
**Key filters/actions** (illustrative):

- `aggregate_it_register_ai_provider`, `…_research_provider`, `…_keyword_provider`
- `aggregate_it_rewrite_prompt` — mutate the structured-call prompt/schema
- `aggregate_it_cluster_match` — override/augment the 3-signal decision
- `aggregate_it_entity_resolve` — inject custom resolution
- `aggregate_it_before_publish` / `…_after_update` — hook the lifecycle
- `aggregate_it_schema_graph` — mutate the JSON-LD before output
- `aggregate_it_rewrite_prompt`: mutate the structured-call prompt/schema
- `aggregate_it_cluster_match`: override/augment the 3-signal decision
- `aggregate_it_entity_resolve`: inject custom resolution
- `aggregate_it_before_publish` / `…_after_update`: hook the lifecycle
- `aggregate_it_schema_graph`: mutate the JSON-LD before output

Premium add-ons (keyword-data provider, research provider, headless extraction,
multilingual, strategic-mode packs) are pure consumers of these seams.
Expand All @@ -317,7 +317,7 @@ multilingual, strategic-mode packs) are pure consumers of these seams.

## 7. Failure & safety posture

- **Scaled Content Abuse:** mitigated structurally — living posts + novelty gate +
- **Scaled Content Abuse:** mitigated structurally: living posts + novelty gate +
entity value + citations = genuine added value, not thin rewrites.
- **Hallucination:** faithful-rewrite prompt + deterministic facts-guard; strict cited
entity stubs; entities degrade to in-article-only without a research provider.
Expand Down
Loading
Loading