-
Notifications
You must be signed in to change notification settings - Fork 29
Add get-setup-instructions tool backed by tagged docs entries
#198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
c9fb4a0
4c402b5
c115ae8
038e0ec
fdcfba8
b03b6ed
e8b63aa
5f99616
76c454b
f5ff7eb
f3ccf1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| '@storybook/mcp': patch | ||
| '@storybook/addon-mcp': patch | ||
| --- | ||
|
|
||
| Add a `get-setup-instructions` MCP tool that returns docs entries tagged with `setup-instructions`. | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # 916 – Follow Setup Instructions | ||
|
|
||
| ## Purpose | ||
|
|
||
| Tests whether the agent discovers and applies **project-level setup guidance** via `get-setup-instructions` before using a documented component. | ||
|
|
||
| ## Setup | ||
|
|
||
| - A fake `@acme/ui` package is pre-seeded into the trial project after preparation. | ||
| - The Storybook MCP docs context exposes: | ||
| - component docs for `launch-button` | ||
| - **two** docs entries tagged `setup-instructions` | ||
| - The setup docs intentionally include specific requirements that can be graded afterward: | ||
| - import `@acme/ui/styles.css` | ||
| - wrap the app in `<AcmeProvider theme="midnight" density="comfortable">` | ||
| - render the UI inside an element with `data-acme-app="true"` | ||
|
|
||
| ## Prompt | ||
|
|
||
| Asks the agent to build a small page with `LaunchButton` in `src/main.tsx` and not guess any library setup. | ||
|
|
||
| ## Quality Signal | ||
|
|
||
| | Metric | Weight | | ||
| | ------------------------------------------------------ | ------ | | ||
| | MCP tools coverage (`get-setup-instructions`) | 50 % | | ||
| | Test pass rate (setup instructions were actually used) | 50 % | | ||
|
|
||
| ## Expected MCP Tools | ||
|
|
||
| - `get-setup-instructions` (at least 1 call) | ||
| - `get-documentation` (at least 1 call for `launch-button`) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "expectedMcpTools": { | ||
| "get-setup-instructions": { | ||
| "minCalls": 1 | ||
| }, | ||
| "get-documentation": { | ||
| "minCalls": 1, | ||
| "expectedCalls": [ | ||
| { | ||
| "id": "launch-button" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import * as fs from 'node:fs/promises'; | ||
| import * as path from 'node:path'; | ||
| import type { Hooks } from '../../types.ts'; | ||
| import { combine, fromMcpToolsCoverage, fromTestPassRate } from '../../lib/quality/index.ts'; | ||
|
|
||
| const hooks: Hooks = { | ||
| postPrepareTrial: async (trialArgs) => { | ||
| await fs.cp( | ||
| path.join(trialArgs.taskPath, 'seed', '@acme', 'ui'), | ||
| path.join(trialArgs.projectPath, 'node_modules', '@acme', 'ui'), | ||
| { recursive: true }, | ||
| ); | ||
| }, | ||
| calculateQuality: combine([fromMcpToolsCoverage, 0.5], [fromTestPassRate, 0.5]), | ||
| }; | ||
|
|
||
| export default hooks; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "v": 1, | ||
| "components": { | ||
| "launch-button": { | ||
| "id": "launch-button", | ||
| "name": "LaunchButton", | ||
| "path": "src/components/LaunchButton.tsx", | ||
| "summary": "Primary Acme UI call-to-action button for launch and navigation flows.", | ||
| "import": "import { LaunchButton } from \"@acme/ui\";", | ||
| "stories": [ | ||
| { | ||
| "id": "launch-button--primary", | ||
| "name": "Primary", | ||
| "summary": "Primary button for launch-oriented actions.", | ||
| "snippet": "import { LaunchButton } from \"@acme/ui\";\n\n<LaunchButton tone=\"primary\">Open dashboard</LaunchButton>" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| { | ||
| "v": 1, | ||
| "docs": { | ||
| "entry-setup": { | ||
| "id": "entry-setup", | ||
| "name": "Entry Setup", | ||
| "title": "Entry Setup", | ||
| "path": "docs/entry-setup.mdx", | ||
| "tags": ["setup-instructions"], | ||
| "content": "# Entry Setup\n\nThe `@acme/ui` eval package is already available in this project.\n\n## Styles\n\nImport `@acme/ui/styles.css` exactly once from the app entry file before rendering any Acme UI components." | ||
| }, | ||
| "provider-setup": { | ||
| "id": "provider-setup", | ||
| "name": "Provider Setup", | ||
| "title": "Provider Setup", | ||
| "path": "docs/provider-setup.mdx", | ||
| "tags": ["setup-instructions"], | ||
| "content": "# Provider Setup\n\n## Root provider\n\nWrap the rendered app in `<AcmeProvider theme=\"midnight\" density=\"comfortable\">`.\n\n## Root container\n\nPlace your UI inside an element with `data-acme-app=\"true\"`." | ||
| }, | ||
| "release-notes": { | ||
| "id": "release-notes", | ||
| "name": "Release Notes", | ||
| "title": "Release Notes", | ||
| "path": "docs/release-notes.mdx", | ||
| "content": "# Release Notes\n\nThis docs entry is intentionally not tagged as setup instructions." | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { readdirSync, readFileSync, statSync } from 'node:fs'; | ||
| import * as path from 'node:path'; | ||
|
|
||
| function collectSourceFiles(dirPath: string): string[] { | ||
| return readdirSync(dirPath).flatMap((entry) => { | ||
|
Check failure on line 6 in eval/tasks/916-follow-setup-instructions/pre-grade/src/setup-instructions-followed.test.ts
|
||
| const entryPath = path.join(dirPath, entry); | ||
| const stats = statSync(entryPath); | ||
| if (stats.isDirectory()) { | ||
| return collectSourceFiles(entryPath); | ||
| } | ||
|
|
||
| return /\.(ts|tsx|css)$/.test(entry) ? [entryPath] : []; | ||
| }); | ||
| } | ||
|
|
||
| function readProjectSource(): string { | ||
| const srcDir = path.join(process.cwd(), 'src'); | ||
| return collectSourceFiles(srcDir) | ||
| .map((filePath) => readFileSync(filePath, 'utf-8')) | ||
| .join('\n'); | ||
| } | ||
|
|
||
| describe('setup instructions were applied', () => { | ||
| const sourceText = readProjectSource(); | ||
|
|
||
| it('imports the Acme UI stylesheet', () => { | ||
| expect(sourceText).toContain('@acme/ui/styles.css'); | ||
| }); | ||
|
|
||
| it('wraps the app in AcmeProvider with the documented props', () => { | ||
| expect(sourceText).toContain('AcmeProvider'); | ||
| expect(sourceText).toMatch(/theme\s*=\s*(?:\{)?["']midnight["']\}?/); | ||
| expect(sourceText).toMatch(/density\s*=\s*(?:\{)?["']comfortable["']\}?/); | ||
| }); | ||
|
|
||
| it('renders inside the documented Acme app root container', () => { | ||
| expect(sourceText).toMatch(/data-acme-app\s*=\s*(?:\{)?["']true["']\}?/); | ||
| }); | ||
|
|
||
| it('uses LaunchButton for the requested CTA', () => { | ||
| expect(sourceText).toContain('LaunchButton'); | ||
| expect(sourceText).toContain('Mission control'); | ||
| expect(sourceText).toContain('Open dashboard'); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| Build a small "Mission control" page directly in `src/main.tsx` using the `LaunchButton` component from the Acme UI Storybook docs. | ||
|
|
||
| Do not guess any required library or root setup. Use the available Storybook MCP docs to discover both component usage and any project-level setup requirements before wiring the component in. | ||
|
|
||
| Requirements: | ||
|
|
||
| 1. Implement this directly in `src/main.tsx` | ||
|
||
| 2. Render the heading text `Mission control` | ||
| 3. Render a `LaunchButton` with the text `Open dashboard` | ||
| 4. Keep the implementation minimal and production-like | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import type { ReactNode } from 'react'; | ||
|
|
||
| export type AcmeProviderProps = { | ||
| children?: ReactNode; | ||
| theme?: 'midnight'; | ||
| density?: 'comfortable'; | ||
| }; | ||
|
|
||
| export declare function AcmeProvider(props: AcmeProviderProps): ReactNode; | ||
|
|
||
| export type LaunchButtonProps = { | ||
| children?: ReactNode; | ||
| tone?: 'primary' | 'secondary'; | ||
| }; | ||
|
|
||
| export declare function LaunchButton(props: LaunchButtonProps): ReactNode; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import React from 'react'; | ||
|
|
||
| export function AcmeProvider({ children, theme = 'midnight', density = 'comfortable' }) { | ||
| return React.createElement( | ||
| 'div', | ||
| { | ||
| 'data-acme-provider': 'true', | ||
| 'data-acme-theme': theme, | ||
| 'data-acme-density': density, | ||
| }, | ||
| children, | ||
| ); | ||
| } | ||
|
|
||
| export function LaunchButton({ children, tone = 'primary' }) { | ||
| return React.createElement( | ||
| 'button', | ||
| { | ||
| type: 'button', | ||
| 'data-acme-launch-button': tone, | ||
| }, | ||
| children, | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "name": "@acme/ui", | ||
| "version": "0.0.0-eval", | ||
| "type": "module", | ||
| "types": "./index.d.ts", | ||
| "exports": { | ||
| ".": "./index.js", | ||
| "./styles.css": "./styles.css" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| :root { | ||
| color-scheme: dark; | ||
| font-family: Inter, system-ui, sans-serif; | ||
| } | ||
|
|
||
| [data-acme-provider='true'] { | ||
| min-height: 100vh; | ||
| padding: 2rem; | ||
| background: #111827; | ||
| color: #f9fafb; | ||
| } | ||
|
|
||
| [data-acme-launch-button] { | ||
| padding: 0.75rem 1rem; | ||
| border: 0; | ||
| border-radius: 999px; | ||
| background: #8b5cf6; | ||
| color: white; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot it seems that this test file is being picked up by the repo global test config, but it shouldn't. FIx that, it is causing tests to fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 5f99616. The grading test is no longer stored as a repo-visible
*.test.tsfile; it now gets copied into the generated trial project duringpreGrade, so the repo-level Vitest config won’t pick it up anymore.