diff --git a/.gitignore b/.gitignore index 71cb687..1e6147a 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,22 @@ logs/ .env .env.local +# Local pipeline settings may contain API keys +config/settings.yaml +config/settings.yml +config/settings.json + +# Generated pipeline outputs and simulation runs +generation_pipeline/outputs/* +!generation_pipeline/outputs/.gitkeep +generation_pipeline/old_outputs/ +generation_pipeline/**/__pycache__/ +runs/ +runs_*/ +experiments/ +experiments_*/ +.cache/stage5/ + # OS Thumbs.db diff --git a/README.md b/README.md index b261a49..32baa60 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,136 @@
HumanStudy-Bench Logo -

HumanStudy-Bench: Community Edition

-

Open community-driven expansion of the HumanStudy-Bench benchmark

- - Read the Paper - License: MIT - Contributions Welcome - Docs +

HumanStudy-Bench

+

A benchmark for evaluating AI agents through runnable human studies

+ Read the Paper + MIT License + Contributions welcome
--- -> LLMs are increasingly used to simulate human participants in social science research, but existing evaluations conflate base model capabilities with agent design choices, making it unclear whether results reflect the model or the configuration. +HumanStudy-Bench is a standardized testbed for replaying human-subject +experiments with AI agents. It combines: + +- an execution engine that reconstructs published experimental protocols; +- a growing collection of runnable social-science studies; and +- evaluation at the level of behavior, effects, and scientific findings. + +The 12 foundational studies cover cognition, strategic interaction, and social +psychology. Community studies live alongside them under [`studies/`](studies/). -## Overview +## Add a study -HumanStudy-Bench treats participant simulation as an *agent design problem* and provides a **standardized testbed** — combining an **Execution Engine** that reconstructs full experimental protocols from published studies and a **Benchmark** with standardized evaluation metrics — for *replaying human-subject experiments end-to-end* with alignment evaluation at the level of scientific inference. +There are two contribution paths. -## How to Contribute a Study +| Path | Best for | Current status | +|---|---|---| +| **Build Study** | Researchers starting from a paper PDF and optional open materials | **Private beta.** HumanStudy-Hub runs this repository's pipeline, persists review decisions, and produces a ZIP or GitHub pull request. | +| **Direct pull request** | Contributors who want full control over study files | **Available now.** Build the folder locally, run validation, and open a PR. | + +The intended Build Study workflow is: + +```text +paper PDF + optional OSF/open materials + -> evidence and study extraction + -> researcher review at uncertain decisions + -> runnable HumanStudy-Bench folder + -> download ZIP and/or save to GitHub +``` -### 1. Fork and clone +Build Study is currently operated through the private HumanStudy-Hub web +application. The direct contribution documentation remains supported for +researchers who prefer to author or inspect every file themselves. The manual +entry point is the [direct pull request guide](docs/submit_study.md). + +### Direct pull request + +1. Fork this repository and clone your fork. ```bash git clone https://github.com//HumanStudy-Bench.git cd HumanStudy-Bench -git checkout -b contrib--013 +git checkout -b contrib-- ``` -### 2. Create your study folder - -Add a new directory under `studies/` with the required folders: +2. Add a study folder. +```text +studies/_/ + index.json + README.md + source/ + scripts/ ``` -studies/_013/ - ├── index.json - ├── source/ - ├── scripts/ - └── README.md + +3. Validate it locally. + +```bash +bash scripts/verify_study.sh _ ``` -See the docs below for what goes inside each folder and the exact schemas: +4. Commit the study, push your branch, and open a pull request targeting + `main`. CI validates the package and contributor attribution; maintainers + complete the human review and assign the final study ID. -| # | Guide | Description | -|---|-------|-------------| -| 1 | [What Should I Submit?](https://www.hs-bench.clawder.ai/docs/what_to_submit) | Overview of contribution, required folders and files | -| 2 | [How to Extract Data from a Paper](https://www.hs-bench.clawder.ai/docs/extract_from_paper) | Paper hierarchy, AI extraction prompt, walkthrough example | -| 3 | [How to Build Your Study Files](https://www.hs-bench.clawder.ai/docs/build_study_files) | Schemas, code examples, and contracts for each file | -| 4 | [How to Submit Your Study](https://www.hs-bench.clawder.ai/docs/submit_study) | Fork, verify, push, and open a PR | +Contributor guides: -### 3. Verify locally +- [What should I submit?](docs/what_to_submit.md) +- [How to extract data from a paper](docs/extract_from_paper.md) +- [How to build study files](docs/build_study_files.md) +- [How to submit a study](docs/submit_study.md) -```bash -bash scripts/verify_study.sh _013 -``` +## Generation pipeline -### 4. Commit and push +This repository includes a human-in-the-loop pipeline for drafting a study +folder from a paper PDF and optional OSF or supplementary sources. -```bash -git add studies/_013/ -git commit -m "Add study: " -git push origin contrib--013 -``` +1. Inventory empirical units and comparison groups. +2. Extract findings, effects, samples, and statistics. +3. Assemble and audit participant-facing materials. +4. Write a package under `studies//`. +5. Run a simulation only after the package passes readiness review. -### 5. Open a Pull Request +Install the generation dependencies: -Open a PR on GitHub targeting the `main` branch. Maintainers assign final `study_XXX` numbering by merge order. CI runs validation automatically; confirmation is by human review. +```bash +python -m pip install -e ".[llm,pdf]" +cp config/settings.example.yaml config/settings.yaml +``` -You can also submit a study via **web upload** at [hs-bench.clawder.ai/contribute](https://www.hs-bench.clawder.ai/contribute). +Do not commit `config/settings.yaml`. -## Existing Studies +Run a stage with: -The 12 foundational studies (cognition, strategic interaction, social psychology) serve as reference examples. Browse them on the [website](https://www.hs-bench.clawder.ai/contribute#studies) or locally under `studies/`. +```bash +python generation_pipeline/run.py \ + --settings config/settings.yaml \ + --stage 1 \ + --pdf /absolute/path/to/paper.pdf \ + --provider openai \ + --model gpt-5-mini +``` -## Citation +Generated evidence and review artifacts are written under +`generation_pipeline/outputs//`. Pipeline-generated packages remain +blocked from simulation until `source/audit.json` marks them ready. The pipeline +entry point is [`generation_pipeline/run.py`](generation_pipeline/run.py); run +it with `--help` for the full stage and review options. + +## Repository structure + +```text +studies/ Runnable benchmark studies +src/ Execution and evaluation code +generation_pipeline/ PDF/material extraction and package generation +scripts/ Study validation and index utilities +docs/ Manual contribution guides +tests/ Pipeline and benchmark tests +``` -If you use HumanStudy-Bench, please cite: +## Citation ```bibtex @misc{liu2026humanstudybenchaiagentdesign, @@ -90,6 +144,6 @@ If you use HumanStudy-Bench, please cite: } ``` -## License +## License -MIT License. See [LICENSE](LICENSE) for details. +MIT License. See [LICENSE](LICENSE). diff --git a/co_website/.gitignore b/co_website/.gitignore deleted file mode 100644 index 5ef6a52..0000000 --- a/co_website/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/co_website/README.md b/co_website/README.md deleted file mode 100644 index 9b95270..0000000 --- a/co_website/README.md +++ /dev/null @@ -1,41 +0,0 @@ -## co_website (HumanStudy-Bench web app) - -Next.js app for: -- Homepage sections (`Overview`, `Leaderboard`, `Dataset`) -- Study catalog and study detail pages -- Contribute flow (upload + PR creation) -- Docs tab (`/docs`) - -## Local preview - -```bash -cd co_website -npm install -npm run dev -``` - -Open `http://localhost:3000`. - -## Build checks - -```bash -cd co_website -npm run lint -npm run build -``` - -## Study ZIP download - -ZIP downloads are served by: -- `GET /api/studies/:studyId/zip` - -This endpoint packages `studies/:studyId` directly from the repository. - -## Refreshing effect-size plot data - -Homepage effect-size scatter reads: -- `public/data/effects/gemini_flash_v4_effect_data.json` - -To refresh: -1. From repo root: `python scripts/plot_single_fig3_effects.py --export-data /absolute/path/to/co_website/public/data/effects/gemini_flash_v4_effect_data.json` -2. Rebuild/redeploy the site. diff --git a/co_website/adm-zip.d.ts b/co_website/adm-zip.d.ts deleted file mode 100644 index 1caa077..0000000 --- a/co_website/adm-zip.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -declare module "adm-zip" { - interface ZipEntry { - isDirectory: boolean; - entryName: string; - getData(): Buffer; - } - - class AdmZip { - constructor(buffer?: Buffer); - getEntries(): ZipEntry[]; - } - - export default AdmZip; -} diff --git a/co_website/app/api/contribute/upload/route.ts b/co_website/app/api/contribute/upload/route.ts deleted file mode 100644 index 3aa42a0..0000000 --- a/co_website/app/api/contribute/upload/route.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -const MAX_ZIP_BYTES = 50 * 1024 * 1024; // 50MB - -function getStudyIdFromPath(entryName: string): string | null { - const m = entryName.match(/^(?:data\/studies\/)?(study_\d{3})\//) || entryName.match(/^(study_\d{3})\//); - return m ? m[1] : null; -} - -function parseJson(buf: Buffer): unknown { - return JSON.parse(buf.toString("utf8")); -} - -function normalizeGithub(input: string): string { - const raw = input.trim(); - if (!raw) return ""; - if (raw.startsWith("http://") || raw.startsWith("https://")) return raw; - return `https://github.com/${raw.replace(/^@/, "")}`; -} - -type ValidationResult = { ok: true; studyId: string; files: Map } | { ok: false; errors: string[] }; - -function validateZip(entries: { name: string; data: Buffer }[]): ValidationResult { - const errors: string[] = []; - const fileMap = new Map(); - for (const e of entries) { - if (e.data.length) fileMap.set(e.name, e.data); - } - - let studyId: string | null = null; - for (const name of fileMap.keys()) { - const id = getStudyIdFromPath(name) || (name.startsWith("study_") && name.match(/^study_\d{3}/)?.[0]); - if (id) { - studyId = id; - break; - } - } - if (!studyId) { - const dataStudy = [...fileMap.keys()].find((k) => k.includes("studies/study_")); - const match = dataStudy?.match(/study_\d{3}/); - studyId = match ? match[0] : null; - } - if (!studyId) { - errors.push("Zip must contain a study_XXX directory (e.g. studies/study_014/ or study_014/)."); - return { ok: false, errors }; - } - - const dataPrefix = `studies/${studyId}/`; - const dataPrefixAlt = `${studyId}/`; - const getData = (path: string) => - fileMap.get(dataPrefix + path) || fileMap.get(dataPrefixAlt + path); - - const specBuf = getData("specification.json"); - const metaBuf = getData("metadata.json"); - const gtBuf = getData("ground_truth.json"); - if (!specBuf) errors.push("Missing specification.json"); - if (!metaBuf) errors.push("Missing metadata.json"); - if (!gtBuf) errors.push("Missing ground_truth.json"); - - let spec: Record; - let meta: Record; - let gt: Record; - try { - spec = specBuf ? (parseJson(specBuf) as Record) : {}; - meta = metaBuf ? (parseJson(metaBuf) as Record) : {}; - gt = gtBuf ? (parseJson(gtBuf) as Record) : {}; - } catch { - errors.push("Invalid JSON in specification, metadata, or ground_truth."); - return { ok: false, errors }; - } - - if (spec) { - if (!spec.participants || typeof (spec.participants as Record)?.n !== "number") - errors.push("specification.json must have participants.n (number)."); - if (!spec.design) errors.push("specification.json must have design."); - if (!spec.procedure) errors.push("specification.json must have procedure."); - } - if (meta) { - if (!meta.id && !meta.title) errors.push("metadata.json must have id or title."); - if (!meta.authors) errors.push("metadata.json must have authors."); - if (!meta.domain) errors.push("metadata.json must have domain."); - if (!meta.findings) errors.push("metadata.json must have findings."); - } - const studies = gt?.studies as Array> | undefined; - if (!Array.isArray(studies) || studies.length === 0) { - errors.push("ground_truth.json must have a non-empty studies array."); - } - - const materialsEntries = [...fileMap.entries()].filter( - ([n]) => n.startsWith(dataPrefix + "materials/") || n.startsWith(dataPrefixAlt + "materials/") - ); - if (materialsEntries.length === 0) errors.push("At least one materials/*.json file is required."); - const gtKeys = new Set(); - if (Array.isArray(studies)) { - for (const s of studies) { - const findings = s.findings as Array> | undefined; - if (!Array.isArray(findings)) continue; - for (const f of findings) { - const odp = f.original_data_points as Record | undefined; - const data = odp?.data as Record | undefined; - if (data && typeof data === "object") Object.keys(data).forEach((k) => gtKeys.add(k)); - } - } - } - for (const [name, buf] of materialsEntries) { - if (!name.endsWith(".json")) continue; - try { - const mat = parseJson(buf) as Record; - if (!mat.sub_study_id) errors.push(`Materials file ${name} must have sub_study_id.`); - if (!mat.instructions) errors.push(`Materials file ${name} must have instructions.`); - const items = mat.items as Array> | undefined; - if (!Array.isArray(items)) { - errors.push(`Materials file ${name} must have items array.`); - } else { - for (const item of items) { - const metaItem = item.metadata as Record | undefined; - const gk = metaItem?.gt_key as string | undefined; - if (gk && !gtKeys.has(gk)) errors.push(`gt_key "${gk}" in ${name} not found in ground_truth data.`); - } - } - } catch { - errors.push(`Invalid JSON in materials file ${name}.`); - } - } - - const configName = `src/studies/${studyId}_config.py`; - const evaluatorName = `src/studies/${studyId}_evaluator.py`; - const hasConfig = fileMap.has(configName) || [...fileMap.keys()].some((k) => k.endsWith(`${studyId}_config.py`)); - const hasEvaluator = fileMap.has(evaluatorName) || [...fileMap.keys()].some((k) => k.endsWith(`${studyId}_evaluator.py`)); - if (!hasConfig) errors.push(`Missing ${studyId}_config.py in src/studies/ or zip root.`); - if (!hasEvaluator) errors.push(`Missing ${studyId}_evaluator.py in src/studies/ or zip root.`); - - if (errors.length > 0) return { ok: false, errors }; - return { ok: true, studyId, files: fileMap }; -} - -export async function POST(request: NextRequest) { - try { - const formData = await request.formData(); - const file = formData.get("file") as File | null; - const contributorGithub = normalizeGithub(String(formData.get("contributor_github") || "")); - const contributorName = String(formData.get("contributor_name") || "").trim(); - const contributorInstitution = String(formData.get("contributor_institution") || "Independent Researcher").trim(); - if (!file || !(file instanceof File)) { - return NextResponse.json({ success: false, errors: ["No file provided."] }, { status: 400 }); - } - if (!contributorGithub) { - return NextResponse.json( - { success: false, errors: ["GitHub ID or profile URL is required."] }, - { status: 400 } - ); - } - if (!file.name.endsWith(".zip")) { - return NextResponse.json({ success: false, errors: ["File must be a .zip archive."] }, { status: 400 }); - } - const buf = Buffer.from(await file.arrayBuffer()); - if (buf.length > MAX_ZIP_BYTES) { - return NextResponse.json({ success: false, errors: ["Zip file exceeds 50MB limit."] }, { status: 400 }); - } - - const AdmZip = (await import("adm-zip")).default; - const zip = new AdmZip(buf); - const entries = zip.getEntries(); - const extracted: { name: string; data: Buffer }[] = []; - for (const e of entries) { - if (e.isDirectory) continue; - extracted.push({ name: e.entryName.replace(/\/$/, ""), data: e.getData() }); - } - const result = validateZip(extracted); - if (!result.ok) { - return NextResponse.json({ success: false, errors: result.errors }, { status: 400 }); - } - const { studyId, files: validatedFiles } = result; - - const token = process.env.GITHUB_TOKEN; - const repo = process.env.GITHUB_REPO || "HumanStudy-Hub/HumanStudy-Bench"; - if (!token) { - return NextResponse.json( - { success: false, errors: ["Server is not configured with GITHUB_TOKEN for creating PRs."] }, - { status: 503 } - ); - } - - const [owner, repoName] = repo.split("/"); - const { Octokit } = await import("@octokit/rest"); - const octokit = new Octokit({ auth: token }); - - const { data: defaultBranch } = await octokit.repos.get({ owner, repo: repoName }); - const baseRef = defaultBranch.default_branch || "main"; - const branchName = `contribute/${studyId}-${Date.now()}`; - - const { data: baseRefData } = await octokit.git.getRef({ owner, repo: repoName, ref: `heads/${baseRef}` }); - const baseCommitSha = baseRefData.object.sha; - const { data: commitData } = await octokit.git.getCommit({ owner, repo: repoName, commit_sha: baseCommitSha }); - const baseTreeSha = commitData.tree.sha; - - function zipEntryToRepoPath(entryName: string): string | null { - const sid = studyId; - if (entryName.startsWith(`studies/${sid}/`)) return entryName; - if (entryName.startsWith(`${sid}/`)) return `studies/${entryName}`; - if (entryName === `src/studies/${sid}_config.py` || entryName.endsWith(`/${sid}_config.py`)) return `src/studies/${sid}_config.py`; - if (entryName === `src/studies/${sid}_evaluator.py` || entryName.endsWith(`/${sid}_evaluator.py`)) return `src/studies/${sid}_evaluator.py`; - if (entryName.includes("specification.json")) return `studies/${sid}/specification.json`; - if (entryName.includes("metadata.json")) return `studies/${sid}/metadata.json`; - if (entryName.includes("ground_truth.json")) return `studies/${sid}/ground_truth.json`; - if (entryName.includes("materials/")) return `studies/${sid}/materials/${entryName.split("materials/")[1]}`; - return null; - } - - const pathToContent = new Map(); - for (const [name, data] of validatedFiles.entries()) { - if (!data.length) continue; - const repoPath = zipEntryToRepoPath(name); - if (repoPath && (repoPath.startsWith("studies/") || repoPath.startsWith("src/"))) { - pathToContent.set(repoPath, data); - } - } - - const metaBuf = pathToContent.get(`studies/${studyId}/metadata.json`); - const meta = metaBuf ? (parseJson(metaBuf) as Record) : {}; - const title = (meta.title as string) || studyId; - - const generatedIndex = { - title: title, - authors: Array.isArray(meta.authors) ? meta.authors : [], - year: typeof meta.year === "number" ? meta.year : null, - description: typeof meta.summary === "string" ? meta.summary : typeof meta.abstract === "string" ? meta.abstract : "", - contributors: [{ name: contributorName || undefined, github: contributorGithub, institution: contributorInstitution }], - }; - pathToContent.set( - `studies/${studyId}/index.json`, - Buffer.from(JSON.stringify(generatedIndex, null, 2), "utf8") - ); - - const treeItems: { path: string; mode: "100644"; type: "blob"; sha: string }[] = []; - for (const [path, data] of pathToContent.entries()) { - const { data: blob } = await octokit.git.createBlob({ - owner, - repo: repoName, - content: data.toString("base64"), - encoding: "base64", - }); - treeItems.push({ path, mode: "100644", type: "blob", sha: blob.sha }); - } - - const { data: newTree } = await octokit.git.createTree({ - owner, - repo: repoName, - base_tree: baseTreeSha, - tree: treeItems, - }); - - const { data: newCommit } = await octokit.git.createCommit({ - owner, - repo: repoName, - message: `chore: add ${studyId} (contribute upload)`, - tree: newTree.sha, - parents: [baseCommitSha], - }); - - await octokit.git.createRef({ - owner, - repo: repoName, - ref: `refs/heads/${branchName}`, - sha: newCommit.sha, - }); - - const prBody = `## Contributed study (website upload)\n\n- **Study ID:** ${studyId}\n- **Title:** ${title}\n- **GitHub:** ${contributorGithub}\n- **Domain:** ${meta.domain || "—"}\n\nPlease run \`python scripts/validate_study.py --study ${studyId}\` and review.`; - - const pr = await octokit.pulls.create({ - owner, - repo: repoName, - title: `Add ${studyId}: ${title}`, - head: branchName, - base: baseRef, - body: prBody, - }); - - return NextResponse.json({ success: true, pr_url: pr.data.html_url }); - } catch (err) { - console.error("Contribute upload error:", err); - const message = err instanceof Error ? err.message : "Unknown error"; - return NextResponse.json( - { success: false, errors: [message] }, - { status: 500 } - ); - } -} diff --git a/co_website/app/benchmark/page.tsx b/co_website/app/benchmark/page.tsx deleted file mode 100644 index 2068084..0000000 --- a/co_website/app/benchmark/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import fs from "fs"; -import path from "path"; -import ReactMarkdown from "react-markdown"; - -export default function BenchmarkPage() { - const filePath = path.join(process.cwd(), "content/benchmark.md"); - const content = fs.readFileSync(filePath, "utf-8"); - - return ( -
- {content} -
- ); -} diff --git a/co_website/app/citation/page.tsx b/co_website/app/citation/page.tsx deleted file mode 100644 index 7195b6a..0000000 --- a/co_website/app/citation/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export default function CitationPage() { - return ( -
-

Citation

-

Citation content will appear here.

-
- ); -} diff --git a/co_website/app/contribute/page.tsx b/co_website/app/contribute/page.tsx deleted file mode 100644 index e6bc088..0000000 --- a/co_website/app/contribute/page.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import { promises as fs } from "fs"; -import path from "path"; -import Link from "next/link"; -import ContributeUploadForm from "@/components/ContributeUploadForm"; - -type Contributor = { name?: string; github?: string }; -type StudySummary = { - study_id: string; - title: string; - authors: string[]; - year: number | null; - contributors?: Contributor[]; -}; - -export default async function ContributePage() { - const filePath = path.join(process.cwd(), "data/studies_index.json"); - let studies: StudySummary[] = []; - try { - const raw = await fs.readFile(filePath, "utf8"); - const data = JSON.parse(raw); - studies = data.studies ?? []; - } catch (e) { - console.error("Could not read studies index", e); - } - - const foundational = studies.slice(0, 12); - const community = studies.slice(12); - - return ( -
- {/* Hero */} -
-
-

- Contribute -

-

- Add a new study to HumanStudy-Bench. -

-
-

HumanStudy-Bench is a non-profit and open-source platform.

-

We welcome collaborators from all backgrounds!

-

All accepted contributions will be acknowledged and credited.

-
- {/* Section nav */} -
- - Studies - - - How to Contribute - - - - - - - - - Step-by-step Tutorial → - - -
-
-
- - {/* Studies Catalog */} -
-
-

- Study Catalog -

- -
- {foundational.map((study, idx) => ( -
-
- - {(idx + 1).toString().padStart(3, "0")} - - - {study.title} - -
- {study.contributors?.[0]?.github && ( - - @{study.contributors[0].github.replace(/^https?:\/\/github\.com\//, "")} - - )} -
- ))} -
- - {community.length > 0 && ( - <> -
-
- - Community Contributions - -
-
- -
- {community.map((study, idx) => ( -
-
- - {(idx + 13).toString().padStart(3, "0")} - - - {study.title} - -
- {study.contributors?.[0]?.github && ( - - @{study.contributors[0].github.replace(/^https?:\/\/github\.com\//, "")} - - )} -
- ))} -
- - )} -
-
- - {/* How to Contribute */} -
-
-

- How to Contribute -

-

- There are two ways to submit a study. We recommend contributing via GitHub. -

- -
- {/* GitHub PR path */} -
-
- - Recommended - -

GitHub Pull Request

-
-
    -
  1. - 1 - Fork and clone the repository -
  2. -
  3. - 2 - Create a branch (e.g. contrib-yourgithubid-013) -
  4. -
  5. - 3 - Add your study under studies/ with index.json, source/, scripts/, and README.md -
  6. -
  7. - 4 - Run bash scripts/verify_study.sh study_XXX and open a PR -
  8. -
-

- Maintainers assign final study numbering by merge order. Include your GitHub ID in index.json contributors. -

-
- - {/* Web upload path */} -
-

Web Upload

-

- Upload a .zip of your study and we create a GitHub PR for you. -

- -
-
- - {/* Docs link */} -
- - - - - Not sure where to start? Read the detailed walkthrough - - - - -
-
-
- - {/* Footer note */} -
-

- Questions? Open an issue on{" "} - - GitHub - - . For other inquiries, contact{" "} - - xul049@ucsd.edu - - . -

-
-
- ); -} diff --git a/co_website/app/dataset/page.tsx b/co_website/app/dataset/page.tsx deleted file mode 100644 index a408697..0000000 --- a/co_website/app/dataset/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from "next/navigation"; - -export default function DatasetPage() { - redirect("/#dataset"); -} diff --git a/co_website/app/docs/[slug]/page.tsx b/co_website/app/docs/[slug]/page.tsx deleted file mode 100644 index efe3089..0000000 --- a/co_website/app/docs/[slug]/page.tsx +++ /dev/null @@ -1,266 +0,0 @@ -import { promises as fs } from "fs"; -import path from "path"; -import { notFound } from "next/navigation"; -import Link from "next/link"; -import Markdown from "react-markdown"; -import remarkGfm from "remark-gfm"; -import rehypeHighlight from "rehype-highlight"; -import rehypeRaw from "rehype-raw"; - -const DOCS_DIR = path.join(process.cwd(), "..", "docs"); - -const docsMeta: Record< - string, - { title: string; description: string; order: number } -> = { - what_to_submit: { - title: "What Should I Submit?", - description: "The big picture — what goes in a study folder", - order: 1, - }, - extract_from_paper: { - title: "How to Extract Data from a Paper", - description: "Turn a PDF into structured, replicable data", - order: 2, - }, - build_study_files: { - title: "How to Build Your Study Files", - description: "Schemas, code, and contracts for every file", - order: 3, - }, - submit_study: { - title: "How to Submit Your Study", - description: "Fork, verify, push, and ship it", - order: 4, - }, -}; - -const CALLOUT_ICONS = { - tip: '', - info: '', - warning: '', - note: '', -}; - -function stripAdmonitions(md: string): string { - return md.replace( - /^!!! (\w+)(?: "([^"]*)")?\n((?: .*\n?)*)/gm, - (_match, type, title, body) => { - const content = body.replace(/^ /gm, "").trim(); - const normalizedType = type === "note" ? "info" : type; - const label = title || type.charAt(0).toUpperCase() + type.slice(1); - const icon = - CALLOUT_ICONS[normalizedType as keyof typeof CALLOUT_ICONS] || - CALLOUT_ICONS.info; - - return `\n
\n\n
\n${label}\n\n${content}\n\n
\n
\n\n`; - } - ); -} - -function fixInternalLinks(md: string): string { - return md.replace( - /\[([^\]]+)\]\((?!https?:\/\/)([a-z_]+)\.md(?:#([a-z_-]+))?\)/g, - (_match, text, slug, anchor) => { - const href = anchor ? `/docs/${slug}#${anchor}` : `/docs/${slug}`; - return `[${text}](${href})`; - } - ); -} - -export async function generateStaticParams() { - return Object.keys(docsMeta).map((slug) => ({ slug })); -} - -export default async function DocPage({ - params, -}: { - params: Promise<{ slug: string }>; -}) { - const { slug } = await params; - const meta = docsMeta[slug]; - if (!meta) notFound(); - - let content = ""; - try { - const raw = await fs.readFile(path.join(DOCS_DIR, `${slug}.md`), "utf8"); - const withoutH1 = raw.replace(/^# .+\n/, ""); - content = fixInternalLinks(stripAdmonitions(withoutH1)); - } catch { - notFound(); - } - - const allDocs = Object.entries(docsMeta).sort((a, b) => a[1].order - b[1].order); - const currentIdx = allDocs.findIndex(([s]) => s === slug); - const prev = currentIdx > 0 ? allDocs[currentIdx - 1] : null; - const next = - currentIdx < allDocs.length - 1 ? allDocs[currentIdx + 1] : null; - - return ( -
-
-
- {/* Sidebar — GitHub Docs style */} - - - {/* Main content */} -
-
- {/* Breadcrumb + title + description (inside content column) */} -
-

- - Contribute - - {" / "} - Docs -

-

- {meta.title} -

-

- {meta.description} -

-
- - {/* Mobile nav — horizontal scroll */} -
-
- {allDocs.map(([s, m]) => ( - - {m.title} - - ))} -
-
- - {/* Markdown content — GitHub Docs typography */} -
- - {content} - -
- - {/* Prev/Next — flat GitHub Docs style */} -
- {prev ? ( - - - - -
-

Previous

-

- {prev[1].title} -

-
- - ) : ( -
- )} - {next ? ( - -
-

Next

-

- {next[1].title} -

-
- - - - - ) : ( -
- )} -
-
-
-
-
-
- ); -} diff --git a/co_website/app/docs/page.tsx b/co_website/app/docs/page.tsx deleted file mode 100644 index 82fa24b..0000000 --- a/co_website/app/docs/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from "next/navigation"; - -export default function DocsPage() { - redirect("/docs/what_to_submit"); -} diff --git a/co_website/app/globals.css b/co_website/app/globals.css deleted file mode 100644 index a31e8af..0000000 --- a/co_website/app/globals.css +++ /dev/null @@ -1,134 +0,0 @@ -@import "tailwindcss"; -@plugin "@tailwindcss/typography"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme { - --font-serif: var(--font-serif), ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - --font-sans: var(--font-sans), ui-sans-serif, system-ui, sans-serif; -} - -@media (prefers-color-scheme: dark) { - :root { - /* Keeping it light/academic as per request even in dark mode preference */ - --background: #ffffff; - --foreground: #171717; - } -} - -body { - background: var(--background); - color: var(--foreground); -} - -/* GitHub-style callouts (Note / Tip / Warning) */ -.callout { - border-left: 3px solid; - border-radius: 6px; - padding: 16px; - margin: 24px 0; - display: flex; - gap: 12px; - align-items: flex-start; -} -.callout-icon { - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - margin-top: 2px; -} -.callout-svg { - width: 1.25rem; - height: 1.25rem; -} -.callout-body { - flex: 1; - min-width: 0; -} -.callout-body strong { - display: block; - font-family: var(--font-sans); - font-size: 0.95rem; - margin-bottom: 0.5rem; -} -.callout-body > p:first-of-type { - margin-top: 0; -} -.callout-body > p:last-of-type { - margin-bottom: 0; -} -.callout-tip { - border-color: #22c55e; - background: #f0fdf4; -} -.callout-tip .callout-svg { - color: #16a34a; -} -.callout-info { - border-color: #0ea5e9; - background: #f0f9ff; -} -.callout-info .callout-svg { - color: #0284c7; -} -.callout-warning { - border-color: #eab308; - background: #fefce8; -} -.callout-warning .callout-svg { - color: #ca8a04; -} - -/* Collapsible details/summary sections */ -.prose details { - border: 1px solid #d0d7de; - border-radius: 6px; - padding: 0; - margin: 16px 0; -} -.prose details > summary { - padding: 12px 16px; - cursor: pointer; - list-style: none; - user-select: none; - background: #f6f8fa; - border-radius: 6px; - font-size: 0.925rem; - color: #1F2328; - transition: background-color 0.15s; -} -.prose details[open] > summary { - border-bottom: 1px solid #d0d7de; - border-radius: 6px 6px 0 0; -} -.prose details > summary:hover { - background: #eef1f5; -} -.prose details > summary::-webkit-details-marker { - display: none; -} -.prose details > summary::before { - content: "▶"; - display: inline-block; - margin-right: 8px; - font-size: 0.7em; - transition: transform 0.15s; - color: #656d76; -} -.prose details[open] > summary::before { - transform: rotate(90deg); -} -.prose details > :not(summary) { - padding-left: 16px; - padding-right: 16px; -} -.prose details > :last-child { - padding-bottom: 16px; -} -.prose details > :nth-child(2) { - padding-top: 16px; -} diff --git a/co_website/app/icon.svg b/co_website/app/icon.svg deleted file mode 100644 index d83ce00..0000000 --- a/co_website/app/icon.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - H - diff --git a/co_website/app/layout.tsx b/co_website/app/layout.tsx deleted file mode 100644 index 2a30410..0000000 --- a/co_website/app/layout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { Metadata } from "next"; -import { Merriweather, Inter } from "next/font/google"; -import { SpeedInsights } from "@vercel/speed-insights/next"; -import { Analytics } from "@vercel/analytics/react"; -import "./globals.css"; -import "highlight.js/styles/github.min.css"; -import Navbar from "@/components/Navbar"; -import Footer from "@/components/Footer"; - -const merriweather = Merriweather({ - subsets: ["latin"], - weight: ["300", "400", "700", "900"], - variable: "--font-serif", -}); - -const inter = Inter({ - subsets: ["latin"], - variable: "--font-sans", -}); - -export const metadata: Metadata = { - title: "HumanStudy-Bench: Towards AI Agent Design for Participant Simulation", - description: "A reusable platform for replaying human-subject experiments end-to-end, evaluating agent design alignment with human ground-truth outcomes at the level of scientific inference.", - icons: { - icon: [{ url: "/favicon.svg?v=2", type: "image/svg+xml" }], - shortcut: "/favicon.svg?v=2", - apple: "/favicon.svg?v=2", - }, -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - -
{children}
-