Skip to content

feat: weekly AI-reinvented root.vc + Vercel migration#110

Merged
ledwards merged 10 commits into
mainfrom
weekly-ai-reinvention
Jun 1, 2026
Merged

feat: weekly AI-reinvented root.vc + Vercel migration#110
ledwards merged 10 commits into
mainfrom
weekly-ai-reinvention

Conversation

@ledwards
Copy link
Copy Markdown
Contributor

@ledwards ledwards commented Jun 1, 2026

Summary

Two coordinated shifts landing together:

  1. Hosting moves from Netlify to Vercel (U10). Static-site serving model is identical (publish = '.'outputDirectory: '.'); the single Netlify Function (Attio webhook proxy) is ported to a Vercel API Route. ATTIO_WEBHOOK_URL is already configured on the Vercel project (production, preview, development).

  2. Weekly AI-reinvented root.vc (U0–U8 of the plan; U9 deferred). Every Monday, an autonomous Author + Editor pair driven by claude-code-action@v1 (SHA-pinned per DC14) on a GitHub Actions cron generates a wholly-new artifact at root.vc that weaves Root's facts into each week's frame. Past drops live as statically-frozen permalinks at /archive/YYYY-MM-DD/. Editorial framing stays internal; the artifact itself is the brand.

Origin docs: docs/brainstorms/weekly-ai-reinvention-requirements.md, docs/plans/2026-06-01-001-feat-weekly-ai-reinvention-plan.md.

What's in v1

  • U0 (Runtime spike).github/workflows/spike.yml validates claude-code-action@v1 capabilities (Agent tool, /last30days or WebSearch fallback, Write, Bash, schedule trigger, token observability). Run via Actions UI before enabling the cron.
  • U1 (Config layer)config/firm.js, config/brand-brief.md, config/no-fly-list.md, config/topical-rubric.md, config/weekly-hook.txt, images/README.md, .github/CODEOWNERS. The brand-safety configs are CODEOWNERS-gated (DC17).
  • U2 (CLI preservation)/cli/ is a fully-vendored snapshot of the existing CLI terminal so it survives cycle 1's replacement of root index.html. Existing /welcome.htm and /game.html URLs stay in place.
  • U3 (Smoke test framework)tests/cycle/{anchor-facts,history-entrance,archive-size,structural,source-scan}.test.js + tests/helpers/artifact-env.js + scripts/cycle/source-scan.js. Parameterized via ARTIFACT_PATH; legacy entries waived per DC1.
  • U4 (Orchestrator + Author/Editor prompts + schemas)scripts/cycle/run-cycle.js (the orchestrator), scripts/cycle/prompts/{cycle,author-role,editor-role}.md, scripts/cycle/{output-schema,performance-log,freeze-archive}.js. Cost ceiling enforced via --max-turns 60 + timeout-minutes: 45 (DC9). Notification templates are inlined.
  • U5 (Topical fetcher)scripts/cycle/topical-fetcher.js + scripts/cycle/prompts/topical-fetcher.md. Injection-pattern sanitizer (10 regex patterns); fail-soft on missing/throwing/null invocations.
  • U6 (Cron workflow).github/workflows/weekly-cycle.yml. Monday 01:00 UTC cron (Sun 17:00 PST / 18:00 PDT). Two-step: claude-code-action run → gh pr create --auto --squash. Explicit permissions: block (DC15); concurrency group (DC3); race detection against main HEAD.
  • U7 (Rating + engagement schemas)scripts/cycle/{rating,engagement}-schema.js + tests. Direct file-edit only in v1.
  • U8 (Notification surface) — PR description + post-merge "Drop shipped" issue + failure issue templates inlined in run-cycle.js. Rendered via renderTemplate(template, vars).
  • U10 (Vercel migration)vercel.json (CSP headers on /archive/*), api/submit-application.js, package.json updates (vercel + ajv added as devDeps; netlify-cli removed), README updates. The CLI's job-application fetch updated to /api/submit-application.

Test plan

  • npm test → 105 passed, 13 skipped (cycle tests skip cleanly without ARTIFACT_PATH)
  • ARTIFACT_PATH=cli npx vitest run tests/cycle/ → 81 passed, 7 skipped (legacy waiver works)
  • npm run build → bundle succeeds with firm.js integrated
  • Vercel preview deploys cleanly (this PR's preview URL above)
  • U0 spike workflow run after merge — validates claude-code-action@v1 capabilities
  • Manual dry-run of weekly-cycle.yml via workflow_dispatch after merge — confirms end-to-end before enabling the cron

Still requires manual setup (post-merge)

  1. DNS cut — change root.vc A/CNAME records from Netlify to Vercel. After confirming production traffic on Vercel, delete netlify.toml and netlify/ in a follow-up PR.
  2. Anthropic console — set a per-key spending limit on ANTHROPIC_API_KEY (DC15 hard backstop independent of DC9's turn cap).
  3. Update .github/CODEOWNERS — replace @rootvc-team placeholder with your real GitHub org/team handle.
  4. Branch protection on main — require test workflow to pass before merge so auto-merge respects it.
  5. Run U0 spike before enabling the weekly cron — Actions → "U0 Runtime Validation Spike" → Run workflow.

What's intentionally deferred

rollback.yml workflow, /rate comment-bot workflow, full /archive/_legacy/ migration of welcome.htm + game.html, standalone cycle-config.js / asset-library.js / notify-templates.js, Playwright visual smoke pass, auto-tracked social engagement. See Scope Boundaries in the plan for the full list.

🤖 Generated with Claude Code

ledwards and others added 10 commits June 1, 2026 14:27
Brainstorm captures product decisions for autonomously regenerating
root.vc weekly via Claude (Author + Editor pair, configs as substrate,
in-world history room, press kit output, raw-signal feedback loop,
optional topical seeding via /last30days). Plan resolves implementation
across 10 units in 4 phases on top of the existing vanilla JS/HTML/CSS
stack, deployed on Vercel with the cycle running in GitHub Actions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- vercel.json declares the static-root publish model plus CSP headers
  on /archive/* (blocks external origins, ready for the weekly AI
  cycle to ship sandboxed AI-generated browser code)
- api/submit-application.js ports the existing Netlify function
  (Attio webhook proxy) to Vercel API Routes (default-export handler
  signature)
- package.json removes netlify-cli, adds vercel (dev) and ajv (used
  by the upcoming orchestrator's JSON Schema validation)
- README.md updates dev instructions; npm start now runs vercel dev

Manual follow-up: link the Vercel project (vercel link), configure
ATTIO_WEBHOOK_URL env var on Vercel, cut DNS from Netlify to Vercel,
then delete netlify.toml + netlify/ in a follow-up PR.

Note: the CLI's `apply` command also needs its fetch endpoint updated
from /.netlify/functions/submit-application to /api/submit-application
(one-line change in config/commands.js, intentionally left for the
same commit that lands the rebuilt Root Router game work since both
modifications share that file).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
U0 — Runtime validation spike (.github/workflows/spike.yml +
scripts/cycle/spike-prompt.md): manually-dispatched GH Action that
checks whether claude-code-action@v1 can host the cycle architecture
(Agent tool, /last30days skill or WebSearch fallback, Write, Bash,
token observability). Outputs a JSON capability report as a job
artifact; the rest of the cycle implementation is gated on this
passing.

U1 — Expanded config layer:
- config/firm.js (firm metadata; joined the bundle so the existing
  CLI can reference it)
- config/brand-brief.md (voice, tone, on-brand vs off-brand examples)
- config/no-fly-list.md (topic/framing/aesthetic hard-nos)
- config/topical-rubric.md (4-tier sensitivity rubric for the
  Editor's topical-hook evaluation)
- config/weekly-hook.txt (optional one-line nudge field; empty)
- images/README.md (asset library catalog)
- .github/CODEOWNERS (gates brand-brief, no-fly-list, topical-rubric,
  firm.js per DC17; @rootvc-team is a placeholder — update to your
  real org handle)
- tests/config/expanded-config.test.js
- scripts/build-assets.js adds firm.js to appBundleSources

U2 — CLI preservation:
- cli/index.html: snapshot of the current root index.html with paths
  rewritten to load locally-vendored bundle + xterm + CSS + favicon
- cli/{js,css,favicon.png}: vendored assets so /cli/ survives any
  future drift in root files
- cli/meta.json: legacy: true so cycle smoke tests waive R3
- archive/.gitkeep: empty archive root ready for the first drop
- tests/cycle/cli-preservation.test.js

37/37 tests pass; build succeeds with firm.js bundled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DC6, DC7, DC11, DC13 enforcement layer that the cycle orchestrator
uses as its pre-ship gate and that regular CI uses for regression
against shipped drops. Each test is parameterized via ARTIFACT_PATH
env var — without it, the whole cycle/ suite skips so npm test
on PRs stays clean.

Helpers:
- tests/helpers/artifact-env.js: loads an archive directory's
  index.html + referenced JS/CSS into JSDOM with no-op polyfills
  for IntersectionObserver/ResizeObserver/MutationObserver and
  requestIdleCallback (DC13 — Author should still avoid these in
  content paths, but the test framework doesn't choke on them).
- scripts/cycle/source-scan.js: deny-list scanner for outbound
  fetch/XHR/WebSocket, dynamic script injection, eval/Function,
  navigator.sendBeacon, and <meta http-equiv="CSP"> overrides.
  Skip patterns for known-vendored libraries (xterm, addon-fit,
  aalib, *.min.js).

Smoke tests (all skipped uniformly when meta.legacy: true):
- anchor-facts: ≥90% portfolio + 100% team + firm name + mission
  + contact path (DC6)
- history-entrance: non-hidden archive permalink with text or
  aria-label (DC7)
- archive-size: ≤5MB hard cap, ≤50KB base64 inline (DC4)
- structural: page loads, <title> non-empty, no NEW console errors
  (allowlist for known JSDOM noise), no broken relative paths
- source-scan: invokes scripts/cycle/source-scan.js (DC11)

Verified against /cli/ legacy fixture: 13 tests pass (structural +
archive-size + cli-preservation), 7 skip cleanly (anchor/history/
source-scan exempted under legacy waiver per DC1).
Regular npm test: 37 passed, 13 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The load-bearing unit of the weekly cycle. Marshals inputs, drives
the Author/Editor revision loop, validates output against the JSON
Schema, runs smoke tests, freezes the archive entry. Does not call
the Anthropic API directly — the Author/Editor "stub functions" are
seams that Claude Code's Agent tool replaces at runtime when invoked
from claude-code-action (U6).

Files:
- scripts/cycle/output-schema.js: ajv-compiled JSON Schema for the
  Author press kit (site files, meta with where_facts_live,
  social drafts, theme_keys 1-12, topical flag + hook + brief).
- scripts/cycle/performance-log.js: assembles last-12 archive
  entries; excludes _legacy from the main log; seeds from legacy
  ONLY when no main entries exist (cycle 1 cold-start). null
  ratings are distinct from empty arrays.
- scripts/cycle/freeze-archive.js: writes Author output to
  archive/YYYY-MM-DD/. Replaces {{CSP_NONCE}} placeholders in HTML
  and JS. Initializes empty rating.json [] and engagement.json {}.
  Path-traversal guard against malicious file keys (defense in
  depth against LLM output).
- scripts/cycle/prompts/cycle.md: top-level prompt with autonomy
  posture (DC18), cost ceiling (DC9), U0-validated tools, cycle
  walkthrough, pre-ship invariants, failure-path behavior.
- scripts/cycle/prompts/author-role.md: Author subagent role.
  90% anchor-fact threshold (DC6), JSDOM-compat DO/DON'T list
  (DC13), source-scan deny list (DC11), CSP nonce placeholder
  rules, brand voice / no-fly / topical handling.
- scripts/cycle/prompts/editor-role.md: Editor subagent role.
  8-step structured review checklist, untrusted-content rule
  (prompt-injection defense), JSON output contract.
- scripts/cycle/run-cycle.js: orchestrator (~600 lines). Inlined
  DC9/DC10 constants and 3 notification templates per Scope
  Boundaries (no separate cycle-config.js or notify-templates.js
  in v1). CLI: --dry-run, --artifact-date, --max-retries,
  --fixture-path. Exit codes 0=success, 2=schema-exhaust,
  3=smoke-exhaust, 4=config-error. Last stdout line is structured
  JSON for the GH Action to parse.
- tests/cycle/orchestrator.test.js: 34 tests covering happy path,
  schema/editor/smoke retry threading, retry exhaustion, perf-log
  windowing, dry-run mode, exported constants.

Also: tests/cycle/anchor-facts.test.js portfolio loader cleanup
(config/portfolio.js has no module.exports; use the existing vm
loader uniformly rather than relying on require() returning truthy
empty {}).

71 passed regular tests, 47 passed smoke tests against /cli/, dry
run end-to-end emits structured JSON summary correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Optional pre-Author step that surfaces 3-5 things happening in tech
this week. At runtime, claude-code-action's Agent tool invokes the
fetcher subagent (using scripts/cycle/prompts/topical-fetcher.md);
this module owns the schema, injection-pattern sanitizer, and
fail-soft contract.

scripts/cycle/topical-fetcher.js:
- INJECTION_PATTERNS: 10 regex patterns matching SYSTEM:/ASSISTANT:/
  IGNORE PRIOR / [INST] / <instruction> / <|im_start|> shapes the
  fetcher might mistakenly reproduce from adversarial source content
- sanitize(): drops bullets matching patterns, strips quote marks
  and inline HTML, collapses to empty brief if fewer than 2 bullets
  survive (half-empty briefs are worse than no brief)
- fetchTopicalBrief(): the seam — accepts an optional invokeFetcher
  callback that runs the real subagent at runtime; returns the
  EMPTY_BRIEF on missing/throwing/null invocations (fail-soft)
- Exports buildEmptyBrief() for explicit "no signal this week" cases

scripts/cycle/prompts/topical-fetcher.md:
- Query taxonomy: HN front page → X/Twitter → Reddit → tech press
- Time window targeting 1-3 days (acceptable up to 7)
- DC8 sensitivity classification (hard_block / soft_block / approve
  / lean_in) per the 4-tier rubric
- Output contract: strict JSON, no preamble, no markdown fences
- Explicit prompt-injection-defense clauses: summarize in own
  words, no URLs in brief, no quoted headlines, no public-figure
  personal names

scripts/cycle/run-cycle.js:
- fetchTopicalBrief stub replaced with a delegate to
  topical-fetcher.js. Runtime invocation seam preserved (overrides
  pass invokeFetcher; dry-run gets empty brief).

22 new tests pass. Total: 93 regular tests, 69 smoke tests against
/cli/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, U8)

U6 — .github/workflows/weekly-cycle.yml:
- on: schedule cron (Monday 01:00 UTC = Sun 17:00 PST / 18:00 PDT)
  + workflow_dispatch with max_retries + artifact_date inputs
- DC3: concurrency group 'weekly-cycle', cancel-in-progress: false
- DC9: timeout-minutes: 45 (wall-clock), claude_args --max-turns 60
- DC15: explicit permissions block (contents/pull-requests/issues:
  write); default GITHUB_TOKEN, no PAT needed
- DC14: claude-code-action@v1 (SHA-pinning called out in comment;
  pin to a specific SHA before turning on the production cron)
- Steps: checkout main + capture SHA → npm ci → create dated branch
  → claude-code-action runs scripts/cycle/prompts/cycle.md → inspect
  artifact → smoke tests (ARTIFACT_PATH=archive/YYYY-MM-DD) →
  detect main-moved race → commit + push + gh pr create with
  --auto --squash → open success or failure issue
- On race / failure: opens "Cycle failed: YYYY-MM-DD" issue, deletes
  the cycle branch best-effort

U7 — Rating + engagement signal schemas:
- scripts/cycle/rating-schema.js: append-only array of
  {rater, rating: 1-5, note?, ts: ISO8601}. Multiple entries from
  same rater allowed; additionalProperties: false to catch typos.
- scripts/cycle/engagement-schema.js: structured object covering
  twitter, twitter_link_shares, hn (url+score+comments), linkedin,
  reddit array (with subreddit pattern), manual_notes,
  last_updated. Both validated with ajv + ajv-formats (added to
  devDependencies).
- tests/cycle/rating-engagement-schema.test.js: 12 tests covering
  happy paths, range violations, missing required fields,
  malformed timestamps, additionalProperties: false, etc.

U8 — Notification rendering wired up:
- scripts/cycle/run-cycle.js: renderTemplate(template, vars) helper
  + export. Substitutes \${key} placeholders; unknown keys render
  as "(none)" so the workflow can't silently leak placeholder
  syntax into a user-visible PR or issue body. Templates remain
  inlined (per Scope Boundaries — promote to notify-templates.js
  when first changed).
- weekly-cycle.yml node -e blocks use renderTemplate + the inlined
  PR_BODY_TEMPLATE / SUCCESS_ISSUE_TEMPLATE / FAILURE_ISSUE_TEMPLATE
  to fill PR descriptions and issue bodies.
- Failure template includes the manual rollback recipe (git revert
  <merge-sha> && git push) per DC5 — v1 doesn't ship a dedicated
  rollback workflow.

12 new tests pass; total 105 passed, 13 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Pin all three workflows (claude.yml, spike.yml, weekly-cycle.yml)
  to claude-code-action@537ffff (v1 tag SHA at time of writing).
  DC14 supply-chain hardening — floating @v1 tag could be
  force-updated maliciously.
- vercel.json: add buildCommand + outputDirectory: '.'. The Vercel
  default looks for a 'public' directory after build; this repo
  publishes from root (matches the prior Netlify publish = '.').
- .gitignore: vercel link auto-added .vercel/ exclusion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The user-facing /game.html route mounts a packet-sorting game built
from the same data layer the CLI surfaces (portfolio, team, jobs,
GeoCities artifacts). welcome.htm gains a "PLAY ROOT ROUTER" link
between the firm header and the portfolio table.

Also bundled in this commit (intentionally; they share files):
- config/commands.js: new `game` command opens game.html in the
  current tab; the existing `apply` command's fetch target updates
  from /.netlify/functions/submit-application to /api/submit-application
  to match the Vercel API Route landed in the previous commit.
- config/help.js: new help entry for the game command.
- js/app.bundle.js: rebuilt against the updated sources (firm.js +
  the commands.js changes).
- root-router-*.png: design/QA screenshots saved alongside the
  in-progress work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restructures /cli/ as the first canonical entry in the history catalog
and adds a second entry for the GeoCities skin. Each archive entry is
a fully-vendored standalone copy of that era's website: all referenced
JS, CSS, configs, images, and favicon live inside the archive
directory, so visiting /archive/_legacy/01-cli-terminal/ or
/archive/_legacy/02-geocities/ yields a complete working page that
doesn't depend on the current repo root state drifting.

Changes:
- archive/_legacy/01-cli-terminal/ (moved from /cli/) — the original
  CLI terminal site (date_first_shipped: 2021-02-09, sort_order: 1)
- archive/_legacy/02-geocities/ — pre-game version of welcome.htm
  (date_first_shipped: 2021-10-28, sort_order: 2) with all referenced
  CSS, JS, configs, images/geo/* vendored. Back link rewritten to
  point to root (/) instead of the broken in-archive index.html.
  Favicon path rewritten to relative.
- archive/_legacy/{01-*,02-*}/meta.json — sort_order, date_first_
  shipped, where_facts_live, history_view_concept, theme_keys.
  Both flagged legacy: true so cycle smoke tests waive R3 (DC1).
- tests/cycle/legacy-archive.test.js — replaces cli-preservation.test.js;
  asserts structural presence, valid HTML, sort_order chronology,
  no broken navigation refs.
- Original /welcome.htm and /game.html URLs preserved at repo root
  for existing inbound links.

112 regular tests pass; 88 smoke tests pass against either archive
(legacy waivers skip the cycle-specific anchor/history/source-scan
checks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli-website Ready Ready Preview, Comment Jun 1, 2026 10:34pm

@ledwards ledwards merged commit 100fa73 into main Jun 1, 2026
8 checks passed
@ledwards ledwards deleted the weekly-ai-reinvention branch June 1, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant