Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

AI Launchpad: add cross-stream contract files (AI output JSON schema + eval fixtures)
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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+."
}
}
]
}