Skip to content
Merged
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/helion-hackathon.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
65 changes: 53 additions & 12 deletions src/cmd/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -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)?;

Expand All @@ -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!(
Expand All @@ -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(),
Expand Down
Loading