diff --git a/README.md b/README.md index ceb114f..fc4cf66 100644 --- a/README.md +++ b/README.md @@ -70,14 +70,14 @@ We regularly run competitions with clear due dates but for beginners we will alw ### Setup -Bootstrap a project with Popcorn skill scaffolding and a submission template. You can overwrite existing files with `--force`. +Bootstrap a project with Popcorn skill scaffolding and a submission template. ```bash -# Create project skill scaffolding + submission.py +# Create a project folder with skill scaffolding + submission.py popcorn setup ``` -This will create a new agent skill based on the [templates](templates/setup) and add it to your `.claude/skills` or `.codex/skills` directory. +This will create a new folder named after the selected problem (e.g. `softmax/`), containing a `submission.py` template and agent skills in `.claude/skills` and `.codex/skills`. If a folder with that name already exists, a `-N` suffix is appended (e.g. `softmax-1/`). ### Submit diff --git a/docs/helion-hackathon.md b/docs/helion-hackathon.md index 5986ad0..40c9f7b 100644 --- a/docs/helion-hackathon.md +++ b/docs/helion-hackathon.md @@ -35,7 +35,7 @@ popcorn setup # Select "Helion Kernel Challenge", then pick a problem and GPU ``` -`popcorn setup` fetches the latest problems from reference-kernels, downloads the submission template with `#!POPCORN` directives pre-filled, and scaffolds agent skills for Codex/Claude Code. +`popcorn setup` fetches the latest problems from reference-kernels, creates a project folder named after the selected problem (e.g. `causal_conv1d_py/`), downloads the submission template with `#!POPCORN` directives pre-filled, and scaffolds agent skills for Codex/Claude Code. If a folder with that name already exists, a `-N` suffix is appended (e.g. `causal_conv1d_py-1/`). Alternatively, you can clone the full reference-kernels repo to browse all problems locally: diff --git a/src/cmd/setup.rs b/src/cmd/setup.rs index eb6c892..06e490b 100644 --- a/src/cmd/setup.rs +++ b/src/cmd/setup.rs @@ -262,15 +262,28 @@ pub async fn run_setup() -> Result<()> { ) .await?; - // Write scaffolding files - let popcorn_dir = cwd.join(".popcorn"); + // Create a unique project folder for this kernel + let project_dir = unique_folder_name(&cwd, &chosen_problem.directory); + fs::create_dir_all(&project_dir).with_context(|| { + format!( + "Failed to create project directory at {}", + project_dir.to_string_lossy() + ) + })?; + println!( + "\nCreated project folder: {}", + relative_display(&cwd, &project_dir) + ); + + // Write scaffolding files into the project folder + let popcorn_dir = project_dir.join(".popcorn"); let skill_dir = popcorn_dir.join("skills").join(SKILL_NAME); let skill_path = skill_dir.join("SKILL.md"); let native_skill_dir = popcorn_dir.join("skills").join(NATIVE_SKILL_NAME); let native_skill_path = native_skill_dir.join("SKILL.md"); let manifest_path = popcorn_dir.join("setup.json"); - let submission_path = cwd.join(SUBMISSION_FILENAME); - let agents_path = cwd.join("AGENTS.md"); + let submission_path = project_dir.join(SUBMISSION_FILENAME); + let agents_path = project_dir.join("AGENTS.md"); fs::create_dir_all(&skill_dir).with_context(|| { format!( @@ -313,11 +326,12 @@ pub async fn run_setup() -> Result<()> { let agents_md = build_agents_markdown(&skill_path, &native_skill_path); let agents_status = write_text_file(&agents_path, &agents_md, true)?; - let codex_link_status = create_agent_skill_view(&cwd, "codex", &skill_dir, true)?; - let claude_link_status = create_agent_skill_view(&cwd, "claude", &skill_dir, true)?; - let codex_native_link_status = create_agent_skill_view(&cwd, "codex", &native_skill_dir, true)?; + let codex_link_status = create_agent_skill_view(&project_dir, "codex", &skill_dir, true)?; + let claude_link_status = create_agent_skill_view(&project_dir, "claude", &skill_dir, true)?; + let codex_native_link_status = + create_agent_skill_view(&project_dir, "codex", &native_skill_dir, true)?; let claude_native_link_status = - create_agent_skill_view(&cwd, "claude", &native_skill_dir, true)?; + create_agent_skill_view(&project_dir, "claude", &native_skill_dir, true)?; let submission_status = write_text_file(&submission_path, &submission_content, true)?; @@ -344,27 +358,39 @@ pub async fn run_setup() -> Result<()> { println!( "{} {}", codex_link_status.label(), - relative_display(&cwd, &cwd.join(".codex").join("skills").join(SKILL_NAME)) + relative_display( + &cwd, + &project_dir.join(".codex").join("skills").join(SKILL_NAME) + ) ); println!( "{} {}", codex_native_link_status.label(), relative_display( &cwd, - &cwd.join(".codex").join("skills").join(NATIVE_SKILL_NAME) + &project_dir + .join(".codex") + .join("skills") + .join(NATIVE_SKILL_NAME) ) ); println!( "{} {}", claude_link_status.label(), - relative_display(&cwd, &cwd.join(".claude").join("skills").join(SKILL_NAME)) + relative_display( + &cwd, + &project_dir.join(".claude").join("skills").join(SKILL_NAME) + ) ); println!( "{} {}", claude_native_link_status.label(), relative_display( &cwd, - &cwd.join(".claude").join("skills").join(NATIVE_SKILL_NAME) + &project_dir + .join(".claude") + .join("skills") + .join(NATIVE_SKILL_NAME) ) ); println!( @@ -376,6 +402,21 @@ pub async fn run_setup() -> Result<()> { Ok(()) } +fn unique_folder_name(parent: &Path, base_name: &str) -> PathBuf { + let candidate = parent.join(base_name); + if !candidate.exists() { + return candidate; + } + let mut n = 1; + loop { + let candidate = parent.join(format!("{}-{}", base_name, n)); + if !candidate.exists() { + return candidate; + } + n += 1; + } +} + fn relative_display(cwd: &Path, target: &Path) -> String { match target.strip_prefix(cwd) { Ok(relative) => relative.to_string_lossy().to_string(),