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
100 changes: 78 additions & 22 deletions routines/_skills/write-blog-draft/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,44 @@ Generate a Knowcap blog post draft. Adapts to what's available — Knowcap sourc

## Status: P1 spec (real prompt, ready to run manually)

## Three modes
> **Governed by the PLAYBOOK** (`../claude-knowcap/marketing/digital-employees/seo/PLAYBOOK.md`). Every
> mode below is SERP-STOLEN — the routine passes either an inherited SEO content-brief or a live top-3
> SERP digest as `serp_steal`. **No `serp_steal` → REFUSE.** No more invented essays (16 of them = 0 clicks).

Routine picks mode at runtime based on what's available — the routine doesn't fail when a Knowcap source isn't available, it shifts mode.
## Modes

Routine picks mode at runtime: first by INTENT (Law 2), then — for informational intent — by what spine is available.

| Mode | Spine of the post | When the routine uses it |
|---|---|---|
| **`thesis`** | Persona + audit + vision + positioning | Default. General positioning posts. NO Knowcap source needed. |
| **`case-study`** | A specific Knowcap recording + its confirmed memories + persona | When the routine finds at least 1 source in Demo org for the target persona with ≥3 confirmed memories. |
| **`comparison`** | Competitor research + persona + audit | When `../claude-knowcap/company/docs/research/competitors-*.md` is fresher than 30 days AND not already covered in last 5 shipped blogs. |
| **`money-page`** | A `/compare/*` or use-case "zipper" landing page | **Commercial** intent (best/software/tool/vs/for-persona/pricing). The only format ranking #1 for us (Law 3). NOT a blog post. |
| **`thesis`** | SERP-stolen answer + persona + vision + positioning | Default for informational intent. Answers the query the way the winning results do, beats their depth. NO Knowcap source needed. |
| **`case-study`** | A specific Knowcap recording + its confirmed memories + persona | When the routine finds ≥1 Demo-org source for the persona with ≥3 confirmed memories. |
| **`comparison`** | Competitor research + persona + audit | When `../claude-knowcap/company/docs/research/competitors-*.md` is < 30 days AND not covered in last 5 shipped. |

Comment on lines +11 to 21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing prompt for comparison mode.

The modes table lists four modes (money-page, thesis, case-study, comparison), but only thesis and money-page have dedicated prompt sections below. Either add a comparison mode prompt or remove it from the table to avoid runtime ambiguity when the routine selects it.

♻️ Suggested fixes

Option A — Add a comparison prompt (preferred if the mode is real):

 ## Mode `comparison` — the prompt
+
+```
+[comparison prompt here]
+```

Option B — Remove from table if not yet implemented:

-| **`comparison`** | Competitor research + persona + audit | When `../claude-knowcap/company/docs/research/competitors-*.md` is < 30 days AND not covered in last 5 shipped. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@routines/_skills/write-blog-draft/SKILL.md` around lines 11 - 21, The modes
table in SKILL.md includes a `comparison` mode, but there is no corresponding
prompt section below, so the routine can select an undefined path. Update the
`write-blog-draft` skill by either adding a dedicated `comparison` prompt
section that matches the existing mode naming used in the table, or remove
`comparison` from the Modes table if it is not supported yet. Keep the mode list
and the prompt sections in sync so runtime selection is unambiguous.

Each mode has its own prompt template below.
Each mode is shaped by `serp_steal` (beat the top-10 structure). Each has its own prompt template below.

## Inputs (the routine assembles before calling the skill)

```yaml
mode: thesis | case-study | comparison
mode: money-page | thesis | case-study | comparison
intent: informational | commercial # navigational was skipped upstream (Law 2)
lang: en | ar # follows the keyword's demand (Law 4)
dir: ltr | rtl # ltr for en, rtl for ar
hreflang_pair: "<url-or-slug of the other-language equivalent, or null>"
target_persona: odoo-partners | mena-audit-firms | mena-agencies | regulated-verticals
target_keyword: "<string>"
target_keyword_5y_mena_interest: <0-100 OR null if Trends unavailable>

# SERP steal — REQUIRED (Law 1). Either an inherited brief or a live top-3 digest. Absent → REFUSE.
serp_steal:
source: brief | live-serp
top_results: [ { title, type, approx_word_count, h2s: [...] }, ... ] # the ranked competition
avg_word_count: <int> # the target to BEAT
shared_h2s: [...] # sub-topics every top result covers — must cover all
paa: [...] # People-Also-Ask → become FAQ + H2s
citable_passages: [...] # 1-2 passages to write as 134-167w answer blocks (Law 5)

# Always required (brand DNA lives in the claude-knowcap hub — sibling repo, NOT this repo's docs/)
vision_md: "<text>" # from ../claude-knowcap/company/docs/strategy/vision.md
positioning_md: "<text>" # from ../claude-knowcap/company/docs/strategy/POSITIONING.md
Expand Down Expand Up @@ -62,29 +80,35 @@ available_screenshots:
- USE: "the team", "the partner", "the client", "the meeting", "the recording", "the call", "the routine"
- Active voice. Short sentences when the claim is sharp.
- GEO structure: 134-167 word self-contained passages, FAQ section, schema-friendly H2/H3 hierarchy.
- **Arabic posts (`lang: ar`)** — write NATIVELY in Modern Standard Arabic for the searcher, NOT a translation of an English draft. Knowcap voice carries over (founder/expert, concrete, no consultant-speak); use the Arabic product terms (محاضر الاجتماعات، توثيق، تدقيق، تأكيد بشري) and `dir: rtl`. The banned-words list is English; for Arabic apply the same spirit (no filler/buzzwords).

## ICP gate (enforced at skill entry, all modes)
## Entry gates (enforced at skill entry, all modes)

If `target_persona` ∉ {odoo-partners, mena-audit-firms, mena-agencies, audit-and-legal, regulated-verticals} → emit `REFUSED` with reason `out-of-ICP`. Do NOT write the draft.
1. **ICP gate** — `target_persona` ∉ {odoo-partners, mena-audit-firms, mena-agencies, audit-and-legal, regulated-verticals} → `REFUSED`, reason `out-of-ICP`.
2. **SERP-steal gate (Law 1)** — `serp_steal` missing/empty → `REFUSED`, reason `no-serp-steal`. We never write from imagination.
3. **Intent gate (Law 2)** — if upstream passed a navigational/tool keyword → `REFUSED`, reason `navigational-intent`. Commercial intent must be `mode: money-page`, not a blog.
Do NOT write the draft if any gate fails.

## Frontmatter (emitted first, all modes, exact shape)

```yaml
---
title: "<title — max 70 chars, must include target_keyword OR close synonym>"
slug: <slug-form-of-title>
mode: thesis | case-study | comparison
title: "<title — max 70 chars, must include target_keyword OR close synonym; in {lang}>"
slug: <slug-form-of-title; append "-ar" when lang=ar>
mode: money-page | thesis | case-study | comparison
intent: {intent}
persona: {target_persona}
target_keyword: "{target_keyword}"
target_keyword_5y_mena_interest: {target_keyword_5y_mena_interest}
geo_score: <0-100 per GEO-AUDIT rubric, your honest estimate>
est_word_count: <draft word count>
draft_date: <today YYYY-MM-DD>
description: "<SEO meta description — 120-165 chars, plain English, no keyword stuffing>"
description: "<SEO meta description — 120-165 chars, in {lang}, no keyword stuffing>"
tags: [<5-8 kebab-case tags derived from persona + keyword + topic>]
author: "Hassan Arslan"
lang: "en"
dir: "ltr"
lang: "{lang}" # en | ar — follows the keyword's demand (Law 4)
dir: "{dir}" # ltr | rtl
hreflang_pair: "{hreflang_pair}" # the other-language equivalent URL/slug, or null
source_knowcap_ids: [<list only if mode=case-study, else []>]
embedded_screenshots: [<list of screenshot slugs used, else []>]
status: draft
Expand All @@ -96,9 +120,16 @@ status: draft
## Mode `thesis` — the prompt

```
You are writing a positioning post for Knowcap, persona-targeted. The post is GROUND-LEVEL THESIS — it doesn't cite a specific recording, it builds the argument from market + persona + vision.
You are writing a SERP-GROUNDED answer post for Knowcap, persona-targeted. This is NOT a free-form
opinion essay — that format earned 0 clicks across 16 posts. You are answering {target_keyword} the way
the winning results answer it, then beating their depth and adding Knowcap's honest angle.

Body structure (locked, 1,300-1,600 words total):
BEFORE structuring, obey `serp_steal`: cover EVERY heading in `serp_steal.shared_h2s`, answer every
`serp_steal.paa` question (these seed the FAQ + H2s), and write at least `serp_steal.avg_word_count`
words (beat the top-10, don't merely match). Lead each major section with a self-contained 134-167-word
answer block (the AI-citation sweet spot, Law 5).

Body structure (target = serp_steal.avg_word_count, beat the top-10; the outline below is the floor):

1. HOOK — 1-2 sentences with a sharp persona-specific opener. Cite a real-world surface fact (e.g., "187 Odoo partners in Egypt sell at $40-80 per consulting hour. The first scope dispute of the year costs the average partner 4-6 days they don't bill for.")
2. THE PAIN — 134-167 words. Why {target_persona} specifically. Use one bullet from persona_section_md as the load-bearing claim.
Expand Down Expand Up @@ -151,6 +182,28 @@ Body structure (locked, 1,300-1,600 words total):
7. CLOSING — 1-2 sentences.
```

## Mode `money-page` — the prompt

```
You are writing a COMMERCIAL landing page, not a blog post — the keyword has buyer intent (Law 2/3).
This is the only format that ranks #1 for us (the /compare/* pages). Build a `/compare/*` or use-case
"zipper" page: [use-case] × [persona | language] (e.g. "best AI meeting-minutes software for audit
firms", "أفضل برنامج محاضر اجتماعات"). The output is still a markdown draft, but its shape is a
conversion page, and the routine routes it to a money-page route, NOT /blog (flag in the PR body).

Obey `serp_steal`: match the page TYPE that ranks (comparison table / feature page / listicle), cover
every `serp_steal.shared_h2s`, beat `serp_steal.avg_word_count`.

Structure:
1. H1 with the exact commercial keyword + a one-line value promise.
2. A 134-167-word self-contained answer block directly answering the query (AI-citation; Law 5).
3. The comparison/feature substance the SERP demands (table if competitors rank with one).
4. Honest Knowcap differentiation (verified-facts / human-confirmation / audit-trail).
5. One clear conversion action (NOT "book a demo" copy-slop — a plain, specific next step).
6. FAQ — 5 questions from serp_steal.paa, 134-167 words each, FAQPage schema.
7. JSON-LD schema appropriate to the page type.
```

## Screenshot integration (any mode, optional)

After draft generation, scan the body for trigger keywords:
Expand Down Expand Up @@ -181,11 +234,14 @@ Add the used screenshot slugs to frontmatter `embedded_screenshots: [...]`.

## Validation gates (post-generation, all modes)

1. Banned words → regex scan body. If any present, REGENERATE that paragraph (max 2 retries) or REFUSE.
2. Word count → 1,300-1,600 (body only, not frontmatter). If outside, retry once with tightening/expansion guidance.
3. Target keyword → must appear in title AND H2-section-one AND ≥3 times in body.
4. Slug uniqueness → must not match any slug in `recent_shipped_slugs` or `recent_drafts_in_pipeline`.
5. FAQ count → exactly 5 H3 questions in the FAQ section.
1. Banned words → regex scan body (English posts). If any present, REGENERATE that paragraph (max 2 retries) or REFUSE. Arabic posts → apply the same no-buzzword spirit.
2. Word count → **≥ `serp_steal.avg_word_count`** (beat the top-10). Absent a SERP signal, 1,300-1,600 body words. Under target → retry once with expansion guidance.
3. SERP coverage (Law 1) → every `serp_steal.shared_h2s` heading is covered and every `serp_steal.paa` question is answered. Missing any → retry once.
4. Citable passages (Law 5) → at least 2 self-contained 134-167-word blocks present.
5. Target keyword → must appear in title AND H2-section-one AND ≥3 times in body (in the post's language).
6. Language → body language matches `lang`; `dir` correct; `hreflang_pair` set (or explicitly null).
7. Slug uniqueness → must not match any slug in `recent_shipped_slugs` or `recent_drafts_in_pipeline` (AR slug carries `-ar`).
8. FAQ count → exactly 5 H3 questions in the FAQ section.
6. Frontmatter shape → all required keys present, types correct.

## REFUSED output shape
Expand Down
58 changes: 44 additions & 14 deletions routines/blogger/AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Daily SEO-driven blog engine for knowcap.ai. Fires 07:00 Sun–Thu (open:agent,
Read this fully, then execute. The full spec is [`ROUTINE.md`](./ROUTINE.md); the per-mode
prompts + gates are [`_skills/write-blog-draft/SKILL.md`](../_skills/write-blog-draft/SKILL.md).

**READ THE PLAYBOOK FIRST (every run):** `../claude-knowcap/marketing/digital-employees/seo/PLAYBOOK.md`
— the 7 laws shared with the `seo` routine. It governs HOW we win non-brand clicks. The blogger owns the
**content lane** (writing); the seo routine owns the mechanical lane. Where any step below conflicts with
the playbook, the playbook wins. The load-bearing reality it encodes: 16 EN thesis posts earned **0
non-brand clicks in 28 days** — that format is dead. Stop inventing essays; answer the live SERP, in the
language the demand is in.

## Mission

Every day: pull live MENA SEO demand, refresh the ranked opportunity queue, surface a
Expand All @@ -22,21 +29,42 @@ hit a quota.
2. **Digest** — surface the top picks to Hassan in the run window / Claude agents sidebar.
3. **Burn-state** — read `burn-state.json`; decide if today is a blog-gen day (see ROUTINE.md
"Daily flow + burn cadence"). If not → done for today (insights only).
4. **Blog day** — pick the persona's top fresh EN opportunity as `target_keyword`. Assemble inputs
(persona section from `../claude-knowcap/company/docs/research/product-personas.md`,
VISION from `../claude-knowcap/company/docs/strategy/vision.md`,
POSITIONING from `../claude-knowcap/company/docs/strategy/POSITIONING.md`;
shipped slugs; queue row's volume+comp).
5. **Mode** — try in order:
(1) **case-study**: Demo-org → persona project → source with ≥3 confirmed memories → cite `source_knowcap_ids`, generate verification panel via `lib/gen-verification-panel.mjs`.
(2) **comparison**: freshest `../claude-knowcap/company/docs/research/competitors-*.md` < 30d AND not covered in last 5 shipped → compare.
(3) **thesis**: default.
6. **Write** via write-blog-draft SKILL → run ALL gates (banned words, 1300–1600 words, keyword
in title+H2+≥3, slug unique, exactly 5 FAQ, frontmatter). Fail → regenerate/skip, never ship thin.
7. **Output** — draft → `docs/content-pipeline/drafts/<slug>.md`, open `[blog-draft]` PR to main.
4. **Blog day — pick + classify the keyword.** Take the persona's top fresh opportunity as
`target_keyword` (best by score, NOT "the top EN one" — Arabic is in play now).
- **Classify intent (Law 2):** navigational/tool (login/download/تسجيل دخول/تحميل, bare brand) →
**skip this keyword**, advance to the next. Commercial (best/software/tool/vs/for-persona/pricing) →
**money-page mode** (Law 3), not a blog. Informational (how/what/template/نموذج/دليل) → continue.
- **Pick language (Law 4):** Arabic-demand keyword → **native Arabic post** (`lang: ar`, `dir: rtl`,
slug suffixed `-ar`), written in Arabic for Arabic searchers. English-demand → English. Note the
EN/AR hreflang pair to link.
- Assemble inputs (persona section from `../claude-knowcap/company/docs/research/product-personas.md`,
VISION `…/strategy/vision.md`, POSITIONING `…/strategy/POSITIONING.md`; shipped slugs; queue row's
volume+comp).
5. **Steal the SERP / inherit the brief (Law 1 — MANDATORY gate).** Look for the matching SEO brief at
`../claude-knowcap/knowledge/topics/research/seo/content-briefs/<keyword-slug>.md`. **If it exists, it
IS the SERP steal** — write TO it (it carries intent, the H2 outline that beats top-10, PAA questions,
target word count, the citable-passage callouts). If NO brief exists, pull the live top-3 yourself via
`serp_organic_live_advanced` (keyword's market: AR→KSA/Egypt+Arabic, EN→Egypt/UAE+English); extract the
avg word count, the H2s every top result shares, and the PAA. **A draft not shaped from a brief or a
live SERP is REFUSED** — no more invented essays.
6. **Mode** — within informational intent, pick the spine:
(1) **case-study**: Demo-org → persona project → source with ≥3 confirmed memories → cite
`source_knowcap_ids`, generate verification panel via `lib/gen-verification-panel.mjs`.
(2) **comparison**: freshest `../claude-knowcap/company/docs/research/competitors-*.md` < 30d AND not
covered in last 5 shipped → compare.
(3) **thesis**: default — now **SERP-grounded** (answer the query the way the winning results do, beat
their depth; never a free-form opinion essay).
For **commercial** intent → **money-page mode** (a `/compare/*` or use-case "zipper" landing page,
Law 3 — the only format that ranks #1 for us), not a blog post.
7. **Write** via write-blog-draft SKILL → run ALL gates (banned words; word count = the SERP/brief target
(beat top-10), not a fixed range; keyword in title+H2+≥3; 134–167w citable passages; schema + exactly
5 FAQ; correct `lang`/`dir` + hreflang pair; slug unique; frontmatter). Fail → regenerate/skip, never
ship thin.
8. **Output** — draft → `docs/content-pipeline/drafts/<slug>.md`, open `[blog-draft]` PR to main.
Always report live URL: `https://knowcap.ai/blog/<slug>` — post goes live automatically on PR merge
via `.github/workflows/publish-blog-draft.yml` + `scripts/publish-draft.mjs` (no manual move needed).
Update `burn-state.json` (total_posts++, posts_this_week++).
Update `burn-state.json` (total_posts++, posts_this_week++). After the PR opens, hand the URL to the
`seo` routine's TASK 9 (request-indexing) so it gets indexed in ~1 day, not weeks (Law 6).
Comment on lines +59 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify SEO routine TASK 9 and citable passage source
# 1. Find seo routine TASK 9
rg -n 'TASK 9|request-indexing|request indexing' ../claude-knowcap/marketing/digital-employees/seo/ || echo "Not found in expected path"
# 2. Search for 134 or 167 in playbook or specs
rg -n '134|167' ../claude-knowcap/marketing/digital-employees/seo/PLAYBOOK.md 2>/dev/null || echo "Not in playbook"
rg -n '134|167' routines/_skills/write-blog-draft/SKILL.md 2>/dev/null || echo "Not in SKILL.md"
# 3. Check for burn-state.json update logic in scripts
rg -n 'burn-state' routines/blogger/scripts/ || echo "No script references found"

Repository: Knowcap-V2/knowcap-website

Length of output: 1855


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant files and search for the SEO handoff and URL rules.
git ls-files | rg '(^|/)(AGENT\.md|PLAYBOOK\.md|SKILL\.md|burn-state\.json|publish-blog-draft\.yml|publish-draft\.mjs|seo|blogger)'
printf '\n-- TASK 9 / request-indexing matches --\n'
rg -n 'TASK 9|request-indexing|request indexing|seo routine' routines .github scripts docs || true
printf '\n-- live URL / blog slug matches --\n'
rg -n 'https://knowcap\.ai/blog/|/blog/<slug>|hreflang|lang/dir|burn-state\.json|total_posts\+\+|posts_this_week\+\+' routines .github scripts docs || true

Repository: Knowcap-V2/knowcap-website

Length of output: 4482


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n-- audit-seo skill --\n'
ast-grep outline routines/_skills/audit-seo/SKILL.md --view expanded || true
printf '\n-- request-indexing matches in repo --\n'
rg -n 'request-indexing|request indexing|indexing|TASK 9' routines/_skills routines/blogger .github scripts docs || true
printf '\n-- blogger routine/AGENT relevant lines --\n'
sed -n '56,70p' routines/blogger/AGENT.md
printf '\n-- audit-seo file excerpt --\n'
sed -n '1,220p' routines/_skills/audit-seo/SKILL.md

Repository: Knowcap-V2/knowcap-website

Length of output: 3200


Remove the nonexistent SEO handoff
routines/blogger/AGENT.md:66-67 points to seo TASK 9 (request-indexing), but no such task exists in the repo. Point this to the real indexing step or drop the handoff.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~65-~65: The official name of this software platform is spelled with a capital “H”.
Context: ...s live automatically on PR merge via .github/workflows/publish-blog-draft.yml + `sc...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@routines/blogger/AGENT.md` around lines 59 - 67, The handoff at the end of
the blogger workflow references a nonexistent SEO task, so update the guidance
in AGENT.md to point to the real indexing step used in this repo or remove the
handoff entirely. Locate the final “After the PR opens” instruction in the
blogger routine and replace the `seo` TASK 9 / `request-indexing` reference with
the correct documented workflow so the `burn-state.json` and blog publishing
steps remain accurate.


## Hard rules

Expand All @@ -45,7 +73,9 @@ hit a quota.
- **Quality > quota** — no fresh opportunity clears the gates → skip the day. A missed burn post beats a thin one (Google scaled-content-abuse demotes the whole domain).
- **Honesty** — case-study only on genuinely human-confirmed claims. SEO-grounded thesis must add real value, not keyword-stuff. Never claim a verification panel for unconfirmed claims.
- **No duplicates** — dedup against last 20 shipped slugs AND in-flight drafts. The queue already flags `covered`.
- **English posts**, MENA-targeted (mine EN + AR demand, publish EN).
- **SERP-steal is a gate (Law 1)** — never write from imagination. Inherit the SEO brief, or pull the live SERP yourself, first. No brief + no SERP = no draft.
- **Intent-match or skip (Law 2)** — navigational/tool keyword → skip; commercial → money page, not a blog; only informational keywords become posts.
- **Bilingual, demand-driven (Law 4)** — write in the language of the keyword's demand. Arabic-demand → native Arabic post (`lang: ar`, `dir: rtl`); English-demand → English. EN-only is retired; the largest winnable demand is Arabic. Pair EN/AR with hreflang.
- **DataForSEO down → stop**, don't invent data. One-line error, never silent.

## Files
Expand Down
Loading