Skip to content

feat(ai): SeoCrew — in-process SEO Backfill crew via shared FieldCrew (AI-043)#338

Merged
mrviduus merged 1 commit into
mainfrom
ai-043-seo-crew
Jun 16, 2026
Merged

feat(ai): SeoCrew — in-process SEO Backfill crew via shared FieldCrew (AI-043)#338
mrviduus merged 1 commit into
mainfrom
ai-043-seo-crew

Conversation

@mrviduus

Copy link
Copy Markdown
Owner

AI-043 — SeoCrew integration (Phase 7)

Sibling to AI-042's AutoPublishCrew: runs the AI-041 specialists (researcher→drafter→critic→editor) over ILlmService to generate Edition.Description + Author.Bio, routed through the existing SeoBackfillJob apply path so the trust gate / Before-After snapshot / revert / SSG enqueue all keep working unchanged. Admin-triggered; legacy seo-backfill-poll.sh + Claude CLI untouched and default.

DRY — shared FieldCrew

Extracted the per-field crew machinery (FieldCrewState, 4-stage plan, NeedsReview gate, agent_run persistence, FieldResult) out of AutoPublishCrew into a shared FieldCrew runner. AutoPublishCrew is now a thin wrapper — crew.autopublish, behavior + all AI-042 tests unchanged. Banned-phrase list shared via CrewBannedPhrases.

Brief construction

SeoBriefs builds ContentBrief from per-field defaults (Edition.Description 800–1600, Author.Bio 600–1400) — deliberately NOT the raw SeoTemplate.PromptTemplate (it carries {{placeholders}} + JSON-output instructions that would corrupt the prose specialists). The template is read only for its TrustLevel, via the job apply path. SeoPromptSanitizer applied to every entity value (injection defense not bypassed).

Endpoint

POST /admin/seo/{entityType}/{id}/crew-generate (admin auth, seo.crew rate limit). Resolve entity → build+sanitize source → run crew → enqueue SeoBackfillJob → apply via job path. Returns runId + jobId + critique summary + status.

Hardened per adversarial QA (2 P1 + P2 + P3)

  • P1 — endpoint was broken end-to-end: GetContextAsync asserts the job is Running but nothing transitioned it from Queued → every call threw 500 after spending LLM cost and wedged the job. Added targeted MarkRunningAsync (Queued→Running for this job) + crew failure → job Failed. (Caught only by adversarial review — DB-free unit tests never exercised the wiring.)
  • P1 — manual-source data loss: this path had dropped AI-042's protection. ApplyCrewResultAsync now loads live SeoSource; a Manual filled field parks NeedsReview and is never overwritten.
  • P2 — empty-context hallucination: source-material floor (<200 chars → NeedsReview insufficient_source) blocks auto-applying ungrounded prose.
  • P3 — author runs persist edition_id=null (was storing an author id in the edition slot); cross-link via the SeoBackfillJob (entityType+entityId).

Tests — 28 unit (full suite 402 green)

Fake ILlmService routed per FeatureTag + recording IAgentRunWriter + pure gate helpers (IsManualProtected, IsSourceMaterialInsufficient, EffectiveTrust/CrewResultRequiresReview), DB-free. AI-042 AutoPublish + StudyBuddy tool set-equality preserved; no ITool leaked.

Verify

  • dotnet test tests/TextStack.UnitTests → 402 pass
  • dotnet test tests/TextStack.AiEvals → 24 pass / 5 skip (no-key floor)
  • dotnet format --verify-no-changes → clean

Legacy SEO poller + /internal/seo/* unmodified. No DB migration.

🤖 Generated with Claude Code

… (AI-043)

Sibling to AutoPublishCrew: runs the AI-041 specialists (researcher→
drafter→critic→editor) over ILlmService to generate Edition.Description
+ Author.Bio, routed through the EXISTING SeoBackfillJob apply path so
trust gate / Before-After snapshot / revert / SSG enqueue keep working
unchanged. Admin-triggered; legacy seo-backfill-poll.sh + Claude CLI
untouched and default.

DRY: extracted the per-field crew machinery (FieldCrewState, 4-stage
plan, NeedsReview gate, agent_run persistence, FieldResult) out of
AutoPublishCrew into a shared FieldCrew runner; AutoPublishCrew is now a
thin wrapper (crew.autopublish, behavior + tests unchanged). Banned
phrases shared via CrewBannedPhrases.

SeoBriefs builds ContentBrief from per-field defaults (NOT the raw
SeoTemplate prompt — it has placeholders + JSON-output instructions that
would corrupt the prose specialists); template read only for TrustLevel
via the job path. SeoPromptSanitizer applied to every entity value.

Hardened per QA (2 P1 + P2 + P3):
- MarkRunningAsync: targeted Queued→Running before GetContextAsync (was
  500 + wedged job on every call); crew failure marks job Failed
- manual-source protection in ApplyCrewResultAsync (loads live SeoSource;
  Manual+filled field parks NeedsReview, never overwritten) — restores
  the AI-042 contract this path had dropped
- source-material floor (<200 chars → NeedsReview insufficient_source)
  blocks empty-context hallucination from auto-applying
- author runs persist edition_id=null (was storing author id in the
  edition slot); cross-link via SeoBackfillJob

28 unit tests (fake ILlmService + recording IAgentRunWriter + pure gate
helpers), DB-free. 402 total green; AI-042 + StudyBuddy set-equality
preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mrviduus mrviduus merged commit 8891ca5 into main Jun 16, 2026
5 checks passed
@mrviduus mrviduus deleted the ai-043-seo-crew branch June 16, 2026 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant