content-portfolio · diff
git:20261015.aa864d4 to git:20261015.6456cfc
25 added, 14 removed. Audit A to A.
---
name: content-portfolio
description: Plan a founder's weekly content program as a prioritized portfolio of pages (alternatives, comparisons, answer pages, page families, guides, refreshes) from Tin's brief and the project's files, and write it as content-portfolio/1.
---
# Content portfolio
You plan pages for one product's site, one or more a week, for the whole horizon in the brief.
Tin drafts them later, one at a time, and the founder approves each. Your plan decides what the
founder publishes for months, so think it through: each week's piece should be the best page
this product could publish next.
## 1. Understand the product and the buyer
Read before you plan:
- `wiki/INDEX.md` (project memory and the Code map), the brand guide, the Start here plan and the
founder notes in `context/`: what the product does, for whom, what it costs, how it is used,
what it integrates with, what it is not.
- `context.json` → `program`: host, market, horizon, weeks, `pieces_per_week`, `slots`, the
instruction, and `existing_items` (work already planned; in a revision only `editable` weeks
are yours to plan).
- `context.json` → `site_signals`: Page decisions (pages to refresh, rewrite, keep, merge or
retire) and the traffic snapshot (pages that turn visitors into signups, pages with visits but
few signups).
- `research.json`: every keyword with its measured volume, difficulty, ranking page and Search
Console clicks and impressions; the keyword groups; the audit's content findings (buyer
questions AI answers miss the site on); refresh rows (pages near the top results or seen but
rarely clicked, in order of upside); competitor rows.
- `pages.json`: the pages Tin read from the live site, and every address Tin knows on the site
(`site_pages`). Pages with search signal carry a longer excerpt; every other listed page Tin
could read carries its title, meta description, its H1 to H3 outline and its opening. The
outline says what a page covers beyond its opening: read it before you call a topic missing.
An address alone is not content.
- The audit's `evidence.json` (`ai_visibility`: the buyer panel, its questions, the answers AI
assistants gave and the sites they cited) and the keyword plan's `evidence.json` (search
competitors and Search Console rows) hold more than the summaries. Read them with a script
when they are large.
Write down, for yourself: the priority buyer, the decision they are making, the alternatives they
weigh (named products and the manual way: a spreadsheet, an agency, doing it by hand), and the
reasons this product wins, taken from the project's files.
## 2. Settle the competitive set
- `context.json` → `competitors` merges the audit's buyer panel, the domains AI answers cite, the
- keyword plan's search competitors and competitor.watch, with the sources for each.
- `corroborated` means two sources agree.
+ Do this before you plan, following `COMPETITORS.md` in this skill's folder. In short:
- - Check each candidate on the web: is it a real alternative for the same buyer and job? A site
- that only ranks for the same words (a publisher, a directory, a namesake) is not a competitor;
- it may still be a page to beat for a search.
- - Add a competitor the project's files or search results show buyers compare, even if no source
- listed it, and say where you saw it.
- - For each real competitor decide its use: an alternatives page, a head-to-head comparison, a
- place in a best-tools page, or nothing (say why). Lead with the competitors buyers name most
- and that the AI answers recommend.
+ - Frame the search from the project's files: the buyer, the job in their words, two or three
+ category phrases (one narrow, one broad) and the stack the product plugs into.
+ - Search four channels: category roundups, the job with the stack attached (and the stack's own
+ ecosystems), recent launches (Hacker News, GitHub, Product Hunt), and the comparison pages of
+ competitors you have confirmed are direct. If your tools include `spawn_agent`, you must run
+ the four channels as subagents in parallel (direct `spawn_agent` calls, not from `exec`) and
+ read the project while they search.
+ - The brief's leads are where you start, not a checked list. `context.json` → `competitors`
+ merges the site's own comparison pages, the earlier research, competitor.watch, the audit's
+ buyer panel and the keyword plan's search competitors, with the sources for each. Sites that
+ rank for the same searches and domains AI answers cite are usually publishers, directories or
+ tools for another job.
+ - Open every candidate's own site and label it direct, adjacent, diy or drift. A candidate you
+ did not open stays out. When `earlier_competitor_research` is in `context.json`, start from it
+ as the brief says.
+ - For each direct, adjacent or diy competitor decide its use: an alternatives page, a head-to-head
+ comparison, a place in a best-tools page, or nothing (say why). Lead with the competitors
+ buyers name most and that the AI answers recommend. A drift product gets no alternatives or
+ comparison page.
- The manual way is a competitor too: "{product} vs spreadsheets", "vs hiring an agency".
## 3. Find the page families the product really has
A page family is one intentional page type with a shared structure, where each member says
something true and different: a page for each workflow, integration, template, use case, role or
industry the product serves. It is not the same page with one word swapped.
- Find the members in the Code map, the product's pages and the brand files. Every member must
exist in the product today; list them by name in the family's `members`.
- Each member page needs its own substance: what goes in, what comes out, the steps, who it is
for, what it replaces. If you cannot say what makes member pages differ beyond the name, it is
not a family; plan one guide instead.
- Plan the hub first, then members by demand and buyer value, one or two a week mixed with other
formats. The rest of a large family can follow in later weeks.
- Check `site_pages` first: the site may already have the hub or some members; plan updates or
the missing members only.
## 4. Build the candidate pool
Draft far more candidates than slots, then choose. Formats (`context.json` → `formats`):
- **refresh / update** (existing pages, `action: update_page`, `destination` = the page URL):
Page decisions' refresh and rewrite rows; refresh rows near page one or seen but rarely
clicked; pages losing traffic; a comparison page whose competitor changed. Improving a page
that already ranks is usually faster than a new page. One item per page.
A `refresh` changes only the title, the meta description, the H1, the opening paragraph and,
when Page decisions allows body changes, up to three existing body paragraphs, each replaced
in place. It cannot touch a table, a list, a heading below the H1, a link, a section or an
FAQ, and it adds nothing. When the page needs any of those (a wrong row in a comparison
table, a claim repeated through the body, a missing section), plan an `update` instead: a
substantive rewrite of the whole page that keeps what is still right. Write a refresh brief
only about the lines a refresh can change.
- **alternative**: "{competitor} alternatives" for one named competitor.
- **comparison**: "{product} vs {competitor}", or a three-way page with two leaders.
- **workaround**: the product against the manual way.
- **roundup**: "best {category} for {buyer}" that includes the product honestly.
- **answer**: one canonical page per core buyer question (the audit's buyer questions, question
searches in the keyword inventory and Search Console). The answer is in the first paragraph,
then a dated decision table, who it is best for and not for, and current pricing and limits
from the project's files.
- **family_hub / family_page**: section 3.
- **use_case**: a problem or job the buyer has, solved with the product.
- **guide**: a how-to or explainer for a real task the product does.
Searches matter most where they are measured: prefer candidates backed by keyword volume,
Search Console impressions or a refresh row. A buyer question from the audit, a competitor buyers
name, or a search result you inspected is real evidence too. A strategic page with no measured
demand is allowed when the reason is clear; label it a bet.
## 5. Check the search results before you commit
For every new page in the first eight weeks, and any page you are unsure of, search its target
query:
- Who ranks: competitors' own pages, publishers' lists, forums, the product itself?
- Is there an AI overview or a direct answer? Prefer a sibling query where results are cleaner.
- Can this site's page be better than the top results for this buyer? Write that reason in
`win_case`. If you cannot, choose a different query or drop the page.
- On a young site prefer queries with low difficulty (under about 30) and specific intent: a
winnable search with 200 monthly searches beats a contested one with 5,000. A keyword missing
from the difficulty data is unknown, not easy.
## 6. Choose and order
Score each candidate on what it does for the founder's goal (signups, customers), how close the
searcher is to buying, demand, winnability and fit with the product. Then order the portfolio:
- Weeks 1–4: refreshes with measured upside, the alternatives and comparison pages for the
competitors buyers name most, the core answer page, and a family hub if the product has one.
- After that, mix every week: a bottom-of-funnel or answer page, a family member or use case, a
guide or refresh. Do not run one format for many weeks in a row.
- The program grows the site. Plan a new page in most weeks; keep refreshes and updates to about
half the slots or fewer, the ones with measured upside first, unless the site truly has
little new left to add (say so in `strategy`).
- Cover the ground buyers search: an alternatives or comparison page for each real competitor
the site lacks one for, an answer page for each distinct core buyer question (the audit's
panel, question searches, AI answers that cite others), and the missing members of each page
family. A family whose hub and members already exist is updated, and its missing members are
new `family_page` items.
- Put pages near the pages that convert (traffic snapshot) ahead of others of equal value.
- Every page Page decisions marks for a refresh or rewrite gets an item or a line in `excluded`
saying why not; Tin adds the ones you leave unanswered after your items.
- Plan nothing on a page Page decisions keeps, merges or retires, and no new page on the topic of
one it merges or retires.
- Never plan two pages for one search intent, a new page that competes with an existing page on
the site, or two items that change the same page.
Fill `program.slots` items. If you truly run out, return fewer and say in `gaps` what evidence
(a keyword lookup, a founder fact, a connection) would unlock more.
## 7. Write each brief
`brief` (at most 1,800 characters) is what the writer works from:
- the reader, their question in their words, and the target search;
- the angle and the argument: the answer, the proof to use (name the project files), the main
objection, the next step to the product;
- the sections the page needs, and pages to link to and from (hub, product pages);
- what not to claim.
Comparison pages: the product's column states a capability; a competitor's column states its
scope, from the competitor's own published pages and units. A parity you cannot verify becomes a
verification task, not an assumption. Include an honest "when {competitor} is the better fit".
`verification` lists the desk checks before drafting: confirm each product claim in the named
files, confirm competitor facts on their current pages, re-read the page being changed. No live
product tests, sign-ups or sends.
`sources` cites what the item rests on, best first, at most 12:
- research rows and pages by their `source_id` (`keyword:…`, `group:…`, `audit:…`, `refresh:…`,
`efficacy:…`, `competitor:…`, `page:…`);
- project files as `file:<path>`; the writer loads only files of 20 KB or less, so cite the
small, specific files (the brand guide, a founder note) rather than large JSON;
- web pages you inspected as their `https://` URL.
`evidence_strength`: `measured` (keyword volume, Search Console, a refresh or Page decisions
row), `inferred` (buyer questions, AI answers, competitor presence, results you inspected),
`bet` (a reasoned strategic page without measured demand).
## 8. Write the portfolio
- Write the output file as Markdown: a short human summary (buyer, competitive set, families,
- the first month and why), then exactly one JSON block between these two marker lines:
+ Write the output file as Markdown: a short human summary (buyer, competitive set and how you
+ found it, families, the first month and why), then exactly one JSON block between these two
+ marker lines:
```
<!-- content-portfolio.json:start -->
{ ...JSON... }
<!-- content-portfolio.json:end -->
```
```json
{
"schema": "content-portfolio/1",
"strategy": "The buyer, their decision, the alternatives, why the product wins (from the files), and how the order serves the founder's goal. At most 4,200 characters.",
- "competitors": [{"name": "", "host": "", "use": "alternative | comparison | roundup | none", "why": ""}],
+ "competitors": [{"name": "", "host": "", "label": "direct | adjacent | diy | drift", "does": "what its own site says it does", "buyer": "", "evidence_url": "https://the page you opened", "channels": ["roundups | job_and_stack | launches | comparison_pages"], "use": "alternative | comparison | roundup | none", "why": ""}],
+ "competitor_research": {"started_from": "earlier research of <date> | none", "channels": [{"channel": "roundups", "queries": [""], "found": [""], "notes": ""}]},
"families": [{"id": "", "name": "", "why": "", "members": [""]}],
"opportunities": [
{
"id": "op001",
"format": "alternative",
"title": "",
"target_query": "",
"intent": "",
"action": "new_page",
"destination": "/alternatives/competitor-name",
"brief": "",
"sources": [""],
"evidence_strength": "measured",
"why_this": "why it beat the alternatives you considered",
"win_case": "why this page can rank or be cited",
"metric": "what it should move",
"rejected": ["candidates it beat"],
"verification": [""],
"competitor": "",
"family": ""
}
],
"gaps": [""],
"excluded": [""]
}
```
`opportunities` is in priority order; Tin fills the weeks in that order. In a revision, keep the
`id` of every existing item you keep.
Every new page names its address in `destination`: the path it will have on the site, such as
`/alternatives/zapier`. The founder approves it with the plan, and Tin publishes the page at
exactly that path, so choose it the way the site already names its pages:
- Put it in the section where the site keeps that kind of page. `site_pages` shows the sections:
its comparison or alternatives pages, guides, docs or blog. A site that serves some pages at
short paths (`/self-host`) rather than in a folder keeps that pattern for the same kind of page.
- When the site has no section for this kind of page, use the plainest one a reader would
expect: `/blog` for articles and answers, `/alternatives` for alternatives, `/compare` for
comparisons. Use the reader's words, never a Tin term such as answers.
- End it with three to six lowercase, hyphenated words from the target search, without filler
words such as the, a, of or how.
- Never reuse the path of a page the site already has or of another item.
Finally run `python3 check_portfolio.py <output path> <brief folder>` from this skill's folder
and fix every problem it reports.