From 7d221f560120401e9673366ad8d0cde9760a967b Mon Sep 17 00:00:00 2001 From: Hassan Arslan Date: Thu, 18 Jun 2026 12:20:34 +0300 Subject: [PATCH] docs(blogger): sync routine + skill to auto-publish model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AGENT.md: fix mode-selection to list all 3 modes (case-study → comparison → thesis); fix hub paths; add live URL output + auto-publish note; add publish-draft.mjs to Files section - ROUTINE.md: fix all stale hub paths (knowledge/ → company/docs/); fix comparison mode path to competitors-*.md flat files; update Human confirms — merge PR now auto-publishes via GitHub Action, no manual move; update PR body to include live URL; remove dead Google Trends row from tool table - SKILL.md: fix hub paths in Inputs section (knowledge/ → company/docs/); fix comparison mode path; add description/tags/author/lang/dir to frontmatter shape with publish-script note - publish-draft.mjs: warn on missing description instead of silently falling back Co-Authored-By: Claude Sonnet 4.6 --- routines/_skills/write-blog-draft/SKILL.md | 19 ++++++---- routines/blogger/AGENT.md | 19 ++++++---- routines/blogger/ROUTINE.md | 41 +++++++++++----------- scripts/publish-draft.mjs | 5 ++- 4 files changed, 50 insertions(+), 34 deletions(-) diff --git a/routines/_skills/write-blog-draft/SKILL.md b/routines/_skills/write-blog-draft/SKILL.md index e5f93da..587089d 100644 --- a/routines/_skills/write-blog-draft/SKILL.md +++ b/routines/_skills/write-blog-draft/SKILL.md @@ -12,7 +12,7 @@ Routine picks mode at runtime based on what's available — the routine doesn't |---|---|---| | **`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 `docs/research/competitors//positioning.md` is fresher than 30 days AND not already covered in last 5 shipped blogs. | +| **`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. | Each mode has its own prompt template below. @@ -25,9 +25,9 @@ target_keyword: "" target_keyword_5y_mena_interest: <0-100 OR null if Trends unavailable> # Always required (brand DNA lives in the claude-knowcap hub — sibling repo, NOT this repo's docs/) -vision_md: "" # from ../claude-knowcap/knowledge/strategies/VISION.md -positioning_md: "" # from ../claude-knowcap/knowledge/strategies/POSITIONING.md -persona_section_md: "" # the persona's section from ../claude-knowcap/knowledge/people/PRODUCT-PERSONAS.md +vision_md: "" # from ../claude-knowcap/company/docs/strategy/vision.md +positioning_md: "" # from ../claude-knowcap/company/docs/strategy/POSITIONING.md +persona_section_md: "" # the persona's section from ../claude-knowcap/company/docs/research/product-personas.md recent_shipped_slugs: [...] # last 20 slugs from app/content/blog/ recent_drafts_in_pipeline: [...] # to avoid double-drafting @@ -43,14 +43,14 @@ knowcap_sources: facts: [...] # Required only when mode = comparison -competitor_positioning_md: "" # from ../claude-knowcap/knowledge/topics/research/competitors//positioning.md +competitor_positioning_md: "" # from ../claude-knowcap/company/docs/research/competitors--positioning.md # Optional enhancement (any mode) available_screenshots: - slug: "verification-inbox" alt: "" caption: "" - file: "../claude-knowcap/knowledge/product/screenshots/verification-inbox/full.png" + file: "../claude-knowcap/company/docs/product/screenshots/verification-inbox/full.png" features: [verification, inbox] personas: [odoo-partners, mena-audit-firms] ``` @@ -80,12 +80,19 @@ 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_date: +description: "" +tags: [<5-8 kebab-case tags derived from persona + keyword + topic>] +author: "Hassan Arslan" +lang: "en" +dir: "ltr" source_knowcap_ids: [] embedded_screenshots: [] status: draft --- ``` +`description`, `tags`, `author`, `lang`, `dir` are consumed by `scripts/publish-draft.mjs` on PR merge to populate the live blog post's frontmatter. Always include them — the publish script errors if `description` is missing. + ## Mode `thesis` — the prompt ``` diff --git a/routines/blogger/AGENT.md b/routines/blogger/AGENT.md index 7c4141f..e356d3c 100644 --- a/routines/blogger/AGENT.md +++ b/routines/blogger/AGENT.md @@ -22,15 +22,20 @@ 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 opportunity as `target_keyword`. Assemble inputs - (persona section, VISION, POSITIONING from the hub; shipped slugs; queue row's volume+comp). -5. **Mode** — Knowcap MCP Demo-org → persona project → source with ≥3 human-confirmed memories? - → **case-study** (cite `source_knowcap_ids`, generate the verification panel via - `lib/gen-verification-panel.mjs`). Else → **SEO-grounded thesis** (answer the keyword's real - SERP / People-Also-Ask demand with a unique Knowcap angle). +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/.md`, open `[blog-draft]` PR to main. + Always report live URL: `https://knowcap.ai/blog/` — 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++). ## Hard rules @@ -51,3 +56,5 @@ hit a quota. - `state.json` — persona rotation cursor (gitignored) - `lib/gen-verification-panel.mjs` — data-driven Knowcap UI SVG (case-study posts) - `runs//` — per-run digest + draft + report (gitignored) +- `../../.github/workflows/publish-blog-draft.yml` — auto-publish on `[blog-draft]` PR merge +- `../../scripts/publish-draft.mjs` — frontmatter transform: draft fields → blog fields, moves draft to `app/content/blog/` diff --git a/routines/blogger/ROUTINE.md b/routines/blogger/ROUTINE.md index 919322e..cbdcbb0 100644 --- a/routines/blogger/ROUTINE.md +++ b/routines/blogger/ROUTINE.md @@ -39,27 +39,26 @@ After each shipped draft PR: `total_posts++`, `posts_this_week++`. Reset `posts_ - For each candidate source, pull confirmed memories (>= 3 required) - If 1+ candidate qualifies → mode = case-study, pick the most recent 3. If no case-study candidate, try `comparison` mode: - - Check ../claude-knowcap/knowledge/topics/research/competitors//positioning.md mtime - - If freshest competitor doc is < 30 days old AND not covered in last 5 shipped → mode = comparison + - List ../claude-knowcap/company/docs/research/competitors-*.md, check mtime of each + - If freshest competitor doc is < 30 days old AND topic not covered in last 5 shipped → mode = comparison 4. Default to `thesis` mode ``` -The first dry-run picked `thesis` mode (Demo org empty, no fresh competitor doc < 30d). That's the expected path until Demo org gets seeded. +The first few runs pick `thesis` mode (Demo org empty, no fresh competitor doc < 30d). Expected until Demo org gets seeded. ## Knowledge base (hub) — paths -Brand DNA, personas, research, and screenshots migrated to the `claude-knowcap` hub (PR #40, 2026-06-11). They are NO LONGER under this repo's `docs/`. Hub paths below are written relative to this repo root (`knowcap-website/`) — the hub is a sibling repo under `knowcap/`: +Brand DNA, personas, research, and screenshots live in the `claude-knowcap` hub (PR #40 2026-06-11, restructured into `company/docs/` 2026-06-15). NOT under this repo's `docs/`. Hub paths below are relative to this repo root (`knowcap-website/`) — the hub is a sibling repo under `knowcap/`: | Input | Hub path (relative to repo root) | |---|---| -| Personas | `../claude-knowcap/knowledge/people/PRODUCT-PERSONAS.md` | -| Vision | `../claude-knowcap/knowledge/strategies/VISION.md` | -| Positioning | `../claude-knowcap/knowledge/strategies/POSITIONING.md` | -| SEO audits | `../claude-knowcap/knowledge/topics/research/audits/SEO-AUDIT-*.md` | -| Competitors | `../claude-knowcap/knowledge/topics/research/competitors//positioning.md` | -| Screenshots index | `../claude-knowcap/knowledge/product/screenshots/_index.json` | +| Personas | `../claude-knowcap/company/docs/research/product-personas.md` | +| Vision | `../claude-knowcap/company/docs/strategy/vision.md` | +| Positioning | `../claude-knowcap/company/docs/strategy/POSITIONING.md` | +| Competitors | `../claude-knowcap/company/docs/research/competitors-*.md` (one file per competitor, e.g. `competitors-read-ai-positioning.md`) | +| Screenshots index | `../claude-knowcap/company/docs/product/screenshots/_index.json` | -Outputs stay in THIS repo: drafts → `docs/content-pipeline/drafts/`, shipped → `app/content/blog/`. +Outputs stay in THIS repo: drafts → `docs/content-pipeline/drafts/`, shipped → `app/content/blog/` (auto-moved on PR merge). ## Persona rotation state @@ -74,18 +73,18 @@ Each run reads `cursor`, picks `personas[cursor]`, then advances `cursor = (curs ## Inputs (in execution order) 1. **Read persona rotation state** from `routines/blogger/state.json` (gitignored; if absent, default to index 0 = `odoo-partners` and create it) -2. **Read `../claude-knowcap/knowledge/people/PRODUCT-PERSONAS.md`** → pick the persona's section (study segment names map to slugs: "Odoo implementation partners" → `odoo-partners`, "Audit / accounting firms" → `mena-audit-firms`, etc.) -3. **Read `../claude-knowcap/knowledge/strategies/VISION.md`** → voice + anti-positioning -4. **Read `../claude-knowcap/knowledge/strategies/POSITIONING.md`** → three sentences + anti-positioning -5. **Run `node routines/blogger/scripts/seo-pull.mjs`** (live SEO engine, replaces the old static SEO-audit scan AND the dead Google Trends step). Pulls DataForSEO Google-Ads keyword demand for MENA (KSA + Egypt + UAE) in EN + AR, expands persona seeds into real related keywords with **search volume + competition**, filters to Knowcap ICP intent, ranks by `volume × competition-weight`, dedups against shipped posts, and writes `routines/blogger/opportunity-queue.json` + a digest. **`target_keyword` = the persona's top fresh (uncovered) opportunity** from the queue. Auth: DataForSEO creds in `~/.claude/secrets/blogger.md`. -6. **(Google Trends removed.)** DataForSEO volume + competition from step 5 is the demand signal — Trends was near-zero for this B2B ICP. Record the chosen keyword's `search_volume` + `competition` in frontmatter. +2. **Read `../claude-knowcap/company/docs/research/product-personas.md`** → pick the persona's section (study segment names map to slugs: "Odoo implementation partners" → `odoo-partners`, "Audit / accounting firms" → `mena-audit-firms`, etc.) +3. **Read `../claude-knowcap/company/docs/strategy/vision.md`** → voice + anti-positioning +4. **Read `../claude-knowcap/company/docs/strategy/POSITIONING.md`** → three sentences + anti-positioning +5. **Run `node routines/blogger/scripts/seo-pull.mjs`** (live SEO engine). Pulls DataForSEO Google-Ads keyword demand for MENA (KSA + Egypt + UAE) in EN + AR, expands persona seeds into real related keywords with **search volume + competition**, filters to Knowcap ICP intent, ranks by `volume × competition-weight`, dedups against shipped posts, and writes `routines/blogger/opportunity-queue.json` + a digest. **`target_keyword` = the persona's top fresh (uncovered) EN opportunity** from the queue. Auth: DataForSEO creds in `~/.claude/secrets/blogger.md`. +6. DataForSEO volume + competition from step 5 is the demand signal. Record the chosen keyword's `search_volume` + `competition` in frontmatter. 7. **Try `case-study` mode:** - Query Knowcap MCP `mcp__knowcap__list_sources` → Demo org, persona project - For each source, `mcp__knowcap__list_memories` filtered to source via `metadata.source_id` - Pick source with ≥3 confirmed memories tagged with target_keyword OR persona - If found: assemble `knowcap_sources` input array 8. **If no case-study candidate, try `comparison` mode:** - - List `../claude-knowcap/knowledge/topics/research/competitors/*/positioning.md` mtime + - List `../claude-knowcap/company/docs/research/competitors-*.md`, check mtime of each - If freshest is < 30 days AND topic not in recent 5 shipped → mode = comparison 9. **Default to `thesis` mode** if neither condition met 10. **Pick matching screenshots** from `../claude-knowcap/knowledge/product/screenshots/_index.json`: @@ -102,7 +101,7 @@ Each run reads `cursor`, picks `personas[cursor]`, then advances `cursor = (curs | `mcp__knowcap__list_sources` | List Demo org sources for persona | `KNOWCAP_API_KEY` in `~/.claude.json` mcpServers.knowcap.env | read | | `mcp__knowcap__list_memories` | Pull verified claims from picked sources | same | read | | `mcp__knowcap__get_source` | Get source title + duration + metadata | same | read | -| Google Trends via `pytrends` | Keyword sizing (MENA-only, 5-year) | none (free) | read | +| DataForSEO Google Ads | Keyword volume + competition (replaces Trends) | creds in `~/.claude/secrets/blogger.md` | read | | Filesystem | Read docs/, write `docs/content-pipeline/drafts/.md` | OS perms | read + write | | `gh` CLI (or GitHub MCP when re-loaded) | Open PR | `gh auth` | write | @@ -112,15 +111,15 @@ A single markdown file at `docs/content-pipeline/drafts/.md` with frontmat PR body includes: - Mode picked + why -- Persona + keyword + Google Trends signal +- Persona + keyword + volume/competition - If case-study: source_knowcap_ids cited - If screenshots embedded: which slugs from `_index.json` - Validation gate pass/fail summary -- Link to `runs//REPORT.md` +- Live URL: `https://knowcap.ai/blog/` ## Human confirms -- **Merge PR** = approve; next routine (`content-curator` or manual edit pass) handles polish before publish-to-blog (move from `docs/content-pipeline/drafts/` to `app/content/blog/`) +- **Merge PR** = approve → `.github/workflows/publish-blog-draft.yml` fires automatically, runs `scripts/publish-draft.mjs` to transform draft frontmatter and move the file to `app/content/blog/.md`, then commits to main. Vercel redeploys. Post is live within ~2 min. No manual move needed. - **Close PR** = reject; routine logs the rejection reason to `runs//rejected.txt` for future prompt-tuning ## Constraints diff --git a/scripts/publish-draft.mjs b/scripts/publish-draft.mjs index 278a2f6..4b19dc9 100644 --- a/scripts/publish-draft.mjs +++ b/scripts/publish-draft.mjs @@ -102,7 +102,10 @@ function buildPublishedFrontmatter(meta, body) { pub.date = meta.draft_date || new Date().toISOString().slice(0, 10) pub.author = meta.author || 'Hassan Arslan' - // Description — use meta.description if set, else auto-extract + // Description — required field; auto-extract only as last-resort fallback + if (!meta.description) { + console.warn(`Warning: draft missing description field — auto-extracting from body. Add description to frontmatter for better SEO.`) + } pub.description = meta.description || extractDescription(body) // Tags