Skip to content
Open
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
164 changes: 164 additions & 0 deletions .agents/skills/open-collider-brainstorm/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
name: open-collider-brainstorm
description: "Codex equivalent of Open Collider's Claude Code /brainstorm command. Use when the user asks to run, continue, resume, curate, flag, inspect, or close an Open Collider brainstorm; when migrating Claude Code brainstorm workflows to Codex; or when a configured project uses llm_provider: codex/openai/anthropic and needs the complete domains, ideas, scoring, curation, and love/like/trash loop. For project creation or /collider_setup, use $open-collider-setup instead."
---

# Open Collider Brainstorm

## Command Equivalent

This is the Codex equivalent of Claude Code's `/brainstorm` command.

If the user asks to create or configure a project, use `$open-collider-setup`
first. Do not run brainstorm until setup is complete and the user asks to
continue.

## Core rule

Do not stop after `BrainstormOrchestrator.run_iteration()`. A complete Open Collider brainstorm includes:

1. domain generation,
2. idea generation,
3. scoring and thresholding,
4. Codex curation into `curated_ideas.json` and `insights_without_collision.json`,
5. user flags `love / like / trash`,
6. `apply_flags()` and regenerated reports.

If the user only asks for a raw API smoke test, say explicitly that curation is skipped.

## Repository context

Run commands from the Open Collider repository root. Prefer `.venv/bin/python` when it exists. Always insert the repo `src/` path before importing local modules.

Helpful scripts bundled with this skill:

- `scripts/run_iteration.py`: run one API-mode iteration and print JSON.
- `scripts/apply_flags_from_text.py`: parse `love 1,3 — like 2 — trash the rest`, map display numbers to idea IDs, call `apply_flags()`, and regenerate reports.

Use reference details only when needed:

- `references/curation.md`: exact curation criteria and file shapes.
- `references/terminal-usage.md`: terminal commands for installing and exercising the skill.

## Workflow

If the user asks to create a new Open Collider project, hand off to
`$open-collider-setup` first.

### 1. Orient

List `projects/` excluding `_template`. If there is one project, use it. If there are several and the user did not specify one, ask which project to run.

Inspect `project_config.yaml`. For Codex-only runs, the project should contain:

```yaml
llm_backend: api
llm_provider: codex
domain_model: default
generation_model: default
scoring_model: default
max_concurrent: 1
max_concurrent_scoring: 1
```

If the config is not Codex-ready and the user asked for Codex-only, edit the project config before running.

### 2. Run one iteration

Use the bundled script or equivalent Python:

```bash
python /path/to/open-collider-brainstorm/scripts/run_iteration.py projects/my_project
```

If resuming a specific brainstorm:

```bash
python /path/to/open-collider-brainstorm/scripts/run_iteration.py projects/my_project --brainstorm-id brainstorm_001
```

If network or home-directory access is required for `codex exec`, request escalation. Do not work around a rejected escalation.

### 2b. Curate an existing raw iteration

If the user asks to curate an existing run, or if `iter_NNN/scored_ideas.json` exists but `curated_ideas.json` is missing, do not rerun generation. Start at step 3 using that existing iteration.

Useful prompt:

```text
Use $open-collider-brainstorm to curate existing brainstorm_001 iteration 1 for projects/my_project. Do not rerun generation.
```

### 3. Curate immediately

Read all ideas from `iter_NNN/scored_ideas.json`, both retained and non-retained. Read `brief_validated.json` from the project root.

Select the best 10-20 ideas with the criteria in `references/curation.md`. Be selective: a smaller strong set is better than a long mediocre set.

Write:

- `iter_NNN/curated_ideas.json`
- `iter_NNN/insights_without_collision.json`

Then call:

```python
from open_collider.skill_interface import mark_curated, generate_report

mark_curated(project_dir)
generate_report(project_dir)
```

### 4. Display and ask for flags

Display every curated idea and every insight without rewriting the `text` field. Use continuous numbering across both lists, then save the same mapping to:

```text
iter_NNN/numbering_map.json
```

Mapping shape:

```json
[
{"number": 1, "idea_id": "...", "kind": "curated"},
{"number": 2, "idea_id": "...", "kind": "insight"}
]
```

Ask the user:

```text
Flag each idea: love (want more like this), like (interesting), or trash (not useful).
Format: love 1,3,7 - like 2,5 - trash the rest
```

Stop and wait for the user's flags.

### 5. Apply flags

When the user provides flags, parse them with:

```bash
python /path/to/open-collider-brainstorm/scripts/apply_flags_from_text.py projects/my_project 1 "love 1,3 - like 2 - trash the rest"
```

This calls `apply_flags(project_dir, iteration, flags)` and regenerates reports.

### 6. Continue or close

Ask whether the user wants:

- next iteration,
- brief revision,
- done.

If done, call `generate_brainstorm_report(project_dir)` and point the user to `REPORT.md`.

## Codex-specific behavior

For Codex-only projects, treat `default` as the safest model value. It lets the local Codex CLI choose the model supported by the user's account.

Do not use Anthropic or OpenAI API keys unless the user explicitly asks. If the config says `llm_provider: codex`, all unprefixed model names must be Codex-compatible.

`projects/*` may be gitignored in this repository. Mention that outputs can exist without appearing in `git status`.
4 changes: 4 additions & 0 deletions .agents/skills/open-collider-brainstorm/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Open Collider Brainstorm"
short_description: "Run and curate Open Collider brainstorms"
default_prompt: "Use $open-collider-brainstorm to run a full Open Collider brainstorm for a configured project, including post-run curation."
65 changes: 65 additions & 0 deletions .agents/skills/open-collider-brainstorm/references/curation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Open Collider curation

Use this after `run_iteration()` creates `scored_ideas.json`.

## Inputs

- `projects/<name>/brief_validated.json`
- `projects/<name>/brainstorms/<brainstorm_id>/iter_NNN/scored_ideas.json`

Read all scored ideas, including non-retained ideas. Do not trust score alone.

## Pass 1: collision ideas

For each idea, keep it only if all filters pass:

1. Real collision: the distant domain mechanism changes the idea structurally.
2. Verifiable: factual claims are named or checkable; if the idea makes important external claims, verify them.
3. Non-trivial: the idea would not appear from a vanilla prompt.
4. Project voice: the idea fits the brief, audience, taste, and constraints.

Deduplicate aggressively. If two ideas express the same mechanism, keep the stronger one.

Write `curated_ideas.json`:

```json
[
{
"rank": 1,
"idea_id": "...",
"text": "full original idea text",
"combo": "...",
"score": 4.65,
"has_collision": true,
"why_selected": "One sentence.",
"source_note": "What is verifiable, or why no external claim needs verification.",
"challenge": "Strongest objection."
}
]
```

## Pass 2: insights without collision

Keep ideas that fail only the real-collision test but are still strong, non-trivial, and on-brief.

Write `insights_without_collision.json`:

```json
[
{
"rank": 1,
"idea_id": "...",
"text": "full original idea text",
"combo": "...",
"score": 4.35,
"has_collision": false,
"why_kept": "One sentence."
}
]
```

## Display requirements

After writing curation files, display all curated items with exact `text`. Do not summarize candidate names or rewrite territory text.

Use continuous display numbers across both files and save `numbering_map.json` so flags can be applied deterministically later.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Terminal usage

Install the skill for Codex discovery:

```bash
mkdir -p ~/.codex/skills
cp -R .agents/skills/open-collider-setup ~/.codex/skills/
cp -R .agents/skills/open-collider-brainstorm ~/.codex/skills/
```

Run the Codex equivalent of `/collider_setup` from the Open Collider repo:

```bash
codex "Use $open-collider-setup to create a new Open Collider project configured for Codex-only API mode."
```

Run the Codex equivalent of `/brainstorm`:

```bash
codex "Use $open-collider-brainstorm to run a complete brainstorm for projects/namerkit_pulsed_naming with Codex-only API mode. Do the post-run curation and ask me for love/like/trash flags."
```

Smoke-test the Python provider without a project brief:

```bash
.venv/bin/python - <<'PY'
from open_collider.llm.client import LLMClient
print(LLMClient(provider="codex").call("default", "Reply exactly: codex provider ok"))
PY
```

Run a raw iteration manually:

```bash
python ~/.codex/skills/open-collider-brainstorm/scripts/run_iteration.py projects/namerkit_pulsed_naming
```

Apply flags after Codex has displayed curated ideas and written `numbering_map.json`:

```bash
python ~/.codex/skills/open-collider-brainstorm/scripts/apply_flags_from_text.py projects/namerkit_pulsed_naming 1 "love 1,3 - like 2,5 - trash the rest"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path


FLAG_ALIASES = {
"love": "loved",
"loved": "loved",
"like": "liked",
"liked": "liked",
"trash": "trashed",
"trashed": "trashed",
}


def parse_number_list(text: str) -> set[int]:
numbers: set[int] = set()
for part in re.split(r"[, ]+", text.strip()):
if not part:
continue
if "-" in part and re.fullmatch(r"\d+\s*-\s*\d+", part):
start, end = [int(x) for x in re.split(r"\s*-\s*", part)]
numbers.update(range(min(start, end), max(start, end) + 1))
elif part.isdigit():
numbers.add(int(part))
return numbers


def parse_flags(spec: str, all_numbers: set[int]) -> dict[int, str]:
normalized = spec.replace("—", "-").replace(";", " - ")
matches = list(re.finditer(r"\b(love|loved|like|liked|trash|trashed)\b", normalized, re.I))
number_to_flag: dict[int, str] = {}

for index, match in enumerate(matches):
label = FLAG_ALIASES[match.group(1).lower()]
start = match.end()
end = matches[index + 1].start() if index + 1 < len(matches) else len(normalized)
chunk = normalized[start:end].strip(" :-")
if re.search(r"\b(the\s+)?rest\b", chunk, re.I):
selected = all_numbers - set(number_to_flag)
else:
selected = parse_number_list(chunk)
for number in selected:
number_to_flag[number] = label

unknown = set(number_to_flag) - all_numbers
if unknown:
raise SystemExit(f"Unknown display number(s): {sorted(unknown)}")
return number_to_flag


def load_mapping(iter_dir: Path) -> list[dict]:
mapping_path = iter_dir / "numbering_map.json"
if mapping_path.is_file():
return json.loads(mapping_path.read_text(encoding="utf-8"))

curated = json.loads((iter_dir / "curated_ideas.json").read_text(encoding="utf-8"))
insights_path = iter_dir / "insights_without_collision.json"
insights = json.loads(insights_path.read_text(encoding="utf-8")) if insights_path.is_file() else []
mapping: list[dict] = []
for item in sorted(curated, key=lambda i: i.get("rank", 999999)):
mapping.append({"number": len(mapping) + 1, "idea_id": item["idea_id"], "kind": "curated"})
for item in sorted(insights, key=lambda i: i.get("rank", 999999)):
mapping.append({"number": len(mapping) + 1, "idea_id": item["idea_id"], "kind": "insight"})
mapping_path.write_text(json.dumps(mapping, ensure_ascii=False, indent=2), encoding="utf-8")
return mapping


def main() -> int:
parser = argparse.ArgumentParser(description="Apply love/like/trash flags from display numbers.")
parser.add_argument("project", help="Project directory, for example projects/my_project")
parser.add_argument("iteration", type=int, help="Iteration number, for example 1")
parser.add_argument("flags", help='Flag spec, for example "love 1,3 - like 2 - trash the rest"')
args = parser.parse_args()

repo_root = Path.cwd()
sys.path.insert(0, str(repo_root / "src"))

from open_collider.skill_interface import _load_state, apply_flags

project_dir = Path(args.project)
state = _load_state(project_dir)
brainstorm_dir = project_dir / "brainstorms" / state["brainstorm_id"]
iter_dir = brainstorm_dir / f"iter_{args.iteration:03d}"

mapping = load_mapping(iter_dir)
number_to_id = {int(item["number"]): item["idea_id"] for item in mapping}
number_to_flag = parse_flags(args.flags, set(number_to_id))

flags = {number_to_id[number]: flag for number, flag in number_to_flag.items()}
apply_flags(str(project_dir), args.iteration, flags)
print(json.dumps({"applied": len(flags), "flags": flags}, ensure_ascii=False, indent=2))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading