Files
whetstone_DSL/tools/mcp/generate_example_projects_and_specs.sh

349 lines
9.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
PROJECT_ROOT="${1:-$ROOT_DIR/example_projects}"
SPEC_ROOT="${2:-$ROOT_DIR/datasets/example_run_specs}"
VARIANTS="${VARIANTS:-12}"
mkdir -p "$PROJECT_ROOT" "$SPEC_ROOT"
cat > "$PROJECT_ROOT/README.md" <<'DOC'
# Example Projects Corpus
Curated multi-stack fixtures for MCP training data generation.
- Designed for tool-use diversity, not production deployment.
- Each project has a small, realistic file tree and a validator command.
- Matching run specs live in `datasets/example_run_specs`.
DOC
create_project() {
local name="$1"
local stack="$2"
local validator="$3"
local files="$4"
local failures="$5"
local dir="$PROJECT_ROOT/$name"
mkdir -p "$dir"
cat > "$dir/PROJECT_META.md" <<META
# ${name}
- stack: ${stack}
- validator: ${validator}
- common_failure_modes: ${failures}
## Scope
Small representative project for training data generation.
META
case "$name" in
py_cli_todo)
mkdir -p "$dir/src" "$dir/tests"
cat > "$dir/src/todo.py" <<'PY'
def add_task(tasks, title):
title = title.strip()
if not title:
raise ValueError("title is required")
tasks.append({"title": title, "done": False})
return tasks
def complete_task(tasks, index):
tasks[index]["done"] = True
return tasks
PY
cat > "$dir/tests/test_todo.py" <<'PY'
from src.todo import add_task, complete_task
def test_add_and_complete():
tasks = []
add_task(tasks, "pay bills")
complete_task(tasks, 0)
assert tasks[0]["done"] is True
PY
;;
node_api_gateway)
mkdir -p "$dir/src" "$dir/test"
cat > "$dir/src/server.js" <<'JS'
const http = require("http");
function health() {
return { ok: true, service: "gateway" };
}
const server = http.createServer((_req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(health()));
});
if (require.main === module) {
server.listen(8080);
}
module.exports = { health };
JS
cat > "$dir/test/server.test.js" <<'JS'
const assert = require("assert");
const { health } = require("../src/server");
assert.equal(health().ok, true);
JS
;;
go_worker_pool)
mkdir -p "$dir/cmd/worker" "$dir/internal/queue"
cat > "$dir/internal/queue/queue.go" <<'GO'
package queue
type Job struct {
ID string
}
func Enqueue(q []Job, j Job) []Job {
return append(q, j)
}
GO
cat > "$dir/cmd/worker/main.go" <<'GO'
package main
import (
"fmt"
"example/internal/queue"
)
func main() {
jobs := []queue.Job{}
jobs = queue.Enqueue(jobs, queue.Job{ID: "job-1"})
fmt.Println(len(jobs))
}
GO
;;
bash_log_shipper)
mkdir -p "$dir/bin" "$dir/etc"
cat > "$dir/bin/ship_logs.sh" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
SRC="${1:-/var/log/app.log}"
DST="${2:-/tmp/app.log.gz}"
gzip -c "$SRC" > "$DST"
echo "$DST"
SH
chmod +x "$dir/bin/ship_logs.sh"
cat > "$dir/etc/shipper.conf" <<'CONF'
retention_days=7
max_size_mb=50
CONF
;;
cpp_cache_indexer)
mkdir -p "$dir/src" "$dir/include"
cat > "$dir/include/indexer.h" <<'H'
#pragma once
#include <string>
#include <vector>
std::vector<std::string> tokenize(const std::string& line);
H
cat > "$dir/src/indexer.cpp" <<'CPP'
#include "indexer.h"
#include <sstream>
std::vector<std::string> tokenize(const std::string& line) {
std::istringstream in(line);
std::vector<std::string> out;
std::string tok;
while (in >> tok) out.push_back(tok);
return out;
}
CPP
;;
rust_metrics_agent)
mkdir -p "$dir/src"
cat > "$dir/src/main.rs" <<'RS'
fn parse_metric(line: &str) -> Option<(&str, i64)> {
let parts: Vec<&str> = line.split('=').collect();
if parts.len() != 2 {
return None;
}
let value = parts[1].parse::<i64>().ok()?;
Some((parts[0], value))
}
fn main() {
let _ = parse_metric("requests=10");
}
RS
;;
java_order_service)
mkdir -p "$dir/src/main/java/app" "$dir/src/test/java/app"
cat > "$dir/src/main/java/app/Order.java" <<'JAVA'
package app;
public record Order(String id, int quantity) {}
JAVA
cat > "$dir/src/main/java/app/OrderValidator.java" <<'JAVA'
package app;
public class OrderValidator {
public static boolean valid(Order order) {
return order != null && order.quantity() > 0;
}
}
JAVA
;;
terraform_vpc_stack)
mkdir -p "$dir"
cat > "$dir/main.tf" <<'TF'
terraform {
required_version = ">= 1.5.0"
}
resource "null_resource" "example" {}
TF
;;
react_admin_panel)
mkdir -p "$dir/src/components"
cat > "$dir/src/App.jsx" <<'JSX'
import React from "react";
export default function App() {
return <main><h1>Admin Panel</h1></main>;
}
JSX
cat > "$dir/src/components/UserTable.jsx" <<'JSX'
import React from "react";
export function UserTable() {
return <table><tbody></tbody></table>;
}
JSX
;;
sql_etl_pipeline)
mkdir -p "$dir/sql"
cat > "$dir/sql/001_create_tables.sql" <<'SQL'
CREATE TABLE IF NOT EXISTS events (
id TEXT PRIMARY KEY,
ts TEXT NOT NULL,
kind TEXT NOT NULL
);
SQL
cat > "$dir/sql/010_transform.sql" <<'SQL'
INSERT INTO events (id, ts, kind)
SELECT id, ts, kind FROM staging_events;
SQL
;;
esac
cat > "$dir/TASKS.md" <<TASKS
# Task Seeds: ${name}
${files}
## Validator
\`${validator}\`
TASKS
}
create_project "py_cli_todo" "python" "pytest -q" "src/todo.py, tests/test_todo.py" "arg validation, off-by-one index, missing tests"
create_project "node_api_gateway" "node" "node test/server.test.js" "src/server.js, test/server.test.js" "route mismatch, status/body drift, invalid json"
create_project "go_worker_pool" "go" "go test ./..." "cmd/worker/main.go, internal/queue/queue.go" "dependency order, wrong package path, nil handling"
create_project "bash_log_shipper" "bash" "bash -n bin/ship_logs.sh" "bin/ship_logs.sh, etc/shipper.conf" "path misuse, quoting, retention config mismatch"
create_project "cpp_cache_indexer" "cpp" "cmake --build . && ctest" "include/indexer.h, src/indexer.cpp" "header/impl mismatch, parser edge cases"
create_project "rust_metrics_agent" "rust" "cargo test" "src/main.rs" "parse failures, error propagation, type mismatch"
create_project "java_order_service" "java" "mvn -q test" "src/main/java/app/*.java" "null checks, quantity rules, test gaps"
create_project "terraform_vpc_stack" "terraform" "terraform validate" "main.tf" "resource drift, provider/schema mismatch"
create_project "react_admin_panel" "react" "npm test -- --watch=false" "src/App.jsx, src/components/UserTable.jsx" "component state misuse, prop mismatch"
create_project "sql_etl_pipeline" "sql" "sqllogictest tests/*.slt" "sql/001_create_tables.sql, sql/010_transform.sql" "schema drift, non-idempotent transforms"
cat > "$SPEC_ROOT/README.md" <<'DOC'
# Example Project Run Specs
Cross-project run specs for broad training distribution.
- `index.jsonl` stores metadata for each spec.
- `runspec_*.md` are directly consumable by `run_spec_batch_with_capture.sh`.
DOC
INDEX_FILE="$SPEC_ROOT/index.jsonl"
: > "$INDEX_FILE"
families=(
wrong_tool_selection
missing_required_args
dependency_order_violation
premature_finalize
overfetch_context
)
projects=(
py_cli_todo
node_api_gateway
go_worker_pool
bash_log_shipper
cpp_cache_indexer
rust_metrics_agent
java_order_service
terraform_vpc_stack
react_admin_panel
sql_etl_pipeline
)
for project in "${projects[@]}"; do
for family in "${families[@]}"; do
for i in $(seq 1 "$VARIANTS"); do
id="${project}_${family}_$(printf '%02d' "$i")"
file="$SPEC_ROOT/runspec_${id}.md"
cat > "$file" <<SPEC
# Run Spec: ${id}
## Context
project = ${project}
family = ${family}
variant = ${i}
project_root = example_projects/${project}
## Objective
Solve a bounded engineering task in ${project} while producing deterministic, stage-labeled traces.
## Target Behavior
- Prefer minimal-context file reads in the project root.
- Use phase-correct flow: intake -> generate -> queue -> validate.
- Stop once acceptance checks are met.
## Anti-Pattern To Detect
- ${family}
- Non-terminal tool loops.
- Schema-invalid tool args.
## Acceptance Criteria
- Uses project-local files under \`example_projects/${project}\`.
- Includes at least one concrete validator/test command in task items.
- No malformed payloads in MCP stages.
- Explicit finalization condition and bounded retries.
## Data Capture Tags
- corpus: example_projects
- project: ${project}
- family: ${family}
- variant: ${i}
SPEC
jq -nc \
--arg id "$id" \
--arg file "$(basename "$file")" \
--arg project "$project" \
--arg family "$family" \
--argjson variant "$i" \
'{id:$id,file:$file,project:$project,family:$family,variant:$variant}' >> "$INDEX_FILE"
done
done
done
echo "Example projects generated: $(find "$PROJECT_ROOT" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')"
echo "Example run specs generated: $(find "$SPEC_ROOT" -maxdepth 1 -type f -name 'runspec_*.md' | wc -l | tr -d ' ')"
echo "Spec index: $INDEX_FILE"