feat(ai): SeoCrew — in-process SEO Backfill crew via shared FieldCrew (AI-043)#338
Merged
Conversation
… (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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI-043 —
SeoCrewintegration (Phase 7)Sibling to AI-042's
AutoPublishCrew: runs the AI-041 specialists (researcher→drafter→critic→editor) overILlmServiceto generate Edition.Description + Author.Bio, routed through the existingSeoBackfillJobapply path so the trust gate / Before-After snapshot / revert / SSG enqueue all keep working unchanged. Admin-triggered; legacyseo-backfill-poll.sh+ Claude CLI untouched and default.DRY — shared
FieldCrewExtracted the per-field crew machinery (
FieldCrewState, 4-stage plan,NeedsReviewgate,agent_runpersistence,FieldResult) out ofAutoPublishCrewinto a sharedFieldCrewrunner.AutoPublishCrewis now a thin wrapper —crew.autopublish, behavior + all AI-042 tests unchanged. Banned-phrase list shared viaCrewBannedPhrases.Brief construction
SeoBriefsbuildsContentBrieffrom per-field defaults (Edition.Description 800–1600, Author.Bio 600–1400) — deliberately NOT the rawSeoTemplate.PromptTemplate(it carries{{placeholders}}+ JSON-output instructions that would corrupt the prose specialists). The template is read only for itsTrustLevel, via the job apply path.SeoPromptSanitizerapplied to every entity value (injection defense not bypassed).Endpoint
POST /admin/seo/{entityType}/{id}/crew-generate(admin auth,seo.crewrate limit). Resolve entity → build+sanitize source → run crew → enqueueSeoBackfillJob→ apply via job path. Returns runId + jobId + critique summary + status.Hardened per adversarial QA (2 P1 + P2 + P3)
GetContextAsyncasserts the job isRunningbut nothing transitioned it fromQueued→ every call threw 500 after spending LLM cost and wedged the job. Added targetedMarkRunningAsync(Queued→Running for this job) + crew failure → jobFailed. (Caught only by adversarial review — DB-free unit tests never exercised the wiring.)ApplyCrewResultAsyncnow loads liveSeoSource; aManualfilled field parksNeedsReviewand is never overwritten.NeedsReviewinsufficient_source) blocks auto-applying ungrounded prose.edition_id=null(was storing an author id in the edition slot); cross-link via theSeoBackfillJob(entityType+entityId).Tests — 28 unit (full suite 402 green)
Fake
ILlmServicerouted perFeatureTag+ recordingIAgentRunWriter+ pure gate helpers (IsManualProtected,IsSourceMaterialInsufficient,EffectiveTrust/CrewResultRequiresReview), DB-free. AI-042 AutoPublish + StudyBuddy tool set-equality preserved; noIToolleaked.Verify
dotnet test tests/TextStack.UnitTests→ 402 passdotnet test tests/TextStack.AiEvals→ 24 pass / 5 skip (no-key floor)dotnet format --verify-no-changes→ cleanLegacy SEO poller +
/internal/seo/*unmodified. No DB migration.🤖 Generated with Claude Code