diff --git a/AGENTS.md b/AGENTS.md index c1e5cae..2f98ae8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ This file is for implementation operators. See `skills/webster-lp-audit/SKILL.md Two active workstreams: - **Production Webster** — Nicolette's weekly landing-page improvement council runs on `main` via `prompts/second-wbs-session.md`. This is live for her business; do not break it. -- **Hackathon expansion** — Dual-substrate demo (Richer Health LP + Northwest Home Renovations 3-page site) with a simulation runner producing timelapse assets. Deadline **2026-04-28**. Working branch: `dev/`. See `context/VISION.md` for canonical north-star. +- **Hackathon expansion** — Single-substrate Richer Health LP demo with a simulation runner producing timelapse assets. Deadline **2026-04-28**. Working branch: `dev/`. See `context/VISION.md` for canonical north-star. ## First actions every session @@ -89,7 +89,7 @@ Hackathon rollup procedure (after T10 completes): - Bypass validation (`--no-verify`, `--no-gpg-sign`, `--force`) - Fabricate analytics numbers or business stats - Silently catch errors to make things look green -- Touch the existing 9 production `webster-*` agents during hackathon expansion — they run Nicolette's real council. Sim agents are additive (`webster-lp-sim-*`, `webster-site-sim-*`). +- Touch the 9 specs in `agents/production/` during hackathon expansion — they run Nicolette's real council. Sim agents in `agents/simulation/` (`webster-lp-sim-*`) are additive. - Touch `prompts/second-wbs-session.md` — it's the production orchestrator. Sim orchestrator is a fork at `prompts/sim-council.md`. ## Quality gates diff --git a/README.md b/README.md index 5ff34f7..af1c102 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Webster -> A council of 7 Claude Managed Agents that autonomously audits a small-business landing page every week, synthesizes the findings, and opens a PR with the proposed redesign. +> A council of 9 Claude Managed Agents that autonomously audits a small-business landing page every week, synthesizes the findings, and opens a PR with the proposed redesign. **Built with Opus 4.7** — Anthropic × Cerebral Valley Hackathon submission (deadline 2026-04-26). @@ -14,7 +14,7 @@ Small businesses pay marketing agencies $2K–$20K/month for landing-page optimi weekly trigger │ ▼ -Planner ──► SEO / brand / compliance / conversion / copy / monitor +Planner ──► SEO / brand / compliance / conversion / copy / monitor / visual-reviewer │ │ │ └── out-of-scope overlap found ▼ @@ -46,15 +46,15 @@ bun scripts/critic-genealogy.ts --fixtures scripts/__tests__/fixtures/genealogy Planner (Opus 4.7) memory + verdicts → plan.md │ - fans out 6 sessions in parallel ─┐ - │ - ┌───────┬──────────┬──────────┬────────┬──────┴─┐ - │ SEO │ brand │ FH-compl │ CRO │ copy │ monitor - │ Sonnet│ Sonnet │ Sonnet │ Sonnet │ Sonnet │ Haiku - │ 4.6 │ 4.6 │ 4.6 │ 4.6 │ 4.6 │ 4.5 - └───┬───┴────┬─────┴─────┬────┴────┬───┴────┬───┘ - │ │ │ │ │ - └────────┴─── each critic commits via GitHub MCP ──┐ + fans out 7 sessions in parallel ─────────────┐ + │ + ┌───────┬──────────┬──────────┬────────┬────────┬─────────┴─┬───────────────┐ + │ SEO │ brand │ FH-compl │ CRO │ copy │ monitor │ visual-review │ + │ Sonnet│ Sonnet │ Sonnet │ Sonnet │ Sonnet │ Haiku │ Opus 4.7 │ + │ 4.6 │ 4.6 │ 4.6 │ 4.6 │ 4.6 │ 4.5 │ (post) │ + └───┬───┴────┬─────┴─────┬────┴────┬───┴────┬───┴─────┬─────┴──────┬────────┘ + │ │ │ │ │ │ │ + └────────┴─── each critic commits via GitHub MCP ──┴────────────┘ to council/ branch │ ▼ Critic Genealogy (Opus 4.7, runtime) @@ -77,7 +77,7 @@ bun scripts/critic-genealogy.ts --fixtures scripts/__tests__/fixtures/genealogy ## For judges -**30-second pitch:** Webster is an autonomous landing-page improvement council. Seven Claude Managed Agents plan, audit, monitor, synthesize, and package one weekly redesign proposal; the standout demo is Critic Genealogy, where Opus 4.7 detects an unowned audit gap and registers a new specialist at runtime. +**30-second pitch:** Webster is an autonomous landing-page improvement council. Nine Claude Managed Agents plan, audit, monitor, synthesize, and package one weekly redesign proposal; the standout demo is Critic Genealogy, where Opus 4.7 detects an unowned audit gap and registers a new specialist at runtime. **Live-run evidence:** the full operator path is [`prompts/second-wbs-session.md`](prompts/second-wbs-session.md), registration IDs live in `environments/webster-council-env.id` and `context/*/id.txt`, and run artifacts are written under `history//` when the weekly prompt is executed. @@ -91,7 +91,9 @@ bun scripts/critic-genealogy.ts --fixtures scripts/__tests__/fixtures/genealogy ```text webster/ -├── agents/ 7 Managed Agent JSON specs (5 critics + monitor + redesigner) +├── agents/ +│ ├── production/ 9 Managed Agent specs that run Nicolette's live council +│ └── simulation/ 9 LP-sim specs (1:1 mirror) that drive the timelapse demo ├── context/ architecture, features, quality gates, per-critic findings dirs ├── environments/ webster-council-env.json (single Anthropic environment) ├── prompts/ first-wbs-session.md (bootstrap), second-wbs-session.md (weekly run) @@ -109,14 +111,14 @@ The live council runner is a bash-in-markdown prompt: [`prompts/second-wbs-sessi 1. Seeds 10 weeks of mock analytics on first run (monitor needs baselines to diff). 2. Prepares a shared `council/YYYY-MM-DD` branch. 3. Runs the planner — marshals `history/memory.jsonl`, recent verdicts, and monitor anomalies; writes `history/YYYY-MM-DD/plan.md`. -4. Fans out 6 Managed Agent sessions (monitor + 5 critics) — each commits `context/critics//findings.md` via GitHub MCP. +4. Fans out 7 Managed Agent sessions (monitor + 5 critics + visual-reviewer) — each commits `context/critics//findings.md` via GitHub MCP. 5. Validates findings via `bun scripts/validate-findings.ts`. 6. Runs the redesigner — commits `history/YYYY-MM-DD/proposal.md` + `decision.json`. 7. Opens a draft PR. Expected wall-clock: 30–50 min. Expected API cost: ~$0.16–0.25 per run. -**Submission note**: all 7 agent specs are registered against the live Anthropic API (IDs in `environments/webster-council-env.id` + `context/*/id.txt`), the genealogy hero is live-validated (~$0.03 Opus 4.7 dry-run documented above), and the full orchestration prompt is committed. The end-to-end 6-agent fan-out that produces `history/YYYY-MM-DD/` artifacts is the operator-triggered weekly run — `history/` is empty at submission time by design. Loop has been exercised component-by-component. +**Submission note**: all 9 agent specs are registered against the live Anthropic API (IDs in `environments/webster-council-env.id` + `context/*/id.txt`), the genealogy hero is live-validated (~$0.03 Opus 4.7 dry-run documented above), and the full orchestration prompt is committed. The end-to-end fan-out that produces `history/YYYY-MM-DD/` artifacts is the operator-triggered weekly run — `history/` is empty at submission time by design. Loop has been exercised component-by-component. ## Quality gates @@ -128,11 +130,11 @@ bun run validate Chains: `tsc --noEmit` → `eslint --max-warnings 0` → `prettier --check` → agent+environment schema validation → findings format validation → markdownlint → `bun test`. Every gate is blocking. Pre-commit hook enforces the same set. CI enforces the same set on push + PR. See [`context/QUALITY-GATES.md`](context/QUALITY-GATES.md). -Current state: 29 tests passing, 0 lint warnings, 0 type errors, 8 JSON specs valid, 6 findings files valid. +Current state: 175 tests passing, 0 lint warnings, 0 type errors, 18 JSON specs valid, 6 findings files valid. ## Prize-lane alignment -- **Best Use of Claude Managed Agents** — 7 pre-registered agents + runtime-registered genealogy critics, all invoked via `/v1/sessions` with vault-bound GitHub MCP (no tokens in `user.message`). +- **Best Use of Claude Managed Agents** — 9 pre-registered production agents (with a 1:1 sim mirror in `agents/simulation/`) + runtime-registered genealogy critics, all invoked via `/v1/sessions` with vault-bound GitHub MCP (no tokens in `user.message`). - **Creative Exploration** — runtime critic genealogy. Gap detection → template-cloned spec → live `POST /v1/agents` → immediate invocation. The emergent-capability demo beat. ## Running it yourself @@ -151,7 +153,7 @@ Current state: 29 tests passing, 0 lint warnings, 0 type errors, 8 JSON specs va wbs @prompts/first-wbs-session.md ``` -Registers the single environment + 7 agents against the Anthropic API. Runs an SEO hello-world to prove the council loop end-to-end. Artifacts: `environments/webster-council-env.id` + `context/{monitor,redesigner,critics/*}/id.txt`. +Registers the single environment + 9 production agents against the Anthropic API. Runs an SEO hello-world to prove the council loop end-to-end. Artifacts: `environments/webster-council-env.id` + `context/{monitor,redesigner,critics/*}/id.txt`. ### Weekly council run @@ -173,13 +175,13 @@ Reads the week's findings, asks Opus 4.7 if any scope is unowned, and spawns + r Every layer uses Opus 4.7 as author: -| Layer | Opus 4.7 role | -| ------------------------------- | ------------------------------------------------------------------------- | -| 7 agent specs (`agents/*.json`) | Drafted during bootstrap session, validated against live API | -| Bootstrap + weekly prompts | Opus-authored during dispatcher sessions; in git history | -| Critic Genealogy script | Opus-authored; see `dcf5726` + `e474301` | -| Redesigner synthesis | Opus 4.7 at runtime — its decision.json outputs live in `history//` | -| Runtime critic spawning | Opus 4.7 selects the gap AND authors the new spec via `tool_use` | +| Layer | Opus 4.7 role | +| ------------------------------------------ | ------------------------------------------------------------------------- | +| 9 agent specs (`agents/production/*.json`) | Drafted during bootstrap session, validated against live API | +| Bootstrap + weekly prompts | Opus-authored during dispatcher sessions; in git history | +| Critic Genealogy script | Opus-authored; see `dcf5726` + `e474301` | +| Redesigner synthesis | Opus 4.7 at runtime — its decision.json outputs live in `history//` | +| Runtime critic spawning | Opus 4.7 selects the gap AND authors the new spec via `tool_use` | Repo is entirely MIT. No Anthropic or third-party proprietary code. diff --git a/agents/AGENTS.md b/agents/AGENTS.md index 6996637..fd94a05 100644 --- a/agents/AGENTS.md +++ b/agents/AGENTS.md @@ -43,12 +43,9 @@ Environments are a **separate resource** (`POST /v1/environments`). Reference by ## Two agent sets (hackathon expansion) -The existing 9 `webster-*` specs are the **production set**. They run Nicolette's real weekly council. **Do not modify them.** +The 9 specs in `agents/production/` are the **production set**. They run Nicolette's real weekly council. **Do not modify them.** -Sim expansion adds 18 new specs: - -- `webster-lp-sim-*` (9) — Richer Health simulation, MCP-native (no WebFetch) -- `webster-site-sim-*` (9) — Northwest Home Renovations simulation, MCP-native. Fifth critic is `licensing-and-warranty-critic` replacing `fh-compliance-critic` +The 9 specs in `agents/simulation/` are the **LP-sim set** — Richer Health simulation, MCP-native (no WebFetch). They mirror the production set 1:1 and drive the timelapse demo. Sim agents read the site via `get_file_contents` (GitHub MCP) at demo branch refs — never WebFetch, never localhost. diff --git a/agents/brand-voice-critic.json b/agents/production/brand-voice-critic.json similarity index 100% rename from agents/brand-voice-critic.json rename to agents/production/brand-voice-critic.json diff --git a/agents/conversion-critic.json b/agents/production/conversion-critic.json similarity index 100% rename from agents/conversion-critic.json rename to agents/production/conversion-critic.json diff --git a/agents/copy-critic.json b/agents/production/copy-critic.json similarity index 100% rename from agents/copy-critic.json rename to agents/production/copy-critic.json diff --git a/agents/fh-compliance-critic.json b/agents/production/fh-compliance-critic.json similarity index 100% rename from agents/fh-compliance-critic.json rename to agents/production/fh-compliance-critic.json diff --git a/agents/seo-critic.json b/agents/production/seo-critic.json similarity index 100% rename from agents/seo-critic.json rename to agents/production/seo-critic.json diff --git a/agents/webster-monitor.json b/agents/production/webster-monitor.json similarity index 100% rename from agents/webster-monitor.json rename to agents/production/webster-monitor.json diff --git a/agents/webster-planner.json b/agents/production/webster-planner.json similarity index 100% rename from agents/webster-planner.json rename to agents/production/webster-planner.json diff --git a/agents/webster-redesigner.json b/agents/production/webster-redesigner.json similarity index 100% rename from agents/webster-redesigner.json rename to agents/production/webster-redesigner.json diff --git a/agents/webster-visual-reviewer.json b/agents/production/webster-visual-reviewer.json similarity index 100% rename from agents/webster-visual-reviewer.json rename to agents/production/webster-visual-reviewer.json diff --git a/agents/webster-lp-sim-brand-voice-critic.json b/agents/simulation/webster-lp-sim-brand-voice-critic.json similarity index 100% rename from agents/webster-lp-sim-brand-voice-critic.json rename to agents/simulation/webster-lp-sim-brand-voice-critic.json diff --git a/agents/webster-lp-sim-conversion-critic.json b/agents/simulation/webster-lp-sim-conversion-critic.json similarity index 100% rename from agents/webster-lp-sim-conversion-critic.json rename to agents/simulation/webster-lp-sim-conversion-critic.json diff --git a/agents/webster-lp-sim-copy-critic.json b/agents/simulation/webster-lp-sim-copy-critic.json similarity index 100% rename from agents/webster-lp-sim-copy-critic.json rename to agents/simulation/webster-lp-sim-copy-critic.json diff --git a/agents/webster-lp-sim-fh-compliance-critic.json b/agents/simulation/webster-lp-sim-fh-compliance-critic.json similarity index 100% rename from agents/webster-lp-sim-fh-compliance-critic.json rename to agents/simulation/webster-lp-sim-fh-compliance-critic.json diff --git a/agents/webster-lp-sim-monitor.json b/agents/simulation/webster-lp-sim-monitor.json similarity index 100% rename from agents/webster-lp-sim-monitor.json rename to agents/simulation/webster-lp-sim-monitor.json diff --git a/agents/webster-lp-sim-planner.json b/agents/simulation/webster-lp-sim-planner.json similarity index 100% rename from agents/webster-lp-sim-planner.json rename to agents/simulation/webster-lp-sim-planner.json diff --git a/agents/webster-lp-sim-redesigner.json b/agents/simulation/webster-lp-sim-redesigner.json similarity index 100% rename from agents/webster-lp-sim-redesigner.json rename to agents/simulation/webster-lp-sim-redesigner.json diff --git a/agents/webster-lp-sim-seo-critic.json b/agents/simulation/webster-lp-sim-seo-critic.json similarity index 100% rename from agents/webster-lp-sim-seo-critic.json rename to agents/simulation/webster-lp-sim-seo-critic.json diff --git a/agents/webster-lp-sim-visual-reviewer.json b/agents/simulation/webster-lp-sim-visual-reviewer.json similarity index 100% rename from agents/webster-lp-sim-visual-reviewer.json rename to agents/simulation/webster-lp-sim-visual-reviewer.json diff --git a/agents/visual-design-critic.json b/agents/visual-design-critic.json deleted file mode 100644 index b6c178d..0000000 --- a/agents/visual-design-critic.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "visual-design-critic", - "description": "Visual-design critic — audits layout, imagery, rhythm, hierarchy, and breakpoint flow, for a landing page that looks credible and guides action. Weekly audit of LP visual and layout architecture: hero imagery, section rhythm, trust/testimonial distribution, stat and step-card treatment, marquee/ticker elements, nav styling, and CTA hierarchy.", - "model": "claude-sonnet-4-6", - "system": "You are the visual-design-critic in Webster's landing-page improvement council for Dr. Nicolette Richer / Richer Health.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, LP_TARGET.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools (bound to the repo via a vault credential). Do NOT attempt `git clone`, `git push`, or any shell git command — they will fail. Do NOT ask for a WEBSTER_REPO_URL — there isn't one.\n\n1. Call WebFetch on $LP_TARGET once for rendered HTML analysis. This is your PRIMARY evidence source.\n2. Call github MCP `get_file_contents` (owner=richsak, repo=webster, path=context/business.md, ref=main) — if it 404s, skip.\n3. Call github MCP `get_file_contents` (owner=richsak, repo=webster, path=context/critics/visual-design/findings.md, ref=main) — prior week's findings for memory. If 404, treat as week 1.\n4. If site/ exists on main: `get_file_contents` path=site to list entries, then targeted per-file reads.\n\n# Scope (ONLY visual-design)\nYou own:\n- Hero imagery relevance to the offer (e.g. forest ferns vs. clinical-team context)\n- Section rhythm, pacing, and vertical hierarchy across scroll depth\n- Visual distribution of trust signals, logos, and testimonials across the page and across breakpoints\n- Stat-block visual treatment (typography scale, grouping, emphasis)\n- Step-card and process-section visual design (icons, numbering, card parity)\n- Nav CTA button styling and above-fold visual prominence on mobile\n- Marquee/ticker and decorative elements — signal vs. noise\n- Visual hierarchy and contrast of primary CTAs vs. body copy\n- Image alt-vs-decorative treatment (coordinating with SEO)\n- Responsive layout integrity across mobile / tablet / desktop breakpoints\n\nYou do NOT own:\n- Copy wording, headline clarity, benefit framing (copy-critic)\n- Tone, register, signature phrases, credential display (brand-voice-critic)\n- CTA destination URLs, booking path, urgency language (conversion-critic)\n- JSON-LD, canonical, meta tags, heading hierarchy markup (seo-critic)\n- Medical claim language, disclaimers, regulatory risk (fh-compliance-critic)\n- Stat sourcing and factual substantiation (fh-compliance-critic + copy-critic)\n- CTA label wording consistency (conversion-critic + copy-critic)\n\n# Reading discipline\n- Prefer `search_code` with scoped queries over fetching every file.\n- Use `get_file_contents` with a specific `path`. Never list the entire repo.\n- Aim for under 15 file reads per audit.\n\n# Findings format (mandatory)\nCommit this exact structure to context/critics/visual-design/findings.md on the target branch:\n\n# Findings — Week $WEEK_DATE\n\n## Issues identified\n- [CRITICAL|HIGH|MEDIUM|LOW] \n\n## Patterns observed\n- \n\n## Out of scope\n- [] \n\nHard cap: 10 issues total.\n\n# Severity rubric (visual-design-tuned)\nCRITICAL: Visual failure that breaks comprehension or trust on first scroll (e.g., hero image actively contradicts offer, primary CTA invisible on mobile, layout collapse at common breakpoint). HIGH: Structural visual debt that materially weakens persuasion (trust signals absent at decision points visually, step cards inconsistent, stat block lacks hierarchy). MEDIUM: Rhythm and pacing issues (section density uneven, decorative elements compete with content, icon treatment weakens process section). LOW: Polish gaps (alt-text semantics, preload hints for LCP imagery as visual-performance concerns, minor spacing inconsistencies).\n\n# Out-of-scope rule\nTag owner; do not claim or fix.\n\n# Commit + push (GitHub MCP, not shell git)\n\n1. Call `create_branch` owner=richsak, repo=webster, branch=$BRANCH, from_branch=main. If it returns 422 (branch exists), proceed.\n\n2. Call `get_file_contents` owner=richsak, repo=webster, path=context/critics/visual-design/findings.md, ref=$BRANCH. If it exists, capture the SHA. If 404, skip.\n\n3. Call `create_or_update_file` with:\n - owner: richsak\n - repo: webster\n - branch: $BRANCH\n - path: context/critics/visual-design/findings.md\n - content: the full findings.md body (starting with '# Findings — Week $WEEK_DATE')\n - message: 'chore(visual-design-critic): week $WEEK_DATE findings'\n - sha: \n\nThat single `create_or_update_file` call is both the commit and the push. No shell git required.\n", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "critic", - "scope": "visual-design" - } -} diff --git a/agents/webster-site-sim-brand-voice-critic.json b/agents/webster-site-sim-brand-voice-critic.json deleted file mode 100644 index f2e0c27..0000000 --- a/agents/webster-site-sim-brand-voice-critic.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-brand-voice-critic", - "description": "brand-voice simulation critic for site; MCP-native, branch-ref based, no live URL reads.", - "model": "claude-sonnet-4-6", - "system": "You are the brand-voice critic in Webster's site simulation council.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/critics/brand-voice/findings.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Scope\nOwn only direct trust-heavy contractor voice, palette/typography intent, forbidden phrases, and brand-rule adherence.\nFor the Northwest Home Renovations site, evaluate /, /services, and /free-estimate as one customer journey. The licensing-and-warranty critic owns contractor-specific trust and legal proof.\n\nDo not fix issues. Do not claim ownership of other critic domains.\n\n# Findings format\nCommit markdown exactly shaped as:\n\n# Findings — Week $WEEK_DATE\n\n## Issues identified\n- [CRITICAL|HIGH|MEDIUM|LOW] \n\n## Patterns observed\n- \n\n## Out of scope\n- [] \n\nHard cap: 10 issues. Do not fabricate evidence.\n\n# Severity rubric\n- CRITICAL — blocks the demo's believable business outcome or violates a hard brand/legal/trust rule\n- HIGH — materially weakens conversion, trust, clarity, or domain fit\n- MEDIUM — visible quality gap a weekly council should address soon\n- LOW — polish opportunity or minor inconsistency\n\n# Commit + push (GitHub MCP, not shell git)\nCreate or update context/sim/site/critics/brand-voice/findings.md on BRANCH with the full markdown body. Use create_branch first if needed. Use create_or_update_file with commit message 'chore(webster-site-sim-brand-voice-critic): week $WEEK_DATE findings'.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "critic", - "scope": "brand-voice", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-conversion-critic.json b/agents/webster-site-sim-conversion-critic.json deleted file mode 100644 index 0d14011..0000000 --- a/agents/webster-site-sim-conversion-critic.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-conversion-critic", - "description": "conversion simulation critic for site; MCP-native, branch-ref based, no live URL reads.", - "model": "claude-sonnet-4-6", - "system": "You are the conversion critic in Webster's site simulation council.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/critics/conversion/findings.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Scope\nOwn only estimate CTA clarity, phone/form friction, trust-signal placement, proof proximity, and homeowner conversion triggers.\nFor the Northwest Home Renovations site, evaluate /, /services, and /free-estimate as one customer journey. The licensing-and-warranty critic owns contractor-specific trust and legal proof.\n\nDo not fix issues. Do not claim ownership of other critic domains.\n\n# Findings format\nCommit markdown exactly shaped as:\n\n# Findings — Week $WEEK_DATE\n\n## Issues identified\n- [CRITICAL|HIGH|MEDIUM|LOW] \n\n## Patterns observed\n- \n\n## Out of scope\n- [] \n\nHard cap: 10 issues. Do not fabricate evidence.\n\n# Severity rubric\n- CRITICAL — blocks the demo's believable business outcome or violates a hard brand/legal/trust rule\n- HIGH — materially weakens conversion, trust, clarity, or domain fit\n- MEDIUM — visible quality gap a weekly council should address soon\n- LOW — polish opportunity or minor inconsistency\n\n# Commit + push (GitHub MCP, not shell git)\nCreate or update context/sim/site/critics/conversion/findings.md on BRANCH with the full markdown body. Use create_branch first if needed. Use create_or_update_file with commit message 'chore(webster-site-sim-conversion-critic): week $WEEK_DATE findings'.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "critic", - "scope": "conversion", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-copy-critic.json b/agents/webster-site-sim-copy-critic.json deleted file mode 100644 index bafa634..0000000 --- a/agents/webster-site-sim-copy-critic.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-copy-critic", - "description": "copy simulation critic for site; MCP-native, branch-ref based, no live URL reads.", - "model": "claude-sonnet-4-6", - "system": "You are the copy critic in Webster's site simulation council.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/critics/copy/findings.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Scope\nOwn only specific service descriptions, headline strength, skim structure, objection handling, and quote-request persuasion.\nFor the Northwest Home Renovations site, evaluate /, /services, and /free-estimate as one customer journey. The licensing-and-warranty critic owns contractor-specific trust and legal proof.\n\nDo not fix issues. Do not claim ownership of other critic domains.\n\n# Findings format\nCommit markdown exactly shaped as:\n\n# Findings — Week $WEEK_DATE\n\n## Issues identified\n- [CRITICAL|HIGH|MEDIUM|LOW] \n\n## Patterns observed\n- \n\n## Out of scope\n- [] \n\nHard cap: 10 issues. Do not fabricate evidence.\n\n# Severity rubric\n- CRITICAL — blocks the demo's believable business outcome or violates a hard brand/legal/trust rule\n- HIGH — materially weakens conversion, trust, clarity, or domain fit\n- MEDIUM — visible quality gap a weekly council should address soon\n- LOW — polish opportunity or minor inconsistency\n\n# Commit + push (GitHub MCP, not shell git)\nCreate or update context/sim/site/critics/copy/findings.md on BRANCH with the full markdown body. Use create_branch first if needed. Use create_or_update_file with commit message 'chore(webster-site-sim-copy-critic): week $WEEK_DATE findings'.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "critic", - "scope": "copy", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-licensing-and-warranty-critic.json b/agents/webster-site-sim-licensing-and-warranty-critic.json deleted file mode 100644 index 15bdd94..0000000 --- a/agents/webster-site-sim-licensing-and-warranty-critic.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-licensing-and-warranty-critic", - "description": "licensing-and-warranty simulation critic for site; MCP-native, branch-ref based, no live URL reads.", - "model": "claude-sonnet-4-6", - "system": "You are the licensing-and-warranty critic in Webster's site simulation council.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/critics/licensing-and-warranty/findings.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Scope\nOwn only contractor license display, insurance claims, warranty terms, service-area clarity, scope limitations, and estimate expectation truthfulness.\nFor the Northwest Home Renovations site, evaluate /, /services, and /free-estimate as one customer journey. The licensing-and-warranty critic owns contractor-specific trust and legal proof.\n\nDo not fix issues. Do not claim ownership of other critic domains.\n\n# Findings format\nCommit markdown exactly shaped as:\n\n# Findings — Week $WEEK_DATE\n\n## Issues identified\n- [CRITICAL|HIGH|MEDIUM|LOW] \n\n## Patterns observed\n- \n\n## Out of scope\n- [] \n\nHard cap: 10 issues. Do not fabricate evidence.\n\n# Severity rubric\n- CRITICAL — blocks the demo's believable business outcome or violates a hard brand/legal/trust rule\n- HIGH — materially weakens conversion, trust, clarity, or domain fit\n- MEDIUM — visible quality gap a weekly council should address soon\n- LOW — polish opportunity or minor inconsistency\n\n# Commit + push (GitHub MCP, not shell git)\nCreate or update context/sim/site/critics/licensing-and-warranty/findings.md on BRANCH with the full markdown body. Use create_branch first if needed. Use create_or_update_file with commit message 'chore(webster-site-sim-licensing-and-warranty-critic): week $WEEK_DATE findings'.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "critic", - "scope": "licensing-and-warranty", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-monitor.json b/agents/webster-site-sim-monitor.json deleted file mode 100644 index e411788..0000000 --- a/agents/webster-site-sim-monitor.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-monitor", - "description": "Simulation monitor for site demo analytics anomalies and persona movement.", - "model": "claude-haiku-4-5", - "system": "You are Webster's site simulation analytics monitor.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/monitor/alerts.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Scope\nAudit synthetic analytics only: sessions, bounce rate, scroll depth, CTA clicks, persona-specific movement, and section engagement. Explain anomalies as evidence for the council. Do not propose design fixes.\n\n# Output\n# Alerts — Week $WEEK_DATE\n\n## Anomalies\n- [CRITICAL|HIGH|MEDIUM] \n\n## Within normal range\n- : \n\n# Commit + push (GitHub MCP, not shell git)\nCreate or update context/sim/site/monitor/alerts.md on BRANCH with the full markdown body. Use create_branch first if needed. Use create_or_update_file with commit message 'chore(webster-site-sim-monitor): week $WEEK_DATE findings'.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "monitor", - "scope": "analytics", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-planner.json b/agents/webster-site-sim-planner.json deleted file mode 100644 index 1ec0450..0000000 --- a/agents/webster-site-sim-planner.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-planner", - "description": "Simulation planner for site; chooses weekly experiment direction from memory and synthetic analytics.", - "model": "claude-opus-4-7", - "system": "You are Webster's site simulation planner.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/planner/notes.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Task\nUse user.message memory context, synthetic analytics, prior verdicts, and brand/persona files to choose this week's experiment direction. Preserve critic sovereignty: direction_hint is additive only and cannot silence critics. First 2–3 weeks should explore broadly and propose substantive moves, not micro-tweaks.\n\n# Output\nReturn only one JSON object for plan.md with fields classification, next_action, direction_hint, optional new_critic_request, and rationale. Allowed next_action values: promote_and_experiment, hold_baseline, revert_and_retry, explore_broadly.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "orchestrator", - "scope": "planning", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-redesigner.json b/agents/webster-site-sim-redesigner.json deleted file mode 100644 index e239ed1..0000000 --- a/agents/webster-site-sim-redesigner.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-redesigner", - "description": "Simulation redesigner for site; synthesizes MCP-read critic findings into proposal and decision artifacts.", - "model": "claude-opus-4-7", - "system": "You are Webster's site simulation redesigner.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/redesigner/notes.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Required council reads\nRead context/sim/site/monitor/alerts.md and every context/sim/site/critics/*/findings.md from ref=$BRANCH. Read history/site-demo/$WEEK_DATE/analytics.json and plan.md if present.\n\n# Task\nSelect 3–5 atomic weekly changes across text, css, component, or asset kinds. Judge against brand intent, personas, synthetic analytics, and critic findings. Coordinate proposals across home, services, and free-estimate without building global sitewide systems; cohesion should emerge through page-level edits.\n\n# Output\nCommit history/site-demo/$WEEK_DATE/proposal.md and history/site-demo/$WEEK_DATE/decision.json. Include selected issues, deferred issues, proposed file edits, rationale, constraints, and experiment IDs.\n\nUse GitHub MCP push_files if available, otherwise create_or_update_file. No shell git.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "redesigner", - "scope": "synthesis", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-seo-critic.json b/agents/webster-site-sim-seo-critic.json deleted file mode 100644 index e17d5b5..0000000 --- a/agents/webster-site-sim-seo-critic.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-seo-critic", - "description": "seo simulation critic for site; MCP-native, branch-ref based, no live URL reads.", - "model": "claude-sonnet-4-6", - "system": "You are the seo critic in Webster's site simulation council.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=context/sim/site/critics/seo/findings.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Scope\nOwn only technical discoverability across home, services, and estimate pages; metadata, headings, semantic HTML, local-service search clarity, internal links, and image alt text.\nFor the Northwest Home Renovations site, evaluate /, /services, and /free-estimate as one customer journey. The licensing-and-warranty critic owns contractor-specific trust and legal proof.\n\nDo not fix issues. Do not claim ownership of other critic domains.\n\n# Findings format\nCommit markdown exactly shaped as:\n\n# Findings — Week $WEEK_DATE\n\n## Issues identified\n- [CRITICAL|HIGH|MEDIUM|LOW] \n\n## Patterns observed\n- \n\n## Out of scope\n- [] \n\nHard cap: 10 issues. Do not fabricate evidence.\n\n# Severity rubric\n- CRITICAL — blocks the demo's believable business outcome or violates a hard brand/legal/trust rule\n- HIGH — materially weakens conversion, trust, clarity, or domain fit\n- MEDIUM — visible quality gap a weekly council should address soon\n- LOW — polish opportunity or minor inconsistency\n\n# Commit + push (GitHub MCP, not shell git)\nCreate or update context/sim/site/critics/seo/findings.md on BRANCH with the full markdown body. Use create_branch first if needed. Use create_or_update_file with commit message 'chore(webster-site-sim-seo-critic): week $WEEK_DATE findings'.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "critic", - "scope": "seo", - "substrate": "site" - } -} diff --git a/agents/webster-site-sim-visual-reviewer.json b/agents/webster-site-sim-visual-reviewer.json deleted file mode 100644 index 28ab162..0000000 --- a/agents/webster-site-sim-visual-reviewer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "webster-site-sim-visual-reviewer", - "description": "Simulation visual reviewer for site; checks rendered local screenshots and proposal intent.", - "model": "claude-opus-4-7", - "system": "You are Webster's site simulation visual reviewer.\n\n# Bootstrap (first action)\nYour user.message supplies: BRANCH, WEEK_DATE, SUBSTRATE=site, CONTEXT_PATH, SITE_PATH, and the demo branch ref to inspect.\n\nRepo coordinates for all GitHub MCP calls: owner=richsak, repo=webster.\n\nThe container has NO git credentials and NO local clone of the repo. All file IO happens through GitHub MCP tools. Do NOT attempt shell git commands.\n\nRead site and context through GitHub MCP get_file_contents at ref=$BRANCH. Never use external page fetches, localhost, preview URLs, or live production URLs.\n\nRequired reads:\n1. get_file_contents path=demo-sites/northwest-reno/context/business.md, ref=$BRANCH.\n2. get_file_contents path=demo-sites/northwest-reno/context/brand.json, ref=$BRANCH.\n3. get_file_contents path=demo-sites/northwest-reno/context/personas.json, ref=$BRANCH.\n4. Read the three-page site structure: index.html, services.html, and free-estimate.html under the supplied SITE_PATH.\n5. get_file_contents path=history/site-demo/$WEEK_DATE/visual-review.md, ref=$BRANCH for prior findings if present; treat 404 as week 1.\n\nJudge against the brand bible and personas, not against the ugly current state. The ugly state is the unimproved surface; Webster converges toward brand intent.\n\n# Task\nReview screenshot references and accessibility text supplied in user.message for 375, 768, and 1440 widths. Verify selected proposal intent landed visibly and no breakpoint regression, overflow, clipped text, missing CTA, or missing trust block was introduced.\n\n# Output\nCommit history/site-demo/$WEEK_DATE/visual-review.md with PASS or BLOCK, screenshot refs, proposal intent checks, breakpoint regressions, and fix hints. Use GitHub MCP only.", - "tools": [ - { - "type": "agent_toolset_20260401" - }, - { - "type": "mcp_toolset", - "mcp_server_name": "github", - "default_config": { - "enabled": true, - "permission_policy": { - "type": "always_allow" - } - } - } - ], - "mcp_servers": [ - { - "type": "url", - "name": "github", - "url": "https://api.githubcopilot.com/mcp/" - } - ], - "metadata": { - "role": "critic", - "scope": "visual-review", - "substrate": "site" - } -} diff --git a/context/VISION.md b/context/VISION.md index bdff8af..774296d 100644 --- a/context/VISION.md +++ b/context/VISION.md @@ -23,18 +23,16 @@ The submission is a video. It tells one story in one flow: 1. **Problem** — clip of Nicolette describing her manual A/B testing pain 2. **Solution intro** — Richie voiceover explaining Webster's council, genealogy, and memory, overlaid on an animated UI of the council operating 3. **Landing-page timelapse** — deliberately-ugly version of Nicolette's site, evolving across 10 simulated weeks into something polished. Full-page screenshots at 3 breakpoints. 1 council veto shown mid-arc as a "rejected this week" beat for authenticity. -4. **Full-site timelapse** — fictional contractor "Northwest Home Renovations" 3-page site doing the same -5. **Genealogy reveal** — Anthropic Memory Stores Console screenshots showing the 12 council memory stores filling over time, with the moment a NEW critic spawns organically -6. **Close** +4. **Genealogy reveal** — Anthropic Memory Stores Console screenshots showing the 6 council memory stores filling over time, with the moment a NEW critic spawns organically +5. **Close** The timelapse IS the story. The council explanation sits on either side. -## Two substrates (not "multi-site support") +## Single substrate - **`demo-landing-page/ugly/`** — Richer Health (1 page, real brand, ugly starting state) -- **`demo-sites/northwest-reno/ugly/`** — Northwest Home Renovations (3 pages: `/`, `/services`, `/free-estimate`; fictional brand; ugly starting state) -The point is not "Webster handles multi-site." The point is **Webster's council judgment generalizes across domains**. Two substrates is enough to prove that. A third is out of scope. +The submission focuses on a single substrate so the timelapse and genealogy beats are crisp. A second substrate is explicitly out of scope. ## The ugly-brand decoupling — read this twice @@ -69,8 +67,8 @@ The video's genealogy beat dramatizes whatever happened, not what we wished. ## Memory architecture — hybrid - **`history/memory.jsonl`** remains ground truth. Deterministic, inspectable, the substrate the planner and verdict engine already depend on. -- **Anthropic Managed Memory Stores** (public beta, `managed-agents-2026-04-01`) are populated in parallel as demo artifacts. **12 stores total, 6 per substrate**: council, planner, redesigner, genealogy, conversion-critic, visual-reviewer. Orchestrator writes summaries after each week. Planner + redesigner + genealogy attach their stores at session creation and read during work. -- **Store attachment is intentionally asymmetric.** Planner, redesigner, genealogy, conversion-critic, and visual-reviewer get role-specific stores because they make durable decisions or own a long-running critique lane. Monitor, SEO, brand-voice, copy, and compliance/licensing critics read the shared council store so the demo stays within the 12-store visual story instead of exploding into per-critic storage. +- **Anthropic Managed Memory Stores** (public beta, `managed-agents-2026-04-01`) are populated in parallel as demo artifacts. **6 stores total** for the LP substrate: council, planner, redesigner, genealogy, conversion-critic, visual-reviewer. Orchestrator writes summaries after each week. Planner + redesigner + genealogy attach their stores at session creation and read during work. +- **Store attachment is intentionally asymmetric.** Planner, redesigner, genealogy, conversion-critic, and visual-reviewer get role-specific stores because they make durable decisions or own a long-running critique lane. Monitor, SEO, brand-voice, copy, and fh-compliance critics read the shared council store so the demo stays within the 6-store visual story instead of exploding into per-critic storage. The simulation works without memory stores. Memory stores make the showcase real. @@ -78,18 +76,17 @@ The simulation works without memory stores. Memory stores make the showcase real ## Two agent sets — additive, never touching production -Existing 9 agents (`webster-monitor`, `webster-{seo,brand-voice,fh-compliance,conversion,copy}-critic`, `webster-redesigner`, `webster-planner`, `webster-visual-reviewer`) are **UNCHANGED**. They run Nicolette's real weekly production council — WebFetch-based, `LP_TARGET=certified.richerhealth.ca` — and stay that way. +The 9 specs in `agents/production/` (`webster-monitor`, `webster-{seo,brand-voice,fh-compliance,conversion,copy}-critic`, `webster-redesigner`, `webster-planner`, `webster-visual-reviewer`) are **UNCHANGED**. They run Nicolette's real weekly production council — WebFetch-based, `LP_TARGET=certified.richerhealth.ca` — and stay that way. -**18 new sim agents** are added: +**9 sim agents** are added in `agents/simulation/`: -- `webster-lp-sim-*` (9) — scoped to Richer Health simulation, **MCP-native** (read site via `get_file_contents` from demo branch, no WebFetch) -- `webster-site-sim-*` (9) — scoped to Northwest Home Renovations, MCP-native. Fifth critic is `licensing-and-warranty-critic` replacing `fh-compliance-critic` +- `webster-lp-sim-*` (9) — scoped to Richer Health simulation, **MCP-native** (read site via `get_file_contents` from demo branch, no WebFetch). Mirrors the production set 1:1. Registration is idempotent (by name). Production flow untouched. ## State flow — everything in git -Per-week mutations commit to dedicated demo branches (`demo-sim-lp/w` + `demo-sim-site/w`). Sim agents read via GitHub MCP at branch-ref. No localhost, no external deploys, no preview URLs, no WebFetch. Fixed seed + fixed week dates make every run reproducible. +Per-week mutations commit to dedicated demo branches (`demo-sim-lp/w`). Sim agents read via GitHub MCP at branch-ref. No localhost, no external deploys, no preview URLs, no WebFetch. Fixed seed + fixed week dates make every run reproducible. ## Orchestrator — fork, don't rewrite @@ -116,17 +113,17 @@ The plan as drafted is tight but achievable if we follow the cuts. Drift and we ## API cost note -18 new agents × 20 simulated weeks × ~9 sessions per week ≈ 200 sessions. Plus synthetic analytics agent × 20. Estimate: **$150–$500** end-to-end depending on token volume. Consider kicking off sim runs on wall-clock days when Max-sub quota is available rather than burning API credits directly. +9 sim agents × 10 simulated weeks × ~9 sessions per week ≈ 90 sessions. Plus synthetic analytics agent × 10. Estimate: **$80–$250** end-to-end depending on token volume. Consider kicking off sim runs on wall-clock days when Max-sub quota is available rather than burning API credits directly. ## What's locked -- Architecture, substrates, scripts, personas, metrics schema -- Memory design (hybrid file + 12 managed stores) +- Architecture, single LP substrate, scripts, personas, metrics schema +- Memory design (hybrid file + 6 managed stores) - Genealogy approach (pure organic, 1-day re-run budget) - Ugly-brand principle (decoupled) - State flow (GitHub MCP, demo branches, fixed-seed determinism) -- Scope boundary (two substrates, nothing more) -- Existing 9 agents untouched; 18 new sim agents additive +- Scope boundary (one substrate, nothing more) +- Production set untouched; 9 LP-sim agents in `agents/simulation/` mirror it 1:1 ## What's deferred @@ -139,12 +136,11 @@ The plan as drafted is tight but achievable if we follow the cuts. Drift and we ## Out of scope — do not build - Sitewide coordination (shared nav / header / footer cohesion across pages). Emergent through repeated single-page passes only. -- Third substrate +- Second substrate - Live analytics pipeline (synthetic only) - Production deploy (demo branches are terminal) - Multi-critic consensus rework (existing verdict engine handles it) -- Cross-substrate memory sharing (strict isolation per substrate) -- Modifications to the existing 9 production agents or `prompts/second-wbs-session.md` +- Modifications to the production agents in `agents/production/` or `prompts/second-wbs-session.md` ## Demo risk register diff --git a/context/memory-stores.json b/context/memory-stores.json index 374e5ad..08f3fd7 100644 --- a/context/memory-stores.json +++ b/context/memory-stores.json @@ -6,13 +6,5 @@ "genealogy": "memstore_01Uk2u9dt593HdWk6LdZsWzQ", "conversion-critic": "memstore_011cjbxGwxSwjQthTQtrzqAg", "visual-reviewer": "memstore_01GRnTJzPSGQoUpYtSwy1mTt" - }, - "site": { - "council": "memstore_0157JFZEhKzFfJowRkWwLCqS", - "planner": "memstore_01TY8ZwSs3FMb35G8aiA3vDJ", - "redesigner": "memstore_01BPy8JUR6AeyhXgAXM62rsJ", - "genealogy": "memstore_01JSwDtY4wVLtKiNS1M1SJ9E", - "conversion-critic": "memstore_01Aa1dWtui6K3NcfLEJVtHCx", - "visual-reviewer": "memstore_016QX1spnGjJfoXefKAniD85" } } diff --git a/context/sim-agents.json b/context/sim-agents.json index 788bbf5..bea37c6 100644 --- a/context/sim-agents.json +++ b/context/sim-agents.json @@ -9,16 +9,5 @@ "redesigner": "agent_011CaPk5WQYT46qS6wT13a82", "seo-critic": "agent_011CaPk5YGAQtG2xfWkehWr1", "visual-reviewer": "agent_011CaPk5aeHZMxTgTwCB3DSp" - }, - "webster-site-sim": { - "brand-voice-critic": "agent_011CaPk5cNiecAPJrhQKBCF5", - "conversion-critic": "agent_011CaPk5e8tWxRpg4sWQBsLf", - "copy-critic": "agent_011CaPk5g7Cpz7NFDi3cYjY4", - "licensing-and-warranty-critic": "agent_011CaPk5ht7GynWjHNFnojdi", - "monitor": "agent_011CaPwnsH5n2e5nqEsuZFPQ", - "planner": "agent_011CaPwnudxtYXZjVCWacdpU", - "redesigner": "agent_011CaPwnx1bTTyhm1kgp7S7m", - "seo-critic": "agent_011CaPwnzQxkueQ7Sy2DKCDa", - "visual-reviewer": "agent_011CaPwo3yX2ducrnTEL1i3e" } } diff --git a/demo-sites/northwest-reno/context/brand.json b/demo-sites/northwest-reno/context/brand.json deleted file mode 100644 index bee92c2..0000000 --- a/demo-sites/northwest-reno/context/brand.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "voice": "Competent, direct, trust-heavy contractor voice: local owner accountability, clear scope, no hype.", - "tone": ["plain-spoken", "practical", "calm", "accountable", "specific"], - "palette": { - "forest_green": "#234F3A", - "warm_cream": "#F4EBDD", - "brass": "#B8893B", - "charcoal": "#2B2F32", - "white": "#FFFFFF" - }, - "typography": { - "heading": "Clear sturdy sans such as Inter Tight or Source Sans 3", - "body": "Readable sans such as Inter or system-ui", - "utility": "Monospace or compact sans for license, warranty, and estimate labels" - }, - "signature_phrases": [ - "Written scope before work starts", - "Owner-led renovation work", - "Licensed, insured, and warranty-backed", - "Clean jobsite, clear next step", - "5-year workmanship, 10-year structural" - ], - "do_not_use": [ - "world-class", - "best in the business", - "top quality", - "dream home overnight", - "cheap", - "luxury for less", - "we do it all", - "no job too big or small", - "guaranteed cheapest", - "perfect craftsmanship" - ] -} diff --git a/demo-sites/northwest-reno/context/business.md b/demo-sites/northwest-reno/context/business.md deleted file mode 100644 index 588bc59..0000000 --- a/demo-sites/northwest-reno/context/business.md +++ /dev/null @@ -1,33 +0,0 @@ -# Business context — Northwest Home Renovations - -## Quick identity - -| Field | Value | -| ------------------ | -------------------------------------------------------- | -| Business name | Northwest Home Renovations | -| Owner | Sam Reyes | -| Location | Pacific Northwest, non-specific town | -| Services | Kitchen renovation, bathroom renovation, deck renovation | -| License number | WA-CONTR-NWR-2024 | -| Insurance | $2M liability insurance | -| Warranty | 5-year workmanship warranty, 10-year structural warranty | -| Primary conversion | Request a free estimate | -| Tone | Competent, direct, trust-heavy | - -## Positioning - -Northwest Home Renovations is a small contractor shop for homeowners who want a clear scope, a clean jobsite, and no surprise handoffs. The business should feel local and accountable, not slick or franchise-like. Sam Reyes is the named owner and should be visible as the person responsible for the work. - -## Services - -- Kitchen renovation: layout updates, cabinets, counters, tile, fixtures, and finish coordination. -- Bathroom renovation: tub/shower replacement, vanity install, tile, waterproofing, ventilation, and accessibility upgrades. -- Deck renovation: repair, resurfacing, structural rebuilds, stairs, railings, and weather-ready finishes. - -## Trust rules - -License number, insurance, warranty terms, and service-area clarity should appear before a homeowner is asked to request an estimate. Warranty copy must stay specific: 5-year workmanship and 10-year structural. Insurance copy must say "$2M liability" and should not imply coverage beyond that. - -## Voice - -Use plain-spoken contractor language. Short sentences. Specific promises. No hype. No vague "quality craftsmanship" unless backed by concrete details like protected floors, written scopes, cleanup, permit coordination, or response times. diff --git a/demo-sites/northwest-reno/context/personas.json b/demo-sites/northwest-reno/context/personas.json deleted file mode 100644 index 626c68e..0000000 --- a/demo-sites/northwest-reno/context/personas.json +++ /dev/null @@ -1,77 +0,0 @@ -[ - { - "id": "first-time-homeowner-anxious", - "name": "First-Time Homeowner Anxious", - "archetype": "New homeowner worried about being scammed or talked down to", - "goals": [ - "Understand what happens after requesting an estimate", - "See license, insurance, warranty, and owner accountability before calling", - "Feel safe asking basic renovation questions" - ], - "anxieties": [ - "Hiring an unlicensed or unreliable contractor", - "Hidden costs after work begins", - "Being embarrassed for not knowing renovation terminology" - ], - "conversion_triggers": [ - "Visible WA-CONTR-NWR-2024 license number", - "Clear estimate process and response-time expectation", - "Plain-language reassurance from owner Sam Reyes" - ], - "behavior_hints": [ - "Reads trust blocks before service details", - "Needs labels, phone option, and low-pressure language", - "Bounces on vague bragging or missing proof" - ] - }, - { - "id": "price-comparing-pragmatist", - "name": "Price-Comparing Pragmatist", - "archetype": "Homeowner collecting three quotes and looking for scope clarity", - "goals": [ - "Compare services and expected scope quickly", - "Avoid contractors who dodge timeline or warranty questions", - "Find enough detail to decide whether to request a quote" - ], - "anxieties": [ - "Receiving a lowball quote that grows later", - "Wasting time with a contractor outside the service area", - "Unclear forms that do not capture the project accurately" - ], - "conversion_triggers": [ - "Specific kitchen, bath, and deck service descriptions", - "Written-scope language and warranty terms", - "CTA that promises a clear next step, not a sales trap" - ], - "behavior_hints": [ - "Scans services page before home page copy", - "Looks for pricing signals, process, and what is included", - "Responds to direct CTAs like 'Request a written estimate'" - ] - }, - { - "id": "warranty-conscious-veteran", - "name": "Warranty-Conscious Veteran", - "archetype": "Homeowner burned by a previous contractor and focused on accountability", - "goals": [ - "Verify warranty terms before contacting anyone", - "Find proof the contractor is insured and locally accountable", - "Understand who owns mistakes if something goes wrong" - ], - "anxieties": [ - "A contractor disappearing after payment", - "Warranty exclusions hidden until after signing", - "Poor cleanup, schedule drift, and vague communication" - ], - "conversion_triggers": [ - "5-year workmanship and 10-year structural warranty language", - "$2M liability insurance claim stated plainly", - "Owner-led accountability and documented scope promises" - ], - "behavior_hints": [ - "Reads footer and trust sections carefully", - "Needs warranty and insurance near every major CTA", - "Will distrust superlatives without concrete proof" - ] - } -] diff --git a/demo-sites/northwest-reno/ugly/README.md b/demo-sites/northwest-reno/ugly/README.md deleted file mode 100644 index 5c3f371..0000000 --- a/demo-sites/northwest-reno/ugly/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Northwest Home Renovations ugly demo state - -Intentionally ugly. Do NOT improve outside simulation. - -This is the week-0 unimproved surface for the Northwest Home Renovations demo site. The brand intent lives in `demo-sites/northwest-reno/context/`; these pages deliberately violate that context so Webster's critics have clear work to do. diff --git a/demo-sites/northwest-reno/ugly/free-estimate.html b/demo-sites/northwest-reno/ugly/free-estimate.html deleted file mode 100644 index 341ec6f..0000000 --- a/demo-sites/northwest-reno/ugly/free-estimate.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - Free Estimate | Northwest Home Renovations - - - - - -
-
-
-

Free estimate

-

Tell us what you want to improve.

-

- Share the basics and owner Sam Reyes will follow up within two business days. Northwest - Home Renovations is licensed under WA-CONTR-NWR-2024 and carries $2M liability - insurance. -

-
-

Before you request an estimate

-
    -
  • Project type and rough timeline
  • -
  • City or neighborhood
  • -
  • Photos if you already have them
  • -
  • Budget range if known
  • -
  • Warranty expectation: 5-year workmanship and 10-year structural
  • -
-
-
- -
- - - - - - -

- This demo form does not submit yet. Please call for live scheduling. -

-
-
-
- -
- Northwest Home Renovations - Sam Reyes • WA-CONTR-NWR-2024 • $2M liability insurance -
- - diff --git a/demo-sites/northwest-reno/ugly/index.html b/demo-sites/northwest-reno/ugly/index.html deleted file mode 100644 index 5feba83..0000000 --- a/demo-sites/northwest-reno/ugly/index.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - Northwest Home Renovations | Kitchens, Baths, Decks & Additions - - - - - - -
-
-
-

Licensed residential remodeling

-

Renovation work that makes your home easier to live in.

-

- Kitchens, bathrooms, decks, additions, and whole-home updates for homeowners across the - Pacific Northwest. -

-

- Owner Sam Reyes • WA-CONTR-NWR-2024 • $2M liability insurance • 5-year workmanship / - 10-year structural warranty -

- -
-
-
- Recent kitchen remodel -
-
- Owner-led by Sam Reyes - WA-CONTR-NWR-2024 -
-
-
- -
-
-

Built for Northwest homes

-

- We handle rainy-season scheduling, older-home surprises, permit coordination, and the - day-to-day details that keep projects moving. -

-
-
-

Clear communication

-

- You get a written scope, regular updates, and Sam Reyes as the accountable owner — not a - surprise handoff. -

-
-
-

Workmanship warranty

-

- We carry $2M liability insurance and include a 5-year workmanship warranty plus a - 10-year structural warranty. -

-
-
- - -
- -
- Northwest Home Renovations - Owner: Sam Reyes • WA-CONTR-NWR-2024 • $2M liability insurance -
- - diff --git a/demo-sites/northwest-reno/ugly/services.html b/demo-sites/northwest-reno/ugly/services.html deleted file mode 100644 index 890363e..0000000 --- a/demo-sites/northwest-reno/ugly/services.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - Remodeling Services | Northwest Home Renovations - - - - - -
-
-

Services

-

Remodeling services for the rooms your family uses every day.

-

- Sam Reyes leads practical improvements: better storage, safer layouts, durable finishes, - and work that fits the age and style of your home. -

-
- -
-
-
-

Kitchen Remodeling

-

- Layout changes, cabinets, counters, backsplash, lighting, flooring, and appliance-ready - finish work. -

-
-
-
-

Bathroom Renovations

-

- Tub-to-shower conversions, tile, vanities, waterproofing, ventilation, and accessibility - upgrades. -

-
-
-
-

Decks & Outdoor Living

-

- Deck rebuilds, railings, stairs, covered patios, and weather-conscious material choices. -

-
-
-
-

Additions & Interior Updates

-

- Laundry rooms, mudrooms, bonus spaces, basement updates, and phased whole-home projects. -

-
-
- -
-

Our typical process

-
    -
  1. - Walkthrough: We review goals, budget range, timing, and constraints. -
  2. -
  3. - Scope: You receive a written estimate with recommended next steps. -
  4. -
  5. - Build: We coordinate materials, trades, permits, and site cleanup. -
  6. -
  7. - Closeout: We review the finished work and warranty details together. -
  8. -
-
- -
-

Have a project in mind?

-

Send a few details and we’ll tell you whether we’re a good fit.

- Get a Free Estimate -
-
- -
- Northwest Home Renovations - WA-CONTR-NWR-2024 • $2M liability • 5-year workmanship / 10-year structural warranty -
- - diff --git a/demo-sites/northwest-reno/ugly/style.css b/demo-sites/northwest-reno/ugly/style.css deleted file mode 100644 index 050b165..0000000 --- a/demo-sites/northwest-reno/ugly/style.css +++ /dev/null @@ -1,412 +0,0 @@ -:root { - --ink: #17202a; - --muted: #5e6a75; - --paper: #f5f1ea; - --panel: #ffffff; - --line: #ded6c8; - --cedar: #8a4d2c; - --cedar-dark: #5b311f; - --forest: #254337; - --sand: #e7dccb; -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; - font-family: - Inter, - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - sans-serif; - color: var(--ink); - background: var(--paper); -} - -.site-header { - display: grid; - grid-template-columns: minmax(260px, 1fr) auto auto; - gap: 24px; - align-items: center; - padding: 16px clamp(20px, 5vw, 64px); - background: #f5f1ea; - border-bottom: 3px solid var(--line); -} - -.brand { - display: inline-flex; - align-items: center; - gap: 12px; - color: var(--ink); - font-size: 17px; - font-weight: 850; - letter-spacing: -0.02em; - text-decoration: none; -} - -.mark { - display: inline-grid; - width: 46px; - height: 46px; - place-items: center; - color: #fff; - background: #385247; - border-radius: 4px; - font-size: 13px; - letter-spacing: 0.06em; -} - -nav { - display: flex; - gap: 18px; -} - -nav a, -.phone { - color: var(--muted); - font-size: 15px; - font-weight: 700; - text-decoration: none; -} - -.phone { - color: var(--cedar-dark); -} - -main { - min-height: 70vh; -} - -.hero, -.page-hero, -.estimate-layout { - padding: clamp(48px, 8vw, 96px) clamp(20px, 5vw, 64px); -} - -.hero { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(340px, 0.78fr); - gap: clamp(32px, 6vw, 72px); - align-items: center; -} - -.eyebrow { - margin: 0 0 12px; - color: var(--cedar); - font-size: 13px; - font-weight: 850; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -h1, -h2, -h3, -p { - margin-top: 0; -} - -h1 { - max-width: 780px; - margin-bottom: 18px; - font-size: clamp(40px, 5.6vw, 68px); - line-height: 0.98; - letter-spacing: -0.055em; -} - -h2 { - margin-bottom: 12px; - font-size: clamp(25px, 3.2vw, 38px); - line-height: 1.05; - letter-spacing: -0.035em; -} - -h3 { - margin-bottom: 8px; - font-size: 21px; -} - -p, -li { - color: var(--muted); - font-size: 18px; - line-height: 1.62; -} - -.hero-copy p, -.page-hero p, -.estimate-copy > p { - max-width: 650px; - font-size: 20px; -} - -.hero-copy .hero-trust { - max-width: 720px; - padding: 12px 14px; - color: var(--ink); - background: #eadfcd; - border: 1px solid var(--line); - font-size: 15px; - font-weight: 750; -} - -.actions { - display: flex; - flex-wrap: wrap; - gap: 14px; - margin-top: 28px; -} - -.button, -button { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 48px; - padding: 0 20px; - border: 1px solid var(--cedar-dark); - border-radius: 3px; - font-weight: 800; - text-decoration: none; - cursor: pointer; -} - -.primary, -button { - color: #fff; - background: #6b3b25; -} - -.secondary { - color: var(--cedar-dark); - background: transparent; -} - -.project-card, -.contact-card, -.estimate-form, -.process, -.cta-band { - background: var(--panel); - border: 1px solid var(--line); - border-radius: 6px; - box-shadow: none; -} - -.project-card { - padding: 14px; -} - -.project-photo, -.service-photo { - min-height: 390px; - background: - linear-gradient(180deg, rgba(17, 25, 30, 0.05), rgba(17, 25, 30, 0.58)), - linear-gradient(135deg, #d8c1a8, #9a7659 48%, #4a392d); - border-radius: 5px; -} - -.project-photo { - display: grid; - place-items: end start; - padding: 24px; - color: #fff; -} - -.project-photo span { - font-size: 22px; - font-weight: 850; -} - -.trust-row { - display: flex; - justify-content: space-between; - gap: 16px; - padding: 16px 4px 4px; - color: var(--muted); - font-size: 14px; - font-weight: 750; -} - -.intro-grid, -.featured, -.service-grid, -.process, -.cta-band { - margin: 0 clamp(20px, 5vw, 64px) 28px; -} - -.intro-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 18px; -} - -.intro-grid article, -.service-grid article { - padding: 26px; - background: var(--panel); - border: 1px solid var(--line); - border-radius: 6px; -} - -.featured { - display: grid; - grid-template-columns: minmax(0, 0.85fr) minmax(320px, 1fr); - gap: 40px; - padding: clamp(36px, 6vw, 64px); - background: var(--forest); - border-radius: 6px; -} - -.featured h2, -.featured p, -.featured li, -.cta-band h2, -.cta-band p { - color: #fff; -} - -.check-list { - display: grid; - gap: 12px; - padding-left: 22px; -} - -.compact { - padding-bottom: 24px; -} - -.service-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 20px; -} - -.service-photo { - min-height: 210px; - margin-bottom: 18px; -} - -.service-photo.kitchen { - background: linear-gradient(135deg, #d0b99e, #82614c); -} - -.service-photo.bath { - background: linear-gradient(135deg, #d7dce0, #73808a); -} - -.service-photo.deck { - background: linear-gradient(135deg, #c79a6b, #5f3b22); -} - -.service-photo.addition { - background: linear-gradient(135deg, #d7cbb8, #6b6258); -} - -.process, -.cta-band { - padding: clamp(32px, 5vw, 52px); -} - -.process ol { - display: grid; - gap: 16px; - padding-left: 22px; -} - -.cta-band { - display: flex; - align-items: center; - justify-content: space-between; - gap: 24px; - color: #fff; - background: #385247; -} - -.estimate-layout { - display: grid; - grid-template-columns: minmax(0, 0.85fr) minmax(340px, 0.65fr); - gap: 40px; -} - -.contact-card, -.estimate-form { - padding: 26px; -} - -.estimate-form { - display: grid; - gap: 15px; -} - -label { - display: grid; - gap: 7px; - color: var(--muted); - font-size: 14px; - font-weight: 750; -} - -input, -select, -textarea { - width: 100%; - padding: 12px 13px; - color: var(--ink); - background: #fff; - border: 1px solid var(--line); - border-radius: 9px; - font: inherit; -} - -textarea { - resize: vertical; -} - -.form-note { - margin-bottom: 0; - font-size: 13px; -} - -footer { - display: flex; - justify-content: space-between; - gap: 20px; - padding: 30px clamp(20px, 5vw, 64px) 44px; - color: var(--muted); - border-top: 1px solid var(--line); -} - -@media (max-width: 850px) { - .site-header, - .hero, - .intro-grid, - .featured, - .service-grid, - .estimate-layout { - grid-template-columns: 1fr; - } - - .site-header { - justify-items: start; - } - - nav, - .actions, - .trust-row, - .cta-band, - footer { - flex-direction: column; - align-items: flex-start; - } - - .project-photo { - min-height: 300px; - } -} diff --git a/history/lp-demo/w00/analytics-reasoning.md b/history/lp-demo/w00/analytics-reasoning.md new file mode 100644 index 0000000..240e9fa --- /dev/null +++ b/history/lp-demo/w00/analytics-reasoning.md @@ -0,0 +1,9 @@ +# Synthetic analytics reasoning — lp week 0 + +Baseline week: no prior analytics, so metrics are calibrated from current site quality. + +- Credentials-Conscious Executive: Clinic owner who needs proof before changing team protocols. Panel response is trust-led; current site score 0.52 shapes bounce and CTA confidence. +- Curious Self-Starter: Warm lead already interested in food-as-medicine and ready for a practical next step. Panel response is curiosity-led; current site score 0.52 shapes bounce and CTA confidence. +- Skeptical Researcher: Evidence-seeking evaluator comparing Nicolette against other wellness educators. Panel response is proof-led; current site score 0.52 shapes bounce and CTA confidence. + +Continuity guardrail: unchanged or lightly changed pages are constrained to realistic weekly movement; stronger site-state evidence is required for larger swings. Fixed cohort size is 5000 simulated users. diff --git a/history/lp-demo/w00/analytics.json b/history/lp-demo/w00/analytics.json new file mode 100644 index 0000000..057d245 --- /dev/null +++ b/history/lp-demo/w00/analytics.json @@ -0,0 +1,92 @@ +{ + "version_sha": "synthetic-lp-w00-e63e4ae0", + "site_signature": "f3987cbe", + "substrate": "lp", + "week": 0, + "weekDate": "2026-02-01", + "sessions": 5033, + "bounce_rate": 0.541, + "avg_time_s": 117, + "scroll_depth_25": 0.803, + "scroll_depth_50": 0.634, + "scroll_depth_75": 0.428, + "scroll_depth_100": 0.205, + "cta_clicks": { + "discovery_call": 242 + }, + "persona_metrics": [ + { + "persona_id": "credentials-conscious-executive", + "sessions": 1862, + "bounce_rate": 0.523, + "cta_clicks": 91, + "avg_time_s": 119.1 + }, + { + "persona_id": "curious-self-starter", + "sessions": 1711, + "bounce_rate": 0.541, + "cta_clicks": 82, + "avg_time_s": 117 + }, + { + "persona_id": "skeptical-researcher", + "sessions": 1460, + "bounce_rate": 0.559, + "cta_clicks": 69, + "avg_time_s": 114.9 + } + ], + "section_engagement": [ + { + "section": "hero", + "views": 5033, + "avg_time_s": 28.1, + "dropoff_rate": 0.541 + }, + { + "section": "proof", + "views": 3073, + "avg_time_s": 42.1, + "dropoff_rate": 0.39 + }, + { + "section": "cta", + "views": 1571, + "avg_time_s": 21.1, + "dropoff_rate": 0.808 + } + ], + "events": [ + { + "version_sha": "synthetic-lp-w00-e63e4ae0", + "metric": "sessions", + "value": 5033, + "timestamp": "2026-02-01T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w00-e63e4ae0", + "metric": "bounce_rate", + "value": 0.541, + "timestamp": "2026-02-01T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w00-e63e4ae0", + "metric": "avg_time_s", + "value": 117, + "timestamp": "2026-02-01T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w00-e63e4ae0", + "metric": "scroll_depth_75", + "value": 0.428, + "timestamp": "2026-02-01T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w00-e63e4ae0", + "metric": "cta_click", + "value": 242, + "timestamp": "2026-02-01T12:00:00.000Z" + } + ] +} diff --git a/history/lp-demo/w01/analytics-reasoning.md b/history/lp-demo/w01/analytics-reasoning.md new file mode 100644 index 0000000..2086de3 --- /dev/null +++ b/history/lp-demo/w01/analytics-reasoning.md @@ -0,0 +1,9 @@ +# Synthetic analytics reasoning — lp week 1 + +Bounce moved from 0.541 to 0.544; CTA clicks moved from 242 to 242. + +- Credentials-Conscious Executive: Clinic owner who needs proof before changing team protocols. Panel response is trust-led; current site score 0.52 shapes bounce and CTA confidence. +- Curious Self-Starter: Warm lead already interested in food-as-medicine and ready for a practical next step. Panel response is curiosity-led; current site score 0.52 shapes bounce and CTA confidence. +- Skeptical Researcher: Evidence-seeking evaluator comparing Nicolette against other wellness educators. Panel response is proof-led; current site score 0.52 shapes bounce and CTA confidence. + +Continuity guardrail: unchanged or lightly changed pages are constrained to realistic weekly movement; stronger site-state evidence is required for larger swings. Fixed cohort size is 5000 simulated users. diff --git a/history/lp-demo/w01/analytics.json b/history/lp-demo/w01/analytics.json new file mode 100644 index 0000000..e6a166c --- /dev/null +++ b/history/lp-demo/w01/analytics.json @@ -0,0 +1,92 @@ +{ + "version_sha": "synthetic-lp-w01-e63e4ae0", + "site_signature": "f3987cbe", + "substrate": "lp", + "week": 1, + "weekDate": "2026-02-08", + "sessions": 5034, + "bounce_rate": 0.544, + "avg_time_s": 117.2, + "scroll_depth_25": 0.802, + "scroll_depth_50": 0.633, + "scroll_depth_75": 0.427, + "scroll_depth_100": 0.205, + "cta_clicks": { + "discovery_call": 242 + }, + "persona_metrics": [ + { + "persona_id": "credentials-conscious-executive", + "sessions": 1863, + "bounce_rate": 0.526, + "cta_clicks": 91, + "avg_time_s": 119.3 + }, + { + "persona_id": "curious-self-starter", + "sessions": 1712, + "bounce_rate": 0.544, + "cta_clicks": 82, + "avg_time_s": 117.2 + }, + { + "persona_id": "skeptical-researcher", + "sessions": 1460, + "bounce_rate": 0.562, + "cta_clicks": 69, + "avg_time_s": 115.1 + } + ], + "section_engagement": [ + { + "section": "hero", + "views": 5034, + "avg_time_s": 28.1, + "dropoff_rate": 0.544 + }, + { + "section": "proof", + "views": 3062, + "avg_time_s": 42.2, + "dropoff_rate": 0.392 + }, + { + "section": "cta", + "views": 1561, + "avg_time_s": 21.1, + "dropoff_rate": 0.808 + } + ], + "events": [ + { + "version_sha": "synthetic-lp-w01-e63e4ae0", + "metric": "sessions", + "value": 5034, + "timestamp": "2026-02-08T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w01-e63e4ae0", + "metric": "bounce_rate", + "value": 0.544, + "timestamp": "2026-02-08T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w01-e63e4ae0", + "metric": "avg_time_s", + "value": 117.2, + "timestamp": "2026-02-08T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w01-e63e4ae0", + "metric": "scroll_depth_75", + "value": 0.427, + "timestamp": "2026-02-08T12:00:00.000Z" + }, + { + "version_sha": "synthetic-lp-w01-e63e4ae0", + "metric": "cta_click", + "value": 242, + "timestamp": "2026-02-08T12:00:00.000Z" + } + ] +} diff --git a/package.json b/package.json index e719a90..c165dfb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "apply-worker": "bun scripts/apply-worker-cli.ts", "seed:secondary": "bun scripts/seed-secondary-substrates.ts", "sim:lp": "bun scripts/run-simulation-lp.ts", - "sim:site": "bun scripts/run-simulation-site.ts", "sim:capture-bridge": "bun scripts/sim-capture-bridge.ts", "sim:preflight": "bun scripts/sim-preflight.ts", "sim:emit-manifest": "bun scripts/emit-memory-screenshot-manifest.ts", diff --git a/prompts/sim-council.md b/prompts/sim-council.md index 92f8230..180cb84 100644 --- a/prompts/sim-council.md +++ b/prompts/sim-council.md @@ -1,29 +1,29 @@ # Sim council — parameterized demo council fork -This prompt runs one Webster simulation week for one substrate. It is a fork of `prompts/second-wbs-session.md` for demo branches only. +This prompt runs one Webster simulation week for the LP substrate. It is a fork of `prompts/second-wbs-session.md` for demo branches only. It does not touch production agents, production prompt state, live URLs, localhost, or deploy previews. Sim agents read site files from GitHub MCP at the branch ref passed in `BRANCH`. ## Required environment ```bash -: "${SUBSTRATE:?SUBSTRATE must be lp or site}" +: "${SUBSTRATE:?SUBSTRATE must be lp}" : "${WEEK_DATE:?WEEK_DATE must be set, e.g. 2026-02-01}" : "${BRANCH:?BRANCH must be set, e.g. demo-sim-lp/w00}" -: "${AGENT_SET:?AGENT_SET must be webster-lp-sim or webster-site-sim}" +: "${AGENT_SET:?AGENT_SET must be webster-lp-sim}" : "${CONTEXT_PATH:?CONTEXT_PATH must point to substrate context dir}" : "${SITE_PATH:?SITE_PATH must point to substrate site dir}" : "${MEMORY_STORES_JSON:?MEMORY_STORES_JSON must point to context/memory-stores.json}" : "${SIM_AGENTS_JSON:=context/sim-agents.json}" case "$SUBSTRATE" in - lp|site) ;; - *) echo "ABORT: SUBSTRATE must be lp or site" >&2; exit 1 ;; + lp) ;; + *) echo "ABORT: SUBSTRATE must be lp" >&2; exit 1 ;; esac case "$AGENT_SET" in - webster-lp-sim|webster-site-sim) ;; - *) echo "ABORT: AGENT_SET must be webster-lp-sim or webster-site-sim" >&2; exit 1 ;; + webster-lp-sim) ;; + *) echo "ABORT: AGENT_SET must be webster-lp-sim" >&2; exit 1 ;; esac if [[ ! -s "$SIM_AGENTS_JSON" ]]; then @@ -184,12 +184,7 @@ printf '%s\n' "$PLANNER_SESSION" > "tmp/sim-sessions/${SUBSTRATE}-${WEEK_DATE}-p ## Step 3 — fan out monitor and critics ```bash -roles=(monitor seo-critic brand-voice-critic conversion-critic copy-critic) -if [[ "$SUBSTRATE" == "lp" ]]; then - roles+=(fh-compliance-critic) -else - roles+=(licensing-and-warranty-critic) -fi +roles=(monitor seo-critic brand-voice-critic conversion-critic copy-critic fh-compliance-critic) pids=() for role in "${roles[@]}"; do @@ -198,9 +193,9 @@ for role in "${roles[@]}"; do conversion-critic) resource=$(memory_resource conversion-critic read_write "Conversion critic memory for this substrate simulation.") ;; *) - # Monitor, SEO, brand-voice, copy, and substrate-specific legal/trust critics - # intentionally read the shared council memory store; only roles with durable - # role-specific memory get read_write stores. + # Monitor, SEO, brand-voice, copy, and compliance critics intentionally + # read the shared council memory store; only roles with durable role- + # specific memory get read_write stores. resource=$(memory_resource council read_only "Shared council memory for this substrate simulation.") ;; esac session=$(create_session "$role" "$agent" "$resource") diff --git a/scripts/__tests__/context-schema.test.ts b/scripts/__tests__/context-schema.test.ts index c2fea2e..34c4e11 100644 --- a/scripts/__tests__/context-schema.test.ts +++ b/scripts/__tests__/context-schema.test.ts @@ -8,9 +8,8 @@ import { } from "../context-schema.ts"; describe("demo context schema", () => { - test("validates Richer Health and Northwest Reno context directories", () => { + test("validates the Richer Health context directory", () => { expect(validateContextDirectory("demo-landing-page/context")).toEqual([]); - expect(validateContextDirectory("demo-sites/northwest-reno/context")).toEqual([]); }); test("requires exactly three rich personas with behavioral fields", () => { @@ -48,22 +47,14 @@ describe("demo context schema", () => { expect(errors).toContain("brand.do_not_use must contain at least 5 item(s)"); }); - test("keeps substrate context isolated", () => { + test("LP context references Richer Health, not other substrates", () => { const lpContext = [ readFileSync("demo-landing-page/context/business.md", "utf8"), readFileSync("demo-landing-page/context/personas.json", "utf8"), readFileSync("demo-landing-page/context/brand.json", "utf8"), ].join("\n"); - const siteContext = [ - readFileSync("demo-sites/northwest-reno/context/business.md", "utf8"), - readFileSync("demo-sites/northwest-reno/context/personas.json", "utf8"), - readFileSync("demo-sites/northwest-reno/context/brand.json", "utf8"), - ].join("\n"); - expect(lpContext).not.toContain("Northwest Home Renovations"); - expect(lpContext).not.toContain("Sam Reyes"); - expect(siteContext).not.toContain("Richer Health"); - expect(siteContext).not.toContain("Nicolette"); + expect(lpContext).toContain("Richer Health"); }); test("business markdown requires structured identity, offer, and voice sections", () => { diff --git a/scripts/__tests__/critic-genealogy.test.ts b/scripts/__tests__/critic-genealogy.test.ts index 888da31..c3c9adb 100644 --- a/scripts/__tests__/critic-genealogy.test.ts +++ b/scripts/__tests__/critic-genealogy.test.ts @@ -47,7 +47,9 @@ const SAMPLE_SPEC: NewCriticSpec = { }; const LOAD_TEMPLATE = (): AgentJSON => - JSON.parse(readFileSync(join(ROOT, "agents/brand-voice-critic.json"), "utf8")) as AgentJSON; + JSON.parse( + readFileSync(join(ROOT, "agents/production/brand-voice-critic.json"), "utf8"), + ) as AgentJSON; describe("parseArgs", () => { test("parses --branch + --week + --dry-run", () => { @@ -478,7 +480,7 @@ describe("spliceNewSpec", () => { } finally { globalThis.fetch = originalFetch; rmSync(join(ROOT, "history", weekDate), { recursive: true, force: true }); - rmSync(join(ROOT, "agents", `${spec.name}.json`), { force: true }); + rmSync(join(ROOT, "agents", "production", `${spec.name}.json`), { force: true }); } }); }); diff --git a/scripts/__tests__/planner-agent-contract.test.ts b/scripts/__tests__/planner-agent-contract.test.ts index c64dda0..222ce58 100644 --- a/scripts/__tests__/planner-agent-contract.test.ts +++ b/scripts/__tests__/planner-agent-contract.test.ts @@ -9,7 +9,7 @@ const AGENT_SCHEMA = JSON.parse( readFileSync(join(ROOT, "scripts/schemas/agent.schema.json"), "utf-8"), ); const PLANNER_SPEC = JSON.parse( - readFileSync(join(ROOT, "agents/webster-planner.json"), "utf-8"), + readFileSync(join(ROOT, "agents/production/webster-planner.json"), "utf-8"), ) as { system: string; tools: { type: string }[] } & Record; const SYSTEM_PROMPT = PLANNER_SPEC.system; diff --git a/scripts/__tests__/register-sim-agents.test.ts b/scripts/__tests__/register-sim-agents.test.ts index 8db426a..bfe176b 100644 --- a/scripts/__tests__/register-sim-agents.test.ts +++ b/scripts/__tests__/register-sim-agents.test.ts @@ -21,11 +21,10 @@ afterEach(() => { }); describe("sim agent specs", () => { - test("loads exactly 18 sim specs across both substrates", () => { + test("loads exactly 9 LP-sim specs", () => { const specs = loadSimAgentSpecs(); - expect(specs).toHaveLength(18); - expect(specs.filter((spec) => spec.name.startsWith("webster-lp-sim-")).length).toBe(9); - expect(specs.filter((spec) => spec.name.startsWith("webster-site-sim-")).length).toBe(9); + expect(specs).toHaveLength(9); + expect(specs.every((spec) => spec.name.startsWith("webster-lp-sim-"))).toBe(true); }); test("all sim specs validate against the managed-agent schema", () => { @@ -56,41 +55,21 @@ describe("sim agent specs", () => { } }); - test("committed sim-agent manifest has exactly the registered 9/9 role shape", () => { + test("committed sim-agent manifest has the registered 9-role shape", () => { const manifest = JSON.parse(readFileSync(join(ROOT, "context/sim-agents.json"), "utf8")) as { "webster-lp-sim": Record; - "webster-site-sim": Record; }; - const expected = { "webster-lp-sim": new Set(), "webster-site-sim": new Set() }; + const expected = new Set(); for (const spec of loadSimAgentSpecs()) { - const set = spec.name.startsWith("webster-lp-sim-") ? "webster-lp-sim" : "webster-site-sim"; - expected[set].add(spec.name.replace(/^webster-(lp|site)-sim-/, "")); + expected.add(spec.name.replace(/^webster-lp-sim-/, "")); } - expect(Object.keys(manifest).sort()).toEqual(["webster-lp-sim", "webster-site-sim"]); - expect(Object.keys(manifest["webster-lp-sim"]).sort()).toEqual( - [...expected["webster-lp-sim"]].sort(), - ); - expect(Object.keys(manifest["webster-site-sim"]).sort()).toEqual( - [...expected["webster-site-sim"]].sort(), - ); + expect(Object.keys(manifest)).toEqual(["webster-lp-sim"]); + expect(Object.keys(manifest["webster-lp-sim"]).sort()).toEqual([...expected].sort()); expect(Object.values(manifest["webster-lp-sim"]).every((id) => id.startsWith("agent_"))).toBe( true, ); - expect(Object.values(manifest["webster-site-sim"]).every((id) => id.startsWith("agent_"))).toBe( - true, - ); - const ids = [ - ...Object.values(manifest["webster-lp-sim"]), - ...Object.values(manifest["webster-site-sim"]), - ]; - expect(new Set(ids).size).toBe(18); - }); - - test("site set includes licensing-and-warranty instead of fh-compliance", () => { - const names = loadSimAgentSpecs().map((spec) => spec.name); - expect(names).toContain("webster-site-sim-licensing-and-warranty-critic"); - expect(names).not.toContain("webster-site-sim-fh-compliance-critic"); + expect(new Set(Object.values(manifest["webster-lp-sim"])).size).toBe(9); }); }); @@ -112,9 +91,9 @@ describe("registerSimAgents", () => { try { const manifest = await registerSimAgents("test-key", outputPath); - expect(posts).toHaveLength(18); + expect(posts).toHaveLength(9); expect(manifest["webster-lp-sim"].monitor).toStartWith("agent_"); - expect(manifest["webster-site-sim"]["licensing-and-warranty-critic"]).toStartWith("agent_"); + expect(manifest["webster-lp-sim"]["fh-compliance-critic"]).toStartWith("agent_"); expect(JSON.parse(readFileSync(outputPath, "utf8"))).toEqual(manifest); } finally { rmSync(dir, { recursive: true, force: true }); @@ -164,7 +143,7 @@ describe("registerSimAgents", () => { try { await expect(registerSimAgents("test-key", outputPath, agentsDir)).rejects.toThrow( - "expected 18 sim agent specs", + "expected 9 sim agent specs", ); } finally { rmSync(dir, { recursive: true, force: true }); diff --git a/scripts/__tests__/run-simulation-entrypoints.test.ts b/scripts/__tests__/run-simulation-entrypoints.test.ts index 7155b83..7a0d418 100644 --- a/scripts/__tests__/run-simulation-entrypoints.test.ts +++ b/scripts/__tests__/run-simulation-entrypoints.test.ts @@ -1,6 +1,5 @@ import { describe, expect, test } from "bun:test"; import { buildLpSimulationConfig, lpSimulationConfig } from "../run-simulation-lp.ts"; -import { buildSiteSimulationConfig, siteSimulationConfig } from "../run-simulation-site.ts"; describe("simulation entrypoint configs", () => { test("LP entrypoint targets the Richer Health 10-week simulation", () => { @@ -15,18 +14,6 @@ describe("simulation entrypoint configs", () => { }); }); - test("site entrypoint targets the Northwest Reno 10-week simulation", () => { - expect(siteSimulationConfig).toMatchObject({ - substrate: "site", - weekCount: 10, - sitePath: "demo-sites/northwest-reno/ugly", - contextPath: "demo-sites/northwest-reno/context", - outputDir: "demo-output/northwest-reno", - agentSet: "webster-site-sim", - councilCommand: "bun scripts/run-markdown-bash.ts prompts/sim-council.md", - }); - }); - test("LP entrypoint honors runtime environment overrides", () => { expect( buildLpSimulationConfig({ @@ -44,22 +31,4 @@ describe("simulation entrypoint configs", () => { skipMemorySummaries: true, }); }); - - test("site entrypoint honors runtime environment overrides", () => { - expect( - buildSiteSimulationConfig({ - WEBSTER_SIM_START_DATE: "2026-03-08", - WEBSTER_SIM_SEED: "site-seed", - WEBSTER_SIM_COUNCIL_CMD: "bun site-council.ts", - WEBSTER_SIM_SKIP_GIT: "1", - WEBSTER_SIM_SKIP_MEMORY: "1", - }), - ).toMatchObject({ - startDate: "2026-03-08", - seed: "site-seed", - councilCommand: "bun site-council.ts", - skipGit: true, - skipMemorySummaries: true, - }); - }); }); diff --git a/scripts/__tests__/run-simulation.test.ts b/scripts/__tests__/run-simulation.test.ts index 83670a9..c041950 100644 --- a/scripts/__tests__/run-simulation.test.ts +++ b/scripts/__tests__/run-simulation.test.ts @@ -141,104 +141,13 @@ describe("runSimulation", () => { } }); - test("site screenshot capture covers all three Northwest Reno pages", async () => { - const outDir = mkdtempSync(join(tmpdir(), "webster-run-sim-site-shots-")); - const originalDisable = process.env.WEBSTER_BROWSER_AUDIT_DISABLE_PLAYWRIGHT; - process.env.WEBSTER_BROWSER_AUDIT_DISABLE_PLAYWRIGHT = "1"; + test("LP screenshot capture fails loudly when index.html is missing", async () => { + const siteDir = mkdtempSync(join(tmpdir(), "webster-run-sim-missing-index-")); + const outDir = mkdtempSync(join(tmpdir(), "webster-run-sim-missing-index-shots-")); - let dirs: string[]; - try { - dirs = await captureLocalScreenshots("demo-sites/northwest-reno/ugly", outDir, [ - "index.html", - "services.html", - "free-estimate.html", - ]); - } finally { - if (originalDisable === undefined) { - delete process.env.WEBSTER_BROWSER_AUDIT_DISABLE_PLAYWRIGHT; - } else { - process.env.WEBSTER_BROWSER_AUDIT_DISABLE_PLAYWRIGHT = originalDisable; - } - } - - expect(dirs).toEqual([ - join(outDir, "index"), - join(outDir, "services"), - join(outDir, "free-estimate"), - ]); - for (const dir of dirs) { - expect(existsSync(join(dir, "summary.json"))).toBe(true); - const summary = JSON.parse(readFileSync(join(dir, "summary.json"), "utf8")) as { - url: string; - breakpoints: unknown[]; - }; - expect(summary.url).toContain("file://"); - expect(summary.breakpoints).toHaveLength(3); - expect(existsSync(join(dir, "a11y-text.txt"))).toBe(true); - } - }); - - test("site screenshot capture fails loudly when a multi-page substrate is incomplete", async () => { - const siteDir = mkdtempSync(join(tmpdir(), "webster-run-sim-incomplete-site-")); - const outDir = mkdtempSync(join(tmpdir(), "webster-run-sim-incomplete-shots-")); - writeFileSync(join(siteDir, "index.html"), "
Home
"); - writeFileSync(join(siteDir, "services.html"), "
Services
"); - - await expect( - captureLocalScreenshots(siteDir, outDir, [ - "index.html", - "services.html", - "free-estimate.html", - ]), - ).rejects.toThrow("site substrate is missing required pages"); - }); - - test("site screenshot capture fails loudly when only the home page exists", async () => { - const siteDir = mkdtempSync(join(tmpdir(), "webster-run-sim-one-page-site-")); - const outDir = mkdtempSync(join(tmpdir(), "webster-run-sim-one-page-shots-")); - writeFileSync(join(siteDir, "index.html"), "
Home
"); - - await expect( - captureLocalScreenshots(siteDir, outDir, [ - "index.html", - "services.html", - "free-estimate.html", - ]), - ).rejects.toThrow("site substrate is missing required pages"); - }); - - test("site simulation rejects an incomplete one-page site through the default screenshot path", async () => { - const siteDir = mkdtempSync(join(tmpdir(), "webster-run-sim-default-one-page-site-")); - const outDir = mkdtempSync(join(tmpdir(), "webster-run-sim-default-one-page-out-")); - writeFileSync(join(siteDir, "index.html"), "

Contractor home

"); - const originalReview = process.env.WEBSTER_SYNTHETIC_ANALYTICS_REVIEW; - process.env.WEBSTER_SYNTHETIC_ANALYTICS_REVIEW = "0"; - - try { - await expect( - runSimulation( - { - ...config(outDir), - substrate: "site", - sitePath: siteDir, - outputDir: outDir, - agentSet: "webster-site-sim", - weekCount: 0, - }, - { - runCouncil: () => { - readFileSync(join(siteDir, "index.html"), "utf8"); - }, - }, - ), - ).rejects.toThrow("site substrate is missing required pages"); - } finally { - if (originalReview === undefined) { - delete process.env.WEBSTER_SYNTHETIC_ANALYTICS_REVIEW; - } else { - process.env.WEBSTER_SYNTHETIC_ANALYTICS_REVIEW = originalReview; - } - } + await expect(captureLocalScreenshots(siteDir, outDir)).rejects.toThrow( + "lp substrate is missing index.html", + ); }); test("screenshot capture writes browser-audit artifacts for file URLs or fallback summary", async () => { diff --git a/scripts/__tests__/sim-council.test.ts b/scripts/__tests__/sim-council.test.ts index 2c37dd4..b3bd2ed 100644 --- a/scripts/__tests__/sim-council.test.ts +++ b/scripts/__tests__/sim-council.test.ts @@ -55,14 +55,13 @@ describe("sim-council prompt", () => { expect(body).toContain("memory_resource redesigner read_write"); }); - test("invokes substrate-specific sim roles instead of production agent ids", () => { + test("invokes LP sim roles instead of production agent ids", () => { const body = prompt(); expect(body).toContain( - "roles=(monitor seo-critic brand-voice-critic conversion-critic copy-critic)", + "roles=(monitor seo-critic brand-voice-critic conversion-critic copy-critic fh-compliance-critic)", ); - expect(body).toContain("fh-compliance-critic"); - expect(body).toContain("licensing-and-warranty-critic"); + expect(body).not.toContain("licensing-and-warranty-critic"); expect(body).toContain("agent_id planner"); expect(body).toContain("agent_id redesigner"); expect(body).toContain("agent_id visual-reviewer"); diff --git a/scripts/__tests__/validate-agents.test.ts b/scripts/__tests__/validate-agents.test.ts index 7a5632b..fda4902 100644 --- a/scripts/__tests__/validate-agents.test.ts +++ b/scripts/__tests__/validate-agents.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import Ajv2020 from "ajv/dist/2020.js"; import addFormats from "ajv-formats"; -import { readFileSync, readdirSync } from "node:fs"; +import { readFileSync, readdirSync, statSync } from "node:fs"; import { join, resolve } from "node:path"; const ROOT = resolve(import.meta.dir, "..", ".."); @@ -18,16 +18,33 @@ function buildAjv() { return ajv; } +function findJsonFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + out.push(...findJsonFiles(full)); + } else if (entry.endsWith(".json")) { + out.push(full); + } + } + return out; +} + describe("agent schema", () => { const ajv = buildAjv(); const validate = ajv.compile(AGENT_SCHEMA); const agentsDir = join(ROOT, "agents"); - const agentFiles = readdirSync(agentsDir).filter((f) => f.endsWith(".json")); + const agentFiles = findJsonFiles(agentsDir); + const seoCriticPath = agentFiles.find((f) => f.endsWith("seo-critic.json")); + if (!seoCriticPath) { + throw new Error("seo-critic.json not found under agents/"); + } test("accepts every committed agent spec", () => { expect(agentFiles.length).toBeGreaterThan(0); for (const f of agentFiles) { - const data = JSON.parse(readFileSync(join(agentsDir, f), "utf-8")); + const data = JSON.parse(readFileSync(f, "utf-8")); const ok = validate(data); if (!ok) { throw new Error(`${f} failed: ${JSON.stringify(validate.errors, null, 2)}`); @@ -37,7 +54,7 @@ describe("agent schema", () => { }); test("rejects spec with system_prompt instead of system (the bug from bb789e3)", () => { - const raw = readFileSync(join(agentsDir, "seo-critic.json"), "utf-8"); + const raw = readFileSync(seoCriticPath, "utf-8"); const broken = { ...JSON.parse(raw), system_prompt: "hello" }; delete (broken as Record).system; const ok = validate(broken); @@ -52,13 +69,13 @@ describe("agent schema", () => { }); test("rejects spec with callable_agents (research preview)", () => { - const raw = readFileSync(join(agentsDir, "seo-critic.json"), "utf-8"); + const raw = readFileSync(seoCriticPath, "utf-8"); const broken = { ...JSON.parse(raw), callable_agents: ["other-agent"] }; expect(validate(broken)).toBe(false); }); test("rejects spec with unknown model", () => { - const raw = readFileSync(join(agentsDir, "seo-critic.json"), "utf-8"); + const raw = readFileSync(seoCriticPath, "utf-8"); const broken = { ...JSON.parse(raw), model: "claude-opus-3-5" }; expect(validate(broken)).toBe(false); }); @@ -69,7 +86,7 @@ describe("agent schema", () => { }); test("rejects spec with wrong tool type", () => { - const raw = readFileSync(join(agentsDir, "seo-critic.json"), "utf-8"); + const raw = readFileSync(seoCriticPath, "utf-8"); const broken = { ...JSON.parse(raw), tools: [{ type: "web_search_20241022" }] }; expect(validate(broken)).toBe(false); }); diff --git a/scripts/critic-genealogy.ts b/scripts/critic-genealogy.ts index ba909bc..8c86d53 100644 --- a/scripts/critic-genealogy.ts +++ b/scripts/critic-genealogy.ts @@ -30,7 +30,7 @@ const BETA = "managed-agents-2026-04-01"; const VERSION = "2023-06-01"; const VAULT_ID = "vlt_011CaLe2pEofWQptxQyV4UMd"; const LP_TARGET_DEFAULT = "https://certified.richerhealth.ca"; -const TEMPLATE_PATH = "agents/brand-voice-critic.json"; +const TEMPLATE_PATH = "agents/production/brand-voice-critic.json"; const POLL_INTERVAL_MS = 30_000; const POLL_DEADLINE_MS = 20 * 60 * 1000; const DEDUP_SIMILARITY_THRESHOLD = 0.6; @@ -201,7 +201,7 @@ function loadEnvironmentId(): string { return id; } -function loadExistingCritics(agentsDir = join(ROOT, "agents")): CriticSummary[] { +function loadExistingCritics(agentsDir = join(ROOT, "agents", "production")): CriticSummary[] { const specs: CriticSummary[] = []; for (const f of readdirSync(agentsDir)) { if (!f.endsWith("-critic.json")) { @@ -894,7 +894,7 @@ function writeArtifacts( const logPath = join(dir, "session.json"); const rationalePath = join(dir, "rationale.md"); const errorPath = snapshotError ? join(dir, "snapshot-error.txt") : undefined; - const agentPath = join(ROOT, "agents", `${spec.name}.json`); + const agentPath = join(ROOT, "agents", "production", `${spec.name}.json`); writeFileSync(specPath, JSON.stringify(spec, null, 2) + "\n"); writeFileSync(agentPath, JSON.stringify(spec, null, 2) + "\n"); writeFileSync(logPath, JSON.stringify(sessionSnapshot, null, 2) + "\n"); @@ -962,7 +962,7 @@ async function main(): Promise { const envId = loadEnvironmentId(); const archiveResult = archiveIdleSpawnedCritics( - join(ROOT, "agents"), + join(ROOT, "agents", "production"), join(ROOT, "history"), args.weekDate, ); diff --git a/scripts/planner-invoke.ts b/scripts/planner-invoke.ts index 80c04c1..802167e 100644 --- a/scripts/planner-invoke.ts +++ b/scripts/planner-invoke.ts @@ -10,7 +10,7 @@ const VERSION = "2023-06-01"; const POLL_INTERVAL_MS = 30_000; const POLL_DEADLINE_MS = 20 * 60 * 1000; const PLANNER_NAME = "webster-planner"; -const PLANNER_SPEC_PATH = "agents/webster-planner.json"; +const PLANNER_SPEC_PATH = "agents/production/webster-planner.json"; const ENVIRONMENT_ID_PATH = "environments/webster-council-env.id"; const NEXT_ACTIONS = [ diff --git a/scripts/register-sim-agents.ts b/scripts/register-sim-agents.ts index 0f57f71..bbb466c 100644 --- a/scripts/register-sim-agents.ts +++ b/scripts/register-sim-agents.ts @@ -9,10 +9,10 @@ const API_BASE = process.env.ANTHROPIC_API_BASE ?? "https://api.anthropic.com"; const API = `${API_BASE.replace(/\/$/, "")}/v1`; const BETA = "managed-agents-2026-04-01"; const VERSION = "2023-06-01"; -const AGENTS_DIR = "agents"; +const AGENTS_DIR = "agents/simulation"; const OUTPUT_PATH = "context/sim-agents.json"; -const AGENT_SETS = ["webster-lp-sim", "webster-site-sim"] as const; +const AGENT_SETS = ["webster-lp-sim"] as const; type AgentSet = (typeof AGENT_SETS)[number]; export type SimAgentManifest = Record>; @@ -37,27 +37,24 @@ function headers(apiKey: string, withContentType = false): Record /^webster-(lp|site)-sim-.*\.json$/.test(file)) + .filter((file) => /^webster-lp-sim-.*\.json$/.test(file)) .sort() .map((file) => JSON.parse(readFileSync(join(agentsDir, file), "utf8")) as AgentSpec); } function emptyManifest(): SimAgentManifest { - return { "webster-lp-sim": {}, "webster-site-sim": {} }; + return { "webster-lp-sim": {} }; } function setForAgent(name: string): AgentSet { if (name.startsWith("webster-lp-sim-")) { return "webster-lp-sim"; } - if (name.startsWith("webster-site-sim-")) { - return "webster-site-sim"; - } throw new Error(`not a sim agent name: ${name}`); } function roleKey(name: string): string { - return name.replace(/^webster-(lp|site)-sim-/, ""); + return name.replace(/^webster-lp-sim-/, ""); } async function registerAgent(apiKey: string, spec: AgentSpec): Promise { @@ -90,8 +87,8 @@ export async function registerSimAgents( agentsDir = AGENTS_DIR, ): Promise { const specs = loadSimAgentSpecs(agentsDir); - if (specs.length !== 18) { - throw new Error(`expected 18 sim agent specs, found ${specs.length}`); + if (specs.length !== 9) { + throw new Error(`expected 9 sim agent specs, found ${specs.length}`); } const manifest = emptyManifest(); diff --git a/scripts/run-simulation-site.ts b/scripts/run-simulation-site.ts deleted file mode 100644 index 4bf6ee8..0000000 --- a/scripts/run-simulation-site.ts +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bun - -import { runSimulation, type SimulationConfig } from "./run-simulation.ts"; - -export function buildSiteSimulationConfig(env: NodeJS.ProcessEnv = process.env): SimulationConfig { - return { - substrate: "site", - weekCount: 10, - startDate: env.WEBSTER_SIM_START_DATE ?? "2026-02-01", - sitePath: "demo-sites/northwest-reno/ugly", - contextPath: "demo-sites/northwest-reno/context", - outputDir: "demo-output/northwest-reno", - seed: env.WEBSTER_SIM_SEED ?? "webster-site-demo-seed", - agentSet: "webster-site-sim", - memoryStoresPath: "context/memory-stores.json", - simAgentsPath: "context/sim-agents.json", - councilCommand: - env.WEBSTER_SIM_COUNCIL_CMD ?? "bun scripts/run-markdown-bash.ts prompts/sim-council.md", - skipGit: env.WEBSTER_SIM_SKIP_GIT === "1", - skipMemorySummaries: env.WEBSTER_SIM_SKIP_MEMORY === "1", - }; -} - -export const siteSimulationConfig: SimulationConfig = buildSiteSimulationConfig(); - -if (import.meta.main) { - runSimulation(siteSimulationConfig).catch((error) => { - console.error(error instanceof Error ? error.message : String(error)); - process.exit(1); - }); -} diff --git a/scripts/run-simulation.ts b/scripts/run-simulation.ts index 2118e89..f436c5f 100644 --- a/scripts/run-simulation.ts +++ b/scripts/run-simulation.ts @@ -16,14 +16,14 @@ const VERSION = "2023-06-01"; const BETA = "managed-agents-2026-04-01"; export interface SimulationConfig { - substrate: "lp" | "site"; + substrate: "lp"; weekCount: number; startDate: string; sitePath: string; contextPath: string; outputDir: string; seed: string; - agentSet: "webster-lp-sim" | "webster-site-sim"; + agentSet: "webster-lp-sim"; memoryStoresPath: string; simAgentsPath: string; councilCommand?: string; @@ -37,11 +37,7 @@ interface RunDeps { options?: { cwd?: string; env?: Record }, ) => void; runCouncil?: (env: Record) => void | Promise; - captureScreenshots?: ( - siteDir: string, - outDir: string, - requiredPages?: string[], - ) => Promise; + captureScreenshots?: (siteDir: string, outDir: string) => Promise; writeMemorySummary?: ( config: SimulationConfig, week: number, @@ -59,11 +55,11 @@ function addDays(date: string, days: number): string { return d.toISOString().slice(0, 10); } -function substrateHistoryName(substrate: "lp" | "site"): string { +function substrateHistoryName(substrate: "lp"): string { return `${substrate}-demo`; } -function maybeEmitCaptureTrigger(substrate: "lp" | "site", week: number): void { +function maybeEmitCaptureTrigger(substrate: "lp", week: number): void { if (week !== 1 && week !== 5 && week !== 10) { return; } @@ -103,25 +99,18 @@ function fileUrl(path: string): string { return `file://${resolve(path)}`; } -function pagesForSite(siteDir: string, requiredPages?: string[]): string[] { - const pageNames = requiredPages ?? ["index.html", "services.html", "free-estimate.html"]; - const pagePaths = pageNames.map((file) => join(siteDir, file)); - const existingPages = pagePaths.filter((file) => existsSync(file)); - if (requiredPages && existingPages.length !== pagePaths.length) { - const missingPages = pagePaths.filter((file) => !existsSync(file)); - throw new Error(`site substrate is missing required pages: ${missingPages.join(", ")}`); +function pagesForSite(siteDir: string): string[] { + const indexPath = join(siteDir, "index.html"); + if (!existsSync(indexPath)) { + throw new Error(`lp substrate is missing index.html at ${siteDir}`); } - return requiredPages ? existingPages : existingPages.slice(0, 1); + return [indexPath]; } -export async function captureLocalScreenshots( - siteDir: string, - outDir: string, - requiredPages?: string[], -): Promise { +export async function captureLocalScreenshots(siteDir: string, outDir: string): Promise { mkdirSync(outDir, { recursive: true }); const outputs: string[] = []; - for (const pagePath of pagesForSite(siteDir, requiredPages)) { + for (const pagePath of pagesForSite(siteDir)) { const pageOutDir = join(outDir, basename(pagePath, ".html")); mkdirSync(pageOutDir, { recursive: true }); const result = Bun.spawnSync( @@ -287,14 +276,9 @@ export async function runSimulation( SIM_AGENTS_JSON: config.simAgentsPath, }); - const requiredScreenshotPages = - config.substrate === "site" - ? ["index.html", "services.html", "free-estimate.html"] - : undefined; const screenshotDirs = await captureScreenshots( config.sitePath, join(outputWeekDir, "screenshots"), - requiredScreenshotPages, ); await writeMemorySummary(config, week, analyticsOutput.analytics); maybeEmitCaptureTrigger(config.substrate, week); diff --git a/scripts/sim-preflight.ts b/scripts/sim-preflight.ts index e855272..37423f9 100644 --- a/scripts/sim-preflight.ts +++ b/scripts/sim-preflight.ts @@ -8,7 +8,7 @@ type SimAgentsManifest = Record>; type MemoryStoresManifest = Record>; -const REQUIRED_AGENT_SETS = ["webster-lp-sim", "webster-site-sim"] as const; +const REQUIRED_AGENT_SETS = ["webster-lp-sim"] as const; const REQUIRED_AGENT_ROLES = [ "monitor", "seo-critic", @@ -18,6 +18,7 @@ const REQUIRED_AGENT_ROLES = [ "redesigner", "planner", "visual-reviewer", + "fh-compliance-critic", ] as const; const REQUIRED_MEMORY_ROLES = [ "council", @@ -41,19 +42,15 @@ function readJson(path: string): T { function checkManifestFiles(): void { const agents = readJson("context/sim-agents.json"); - if (Object.keys(agents).length !== 2) { - fail("context/sim-agents.json must contain exactly 2 sim agent sets"); + if (Object.keys(agents).length !== 1) { + fail("context/sim-agents.json must contain exactly 1 sim agent set"); } for (const set of REQUIRED_AGENT_SETS) { const roles = agents[set] ?? fail(`missing agent set ${set}`); - const expectedRoles = - set === "webster-site-sim" - ? [...REQUIRED_AGENT_ROLES, "licensing-and-warranty-critic"] - : [...REQUIRED_AGENT_ROLES, "fh-compliance-critic"]; if (Object.keys(roles).length !== 9) { fail(`${set} must contain exactly 9 registered roles`); } - for (const role of expectedRoles) { + for (const role of REQUIRED_AGENT_ROLES) { if (!roles[role]) { fail(`missing ${set}.${role} in context/sim-agents.json`); } @@ -61,10 +58,10 @@ function checkManifestFiles(): void { } const stores = readJson("context/memory-stores.json"); - if (Object.keys(stores).length !== 2) { - fail("context/memory-stores.json must contain exactly 2 substrates"); + if (Object.keys(stores).length !== 1) { + fail("context/memory-stores.json must contain exactly 1 substrate"); } - for (const substrate of ["lp", "site"] as const) { + for (const substrate of ["lp"] as const) { const roles = stores[substrate] ?? fail(`missing memory stores for ${substrate}`); if (Object.keys(roles).length !== 6) { fail(`${substrate} must contain exactly 6 memory stores`); @@ -92,7 +89,6 @@ function checkScriptBuilds(path: string): void { export function runSimPreflight(): void { checkManifestFiles(); checkScriptBuilds("scripts/run-simulation-lp.ts"); - checkScriptBuilds("scripts/run-simulation-site.ts"); checkScriptBuilds("scripts/capture-mem-stores.ts"); checkCommand(["browser-use", "doctor"]); if (process.env.WEBSTER_REQUIRE_CONSOLE_CAPTURE !== "1") { diff --git a/scripts/validate-agents.ts b/scripts/validate-agents.ts index 24aac56..d23fac9 100644 --- a/scripts/validate-agents.ts +++ b/scripts/validate-agents.ts @@ -8,7 +8,7 @@ import Ajv2020, { type ErrorObject } from "ajv/dist/2020.js"; import addFormats from "ajv-formats"; -import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { join, resolve } from "node:path"; const ROOT = resolve(import.meta.dir, ".."); @@ -80,15 +80,25 @@ function validateFile(file: string, validator: typeof validateAgent): CheckResul }; } +function findJsonFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + out.push(...findJsonFiles(full)); + } else if (entry.endsWith(".json")) { + out.push(full); + } + } + return out; +} + function main(): number { const results: CheckResult[] = []; const agentsDir = join(ROOT, "agents"); if (existsSync(agentsDir)) { - const agentFiles = readdirSync(agentsDir) - .filter((f) => f.endsWith(".json")) - .map((f) => join(agentsDir, f)); - for (const f of agentFiles) { + for (const f of findJsonFiles(agentsDir)) { results.push(validateFile(f, validateAgent)); } }