feat: weekly AI-reinvented root.vc + Vercel migration#110
Merged
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two coordinated shifts landing together:
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_URLis already configured on the Vercel project (production, preview, development).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
.github/workflows/spike.ymlvalidatesclaude-code-action@v1capabilities (Agent tool, /last30days or WebSearch fallback, Write, Bash, schedule trigger, token observability). Run via Actions UI before enabling the cron.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)./cli/is a fully-vendored snapshot of the existing CLI terminal so it survives cycle 1's replacement of rootindex.html. Existing/welcome.htmand/game.htmlURLs stay in place.tests/cycle/{anchor-facts,history-entrance,archive-size,structural,source-scan}.test.js+tests/helpers/artifact-env.js+scripts/cycle/source-scan.js. Parameterized viaARTIFACT_PATH; legacy entries waived per DC1.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.scripts/cycle/topical-fetcher.js+scripts/cycle/prompts/topical-fetcher.md. Injection-pattern sanitizer (10 regex patterns); fail-soft on missing/throwing/null invocations..github/workflows/weekly-cycle.yml. Monday 01:00 UTC cron (Sun 17:00 PST / 18:00 PDT). Two-step:claude-code-actionrun →gh pr create --auto --squash. Explicitpermissions:block (DC15); concurrency group (DC3); race detection against main HEAD.scripts/cycle/{rating,engagement}-schema.js+ tests. Direct file-edit only in v1.run-cycle.js. Rendered viarenderTemplate(template, vars).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-applicationfetchupdated to/api/submit-application.Test plan
npm test→ 105 passed, 13 skipped (cycle tests skip cleanly withoutARTIFACT_PATH)ARTIFACT_PATH=cli npx vitest run tests/cycle/→ 81 passed, 7 skipped (legacy waiver works)npm run build→ bundle succeeds withfirm.jsintegratedclaude-code-action@v1capabilitiesweekly-cycle.ymlviaworkflow_dispatchafter merge — confirms end-to-end before enabling the cronStill requires manual setup (post-merge)
netlify.tomlandnetlify/in a follow-up PR.ANTHROPIC_API_KEY(DC15 hard backstop independent of DC9's turn cap)..github/CODEOWNERS— replace@rootvc-teamplaceholder with your real GitHub org/team handle.main— requiretestworkflow to pass before merge so auto-merge respects it.What's intentionally deferred
rollback.ymlworkflow,/ratecomment-bot workflow, full/archive/_legacy/migration ofwelcome.htm+game.html, standalonecycle-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