greprag 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ # Handoff Contract
2
+
3
+ The content-advisor's output ends at a brief. The writer master takes it from there. Source of truth: `C:/openwriter/skills/WRITER-CONVENTION.md` — read it before changing anything in this doc.
4
+
5
+ ## v1 mode: list-only
6
+
7
+ Advisor produces the opportunity list with the exact slash-command in each item's `Handoff` field. User reads, picks, runs the command manually.
8
+
9
+ **Why list-only:** auto-invocation creates two problems early: (1) the user can't review the angle before the writer commits to it, and (2) if the writer needs clarifying input, the advisor isn't in the call to provide it. List-only keeps the human in the loop where judgment matters most — angle selection.
10
+
11
+ Future v2: when the user picks an opportunity by number ("do #3"), advisor invokes the chosen writer with the brief.
12
+
13
+ ## Brief shape (per WRITER-CONVENTION.md §4)
14
+
15
+ The slash-command serializes a brief object:
16
+
17
+ | Field | Required | Where it comes from in advisor |
18
+ |---|---|---|
19
+ | `source_material` | yes | `--source memory:<uuid>` for ship-event / daily / weekly OR `--source doc:<id>` for OpenWriter draft OR `--source inline:"topic phrase"` as last resort |
20
+ | `angle` | no but advisor always provides | `--angle "<phrase>"` derived from source content's first informative clause |
21
+ | `sub_form` | yes | Positional arg per writer (`/x-writer thread`, `/blog-writer long`, `/newsletter-writer issue`) |
22
+ | `venture` | yes when relevant | `--venture <slug>` matches the venture in `docs/ventures.md` |
23
+ | `voice` | no | Defaults to venture's authors-voice profile; advisor doesn't override |
24
+
25
+ ## Per-writer command shape
26
+
27
+ ### `/x-writer`
28
+
29
+ ```
30
+ /x-writer <sub_form> --source <ref> --venture <slug> --angle "<phrase>"
31
+ ```
32
+
33
+ Sub-forms: `thread` | `single` | `article` | `comic` | `reply` | `quote`.
34
+
35
+ Advisor picks sub-form by:
36
+ - Source is a fresh ship-event with rich content → `thread` or `article`
37
+ - Source is a pattern across 3+ dailies → `thread`
38
+ - Source is a single sharp observation → `single`
39
+ - Visual narrative or comparative → `comic`
40
+ - `reply` / `quote` are out of scope for the advisor (those belong to `/x-strategy`)
41
+
42
+ ### `/blog-writer`
43
+
44
+ ```
45
+ /blog-writer <sub_form> --source <ref> --venture <slug> --angle "<phrase>"
46
+ ```
47
+
48
+ Sub-forms: `long` | `short` | `tutorial` | `announcement`.
49
+
50
+ Advisor picks:
51
+ - Source is a ship-event with code-level detail → `announcement` or `tutorial`
52
+ - Source is a weekly through-line → `long`
53
+ - Source is one focused topic → `short`
54
+
55
+ ### `/newsletter-writer`
56
+
57
+ ```
58
+ /newsletter-writer <sub_form> --source <ref> --venture <slug> --angle "<phrase>"
59
+ ```
60
+
61
+ Sub-forms: `issue` | `supplemental` | `archive`.
62
+
63
+ Default for cadence-floor opportunities: `issue`. Use `supplemental` only when an issue already shipped this week but a hot ship-event landed after.
64
+
65
+ ## Return contract (what the writer reports back)
66
+
67
+ Per WRITER-CONVENTION.md §5:
68
+
69
+ ```json
70
+ {
71
+ "status": "draft-ready" | "needs-input" | "blocked",
72
+ "artifact": { "doc_id": "...", "workspace_id": "..." },
73
+ "next_steps": ["/polish", "/schedule", "/announce"],
74
+ "notes": "..."
75
+ }
76
+ ```
77
+
78
+ In v1 the advisor doesn't observe this directly (user invokes the writer themselves). When v2 lands, the advisor will pass `notes` back to the user verbatim and the suggested `next_steps` become the next round of advisor proposals.
79
+
80
+ ## What the advisor does NOT do
81
+
82
+ - **Generate the slash-command on behalf of the user and run it.** v1 prints; user runs.
83
+ - **Pick voice profile.** Default is venture's profile per `/authors-voice`.
84
+ - **Schedule or polish.** Those are downstream skills (`/schedule`, `/polish`); writer's `next_steps` surfaces them; user chains.
85
+ - **Fall back to inline-topic.** Always prefer `memory:<uuid>` or `doc:<id>`. Only use `inline:` for cadence-floor opportunities where no specific memory row drives the pitch.
86
+
87
+ ## Anti-patterns
88
+
89
+ - Calling a writer for a venture not in `channels_per_venture` for that channel. Setup config guards this; if a venture has only `["x"]`, do not propose blog or newsletter opportunities for it.
90
+ - Stuffing multiple ships into one brief. One brief = one source. If three ships cluster on a theme, that's a weekly-pattern opportunity, not a multi-source brief.
91
+ - Inventing a sub-form not in the writer's enum. Per WRITER-CONVENTION.md §4: if a needed sub-form doesn't exist, that's a signal to propose adding it to the writer — don't improvise on the advisor side.
@@ -0,0 +1,89 @@
1
+ # Output Format
2
+
3
+ What the user sees when `/content-advisor` finishes. Plain English. Top-line first, then ranked list, then optional "consider later" tier.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ # Content Advisor — <YYYY-MM-DD>
9
+
10
+ **Top of the queue:** <one-sentence pitch for #1 with the venture and channel>
11
+
12
+ ## Act now (final_rank ≥ 6)
13
+
14
+ ### 1. <Channel> — <Venture> — <short angle>
15
+ - **Why now:** <one sentence>
16
+ - **Source:** <memory_id excerpt | doc_id title | "recombine: <topic>">
17
+ - **Handoff:** `/<channel>-writer <sub_form> --source <source_ref> --venture <venture> --angle "<angle>"`
18
+
19
+ ### 2. ...
20
+
21
+ ## Consider later (final_rank 3–5)
22
+
23
+ ### N. ...
24
+
25
+ ## Notes
26
+
27
+ - <warnings: missing focus.md, X data unavailable, fallback turns used for venture Y, etc.>
28
+ - <ventures excluded as backburner>
29
+ - <ships that triggered an opportunity already in flight as an OpenWriter draft — listed here so the user knows the system noticed>
30
+ ```
31
+
32
+ ## Per-entry fields (rendered, not raw JSON in v1)
33
+
34
+ | Field | Source | Example |
35
+ |---|---|---|
36
+ | Channel | candidate.channel | `x: thread`, `blog: long`, `newsletter: issue` |
37
+ | Venture | candidate.venture | `paybot`, `tournament-male`, `personal` |
38
+ | Angle | derived from source content (first informative phrase) | "Upgrade-flow rebuild lessons" |
39
+ | Why now | rule_triggered + tier | "Shipped 9d ago, no content yet · Tier 1 venture" |
40
+ | Source | memory_id (preferred) / doc_id / "cadence-recombine" | `memory:7f3a...` |
41
+ | Handoff | slash-command per WRITER-CONVENTION.md | `/x-writer thread --source memory:7f3a-... --venture paybot --angle "..."` |
42
+
43
+ ## Ranking display
44
+
45
+ Items are numbered globally (1-N), not per-tier. The "Act now" / "Consider later" split is a cut on `final_rank ≥ 6`. Cap total at 10 items in v1.
46
+
47
+ ## When the list is empty
48
+
49
+ ```
50
+ # Content Advisor — <date>
51
+
52
+ No opportunities surfaced.
53
+
54
+ This usually means one of:
55
+ - All recent ships are already covered (good — you're caught up)
56
+ - All channels are within their cadence floor (good — you're current)
57
+ - GrepRAG has no recent activity (write something, ship something)
58
+
59
+ Specific signal:
60
+ - Ventures scanned: <N>
61
+ - Ships in window: <N>
62
+ - Open in-flight drafts: <N>
63
+ - Channels within cadence: <list>
64
+ ```
65
+
66
+ Empty is a valid state — surface why explicitly so the user knows the advisor ran cleanly.
67
+
68
+ ## When data is degraded
69
+
70
+ Lead with a Notes block at the top before the queue, naming exactly what's missing:
71
+
72
+ ```
73
+ **Degraded run** — operating with partial data:
74
+ - focus.md missing → tier prioritization disabled
75
+ - X recent posts unavailable → X cadence-floor rule skipped
76
+ - Project `xyz` returned 5xx → excluded this pass
77
+ ```
78
+
79
+ Then the list as normal. The user makes a judgment call on whether to act on a degraded ranking.
80
+
81
+ ## What we deliberately don't include in v1
82
+
83
+ - Engagement predictions ("this will get N views")
84
+ - Auto-scheduling slot picks
85
+ - Image suggestions
86
+ - Polishing scores
87
+ - Multi-step plans ("first do A, then B, then C")
88
+
89
+ The advisor proposes the next thing. The writer ships it. The user decides which one.
@@ -0,0 +1,125 @@
1
+ # Setup — First-Run Gateway
2
+
3
+ The advisor's first invocation enters bootstrap. Setup runs once, written to a config file, then subsequent invocations skip directly to the standard protocol.
4
+
5
+ ## Portfolio root resolution
6
+
7
+ Resolution order:
8
+
9
+ 1. Env var `CONTENT_ADVISOR_ROOT`
10
+ 2. Config file `~/.claude/content-advisor.json` with `{ "portfolio_root": "/abs/path" }`
11
+ 3. Marker file scan up the cwd tree — looks for `docs/ventures.md` and `docs/strategy/focus.md`
12
+ 4. Fallback default: `C:/orchestrator`
13
+ 5. None applicable → ask user, save to config
14
+
15
+ The advisor never operates against an unresolved portfolio root.
16
+
17
+ ## Setup flow
18
+
19
+ ```
20
+ /content-advisor (no config detected)
21
+
22
+ [Step 1] Locate portfolio root
23
+ → default C:/orchestrator; confirm or override
24
+ → save to ~/.claude/content-advisor.json
25
+
26
+ [Step 2] Check GrepRAG
27
+ → command -v greprag → exits 0?
28
+ → test -n "$GREPRAG_API_KEY"? (source ~/.env if missing)
29
+ → greprag discover --json → returns ≥1 project?
30
+ → any failure → walk user through /greprag setup
31
+
32
+ [Step 3] Verify writer skills present
33
+ → ~/.claude/skills/x-writer/SKILL.md exists?
34
+ → ~/.claude/skills/blog-writer/SKILL.md exists?
35
+ → ~/.claude/skills/newsletter-writer/SKILL.md exists?
36
+ → any missing → "Run /skill-publish from C:/openwriter to install the writer masters."
37
+
38
+ [Step 4] Read WRITER-CONVENTION.md
39
+ → C:/openwriter/skills/WRITER-CONVENTION.md (source of truth for handoff brief shape)
40
+ → if absent, advisor still works but warns the contract is unverified
41
+
42
+ [Step 4.5] Per-venture ICP (Ideal Customer Profile) derivation
43
+ → For each venture in `project_to_venture`:
44
+ a) Search for an existing avatar / audience doc:
45
+ - OpenWriter workspaces with titles like "Audience", "Audience & Reader Journey", "Avatar", "ICP"
46
+ - The venture's marketing-site repo for `content/audience.md` or similar
47
+ - If found → store pointer in config under `icp_per_venture[venture]` as
48
+ `"openwriter:<docId>"` or `"file:<absolute-path>"`
49
+ b) If no doc exists, conduct a short interview (5-7 questions max):
50
+ - Who is the buyer? (one sentence)
51
+ - What's their current state? (the pain that brings them in)
52
+ - What do they pick up your content hoping for?
53
+ - What's the recognition shock they want? (their life mirrored back)
54
+ - What do they walk away with that's actionable this week?
55
+ - What's the failure mode of content that doesn't land?
56
+ - Optional: who do they trust today that you're competing with?
57
+ c) Write the interview output to `docs/icp/<venture>.md` in the portfolio
58
+ root and store the pointer in config.
59
+ → Per-venture ICP is the demand-side spec the advisor reads at rank time
60
+ (see Rule 5 in `decision-rules.md`).
61
+
62
+ [Step 5] Verify portfolio docs
63
+ → docs/ventures.md, docs/strategy/focus.md present?
64
+ → each missing → "Run /business-advisor to scaffold portfolio docs."
65
+
66
+ [Step 6] Project ↔ venture mapping
67
+ → greprag discover --json lists project names
68
+ → match each to a venture umbrella (often 1:1; some ventures span multiple repos)
69
+ → save to ~/.claude/content-advisor.json under `project_to_venture`
70
+
71
+ [Step 7] Optional: per-venture cadence overrides
72
+ → defaults in docs/decision-rules.md (X daily, newsletter weekly, blog 2×/mo)
73
+ → user can override per venture in config under `cadence_overrides`
74
+
75
+ [Step 8] Lock in
76
+ → run one full pass, present opportunity list as the first output
77
+ → confirm config persisted
78
+ ```
79
+
80
+ ## Config file format
81
+
82
+ `~/.claude/content-advisor.json`:
83
+
84
+ ```json
85
+ {
86
+ "portfolio_root": "C:/orchestrator",
87
+ "greprag_api_url": "https://api.greprag.com",
88
+ "project_to_venture": {
89
+ "openwriter": "openwriter",
90
+ "openwriter-site": "openwriter",
91
+ "openwriter-publish": "openwriter",
92
+ "paybot": "paybot",
93
+ "paybot-portal": "paybot",
94
+ "tournamentmale": "tournament-male",
95
+ "tm-book": "tournament-male",
96
+ "orchestrator": "personal"
97
+ },
98
+ "cadence_overrides": {
99
+ "openwriter": { "blog": "weekly" }
100
+ },
101
+ "icp_per_venture": {
102
+ "your-venture-here": "openwriter:doc_abc123",
103
+ "another-venture": "file:C:/path/to/portfolio/docs/icp/another-venture.md"
104
+ },
105
+ "channels_per_venture": {
106
+ "tournament-male": ["x", "newsletter", "blog"],
107
+ "paybot": ["x", "blog"],
108
+ "openwriter": ["x", "blog"],
109
+ "personal": ["x", "newsletter"]
110
+ }
111
+ }
112
+ ```
113
+
114
+ `channels_per_venture` is the gate that prevents the advisor from proposing a blog post for a venture that has no blog. Default behavior when a venture is missing from this map: assume all three channels eligible.
115
+
116
+ ## Re-running setup
117
+
118
+ Delete `~/.claude/content-advisor.json` and re-invoke. Or invoke with "redo setup step N" to re-enter one sub-flow.
119
+
120
+ ## Failure modes
121
+
122
+ - **GrepRAG unreachable** → advisor cannot run. Setup blocks. Source-farmer rule needs ship-events; nothing else compensates.
123
+ - **Writer skills missing** → advisor still produces the list but flags handoff commands as unverified.
124
+ - **Portfolio docs missing** → tier prioritization disabled (all opportunities treated equal). Surface a warning.
125
+ - **No projects in greprag discover** → no source material. Advisor exits with "no episodic memory yet — write code, ship something, then check back."
@@ -0,0 +1,89 @@
1
+ # Writing Activity
2
+
3
+ The "what have we already written" stream. Pairs with episodic memory to drive the anti-duplication and cadence-floor rules.
4
+
5
+ ## What we want to know per channel per venture
6
+
7
+ | Channel | The question | Source |
8
+ |---|---|---|
9
+ | X | When was the last post? What topics in last 7d? | x-strategy cache, or fxtwitter recent timeline |
10
+ | Blog | When was the last post? What topics in last 30d? | git log on the venture's marketing-site repo + frontmatter scan |
11
+ | Newsletter | When was the last issue sent? What was its through-line? | `mcp__openwriter__list_newsletter_issues` |
12
+ | OpenWriter drafts | What's in flight right now (un-shipped drafts touched in last 14d)? | `mcp__openwriter__list_documents` filtered by recency |
13
+
14
+ ## Per-source procedure
15
+
16
+ ### OpenWriter drafts (in-flight)
17
+
18
+ ```
19
+ list_workspaces → for each workspace:
20
+ list_documents → filter where updatedAt > now-14d AND not in trash
21
+ → for each doc: extract { id, title, workspace_name, updatedAt, tags }
22
+ → infer channel from workspace name conventions (blog/newsletter/x/scratch)
23
+ OR from tags if present
24
+ → infer venture from workspace name OR explicit metadata
25
+ ```
26
+
27
+ These are CRUCIAL — an in-flight draft on topic X means "don't propose another piece on X." Surface them as "in progress" markers, not opportunities.
28
+
29
+ ### Newsletter sends
30
+
31
+ ```
32
+ list_newsletter_issues → sort desc by sentAt → take top 4
33
+ → for each: { id, subject, sentAt, doc_id }
34
+ → most recent sentAt → "newsletter last sent N days ago"
35
+ → if N > cadence floor (default 7d): cadence-floor opportunity for newsletter
36
+ ```
37
+
38
+ ### Blog history
39
+
40
+ For each venture with a marketing-site repo (path resolved via `project_to_venture` config + scanning `ventures.md` for repo paths):
41
+
42
+ ```
43
+ git -C <repo> log --since="30 days ago" --name-only --pretty=format:'%h|%ci|%s'
44
+ → filter file changes to blog content paths (src/content/blog/**, content/posts/**, etc.)
45
+ → group commits by post file → { post_file, first_commit_date, latest_commit_date }
46
+ → most recent → "blog last shipped N days ago"
47
+ ```
48
+
49
+ The blog-content path convention varies per site; try common patterns:
50
+ - `src/content/blog/**` (Astro)
51
+ - `content/posts/**` (Hugo)
52
+ - `_posts/**` (Jekyll)
53
+ - `app/blog/**/page.mdx` (Next.js)
54
+
55
+ If no match, fall back to scanning frontmatter (`---\npubDate:`) in any `.md`/`.mdx` file modified in window.
56
+
57
+ ### X activity
58
+
59
+ Three options, in preference order:
60
+
61
+ 1. **`/x-strategy` cache** — if present at a known path, parse the latest cached export.
62
+ 2. **`mcp__x__*` tools** — if the X MCP is connected, query recent tweets for @Meta_Trav.
63
+ 3. **fxtwitter timeline** — public, free; parse the user's recent posts.
64
+
65
+ What we want: list of `{ posted_at, text, type: thread|single|article|comic, topic_inferred }`. Topic inference is best-effort — a 30-word excerpt is enough to dedupe against memory rows.
66
+
67
+ ## Output of this stream
68
+
69
+ A per-venture-per-channel activity map:
70
+
71
+ ```json
72
+ {
73
+ "tournament-male": {
74
+ "x": { "last_at": "2026-05-24T...", "topics_7d": ["six-week challenge", "kettlebell program"] },
75
+ "newsletter": { "last_at": "2026-05-19T...", "subject": "Week of May 19" },
76
+ "blog": { "last_at": "2026-04-30T...", "recent_topics": ["bible study cohort"] }
77
+ },
78
+ "paybot": {
79
+ "x": { "last_at": "2026-05-23T...", "topics_7d": ["upgrade flow ship"] },
80
+ "blog": { "last_at": "2026-05-10T...", "recent_topics": ["new pricing"] }
81
+ }
82
+ }
83
+ ```
84
+
85
+ Plus a separate "in-flight drafts" list across all ventures (the OpenWriter list_documents output, filtered by recency).
86
+
87
+ ## Failure tolerance
88
+
89
+ Any one of these sources can be down without breaking the advisor — they're enrichment. The minimum viable run is: greprag memory + ventures.md + focus.md. Everything else just sharpens ranking.