From 9159f9a9852e23cd4ef651c47e88f41b969e8ec6 Mon Sep 17 00:00:00 2001 From: Copons Date: Thu, 11 Jun 2026 17:25:53 +0100 Subject: [PATCH] jetpack-mu-wpcom: add AI Launchpad cross-stream contract files --- .../changelog/add-ai-launchpad-contracts | 4 + .../contracts/agent-output-schema.json | 100 +++++++++++++ .../ai-launchpad/contracts/eval-fixtures.json | 134 ++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 projects/packages/jetpack-mu-wpcom/changelog/add-ai-launchpad-contracts create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/agent-output-schema.json create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/eval-fixtures.json diff --git a/projects/packages/jetpack-mu-wpcom/changelog/add-ai-launchpad-contracts b/projects/packages/jetpack-mu-wpcom/changelog/add-ai-launchpad-contracts new file mode 100644 index 00000000000..f00f8713a07 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/add-ai-launchpad-contracts @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +AI Launchpad: add cross-stream contract files (AI output JSON schema + eval fixtures) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/agent-output-schema.json b/projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/agent-output-schema.json new file mode 100644 index 00000000000..d9a4fea169e --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/agent-output-schema.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/Automattic/jetpack/projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/agent-output-schema.json", + "title": "AI Launchpad Selector Agent Output", + "description": "Cross-stream contract for the AI Launchpad. Three consumers: (1) Stream F's `js/lib/tailor.ts` parses the `jetpack-ai-query` response and validates against this schema (Ajv); (2) Stream B's `PUT /tailored` validates incoming request bodies against this schema server-side; (3) the Node eval runner (`bin/eval-ai-launchpad.mjs`) validates fixture outputs against this schema during prompt iteration. Change this schema and three test paths fail in lockstep — that's the point.", + "type": "object", + "required": [ "tasks", "inferred", "first_post_draft" ], + "additionalProperties": false, + "properties": { + "tasks": { + "type": "array", + "minItems": 6, + "maxItems": 6, + "description": "Exactly six task selections. The last task MUST be a launch task (canonical: `site_launched`; flow-specific alternatives: `blog_launched`, `woo_launch_site`, `link_in_bio_launched`, `videopress_launched`); server enforces. Order follows the STEP rules from the system prompt: first-creation → niche-specific → foundation → launch. Catalog uses snake_case IDs.", + "items": { + "type": "object", + "required": [ "id", "subtitle" ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "Task ID drawn from the legacy PHP launchpad catalog. Stream B's PUT /tailored re-validates id ∈ menu; rejects with 422 if intersection < 4." + }, + "subtitle": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "description": "AI-generated subtitle (v1 = English only). Server strips HTML and rejects URLs / template syntax before persisting." + } + } + } + }, + "inferred": { + "type": "object", + "required": [ "goal" ], + "additionalProperties": false, + "description": "Structured extraction from the user's free-text description, reused downstream by theme picker, post draft, etc.", + "properties": { + "goal": { + "type": "string", + "enum": [ "write", "build", "sell", "newsletter", "educate", "portfolio" ], + "description": "Echoes or re-resolves the wizard goal. Single source of truth downstream." + }, + "brand_name": { + "type": "string", + "maxLength": 80, + "description": "Per the prompt's name-resolution rule: prefer the wizard's site_name over anything in the free-text description." + }, + "niche": { + "type": "string", + "maxLength": 120 + }, + "vibe": { + "type": "string", + "maxLength": 120 + }, + "audience": { + "type": "string", + "maxLength": 200 + }, + "tagline": { + "type": "string", + "maxLength": 200, + "description": "Used as the site tagline by the wizard's tagline-write completion." + } + } + }, + "first_post_draft": { + "type": "object", + "required": [ "title", "paragraphs" ], + "additionalProperties": false, + "description": "Starter post content. Persisted via POST /wp/v2/posts as status=draft when the user clicks the first-post task CTA.", + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "≤8 words per the system prompt." + }, + "subtitle": { + "type": "string", + "maxLength": 120, + "description": "Verb-led, ≤10 words per the system prompt. Optional." + }, + "paragraphs": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "description": "Two paragraphs; warm tone; no 'Welcome to my blog' clichés per the system prompt.", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 1200 + } + } + } + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/eval-fixtures.json b/projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/eval-fixtures.json new file mode 100644 index 00000000000..b44efe2c138 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/contracts/eval-fixtures.json @@ -0,0 +1,134 @@ +{ + "_note": "Six smoke fixtures, one per goal. Consumed by `bin/eval-ai-launchpad.mjs` (Node runner that POSTs each to `/wpcom/v2/jetpack-ai-query` with the AI Launchpad prompt and validates the response against `agent-output-schema.json`) and by the PL during manual click-through on the Atomic test site. Task IDs are validated against the actual catalog in `jetpack-mu-wpcom/src/features/launchpad/launchpad-task-definitions.php` (verified 2026-06-02). Catalog uses snake_case throughout. Expectations are deliberately loose: `must_include_one_of` is a hard assertion, `should_match_niche_vocab` is a soft vocabulary check, `banned_tasks` is a hard exclusion.", + "_catalog_caveats": "Two goals are weakly supported by the current catalog: (1) `portfolio` has no native gallery / portfolio-piece tasks, so the portfolio fixture falls back to generic build tasks (theme, about page, first post); (2) `educate` has only `sensei_setup`, so the educate fixture falls back to generic write/build tasks. Adding portfolio/educate-specific tasks is v1.1+.", + "version": 1, + "fixtures": [ + { + "name": "alpine-notes-write", + "input": { + "goal": "write", + "site_name": "Alpine Notes", + "description": "Personal blog about long-distance hiking in the Alps.", + "locale": "en" + }, + "expectations": { + "must_include_one_of": [ "first_post_published" ], + "should_match_niche_vocab": [ "hiking", "outdoor", "trail", "travel", "alps", "mountain" ], + "banned_tasks": [ + "woo_products", + "set_up_payments", + "stripe_connected", + "woo_woocommerce_payments" + ], + "inferred_brand_name": "Alpine Notes", + "last_task_is_launch": true + } + }, + { + "name": "atelier-mercier-build", + "input": { + "goal": "build", + "site_name": "Atelier Mercier", + "description": "Small architecture studio in Lyon, residential projects.", + "locale": "en" + }, + "expectations": { + "must_include_one_of": [ "site_theme_selected", "add_about_page" ], + "should_match_niche_vocab": [ "architect", "studio", "design", "portfolio", "project" ], + "banned_tasks": [ "woo_products", "set_up_payments", "add_10_email_subscribers" ], + "inferred_brand_name": "Atelier Mercier", + "last_task_is_launch": true + } + }, + { + "name": "terra-ceramics-sell", + "input": { + "goal": "sell", + "site_name": "Terra Ceramics", + "description": "Handmade ceramic homewares from a one-person studio.", + "locale": "en" + }, + "expectations": { + "must_include_one_of": [ + "woo_products", + "set_up_payments", + "stripe_connected", + "woo_woocommerce_payments" + ], + "should_match_niche_vocab": [ + "ceramic", + "homeware", + "handmade", + "craft", + "pottery", + "shop" + ], + "banned_tasks": [ "add_10_email_subscribers", "newsletter_plan_created" ], + "inferred_brand_name": "Terra Ceramics", + "last_task_is_launch": true + } + }, + { + "name": "off-beat-newsletter", + "input": { + "goal": "newsletter", + "site_name": "Off-Beat", + "description": "Weekly digest of independent music recommendations.", + "locale": "en" + }, + "expectations": { + "must_include_one_of": [ + "add_10_email_subscribers", + "first_post_published", + "first_post_published_newsletter", + "subscribers_added" + ], + "should_match_niche_vocab": [ "music", "independent", "newsletter", "digest", "subscribe" ], + "banned_tasks": [ "woo_products", "stripe_connected", "woo_woocommerce_payments" ], + "inferred_brand_name": "Off-Beat", + "last_task_is_launch": true + } + }, + { + "name": "piano-at-40-educate", + "input": { + "goal": "educate", + "site_name": "Piano at 40", + "description": "Online piano lessons for adult beginners.", + "locale": "en" + }, + "expectations": { + "must_include_one_of": [ "first_post_published", "add_about_page", "sensei_setup" ], + "should_match_niche_vocab": [ "piano", "lesson", "music", "teach", "student", "adult" ], + "banned_tasks": [ "woo_products", "stripe_connected" ], + "inferred_brand_name": "Piano at 40", + "last_task_is_launch": true, + "_caveat": "Catalog has only one education-specific task (`sensei_setup`); fallback to generic write/build tasks is expected." + } + }, + { + "name": "lea-dupin-portfolio", + "input": { + "goal": "portfolio", + "site_name": "Léa Dupin", + "description": "Freelance illustrator showcasing editorial commissions.", + "locale": "en" + }, + "expectations": { + "must_include_one_of": [ "site_theme_selected", "add_about_page", "first_post_published" ], + "should_match_niche_vocab": [ + "illustrator", + "illustration", + "editorial", + "art", + "portfolio", + "commission" + ], + "banned_tasks": [ "woo_products", "set_up_payments", "add_10_email_subscribers" ], + "inferred_brand_name": "Léa Dupin", + "last_task_is_launch": true, + "_caveat": "Catalog has no portfolio-specific tasks (no setup_gallery / add_portfolio_piece). Fixture falls back to theme + about page + first post. Adding portfolio tasks is v1.1+." + } + } + ] +}