---
name: new-blog
description: |
  Use when: kullanıcı "yeni blog yaz", "blog üret", "content gen", "blog
  draft", "makale yaz", "yeni içerik üret", "new blog post", "pillar yaz",
  "cluster yaz" der ya da /pseo-new-blog çağırır; master.xlsx[new_content_plan]
  satırından plan al, SERP analiz yap, 5 template render et, JSON-LD @graph
  5 entity üret, Foundational Principles 3-katman (truth-verifiable +
  profile-aware + anti-cheap-content) enforce et.
  Also use when: master.xlsx[topical_map] içinde assigned_url=null olan
  pillar/cluster/supporting satırı için ilk içerik üretilecek; new_content_plan
  satırının lifecycle_status=GREEN ya da PLANNED durumda; mode='draft'
  (review) veya 'publish' (immediate) seçildi; profile-aware switch tetik
  (project-config[profile] ya da project-config[profiles] priority merge).
  Do not use when: mevcut blog revize için (revise-content kullan); FAQ
  block re-render için (faq-optimization kullan); decay/sunset/prune için
  (content-remediation kullan); image generation için (generate-images
  kullan); manuel WordPress upload için (Section A upload-instructions
  template render edilir, skill upload yapmaz); master.xlsx eksikse
  (init-project önce çalışmalı, DURUR #1).
version: "1.0"
status: wip
category: production
inputs:
  project_slug:        { type: string,  required: true,  description: "Workspace proje slug'ı (örn 'projeA'). projects/{slug}/ dizinine map edilir." }
  new_content_plan_id: { type: string,  required: true,  description: "master.xlsx[new_content_plan] row id reference (kolon A)." }
  mode:                { type: string,  required: false, default: "draft", description: "Output mode — 'draft' (review öncesi) veya 'publish' (immediate). Enum dışı değer DURUR #5." }
outputs:
  - "outputs/blog/{slug}/article.html"
  - "outputs/blog/{slug}/schema.jsonld"
  - "outputs/blog/{slug}/meta-tags.json"
  - "outputs/blog/{slug}/upload-instructions.md"
  - "_state/events.jsonl"
consumes:
  - "init-project:master.xlsx[new_content_plan]"
  - "init-project:master.xlsx[cluster_keywords]"
  - "init-project:master.xlsx[topical_map]"
  - "init-project:project-config[profile]"
  - "init-project:project-config[content_settings]"
  - "init-project:project-config[brand_identity]"
  - "rules/content-quality.md"
  - "rules/content-html-discipline.md"
  - "rules/content-seo-discipline.md"
  - "rules/content-eeat-discipline.md"
  - "rules/content-llm-discipline.md"
  - "rules/content-update-discipline.md"
  - "rules/content-craft-discipline.md"
  - "templates/content/new-blog.template.md"
  - "templates/content/new-blog.template.html"
  - "templates/content/faq-block.template.html"
  - "templates/content/upload-instructions.template.md"
  - "scripts/production/new_blog.py"
  - "scripts/production/quality_gates.py"
produces:
  - "content-remediation"
  - "faq-optimization"
  - "generate-images"
  - "indexing-ping"
triggers:
  manual: ["/pseo-new-blog"]
  natural_language: |
    "yeni blog yaz", "blog üret", "content gen", "blog draft",
    "makale yaz", "yeni içerik üret", "new blog post yaz",
    "pillar yaz", "cluster yaz", "supporting içerik yaz"
  hooks: []
  scheduled: []
mcp_tools:
  required:
    - "mcp__gsc__search_analytics"
    - "mcp__dataforseo__serp_organic_live_advanced"
    - "mcp__ScraplingServer__stealthy_fetch"
  optional:
    - "mcp__dataforseo__dataforseo_labs_google_keyword_overview"
budget:
  uses_paid_mcp: true
  estimated_credits: 8
autonomy:
  confidence: HIGH
  requires_approval: true
  safe_auto_execute: false
---

# new-blog — production skill (Phase 11 Wave 1, W-F1)

Plan-driven full-article generator. Reads a single
`master.xlsx[new_content_plan]` row, joins it against
`master.xlsx[cluster_keywords]` (keyword set + intent + forbidden filters)
and `master.xlsx[topical_map]` (pillar/cluster/supporting page_type +
internal-link plan), runs SERP top-10 + **mandatory** Tier-1 Scrapling
competitor recon (×10 organic, CCE arming) and gates the draft against the
Brief Paketi, then renders four artifacts:

1. `outputs/blog/{slug}/article.html` — `<article class="pse-blog-post">`
   fragment (R-22 fragment boundary; no `<html>` / `<body>` wrap).
2. `outputs/blog/{slug}/schema.jsonld` — JSON-LD `@graph` with 5
   entities (Article + Organization + Person + BreadcrumbList +
   FAQPage; R-78..R-83).
3. `outputs/blog/{slug}/meta-tags.json` — meta title (≤580px desktop)
   + meta description (≤990px desktop) per R-35 (single authority) +
   OpenGraph + Twitter card pack.
4. `outputs/blog/{slug}/upload-instructions.md` — Section A new-blog
   manuel WordPress upload workflow (R-74); skill itself does NOT
   publish.

The skill is **READ-ONLY** with respect to `master.xlsx`
(`new_content_plan` allowed_writers is `null` — F-1 schema authority,
verified pre-flight). It never calls `transaction.append`,
`transaction.update`, or `transaction.delete` against the workbook.
The only state mutation is the audit append to `_state/events.jsonl`
with `event_type=content_new`.

## Inputs (frontmatter contract)

| Name                  | Type   | Default | Notes                                                                         |
|-----------------------|--------|---------|-------------------------------------------------------------------------------|
| `project_slug`        | string | —       | Required. `projects/{slug}/` workspace root.                                  |
| `new_content_plan_id` | string | —       | Required. Row id (kolon A) in `master.xlsx[new_content_plan]`.                |
| `mode`                | string | `draft` | `draft` (review) or `publish` (immediate). Other values → DURUR #5.           |

## Outputs (artifacts produced)

- `outputs/blog/{slug}/article.html` — schema-markup-aware, WCAG 2.1 AA
  compliant, BEM `pse-` CSS class prefix HTML article fragment.
- `outputs/blog/{slug}/schema.jsonld` — JSON-LD `@graph` 5 entity
  (Article + Organization + Person + BreadcrumbList + FAQPage).
- `outputs/blog/{slug}/meta-tags.json` — meta title (≤580px) + meta
  description (≤990px) per R-35 + OG + Twitter pack.
- `outputs/blog/{slug}/upload-instructions.md` — Section A new-blog
  WordPress manual upload steps (R-74).
- `_state/events.jsonl` — audit append, `event_type=content_new`,
  `event_kind=work` (events.schema enum: provenance / work / audit /
  workflow), `schema_version=1.0`.

## Foundational Principles Enforcement (3-Layer)

The 3 üst-prensip (Phase 10 `rules/content-quality.md#foundational-principles`)
gate this skill end-to-end. No alt-rule (R-01..R-148) overrides them.

### Principle 1 — Truth-Verifiable Content (R-27, Süleyman 5x vurgu)

**Statement.** Tüm content/source/link/data %100 doğru ve kanıtlanabilir;
uydurma yasak (kaynak, hikaye, case study, fiyat, stat, ürün/feature/
image-link).

**3-katman defense (Step 8 implements):**
- Layer 1: skill prompt explicit "uydurma yasak" başlangıçta — Step 7
  generation öncesi system note inject.
- Layer 2: post-generate fact-check pass — her claim için
  `content_settings.original_research_database` + SERP top-10 source
  match; citation count ≥ R-44 threshold.
- Layer 3: citation enforce — R-44 source verification 3-katman + R-105
  expert quote bank-driven (`content_settings.experience_database`
  lookup, uydurma quote yasak).

**Failure mode:** RED — yayın iptal, çıktı discard. DURUR #6 trigger.

### Principle 2 — Profile-Aware Enforcement

**Statement.** Skill behavior `project.config.json[profile]` enum'una
göre değişir (singular field, Phase 11 W-F1 cascade fix). When `profile`
is unset, fall back to `profiles[]` priority merge per
`rules/content-quality.md` Principle 2 tablosu.

**Enum 5-value:** `e-commerce` / `ymyl` / `local-service` / `b2b-saas` /
`portfolio`.

**Switch logic (Step 2 + Step 7 + Step 8):**

| Boyut                      | YMYL                       | e-commerce        | b2b-saas          | local-service       | portfolio  |
|----------------------------|----------------------------|-------------------|-------------------|---------------------|------------|
| Author byline (R-28)       | Zorunlu                    | Yok               | Esnek             | Esnek               | Yok        |
| Tone                       | semi-pro + siz             | conversational+sen| formal + siz      | conversational+siz  | esnek      |
| Word count                 | 1500–4000                  | 800–2500          | 1800–3500         | 1000–2500           | 800–2000   |
| Counter-argument (R-50)    | Zorunlu                    | Skip              | Opsiyonel         | Skip                | Skip       |
| Disclaimer (R-51)          | medical/legal/financial    | Skip              | Opsiyonel         | Skip                | Skip       |
| Image style                | clean-illustration         | product-photo     | diagram-screenshot| location-photo      | esnek      |
| Stats density (R-104)      | min 1/500w, max 1/200w     | min 1/800w, max 1/300w | min 1/600w, max 1/250w | min 1/1000w, max esnek | esnek |

**Failure mode:** AMBER — wrong profile resolution worker raporlar; RED
ancak `profile` enum dışı (DURUR #2).

### Principle 3 — AI Suistimal Önlemi (Anti-Cheap-Content, 7-pattern gate)

**Statement.** AI'ın doğal cheap content padding davranışını preempt et.

**7-pattern gate (Step 8 implements):**
- H3 zorunluluk gate — H2 word count > 200 → min 2 H3 (R-30).
- Heading keyword density %40–60 — primary_keyword count / total heading
  count (hard cap, R-30 stuffing önleme).
- Citation density per 500 word 1-2 (R-106).
- FAQ count talep-güdümlü 3-6 (kanıt varsa), hard cap 10 (R-09).
- Stats density profile-aware min/max (R-104 **tek kaynak / single
  source**; yukarıdaki tablo R-104'ün min+max cap'lerini birebir taşır,
  new-blog kendi sayısını uydurmaz — FIX-H5 hizalaması).
- Per-H2 list cap 1 (R-07; multi-list AI padding önleme).
- AI signature humanize — `brand_identity.tone_phrases_blocklist`
  consume + replace pattern ("Aslında", "Sonuç olarak", "Özetle",
  R-118).

**Failure mode:** AMBER warning (auto-correct attempt) → RED fail
(manuel revise). 2x AMBER aynı pass → RED upgrade.

## R-121 Bank Selection Logic (Pre-Publish)

Bank entry (R-105 expert quote / R-114 original research / R-119
first-hand experience) selection passes a **3-step filter** before any
template renders. Step 7 (`render_5_templates`) reads the result; Step
12 (`emit_provenance`) records the chosen entries. R-121 catches
**semantic** repetition (the same core fact copy-pasted across many
blogs); R-118 catches **stylistic** repetition (AI signature words
within one page). The two rules compose — both fire pre-publish.

1. **Topic match.** `entry.applicable_topics ∩
   (new_content_plan.primary_keyword ∪ new_content_plan.assigned_cluster
   ∪ topical_map[matching_row].cluster) ≠ ∅` → entry remains a
   candidate; empty intersection → entry skipped.
2. **Profile density cap.** From the candidate set, select up to the
   per-profile maximum:
   - YMYL: 2 experience + 1 research entry per content
   - b2b-saas: 1 experience + 1 research entry per content
   - e-commerce / local-service / portfolio: 1 experience + 0 research
     entry per content
3. **Rotation (30-day).** For each remaining candidate, count usage in
   the append-only `_state/events.jsonl` `content_new` work events whose
   `note` records `bank_entry_id == entry.id` AND
   `timestamp >= now - 30d`. Skip if
   `count >= entry.max_usage_per_month`; rotate to a different entry or
   to a different `phrasings[]` form of the same entry.

If all candidates fail the 3-step filter, the skill emits AMBER and
retries with `phrasings[]` rotation; 2x AMBER same pass → RED upgrade
(manual review).

**Post-publish usage record** (in the `events.jsonl` audit append at
Step 12): the `content_new` work event records the `bank_entry_id`(s)
used inside its `note` field — the append-only `_state/events.jsonl` is
the **only** state surface this skill writes (consistent with the
READ-ONLY claim above). The rotation counter at Step 3 reads from this
same append-only `content_new` event log on the next invocation —
single source of truth. The skill does **NOT** write
`master.xlsx[completed_work]` (`allowed_writers=null` — F-1); if a
durable `completed_work` row is later required, the done-protocol /
mark-done skill owns that append.

**Schema enablement (v1.4, commit `8e07e1c`):** each bank entry exposes
`applicable_topics`, `phrasings`, `last_used_in_content_id`,
`max_usage_per_month`. Stage C of brand-onboarding (commit `cb8df43`)
populates these via R-44 evidence-gated atomic write.

**Runtime integration deferred to Phase 11 Wave 2.** This SKILL.md
section is the spec lock. `scripts/production/new_blog.py` now exists (CCE
B4 orchestrator helpers `assemble_brief` / `evaluate`) but does NOT yet
implement the R-121 3-step bank filter. When Wave 2 lands that runtime,
the 3-step filter implements as a pre-`render_5_templates` helper consumed
by Step 7.

## R-124 — YMYL Uzman İnceleme İmzası (Pre-Publish Expert-Review Sign-Off)

YMYL profillerinde (`profile == "ymyl"`), R-124 gereği yazar künyesi
(R-28) yalnızca **kayıtlı bir insan incelemesi** ile geçerlidir. Bu
skill, YMYL içeriğini yayınlamadan ÖNCE zorunlu bir inceleme-imzası
adımı çalıştırır (Step 11.5 `ymyl_review_signoff`, Step 11 ile Step 12
arasında):

1. **Operatör inceleyiciyi adlandırır.** Yayın öncesi, operatör (Süleyman
   ya da atanmış uzman) insan **reviewer** (inceleyenin) adını verir.
   Reviewer adı yoksa → **DURUR #9** (RED, yayın bloklu — R-124).
2. **Audit kaydı (events.jsonl, append-only).** Skill, inceleme kanıtını
   `scripts/state/events_writer.append_audit(...)` ile bir
   `event_kind=audit` satırına yazar:
   - `audit_action="modified"` (events.schema 6-değer canonical enum
     `{created, modified, deleted, accessed, permission_changed,
     config_changed}` — içeriğin yayın-öncesi inceleme kapısı
     onaylandı/değiştirildi).
   - `audit_target="content:{project_slug}:{post-slug}"`.
   - `actor="skill:new-blog"`.
   - `notes` payload üç zorunlu alan taşır (R-124): **reviewer**
     (inceleyen adı) + **review_date** (ISO 8601) + **content_version**
     (content hash veya R-103 revision id).
3. **Eşleştirme.** Bu audit satırı, R-28 künyesinin arkasındaki gerçek
   insan inceleme kanıtıdır (Principle 1 Truth-Verifiable + EEAT
   Trustworthiness). İnceleme kaydı olmadan adlı yazar künyesi =
   uydurulmuş yazarlık sinyali (Google rater-guideline "Lowest"). R-28
   künyeyi görünür kılar; R-124 künyenin arkasında belgelenmiş bir insan
   inceleme olmasını zorunlu kılar.

Non-YMYL profillerde bu adım atlanır (R-28 künye opsiyonel/yok), audit
satırı yazılmaz.

## CCE Döngüsü (Competitive Content Engine — Silahlan → Yaz → Kapı → Re-write)

new-blog artık CCE motorunu çalıştırır: rakipten **ölçülebilir biçimde daha
iyi** içerik üretmek için bir **silahlan → yaz → kapı → re-write** döngüsü.
Mimari kural (spec §3): döngüyü **Claude (bu skill) sürer** çünkü
`scripts/production/new_blog.py` SAF Python'dur — MCP'yi/Claude'u ÇAĞIRMAZ;
döngüye yalnızca iki saf yardımcı verir (`assemble_brief`, `evaluate`). MCP
sınırı skill'e (Claude'a) aittir; deterministik tutkal koda.

### 1. Silahlan (Arming) — ham MCP JSON → Brief Paketi

Claude ham veriyi MCP ile toplar (kod değil), diske yazar, sonra
`new_blog.assemble_brief(...)`'i çağırır:

| Sinyal              | MCP tool (Claude çağırır)                                          | Not                                  |
|---------------------|-------------------------------------------------------------------|--------------------------------------|
| SERP top-10 organic | `mcp__dataforseo__serp_organic_live_advanced` depth=10            | rakip sıralaması + URL listesi       |
| Rakip DOM ×10       | `mcp__ScraplingServer__stealthy_fetch`                            | **ZORUNLU** her organic URL (Step 4) |
| Keyword genişleme   | `mcp__dataforseo__dataforseo_labs_google_keyword_suggestions` + `…_keyword_overview` | reliable substring + volume/intent   |
| AI Overview         | `serp_organic_live_advanced` `ai_overview` bloğu                  | gerçek `answer_points` (Step 5)      |
| GSC sinyali         | `mcp__gsc__search_analytics`                                      | `{real_queries, current_position}`   |

`assemble_brief` 3 B1 transform'u sırayla çalıştırır (`competitor_recon_transform`
+ `keyword_aio_intel_transform` + `build_brief`) → tam **Brief Paketi**
(`gap` + `structure_ceiling` + `keyword_set` + `aio` + `gsc`). GSC sinyali
`gsc=` parametresiyle `build_brief`'in boş default'unu override eder.

### 2. Yaz (Craft) — Claude, R-149+ rehberiyle

Claude makaleyi yazar; `rules/content-craft-discipline.md` (R-149..R-153)
**pozitif yazım katmanını** uygular: pain-mirror empati girişi (R-149), somut
örnek/senaryo (R-150), aktif marka sesi (R-151), rakip-üstü özgün değer
`data-original="true"` (R-152), H2 derinliği + mantıksal akış (R-153). Brief'in
`gap` maddeleri kapsanır; `structure_ceiling`+1 anlamlı tablo/liste hedeflenir.
Foundational Principles 3-katman + R-121 bank seçimi yine geçerlidir (üstte).

### 3. Kapı (Gate) — `new_blog.evaluate(...)`

Üretilen HTML `new_blog.evaluate(content_html, brief, round_num=..., max_rounds=3)`
ile ölçülür; bu `scripts/production/quality_gates.py`'nin `run_all`'ini **IMPORT
ile** çağırır (subprocess değil) — 8 kapı sırasıyla: p0_truthfulness > gap >
structure > depth > aeo > voice > originality > schema_visual. Döner:
`{gate_result, action, feedback}`.

### 4. Re-write / Terminal — döngü kararı

| `action`         | Anlam                       | Sonraki adım                                                       |
|------------------|-----------------------------|-------------------------------------------------------------------|
| `PASS`           | overall PASS                | yayın akışına devam (Step 9+)                                     |
| `REWRITE`        | overall RED, round < 3      | `feedback` (= `rewrite_feedback`) ile YENİDEN yaz (adım 2)        |
| `AMBER_TERMINAL` | overall RED, round ≥ 3       | **3-tur cap** — sonsuz döngü YOK; eksik kapılar operatöre raporlanır (spec §8) |

`AMBER` yalnız orkestratör kararıdır; kapıların kendisi PASS/RED kalır. Döngü
`PASS`'e ya da `AMBER_TERMINAL`'e ulaşana kadar adım 2 ↔ 3 tekrar eder. Craft
kuralı ↔ kapı eşlemesi: R-149 → voice+aeo, R-150 → depth, R-151 → voice,
R-152 → originality, R-153 → depth (`content-craft-discipline.md`).

## Routing — 12-Step Workflow

> Step names are stable identifiers across runs (used as
> `steps[*].name` when the skill calls `workflow_runner.create_run`).

### Step 1 — `read_master_xlsx`

`master.xlsx[new_content_plan]` row read where
`id == {input.new_content_plan_id}`. `master.xlsx[cluster_keywords]`
filter where `assigned_url == new_content_plan.url_slug`.
`master.xlsx[topical_map]` filter where
`pillar == new_content_plan.assigned_cluster` OR
`cluster == new_content_plan.assigned_cluster`.

Workbook opens with `openpyxl.load_workbook(read_only=True)`. F-1
allowed_writers=null discipline: skill MUST NOT call
`transaction.append`, `transaction.update`, or `transaction.delete`.

`new_content_plan` 14 columns consumed:
`id, title, url_slug, primary_keyword, monthly_volume, assigned_cluster,
target_word_count, priority, created_date, tivl_tag, lifecycle_status,
image_prompt, alt_text, content_type` (Phase 10 additive bump).
`content_type` enum: `[listicle, guide, comparison, research, tutorial,
review]`.

`cluster_keywords` 11 columns consumed: `cluster, keyword,
monthly_volume, data_source, assigned_url, gsc_clicks, gsc_impressions,
gsc_position, intent, forbidden_kw, forbidden_reason`. `intent` enum:
`[Informational, Commercial, Transactional, Navigational]`.

`topical_map` 10 columns consumed: `pillar, cluster, primary_keyword,
monthly_volume, data_source, assigned_url, page_type, status, priority,
note`. `page_type` enum: `[pillar, cluster, supporting]`.

**DURUR #1.** master.xlsx eksik → init-project skill önce çalışmalı.

### Step 2 — `read_project_config` (Profile-Aware Switch)

`projects/{slug}/project.config.json` parse. Singular `profile` field
(schema v1.2, Phase 11 W-F1 cascade fix) is the dominant Principle 2
switch; when missing, fall back to `profiles[]` priority merge per
`rules/content-quality.md` Principle 2 tablo.

`content_settings` 14 fields consumed (Phase 10): `toc_strategy,
related_posts_strategy, author_strategy, css_strategy, indexnow_enabled,
ai_training_optin, video_integration, internal_data_sharing,
external_uniqueness_check, original_research_database,
experience_database, video_database, disclaimer_templates, image_model`.

`brand_identity` 18 fields consumed (Phase 10): `logo_url, primary_color,
secondary_color, accent_color, font_family_heading, font_family_body,
header_template_id, footer_template_id, source_url_for_sampling, tone,
hitap, anglicism_tolerance, tone_phrases_blocklist, font_heading,
font_body, default_hero_url, same_as_urls, image_style`.

**DURUR #2.** Singular `profile` enum dışı bir değere sahipse RED;
schema v1.2 cascade fix uygulanmadıysa (`schema_version != "1.2"` ve
profile field schema'da yok) önce `scripts/migrations/0002_project_
config_1.1_to_1.2.py` çalışmalı.

### Step 3 — `serp_analysis` (R-08)

`mcp__dataforseo__serp_organic_live_advanced` çağrı:
`keyword=new_content_plan.primary_keyword`, `depth=10`. Top-10 sonuç +
entity extract (organic results `dom_url`, `title`, `description`).

**DURUR #5.** SERP analiz fail (boş response, API error, mode enum dışı)
→ manuel input gerekli, skill `awaiting_approval`.

### Step 4 — `scrapling_competitor_recon` (CCE silahlanma, R-08 derinleştirme)

**ZORUNLU (artık optional değil; top-3 değil top-10).** SERP top-10 organic
sonucun HER BİRİ için `mcp__ScraplingServer__stealthy_fetch` ile DOM çekilir —
CCE silahlanma motoru rakip kapsamasının TAMAMINI görmeli (spec §4a), yarısını
değil. Her sayfa `{"url": ..., "html": ...}` olarak biriktirilir; bu liste +
ham SERP `assemble_brief`'e verilir → `competitor_recon_transform` heading tree
+ answered questions + candidate entities + structure ceiling (anlamlı
tablo/liste) + word_count çıkarır.

Tier-0 fallback (basic SERP description) yalnızca tek bir URL kalıcı 403/blok
verirse o URL'e özgüdür; Scrapling **required** MCP bağımlılığı olduğundan
adım topluca atlanamaz. Eksik/bloklu sayfalar AMBER warning raporlanır,
silahlanma toplanan sayfalarla devam eder.

### Step 5 — `aio_citation_check` (R-109/R-110/R-111 + CCE AEO)

Pasaj alıntılanabilirlik: SERP top-10 page'lerde featured snippet detect
(R-109), people_also_ask (R-110), ai_overview citation (R-111).

**CCE AEO bağı.** Ham `serp_organic_live_advanced` payload'undaki `ai_overview`
bloğu boş heuristik DEĞİL, `assemble_brief` → `keyword_aio_intel_transform` ile
gerçek `aio.answer_points` (+ `aio.cited_sources`) listesine dönüştürülür ve
Brief Paketi'nin `aio` alanına yazılır. Bu `answer_points` B2 kapı motorunun
**aeo** kapısı tarafından ölçülür: içerik bu noktaların HER BİRİNİ kapsamalı
(`aio.present=False` ise yalnız "intro cevap-önce" şartı — R-149 / spec §6 R2).

### Step 6 — `content_gap`

`cluster_keywords[forbidden_kw]` negatif filter (forbidden_reason
kontrol). `topical_map` gap detect: `status == 'planned' AND
assigned_url == null`. Internal link plan: relevant
`topical_map[pillar/cluster]` URL'leri article body içinde reference
(per ~300 word 1 link, R-06).

### Step 7 — `render_5_templates`

5 template render-time slot consume (4 file + 1 internal upload section):

1. `templates/content/new-blog.template.md` — markdown skeleton
   (frontmatter + H1/H2/H3 outline). Planning-only, NOT yayınlanır.
2. `templates/content/new-blog.template.html` — HTML article fragment
   (`<article class="pse-blog-post">` kök, R-22 fragment boundary).
3. `templates/content/faq-block.template.html` — FAQ statik R-43
   (accordion `<details>` + `<summary>` YASAK, R-43 statik HTML zorunlu).
4. `templates/content/upload-instructions.template.md` — Section A
   new-blog kısmı.

Render-time slot dictionary:

| Slot                 | Source                                                           |
|----------------------|------------------------------------------------------------------|
| `{primary_keyword}`  | `new_content_plan.primary_keyword`                               |
| `{tone}`             | `brand_identity.tone` (Principle 2 profile-aware override)       |
| `{hitap}`            | `brand_identity.hitap`                                           |
| `{primary_color}`    | `brand_identity.primary_color`                                   |
| `{font_heading}`     | `brand_identity.font_heading` (alias to font_family_heading)     |
| `{font_body}`        | `brand_identity.font_body`                                       |
| `{author}`           | `content_settings.author_strategy` switch (YMYL byline-required) |
| `{disclaimer}`       | `content_settings.disclaimer_templates[profile_kind]`            |
| `{default_hero_url}` | `brand_identity.default_hero_url` (R-77 fallback)                |
| `{image_style}`      | `brand_identity.image_style` (Principle 2 profile-aware)         |

CSS class prefix: `pse-` BEM (plugin agnostik — proje slug
hardcode YASAK, e.g. `demo-dental`/`demo-furniture`/`demo-hvac`/`demo-petcare`/`demo-shop`/
`demo-tires`/`demo-construction`/`demo-agency` slug'lar HTML/CSS içinde geçemez).

### Step 8 — `foundational_principles_gate`

3-katman gate çalışır (Principle 1 + 2 + 3 yukarıda detaylı). Çıktı:
P1 RED → DURUR #6; P2 enum dışı → DURUR #2; P3 7-pattern AMBER 2x →
RED upgrade.

### Step 9 — `render_jsonld_graph` (R-78..R-83)

JSON-LD `@graph` 5 entity render:

- `Article` — `headline, description, datePublished, dateModified,
  author (Person ref), publisher (Organization ref), image, mainEntityOfPage`.
- `Organization` — `name, logo (logo_url), sameAs (same_as_urls;
  R-100)`.
- `Person` — `name, jobTitle, knowsAbout` (`author_strategy` switch,
  R-105 expert quote bank-driven `content_settings.experience_database`
  lookup; uydurma yasak — Principle 1 Layer 3).
- `BreadcrumbList` — Home > Pillar > Cluster > Article (R-83 4-level
  cap).
- `FAQPage` — talep-güdümlü 3-6 FAQ Q&A items, hard cap 10 (R-79; R-09).

Validate: Google Rich Results Test API mock validate (skill internal —
actual API call optional). DURUR #8 trigger: REJECT → schema fix önce.

### Step 10 — `meta_pixel_wcag_validate`

Meta title pixel calc: ≤580px desktop (R-35 single authority; ~60 char
is only a tech-audit approximation heuristic — Türkçe glyph widths
vary, so the pixel cap is authoritative, not the char count). Meta
description pixel calc: ≤990px desktop (R-35; ~160 char heuristic
only). DURUR #7 trigger: cap aşımı → auto-correct attempt; fail ise
manuel revise.

WCAG 2.1 AA axe-core simulate: violations=0 hedef. Color contrast
(`primary_color` vs background) ≥ 4.5:1. Image alt text required
(`new_content_plan.alt_text` consume; uydurma alt-text yasak —
Principle 1).

### Step 11 — `write_outputs`

4 artefact yaz:

- `outputs/blog/{slug}/article.html` — `<article class="pse-blog-post">`
  kök, R-22 fragment boundary.
- `outputs/blog/{slug}/schema.jsonld` — JSON-LD `@graph`.
- `outputs/blog/{slug}/meta-tags.json` — meta title/description/OG/
  Twitter pack.
- `outputs/blog/{slug}/upload-instructions.md` — Section A WordPress
  media library workflow (R-74).

### Step 12 — `emit_provenance`

`_state/events.jsonl` append (audit):

| Field            | Value                                          |
|------------------|------------------------------------------------|
| `event_type`     | `content_new`  (events.schema event_type enum) |
| `event_kind`     | `work`         (events.schema enum, ADR-020)   |
| `schema_version` | `1.0`                                          |
| `actor`          | `skill:new-blog`                               |
| `target`         | `master.xlsx[new_content_plan]#{id}`           |
| `timestamp`      | UTC ISO 8601                                   |
| `mode`           | `input.mode` (`draft` / `publish`)             |

## DURUR Conditions (9)

| #  | Trigger                                               | Resolution                                           |
|----|-------------------------------------------------------|------------------------------------------------------|
| 1  | master.xlsx eksik                                     | init-project önce çalışmalı (Phase 5 W-Q paterni)    |
| 2  | project-config[profile] enum dışı / schema v1.2 yok    | scripts/migrations/0002_project_config_1.1_to_1.2.py |
| 3  | 6 rules dosyası eksik                                 | Phase 10 deliverable verify (e4369ea commit)         |
| 4  | MCP budget aşımı (estimated_credits 8)                 | scripts/budget/check_budget.py blocked               |
| 5  | SERP analiz fail (R-08, mode enum dışı)               | manuel input, awaiting_approval                      |
| 6  | P1 fact-check fail                                    | RED — çıktı discard, claim revize                    |
| 7  | meta pixel cap aşımı                                  | auto-correct fail ise manuel revise                  |
| 8  | Schema Rich Results Test REJECT                       | JSON-LD `@graph` fix önce                            |
| 9  | YMYL profili + reviewer adı yok (R-124 imza eksik)    | RED yayın bloklu; operatör reviewer adlandır + append_audit kaydı (reviewer+review_date+content_version) |

## Cascade Fix W-F1 (Phase 10 EKSİĞİ Closure — Atomic Commit Içinde)

Phase 10 finished without `project-config[profile]` (singular) which
Foundational Principle 2 directly cites. Phase 11 W-F1 closes that gap
in the SAME atomic commit as the new-blog skill itself, so this skill
ships with a schema-valid Principle 2 contract from day one.

**File 1 — `schemas/project-config.schema.json`:**
- `schema_version` `"1.1"` → `"1.2"`.
- `properties.profile` added (singular, enum 5-value:
  `e-commerce` / `ymyl` / `local-service` / `b2b-saas` / `portfolio`).
- `required[]` UNCHANGED (additive policy →
  `rules/schema-versioning-discipline.md`).

**File 2 — `scripts/migrations/0002_project_config_1.1_to_1.2.py`:**
- 0001 paterni reuse (idempotent + dry-run + `.bak` backup).
- Refuses out-of-range source versions; pure `migrate(doc) -> dict`
  function for tests.

**File 3 — `scripts/state/bootstrap_project.py`:**
- `SCHEMA_VERSION = "1.1"` → `"1.2"` constant update.

**File 4 — `tests/skills/test_init_project.py`:**
- `schema_version` assertion `"1.1"` → `"1.2"`.
- Asserts schema declares `profile` field with the exact enum 5-value.

**Smoke test (cascade fix verify):**

```bash
python3 scripts/migrations/0002_project_config_1.1_to_1.2.py \
    --in /tmp/test_config.json --dry-run
jq '.properties.schema_version.const' schemas/project-config.schema.json
jq '.properties.profile.enum'         schemas/project-config.schema.json
```

Expected: schema_version `"1.2"`, profile.enum array of 5 strings.

## Plugin-Agnostic Discipline

- No project slug hardcoded inside this skill or its templates
  (`demo-dental`, `demo-furniture`, `demo-hvac`, `demo-petcare`, `demo-shop`, `demo-tires`,
  `demo-construction`, `demo-agency` are FORBIDDEN tokens — pytest grep verifies).
- CSS class prefix `pse-` BEM only (`pse-blog-post`, `pse-h2-answer`,
  `pse-tldr`, `pse-faq-block`).
- Brand styling resolves at render-time from `brand_identity` (per-
  project), never via hardcoded color/font.
- Per-call / per-url credit shape names (Phase 7 lesson, ADR-028 anti-
  pattern) MUST NOT appear in this skill — `budget.estimated_credits`
  (number) is the only allowed shape per skill-frontmatter schema.

## Error Handling

| Failure                          | Mode  | Action                                                                |
|----------------------------------|-------|-----------------------------------------------------------------------|
| master.xlsx schema drift         | RED   | DURUR #1, init-project re-run                                         |
| project-config[profile] dışı     | RED   | DURUR #2, cascade fix verify                                          |
| MCP DataForSEO budget exhausted  | RED   | DURUR #4, budget gate                                                 |
| Tier-1 Scrapling 403 / blocked   | AMBER | tier-0 SERP description fallback                                      |
| P1 citation missing              | RED   | DURUR #6, claim revize                                                |
| P3 7-pattern AMBER 2x same pass  | RED   | manuel revise                                                         |
| meta pixel cap > 580 / 990 (R-35)| AMBER | auto-correct → fail RED                                               |
| Schema Rich Results REJECT       | RED   | DURUR #8, `@graph` fix                                                |

## References

- `rules/content-quality.md` (Foundational Principles, R-01..R-148).
- `rules/content-html-discipline.md` (R-22 fragment, R-23 inline CSS,
  R-43 statik FAQ, R-77 fallback hero).
- `rules/content-seo-discipline.md` (R-08 SERP, R-78..R-83 schema).
- `rules/content-eeat-discipline.md` (R-28 byline, R-44 source verify,
  R-104 stats-density single-source, R-105 expert quote, R-114/R-119
  research bank, R-124 YMYL pre-publish review sign-off).
- `rules/content-llm-discipline.md` (R-109..R-111 AIO citation, R-118
  humanize).
- `rules/content-update-discipline.md` (R-50 counter-argument).
- `rules/content-craft-discipline.md` (R-149..R-153 — CCE pozitif yazım
  katmanı: pain-mirror giriş, somut örnek, marka sesi, özgünlük, derinlik).
- `scripts/production/new_blog.py` (CCE B4 orkestratör yardımcıları —
  `assemble_brief` + `evaluate`; saf Python, MCP/Claude çağırmaz).
- `scripts/production/quality_gates.py` (CCE B2 8-kapı motoru — `run_all`).
- `schemas/project-config.schema.json` v1.4 (Phase 3.1 bank entry
  schema bump — `applicable_topics`, `phrasings`,
  `last_used_in_content_id`, `max_usage_per_month` fields R-121 reads;
  W-F1 v1.2 + Phase 11 cascade still applies for `profile` singular).
- `schemas/master-excel.schema.json` (18 sheets — `internal_links` and
  `content_gaps` are NOT among them; F-4 schema authority).
- `schemas/events.schema.json` (event_type enum 12 values, event_kind
  enum 4 values).
- `schemas/skill-frontmatter.schema.json` (8 required fields, category
  enum 8 values, status enum 3 values).
- ADR-020 (event_kind=workflow vs work routing).

## Wave-Out Note

This skill is `status: wip` until Phase 11 Wave 2 acceptance gate;
`active` bump deferred to the post-Wave 1 closeout. Manager owns the
status flip + governance audit.
