From e0811b607a89e0f7be96ca295de5f0be27fc1a7e Mon Sep 17 00:00:00 2001 From: MaxRonce Date: Mon, 18 May 2026 13:16:25 +0200 Subject: [PATCH 1/4] Add init agent option --- src/lightcone/cli/commands.py | 28 ++++++++++++++++++++------- src/lightcone/cli/plugin.py | 36 +++++++++++++++++++++++------------ tests/test_cli.py | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/src/lightcone/cli/commands.py b/src/lightcone/cli/commands.py index 869faf81..e299f7b1 100644 --- a/src/lightcone/cli/commands.py +++ b/src/lightcone/cli/commands.py @@ -30,7 +30,7 @@ import yaml from rich.console import Console -from lightcone.cli.plugin import get_plugin_source_dir +from lightcone.cli.plugin import get_agent_bundle_source_dir console = Console() logger = logging.getLogger(__name__) @@ -142,6 +142,12 @@ def _project_root(start: Path | None = None) -> Path: @click.argument("directory", type=click.Path(path_type=Path), default=".") @click.option("--no-git", is_flag=True, help="Skip git init") @click.option("--no-venv", is_flag=True, help="Skip Python venv creation") +@click.option( + "--agent", + type=click.Choice(["claude", "codex", "none", "both"]), + default="claude", + help="Agent bundle to install", +) @click.option( "--permissions", type=click.Choice(["yolo", "recommended", "minimal"]), @@ -163,6 +169,7 @@ def init( directory: Path, no_git: bool, no_venv: bool, + agent: str, permissions: str, scratch_override: str | None, ) -> None: @@ -185,6 +192,9 @@ def init( if (directory / "astra.yaml").exists(): raise click.ClickException(f"{directory}/astra.yaml already exists.") + if agent == "codex" and get_agent_bundle_source_dir("codex") is None: + raise click.ClickException("Codex agent bundle is not available yet.") + # Spec scaffold: astra.yaml, universes/baseline.yaml, base .gitignore, # src/. We hold off on git init until our own files are in place so # the initial commit captures the full project state. @@ -223,13 +233,17 @@ def init( # results/ directory placeholder (directory / "results").mkdir(exist_ok=True) - # Claude Code plugin bundle - plugin_source = get_plugin_source_dir() - if plugin_source is not None and plugin_source.exists(): - _install_claude_plugin(directory, plugin_source, permissions) + # Agent bundles + if agent in {"claude", "both"}: + plugin_source = get_agent_bundle_source_dir("claude") + if plugin_source is not None and plugin_source.exists(): + _install_claude_plugin(directory, plugin_source, permissions) + + # Project CLAUDE.md (a stub) + (directory / "CLAUDE.md").write_text(_PROJECT_CLAUDE_MD) - # Project CLAUDE.md (a stub) - (directory / "CLAUDE.md").write_text(_PROJECT_CLAUDE_MD) + if agent == "both" and get_agent_bundle_source_dir("codex") is None: + console.print("[yellow]Codex agent bundle is not available yet; skipping Codex.[/yellow]") # git init last so the initial commit captures every scaffolded file. no_git = no_git or (directory / ".git").exists() diff --git a/src/lightcone/cli/plugin.py b/src/lightcone/cli/plugin.py index 903dd263..12fd0a48 100644 --- a/src/lightcone/cli/plugin.py +++ b/src/lightcone/cli/plugin.py @@ -1,4 +1,4 @@ -"""Plugin bundle discovery — finds the Claude Code skills/hooks shipped with lightcone-cli. +"""Agent bundle discovery for skills/hooks shipped with lightcone-cli. Kept deliberately leaf (no imports from :mod:`lightcone.cli.commands` or :mod:`lightcone.eval`) so it can be used by both the CLI and the eval harness without introducing an import cycle. @@ -9,26 +9,38 @@ from pathlib import Path -def get_plugin_source_dir() -> Path | None: - """Find the lightcone Claude plugin source directory. +def get_agent_bundle_source_dir(agent: str) -> Path | None: + """Find the lightcone bundle source directory for *agent*. - Looks for the plugin files in: + Looks for bundle files in: - 1. Bundled location (installed package): ``lightcone/cli/claude/lightcone/`` - 2. Development location (repo): ``claude/lightcone/`` relative to repo root + 1. Bundled location (installed package): ``lightcone/cli//lightcone/`` + 2. Development location (repo): ``/lightcone/`` relative to repo root """ + if "/" in agent or "\\" in agent or agent in {"", ".", ".."}: + raise ValueError(f"Invalid agent bundle name: {agent!r}") + import lightcone.cli package_dir = Path(lightcone.cli.__file__).parent - bundled_plugin = package_dir / "claude" / "lightcone" - if bundled_plugin.exists(): - return bundled_plugin + bundled_bundle = package_dir / agent / "lightcone" + if bundled_bundle.exists(): + return bundled_bundle # Try development location (running from repo) # package_dir == /src/lightcone/cli → parents[2] == repo_root = package_dir.parents[2] - dev_plugin = repo_root / "claude" / "lightcone" - if dev_plugin.exists(): - return dev_plugin + dev_bundle = repo_root / agent / "lightcone" + if dev_bundle.exists(): + return dev_bundle return None + + +def get_plugin_source_dir() -> Path | None: + """Find the lightcone Claude plugin source directory. + + Backward-compatible wrapper for callers that still use the historical + Claude-specific name. + """ + return get_agent_bundle_source_dir("claude") diff --git a/tests/test_cli.py b/tests/test_cli.py index 5f5f0053..4eb96d93 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -10,6 +10,7 @@ from click.testing import CliRunner from lightcone.cli.commands import main +from lightcone.cli.plugin import get_agent_bundle_source_dir, get_plugin_source_dir @pytest.fixture @@ -69,12 +70,33 @@ def test_init_creates_project(runner: CliRunner, tmp_path: Path) -> None: assert result.exit_code == 0, result.output assert (project / "astra.yaml").exists() assert (project / "CLAUDE.md").exists() + assert (project / ".claude").is_dir() assert (project / ".gitignore").exists() assert (project / ".lightcone").is_dir() assert (project / "results").is_dir() assert (project / "universes").is_dir() +def test_init_agent_none_skips_agent_files( + runner: CliRunner, tmp_path: Path +) -> None: + project = tmp_path / "proj" + result = runner.invoke( + main, ["init", str(project), "--agent", "none", "--no-git", "--no-venv"] + ) + assert result.exit_code == 0, result.output + assert (project / "astra.yaml").exists() + assert not (project / "CLAUDE.md").exists() + assert not (project / ".claude").exists() + assert (project / ".lightcone").is_dir() + + +def test_init_help_mentions_agent_option(runner: CliRunner) -> None: + result = runner.invoke(main, ["init", "--help"]) + assert result.exit_code == 0 + assert "--agent" in result.output + + def test_init_refuses_when_astra_yaml_exists( runner: CliRunner, tmp_path: Path ) -> None: @@ -126,6 +148,18 @@ def _fake_run(cmd: list[str], **kwargs: object) -> MagicMock: assert [".venv/bin/python", "-m", "pip", "install", "-q", "lightcone-cli"] in calls +def test_agent_bundle_discovery_keeps_claude_plugin_compatibility() -> None: + claude_bundle = get_agent_bundle_source_dir("claude") + + assert claude_bundle is not None + assert claude_bundle == get_plugin_source_dir() + assert (claude_bundle / "hooks.json").is_file() + + +def test_agent_bundle_discovery_returns_none_for_missing_bundle() -> None: + assert get_agent_bundle_source_dir("codex") is None + + # ---- lc verify ------------------------------------------------------------ From c7914a98ed63b6eda423117fb8b39c903895d109 Mon Sep 17 00:00:00 2001 From: MaxRonce Date: Mon, 18 May 2026 13:18:30 +0200 Subject: [PATCH 2/4] Install Codex bundle from lc init --- codex/lightcone/skills/astra/SKILL.md | 25 +++++++++++++++++++ codex/lightcone/skills/lc-cli/SKILL.md | 26 ++++++++++++++++++++ codex/lightcone/skills/lc-from-code/SKILL.md | 22 +++++++++++++++++ codex/lightcone/skills/lc-new/SKILL.md | 25 +++++++++++++++++++ codex/lightcone/templates/AGENTS.md | 21 ++++++++++++++++ pyproject.toml | 2 ++ tests/test_cli.py | 9 ++++++- 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 codex/lightcone/skills/astra/SKILL.md create mode 100644 codex/lightcone/skills/lc-cli/SKILL.md create mode 100644 codex/lightcone/skills/lc-from-code/SKILL.md create mode 100644 codex/lightcone/skills/lc-new/SKILL.md create mode 100644 codex/lightcone/templates/AGENTS.md diff --git a/codex/lightcone/skills/astra/SKILL.md b/codex/lightcone/skills/astra/SKILL.md new file mode 100644 index 00000000..beddb396 --- /dev/null +++ b/codex/lightcone/skills/astra/SKILL.md @@ -0,0 +1,25 @@ +--- +name: astra +description: Reference for ASTRA project structure and astra.yaml authoring. +--- + +# ASTRA Reference + +ASTRA projects describe an analysis in `astra.yaml`. Treat that file as +the durable specification for inputs, outputs, decisions, and recipes. + +Use this skill when editing or reviewing `astra.yaml`: + +- Keep output ids stable once results exist. +- Make each output represent one concrete artifact, metric, table, or plot. +- Keep recipes executable through `lc run`; do not rely on hidden manual + steps. +- Update the spec when scripts, parameters, inputs, or decisions change. + +Useful checks: + +```bash +astra validate astra.yaml +lc status +lc verify +``` diff --git a/codex/lightcone/skills/lc-cli/SKILL.md b/codex/lightcone/skills/lc-cli/SKILL.md new file mode 100644 index 00000000..962b46a6 --- /dev/null +++ b/codex/lightcone/skills/lc-cli/SKILL.md @@ -0,0 +1,26 @@ +--- +name: lc-cli +description: Reference for lightcone-cli execution commands and project checks. +--- + +# lightcone-cli Reference + +Use `lc` as the execution surface for ASTRA projects. It keeps recipes, +containers, universes, manifests, and provenance checks tied together. + +Common commands: + +```bash +lc run [OUTPUTS...] # materialize all or selected outputs +lc status # show current, missing, stale, and invalid outputs +lc verify # recompute hashes and validate provenance +lc build # build project containers +``` + +Workflow rules: + +- Run recipes through `lc run`, not by calling scripts manually. +- Use `lc status` after edits to see what needs rematerialization. +- Use `lc verify` before handing off completed work. +- If a result is stale, update the recipe/spec or rerun it; do not patch the + result in place. diff --git a/codex/lightcone/skills/lc-from-code/SKILL.md b/codex/lightcone/skills/lc-from-code/SKILL.md new file mode 100644 index 00000000..7827185e --- /dev/null +++ b/codex/lightcone/skills/lc-from-code/SKILL.md @@ -0,0 +1,22 @@ +--- +name: lc-from-code +description: Guide for wrapping an existing codebase or script as an ASTRA project. +--- + +# Existing Code to ASTRA + +Use this skill when a project already has scripts, notebooks, or data +processing code and needs an ASTRA specification around it. + +Process: + +1. Inventory the existing code and identify real analytical outputs. +2. Map each output to a recipe command that can run from the project root. +3. Record required inputs, parameters, and methodological decisions in + `astra.yaml`. +4. Prefer small reproducible scripts over notebook-only execution. +5. Run through `lc run` and inspect with `lc status` and `lc verify`. + +Do not change generated results directly. If existing outputs need to be +recreated, express the command in `astra.yaml` and let `lc run` materialize +them. diff --git a/codex/lightcone/skills/lc-new/SKILL.md b/codex/lightcone/skills/lc-new/SKILL.md new file mode 100644 index 00000000..2843cf61 --- /dev/null +++ b/codex/lightcone/skills/lc-new/SKILL.md @@ -0,0 +1,25 @@ +--- +name: lc-new +description: Guide for scoping a new ASTRA analysis from a research question. +--- + +# New ASTRA Analysis + +Use this skill when starting from a research question or loose analysis idea. +The goal is to turn the idea into a precise `astra.yaml` before implementing +code. + +Process: + +1. Identify the research question and the decision the analysis informs. +2. Define the inputs that are allowed to influence the result. +3. Define outputs as concrete artifacts, metrics, tables, or plots. +4. Record important methodological choices as decisions. +5. Keep implementation work separate until the spec is coherent. + +After drafting the spec, validate it and inspect project state: + +```bash +astra validate astra.yaml +lc status +``` diff --git a/codex/lightcone/templates/AGENTS.md b/codex/lightcone/templates/AGENTS.md new file mode 100644 index 00000000..ecb9f935 --- /dev/null +++ b/codex/lightcone/templates/AGENTS.md @@ -0,0 +1,21 @@ +# AGENTS.md + +This is an ASTRA analysis project orchestrated by `lightcone-cli`. + +The source of truth is `astra.yaml`. Keep the spec, recipes, scripts, and +recorded outputs in sync. Do not edit or replace files under `results/` +without updating the relevant `astra.yaml` recipe or decisions and +rematerializing the output through `lc run`. + +Use the `lc` commands for execution and checks: + +```bash +lc run # materialize outputs in the default universe +lc run output_id # materialize one output +lc status # inspect missing, stale, and current outputs +lc verify # validate manifests and provenance integrity +``` + +When changing analysis code, update `astra.yaml` in the same change if the +inputs, outputs, decisions, parameters, or command recipes changed. Run +`lc status` and `lc verify` before considering the project state complete. diff --git a/pyproject.toml b/pyproject.toml index a48f4e25..e7d9241b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,12 +77,14 @@ packages = [ [tool.hatch.build.targets.wheel.force-include] "claude/lightcone" = "lightcone/cli/claude/lightcone" +"codex/lightcone" = "lightcone/cli/codex/lightcone" [tool.hatch.build.targets.sdist] include = [ "src/lightcone", "src/snakemake_executor_plugin_dask", "claude/lightcone", + "codex/lightcone", ] [tool.ruff] diff --git a/tests/test_cli.py b/tests/test_cli.py index 4eb96d93..7e773b4a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -156,8 +156,15 @@ def test_agent_bundle_discovery_keeps_claude_plugin_compatibility() -> None: assert (claude_bundle / "hooks.json").is_file() +def test_agent_bundle_discovery_finds_codex_bundle() -> None: + codex_bundle = get_agent_bundle_source_dir("codex") + + assert codex_bundle is not None + assert (codex_bundle / "templates" / "AGENTS.md").is_file() + + def test_agent_bundle_discovery_returns_none_for_missing_bundle() -> None: - assert get_agent_bundle_source_dir("codex") is None + assert get_agent_bundle_source_dir("missing-agent") is None # ---- lc verify ------------------------------------------------------------ From 671ffee5ef6f61f3dfafe3dc6e95a72aac638de4 Mon Sep 17 00:00:00 2001 From: MaxRonce Date: Mon, 18 May 2026 13:21:22 +0200 Subject: [PATCH 3/4] Improve Codex skills guidance --- codex/lightcone/skills/astra/SKILL.md | 85 +++++++++++++++-- codex/lightcone/skills/lc-cli/SKILL.md | 96 +++++++++++++++++--- codex/lightcone/skills/lc-from-code/SKILL.md | 89 +++++++++++++++--- codex/lightcone/skills/lc-new/SKILL.md | 72 +++++++++++++-- 4 files changed, 299 insertions(+), 43 deletions(-) diff --git a/codex/lightcone/skills/astra/SKILL.md b/codex/lightcone/skills/astra/SKILL.md index beddb396..92ecabab 100644 --- a/codex/lightcone/skills/astra/SKILL.md +++ b/codex/lightcone/skills/astra/SKILL.md @@ -5,21 +5,88 @@ description: Reference for ASTRA project structure and astra.yaml authoring. # ASTRA Reference -ASTRA projects describe an analysis in `astra.yaml`. Treat that file as -the durable specification for inputs, outputs, decisions, and recipes. +ASTRA projects describe an analysis in `astra.yaml`. Treat that file as the +durable specification for inputs, outputs, decisions, findings, sub-analyses, +containers, and recipes. The code implements the spec; it should not hide +extra analytical choices that are absent from `astra.yaml`. -Use this skill when editing or reviewing `astra.yaml`: +## What Belongs In The Spec -- Keep output ids stable once results exist. -- Make each output represent one concrete artifact, metric, table, or plot. -- Keep recipes executable through `lc run`; do not rely on hidden manual - steps. -- Update the spec when scripts, parameters, inputs, or decisions change. +- Inputs: data, files, external analysis outputs, or other material + dependencies. +- Outputs: concrete artifacts, metrics, tables, plots, reports, or datasets. +- Decisions: methodological choices where multiple defensible options could + affect the result. +- Recipes: commands that materialize outputs through `lc run`. +- Containers: the environment needed to run recipes reproducibly. -Useful checks: +Keep output ids stable once results exist. If an output changes meaning, prefer +adding a new output or clearly updating the spec and rerunning the affected +recipes. + +## Decisions + +A decision is an analytical choice, not a general implementation detail. +Include choices such as thresholds, statistical methods, filtering criteria, +model families, binning, smoothing, priors, convergence criteria, and data +selection rules. Skip choices that should not change the scientific answer, +such as ordinary refactors, plotting style, file formats, or library choices +that produce equivalent results. + +Every decision used by code should be parameterized. The recipe should pass it +with `{decisions.}`, and the script should accept the corresponding command +line argument or config value. Do not leave decision values hardcoded in code +while claiming they are represented in `astra.yaml`. + +## Outputs And Recipes + +Each output should represent one concrete result. Avoid bundling unrelated +metrics or plots into one output just because one script can create them. + +Recipes live under outputs and describe how to materialize the output: + +```yaml +outputs: + - id: accuracy + type: metric + inputs: [training_data] + decisions: [model_family, threshold] + recipe: + command: >- + python src/evaluate.py + --data {inputs.training_data} + --model {decisions.model_family} + --threshold {decisions.threshold} + --output {output} +``` + +The output should be written under `{output}`. Do not write final artifacts to +untracked ad hoc paths and then copy them into `results/`. + +## Spec-Code Invariant + +`astra.yaml` and code must move together: + +- New script argument or analytical parameter: add or update the matching + decision and recipe. +- New result: add an output and recipe. +- Changed input data path: update the input declaration. +- Removed or renamed output: update the spec, universes, code, and any + downstream references. +- Changed default behavior: update the baseline universe or default option. + +## Validation And Checks + +If available, run: ```bash astra validate astra.yaml lc status +lc run lc verify ``` + +If `astra validate` is unavailable in the environment, say that explicitly and +continue with structural review plus `lc status` / `lc verify` where possible. +Do not ignore validation failures. Fix the spec or explain the remaining +blocker. diff --git a/codex/lightcone/skills/lc-cli/SKILL.md b/codex/lightcone/skills/lc-cli/SKILL.md index 962b46a6..4608c9d8 100644 --- a/codex/lightcone/skills/lc-cli/SKILL.md +++ b/codex/lightcone/skills/lc-cli/SKILL.md @@ -5,22 +5,92 @@ description: Reference for lightcone-cli execution commands and project checks. # lightcone-cli Reference -Use `lc` as the execution surface for ASTRA projects. It keeps recipes, -containers, universes, manifests, and provenance checks tied together. +Use `lc` as the execution surface for ASTRA projects. It ties together +recipes, containers, universes, manifests, and provenance checks. Scripts +can be run directly while debugging, but final analysis outputs must be +materialized through `lc run` so the result has a traceable manifest. -Common commands: +## Commands ```bash -lc run [OUTPUTS...] # materialize all or selected outputs -lc status # show current, missing, stale, and invalid outputs -lc verify # recompute hashes and validate provenance -lc build # build project containers +lc init [DIR] # scaffold a project +lc run [OUTPUTS...] # materialize all or selected outputs +lc run output_id --universe NAME # materialize one output in one universe +lc status # show ok, missing, stale, alias, invalid +lc status --json # machine-readable status +lc verify # recompute hashes and validate provenance +lc build # build project containers +lc export wrroc # export a Workflow Run RO-Crate ``` -Workflow rules: +`lc run` is quiet by default. If a run fails, inspect the actual error and fix +the cause; do not hide failures by writing placeholder outputs, weakening +recipes, or bypassing `lc`. -- Run recipes through `lc run`, not by calling scripts manually. -- Use `lc status` after edits to see what needs rematerialization. -- Use `lc verify` before handing off completed work. -- If a result is stale, update the recipe/spec or rerun it; do not patch the - result in place. +## Core Invariants + +- `astra.yaml` is the source of truth for the analysis structure. +- The spec-code invariant must hold: when code, inputs, parameters, outputs, + or recipe commands change, update `astra.yaml` in the same change. +- Results under `results///` are not hand-authored + deliverables. They are materialized outputs with `.lightcone-manifest.json` + provenance. +- Do not patch results in place to make status look clean. Update the spec or + code, rerun `lc run`, then verify. +- Do not mask execution failures. A failed run is information that should lead + to a concrete fix. + +## Development Flow + +1. Edit code and `astra.yaml` together. +2. If the ASTRA CLI is available, run: + + ```bash + astra validate astra.yaml + ``` + +3. Check what changed: + + ```bash + lc status + ``` + +4. Materialize the smallest useful target: + + ```bash + lc run output_id --universe baseline + ``` + +5. After relevant outputs are materialized, run: + + ```bash + lc verify + ``` + +For multi-output projects, prefer running one upstream output at a time while +debugging. It is usually easier to inspect a direct failure than to run the +whole DAG and debug from a downstream error. + +## Status Meanings + +- `ok`: output has a manifest that matches the current spec and inputs. +- `stale`: spec, code, decisions, or upstream data changed after the last run. +- `missing`: recipe exists, but no current manifest is present. +- `alias`: output is produced by another recipe or references another output. +- invalid or verification failures: inspect with `lc verify`, then rerun the + affected recipe after fixing the cause. + +## Failure Handling + +Common causes: + +- Recipe command references `{decisions.foo}` or `{inputs.bar}` but the output + does not declare that decision or input. +- Script argument names do not match the recipe command. +- A script writes outside `{output}`, leaving the output directory empty or + incomplete. +- Existing files were copied into `results/` without a manifest. +- A result file was edited after materialization, causing a hash mismatch. + +Fix the recipe, script, or spec. Then run `astra validate astra.yaml` if +available, `lc run ...`, `lc status`, and `lc verify`. diff --git a/codex/lightcone/skills/lc-from-code/SKILL.md b/codex/lightcone/skills/lc-from-code/SKILL.md index 7827185e..f128e9ea 100644 --- a/codex/lightcone/skills/lc-from-code/SKILL.md +++ b/codex/lightcone/skills/lc-from-code/SKILL.md @@ -6,17 +6,84 @@ description: Guide for wrapping an existing codebase or script as an ASTRA proje # Existing Code to ASTRA Use this skill when a project already has scripts, notebooks, or data -processing code and needs an ASTRA specification around it. +processing code and needs an ASTRA specification around it. The goal is to +preserve the existing behavior while making inputs, outputs, decisions, and +recipes explicit in `astra.yaml`. -Process: +## Workflow -1. Inventory the existing code and identify real analytical outputs. -2. Map each output to a recipe command that can run from the project root. -3. Record required inputs, parameters, and methodological decisions in - `astra.yaml`. -4. Prefer small reproducible scripts over notebook-only execution. -5. Run through `lc run` and inspect with `lc status` and `lc verify`. +1. Inventory the existing code before editing it. +2. Identify real analytical outputs and the files or commands that create + them. +3. Identify inputs, dependencies, containers, and hardcoded analytical choices. +4. Draft or augment `astra.yaml`. +5. Parameterize code only as much as needed to match the spec. +6. Run through `lc run`, then inspect with `lc status` and `lc verify`. -Do not change generated results directly. If existing outputs need to be -recreated, express the command in `astra.yaml` and let `lc run` materialize -them. +## Scan First + +For every script or notebook, determine: + +- what it reads; +- what it writes; +- how it is invoked; +- which choices are hardcoded; +- which outputs are analytical results versus temporary files; +- which dependencies and environment assumptions it has. + +Do not guess from filenames. Read the relevant code. If behavior is unclear, +say what is unclear and inspect the call sites, config files, or notebooks. + +## Draft The Spec + +Use `astra.yaml` as the source of truth. For each output, declare: + +- `id` and `type`; +- upstream inputs; +- methodological decisions that parameterize it; +- a `recipe.command` that can run from the project root; +- a container at analysis or recipe level when needed. + +Use current hardcoded behavior as the baseline default unless the user asks to +change it. If a baseline universe exists, keep it consistent with those +defaults. + +## Parameterize Carefully + +Make minimal code changes. Do not refactor, rename, or reorganize existing +logic unless it is necessary to make the recipe executable. + +Common patterns: + +- Add `argparse` flags for hardcoded decision values. +- Pass `{decisions.}` and `{inputs.}` from the recipe command. +- Pass `{output}` as an output directory and write artifacts inside it. +- Convert notebooks into small scripts only when notebook-only execution blocks + reproducibility. +- Add missing dependencies to `requirements.txt` or the project environment + file if imports require them. + +Keep the spec-code invariant intact. If a recipe changes, update `astra.yaml`. +If a script gains a new parameter, represent it in the spec when it affects the +analysis. + +## Run And Verify + +After spec or code changes, run if available: + +```bash +astra validate astra.yaml +lc status +lc run +lc status +lc verify +``` + +Do not copy old outputs into `results/` to make the project appear complete. +Do not edit generated result files directly. Do not suppress failing commands +with shell tricks that hide nonzero exits. If `lc run` fails, read the error, +fix the recipe/code/spec, and rerun. + +When migration is complete, the baseline run should reproduce the original +behavior as closely as possible, and all final outputs should be traceable +through Lightcone manifests. diff --git a/codex/lightcone/skills/lc-new/SKILL.md b/codex/lightcone/skills/lc-new/SKILL.md index 2843cf61..942210c6 100644 --- a/codex/lightcone/skills/lc-new/SKILL.md +++ b/codex/lightcone/skills/lc-new/SKILL.md @@ -5,21 +5,73 @@ description: Guide for scoping a new ASTRA analysis from a research question. # New ASTRA Analysis -Use this skill when starting from a research question or loose analysis idea. -The goal is to turn the idea into a precise `astra.yaml` before implementing -code. +Use this skill when the user asks to scope a new analysis from a research +question, loose idea, or desired scientific output. The goal is to turn the +conversation into a precise `astra.yaml` before implementation work starts. -Process: +Do not treat scoping as a coding task. First establish the analysis structure: +what question is being answered, what data can be used, what outputs count as +answers, and which methodological choices should be explicit decisions. -1. Identify the research question and the decision the analysis informs. -2. Define the inputs that are allowed to influence the result. -3. Define outputs as concrete artifacts, metrics, tables, or plots. -4. Record important methodological choices as decisions. -5. Keep implementation work separate until the spec is coherent. +## Workflow -After drafting the spec, validate it and inspect project state: +1. Clarify the research question in the user's terms. +2. Ask what a satisfactory answer would look like. +3. Identify allowed inputs and any data that must not influence the result. +4. Define outputs as concrete artifacts, metrics, tables, plots, or reports. +5. Identify methodological decisions that could affect the result. +6. Draft or update `astra.yaml`. +7. Generate or update a baseline universe when defaults are clear. +8. Validate the spec and hand off implementation only after the structure is + coherent. + +## Analysis Structure + +Prefer one output per concrete result. Do not make one broad output like +`performance_metrics` if the project actually produces accuracy, calibration, +and a ROC plot. Each independently interpreted artifact should be its own +output. + +Split into sub-analyses only when stages have genuinely different inputs, +outputs, or scientific roles. If a training step and evaluation step together +produce one model assessment, they may belong in one analysis. If the stages +are independently meaningful products, sub-analyses may be appropriate. + +## Decisions + +Probe for decisions beyond obvious method choices: + +- data inclusion and exclusion criteria; +- thresholds and quality cuts; +- statistical estimators and uncertainty methods; +- model families or algorithm choices; +- priors, smoothing, binning, and convergence criteria; +- operational definitions of measured quantities. + +Skip implementation details that should not affect scientific interpretation. +When unsure, include the candidate decision in `astra.yaml` for review rather +than burying it in code. + +## Spec-Code Invariant + +During scoping, `astra.yaml` is the source of truth. Once implementation +starts, the code must follow it. If the user asks for code before the spec is +coherent, state the gap and finish the spec first. + +After drafting or changing the spec, run if available: ```bash astra validate astra.yaml lc status ``` + +If outputs or recipes were implemented as part of the work, continue with: + +```bash +lc run +lc verify +``` + +Do not produce final results by manual edits. Do not hide failed validation or +execution. Surface unresolved questions clearly and keep them in the spec or +project notes where appropriate. From d784a6b0ba0a3a88ddb7d0572ba5414103699288 Mon Sep 17 00:00:00 2001 From: MaxRonce Date: Mon, 18 May 2026 13:34:17 +0200 Subject: [PATCH 4/4] Wire Codex bundle into init --- README.md | 12 ++++++ docs/skills/index.md | 54 ++++++++++++++++++++++++--- docs/user/codex.md | 69 +++++++++++++++++++++++++++++++++++ docs/user/getting-started.md | 15 ++++++++ docs/user/index.md | 11 +++++- src/lightcone/cli/commands.py | 62 +++++++++++++++++++++++++------ tests/test_cli.py | 36 ++++++++++++++++++ zensical.toml | 1 + 8 files changed, 242 insertions(+), 18 deletions(-) create mode 100644 docs/user/codex.md diff --git a/README.md b/README.md index 3a923a31..82ea548d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,18 @@ Then tell the agent what you have to start from — a research question (`/lc-new`), existing code (`/lc-from-code`), or a paper to reproduce (`/lc-from-paper`). +Experimental Codex support is also available: + +```bash +lc init --agent codex my-analysis +cd my-analysis +codex +``` + +Codex projects use `AGENTS.md` and `.agents/skills/` instead of `.claude/` +and `CLAUDE.md`. The Codex skills are a smaller experimental bundle, not a +complete port of every Claude workflow. + → [Full getting-started guide](https://docs.lightconeresearch.org/user/getting-started/) ## Skills diff --git a/docs/skills/index.md b/docs/skills/index.md index e1c352b7..8f528b73 100644 --- a/docs/skills/index.md +++ b/docs/skills/index.md @@ -1,9 +1,10 @@ # Skills -Skills are Claude Code slash commands bundled in the lightcone-cli -plugin. Each shapes the agent's workflow around a recurring research -operation: scoping an analysis, wrapping existing code, reproducing -a paper. +Lightcone ships agent guidance as bundled skills. The mature bundle today is +the Claude Code plugin, where skills are slash commands. There is also an +experimental Codex bundle with a smaller set of Codex-readable skill files. +The Codex bundle is useful for ASTRA and `lc` workflow guidance, but it is not +yet a complete one-to-one port of every Claude workflow. If you want to *use* these, start with [The Agentic Workflow](../user/agent-workflow.md) in the user guide. @@ -11,6 +12,8 @@ This page is for maintainers. ## Available skills +### Claude Code bundle + The `/lc-from-*` family is parallel in what you start from: a question, code, or a paper. `/lc-from-paper` is the entry point of a six-skill paper-reproduction bundle; the five siblings stand alone and are @@ -53,10 +56,42 @@ Not entry points. Other skills invoke them — or Claude does, when a deeper ref These intentionally stay out of the top-level README. Researchers use the project-lifecycle skills directly; the reference skills are infrastructure. +### Codex bundle + +Codex projects are scaffolded with: + +```bash +lc init --agent codex my-analysis +cd my-analysis +codex +``` + +The Codex bundle installs project instructions as `AGENTS.md` and skill +guidance under `.agents/skills/`. It does not install `.claude/` or write +`CLAUDE.md`. + +Current Codex skills: + +| Skill | Purpose | +|-------|---------| +| `astra` | Reference for `astra.yaml` structure, decisions, recipes, and the spec-code invariant. | +| `lc-cli` | Reference for `lc run`, `lc status`, `lc verify`, failure handling, and provenance checks. | +| `lc-new` | Guidance for scoping a new analysis from a research question. | +| `lc-from-code` | Guidance for wrapping existing code in ASTRA and materializing outputs through `lc`. | + +After relevant Codex-driven edits, run the checks that apply: + +```bash +astra validate astra.yaml +lc run +lc status +lc verify +``` + ## How a skill is wired -Each skill is a `claude/lightcone/skills//SKILL.md` file with -YAML frontmatter: +Claude skills live at `claude/lightcone/skills//SKILL.md` with YAML +frontmatter: ```yaml --- @@ -74,6 +109,10 @@ prompt itself: phase definitions, rules, references to guide files, anti-patterns. Skills bundle their own helper scripts under `scripts/` and longer prompt fragments under `assets/` when relevant. +Codex skills live at `codex/lightcone/skills//SKILL.md`. Their +frontmatter is intentionally simpler (`name` and `description`) and avoids +Claude-only fields such as `allowed-tools`. + ## Plugin layout ```text @@ -99,6 +138,9 @@ The plugin is force-included into the wheel via `pyproject.toml::tool.hatch.build.targets.wheel.force-include`, so `lc init` finds it whether you're running from source or PyPI. +The Codex bundle is co-located under `codex/lightcone/` and is also included +in the package. + ## Other plugin files The two reference *skills* (`/astra` and `/lc-cli`) live under `skills/` and are listed in the [Reference skills](#reference-skills-auto-primed-via-session-start) section above. Remaining plugin files: diff --git a/docs/user/codex.md b/docs/user/codex.md new file mode 100644 index 00000000..de1b1f87 --- /dev/null +++ b/docs/user/codex.md @@ -0,0 +1,69 @@ +# Codex Support + +Codex support is experimental. The Codex bundle is intentionally smaller +than the Claude Code bundle today: it provides project instructions and a +small set of reference skills for ASTRA and lightcone-cli workflows, but it is +not a complete one-to-one port of every Claude skill. + +## Create a Codex project + +```bash +lc init --agent codex my-analysis +cd my-analysis +codex +``` + +The Codex scaffold is separate from the Claude Code scaffold: + +```text +my-analysis/ +├── astra.yaml +├── AGENTS.md # Codex project instructions +├── .agents/ +│ └── skills/ # Codex-readable skill guidance +├── .lightcone/ +├── Containerfile +├── requirements.txt +├── universes/ +├── src/ +└── results/ +``` + +With `--agent codex`, the project does not install `.claude/` and does not +write `CLAUDE.md`. Use `lc init` or `lc init --agent claude` for the Claude +Code bundle. + +## Working With Codex + +Start Codex from inside the project directory: + +```bash +cd my-analysis +codex +``` + +Tell Codex what you are trying to do in plain language. For example: + +- scope a new analysis from a research question; +- wrap an existing codebase in ASTRA; +- update `astra.yaml` and the implementation together; +- debug a failing `lc run`. + +The key invariant is the same as with Claude: `astra.yaml` is the source of +truth. Code, recipes, decisions, inputs, and outputs should stay synchronized +with the spec. Do not hand-edit files in `results/` to make a run look +successful; final outputs should be produced by `lc run` and backed by +Lightcone manifests. + +After relevant changes, ask Codex to run the checks that apply: + +```bash +astra validate astra.yaml +lc run +lc status +lc verify +``` + +If `astra validate` is not available in the environment, Codex should say so +explicitly and continue with `lc status` / `lc verify` where possible. Failed +commands should be fixed or surfaced; do not mask execution failures. diff --git a/docs/user/getting-started.md b/docs/user/getting-started.md index abbbd253..6be4bd8b 100644 --- a/docs/user/getting-started.md +++ b/docs/user/getting-started.md @@ -77,6 +77,21 @@ project you can also just describe what you're trying to do to Claude — `astra.yaml`, `lc run`, and `lc verify` keep things tracked regardless of how you got there. +!!! note "Experimental Codex support" + + You can scaffold a Codex-oriented project instead: + + ```bash + lc init --agent codex my-analysis + cd my-analysis + codex + ``` + + Codex projects use `AGENTS.md` and `.agents/skills/`. They do not install + `.claude/` or write `CLAUDE.md`. The Codex bundle is currently smaller + than the Claude Code bundle, so do not expect every Claude skill workflow + to have an exact Codex equivalent yet. See [Codex Support](codex.md). + ## 4. Scope the analysis with `/lc-new` Type: diff --git a/docs/user/index.md b/docs/user/index.md index 5d7a64ec..c212349e 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -14,6 +14,8 @@ No need to write code by hand, **you stay in charge of the scientific choices**, machine or on a cluster. - [Getting Started](getting-started.md) — create your first project, run it end-to-end, and understand what each piece does. +- [Codex Support](codex.md) — experimental Codex project scaffolding and + workflow notes. - [The Agentic Workflow](agent-workflow.md) — `/lc-new`, `/lc-from-code`, `/lc-from-paper`, and `/lc-feedback` — what each command does and when to reach for it. @@ -44,6 +46,13 @@ No need to write code by hand, **you stay in charge of the scientific choices**, # then, inside Claude Code: /lc-new ``` + === "Codex" + ```bash + uv tool install lightcone-cli + lc init --agent codex my-analysis && cd my-analysis + codex + ``` + That's the shortest possible path. The rest of the guide is the unhurried version. ## What lightcone-cli is *not* @@ -54,7 +63,7 @@ That's the shortest possible path. The rest of the guide is the unhurried versio Python commands, not a DSL. There's no learning curve beyond what's in [Getting Started](getting-started.md). - **An IDE.** `lc` is a command-line tool; the agent surface lives - inside the agent harness (Claude Code for now). + inside an agent harness such as Claude Code or, experimentally, Codex. If you'd rather skim the design and architecture, the [maintainer docs](../maintainer.md) are the other half of this site. diff --git a/src/lightcone/cli/commands.py b/src/lightcone/cli/commands.py index e299f7b1..20e1aeb2 100644 --- a/src/lightcone/cli/commands.py +++ b/src/lightcone/cli/commands.py @@ -192,9 +192,6 @@ def init( if (directory / "astra.yaml").exists(): raise click.ClickException(f"{directory}/astra.yaml already exists.") - if agent == "codex" and get_agent_bundle_source_dir("codex") is None: - raise click.ClickException("Codex agent bundle is not available yet.") - # Spec scaffold: astra.yaml, universes/baseline.yaml, base .gitignore, # src/. We hold off on git init until our own files are in place so # the initial commit captures the full project state. @@ -242,8 +239,16 @@ def init( # Project CLAUDE.md (a stub) (directory / "CLAUDE.md").write_text(_PROJECT_CLAUDE_MD) - if agent == "both" and get_agent_bundle_source_dir("codex") is None: - console.print("[yellow]Codex agent bundle is not available yet; skipping Codex.[/yellow]") + if agent in {"codex", "both"}: + codex_source = get_agent_bundle_source_dir("codex") + if codex_source is None or not codex_source.exists(): + if agent == "codex": + raise click.ClickException("Codex agent bundle is not available yet.") + console.print( + "[yellow]Codex agent bundle is not available yet; skipping Codex.[/yellow]" + ) + else: + _install_codex_bundle(directory, codex_source) # git init last so the initial commit captures every scaffolded file. no_git = no_git or (directory / ".git").exists() @@ -313,12 +318,26 @@ def init( console.print("\nNext steps:") console.print(f" • Go to the newly created directory [cyan]cd {directory}[/cyan]") - console.print(" • Start [cyan]claude[/cyan]") - console.print( - " • Run [cyan]/lc-new[/cyan] to scope a new analysis, " - "[cyan]/lc-from-code[/cyan] to port existing code, " - "or [cyan]/lc-from-paper[/cyan] to reproduce a paper" - ) + if agent == "codex": + console.print(" • Start [cyan]codex[/cyan]") + elif agent == "both": + console.print(" • Start [cyan]claude[/cyan] or [cyan]codex[/cyan]") + elif agent == "none": + console.print(" • Run your preferred agent or editor from the project directory") + else: + console.print(" • Start [cyan]claude[/cyan]") + + if agent in {"claude", "both"}: + console.print( + " • Run [cyan]/lc-new[/cyan] to scope a new analysis, " + "[cyan]/lc-from-code[/cyan] to port existing code, " + "or [cyan]/lc-from-paper[/cyan] to reproduce a paper" + ) + elif agent == "codex": + console.print( + " • Ask Codex to scope a new analysis, wrap existing code, " + "or run [cyan]lc status[/cyan] / [cyan]lc verify[/cyan]" + ) _CONTAINERFILE = """\ @@ -412,6 +431,27 @@ def _install_claude_plugin( (claude_dir / "settings.json").write_text(json.dumps(settings, indent=2)) +def _install_codex_bundle(project_dir: Path, bundle_source: Path) -> None: + """Copy the bundled Codex guidance into the project. + + Codex uses a project-root ``AGENTS.md`` plus skill files under + ``.agents/skills/``. The bundle deliberately does not install Claude Code + hooks or permissions. + """ + agents_md = bundle_source / "templates" / "AGENTS.md" + if agents_md.exists(): + shutil.copy2(agents_md, project_dir / "AGENTS.md") + + skills_src = bundle_source / "skills" + if skills_src.exists(): + agents_dir = project_dir / ".agents" + agents_dir.mkdir(exist_ok=True) + skills_dest = agents_dir / "skills" + if skills_dest.exists(): + shutil.rmtree(skills_dest) + shutil.copytree(skills_src, skills_dest) + + # ============================================================================= # lc run # ============================================================================= diff --git a/tests/test_cli.py b/tests/test_cli.py index 7e773b4a..706403fa 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -71,6 +71,8 @@ def test_init_creates_project(runner: CliRunner, tmp_path: Path) -> None: assert (project / "astra.yaml").exists() assert (project / "CLAUDE.md").exists() assert (project / ".claude").is_dir() + assert not (project / "AGENTS.md").exists() + assert not (project / ".agents").exists() assert (project / ".gitignore").exists() assert (project / ".lightcone").is_dir() assert (project / "results").is_dir() @@ -88,9 +90,43 @@ def test_init_agent_none_skips_agent_files( assert (project / "astra.yaml").exists() assert not (project / "CLAUDE.md").exists() assert not (project / ".claude").exists() + assert not (project / "AGENTS.md").exists() + assert not (project / ".agents").exists() assert (project / ".lightcone").is_dir() +def test_init_agent_codex_installs_codex_bundle( + runner: CliRunner, tmp_path: Path +) -> None: + project = tmp_path / "proj" + result = runner.invoke( + main, ["init", str(project), "--agent", "codex", "--no-git", "--no-venv"] + ) + assert result.exit_code == 0, result.output + assert (project / "astra.yaml").exists() + assert (project / "AGENTS.md").is_file() + assert (project / ".agents" / "skills" / "astra" / "SKILL.md").is_file() + assert (project / ".agents" / "skills" / "lc-cli" / "SKILL.md").is_file() + assert not (project / "CLAUDE.md").exists() + assert not (project / ".claude").exists() + assert "Start codex" in result.output + + +def test_init_agent_both_installs_claude_and_codex( + runner: CliRunner, tmp_path: Path +) -> None: + project = tmp_path / "proj" + result = runner.invoke( + main, ["init", str(project), "--agent", "both", "--no-git", "--no-venv"] + ) + assert result.exit_code == 0, result.output + assert (project / "CLAUDE.md").is_file() + assert (project / ".claude").is_dir() + assert (project / "AGENTS.md").is_file() + assert (project / ".agents" / "skills" / "lc-new" / "SKILL.md").is_file() + assert "Start claude or codex" in result.output + + def test_init_help_mentions_agent_option(runner: CliRunner) -> None: result = runner.invoke(main, ["init", "--help"]) assert result.exit_code == 0 diff --git a/zensical.toml b/zensical.toml index 31acfefd..0855f407 100644 --- a/zensical.toml +++ b/zensical.toml @@ -14,6 +14,7 @@ nav = [ {"Welcome" = "user/index.md"}, {"Install" = "user/install.md"}, {"Getting Started" = "user/getting-started.md"}, + {"Codex Support" = "user/codex.md"}, {"The Agent Workflow" = "user/agent-workflow.md"}, {"Running on a Cluster" = "user/cluster.md"}, {"Troubleshooting" = "user/troubleshooting.md"},