From 4ccf38ef628252b427b25f04469a7610d69f04b5 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:19:42 -0700 Subject: [PATCH 01/18] docs(gtm): synthesize Brian Love's voice + tone reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source-of-truth for any post drafted on Brian's behalf. Synthesized from his blog corpus at ~/repos/brianflove/src/content/posts/. The marketing pipeline will read this file as the authoritative voice reference — no machine-local path dependencies. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/gtm/voice.md | 146 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 docs/gtm/voice.md diff --git a/docs/gtm/voice.md b/docs/gtm/voice.md new file mode 100644 index 000000000..7496a3752 --- /dev/null +++ b/docs/gtm/voice.md @@ -0,0 +1,146 @@ +# Voice: Brian Love + +> The canonical voice + tone reference for any post drafted on Brian's behalf — blog, social, threads, replies. Synthesized from his existing blog at the time of writing (corpus: `~/repos/brianflove/src/content/posts/`, with primary weight on the 2026 frontend + agentic series). + +## tl;dr + +- One thought per line. Most paragraphs are 1–3 short sentences, often a single sentence. +- Opens with a blunt thesis sentence, not an "Introduction" header. The title is the only preamble. +- Heavy use of the **"Not because X. And not because Y. It is Z."** contrast pivot to set up a thesis. +- A `## tl;dr` block of 4–6 bullets sits near the top of long posts. Bullets are declarative, not promotional. +- Vocabulary is plain and operational ("control surface," "contract," "fallback," "the part that matters"). No superlatives, no emoji, no exclamation marks, no marketing hype. +- First-person singular ("I think," "I would ship," "the architecture I would actually ship"). Reader addressed as a peer building the same thing — never "you should be excited." + +## Core stance + +Brian writes as a working engineer talking to other working engineers who are about to ship something. He is not an evangelist and not a teacher in the lecturing sense. He is the person who already built one of these and is telling you which parts will hurt. + +The job of the writing is to compress an architectural opinion into something you can act on this week. He frames decisions as tradeoffs, not best practices. He is willing to recommend ("this is the sequence I would use"), but he labels the recommendation as a personal call, not a universal rule. + +He is not trying to entertain, and he is not trying to sell. There is no closing CTA, no "follow me for more," no narrative arc. Posts end the moment the argument is finished. + +## Sentence-level patterns + +- **Rhythm: short, declarative, often paired.** Two short sentences sit next to each other to set up a contrast. Example: *"A stateless LLM can answer. / A system with agentic memory can improve."* +- **One sentence per line in body prose.** Hard returns inside paragraphs are the norm, not the exception. Example block from "Agentic Memory": *"Summaries drift. / Compression loses nuance. / Derived memory can get subtly wrong over time."* +- **Punctuation is plain.** Periods, colons, and bulleted lists do almost all the work. Semicolons are rare. Exclamation marks are absent in the 2026 corpus. +- **Em-dash usage is restrained.** He prefers a period over an em-dash. When em-dashes appear they are used sparingly for parenthetical asides ("You can (and often should) use all three in one product.") — never for dramatic pauses. +- **Colons introduce lists and definitions.** Example: *"That separation matters because a valid overlay does not guarantee a valid payload."* Then a colon-led list. +- **Paragraph breaks are aggressive.** A "paragraph" is often one sentence on its own line, e.g. *"That is the real shift."* / *"This is the part I care about most."* +- **Bold is reserved for thesis claims.** Example: *"**Memory is not storage. It is a control surface for reasoning.**"* Usually appears once or twice per post, set off on its own line. +- **No contractions in the technical posts.** "It is" not "it's." "Do not" not "don't." (The 2024 personal posts loosen this — "I don't believe…" — but the 2026 technical voice is uncontracted.) + +## Structural moves + +- **Opening: blunt thesis as the first sentence.** No throat-clearing. + Example: *"Memory is becoming one of the most important design surfaces in agentic software."* + Example: *"Google's A2UI is one of the more interesting protocol ideas in agentic UI right now."* +- **"Not because / And not because / It is because" pivot, immediately after the thesis.** + Verbatim from "Agentic Memory": + > Not because models suddenly became databases. + > And not because storing more transcripts is the same thing as making a system smarter. + > It matters because memory changes what kind of software we are building. + This move is a signature. Use it. +- **`## tl;dr` block, 4–6 bullets, near the top.** Bullets are full sentences ending in periods, framed as claims not features. +- **Numbered framework of 3–4 items, named once and reused as section headers.** Example: "1. Chat Components / 2. Component Systems / 3. Embedded Generative UI" appears at the top, then each becomes an H2. +- **"The architecture I would actually ship"** is a recurring section title. Variants: *"The dynamic pattern I recommend," "My practical recommendations," "Recommended rollout."* The post earns its ending by getting concrete. +- **Bolded one-line thesis, set off as its own paragraph.** Example: *"**The problem is no longer remembering more. It is remembering the right abstractions.**"* +- **"That is X."** as a one-line callback. *"That is the real shift."* / *"That is reflection."* / *"That is the architecture."* He uses this to land a section. +- **Closing: two or three short declarative lines.** No CTA, no sign-off, no question. + Example (Agentic Memory): *"Memory is becoming policy. / And policy is becoming product behavior. / That is what makes this interesting."* + +## Vocabulary + +### Reaches for + +- **"control surface"** — *"memory is a control surface for reasoning"*; frames things as surfaces you operate on. +- **"contract"** — *"UI contract," "protocol boundary," "stable protocol boundary."* +- **"posture"** — *"the right production posture is fixed by default,"* *"shift from a posture of sprinting to long distance."* +- **"the part that matters"** / **"the part I care about most"** — sets up the operative paragraph after framing. +- **"That is the real win."** / **"That is what makes this interesting."** — bookends an argument. +- **"in practice,"** — pivots from theory to ship-ready advice. +- **"this is the sequence I would use"** / **"if I were building X today"** — prefaces numbered recommendations. +- **"For me, …"** — flags an opinion as personal rather than universal. +- **"boring"** as a virtue — *"Keep the happy path boring."* +- **"fall back deterministically"** / **"deterministic fallback"** — recurring operational frame. +- **"first-class"** — *"interaction data as first-class infrastructure."* +- **"long-horizon"**, **"multi-session"**, **"long tail"** — preferred over "complex" or "edge cases." +- **"survives contact with production"** — his test for whether an idea is worth shipping. +- **"negotiating with entropy"** — example of his deadpan turn of phrase used to land a rule. + +### Avoids + +- **Superlatives**: no "amazing," "powerful," "revolutionary," "game-changing," "incredible," "blazing fast." +- **Emoji**: zero emoji in the 2026 technical corpus. (One stray "😆" appears in a 2024 personal post — do not carry that over to technical writing.) +- **Exclamation marks** in technical posts. +- **Marketing CTAs**: no "sign up," "follow," "subscribe," "DM me," "let me know in the comments." +- **Hedges and filler**: no "in this article we will explore," "without further ado," "let's dive in," "buckle up." +- **Vague intensifiers**: no "really," "very" (almost never), "super," "incredibly" (rare; appears in the personal 2024 posts but not in the 2026 technical voice). +- **Rhetorical questions used as transitions**: he does ask questions, but they are real ones the post then answers, not filler. +- **"As we all know" / "obviously"**: he flags assumed knowledge differently — *"This should be obvious, but it is still worth stating directly."* + +## Recurring themes + framings + +- **The frontend as a first-class part of agent systems.** Recurring frame: the frontend is where intent, correction, and outcome are visible together. *"The backend can train the policy. But the frontend is where the reward signal is born."* +- **Memory as a control surface, not storage.** Not "we saved the conversation somewhere" — an agent capability to store, retrieve, update, summarize, and delete. +- **Generative UI as a spectrum, not a single pattern.** "Choose per surface, not per company." +- **Fixed by default, dynamic for the long tail, deterministic fallback.** This is his default posture for any model-shaped contract. +- **"Treat all agent output as untrusted."** Validation, schema repair loops, capped retries, fallback. +- **Boring infrastructure is the win.** Flat lists, explicit IDs, validatable contracts, immutable raw history behind derived state. +- **Sequencing over silver bullets.** "Push prompt augmentation hard before training an offline reward model." "You can always loosen the system later. Going the other direction is usually painful." + +## Humor + persona + +Deadpan, dry, used sparingly. The humor is in the framing of a bad practice, not in jokes. Examples: + +- *"That would be a fast way to move chaos across a trust boundary."* +- *"Past that point, you are not repairing the schema. You are negotiating with entropy."* +- *"the backend forwards malformed intent and hopes the renderer is charitable."* +- *"older 'generate a giant nested object and pray' approaches."* +- *"That is not a schema strategy. That is giving up."* + +He never breaks character into bro-voice, hype, or self-deprecation in technical posts. The 2024 personal posts ("Year in Review," "Success in the Ordinary") have a warmer, more reflective register — only use that voice when the topic is explicitly business/personal, not technical. + +Relationship to the reader: peer with one more shipped system. Not mentor, not teacher, not promoter. + +## Examples — opening lines + +Verbatim from the corpus, with what makes each work: + +1. *"Memory is becoming one of the most important design surfaces in agentic software."* — Frames the topic as a surface, not a feature. Sets up the contrast pivot that follows. +2. *"Google's A2UI is one of the more interesting protocol ideas in agentic UI right now."* — Specific, present-tense, no hedge. "One of the more" is his preferred mild qualifier. +3. *"I have been thinking about this after reading the rLLM work on post-training language agents."* — Grounds the post in a specific external trigger. Earns the right to opine. +4. *"Generative UI is no longer just 'chat that can answer questions.'"* — Sets up the spectrum framing by negating the lazy version first. +5. *"I set up OpenClaw on macOS with Telegram as my primary channel."* — When the post is operational, the opener is a literal report of what he did. +6. *"Success in western culture is often marked by follower count, headcount, and GitHub stars."* — Personal-essay variant: states the cultural premise so he can question it in line 2. + +## Examples — closing lines + +Verbatim. Note: no CTA, no "thanks for reading," no question. + +1. *"Memory is becoming policy. / And policy is becoming product behavior. / That is what makes this interesting."* — Three-line cadence landing on "interesting." +2. *"That gives you flexibility without surrendering control. / And in agent systems, control is the thing that lets creativity survive production."* — Pairs the tradeoff with the operating principle. +3. *"Start with prompt augmentation. / Push it hard. / Then add offline RL when the data proves you need it."* — Imperative trio. Sequencing as the takeaway. +4. *"1. Start constrained. / 2. Expand with structure. / 3. Embed only where leverage is clear."* — Sometimes the post ends on the numbered list. No prose tail. +5. *"5. Strong boundaries reduce accidental risk while still keeping automation useful."* — Operational posts end on the last numbered takeaway. No summary paragraph. +6. *"Join me in ordinary success."* — Personal-essay variant. Single short imperative. + +## Drafting checklist + +Before publishing any draft written in Brian's voice, verify: + +- [ ] Opens with a blunt thesis sentence. No "Introduction" header. No "In this post we will explore." +- [ ] Within the first 8 lines, there is a "Not because X. And not because Y. It is because Z." pivot (or close variant). Use one. +- [ ] A `## tl;dr` block with 4–6 declarative bullets sits near the top if the post is longer than ~400 words. +- [ ] Paragraphs are 1–3 lines. Single-sentence paragraphs are expected, not avoided. +- [ ] At least one numbered framework (3–4 items) named once and reused as section headers. +- [ ] At least one bolded one-line thesis set off on its own line. +- [ ] At least one section titled some variant of *"The architecture I would actually ship"* or *"My practical recommendations"* — the post must get concrete. +- [ ] First person singular ("I think," "I would"). Opinions flagged as opinions, not universal claims. +- [ ] Reader treated as a peer building the same thing — no "you might be wondering," no "let me explain." +- [ ] Zero emoji. Zero exclamation marks. Zero superlatives ("amazing," "powerful," "incredible"). +- [ ] No contractions in technical posts. +- [ ] Em-dashes used at most once or twice; a period is the default. +- [ ] Closing is two or three short declarative lines, OR the final numbered list. No CTA, no question, no sign-off. +- [ ] Every recommendation is paired with the tradeoff or the cost of getting it wrong. +- [ ] One deadpan turn of phrase per post is welcome (e.g. "negotiating with entropy"). Zero is fine. More than two reads as trying too hard. From 187ef94fb526bc6bc106d98bcbba29112c73241b Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:20:52 -0700 Subject: [PATCH 02/18] docs(marketing): meta-spec for agentic marketing pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Umbrella spec for marketing/ — 5 composable subsystems (assets, channels, agent, cowork, metrics), trigger model, secrets, file conventions, sub-spec sequencing. Supersedes GTM Spec 6 (community-launch) with an ongoing motion. Voice + messaging source-of-truth lives in this repo at docs/gtm/voice.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-17-marketing-meta-design.md | 368 ++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md diff --git a/docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md b/docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md new file mode 100644 index 000000000..689e5d069 --- /dev/null +++ b/docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md @@ -0,0 +1,368 @@ +--- +workstream: marketing-meta +status: approved +owner: brian +phase: 0 +spec: docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md +parent: docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md +supersedes: docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md#spec-6 +--- + +# Marketing Meta (Design) + +> Umbrella spec for the agentic marketing pipeline. Defines the `marketing/` directory, the five composable subsystems inside it, the interfaces between them, the sequencing, and the cross-cutting concerns (secrets, triggers, voice). No implementation lands here — each subsystem gets its own spec. + +## 1. Goal + +Stand up `marketing/` as a composable, agentic pipeline that turns source content (blog posts, releases, manual prompts, schedules) into multi-channel posts (X, LinkedIn, Dev.to, Reddit), with Cowork as the human approval surface and PostHog as the feedback loop. Replaces ad-hoc launches with a continuous motion. + +This spec **supersedes Spec 6 (`community-launch`)** in the GTM meta. The marketing pipeline subsumes one-shot launch artifacts into the ongoing system. A short note in the GTM meta points here. + +## 2. Context + +- Parent: `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md`. The GTM meta defines analytics, ICP, messaging, and the developer/enterprise CTA fork. This spec consumes those decisions; it does not redesign them. +- Cowork is a Claude skill surface (per `cowork/gtm/SKILL.md`), not a SaaS. The `/marketing` skill is a Claude skill that reads/writes structured files in `marketing/cowork/`. +- The PostHog `marketing:*` namespace already exists. This spec extends it with `marketing:social_*` events when the metrics subsystem ships. +- Voice + messaging come from this repo only: `docs/gtm/voice.md`, `docs/gtm/messaging.md`, `docs/gtm/icp.md`. No machine-local paths in any checked-in code. +- Existing repo conventions: + - Scope: `@ngaf/*` for all internal + published packages. Privacy via `"private": true` in `package.json`. + - Specs at `docs/superpowers/specs//YYYY-MM-DD--design.md`. + - Date-prefixed filenames everywhere (blog content, specs, plans). + +## 3. Scope + +**In scope (deliverables of this meta — see §10):** + +- This spec. +- `marketing/README.md` — directory charter. +- Migration: `cowork/` → `marketing/cowork/`. +- `marketing/.env.example` — placeholder env vars for each channel. +- `docs/gtm/voice.md` — Brian's voice + tone reference, synthesized from his blog corpus. +- Skeleton `package.json` for each of the 5 internal packages, marked `"private": true`. Empty `index.ts`. Nx sees the workspace shape. +- A note in `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` pointing at this umbrella as the supersede for Spec 6. + +**Out of scope (each is its own follow-up spec — see §6):** + +- Brand asset rendering implementation. +- Channel adapter implementations (X, LinkedIn, Dev.to, Reddit). +- The drafting agent. +- The `/marketing` Cowork skill body. +- Metrics ingestion code. +- Replacing the existing `marketing:*` analytics events (the existing namespace stays; the metrics spec extends it). + +## 4. Directory layout + +``` +marketing/ +├── README.md # charter + index of sub-specs +├── .env.example # placeholder secrets per channel +├── assets/ # @ngaf/marketing-assets (sub-spec 1) +│ ├── package.json # "private": true +│ └── src/index.ts # empty in this spec +├── channels/ # @ngaf/marketing-channels (sub-spec 2) +│ ├── package.json +│ └── src/index.ts +├── agent/ # @ngaf/marketing-agent (sub-spec 3) +│ ├── package.json +│ └── src/index.ts +├── cowork/ # Claude skills (migrated from /cowork) +│ ├── README.md # manual install instructions (updated path) +│ ├── gtm/ +│ │ └── SKILL.md # existing GTM skill, content unchanged +│ ├── marketing/ +│ │ └── SKILL.md # stub in this spec; body in sub-spec 4 +│ ├── inbox/ # .gitkeep — drafts await review here +│ ├── outbox/ # .gitkeep — approved + posted drafts +│ └── archive/ # .gitkeep — rejected or expired +└── metrics/ # @ngaf/marketing-metrics (sub-spec 5) + ├── package.json + └── src/index.ts +``` + +## 5. Subsystem charters + interfaces + +Each subsystem is independent, composable, and has one clear job. The interfaces below are the contract — implementation can change behind them. + +### 5.1 `@ngaf/marketing-assets` — Brand asset system + +**Charter.** Renders branded social images (1200×630, 1200×1200, 1080×1080) from typed input. Single source of truth for visual tokens, motifs, typography. Built on Next.js `ImageResponse` so it shares runtime with the website's blog OG cards. Each template is a named TSX function under `assets/templates/`. + +**Interface.** + +```ts +import type { Buffer } from 'node:buffer'; + +export interface CardInput { + template: 'x-announcement' | 'blog-og' | 'release' | 'linkedin-square' | string; + title: string; + subtitle?: string; + tag?: string; + author?: { name: string; role?: string }; +} + +export interface RenderedCard { + png: Buffer; + width: number; + height: number; + contentType: 'image/png'; +} + +export function renderCard(input: CardInput): Promise; +``` + +**Reuse.** The website's existing `/blog/[slug]/opengraph-image.tsx` and `/opengraph-image.tsx` migrate to call `renderCard()` once this package ships. Migration is a sub-spec deliverable, not in this meta. + +### 5.2 `@ngaf/marketing-channels` — Channel adapters + +**Charter.** One adapter per channel. Identical interface. Encapsulates auth, post, read metrics. Initial channels: X, LinkedIn, Dev.to, Reddit. Adding channels = adding adapter files. + +**Interface.** + +```ts +export type ChannelId = 'x' | 'linkedin' | 'devto' | 'reddit'; + +export interface Draft { + channel: ChannelId; + text: string; // channel-native (X = 280, LinkedIn = 3000, etc.) + media?: { png: Buffer; alt: string }[]; + threadParts?: string[]; // for X threads — successive tweets + link?: { url: string; previewTitle?: string }; + scheduledAt?: string; // ISO; null = post now +} + +export interface PostResult { + channel: ChannelId; + postId: string; + url: string; + postedAt: string; // ISO +} + +export interface PostMetrics { + postId: string; + impressions?: number; + clicks?: number; + replies?: number; + shares?: number; + fetchedAt: string; +} + +export interface ChannelAdapter { + readonly id: ChannelId; + post(draft: Draft): Promise; + metrics(postId: string): Promise; +} + +export function getAdapter(id: ChannelId): ChannelAdapter; +``` + +**Dry-run.** Every adapter accepts `DRY_RUN=1` env var. In dry-run, `post()` returns a synthetic `PostResult` with `postId: 'dry-'` and writes the draft to `marketing/cowork/outbox/` without hitting any API. Used by CI + safe local development. + +### 5.3 `@ngaf/marketing-agent` — Content-drafting agent + +**Charter.** LangGraph (JS SDK) agent that takes a `Trigger` + source context and emits N channel drafts. Reads `docs/gtm/voice.md`, `messaging.md`, `icp.md` for tone + claims. Writes output to `marketing/cowork/inbox/.json`. + +**Interface.** + +```ts +export type Trigger = + | { kind: 'blog-merge'; slug: string } + | { kind: 'release'; tag: string } + | { kind: 'cowork-prompt'; topic: string; freeform?: string } + | { kind: 'cadence'; window: 'weekly' }; + +export interface DraftBundle { + id: string; // YYYY-MM-DD- + trigger: Trigger; + drafts: Draft[]; // one per target channel + source: { url?: string; title?: string; excerpt?: string }; + createdAt: string; +} + +export function draft(trigger: Trigger): Promise; +``` + +**CLI.** `pnpm marketing draft --trigger= [args]`. Same entry point for git-action and cron invocations. + +### 5.4 `marketing/cowork/marketing/SKILL.md` — Human approval surface + +**Charter.** Claude skill that reads `inbox/.json`, presents drafts in the conversation, lets the user edit/approve/reject. On approval, dispatches to channel adapters and moves the bundle to `outbox/.json` with post results. On reject, moves to `archive/.json`. + +**Interface.** Skill markdown + file conventions (`inbox/`, `outbox/`, `archive/`). The `DraftBundle` JSON shape (defined in 5.3) is the single source-of-truth for what flows through. + +### 5.5 `@ngaf/marketing-metrics` — Metrics ingestion + +**Charter.** Reads `marketing/cowork/outbox/*.json`, calls each adapter's `metrics(postId)`, and emits PostHog events in the `marketing:*` namespace. + +**Interface.** + +```ts +export function run(opts?: { sinceHours?: number }): Promise<{ posts: number; eventsEmitted: number }>; +``` + +**CLI.** `pnpm marketing metrics --since-hours=24`. Cron-invoked daily. Stateless — re-reading the same outbox just re-emits the same events (PostHog's distinct-id-based dedup handles idempotency for impression deltas; this spec doesn't try to be smarter). + +**New analytics events** (added in metrics sub-spec, not here): + +- `marketing:social_impression` — props: `channel`, `post_id`, `count` +- `marketing:social_click` — props: `channel`, `post_id`, `destination_url` +- `marketing:draft_approved` — props: `channel`, `post_id`, `latency_seconds` +- `marketing:draft_rejected` — props: `channel`, `reason` + +## 6. Sub-spec sequencing + +``` +[0] marketing-meta (this) + │ + ▼ + [migrate cowork/ → marketing/cowork/] + │ + ▼ +[1] brand-assets ─────────┐ + ▼ +[2] channel-adapters ─▶ [3] content-agent ─▶ [4] cowork-loop ─▶ [5] metrics-ingest +``` + +| # | Sub-spec | Depends on | Exit | +|--:|----------|------------|------| +| 0 | marketing-meta (this) | — | This spec merged; `cowork/` migrated; voice.md committed; skeleton packages exist | +| 1 | brand-assets | 0 | `@ngaf/marketing-assets` builds; `renderCard()` produces PNGs for 2+ templates; website blog-OG route migrated to call it | +| 2 | channel-adapters | 0 | All 4 adapters implement `ChannelAdapter`; secrets loading works; dry-run post round-trip green for each channel | +| 3 | content-agent | 1, 2 | Agent produces channel-shaped drafts from a blog-merge trigger; output lands in `marketing/cowork/inbox/` | +| 4 | cowork-loop | 3 | `/marketing` skill installed; full draft→approve→post round-trip works end-to-end for one channel (X) | +| 5 | metrics-ingest | 4 | One full post's metrics flow back into PostHog `marketing:social_impression` + `social_click` | + +**Critical path:** 0 → 2 → 3 → 4. Assets (1) is parallel to channels (2). Metrics (5) is a tail. + +**v1 channel cut.** Adapter sub-spec can ship with X first and add the other 3 incrementally — each is a small follow-up commit, not a re-spec. X first matches Brian's existing audience and the most concrete launch target. + +## 7. Cross-cutting concerns + +### 7.1 Secrets + +Channel API tokens live in `.env` at repo root (already gitignored). Each adapter reads its own keys via `process.env`: + +``` +X_API_KEY= +X_API_SECRET= +X_ACCESS_TOKEN= +X_ACCESS_SECRET= +LINKEDIN_ACCESS_TOKEN= +LINKEDIN_AUTHOR_URN= +DEVTO_API_KEY= +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= +REDDIT_USERNAME= +REDDIT_PASSWORD= +``` + +`marketing/.env.example` lists every required key with a one-line comment. For production cron, secrets come from the host (GitHub Actions secrets — exact configuration is in the cowork-loop or metrics sub-spec, not here). + +### 7.2 Triggers + +Three trigger sources, one entry point (`pnpm marketing draft`): + +1. **Source-content merge** — A new blog post or release tag lands on main. GitHub Action runs `pnpm marketing draft --trigger=blog-merge --slug=`. Action file ships in sub-spec 3. +2. **Manual Cowork prompt** — `/marketing draft ` in Claude. The skill writes a small trigger file and invokes the CLI. +3. **Scheduled cadence** — GitHub Actions cron, weekly. `pnpm marketing draft --trigger=cadence --window=weekly`. + +All converge on the same `draft()` entry point in `@ngaf/marketing-agent`. + +### 7.3 File conventions + +``` +marketing/cowork/ + inbox/.json # drafts awaiting review (agent writes, skill reads) + outbox/.json # approved + posted (skill writes, includes PostResult[]) + archive/.json # rejected or expired (skill writes) +``` + +`` is `YYYY-MM-DD-` — date-prefixed, like blog posts. The JSON shape is `DraftBundle` (defined in 5.3). One JSON file per pipeline run, regardless of how many channels it produced drafts for. + +### 7.4 Voice + messaging source-of-truth + +The agent reads three files **in this repo only**: + +- `docs/gtm/voice.md` — Brian's tone, phrasing, structural quirks (NEW in this spec) +- `docs/gtm/messaging.md` — positioning, claims, no-go phrases (existing) +- `docs/gtm/icp.md` — audience (existing) + +Zero references to `~/repos/brianflove/*` or any machine-local path in checked-in code or specs. Reproducible on CI. + +`voice.md` is synthesized once from Brian's blog corpus by a one-shot subagent task and committed to this repo. Subsequent voice tuning happens by editing `voice.md` directly. + +## 8. Architecture + +``` + ┌─────────────┐ trigger ┌─────────────┐ reads ┌──────────────┐ + │ GH Action / │ ───────────▶ │ agent │ ────────▶ │ voice.md │ + │ cron / CLI │ │ (draft()) │ │ messaging.md │ + └─────────────┘ └──────┬──────┘ │ icp.md │ + │ writes └──────────────┘ + ▼ + ┌─────────────┐ + │ inbox/ │ + │ .json │ + └──────┬──────┘ + │ /marketing skill reads + ▼ + ┌─────────────┐ + │ Cowork │ ── user approves / edits / rejects + │ (Claude) │ + └──────┬──────┘ + │ on approve: dispatch adapters + ▼ + ┌───────────────────────┐ + │ channel adapters │ + │ X · LI · Dev · Rdt │ + └──────┬────────────────┘ + │ writes + ▼ + ┌─────────────┐ + │ outbox/ │ + │ .json │ + └──────┬──────┘ + │ metrics CLI reads (cron) + ▼ + ┌─────────────┐ + │ PostHog │ + │ marketing:* │ + └─────────────┘ +``` + +Asset rendering hangs off the agent (drafts include an asset spec; the agent calls `renderCard()` and embeds the PNG bytes in `Draft.media`). + +## 9. Risks + non-goals + +| # | Risk | Mitigation | Owner | +|--:|------|------------|-------| +| 1 | Channel APIs change underneath us (esp. X) | One adapter file per channel; each is small enough to rewrite in a day | Sub-spec 2 | +| 2 | Auto-posting goes off the rails | Every dispatch is gated by Cowork approval; no auto-post path in v1 | Sub-spec 4 | +| 3 | Voice file ages out of date | One-line drafting checklist at bottom of `voice.md`; revisit quarterly | This spec (subagent) | +| 4 | Metrics get noisy or wrong | Single namespace + draft_id correlation makes it easy to filter; v1 doesn't try to be smarter than that | Sub-spec 5 | +| 5 | The agent over-produces channel-specific drafts that all sound the same | Channel-specific prompts per adapter; voice.md provides shape, channel adapter prompts provide format | Sub-spec 3 | + +**Non-goals (v1):** + +- Multi-author voice (only Brian). +- Multi-tenant (single Cacheplane). +- Engagement automation (replies, DMs, comment threads). Out of scope and likely permanently. +- A web UI for approvals — Cowork is the UI. +- Analytics dashboards specific to social. Existing developer-funnel + marketing dashboards consume the new events; no new dashboard JSON in v1. + +## 10. Deliverables (of this meta-spec) + +- ☐ This spec at `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +- ☐ `marketing/README.md` — directory charter, links to sub-specs, install/run quickstart +- ☐ Migrate `cowork/` → `marketing/cowork/` (single `git mv` + README path update + `cowork/{inbox,outbox,archive}/.gitkeep`) +- ☐ `marketing/.env.example` — placeholder env var names for each channel +- ☐ `docs/gtm/voice.md` — Brian's voice + tone reference (synthesized by subagent) +- ☐ Skeleton `package.json` + `src/index.ts` for: `@ngaf/marketing-assets`, `@ngaf/marketing-channels`, `@ngaf/marketing-agent`, `@ngaf/marketing-metrics`. All `"private": true`. Each exports an empty interface so Nx + tsconfig paths resolve. +- ☐ Each skeleton package has a `project.json` so `nx graph` sees it. +- ☐ Note appended to `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` pointing at this umbrella as the supersede for Spec 6. + +## 11. References + +- Parent: `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` +- Existing Cowork skill: `cowork/gtm/SKILL.md` (to be moved) +- Existing analytics: `apps/website/src/lib/analytics/events.ts`, `docs/gtm/taxonomy.md` +- Blog seed post (first content this pipeline will syndicate): `apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx` From 7218c235649c7c30e83d5f41bbfa5dfb41e6ca27 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:27:56 -0700 Subject: [PATCH 03/18] docs(gtm): rescope voice synthesis to pre-2026 corpus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original synthesis pulled too heavily from the 2026 technical voice (which was itself synthesized for the streaming-chat pillar post). The marketing pipeline wants Brian's broader voice — across technical, opinion, and personal registers. Resynthesized from 2012-2024 corpus. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/gtm/voice.md | 200 ++++++++++++++++++++++------------------------ 1 file changed, 96 insertions(+), 104 deletions(-) diff --git a/docs/gtm/voice.md b/docs/gtm/voice.md index 7496a3752..08ce12529 100644 --- a/docs/gtm/voice.md +++ b/docs/gtm/voice.md @@ -1,146 +1,138 @@ # Voice: Brian Love -> The canonical voice + tone reference for any post drafted on Brian's behalf — blog, social, threads, replies. Synthesized from his existing blog at the time of writing (corpus: `~/repos/brianflove/src/content/posts/`, with primary weight on the 2026 frontend + agentic series). +> The canonical voice + tone reference for any post drafted on Brian's behalf — blog, social, threads, replies. Synthesized from his pre-2026 blog corpus (`~/repos/brianflove/src/content/posts/`, 2012–2024, ~141 posts). The 2026 technical voice is intentionally excluded — that register was tuned for the streaming-chat pillar post and is narrower than how Brian actually writes. ## tl;dr -- One thought per line. Most paragraphs are 1–3 short sentences, often a single sentence. -- Opens with a blunt thesis sentence, not an "Introduction" header. The title is the only preamble. -- Heavy use of the **"Not because X. And not because Y. It is Z."** contrast pivot to set up a thesis. -- A `## tl;dr` block of 4–6 bullets sits near the top of long posts. Bullets are declarative, not promotional. -- Vocabulary is plain and operational ("control surface," "contract," "fallback," "the part that matters"). No superlatives, no emoji, no exclamation marks, no marketing hype. -- First-person singular ("I think," "I would ship," "the architecture I would actually ship"). Reader addressed as a peer building the same thing — never "you should be excited." +- One thought per line. Most paragraphs are 1–3 short sentences. Hard returns inside a paragraph are normal. +- Openings restate the title in a single sentence, then jump into the work. No "Introduction" header, no "in this article we'll explore." +- Contractions are normal ("it's," "don't," "let's"). Brian writes the way a senior engineer talks at a meetup. +- Warm, low-stakes humor — "freakin' cool," "giddy up and get onboard," the occasional emoji (🤔 💚 😆 👍). Not bro-voice. Closer to "fun coworker." +- Reader addressed directly ("Let's look at," "I suggest you check it out"); opinions flagged as opinions ("For me," "In my opinion," "what I think"). +- A small set of recurring values across 12 years: ship the boring thing, do it well, have fun, name the people you're thankful to. ## Core stance -Brian writes as a working engineer talking to other working engineers who are about to ship something. He is not an evangelist and not a teacher in the lecturing sense. He is the person who already built one of these and is telling you which parts will hurt. +Brian writes as a working engineer and small-business owner who has shipped a lot and is generous about what he learned. He is not an evangelist and not a guru. He is the friend at the meetup who already tried it. -The job of the writing is to compress an architectural opinion into something you can act on this week. He frames decisions as tradeoffs, not best practices. He is willing to recommend ("this is the sequence I would use"), but he labels the recommendation as a personal call, not a universal rule. +The job is to compress what he learned into something you can use this week. He frames decisions as tradeoffs and personal calls — recommends, but labels the recommendation as his own ("what I think," "for me"). -He is not trying to entertain, and he is not trying to sell. There is no closing CTA, no "follow me for more," no narrative arc. Posts end the moment the argument is finished. +He doesn't perform expertise. He's willing to be a beginner in public (*"I have spent one day (so far) learning expressjs"*), to be corrected (*"Please share any other antipatterns that you have learned from!"*), and to be earnest (*"Bootstrapping a startup is *hard*"*). + +## Registers + +Brian writes in three distinct registers. Pick one and stay in it. + +**Technical tutorial** (bulk of corpus, 2012–2020). Walks through a specific task. Numbered Goals, heavy "Let's," H2s as questions, explicit `## Conclusion`. Friendly, practical, code-heavy. For "here is how to do X." + +**Opinion / pattern post** (2017–2020). Antipatterns and recommendations, but softer than the 2026 register. Still contracted, still hedged with "I think," closes with an invitation. For "here is what I learned by getting it wrong." + +**Personal / business essay** (2019, 2024). Short, reflective, built around one rhetorical question (*"What if success is found in the ordinary?"*). One-sentence paragraphs. Italicized intensifiers. Closes on an invitation (*"Join me in ordinary success."*). For how he thinks, lives, or works. ## Sentence-level patterns -- **Rhythm: short, declarative, often paired.** Two short sentences sit next to each other to set up a contrast. Example: *"A stateless LLM can answer. / A system with agentic memory can improve."* -- **One sentence per line in body prose.** Hard returns inside paragraphs are the norm, not the exception. Example block from "Agentic Memory": *"Summaries drift. / Compression loses nuance. / Derived memory can get subtly wrong over time."* -- **Punctuation is plain.** Periods, colons, and bulleted lists do almost all the work. Semicolons are rare. Exclamation marks are absent in the 2026 corpus. -- **Em-dash usage is restrained.** He prefers a period over an em-dash. When em-dashes appear they are used sparingly for parenthetical asides ("You can (and often should) use all three in one product.") — never for dramatic pauses. -- **Colons introduce lists and definitions.** Example: *"That separation matters because a valid overlay does not guarantee a valid payload."* Then a colon-led list. -- **Paragraph breaks are aggressive.** A "paragraph" is often one sentence on its own line, e.g. *"That is the real shift."* / *"This is the part I care about most."* -- **Bold is reserved for thesis claims.** Example: *"**Memory is not storage. It is a control surface for reasoning.**"* Usually appears once or twice per post, set off on its own line. -- **No contractions in the technical posts.** "It is" not "it's." "Do not" not "don't." (The 2024 personal posts loosen this — "I don't believe…" — but the 2026 technical voice is uncontracted.) +- **One sentence per line.** From "Year in Review": *"In 2023, I began development of Polaris in earnest. / This was my first step into bootstrapping a startup."* +- **Short, direct, most under 20 words.** *"I like ordinary. / Ordinary is welcoming, accepting, inclusive, diverse, unapologetic, and authentic."* +- **Contractions default.** *"Let's dive into the core concepts,"* *"You're finally getting the opportunity to build something."* +- **Italics for single-word emphasis.** *"Bootstrapping a startup is *hard*,"* *"build for mobile and *then* respond to users."* Rarely bold. +- **Bold reserved for must-do callouts.** *"This is a **must** for smaller screen devices."* +- **Em-dashes sparingly for asides.** He more often reaches for a period. +- **Exclamation marks allowed and warm.** *"Because it's freakin' cool!"* / *"Thank you RxJs!"* One or two per post; never stacked. +- **Rhetorical questions as section headers, answered immediately.** *"## What is it and why bother? / Simply put, LESS is a dynamic stylesheet language…"* +- **Inline parentheticals as asides.** *"(and of course follow me on Twitter – if that's your thing)."* +- **Hedges flagged honestly.** *"In my opinion,"* *"From my experience,"* *"For me, …"* ## Structural moves -- **Opening: blunt thesis as the first sentence.** No throat-clearing. - Example: *"Memory is becoming one of the most important design surfaces in agentic software."* - Example: *"Google's A2UI is one of the more interesting protocol ideas in agentic UI right now."* -- **"Not because / And not because / It is because" pivot, immediately after the thesis.** - Verbatim from "Agentic Memory": - > Not because models suddenly became databases. - > And not because storing more transcripts is the same thing as making a system smarter. - > It matters because memory changes what kind of software we are building. - This move is a signature. Use it. -- **`## tl;dr` block, 4–6 bullets, near the top.** Bullets are full sentences ending in periods, framed as claims not features. -- **Numbered framework of 3–4 items, named once and reused as section headers.** Example: "1. Chat Components / 2. Component Systems / 3. Embedded Generative UI" appears at the top, then each becomes an H2. -- **"The architecture I would actually ship"** is a recurring section title. Variants: *"The dynamic pattern I recommend," "My practical recommendations," "Recommended rollout."* The post earns its ending by getting concrete. -- **Bolded one-line thesis, set off as its own paragraph.** Example: *"**The problem is no longer remembering more. It is remembering the right abstractions.**"* -- **"That is X."** as a one-line callback. *"That is the real shift."* / *"That is reflection."* / *"That is the architecture."* He uses this to land a section. -- **Closing: two or three short declarative lines.** No CTA, no sign-off, no question. - Example (Agentic Memory): *"Memory is becoming policy. / And policy is becoming product behavior. / That is what makes this interesting."* +- **Opening: title restated as the lede.** *"Learn the basics of implementing the Redux pattern in Angular applications."* +- **`## Goals` block, 3–5 bullets, near the top.** The last bullet is sometimes literally *"Have fun!"* +- **Series cross-link block** with the current post marked: *"1. NgRX: The Basics (this post) / 2. NgRX: Getting Started…"* +- **"Let's" as workhorse transition.** *"Let's break each of these down," "Let's look at an example," "Let's quickly review."* 3–10 times in a long tutorial. **Signature move.** +- **H2-as-question, body-as-answer.** *"## What are Operators? / ## How are they composable? / ## How do I import?"* +- **Three-bullet spine, each bullet becoming an H3.** Personal essays: *"- Building a product / - Listening to developers / - Consulting."* +- **"Anything Else?" near the close.** *"Did I miss something? Is there something on your top 5 list that I did not mention?"* +- **Explicit `## Conclusion`.** Restates the takeaway in one paragraph. +- **Closing invitation, not CTA.** *"Please share any other antipatterns that you have learned from!"* / *"Join me in ordinary success."* ## Vocabulary ### Reaches for -- **"control surface"** — *"memory is a control surface for reasoning"*; frames things as surfaces you operate on. -- **"contract"** — *"UI contract," "protocol boundary," "stable protocol boundary."* -- **"posture"** — *"the right production posture is fixed by default,"* *"shift from a posture of sprinting to long distance."* -- **"the part that matters"** / **"the part I care about most"** — sets up the operative paragraph after framing. -- **"That is the real win."** / **"That is what makes this interesting."** — bookends an argument. -- **"in practice,"** — pivots from theory to ship-ready advice. -- **"this is the sequence I would use"** / **"if I were building X today"** — prefaces numbered recommendations. -- **"For me, …"** — flags an opinion as personal rather than universal. -- **"boring"** as a virtue — *"Keep the happy path boring."* -- **"fall back deterministically"** / **"deterministic fallback"** — recurring operational frame. -- **"first-class"** — *"interaction data as first-class infrastructure."* -- **"long-horizon"**, **"multi-session"**, **"long tail"** — preferred over "complex" or "edge cases." -- **"survives contact with production"** — his test for whether an idea is worth shipping. -- **"negotiating with entropy"** — example of his deadpan turn of phrase used to land a rule. +- **"Let's"** — workhorse. *"Let's dive in," "Let's look at," "Let's review."* +- **"Simply" / "Simple enough" / "Really easy"** — genuine, not condescending. *"That's it. :)"* +- **"For me" / "In my opinion" / "From my experience"** — opinion flags. +- **"Have fun"** — recurring imperative; often the literal last Goals bullet. +- **"Giddy up and get onboard" / "freakin' cool"** — affectionate intensifiers for genuine excitement. +- **"Posture" / "long distance" / "infinite game"** — framing for sustainable work (2024). +- **"Thankful" / "grateful"** — names people: co-founder Mike, Kimberly in the comments, Jon Rista for help on a talk. +- **"P.S." / "haha"** — parenthetical warmth. +- **"In conclusion" / "In summary"** — he uses these. Don't avoid them. ### Avoids -- **Superlatives**: no "amazing," "powerful," "revolutionary," "game-changing," "incredible," "blazing fast." -- **Emoji**: zero emoji in the 2026 technical corpus. (One stray "😆" appears in a 2024 personal post — do not carry that over to technical writing.) -- **Exclamation marks** in technical posts. -- **Marketing CTAs**: no "sign up," "follow," "subscribe," "DM me," "let me know in the comments." -- **Hedges and filler**: no "in this article we will explore," "without further ado," "let's dive in," "buckle up." -- **Vague intensifiers**: no "really," "very" (almost never), "super," "incredibly" (rare; appears in the personal 2024 posts but not in the 2026 technical voice). -- **Rhetorical questions used as transitions**: he does ask questions, but they are real ones the post then answers, not filler. -- **"As we all know" / "obviously"**: he flags assumed knowledge differently — *"This should be obvious, but it is still worth stating directly."* +- **Bro-voice and hype**: no "blazing fast," "game-changing," "revolutionary." For intensity he uses italics or "freakin' cool." +- **Marketing CTAs**: no "subscribe," "sign up," "DM me." Warm "let me know in the comments" is allowed. +- **Filler "let's dive in"**: when he writes it, the diving happens immediately. +- **Pretending to know more than he does.** *"I have spent one day (so far) learning…"* is the move. +- **Lecturing.** No "obviously," no "as we all know." ## Recurring themes + framings -- **The frontend as a first-class part of agent systems.** Recurring frame: the frontend is where intent, correction, and outcome are visible together. *"The backend can train the policy. But the frontend is where the reward signal is born."* -- **Memory as a control surface, not storage.** Not "we saved the conversation somewhere" — an agent capability to store, retrieve, update, summarize, and delete. -- **Generative UI as a spectrum, not a single pattern.** "Choose per surface, not per company." -- **Fixed by default, dynamic for the long tail, deterministic fallback.** This is his default posture for any model-shaped contract. -- **"Treat all agent output as untrusted."** Validation, schema repair loops, capped retries, fallback. -- **Boring infrastructure is the win.** Flat lists, explicit IDs, validatable contracts, immutable raw history behind derived state. -- **Sequencing over silver bullets.** "Push prompt augmentation hard before training an offline reward model." "You can always loosen the system later. Going the other direction is usually painful." +- **Ship the boring thing well.** Apache configs, https setup, font sizing, presentation contrast — treated with the same care as NgRx. +- **Reader is a peer figuring it out.** Tutorials assume curiosity, not ignorance. +- **Personal disclosure as credibility.** *"I invested approximately $100k into Polaris."* *"When I first started giving presentation to a local meetup in Syracuse NY I really wanted to impress the group."* +- **Community and gratitude.** Named thanks to co-founders, advisors, commenters. +- **Fun as a virtue.** "Have fun!" appears as a literal Goals bullet, the 5th presentation tip, and the spine of the personal essays. +- **Faith, family, fitness, prayer** — plain in 2024 ("physical, mental, and spiritual health," "mindfulness, and prayer"). Part of the persona. +- **Bootstrapping and the infinite game.** Long distance over sprint. ## Humor + persona -Deadpan, dry, used sparingly. The humor is in the framing of a bad practice, not in jokes. Examples: +Warm, slightly goofy, occasionally folksy. Humor lives in the asides: -- *"That would be a fast way to move chaos across a trust boundary."* -- *"Past that point, you are not repairing the schema. You are negotiating with entropy."* -- *"the backend forwards malformed intent and hopes the renderer is charitable."* -- *"older 'generate a giant nested object and pray' approaches."* -- *"That is not a schema strategy. That is giving up."* +- *"Because it's freakin' cool!"* +- *"giddy up and get onboard."* +- *"(why not just use an iPhone? Silly kids.)"* — inline CSS-comment aside. +- *"Did I mention that boostrapping a startup is hard?"* — deadpan self-callback (typo and all; he doesn't over-polish). +- *"These are just two antipatterns that I have observed (haha)."* +- *"As the saying goes: 'Developers, developers, developers.' 😆"* -He never breaks character into bro-voice, hype, or self-deprecation in technical posts. The 2024 personal posts ("Year in Review," "Success in the Ordinary") have a warmer, more reflective register — only use that voice when the topic is explicitly business/personal, not technical. +**Emoji** appear 2018–2024, sparingly, one or two per post. Small repertoire: 💚 💙 🔥 🤔 🏖 👍 😲 😆. Color a sentence, never substitute for one. Don't stack. Don't use in business-essay register. -Relationship to the reader: peer with one more shipped system. Not mentor, not teacher, not promoter. +**Relationship to reader**: warm peer. The engineer who saved you a seat at the meetup. ## Examples — opening lines -Verbatim from the corpus, with what makes each work: - -1. *"Memory is becoming one of the most important design surfaces in agentic software."* — Frames the topic as a surface, not a feature. Sets up the contrast pivot that follows. -2. *"Google's A2UI is one of the more interesting protocol ideas in agentic UI right now."* — Specific, present-tense, no hedge. "One of the more" is his preferred mild qualifier. -3. *"I have been thinking about this after reading the rLLM work on post-training language agents."* — Grounds the post in a specific external trigger. Earns the right to opine. -4. *"Generative UI is no longer just 'chat that can answer questions.'"* — Sets up the spectrum framing by negating the lazy version first. -5. *"I set up OpenClaw on macOS with Telegram as my primary channel."* — When the post is operational, the opener is a literal report of what he did. -6. *"Success in western culture is often marked by follower count, headcount, and GitHub stars."* — Personal-essay variant: states the cultural premise so he can question it in line 2. +1. *"It's been on my list for awhile, and I was finally able to learn more about the LESS language."* (2012) — Personal lede; earns the right to teach by saying "I just learned this too." +2. *"I have spent one day (so far) learning expressjs -- a node.js framework for a web application server."* (2014) — Disclaimer as credibility. +3. *"Learn the basics of implementing the Redux pattern in Angular applications."* (2018) — Title restated as imperative. Standard tutorial opener. +4. *"Angular 9 provides improved debugging in the console using `window.ng`."* (2019) — Direct factual claim. No hedging. +5. *"Learn the top 5 tips for creating impactful presentations to ensure inspiration and not exhaustion."* (2019) — Promise paired with contrast: the "X and not Y" opinion opener. +6. *"I want to briefly look back in order to focus on looking forward."* (2024) — Essay opener: one sentence of intent. +7. *"Success in western culture is often marked by follower count, headcount, and GitHub stars. / What if success is found in the ordinary?"* (2024) — Cultural premise stated, then questioned. Signature essay move. ## Examples — closing lines -Verbatim. Note: no CTA, no "thanks for reading," no question. - -1. *"Memory is becoming policy. / And policy is becoming product behavior. / That is what makes this interesting."* — Three-line cadence landing on "interesting." -2. *"That gives you flexibility without surrendering control. / And in agent systems, control is the thing that lets creativity survive production."* — Pairs the tradeoff with the operating principle. -3. *"Start with prompt augmentation. / Push it hard. / Then add offline RL when the data proves you need it."* — Imperative trio. Sequencing as the takeaway. -4. *"1. Start constrained. / 2. Expand with structure. / 3. Embed only where leverage is clear."* — Sometimes the post ends on the numbered list. No prose tail. -5. *"5. Strong boundaries reduce accidental risk while still keeping automation useful."* — Operational posts end on the last numbered takeaway. No summary paragraph. -6. *"Join me in ordinary success."* — Personal-essay variant. Single short imperative. +1. *"I hope this very simply example of using expressjs shows both the simplicity and the power of using expressjs with node.js."* (2014) — Tutorial closer. +2. *"Did I miss something? Is there something on your top 5 list for 2015 website design that I did not mention?"* (2015) — Hands the conversation back. +3. *"These are just two antipatterns that I have observed (haha). / Please share any other antipatterns that you have learned from!"* (2017) — Invitation, not CTA; "(haha)" softens the authority. +4. *"In the next post in this series we'll implement the Redux pattern using NgRx into an existing project."* (2018) — Series closer: forward link, not sign-off. +5. *"If you're going to put yourself out there and stand up in front of a group of peers, don't do it with fear and trembling. Do it with fun."* (2019) — Essay imperative. +6. *"Personally, I want to challenge myself with physical exercise, mindfulness, and prayer."* (2024) — Closes a "Technically / Economically / Personally" trio with the most personal item. +7. *"Join me in ordinary success."* (2024) — Shortest possible closer. ## Drafting checklist -Before publishing any draft written in Brian's voice, verify: - -- [ ] Opens with a blunt thesis sentence. No "Introduction" header. No "In this post we will explore." -- [ ] Within the first 8 lines, there is a "Not because X. And not because Y. It is because Z." pivot (or close variant). Use one. -- [ ] A `## tl;dr` block with 4–6 declarative bullets sits near the top if the post is longer than ~400 words. -- [ ] Paragraphs are 1–3 lines. Single-sentence paragraphs are expected, not avoided. -- [ ] At least one numbered framework (3–4 items) named once and reused as section headers. -- [ ] At least one bolded one-line thesis set off on its own line. -- [ ] At least one section titled some variant of *"The architecture I would actually ship"* or *"My practical recommendations"* — the post must get concrete. -- [ ] First person singular ("I think," "I would"). Opinions flagged as opinions, not universal claims. -- [ ] Reader treated as a peer building the same thing — no "you might be wondering," no "let me explain." -- [ ] Zero emoji. Zero exclamation marks. Zero superlatives ("amazing," "powerful," "incredible"). -- [ ] No contractions in technical posts. -- [ ] Em-dashes used at most once or twice; a period is the default. -- [ ] Closing is two or three short declarative lines, OR the final numbered list. No CTA, no question, no sign-off. -- [ ] Every recommendation is paired with the tradeoff or the cost of getting it wrong. -- [ ] One deadpan turn of phrase per post is welcome (e.g. "negotiating with entropy"). Zero is fine. More than two reads as trying too hard. +- [ ] Opens with one sentence restating the title or stating intent. No "Introduction" header. +- [ ] Contractions present and natural. If absent, draft is in the wrong (2026) register. +- [ ] Paragraphs 1–3 lines; one sentence per line is fine. +- [ ] Pick a register: `## Goals` (tutorial), three-bullet spine (essay), or H2-as-question (concept). Use its scaffolding. +- [ ] At least one "Let's" transition per major section. +- [ ] Italics for single-word emphasis; bold reserved for one must-do callout. +- [ ] Opinions flagged ("I think," "For me," "From my experience"). +- [ ] At least one moment of warmth: an aside, "(haha)," "freakin' cool," a genuine "Thank you!", or a small emoji. +- [ ] No hype: no "blazing," "revolutionary," "game-changing," "10x." +- [ ] No marketing CTAs. Close is an invitation, forward link, or short imperative. +- [ ] Tutorial posts have `## Conclusion`. Essay posts end on a single imperative or invitation. +- [ ] Every recommendation paired with a cost or tradeoff; at least one personal disclosure earns the right to recommend. +- [ ] Reader is a peer. No "obviously," no "as we all know." From 3b0799ae23879a0dc520c41cfb6fae53d1d09453 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:46:56 -0700 Subject: [PATCH 04/18] docs(marketing): plan for meta-spec implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 15 tasks: cowork/ → marketing/cowork/ move, skill stub, 4 skeleton Nx packages (@ngaf/marketing-{assets,channels,agent,metrics}, all private), workspace registration, tsconfig paths, README, .env.example, GTM Spec 6 supersede note, PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../marketing/2026-05-17-marketing-meta.md | 1087 +++++++++++++++++ 1 file changed, 1087 insertions(+) create mode 100644 docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md diff --git a/docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md b/docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md new file mode 100644 index 000000000..c7b3918a5 --- /dev/null +++ b/docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md @@ -0,0 +1,1087 @@ +# Marketing Meta Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Land the structural deliverables of the marketing-meta spec — `marketing/` directory, migrated Cowork home, four skeleton internal packages, voice doc already committed, and a supersede note in the GTM meta. No implementation logic in any subsystem. + +**Architecture:** `marketing/` is a new top-level directory with five sub-trees: `assets/`, `channels/`, `agent/`, `cowork/` (Claude skills + draft inboxes), `metrics/`. Four of them are `@ngaf/*` Nx workspace packages marked `"private": true` and resolved via `tsconfig.base.json` paths. `cowork/` is migrated wholesale from the repo root with one `git mv` and a README path update. + +**Tech Stack:** Nx workspace packages (`@nx/js:tsc` executor), npm workspaces, TypeScript 5.x. No runtime dependencies introduced — skeletons export empty stubs. + +**Spec reference:** `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md`. Branch: `marketing-meta` (already created in worktree; commits `4ccf38ef`, `187ef94f`, `7218c235` already on it). + +--- + +## File Structure + +**Move (single `git mv`):** + +- `cowork/` → `marketing/cowork/` (preserves history) + +**New:** + +- `marketing/README.md` +- `marketing/.env.example` +- `marketing/cowork/inbox/.gitkeep` +- `marketing/cowork/outbox/.gitkeep` +- `marketing/cowork/archive/.gitkeep` +- `marketing/cowork/marketing/SKILL.md` (stub — body lands in sub-spec 4) +- `marketing/assets/package.json` +- `marketing/assets/project.json` +- `marketing/assets/tsconfig.json` +- `marketing/assets/tsconfig.lib.json` +- `marketing/assets/src/index.ts` +- `marketing/channels/package.json` +- `marketing/channels/project.json` +- `marketing/channels/tsconfig.json` +- `marketing/channels/tsconfig.lib.json` +- `marketing/channels/src/index.ts` +- `marketing/agent/package.json` +- `marketing/agent/project.json` +- `marketing/agent/tsconfig.json` +- `marketing/agent/tsconfig.lib.json` +- `marketing/agent/src/index.ts` +- `marketing/metrics/package.json` +- `marketing/metrics/project.json` +- `marketing/metrics/tsconfig.json` +- `marketing/metrics/tsconfig.lib.json` +- `marketing/metrics/src/index.ts` + +**Modified:** + +- `marketing/cowork/README.md` — install path notes (post-move it references the new location). +- `package.json` (root) — add `marketing/*` to `workspaces`. +- `tsconfig.base.json` — add paths for the four new packages. +- `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` — append supersede note for Spec 6. + +--- + +## Task 1: Move `cowork/` → `marketing/cowork/` + +**Files:** + +- Move: `cowork/` → `marketing/cowork/` + +- [ ] **Step 1: Create the marketing root** + +```bash +cd /Users/blove/repos/angular-agent-framework/.claude/worktrees/gtm+cockpit-instrumentation +mkdir -p marketing +``` + +- [ ] **Step 2: Move with git to preserve history** + +```bash +git mv cowork marketing/cowork +git status --short +``` + +Expected: `R cowork/README.md -> marketing/cowork/README.md` and `R cowork/gtm/SKILL.md -> marketing/cowork/gtm/SKILL.md`. + +- [ ] **Step 3: Verify history is preserved** + +```bash +git log --follow --oneline marketing/cowork/gtm/SKILL.md | head -3 +``` + +Expected: at least one historical commit shown (not just the rename). + +- [ ] **Step 4: Commit the move** + +```bash +git add -A +git commit -m "refactor: move cowork/ to marketing/cowork/ + +Cowork becomes a subsystem of the marketing umbrella. The /gtm skill +content is unchanged; only its path moves. Install instructions in the +README will be updated in the next commit." +``` + +--- + +## Task 2: Update `marketing/cowork/README.md` install paths + +**Files:** + +- Modify: `marketing/cowork/README.md` + +- [ ] **Step 1: Read the current README** + +Run: `cat marketing/cowork/README.md` — note the four install snippets (lines that reference `cowork/gtm/SKILL.md`). + +- [ ] **Step 2: Update path references** + +Edit every occurrence of `cowork/gtm/SKILL.md` → `marketing/cowork/gtm/SKILL.md` in the README. The README has install snippets like: + +```bash +cp cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md +``` + +Replace with: + +```bash +cp marketing/cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md +``` + +Also update the "What's in this directory" ASCII tree to show the new layout: + +``` +marketing/cowork/ +├── README.md # This file. +├── gtm/ +│ └── SKILL.md # The GTM Cowork skill. +├── marketing/ +│ └── SKILL.md # The marketing pipeline Cowork skill (stub; body in sub-spec 4). +├── inbox/ # Drafts awaiting review. +├── outbox/ # Approved + posted drafts. +└── archive/ # Rejected or expired drafts. +``` + +- [ ] **Step 3: Commit** + +```bash +git add marketing/cowork/README.md +git commit -m "docs(marketing/cowork): update install paths after move" +``` + +--- + +## Task 3: Add draft inbox/outbox/archive directories + +**Files:** + +- Create: `marketing/cowork/inbox/.gitkeep` +- Create: `marketing/cowork/outbox/.gitkeep` +- Create: `marketing/cowork/archive/.gitkeep` + +- [ ] **Step 1: Create the dirs and gitkeeps** + +```bash +mkdir -p marketing/cowork/inbox marketing/cowork/outbox marketing/cowork/archive +touch marketing/cowork/inbox/.gitkeep marketing/cowork/outbox/.gitkeep marketing/cowork/archive/.gitkeep +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/cowork/inbox/.gitkeep marketing/cowork/outbox/.gitkeep marketing/cowork/archive/.gitkeep +git commit -m "feat(marketing/cowork): scaffold inbox/outbox/archive dirs" +``` + +--- + +## Task 4: Add `/marketing` Cowork skill stub + +**Files:** + +- Create: `marketing/cowork/marketing/SKILL.md` + +- [ ] **Step 1: Create the stub skill** + +```bash +mkdir -p marketing/cowork/marketing +``` + +Then write `marketing/cowork/marketing/SKILL.md`: + +```markdown +--- +name: marketing +description: | + Cacheplane marketing pipeline operator. Reads `marketing/cowork/inbox/*.json` + draft bundles, presents them for review in conversation, supports + edit/approve/reject decisions, and dispatches approved drafts to channel + adapters. Invoke when "drafts are waiting" or when the user wants to + produce a thread/post for X, LinkedIn, Dev.to, or Reddit. +status: stub +--- + +# Marketing Cowork skill — STUB + +Implementation lands in the cowork-loop sub-spec +(`docs/superpowers/specs/marketing/-cowork-loop-design.md`). + +This file exists so the directory shape and skill name are reserved. +Do NOT invoke this skill until the cowork-loop sub-spec is merged. + +## Expected file conventions (preview) + +- `marketing/cowork/inbox/.json` — drafts awaiting review (agent writes) +- `marketing/cowork/outbox/.json` — approved + posted (skill writes) +- `marketing/cowork/archive/.json` — rejected or expired (skill writes) + +`` is `YYYY-MM-DD-`. + +## Expected DraftBundle shape (preview) + +See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` §5.3. +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/cowork/marketing/SKILL.md +git commit -m "feat(marketing/cowork): add /marketing skill stub" +``` + +--- + +## Task 5: Add `marketing/*` to npm workspaces + +**Files:** + +- Modify: `package.json` (root) + +- [ ] **Step 1: Edit workspaces array** + +In root `package.json`, change the `workspaces` array from: + +```json + "workspaces": [ + "packages/*", + "apps/*", + "libs/*" + ], +``` + +to: + +```json + "workspaces": [ + "packages/*", + "apps/*", + "libs/*", + "marketing/assets", + "marketing/channels", + "marketing/agent", + "marketing/metrics" + ], +``` + +(Listing them explicitly rather than `marketing/*` because `marketing/cowork`, `marketing/cowork/inbox`, etc. are NOT packages — they're skill markdown + JSON inbox dirs.) + +- [ ] **Step 2: Commit (install runs in Task 11)** + +```bash +git add package.json +git commit -m "chore: register marketing/* packages in npm workspaces" +``` + +--- + +## Task 6: Scaffold `@ngaf/marketing-assets` skeleton + +**Files:** + +- Create: `marketing/assets/package.json` +- Create: `marketing/assets/project.json` +- Create: `marketing/assets/tsconfig.json` +- Create: `marketing/assets/tsconfig.lib.json` +- Create: `marketing/assets/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-assets", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/assets" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-assets", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/assets/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/assets"], + "options": { + "outputPath": "dist/marketing/assets", + "main": "marketing/assets/src/index.ts", + "tsConfig": "marketing/assets/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-assets — Brand asset rendering for the marketing pipeline. +// Skeleton only. Implementation lands in the brand-assets sub-spec. + +export interface CardInput { + template: string; + title: string; + subtitle?: string; + tag?: string; + author?: { name: string; role?: string }; +} + +export interface RenderedCard { + png: Buffer; + width: number; + height: number; + contentType: 'image/png'; +} + +export function renderCard(_input: CardInput): Promise { + throw new Error( + '@ngaf/marketing-assets: renderCard() not yet implemented. See brand-assets sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/assets/ +git commit -m "feat(marketing/assets): scaffold @ngaf/marketing-assets skeleton" +``` + +--- + +## Task 7: Scaffold `@ngaf/marketing-channels` skeleton + +**Files:** + +- Create: `marketing/channels/package.json` +- Create: `marketing/channels/project.json` +- Create: `marketing/channels/tsconfig.json` +- Create: `marketing/channels/tsconfig.lib.json` +- Create: `marketing/channels/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-channels", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/channels" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-channels", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/channels/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/channels"], + "options": { + "outputPath": "dist/marketing/channels", + "main": "marketing/channels/src/index.ts", + "tsConfig": "marketing/channels/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-channels — Channel adapters for X, LinkedIn, Dev.to, Reddit. +// Skeleton only. Implementations land in the channel-adapters sub-spec. + +export type ChannelId = 'x' | 'linkedin' | 'devto' | 'reddit'; + +export interface Draft { + channel: ChannelId; + text: string; + media?: { png: Buffer; alt: string }[]; + threadParts?: string[]; + link?: { url: string; previewTitle?: string }; + scheduledAt?: string; +} + +export interface PostResult { + channel: ChannelId; + postId: string; + url: string; + postedAt: string; +} + +export interface PostMetrics { + postId: string; + impressions?: number; + clicks?: number; + replies?: number; + shares?: number; + fetchedAt: string; +} + +export interface ChannelAdapter { + readonly id: ChannelId; + post(draft: Draft): Promise; + metrics(postId: string): Promise; +} + +export function getAdapter(_id: ChannelId): ChannelAdapter { + throw new Error( + '@ngaf/marketing-channels: getAdapter() not yet implemented. See channel-adapters sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/channels/ +git commit -m "feat(marketing/channels): scaffold @ngaf/marketing-channels skeleton" +``` + +--- + +## Task 8: Scaffold `@ngaf/marketing-agent` skeleton + +**Files:** + +- Create: `marketing/agent/package.json` +- Create: `marketing/agent/project.json` +- Create: `marketing/agent/tsconfig.json` +- Create: `marketing/agent/tsconfig.lib.json` +- Create: `marketing/agent/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-agent", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/agent" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-agent", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/agent/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/agent"], + "options": { + "outputPath": "dist/marketing/agent", + "main": "marketing/agent/src/index.ts", + "tsConfig": "marketing/agent/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-agent — LangGraph drafting agent for the marketing pipeline. +// Skeleton only. Implementation lands in the content-agent sub-spec. + +import type { Draft } from '@ngaf/marketing-channels'; + +export type Trigger = + | { kind: 'blog-merge'; slug: string } + | { kind: 'release'; tag: string } + | { kind: 'cowork-prompt'; topic: string; freeform?: string } + | { kind: 'cadence'; window: 'weekly' }; + +export interface DraftBundle { + id: string; + trigger: Trigger; + drafts: Draft[]; + source: { url?: string; title?: string; excerpt?: string }; + createdAt: string; +} + +export function draft(_trigger: Trigger): Promise { + throw new Error( + '@ngaf/marketing-agent: draft() not yet implemented. See content-agent sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/agent/ +git commit -m "feat(marketing/agent): scaffold @ngaf/marketing-agent skeleton" +``` + +--- + +## Task 9: Scaffold `@ngaf/marketing-metrics` skeleton + +**Files:** + +- Create: `marketing/metrics/package.json` +- Create: `marketing/metrics/project.json` +- Create: `marketing/metrics/tsconfig.json` +- Create: `marketing/metrics/tsconfig.lib.json` +- Create: `marketing/metrics/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-metrics", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/metrics" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-metrics", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/metrics/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/metrics"], + "options": { + "outputPath": "dist/marketing/metrics", + "main": "marketing/metrics/src/index.ts", + "tsConfig": "marketing/metrics/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-metrics — Metrics ingestion for the marketing pipeline. +// Skeleton only. Implementation lands in the metrics-ingest sub-spec. + +export interface RunOptions { + sinceHours?: number; +} + +export interface RunResult { + posts: number; + eventsEmitted: number; +} + +export function run(_opts?: RunOptions): Promise { + throw new Error( + '@ngaf/marketing-metrics: run() not yet implemented. See metrics-ingest sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/metrics/ +git commit -m "feat(marketing/metrics): scaffold @ngaf/marketing-metrics skeleton" +``` + +--- + +## Task 10: Add path mappings to `tsconfig.base.json` + +**Files:** + +- Modify: `tsconfig.base.json` + +- [ ] **Step 1: Add four paths** + +Find the `"paths"` object in `tsconfig.base.json` (alphabetical with existing `@ngaf/*` entries). Insert these four entries in alphabetical position: + +```json + "@ngaf/marketing-agent": ["marketing/agent/src/index.ts"], + "@ngaf/marketing-assets": ["marketing/assets/src/index.ts"], + "@ngaf/marketing-channels": ["marketing/channels/src/index.ts"], + "@ngaf/marketing-metrics": ["marketing/metrics/src/index.ts"], +``` + +- [ ] **Step 2: Verify Nx sees the new projects** + +```bash +npx nx show projects | grep marketing +``` + +Expected output (order may vary): +``` +marketing-agent +marketing-assets +marketing-channels +marketing-metrics +``` + +- [ ] **Step 3: Verify the agent skeleton can import from channels (cross-package resolution)** + +```bash +npx tsc --noEmit marketing/agent/src/index.ts +``` + +Expected: clean, no errors. (This validates the `import type { Draft } from '@ngaf/marketing-channels';` resolves via the new path mapping.) + +- [ ] **Step 4: Commit** + +```bash +git add tsconfig.base.json +git commit -m "chore(tsconfig): add path mappings for marketing/* packages" +``` + +--- + +## Task 11: Install workspaces + verify build + +**Files:** none (verification only) + +- [ ] **Step 1: Install (surgical, no full lockfile rewrite)** + +```bash +cd /Users/blove/repos/angular-agent-framework/.claude/worktrees/gtm+cockpit-instrumentation +npm install --no-audit --no-fund +``` + +Expected: npm picks up the new `marketing/*` workspaces. No new dependencies are added (skeletons have no `dependencies` blocks). The lockfile may add `marketing/` workspace entries; that's expected. + +**Per project memory ("Don't regenerate package-lock.json on macOS"):** if the install changes any `@next/swc-*` platform binding entries in package-lock.json, revert those specific changes with `git checkout package-lock.json` and try `npm install --package-lock-only` instead. + +- [ ] **Step 2: Verify Nx graph** + +```bash +npx nx graph --file=/tmp/nx-graph.json +node -e "const g = require('/tmp/nx-graph.json'); console.log(Object.keys(g.graph.nodes).filter(n => n.startsWith('marketing-')).sort())" +``` + +Expected: `[ 'marketing-agent', 'marketing-assets', 'marketing-channels', 'marketing-metrics' ]` + +- [ ] **Step 3: Verify each package builds** + +```bash +npx nx run-many --target=build --projects=marketing-assets,marketing-channels,marketing-agent,marketing-metrics +``` + +Expected: 4/4 builds succeed. Each produces a `dist/marketing//index.js` and `index.d.ts`. + +- [ ] **Step 4: Commit lockfile changes if any** + +```bash +git add package-lock.json +git diff --cached --stat +# Only commit if there are real changes +git commit -m "chore: refresh lockfile for marketing/* workspaces" || echo "no lockfile changes" +``` + +--- + +## Task 12: Write `marketing/README.md` + +**Files:** + +- Create: `marketing/README.md` + +- [ ] **Step 1: Create the README** + +```markdown +# marketing/ + +Agentic marketing pipeline. Five composable subsystems that turn source content (blog posts, releases, prompts, schedules) into multi-channel posts (X, LinkedIn, Dev.to, Reddit), with Cowork as the human approval surface and PostHog as the feedback loop. + +## Structure + +``` +marketing/ +├── assets/ # @ngaf/marketing-assets — branded image rendering +├── channels/ # @ngaf/marketing-channels — X, LinkedIn, Dev.to, Reddit adapters +├── agent/ # @ngaf/marketing-agent — LangGraph drafting agent +├── cowork/ # Claude skills (/gtm, /marketing) + inbox/outbox/archive +└── metrics/ # @ngaf/marketing-metrics — feedback ingestion → PostHog +``` + +All four packages are internal (`"private": true`). They are NOT published to npm. + +## Specs + +- Meta (this umbrella): `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +- Sub-specs (when written): + - `brand-assets` — `docs/superpowers/specs/marketing/-brand-assets-design.md` + - `channel-adapters` — `docs/superpowers/specs/marketing/-channel-adapters-design.md` + - `content-agent` — `docs/superpowers/specs/marketing/-content-agent-design.md` + - `cowork-loop` — `docs/superpowers/specs/marketing/-cowork-loop-design.md` + - `metrics-ingest` — `docs/superpowers/specs/marketing/-metrics-ingest-design.md` + +## Voice + messaging source-of-truth + +- `docs/gtm/voice.md` — Brian's tone, phrasing, structural quirks +- `docs/gtm/messaging.md` — positioning, claims, no-go phrases +- `docs/gtm/icp.md` — audience + +All in this repo. No machine-local paths in checked-in code. + +## Status + +This directory was scaffolded by the marketing-meta spec. Subsystems are skeletons. Implementation lands as each sub-spec ships. + +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/README.md +git commit -m "docs(marketing): add directory charter README" +``` + +--- + +## Task 13: Write `marketing/.env.example` + +**Files:** + +- Create: `marketing/.env.example` + +- [ ] **Step 1: Create the file** + +``` +# marketing/.env.example +# +# Placeholder env var names for the marketing pipeline. Real values live in +# .env at the repo root (gitignored). Copy lines you need into .env and fill +# in your credentials. +# +# Sub-specs may add more keys. This file is documentation, not consumed at +# runtime — each adapter reads its own keys via process.env. + +# X / Twitter +X_API_KEY= +X_API_SECRET= +X_ACCESS_TOKEN= +X_ACCESS_SECRET= + +# LinkedIn +LINKEDIN_ACCESS_TOKEN= +LINKEDIN_AUTHOR_URN= + +# Dev.to +DEVTO_API_KEY= + +# Reddit +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= +REDDIT_USERNAME= +REDDIT_PASSWORD= + +# Pipeline behavior +# DRY_RUN=1 # Adapters return synthetic PostResults; nothing is posted. +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/.env.example +git commit -m "docs(marketing): add .env.example with placeholder channel keys" +``` + +--- + +## Task 14: Append supersede note to GTM meta-spec + +**Files:** + +- Modify: `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` + +- [ ] **Step 1: Find the Spec 6 row in the workstream decomposition table** + +Run: `grep -n 'community-launch' docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` + +Expected: at least one line number matching the row `| 6 | community-launch | 3 | ...`. + +- [ ] **Step 2: Add a note immediately under the table** + +Insert this paragraph after the table (immediately before the "Sequencing notes" heading at line ~134): + +```markdown +> **Spec 6 superseded.** The one-shot `community-launch` workstream was replaced by the ongoing marketing pipeline. See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` and its sub-specs. The launch exit criterion (week-1 dashboard snapshot, post-mortem doc) moves into the metrics-ingest sub-spec. +``` + +- [ ] **Step 3: Commit** + +```bash +git add docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md +git commit -m "docs(gtm): note marketing umbrella supersedes Spec 6" +``` + +--- + +## Task 15: Final verification + PR + +**Files:** none (verification only) + +- [ ] **Step 1: Full build of marketing packages** + +```bash +npx nx run-many --target=build --projects=marketing-assets,marketing-channels,marketing-agent,marketing-metrics +``` + +Expected: 4/4 green. + +- [ ] **Step 2: Verify website + cockpit still build (the move didn't break anything)** + +```bash +npx nx run website:build +``` + +Expected: green. (`marketing/cowork/` is unrelated to website, but verify nothing imports from `cowork/` anywhere.) + +```bash +grep -rE "from ['\"]\.\.\/\.\.\/\.\.\/cowork|from ['\"]cowork" --include="*.ts" --include="*.tsx" apps/ libs/ 2>&1 | head +``` + +Expected: no matches. The cowork dir only contains markdown + JSON; nothing should import it. + +- [ ] **Step 3: Push branch + open PR** + +```bash +git push -u origin marketing-meta +gh pr create --title "feat(marketing): scaffold marketing/ umbrella + cowork migration" --body "$(cat <<'EOF' +## Summary +- Adds `marketing/` directory with 4 internal Nx packages: `@ngaf/marketing-{assets,channels,agent,metrics}` (all `"private": true`, skeletons only) +- Moves `cowork/` → `marketing/cowork/` (single `git mv`, history preserved) +- Adds `marketing/cowork/marketing/SKILL.md` stub (body in cowork-loop sub-spec) +- Adds `marketing/cowork/{inbox,outbox,archive}/.gitkeep` +- Adds `docs/gtm/voice.md` (Brian's pre-2026 voice synthesis) +- Adds `marketing/README.md` + `marketing/.env.example` +- Notes Spec 6 supersede in the GTM meta-spec + +Spec: `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +Plan: `docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md` + +## Test plan +- [ ] `npx nx run-many --target=build --projects=marketing-*` green +- [ ] `npx nx show projects | grep marketing` lists all 4 +- [ ] `npx nx run website:build` green (move didn't break anything) +- [ ] `git log --follow marketing/cowork/gtm/SKILL.md` shows pre-move history + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` + +- [ ] **Step 4: Enable auto-merge on green** + +```bash +gh pr merge --auto --squash +``` + +--- + +## Self-review + +**Spec coverage check** (against §10 deliverables in the meta-spec): + +- ✅ Meta-spec doc — committed as `187ef94f` before plan (out of plan scope) +- ✅ `marketing/README.md` — Task 12 +- ✅ Migrate `cowork/` → `marketing/cowork/` — Task 1 +- ✅ `marketing/.env.example` — Task 13 +- ✅ `docs/gtm/voice.md` — already committed as `7218c235` (out of plan scope, done by subagent) +- ✅ Skeleton `package.json` + `src/index.ts` for 4 packages — Tasks 6, 7, 8, 9 +- ✅ `project.json` for each so `nx graph` sees it — Tasks 6, 7, 8, 9 +- ✅ `tsconfig.base.json` path mappings — Task 10 +- ✅ Workspaces registration — Task 5 +- ✅ Inbox/outbox/archive scaffolding — Task 3 +- ✅ `/marketing` skill stub — Task 4 +- ✅ Cowork README path update — Task 2 +- ✅ GTM meta supersede note — Task 14 +- ✅ Build verification — Tasks 11, 15 + +**Placeholder scan:** All steps have concrete code, exact paths, exact commands. The `--design.md` references in the README and skill stub are intentional (sub-specs haven't been written yet); they're documentation pointers, not code TODOs. + +**Type consistency:** +- `Draft`, `PostResult`, `PostMetrics`, `ChannelAdapter`, `ChannelId` defined in Task 7, consumed in Task 8 via `import type { Draft } from '@ngaf/marketing-channels'`. Cross-package import is validated in Task 10 step 3. +- `Trigger`, `DraftBundle` defined in Task 8, referenced in skill stub (Task 4) as documentation. +- `CardInput`, `RenderedCard` defined in Task 6. No cross-package consumers in this plan. +- `RunOptions`, `RunResult` defined in Task 9. No cross-package consumers. + +All package names are `@ngaf/marketing-` consistently; all Nx project names are `marketing-` consistently. + +--- + +## Execution Handoff + +Plan complete and saved to `docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md`. Two execution options: + +**1. Subagent-Driven (recommended)** — I dispatch a fresh subagent per task, two-stage review between each. + +**2. Inline Execution** — Execute tasks in this session using executing-plans, batch execution with checkpoints. + +Which approach? From fc86177753e9371f92100c8c158c32f3793573ed Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:51:07 -0700 Subject: [PATCH 05/18] refactor: move cowork/ to marketing/cowork/ Cowork becomes a subsystem of the marketing umbrella. The /gtm skill content is unchanged; only its path moves. Install instructions in the README will be updated in the next commit. --- {cowork => marketing/cowork}/README.md | 0 {cowork => marketing/cowork}/gtm/SKILL.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {cowork => marketing/cowork}/README.md (100%) rename {cowork => marketing/cowork}/gtm/SKILL.md (100%) diff --git a/cowork/README.md b/marketing/cowork/README.md similarity index 100% rename from cowork/README.md rename to marketing/cowork/README.md diff --git a/cowork/gtm/SKILL.md b/marketing/cowork/gtm/SKILL.md similarity index 100% rename from cowork/gtm/SKILL.md rename to marketing/cowork/gtm/SKILL.md From 38bc1b3b3af18dbbf221d82cb05ac1789bbba70e Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:51:30 -0700 Subject: [PATCH 06/18] docs(marketing/cowork): update install paths after move --- marketing/cowork/README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/marketing/cowork/README.md b/marketing/cowork/README.md index 70bb628c8..f111ad7a3 100644 --- a/marketing/cowork/README.md +++ b/marketing/cowork/README.md @@ -7,26 +7,31 @@ The skill is **project-local**. It isn't published to a marketplace. Install ste ## What's in this directory ``` -cowork/ +marketing/cowork/ ├── README.md # This file. -└── gtm/ - └── SKILL.md # The single Cowork skill. Edit here; install copies/symlinks elsewhere. +├── gtm/ +│ └── SKILL.md # The GTM Cowork skill. +├── marketing/ +│ └── SKILL.md # The marketing pipeline Cowork skill (stub; body in sub-spec 4). +├── inbox/ # Drafts awaiting review. +├── outbox/ # Approved + posted drafts. +└── archive/ # Rejected or expired drafts. ``` ## Install (one-time per machine) -The skill ships as `cowork/gtm/SKILL.md`. To make it loadable by Cowork / Claude Code on your machine, register it as a user-level skill: +The skill ships as `marketing/cowork/gtm/SKILL.md`. To make it loadable by Cowork / Claude Code on your machine, register it as a user-level skill: ```bash mkdir -p ~/.claude/skills/gtm -cp cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md +cp marketing/cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md ``` Or symlink it so edits in this repo flow through immediately: ```bash mkdir -p ~/.claude/skills/gtm -ln -sf "$(pwd)/cowork/gtm/SKILL.md" ~/.claude/skills/gtm/SKILL.md +ln -sf "$(pwd)/marketing/cowork/gtm/SKILL.md" ~/.claude/skills/gtm/SKILL.md ``` Verify: open a Claude Code session in this repo and type `/gtm`. The skill should load and announce its responsibilities. @@ -51,7 +56,7 @@ The routine itself lives at the user/runtime layer; it is not committed to the r ## Update -Edit `cowork/gtm/SKILL.md` in this repo. If you symlinked, the change is live immediately. If you copied, re-run the `cp` command after committing. +Edit `marketing/cowork/gtm/SKILL.md` in this repo. If you symlinked, the change is live immediately. If you copied, re-run the `cp` command after committing. ## Why one skill, not a plugin From 30bbeac4fdf8f7ed2086f19de6f19465cdfe11e0 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:51:35 -0700 Subject: [PATCH 07/18] feat(marketing/cowork): scaffold inbox/outbox/archive dirs --- marketing/cowork/archive/.gitkeep | 0 marketing/cowork/inbox/.gitkeep | 0 marketing/cowork/outbox/.gitkeep | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 marketing/cowork/archive/.gitkeep create mode 100644 marketing/cowork/inbox/.gitkeep create mode 100644 marketing/cowork/outbox/.gitkeep diff --git a/marketing/cowork/archive/.gitkeep b/marketing/cowork/archive/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/marketing/cowork/inbox/.gitkeep b/marketing/cowork/inbox/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/marketing/cowork/outbox/.gitkeep b/marketing/cowork/outbox/.gitkeep new file mode 100644 index 000000000..e69de29bb From d4871b9b6f94e6174686411291e53a933bc92dc9 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:51:48 -0700 Subject: [PATCH 08/18] feat(marketing/cowork): add /marketing skill stub --- marketing/cowork/marketing/SKILL.md | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 marketing/cowork/marketing/SKILL.md diff --git a/marketing/cowork/marketing/SKILL.md b/marketing/cowork/marketing/SKILL.md new file mode 100644 index 000000000..9d1765b44 --- /dev/null +++ b/marketing/cowork/marketing/SKILL.md @@ -0,0 +1,30 @@ +--- +name: marketing +description: | + Cacheplane marketing pipeline operator. Reads `marketing/cowork/inbox/*.json` + draft bundles, presents them for review in conversation, supports + edit/approve/reject decisions, and dispatches approved drafts to channel + adapters. Invoke when "drafts are waiting" or when the user wants to + produce a thread/post for X, LinkedIn, Dev.to, or Reddit. +status: stub +--- + +# Marketing Cowork skill — STUB + +Implementation lands in the cowork-loop sub-spec +(`docs/superpowers/specs/marketing/-cowork-loop-design.md`). + +This file exists so the directory shape and skill name are reserved. +Do NOT invoke this skill until the cowork-loop sub-spec is merged. + +## Expected file conventions (preview) + +- `marketing/cowork/inbox/.json` — drafts awaiting review (agent writes) +- `marketing/cowork/outbox/.json` — approved + posted (skill writes) +- `marketing/cowork/archive/.json` — rejected or expired (skill writes) + +`` is `YYYY-MM-DD-`. + +## Expected DraftBundle shape (preview) + +See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` §5.3. From 2482d5db73cad0ffc25277083f6622fca4d399fe Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:52:06 -0700 Subject: [PATCH 09/18] chore: register marketing/* packages in npm workspaces --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 01700b0f0..81e38c1b7 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,11 @@ "workspaces": [ "packages/*", "apps/*", - "libs/*" + "libs/*", + "marketing/assets", + "marketing/channels", + "marketing/agent", + "marketing/metrics" ], "dependencies": { "@ag-ui/client": "^0.0.52", From 99b44bb038d886f9a0ad5001f5a2f27bdd5020f5 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:52:33 -0700 Subject: [PATCH 10/18] feat(marketing/assets): scaffold @ngaf/marketing-assets skeleton --- marketing/assets/package.json | 14 ++++++++++++++ marketing/assets/project.json | 21 +++++++++++++++++++++ marketing/assets/src/index.ts | 25 +++++++++++++++++++++++++ marketing/assets/tsconfig.json | 5 +++++ marketing/assets/tsconfig.lib.json | 9 +++++++++ 5 files changed, 74 insertions(+) create mode 100644 marketing/assets/package.json create mode 100644 marketing/assets/project.json create mode 100644 marketing/assets/src/index.ts create mode 100644 marketing/assets/tsconfig.json create mode 100644 marketing/assets/tsconfig.lib.json diff --git a/marketing/assets/package.json b/marketing/assets/package.json new file mode 100644 index 000000000..fcb978a5b --- /dev/null +++ b/marketing/assets/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-assets", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/assets" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/assets/project.json b/marketing/assets/project.json new file mode 100644 index 000000000..82c9dfe9a --- /dev/null +++ b/marketing/assets/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-assets", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/assets/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/assets"], + "options": { + "outputPath": "dist/marketing/assets", + "main": "marketing/assets/src/index.ts", + "tsConfig": "marketing/assets/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/assets/src/index.ts b/marketing/assets/src/index.ts new file mode 100644 index 000000000..5f04bc5d9 --- /dev/null +++ b/marketing/assets/src/index.ts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-assets — Brand asset rendering for the marketing pipeline. +// Skeleton only. Implementation lands in the brand-assets sub-spec. + +export interface CardInput { + template: string; + title: string; + subtitle?: string; + tag?: string; + author?: { name: string; role?: string }; +} + +export interface RenderedCard { + png: Buffer; + width: number; + height: number; + contentType: 'image/png'; +} + +export function renderCard(_input: CardInput): Promise { + throw new Error( + '@ngaf/marketing-assets: renderCard() not yet implemented. See brand-assets sub-spec.', + ); +} diff --git a/marketing/assets/tsconfig.json b/marketing/assets/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/assets/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/assets/tsconfig.lib.json b/marketing/assets/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/assets/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} From b7b9d2a5821ba242c4dc848fbdd56fcbfd22dc3c Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:53:01 -0700 Subject: [PATCH 11/18] feat(marketing/channels): scaffold @ngaf/marketing-channels skeleton --- marketing/channels/package.json | 14 +++++++++ marketing/channels/project.json | 21 ++++++++++++++ marketing/channels/src/index.ts | 43 ++++++++++++++++++++++++++++ marketing/channels/tsconfig.json | 5 ++++ marketing/channels/tsconfig.lib.json | 9 ++++++ 5 files changed, 92 insertions(+) create mode 100644 marketing/channels/package.json create mode 100644 marketing/channels/project.json create mode 100644 marketing/channels/src/index.ts create mode 100644 marketing/channels/tsconfig.json create mode 100644 marketing/channels/tsconfig.lib.json diff --git a/marketing/channels/package.json b/marketing/channels/package.json new file mode 100644 index 000000000..94048fc2a --- /dev/null +++ b/marketing/channels/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-channels", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/channels" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/channels/project.json b/marketing/channels/project.json new file mode 100644 index 000000000..e27933ba3 --- /dev/null +++ b/marketing/channels/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-channels", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/channels/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/channels"], + "options": { + "outputPath": "dist/marketing/channels", + "main": "marketing/channels/src/index.ts", + "tsConfig": "marketing/channels/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/channels/src/index.ts b/marketing/channels/src/index.ts new file mode 100644 index 000000000..e1f138630 --- /dev/null +++ b/marketing/channels/src/index.ts @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-channels — Channel adapters for X, LinkedIn, Dev.to, Reddit. +// Skeleton only. Implementations land in the channel-adapters sub-spec. + +export type ChannelId = 'x' | 'linkedin' | 'devto' | 'reddit'; + +export interface Draft { + channel: ChannelId; + text: string; + media?: { png: Buffer; alt: string }[]; + threadParts?: string[]; + link?: { url: string; previewTitle?: string }; + scheduledAt?: string; +} + +export interface PostResult { + channel: ChannelId; + postId: string; + url: string; + postedAt: string; +} + +export interface PostMetrics { + postId: string; + impressions?: number; + clicks?: number; + replies?: number; + shares?: number; + fetchedAt: string; +} + +export interface ChannelAdapter { + readonly id: ChannelId; + post(draft: Draft): Promise; + metrics(postId: string): Promise; +} + +export function getAdapter(_id: ChannelId): ChannelAdapter { + throw new Error( + '@ngaf/marketing-channels: getAdapter() not yet implemented. See channel-adapters sub-spec.', + ); +} diff --git a/marketing/channels/tsconfig.json b/marketing/channels/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/channels/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/channels/tsconfig.lib.json b/marketing/channels/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/channels/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} From e420aaf2dc07c50b28b8db48c96e6e0a11b8fe72 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:53:29 -0700 Subject: [PATCH 12/18] feat(marketing/agent): scaffold @ngaf/marketing-agent skeleton --- marketing/agent/package.json | 14 ++++++++++++++ marketing/agent/project.json | 21 +++++++++++++++++++++ marketing/agent/src/index.ts | 26 ++++++++++++++++++++++++++ marketing/agent/tsconfig.json | 5 +++++ marketing/agent/tsconfig.lib.json | 9 +++++++++ 5 files changed, 75 insertions(+) create mode 100644 marketing/agent/package.json create mode 100644 marketing/agent/project.json create mode 100644 marketing/agent/src/index.ts create mode 100644 marketing/agent/tsconfig.json create mode 100644 marketing/agent/tsconfig.lib.json diff --git a/marketing/agent/package.json b/marketing/agent/package.json new file mode 100644 index 000000000..30578ecd2 --- /dev/null +++ b/marketing/agent/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-agent", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/agent" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/agent/project.json b/marketing/agent/project.json new file mode 100644 index 000000000..a412512f2 --- /dev/null +++ b/marketing/agent/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-agent", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/agent/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/agent"], + "options": { + "outputPath": "dist/marketing/agent", + "main": "marketing/agent/src/index.ts", + "tsConfig": "marketing/agent/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/agent/src/index.ts b/marketing/agent/src/index.ts new file mode 100644 index 000000000..4c7bfa73a --- /dev/null +++ b/marketing/agent/src/index.ts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-agent — LangGraph drafting agent for the marketing pipeline. +// Skeleton only. Implementation lands in the content-agent sub-spec. + +import type { Draft } from '@ngaf/marketing-channels'; + +export type Trigger = + | { kind: 'blog-merge'; slug: string } + | { kind: 'release'; tag: string } + | { kind: 'cowork-prompt'; topic: string; freeform?: string } + | { kind: 'cadence'; window: 'weekly' }; + +export interface DraftBundle { + id: string; + trigger: Trigger; + drafts: Draft[]; + source: { url?: string; title?: string; excerpt?: string }; + createdAt: string; +} + +export function draft(_trigger: Trigger): Promise { + throw new Error( + '@ngaf/marketing-agent: draft() not yet implemented. See content-agent sub-spec.', + ); +} diff --git a/marketing/agent/tsconfig.json b/marketing/agent/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/agent/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/agent/tsconfig.lib.json b/marketing/agent/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/agent/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} From 037368403f396a4ba40ed56f4c81b180908880cd Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:53:54 -0700 Subject: [PATCH 13/18] feat(marketing/metrics): scaffold @ngaf/marketing-metrics skeleton --- marketing/metrics/package.json | 14 ++++++++++++++ marketing/metrics/project.json | 21 +++++++++++++++++++++ marketing/metrics/src/index.ts | 19 +++++++++++++++++++ marketing/metrics/tsconfig.json | 5 +++++ marketing/metrics/tsconfig.lib.json | 9 +++++++++ 5 files changed, 68 insertions(+) create mode 100644 marketing/metrics/package.json create mode 100644 marketing/metrics/project.json create mode 100644 marketing/metrics/src/index.ts create mode 100644 marketing/metrics/tsconfig.json create mode 100644 marketing/metrics/tsconfig.lib.json diff --git a/marketing/metrics/package.json b/marketing/metrics/package.json new file mode 100644 index 000000000..27a15813c --- /dev/null +++ b/marketing/metrics/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-metrics", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/metrics" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/metrics/project.json b/marketing/metrics/project.json new file mode 100644 index 000000000..5528232b8 --- /dev/null +++ b/marketing/metrics/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-metrics", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/metrics/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/metrics"], + "options": { + "outputPath": "dist/marketing/metrics", + "main": "marketing/metrics/src/index.ts", + "tsConfig": "marketing/metrics/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/metrics/src/index.ts b/marketing/metrics/src/index.ts new file mode 100644 index 000000000..454c42e98 --- /dev/null +++ b/marketing/metrics/src/index.ts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-metrics — Metrics ingestion for the marketing pipeline. +// Skeleton only. Implementation lands in the metrics-ingest sub-spec. + +export interface RunOptions { + sinceHours?: number; +} + +export interface RunResult { + posts: number; + eventsEmitted: number; +} + +export function run(_opts?: RunOptions): Promise { + throw new Error( + '@ngaf/marketing-metrics: run() not yet implemented. See metrics-ingest sub-spec.', + ); +} diff --git a/marketing/metrics/tsconfig.json b/marketing/metrics/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/metrics/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/metrics/tsconfig.lib.json b/marketing/metrics/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/metrics/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} From 5a23811d74c8b0c85fc9077bd511a29c862c7c6a Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:54:59 -0700 Subject: [PATCH 14/18] chore(tsconfig): add path mappings for marketing/* packages --- tsconfig.base.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tsconfig.base.json b/tsconfig.base.json index 453b76c99..696b3c26d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -34,6 +34,10 @@ "@ngaf/langgraph": ["libs/langgraph/src/public-api.ts"], "@ngaf/licensing": ["libs/licensing/src/index.ts"], "@ngaf/licensing/testing": ["libs/licensing/src/testing.ts"], + "@ngaf/marketing-agent": ["marketing/agent/src/index.ts"], + "@ngaf/marketing-assets": ["marketing/assets/src/index.ts"], + "@ngaf/marketing-channels": ["marketing/channels/src/index.ts"], + "@ngaf/marketing-metrics": ["marketing/metrics/src/index.ts"], "@ngaf/render": ["libs/render/src/public-api.ts"], "@ngaf/telemetry": ["libs/telemetry/src/index.ts"], "@ngaf/telemetry/browser": ["libs/telemetry/src/browser/public-api.ts"], From 33df3fe9d1669fce0430d86c14dfca49d0d7827b Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:55:48 -0700 Subject: [PATCH 15/18] chore: refresh lockfile for marketing/* workspaces --- package-lock.json | 56 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index d995c618b..4de0dc5d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,11 @@ "workspaces": [ "packages/*", "apps/*", - "libs/*" + "libs/*", + "marketing/assets", + "marketing/channels", + "marketing/agent", + "marketing/metrics" ], "dependencies": { "@ag-ui/client": "^0.0.52", @@ -173,12 +177,12 @@ }, "libs/a2ui": { "name": "@ngaf/a2ui", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT" }, "libs/ag-ui": { "name": "@ngaf/ag-ui", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@ag-ui/client": "*", @@ -190,7 +194,7 @@ }, "libs/chat": { "name": "@ngaf/chat", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "dependencies": { "@cacheplane/partial-json": ">=0.1.1 <0.3.0", @@ -318,7 +322,7 @@ }, "libs/langgraph": { "name": "@ngaf/langgraph", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@angular/core": "^20.0.0 || ^21.0.0", @@ -331,7 +335,7 @@ }, "libs/licensing": { "name": "@ngaf/licensing", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@noble/ed25519": "^2.2.3" @@ -339,7 +343,7 @@ }, "libs/render": { "name": "@ngaf/render", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@angular/common": "^20.0.0 || ^21.0.0", @@ -350,7 +354,7 @@ }, "libs/telemetry": { "name": "@ngaf/telemetry", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "bin": { "ngaf-telemetry-postinstall": "node/postinstall.js" @@ -377,6 +381,26 @@ "react-dom": "^19.0.0" } }, + "marketing/agent": { + "name": "@ngaf/marketing-agent", + "version": "0.0.0", + "license": "MIT" + }, + "marketing/assets": { + "name": "@ngaf/marketing-assets", + "version": "0.0.0", + "license": "MIT" + }, + "marketing/channels": { + "name": "@ngaf/marketing-channels", + "version": "0.0.0", + "license": "MIT" + }, + "marketing/metrics": { + "name": "@ngaf/marketing-metrics", + "version": "0.0.0", + "license": "MIT" + }, "node_modules/@ag-ui/client": { "version": "0.0.52", "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.52.tgz", @@ -12076,6 +12100,22 @@ "resolved": "libs/licensing", "link": true }, + "node_modules/@ngaf/marketing-agent": { + "resolved": "marketing/agent", + "link": true + }, + "node_modules/@ngaf/marketing-assets": { + "resolved": "marketing/assets", + "link": true + }, + "node_modules/@ngaf/marketing-channels": { + "resolved": "marketing/channels", + "link": true + }, + "node_modules/@ngaf/marketing-metrics": { + "resolved": "marketing/metrics", + "link": true + }, "node_modules/@ngaf/minting-service": { "resolved": "apps/minting-service", "link": true From 0f2bd82128a64b6358c94798caa7f39ed951e6f1 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:56:02 -0700 Subject: [PATCH 16/18] docs(marketing): add directory charter README --- marketing/README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 marketing/README.md diff --git a/marketing/README.md b/marketing/README.md new file mode 100644 index 000000000..c925a5a61 --- /dev/null +++ b/marketing/README.md @@ -0,0 +1,38 @@ +# marketing/ + +Agentic marketing pipeline. Five composable subsystems that turn source content (blog posts, releases, prompts, schedules) into multi-channel posts (X, LinkedIn, Dev.to, Reddit), with Cowork as the human approval surface and PostHog as the feedback loop. + +## Structure + +``` +marketing/ +├── assets/ # @ngaf/marketing-assets — branded image rendering +├── channels/ # @ngaf/marketing-channels — X, LinkedIn, Dev.to, Reddit adapters +├── agent/ # @ngaf/marketing-agent — LangGraph drafting agent +├── cowork/ # Claude skills (/gtm, /marketing) + inbox/outbox/archive +└── metrics/ # @ngaf/marketing-metrics — feedback ingestion → PostHog +``` + +All four packages are internal (`"private": true`). They are NOT published to npm. + +## Specs + +- Meta (this umbrella): `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +- Sub-specs (when written): + - `brand-assets` — `docs/superpowers/specs/marketing/-brand-assets-design.md` + - `channel-adapters` — `docs/superpowers/specs/marketing/-channel-adapters-design.md` + - `content-agent` — `docs/superpowers/specs/marketing/-content-agent-design.md` + - `cowork-loop` — `docs/superpowers/specs/marketing/-cowork-loop-design.md` + - `metrics-ingest` — `docs/superpowers/specs/marketing/-metrics-ingest-design.md` + +## Voice + messaging source-of-truth + +- `docs/gtm/voice.md` — Brian's tone, phrasing, structural quirks +- `docs/gtm/messaging.md` — positioning, claims, no-go phrases +- `docs/gtm/icp.md` — audience + +All in this repo. No machine-local paths in checked-in code. + +## Status + +This directory was scaffolded by the marketing-meta spec. Subsystems are skeletons. Implementation lands as each sub-spec ships. From fb000c46f8846e566b5214c5b5228a8f5bf3cda3 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:56:12 -0700 Subject: [PATCH 17/18] docs(marketing): add .env.example with placeholder channel keys --- marketing/.env.example | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 marketing/.env.example diff --git a/marketing/.env.example b/marketing/.env.example new file mode 100644 index 000000000..996a379be --- /dev/null +++ b/marketing/.env.example @@ -0,0 +1,30 @@ +# marketing/.env.example +# +# Placeholder env var names for the marketing pipeline. Real values live in +# .env at the repo root (gitignored). Copy lines you need into .env and fill +# in your credentials. +# +# Sub-specs may add more keys. This file is documentation, not consumed at +# runtime — each adapter reads its own keys via process.env. + +# X / Twitter +X_API_KEY= +X_API_SECRET= +X_ACCESS_TOKEN= +X_ACCESS_SECRET= + +# LinkedIn +LINKEDIN_ACCESS_TOKEN= +LINKEDIN_AUTHOR_URN= + +# Dev.to +DEVTO_API_KEY= + +# Reddit +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= +REDDIT_USERNAME= +REDDIT_PASSWORD= + +# Pipeline behavior +# DRY_RUN=1 # Adapters return synthetic PostResults; nothing is posted. From bb85cf6b31b5dcc54fa1819633a5729d3154c4b8 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 09:56:31 -0700 Subject: [PATCH 18/18] docs(gtm): note marketing umbrella supersedes Spec 6 --- docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md b/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md index e286f7821..f1c7083f3 100644 --- a/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md +++ b/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md @@ -131,6 +131,8 @@ Seven specs follow Spec 0. Dependencies form a DAG: | 6 | community-launch | 3 | 2, 3, 4, 5 | Launch executed, week-1 dashboard snapshot committed, post-mortem doc written. | | 7 | enterprise-pipeline | 4 | 1, 2 | 3 pilots tracked end-to-end in enterprise-funnel dashboard; ≥1 sanitized public artifact produced from a pilot. | +> **Spec 6 superseded.** The one-shot `community-launch` workstream was replaced by the ongoing marketing pipeline. See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` and its sub-specs. The launch exit criterion (week-1 dashboard snapshot, post-mortem doc) moves into the metrics-ingest sub-spec. + **Sequencing notes:** - Specs 5–7 can spin up in parallel as soon as their dependencies clear. The critical path is 0 → 1 → 2 → 3 → 4 (developer track end-to-end).