feat: Shared Business Infrastructure — Phase 1 (Websites + Marketing)

Shared Business Infrastructure — Phase 1: Websites + Marketing

Enhancement Summary

Deepened on: 2026-03-07 Agents used: 13 (architecture-strategist, security-sentinel, performance-oracle, agent-native-reviewer, code-simplicity-reviewer, kieran-typescript-reviewer, pattern-recognition-specialist, deployment-verification-agent, agent-native-architecture-skill, create-agent-skills, frontend-design-skill, cloudflare-researcher, seo-researcher)

Key Improvements from Original Plan

  1. Simplified structure — Dropped tooling/, packages/config/, packages/tailwind-config/. Brand configs moved to apps/<slug>/brand.config.ts. Single tsconfig.base.json at root.
  2. Agent-native architecture — Added discovery skills (/biz-list, /biz-status), Wrangler CLI project creation in skills, social draft persistence, CRUD completeness.
  3. Security hardened — Pin GitHub Actions to SHA, validate CI matrix inputs, scope Cloudflare token, add CSP headers, gitignore PDFs immediately.
  4. Performance optimized — Single-job Turborepo CI (no matrix), remote caching, self-hosted fonts, Astro image pipeline.
  5. Design system — Flat CSS custom properties (~12 vars per site) as single source of truth for visual theming.
  6. TypeScript improvementsas const satisfies, slim BrandConfig (~30 lines), zod imported directly (not astro/zod).

Key Tension Resolved: Simplicity vs. Architecture

The simplicity reviewer argued for dropping Turborepo and all shared packages. The architecture and performance reviewers validated them. Resolution: Keep Turborepo (one config file, earns its keep via --filter and caching) and packages/ui (minimal: BaseLayout + types + schemas + tokens.css). Drop packages/config, packages/tailwind-config, tooling/, BrandStyle, aesthetic presets, three-tier CSS tokens, SocialLinks, and modules?. CSS is the single source of truth for visual properties; TypeScript holds only non-visual metadata. Components built inline in site 1, extracted to shared during Phase 1D.


Overview

Set up a Claude Code skills monorepo that serves as shared operational infrastructure for 4-6 businesses (mixed services + products). Phase 1 establishes the foundation: monorepo scaffolding, shared Astro component library, per-business branding, Cloudflare Pages deployment, and core Claude Code skills for website management and marketing content.

The architecture is skills-first and agent-native — Claude Code’s native skill system is the “operating system.” Skills are shortcuts on top of primitives, not gates. The agent can always fall back to direct file operations for novel requests.

Problem Statement / Motivation

Aaron manages 4-6 businesses with mostly manual/ad-hoc tooling. Each business needs a marketing website, content creation, SEO, and social media — but building these independently per business means duplicated effort and inconsistent quality. A shared infrastructure with AI-powered skills lets one operator manage all businesses efficiently from a single repo.

Proposed Solution

A pnpm + Turborepo monorepo with:

  • Astro static sites per business (zero JS by default, component sharing, TypeScript)
  • Cloudflare Pages hosting (300+ edge locations, generous free tier)
  • GitHub Actions CI/CD with Turborepo-native change detection
  • Claude Code skills as shortcuts over primitives — for scaffolding, content creation, and discovery
  • Config-driven branding via TypeScript BrandConfig (non-visual metadata) + flat CSS custom properties (visual theming)

Technical Approach

Architecture

sendy-admin/                          # Renamed from sendy-adventures-admin
├── CLAUDE.md                         # Root conventions (~50-100 lines)
├── package.json                      # private: true, workspace scripts
├── pnpm-workspace.yaml
├── turbo.json
├── tsconfig.base.json                # Shared TypeScript base config
├── # Tailwind v4: CSS-first config via @tailwindcss/vite (no JS preset)
├── .gitignore                        # Expanded for Node/Astro/Wrangler/PDFs

├── .claude/
│   └── skills/                       # Shared Claude Code skills
│       ├── site-scaffold/
│       │   └── SKILL.md
│       ├── content-create/
│       │   └── SKILL.md
│       └── biz-list/
│           └── SKILL.md

├── apps/                             # One Astro site per business
│   └── zendee-adventures/
│       ├── package.json              # depends on @repo/ui
│       ├── brand.config.ts           # BrandConfig — THE keystone contract
│       ├── astro.config.mjs
│       ├── tailwind.config.mjs       # extends ../../tailwind.preset.js
│       ├── tsconfig.json             # extends ../../tsconfig.base.json
│       ├── CLAUDE.md                 # Business-specific AI context
│       └── src/
│           ├── pages/
│           │   └── index.astro
│           ├── content/
│           │   ├── config.ts         # Astro content collection config
│           │   └── blog/             # Blog posts (Markdown, not MDX)
│           ├── assets/               # Images processed by Astro pipeline
│           ├── layouts/
│           │   └── SiteLayout.astro  # Imports BaseLayout + brand.css
│           ├── components/           # Site-specific components (override shared)
│           └── styles/
│               └── brand.css         # CSS custom property overrides

├── packages/
│   └── ui/                           # Shared Astro components
│       ├── package.json              # name: "@repo/ui"
│       └── src/
│           ├── types.ts              # BrandConfig interface (~30 lines)
│           ├── schemas.ts            # Zod schemas (blog, etc.)
│           ├── layouts/
│           │   └── BaseLayout.astro  # Only shared layout to start
│           └── styles/
│               └── tokens.css        # Default CSS custom properties (~12 vars)

├── .github/
│   └── workflows/
│       └── deploy.yml                # Single-job Turborepo deploy

└── docs/
    ├── brainstorms/
    └── plans/

Research Insights: Architecture

Simplifications from original plan:

  • packages/config/ eliminated — brand configs live at apps/<slug>/brand.config.ts. Skills glob apps/*/brand.config.ts for discovery. This keeps the config package purely definitional and collapses the three-step resolution to one directory.
  • packages/tailwind-config/ eliminated — single tailwind.preset.js at repo root. Each site’s tailwind.config.mjs does presets: [require('../../tailwind.preset.js')].
  • tooling/ directory eliminated — single tsconfig.base.json at root. ESLint deferred to when it is needed.
  • packages/ui/ starts minimal: BaseLayout + types + schemas + tokens.css. Components (Header, Footer, etc.) built inline in site 1, extracted to shared during Phase 1D when a second site reveals what’s truly shared.

Per-site component override convention: Not needed yet — components start per-site. When extracting to packages/ui/, document the import convention in root CLAUDE.md.


BrandConfig Schema

The keystone contract. Lives at apps/<slug>/brand.config.ts per business.

// packages/ui/src/types.ts — shared type definitions (~30 lines, zero runtime)

export interface BrandConfig {
  name: string;                           // "Zendee Adventures"
  slug: string;                           // "zendee-adventures" (must match directory)
  domain: `https://${string}`;            // Full origin, no trailing slash
  type: 'service' | 'product';

  brand: {
    tagline: string;
    voice: string;                        // Free-text tone description for content generation
    audience: string;                     // Target audience description
  };

  meta: {
    title: string;
    description: string;
    ogImage?: string;
  };

  deploy: {
    projectName: string;                  // Cloudflare Pages project name
  };
}

CSS is the single source of truth for visual properties (colors, fonts, spacing, shapes). TypeScript holds only non-visual metadata (voice, audience, slug, domain, deploy). This eliminates the dual-source-of-truth problem where BrandConfig colors and CSS vars must stay in sync.

Research Insights: BrandConfig

Simplification rationale:

  • Dropped BrandColors, BrandFonts, BrandStyle — all visual properties live in CSS custom properties (brand.css) only.
  • Dropped SocialLinks — not needed for Phase 1 (add when social skills mature).
  • Dropped modules? — YAGNI; add when Phase 2 begins.
  • Dropped HexColor branded type — colors are in CSS now, not TypeScript.
  • Use as const satisfies BrandConfig in brand config files — preserves literal types while validating structure.
  • brand.config.ts lives per-app, making discovery trivial: glob apps/*/brand.config.ts.
  • Content-create skill reads voice and audience from TypeScript, visual context from CSS.

Example brand config:

// apps/zendee-adventures/brand.config.ts
import type { BrandConfig } from '@repo/ui/types';

export default {
  name: 'Zendee Adventures',
  slug: 'zendee-adventures',
  domain: 'https://zendee-adventures.com',
  type: 'service',

  brand: {
    tagline: 'Adventure awaits in the wild',
    voice: 'Energetic, outdoorsy, approachable. Short punchy sentences. Active voice.',
    audience: 'Young adults (25-40) seeking outdoor adventures and nature experiences',
  },

  meta: {
    title: 'Zendee Adventures | Outdoor Tours & Experiences',
    description: 'Guided outdoor adventures for thrill-seekers and nature lovers.',
  },

  deploy: {
    projectName: 'zendee-adventures',
  },
} as const satisfies BrandConfig;

Skill Invocation Convention

All shared skills accept the business slug as the first argument:

/site-scaffold zendee-adventures
/content-create zendee-adventures blog
/biz-list

Skills resolve the business by:

  1. Validating slug matches /^[a-z0-9-]+$/ and apps/<slug>/ exists
  2. Loading the brand config from apps/<slug>/brand.config.ts
  3. Using the CLAUDE.md in apps/<slug>/ for business-specific context
  4. On invalid slug: listing available businesses (discovery fallback)

This convention is documented in the root CLAUDE.md.

Research Insights: Skill Design (Agent-Native)

Skills are shortcuts, not gates. The agent can always fall back to direct file operations. Skills exist for convenience and consistency, not as the only way to accomplish a task.

CRUD completeness check:

EntityCreateReadUpdateDelete
Site/site-scaffold/biz-list, /biz-statusDirect file editManual (deferred)
Content/content-createls/read apps contentDirect file editDirect file delete
Brand ConfigVia /site-scaffoldDirect read brand.config.tsDirect file editManual
DeployPush to main / /site-deploy (ad-hoc)wrangler pages deployment listN/AN/A

Key agent-native additions:

  • /biz-list skill — returns all businesses with status summary. Critical for agent autonomy.
  • /site-scaffold uses wrangler pages project create — no manual Cloudflare dashboard step.
  • Social drafts persist to apps/<slug>/content/social/<platform>-<date>.md for retrieval.
  • Build errors are captured and presented to Claude for diagnosis.
  • Per-business CLAUDE.md files should include: business description, brand voice, content guidelines, available images list.
  • Content-create skill reads voice/audience from BrandConfig TypeScript; visual context from brand.css.

Content Collection Schema

// Shared blog schema — importable from @repo/ui/schemas
// packages/ui/src/schemas.ts

import { z } from 'zod';  // Direct import, NOT astro/zod

export const blogSchema = z.object({
  title: z.string(),
  description: z.string(),
  pubDate: z.coerce.date(),
  updatedDate: z.coerce.date().optional(),
  author: z.string().default('Team'),
  heroImage: z.string().optional(),
  tags: z.array(z.string()).default([]),
  draft: z.boolean().default(false),
});

export type BlogFrontmatter = z.infer<typeof blogSchema>;

Research Insights: Content

  • Use .md (Markdown), not .mdx for generated content. MDX allows arbitrary JSX/JavaScript, which is an XSS vector if content generation ever produces embedded scripts. Standard Markdown has no script execution. Use MDX only when you explicitly need interactive components.
  • Import zod directly, not astro/zod. This keeps the schema framework-agnostic and reusable outside Astro.
  • Export inferred types (BlogFrontmatter) alongside schemas so components can type-check without calling z.infer.
  • Images go in apps/<slug>/src/assets/ (not public/). Astro’s <Image> component processes these at build time for WebP/AVIF conversion, responsive srcset, and automatic width/height for CLS prevention.

CSS Custom Properties (Flat, ~12 Variables)

CSS is the single source of truth for all visual properties. Each site overrides brand.css with its own values. The shared tokens.css provides sensible defaults.

/* packages/ui/src/styles/tokens.css — Default brand tokens */
:root {
  --brand-font-heading: 'Inter', sans-serif;
  --brand-font-body: 'Inter', sans-serif;
  --brand-color-primary: #2563eb;
  --brand-color-accent: #f59e0b;
  --brand-color-bg: #ffffff;
  --brand-color-text: #1e293b;
  --brand-color-border: #e2e8f0;
  --brand-radius: 8px;
  --brand-shadow: 0 1px 3px rgba(0,0,0,0.1);
  --brand-container-max: 1200px;
  --brand-content-max: 720px;
  --brand-section-padding: 6rem;
}
/* apps/zendee-adventures/src/styles/brand.css — Per-site overrides */
:root {
  --brand-font-heading: 'Montserrat', sans-serif;
  --brand-font-body: 'Inter', sans-serif;
  --brand-color-primary: #2d5016;
  --brand-color-accent: #f59e0b;
  --brand-color-bg: #fafaf5;
  --brand-color-text: #1a1a1a;
  --brand-color-border: #e5e7d0;
  --brand-radius: 16px;
  --brand-shadow: 0 1px 2px rgba(0,0,0,0.05);
}

Research Insights: Design System

Keep it flat. Start with ~12 CSS variables. Add more when you need them, not before. Two sites feel different through color, font, and border-radius alone — you don’t need three-tier token hierarchies or aesthetic presets yet.

Self-host fonts via @fontsource — eliminates render-blocking Google Fonts requests:

pnpm add @fontsource-variable/montserrat @fontsource-variable/inter --filter=zendee-adventures

Build components inline first. Header, Footer, HeroSection, etc. start in apps/zendee-adventures/src/components/. Extract to packages/ui/ during Phase 1D when a second site reveals what’s truly shared vs. site-specific.


CI/CD Pipeline

Redesigned based on performance and security reviews.

# .github/workflows/deploy.yml
name: Deploy Sites

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:
    inputs:
      site:
        description: 'Site slug to deploy (or "all")'
        required: true

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
    steps:
      # Pin all actions to SHA (supply chain security)
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
        with:
          fetch-depth: 2  # Need HEAD^1 for turbo change detection

      - uses: pnpm/action-setup@fe02b34f77f8bc703a5f83f2ec0b1d17f6cfbf1f # v4

      - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
        with:
          node-version-file: '.node-version'
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile

      # Lint and type-check on all PRs
      - name: Lint & Type Check
        if: github.event_name == 'pull_request'
        run: pnpm turbo check lint

      # Build only changed sites (Turborepo-native detection)
      - name: Build changed sites
        run: pnpm turbo build --filter='./apps/*[HEAD^1]'

      # Deploy each site that produced a dist/
      - name: Deploy to Cloudflare Pages
        if: github.ref == 'refs/heads/main'
        run: |
          for site_dir in apps/*/; do
            if [ -d "$site_dir/dist" ]; then
              site_name=$(basename "$site_dir")
              # Validate slug format
              if [[ ! "$site_name" =~ ^[a-z0-9-]+$ ]]; then
                echo "Invalid site slug: $site_name"
                exit 1
              fi
              echo "Deploying $site_name..."
              npx wrangler pages deploy "$site_dir/dist" \
                --project-name="$site_name"
            fi
          done
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

Research Insights: CI/CD

Single-job Turborepo build replaces matrix strategy (from performance oracle):

  • Pays pnpm install cost once instead of N times.
  • Turborepo handles parallelism internally (better than GitHub Actions matrix).
  • Avoids GitHub Actions concurrent job limits (5 for private repos).
  • --filter='./apps/*[HEAD^1]' uses Turborepo’s native change detection — no dorny/paths-filter dependency.

Turborepo remote caching from day one (free Vercel tier):

  • After first build, unchanged sites complete in 1-3 seconds (cache hit).
  • Set TURBO_TOKEN and TURBO_TEAM in GitHub Secrets.

Security hardening (from security sentinel):

  • All third-party actions pinned to commit SHA (tags are mutable — tj-actions/changed-files was compromised via tag in 2025).
  • Slug validated against /^[a-z0-9-]+$/ before use in shell commands.
  • CLOUDFLARE_API_TOKEN scoped to only “Cloudflare Pages: Edit” permission.
  • .node-version file pins exact Node.js version (not just 22).
  • Added lint/check job on PRs for early error detection.

Performance:

  • With remote cache, a shared package change that normally rebuilds 6 sites completes in ~30 seconds (5 cache hits + 1 rebuild).
  • Without remote cache, all 6 builds run but Turborepo parallelizes them on available CPU cores (~2-3 minutes).

Implementation Phases

Phase 0: Security Prerequisites (BLOCKING — Do Before Anything Else)

  • Add .gitignore protection for PDFs and sensitive files immediately:
    *.pdf
    Sendy Adventures/
    .env*
    node_modules/
    dist/
    .astro/
    .wrangler/
    .turbo/
  • Move the 10 legal PDFs to separate private repo. (PDFs were never committed to git — local only. .gitignore now prevents accidental commits.)

Phase 1A: Foundation (Do First)

Scaffolding, tooling, and one deployable site.

Tasks:

  • Rename repo or update README to reflect multi-business scope
  • Initialize pnpm workspace — pnpm-workspace.yaml, root package.json (private: true)
  • Add .node-version file with exact Node.js version (22.22.1)
  • Add Turborepo — turbo.json with build/dev/lint/check tasks
  • Create tsconfig.base.json at repo root with strict settings
  • Create tailwind.preset.js at repo root — Not needed: Tailwind v4 uses CSS-first config via @tailwindcss/vite
  • Create packages/ui/ — types.ts (slim BrandConfig ~30 lines), schemas.ts (blog schema), BaseLayout.astro, tokens.css (~12 default CSS vars)
  • Scaffold apps/zendee-adventures/:
    • brand.config.ts (using as const satisfies BrandConfig)
    • astro.config.mjs (with @tailwindcss/vite), tsconfig.json (extends base)
    • src/pages/index.astro, src/layouts/SiteLayout.astro
    • src/content.config.ts with blog collection using shared schema
    • src/styles/brand.css with flat CSS custom property overrides (~12 vars)
    • src/assets/ directory for images (Astro pipeline)
    • Self-hosted fonts via @fontsource
  • Create root CLAUDE.md
  • Create apps/zendee-adventures/CLAUDE.md
  • Verify build works: pnpm turbo build — 3 pages in 863ms

Success gate: pnpm turbo build produces a working static site for Zendee Adventures.

Phase 1B: Deployment

CI/CD and hosting.

Tasks:

  • Create Cloudflare Pages project via CLI: wrangler pages project create zendee-adventures --production-branch=main
  • Create Cloudflare API token scoped to Cloudflare Workers/Pages Edit
  • Add GitHub Secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID
  • Add GitHub Actions workflow — .github/workflows/deploy.yml (single-job Turborepo design, SHA-pinned actions)
  • Add _headers file to each site’s public/ with security headers:
    /*
      X-Frame-Options: DENY
      X-Content-Type-Options: nosniff
      Referrer-Policy: strict-origin-when-cross-origin
      Permissions-Policy: camera=(), microphone=(), geolocation=()
      Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
  • Push to main and verify auto-deployment works
  • Verify the site is live at zendee-adventures.pages.dev
  • Configure custom domain in Cloudflare (if domain is ready)
  • Enable Cloudflare Web Analytics (free, no client-side JS needed)

Success gate: Pushing a change to apps/zendee-adventures/ auto-deploys to Cloudflare Pages.

Phase 1C: Core Skills

Three skills for Phase 1. Others are ad-hoc prompts until patterns stabilize.

  • Create /biz-list skill — Lists all businesses with status.

    • SKILL.md at .claude/skills/biz-list/SKILL.md
    • Globs apps/*/brand.config.ts, reads each, presents summary table
    • Shows: name, slug, domain, type, deploy status (via wrangler pages deployment list)
    • This is the discovery primitive — every other skill’s error path falls back to it
  • Create /site-scaffold skill — Scaffolds a new business site end-to-end.

    • SKILL.md at .claude/skills/site-scaffold/SKILL.md
    • disable-model-invocation: true (side effects)
    • Accepts: <slug> + interactive brand info questions
    • Creates: apps/<slug>/ directory with all files (brand.config.ts, package.json, astro.config.mjs, brand.css, CLAUDE.md, content.config.ts, index.astro, SiteLayout.astro)
    • Creates Cloudflare Pages project: wrangler pages project create <slug>
    • Updates root CLAUDE.md business list
    • Runs pnpm install to register new workspace
    • Validates: slug format (/^[a-z0-9-]+$/), no directory collision
  • Create /content-create skill — Generates on-brand content.

    • SKILL.md at .claude/skills/content-create/SKILL.md
    • Accepts: <slug> <content-type> (blog, landing-page, social)
    • Reads apps/<slug>/brand.config.ts for voice, audience, tagline
    • Reads apps/<slug>/CLAUDE.md for content guidelines
    • For blog: outputs .md file (not MDX) with correct frontmatter in apps/<slug>/src/content/blog/<date>-<title-slug>.md
    • For social: outputs to apps/<slug>/content/social/<platform>-<date>.md (persisted, not stdout-only)
    • Content generated as local files for operator review — never auto-committed
    • On invalid slug: runs /biz-list fallback

Skills deferred to when patterns stabilize:

  • /site-deploypnpm turbo build --filter=@repo/<slug> && wrangler pages deploy apps/<slug>/dist --project-name=<slug> is a two-line command. Formalize as a skill when deploying frequently enough to forget the syntax.
  • /seo-audit — Ask Claude directly: “Audit apps/zendee-adventures for SEO issues.” Formalize when you have a stable checklist you repeat.
  • /social-draft — Folded into /content-create <slug> social. Separate skill when platform-specific logic grows complex.

Success gate: Can run /content-create zendee-adventures blog and get a properly formatted, on-brand blog post placed in the correct directory.

Phase 1D: Second Business

Validate the “add a business” flow by onboarding a second business.

  • Run /site-scaffold <second-business> with real business details
  • Use different CSS custom property values in brand.css for visual distinctiveness
  • Create initial content with /content-create
  • Push to main and verify auto-deployment
  • Verify both sites deploy independently (Turborepo change detection)
  • Run /biz-list to see both businesses
  • Retrospective: Did any components need to be overridden per-site? Extract learnings.
  • If common components were copied, extract them to packages/ui/

Success gate: Two visually distinct sites running from one repo, managed by the same shared skills.


Acceptance Criteria

Functional Requirements

  • Monorepo builds all sites with pnpm turbo build
  • Each site has unique branding (colors, fonts, border-radius) driven by CSS custom properties
  • Shared components render correctly with different brand configs
  • /site-scaffold <slug> creates a deployable new business site including Cloudflare project
  • /content-create <slug> blog generates an on-brand blog post with correct frontmatter
  • /biz-list shows all businesses with status
  • GitHub Actions deploys only changed sites on push to main
  • Adding a new business requires no changes to shared skills or CI pipeline
  • Agent can discover all businesses and their configs without human input

Non-Functional Requirements

  • Local dev server starts in under 5 seconds per site
  • Full build completes in under 60 seconds per site
  • Root CLAUDE.md stays under 100 lines
  • All internal packages use @repo/* namespace
  • All GitHub Actions pinned to commit SHA
  • Security headers on all deployed sites
  • Generated content uses .md (not .mdx)
  • Fonts self-hosted (no external Google Fonts requests)

Dependencies & Prerequisites

  • Cloudflare account with Pages enabled
  • Custom domains (optional — Cloudflare provides .pages.dev subdomains)
  • pnpm installed locally (npm install -g pnpm)
  • Wrangler CLI for local deploys and project creation (pnpm add -g wrangler)
  • Node.js 22.x (exact version in .node-version)
  • Vercel account for Turborepo remote cache (free tier sufficient)

Risk Analysis & Mitigation

RiskImpactMitigation
Legal PDFs accidentally committedCRITICALPhase 0 blocker: gitignore + move immediately
CI command injection via dynamic valuesCRITICALValidate all slugs against /^[a-z0-9-]+$/ regex
Third-party GitHub Action compromiseHighPin all actions to commit SHA
Cloudflare API token over-scopedHighScope to “Pages: Edit” only
Astro breaking changes affect all sitesHighPin Astro version, update together
Shared component change breaks a siteMediumBuild all sites in CI on packages/ change
Brand config drift between sitesLowTypeScript BrandConfig type + as const satisfies
Skill produces off-brand contentMediumContent always generated locally for review
Content generation XSS via MDXMediumUse .md not .mdx for generated content

Open Decisions (Resolve During Implementation)

  1. Repo renamesendy-admin? business-ops? ventures-hq? Decide before Phase 1A.
  2. Sensitive docs destination — Separate private repo? Google Drive? Encrypted vault?
  3. Analytics — Cloudflare Web Analytics (free, no JS) is the default. Revisit if richer analytics needed.
  4. Image workflow — Images in apps/<slug>/src/assets/ for Astro pipeline. Consider Cloudflare Images or R2 if asset volume grows.
  5. Vitest setup — Add in Phase 1D when there are enough configs and schemas to validate. At minimum: config validation tests, schema snapshot tests.

Future Phases (Out of Scope)

  • Phase 2: Accounting + Invoicing skills (invoice-generate, expense-track, report-generate)
  • Phase 3: Operations (task management, scheduling, vendor coordination)
  • Phase 4: Creative production (design assets, brand management, content pipelines)
  • Phase 5: Automated workflows (cron-based content publishing, scheduled reports, monitoring)
  • Future agent-native enhancements: Cross-business comparison tools, analytics integration, self-modifying skills, content calendar

References