diff --git a/.gitignore b/.gitignore index aa546cb..cfdb4bc 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,7 @@ out .logs *.tsbuildinfo .gstack/ + +# Routine run logs (per-routine state, not tracked) +routines/**/runs/ +routines/**/baselines/ diff --git a/CLAUDE.md b/CLAUDE.md index 83a55b5..227f3c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,7 @@ Brand + marketing + website for Knowcap (knowcap.ai). Renamed from `knowcap-land | Content drafts | `docs/content-pipeline/` (strategy/, drafts/, ideas/, video/) | | Strategy | `docs/strategy/` (gtm-strategy.md, june-2026-gtm-game.md) | | Design system | `docs/DESIGN.md` + `docs/DESIGN-E.md` | +| Routines (autonomous work) | `routines/` (weekly-blog, nightly-seo-audit, persona-refresh, content-curator) — see `routines/README.md` for the Routine→Skill→Run→Inbox pattern | ## Design System Always read `docs/DESIGN.md` (or `docs/DESIGN-E.md` for the /e variant) before making any visual or UI decisions. Font choices, colors, spacing, and aesthetic direction are defined there. diff --git a/routines/README.md b/routines/README.md new file mode 100644 index 0000000..e594e6f --- /dev/null +++ b/routines/README.md @@ -0,0 +1,61 @@ +# Routines — knowcap-marketing + +Scheduled / triggered work that operates on this repo. Each routine fires on a trigger (cron, file event, manual), runs one or more skills, and produces output (usually a PR for human review). + +## Pattern + +``` +Routine (WHEN) → fires Skill(s) (WHAT) → produces Run (TRACKED) → drafts to Inbox (HUMAN CONFIRMS) +``` + +Borrowed from the [Knowcap product's own architecture](https://github.com/Knowcap-V2/knowcap/blob/main/docs/decisions/2026-05-29-agent-skills-routines-architecture.md). Inbox here = a PR you merge to confirm, close to reject. + +## Layout + +``` +routines/ +├── README.md ← this file +├── _skills/ ← reusable skill definitions (the WHAT) +│ ├── write-blog-draft/SKILL.md +│ ├── audit-seo/SKILL.md +│ └── refresh-persona/SKILL.md +└── / ← one folder per routine + ├── ROUTINE.md ← human-readable: what it does, what it reads, what it produces + ├── triggers.yml ← cron / event triggers + ├── inputs.md ← exact paths read + ├── outputs.md ← exact paths written / PRs opened + └── runs/ ← run log (gitignored — see .gitignore) +``` + +## Routines in this repo + +| Routine | Trigger | Skill | Output | Status | +|---|---|---|---|---| +| [`weekly-blog/`](./weekly-blog/) | Cron: Mon 06:00 UTC | `write-blog-draft` | PR to `docs/content-pipeline/drafts/` | stub | +| [`nightly-seo-audit/`](./nightly-seo-audit/) | Cron: daily 03:00 UTC | `audit-seo` | PR to `docs/research/audits/` if regression | stub | +| [`persona-refresh/`](./persona-refresh/) | Cron: monthly | `refresh-persona` | PR to `docs/brand/personas/` if signal shifts | stub | +| [`content-curator/`](./content-curator/) | Event: new file in `docs/research/` | (chain: read research → suggest blog topics) | Comment on the PR that added the research | stub | + +## Status + +All routines are **stubs**. The definitions (`ROUTINE.md`, `SKILL.md`) describe intended behavior but no runtime executes them autonomously yet. To run a routine manually today: open Claude Code in this repo, point it at the routine folder, ask it to follow the instructions. + +## Phasing + +| Phase | What | +|---|---| +| **P0 (now)** | Structure + stub definitions | +| **P1 (this week)** | Flesh out 1–2 `SKILL.md` files with real instructions, run manually via Claude Code | +| **P2 (next 2 weeks)** | Wire one routine to Hermes or GitHub Actions cron so it runs autonomously | +| **P3 (later)** | Multi-routine orchestration, eval suite, run-log dashboard | + +## Why "routines" not "agents" + +A routine is **trigger + skill chain + output contract**. It's procedural — not an autonomous LLM loop. + +The word "agent" is overloaded: +- **Claude Code subagents** — short-lived helpers Claude Code spawns (lives in `.claude/agents/`) +- **Shipped product agents** — agents Knowcap customers run (lives in `Knowcap-V2/knowcap-agents`) +- **Autonomous LLM agents** — multi-step planners with tool use and decision loops + +What lives here is none of those. It's scheduled work that runs a skill — a routine. Same name the Knowcap product uses for the same pattern. Same name Hassan uses for his 9 cloud routines in `av-claude-workspace/`. diff --git a/routines/_skills/audit-seo/SKILL.md b/routines/_skills/audit-seo/SKILL.md new file mode 100644 index 0000000..cb02380 --- /dev/null +++ b/routines/_skills/audit-seo/SKILL.md @@ -0,0 +1,33 @@ +# audit-seo + +Audit knowcap.ai for SEO health regressions. + +## Status: stub + +## Inputs + +- Live crawl seed: routes from `app/sitemap.ts` +- Yesterday's baseline (stored in `runs/baselines/` outside the repo, fetched at run time) +- Current scoring rubric from the most recent `docs/research/audits/SEO-AUDIT-*.md` + +## Checks (TBD — fill in P1) + +- Title presence + uniqueness per route +- Meta description presence + length +- Canonical tag presence + correctness +- JSON-LD schema presence (Organization, WebSite, SoftwareApplication minimum) +- robots.txt content (must NOT block GPTBot, ClaudeBot, etc.) +- Sitemap completeness — every route in `app/app/**` must have a sitemap entry +- Core Web Vitals — LCP, FID, CLS via web-vitals or Lighthouse CI +- Per-page H1 presence + uniqueness + +## Outputs + +- New `docs/research/audits/SEO-AUDIT-.md` if regression detected +- Default: silent success, no PR + +## TBD + +- Crawler implementation (puppeteer? curl + cheerio? Playwright?) +- Baseline storage location +- Severity rubric — what's a regression vs. an intentional change? diff --git a/routines/_skills/refresh-persona/SKILL.md b/routines/_skills/refresh-persona/SKILL.md new file mode 100644 index 0000000..8b979ae --- /dev/null +++ b/routines/_skills/refresh-persona/SKILL.md @@ -0,0 +1,33 @@ +# refresh-persona + +Re-validate MENA SME persona segmentation against fresh signal. + +## Status: stub + +## Inputs + +- `docs/brand/personas/PRODUCT-PERSONAS.md` (current canonical) +- `docs/brand/personas/PRODUCT-PERSONAS-UPDATE-*.md` (last validation pass) +- Google Trends API — interest by sub-region (MENA-focused), 5-year window +- LinkedIn free company search — SMB counts per country +- YouTube Data API — comment mining on MENA-business channels (pain language) +- (optional) Apollo.io if not rate-limited + +## Checks (TBD — fill in P1) + +- For each persona segment in PRODUCT-PERSONAS.md: + - Has total search interest shifted > 20% MoM or > 50% YoY? + - Has the LinkedIn company count changed > 10% (account for org churn)? + - Are there new pain-language clusters in YouTube comments? +- If ≥1 segment has shifted ≥1 tier (painkiller ↔ vitamin, or reachability), trigger a new UPDATE doc. + +## Outputs + +- New `docs/brand/personas/PRODUCT-PERSONAS-UPDATE-.md` with: the new signal, the proposed re-rating, AND a "DO NOT change canonical" footer reminding the human to make that call. +- Updated CSVs in `docs/research/data/` (per existing patterns: `trends-mena-*.csv`, `MENA-LINKEDIN-COUNTS.csv`, `youtube-mena-pain-comments.csv`) + +## TBD + +- Google Trends MCP wrapper (current pytrends has reliability issues) +- LinkedIn scraping rate-limit handling +- Whether to call Apollo (paid) or skip diff --git a/routines/_skills/write-blog-draft/SKILL.md b/routines/_skills/write-blog-draft/SKILL.md new file mode 100644 index 0000000..e5579fe --- /dev/null +++ b/routines/_skills/write-blog-draft/SKILL.md @@ -0,0 +1,43 @@ +# write-blog-draft + +Generate a Knowcap blog post draft from inputs. + +## Status: stub +Real prompt + instruction set TBD. Today: run this manually via Claude Code in this repo, pointing at a specific persona + audit. + +## Mode + +- **`full-draft`** (default): emit a complete 1,300–1,600 word draft with front-matter, ready for editorial review. +- **`topic-ideation`**: emit 2–3 proposed angles instead of a full draft, used by `content-curator` routine. + +## Inputs + +| Required | Description | +|---|---| +| `target_persona` | One of the personas from `docs/brand/personas/PRODUCT-PERSONAS.md` | +| `target_keyword` | From `docs/research/audits/` opportunity list, OR user-supplied | +| `mode` | `full-draft` or `topic-ideation` | +| `recent_shipped` | List of last 20 blog slugs from `app/content/blog/` to avoid repeating | + +## Voice + style (locked) + +- Founder-voice OR industry-expert voice (no PM-speak, no consultant-speak) +- Front-matter MUST include: `title`, `slug`, `persona`, `target_keyword`, `geo_score`, `est_word_count`, `draft_date` +- Cite docs/brand/VISION.md when the post hinges on the trust-layer thesis +- Anti-pattern: never use "leverage", "synergy", "ecosystem", "stakeholder", "stakeholders" +- GEO structure: 134–167 word self-contained passages, FAQ section, schema-friendly headings + +## ICP gate + +If the persona isn't one of: Odoo partners, MENA SMBs, audit firms, regulated verticals, agencies → refuse and emit "out-of-ICP" message. DO NOT write the draft. + +## Outputs + +`full-draft` mode: a single .md file with front-matter + body. +`topic-ideation` mode: a markdown comment with N proposed angles. + +## TBD + +- Exact prompt template — TBD when P1 hits +- MCP connections — Google Trends MCP for keyword sizing? Persona MCP that returns persona JSON? +- Cost cap — what's the budget per draft run? diff --git a/routines/content-curator/ROUTINE.md b/routines/content-curator/ROUTINE.md new file mode 100644 index 0000000..b0d6770 --- /dev/null +++ b/routines/content-curator/ROUTINE.md @@ -0,0 +1,42 @@ +# content-curator + +Event-triggered: when a new file lands in `docs/research/` (audit, competitor breakdown, persona update), suggest 2–3 blog topic angles that would translate the research into content for shipped channels. + +## Status: stub +Runtime not wired yet. + +## Trigger + +See [`triggers.yml`](./triggers.yml) — event: any push to main that adds files under `docs/research/`. + +## Skill + +Calls [`_skills/write-blog-draft/SKILL.md`](../_skills/write-blog-draft/SKILL.md) — but in "topic-ideation" mode, not full-draft mode. + +## Inputs + +- The new file(s) that triggered the event (full content) +- `docs/brand/personas/` (which personas benefit from this research?) +- `app/content/blog/` (have we covered this angle already?) +- `docs/content-pipeline/drafts/` (don't propose what's already in the queue) + +## Outputs + +- A COMMENT on the PR that added the research (NOT a separate PR — keep the discussion in context) +- Comment body: 2–3 proposed blog angles, each with a target persona, target keyword, and rough outline +- Optional: a `[blog-draft] ...` PR for the strongest of the 2–3 if signal-confidence is high (configured per-trigger) + +## Human confirms + +- React to the comment with thumbs-up = approve a draft (will trigger `weekly-blog` to prioritize) +- Reply with "skip" = ignore + +## Constraints + +- Never propose a topic that overlaps > 60% with a shipped blog or pending draft +- Never propose more than 3 topics per trigger event — quality over quantity +- If the research file is itself a blog draft (someone forgot to put it in `docs/content-pipeline/drafts/`), open a "move" PR instead of curating + +## Run log + +`runs/` (gitignored). diff --git a/routines/content-curator/triggers.yml b/routines/content-curator/triggers.yml new file mode 100644 index 0000000..856984c --- /dev/null +++ b/routines/content-curator/triggers.yml @@ -0,0 +1,6 @@ +event: push-to-main +filter: + path: docs/research/** + action: added +runtime: TBD +status: stub diff --git a/routines/nightly-seo-audit/ROUTINE.md b/routines/nightly-seo-audit/ROUTINE.md new file mode 100644 index 0000000..7f8f383 --- /dev/null +++ b/routines/nightly-seo-audit/ROUTINE.md @@ -0,0 +1,44 @@ +# nightly-seo-audit + +Audits knowcap.ai's live SEO health nightly, opens a PR with a fresh `SEO-AUDIT-.md` report only if regression > 5% from yesterday's baseline. + +## Status: stub +Runtime not wired yet. + +## Trigger + +See [`triggers.yml`](./triggers.yml) — cron `0 3 * * *` (daily 03:00 UTC = 05:00 Cairo). + +## Skill + +Calls [`_skills/audit-seo/SKILL.md`](../_skills/audit-seo/SKILL.md). + +## Inputs + +- Live crawl of all routes in `app/sitemap.ts` +- Yesterday's audit baseline in `runs/baselines/` (gitignored — lives outside repo, fetched at run time) +- `docs/research/audits/SEO-AUDIT-2026-06-01.md` — current scoring rubric (38/100 baseline) + +## Outputs + +- New `docs/research/audits/SEO-AUDIT-.md` IF any of: + - Overall score drops > 5 points vs yesterday + - Any page loses canonical, title, or meta-description (HIGH severity) + - Any new page is missing schema/sitemap entry +- No PR opened if audit is clean — silent success +- If a PR is opened, title: `[seo-audit-regression] — score dropped X→Y` + +## Human confirms + +- Merge PR = acknowledge + the audit file becomes part of the docs trail +- Close PR = ignore (audit was a false positive) + +## Constraints + +- Never block on transient 5xx — retry 3 times before flagging +- Distinguish "regression" from "intentional change" — read the last 3 PR titles to main; if any touched `app/app/*.tsx` it's likely intentional, downgrade severity +- Keep the historical baseline rolling 30 days + +## Run log + +`runs/` (gitignored). diff --git a/routines/nightly-seo-audit/triggers.yml b/routines/nightly-seo-audit/triggers.yml new file mode 100644 index 0000000..9975591 --- /dev/null +++ b/routines/nightly-seo-audit/triggers.yml @@ -0,0 +1,5 @@ +cron: "0 3 * * *" +timezone: UTC +description: Daily 03:00 UTC (05:00 Cairo) +runtime: TBD +status: stub diff --git a/routines/persona-refresh/ROUTINE.md b/routines/persona-refresh/ROUTINE.md new file mode 100644 index 0000000..1776f26 --- /dev/null +++ b/routines/persona-refresh/ROUTINE.md @@ -0,0 +1,44 @@ +# persona-refresh + +Re-validates the MENA SME persona segmentation against fresh signal (Google Trends, LinkedIn counts, YouTube comments). Opens a PR to `docs/brand/personas/` if the signal has shifted enough to change a persona's pain/reachability/WTP score. + +## Status: stub +Runtime not wired yet. + +## Trigger + +See [`triggers.yml`](./triggers.yml) — cron `0 6 1 * *` (first of each month 06:00 UTC). + +## Skill + +Calls [`_skills/refresh-persona/SKILL.md`](../_skills/refresh-persona/SKILL.md). + +## Inputs + +- `docs/brand/personas/PRODUCT-PERSONAS.md` — current canonical segmentation +- `docs/brand/personas/PRODUCT-PERSONAS-UPDATE-*.md` — last update with signal validation +- Google Trends API (5-year window, MENA-only) +- LinkedIn free company search counts (UAE / KSA / Egypt SMB filter) +- YouTube Data API — comment mining on MENA-business channels for pain language +- Apollo.io (if rate-limit allows) — refresh per-country SMB counts + +## Outputs + +- New `docs/brand/personas/PRODUCT-PERSONAS-UPDATE-.md` if any segment's score shifts by ≥1 tier (painkiller ↔ vitamin, or reachability shift) +- One PR titled `[persona-refresh] segments shifted` +- Updates rolling baseline CSVs in `docs/research/data/` only via the PR + +## Human confirms + +- Merge PR = canonical segmentation updated +- Close PR = signal didn't justify a strategy change + +## Constraints + +- Never delete `PRODUCT-PERSONAS.md` — it's the locked canonical Phase-1. Only ADD update files. +- Never automate the verdict — write the data + the proposed re-rating, but the actual "VERDICT: pivot beachhead" line stays manual (Hassan-only). +- Flag rising-search shifts > 100% YoY explicitly in the PR body — those usually indicate a new ICP candidate, not a refinement. + +## Run log + +`runs/` (gitignored). diff --git a/routines/persona-refresh/triggers.yml b/routines/persona-refresh/triggers.yml new file mode 100644 index 0000000..9e2c20f --- /dev/null +++ b/routines/persona-refresh/triggers.yml @@ -0,0 +1,5 @@ +cron: "0 6 1 * *" +timezone: UTC +description: First of each month, 06:00 UTC +runtime: TBD +status: stub diff --git a/routines/weekly-blog/ROUTINE.md b/routines/weekly-blog/ROUTINE.md new file mode 100644 index 0000000..1f852b0 --- /dev/null +++ b/routines/weekly-blog/ROUTINE.md @@ -0,0 +1,46 @@ +# weekly-blog + +Generates one blog draft per week, opens a PR to `docs/content-pipeline/drafts/` for review. + +## Status: stub +Runtime not wired yet. Run manually via Claude Code today; cron-trigger via Hermes/GitHub Actions later. + +## Trigger + +See [`triggers.yml`](./triggers.yml) — cron `0 6 * * MON` (every Monday 06:00 UTC = 09:00 Cairo). + +## Skill + +Calls [`_skills/write-blog-draft/SKILL.md`](../_skills/write-blog-draft/SKILL.md). + +## Inputs + +- `docs/brand/personas/PRODUCT-PERSONAS.md` — pick a target persona based on rotation or audit-driven priority +- `docs/brand/VISION.md` — voice, anti-positioning, trust-layer thesis +- `docs/brand/POSITIONING.md` — three-sentence positioning, anti-positioning +- `docs/research/audits/` — most recent SEO + GEO audit findings (target keywords + gaps) +- `app/content/blog/` — last 20 shipped blogs (avoid topic repetition) +- `docs/research/competitors/` — competitor positioning (for differentiation angle) + +## Outputs + +- One markdown file at `docs/content-pipeline/drafts/.md` +- One PR titled `[blog-draft] ` against `main` +- Front-matter: `title`, `slug`, `persona`, `target_keyword`, `geo_score`, `est_word_count`, `draft_date` + +## Human confirms + +- Merge PR = approve + advance to review queue (next routine: `content-curator` or manual edit pass) +- Close PR = reject (consider tightening the skill prompt if rejected for prompt-quality reasons) + +## Constraints + +- ICP-aligned only — Odoo partners, MENA SMBs, audit firms (regulated), agencies. NEVER write for "any SME." +- 1,300–1,600 words +- GEO-optimized: 134–167 word self-contained passages, FAQ section, schema-friendly structure +- Front-matter must include `geo_score` per `docs/research/audits/GEO-AUDIT-*.md` scoring rubric +- Never write a blog whose title overlaps with a shipped blog in `app/content/blog/` + +## Run log + +`runs/` (gitignored). Each run produces a timestamped folder with the draft, the prompt, the chosen persona, and metadata. diff --git a/routines/weekly-blog/triggers.yml b/routines/weekly-blog/triggers.yml new file mode 100644 index 0000000..5bfb94b --- /dev/null +++ b/routines/weekly-blog/triggers.yml @@ -0,0 +1,5 @@ +cron: "0 6 * * MON" +timezone: UTC +description: Every Monday 06:00 UTC (09:00 Cairo) +runtime: TBD # TBD = Hermes | github-actions | claude-code-manual +status: stub