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
76 changes: 76 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: CI

on:
push:
branches: ["**"]
pull_request:

jobs:
lint-and-typecheck:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Install dev tools
run: pip install ruff mypy

- name: ruff lint
run: ruff check scripts/

- name: mypy type check
run: mypy scripts/ --ignore-missing-imports

syntax-check:
name: Script Syntax Validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Check Python syntax
run: |
python -m py_compile scripts/scrape_positioning.py
python -m py_compile scripts/analyze_positioning.py
python -m py_compile scripts/render_positioning.py
python -m py_compile scripts/run_pipeline.py
echo "All scripts pass syntax check"

deps-install:
name: Dependency Installation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Install system deps for WeasyPrint
run: |
sudo apt-get update -q
sudo apt-get install -y -q \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
libffi-dev libjpeg-dev libopenjp2-7

- name: Install Python dependencies
run: pip install -r requirements.txt

- name: Install Playwright browsers
run: playwright install chromium --with-deps

- name: Verify imports
run: |
python -c "import playwright; print('playwright ok')"
python -c "import anthropic; print('anthropic ok')"
python -c "import openai; print('openai ok')"
python -c "import weasyprint; print('weasyprint ok')"
100 changes: 100 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# AGENTS.md — Neobank Positioning Engine Skill

## How Claude Code Invokes This Skill

Claude Code reads `SKILL.md` at session start. The skill triggers on phrases like:

- `position [company]`
- `positioning audit for [company]`
- `competitive positioning: A vs B vs C`

Claude then drives the three-script pipeline interactively, pausing for human input at defined checkpoints.

---

## Invocation Pattern

```
Trigger phrase → Phase 1 (Scrape) → [PAUSE] → Phase 2 (Analyze) → [PAUSE] → Phase 3 (Render) → Done
```

Claude calls scripts via `subprocess`-style Bash tool calls — it does NOT import them as modules.

---

## Pause Points (Human Review Required)

| After Phase | What Claude Shows | What Human Decides |
| ----------- | ------------------------------------------- | --------------------------------------------------- |
| Scrape | List of URLs scraped + data quality summary | Approve data, add missing competitors, or re-scrape |
| Analyze | Positioning map + white space findings | Confirm strategic direction before generating copy |
| Render | PDF path + brief summary | Accept output or request revisions to framework |

Claude MUST stop and surface results at each pause point. Do not auto-chain all three stages without human confirmation.

---

## Error Handling Expectations

| Error | Expected Behavior |
| --------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Playwright scrape fails (bot protection, 403) | Fall back to `references/neobank-messaging-map.md`, note fallback in output |
| Thin scrape data (<200 chars body text) | Warn user, suggest manual input or app store description as supplement |
| API key missing | Exit with clear message: "Set ANTHROPIC_API_KEY or OPENROUTER_API_KEY in .env" |
| API rate limit / timeout | Retry once (MAX_RETRIES=1), then surface error with raw scraped data for manual analysis |
| WeasyPrint missing system deps | Output HTML only, note PDF generation failed with install instructions |
| `output/` directory missing | Scripts create it automatically — not an error |

---

## Output Contract

`analyze_positioning.py` MUST emit JSON matching the schema in `examples/kast-brief.json`. Fields:

```
company, date, competitors[], executive_summary,
positioning_elements{}, territory_map{}, white_space[],
messaging_framework{
positioning_statements[], one_liners[], value_propositions[],
audience_messaging[], what_not_to_say[], competitive_responses[]
}
```

`render_positioning.py` reads this schema. Breaking changes to the schema break rendering.

---

## Custom Framework Adaptation

To adapt this skill for non-neobank verticals:

1. **References** — Replace `references/neobank-messaging-map.md` with pre-mapped data for the new vertical. Keep `references/positioning-frameworks.md` (Moore/Dunford frameworks are universal).

2. **Territory dimensions** — The four dimensions (audience spectrum, trust model, value prop core, brand personality) are defined in the analyze prompt inside `scripts/analyze_positioning.py`. Search for `POSITIONING_DIMENSIONS` or equivalent prompt section and rewrite for your vertical.

3. **Scrape targets** — `scrape_positioning.py` scrapes generic web content — no neobank-specific logic. Works for any B2C website.

4. **Skill trigger** — Update `SKILL.md` front matter `description` field and trigger phrases in the `## Trigger` section.

5. **Examples** — Add a real brief JSON to `examples/` for the new vertical so Claude has a concrete output reference.

No code changes required for new competitors — they're passed as CLI arguments.

---

## Running Outside Claude Code

The skill scripts are plain Python. Any agent or CI system can invoke them:

```bash
# Full pipeline (non-interactive)
python scripts/run_pipeline.py "Company" "https://url.com" \
--competitors "Rival1:https://r1.com" "Rival2:https://r2.com"

# Stage by stage
python scripts/scrape_positioning.py "Company" "https://url.com"
python scripts/analyze_positioning.py output/company-positioning.json --competitors rival1 rival2
python scripts/render_positioning.py output/company-brief.json
```

All scripts exit 0 on success, non-zero on failure. Errors go to stderr.
63 changes: 57 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,71 @@
# Neobank Positioning Engine

## Build/Run
- Install: `pip install -r requirements.txt && playwright install chromium`
- Scrape: `python scripts/scrape_positioning.py "Company" "https://url.com"`
- Analyze: `python scripts/analyze_positioning.py output/slug-positioning.json --competitors comp1 comp2`
- Render: `python scripts/render_positioning.py output/slug-brief.json`
- Pipeline: `python scripts/run_pipeline.py "Company" "https://url.com" --competitors "Comp:https://url"`
## Run Commands

| Phase | Command |
| ------------- | ---------------------------------------------------------------------------------------------- |
| Scrape | `python scripts/scrape_positioning.py "Company" "https://url.com"` |
| Analyze | `python scripts/analyze_positioning.py output/slug-positioning.json --competitors comp1 comp2` |
| Render | `python scripts/render_positioning.py output/slug-brief.json` |
| Full pipeline | `python scripts/run_pipeline.py "Company" "https://url.com" --competitors "Comp:https://url"` |

## Environment Setup

```bash
# Create virtualenv
python -m venv .venv && source .venv/bin/activate

# Install system deps (macOS)
brew install pango cairo gdk-pixbuf libffi

# Install system deps (Ubuntu/Debian)
sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libffi-dev

# Install Python deps
pip install -r requirements.txt
playwright install chromium
```

## API Key Configuration

```bash
cp .env.example .env
# Edit .env and set at least one:
# ANTHROPIC_API_KEY=sk-ant-... (preferred, ~$0.10-0.20/run)
# OPENROUTER_API_KEY=sk-or-... (fallback, ~$0.15-0.35/run)
```

Auto-detection order: Anthropic key → OpenRouter key. Override with `--provider anthropic|openrouter` and `--model <model-id>`.

## Architecture

Three-stage pipeline: scrape (Playwright) → analyze (Claude API) → render (WeasyPrint).
Scripts in `scripts/`, references in `references/`, examples in `examples/`, output in `output/`.
Brief schema defined by `examples/kast-brief.json`.

## Key Patterns

- Scripts use `sys.argv` / `argparse` at module level with `if __name__ == "__main__"` guards
- `run_pipeline.py` chains scripts via `subprocess.run()` (not imports) because of asyncio + argv patterns
- `analyze_positioning.py` auto-detects API provider from env vars (Anthropic preferred, OpenRouter fallback)
- `.env` loaded by a simple parser in `analyze_positioning.py`, no dotenv dependency
- `slugify()` is duplicated across scripts intentionally (no shared utils module)

## Common Issues

| Issue | Fix |
| ---------------------------- | ----------------------------------------------------------------------------------- |
| `WeasyPrint` import error | Install system deps: `brew install pango cairo` (macOS) or apt-get equivalents |
| Playwright browser not found | `playwright install chromium` |
| Scraper returns empty body | Site uses heavy JS — increase `asyncio.sleep` in `scrape_page()` or add manual data |
| API auth error | Check `.env` exists and key has no trailing whitespace |
| `output/` not found | Scripts create it automatically on first run |
| PDF blank / CSS broken | WeasyPrint version mismatch — pin to `weasyprint~=63.0` |

## Linting

```bash
pip install ruff mypy
ruff check scripts/
mypy scripts/ --ignore-missing-imports
```
Loading
Loading