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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ out
.logs
*.tsbuildinfo
.gstack/

# Routine run logs (per-routine state, not tracked)
routines/**/runs/
routines/**/baselines/
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
61 changes: 61 additions & 0 deletions routines/README.md
Original file line number Diff line number Diff line change
@@ -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
└── <routine-slug>/ ← 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/`.
33 changes: 33 additions & 0 deletions routines/_skills/audit-seo/SKILL.md
Original file line number Diff line number Diff line change
@@ -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-<YYYY-MM-DD>.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?
33 changes: 33 additions & 0 deletions routines/_skills/refresh-persona/SKILL.md
Original file line number Diff line number Diff line change
@@ -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-<YYYY-MM-DD>.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
43 changes: 43 additions & 0 deletions routines/_skills/write-blog-draft/SKILL.md
Original file line number Diff line number Diff line change
@@ -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?
42 changes: 42 additions & 0 deletions routines/content-curator/ROUTINE.md
Original file line number Diff line number Diff line change
@@ -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).
6 changes: 6 additions & 0 deletions routines/content-curator/triggers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
event: push-to-main
filter:
path: docs/research/**
action: added
runtime: TBD
status: stub
44 changes: 44 additions & 0 deletions routines/nightly-seo-audit/ROUTINE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# nightly-seo-audit

Audits knowcap.ai's live SEO health nightly, opens a PR with a fresh `SEO-AUDIT-<date>.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-<YYYY-MM-DD>.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] <YYYY-MM-DD> — 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).
5 changes: 5 additions & 0 deletions routines/nightly-seo-audit/triggers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cron: "0 3 * * *"
timezone: UTC
description: Daily 03:00 UTC (05:00 Cairo)
runtime: TBD
status: stub
44 changes: 44 additions & 0 deletions routines/persona-refresh/ROUTINE.md
Original file line number Diff line number Diff line change
@@ -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-<YYYY-MM-DD>.md` if any segment's score shifts by ≥1 tier (painkiller ↔ vitamin, or reachability shift)
- One PR titled `[persona-refresh] <YYYY-MM-DD> — <N> 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).
5 changes: 5 additions & 0 deletions routines/persona-refresh/triggers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cron: "0 6 1 * *"
timezone: UTC
description: First of each month, 06:00 UTC
runtime: TBD
status: stub
46 changes: 46 additions & 0 deletions routines/weekly-blog/ROUTINE.md
Original file line number Diff line number Diff line change
@@ -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/<slug>.md`
- One PR titled `[blog-draft] <title>` 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.
5 changes: 5 additions & 0 deletions routines/weekly-blog/triggers.yml
Original file line number Diff line number Diff line change
@@ -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