lightcone-cli 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. lightcone/cli/__init__.py +16 -0
  2. lightcone/cli/claude/lightcone/agents/lc-extractor.md +114 -0
  3. lightcone/cli/claude/lightcone/guides/astra-reference.md +290 -0
  4. lightcone/cli/claude/lightcone/guides/lightcone-cli-reference.md +75 -0
  5. lightcone/cli/claude/lightcone/guides/ui-brand.md +86 -0
  6. lightcone/cli/claude/lightcone/hooks/langfuse_git_commit_hook.py +303 -0
  7. lightcone/cli/claude/lightcone/hooks/langfuse_hook.py +894 -0
  8. lightcone/cli/claude/lightcone/hooks/langfuse_prepare_commit_msg.py +142 -0
  9. lightcone/cli/claude/lightcone/hooks/langfuse_session_init_hook.py +83 -0
  10. lightcone/cli/claude/lightcone/hooks/langfuse_utils.py +457 -0
  11. lightcone/cli/claude/lightcone/scripts/activate-venv.sh +44 -0
  12. lightcone/cli/claude/lightcone/scripts/check-lc-run.sh +140 -0
  13. lightcone/cli/claude/lightcone/scripts/session-start.sh +140 -0
  14. lightcone/cli/claude/lightcone/scripts/validate-on-save.sh +77 -0
  15. lightcone/cli/claude/lightcone/skills/lc-build/SKILL.md +92 -0
  16. lightcone/cli/claude/lightcone/skills/lc-build/assets/loop-prompt.md +92 -0
  17. lightcone/cli/claude/lightcone/skills/lc-build/scripts/setup-lc-build.sh +240 -0
  18. lightcone/cli/claude/lightcone/skills/lc-feedback/SKILL.md +94 -0
  19. lightcone/cli/claude/lightcone/skills/lc-migrate/SKILL.md +98 -0
  20. lightcone/cli/claude/lightcone/skills/lc-new/SKILL.md +183 -0
  21. lightcone/cli/claude/lightcone/skills/lc-verify/SKILL.md +53 -0
  22. lightcone/cli/claude/lightcone/templates/CLAUDE.md +32 -0
  23. lightcone/cli/commands.py +2327 -0
  24. lightcone/cli/plugin.py +34 -0
  25. lightcone/engine/__init__.py +42 -0
  26. lightcone/engine/assets.py +418 -0
  27. lightcone/engine/container.py +370 -0
  28. lightcone/engine/io_manager.py +27 -0
  29. lightcone/engine/runner.py +1017 -0
  30. lightcone/engine/site_registry.py +142 -0
  31. lightcone/engine/status.py +135 -0
  32. lightcone/engine/targets.py +68 -0
  33. lightcone/engine/tree.py +245 -0
  34. lightcone/eval/__init__.py +25 -0
  35. lightcone/eval/build.py +148 -0
  36. lightcone/eval/cli.py +176 -0
  37. lightcone/eval/graders.py +192 -0
  38. lightcone/eval/harness.py +265 -0
  39. lightcone/eval/models.py +117 -0
  40. lightcone/eval/report.py +214 -0
  41. lightcone/eval/sandbox.py +394 -0
  42. lightcone_cli-0.2.0.dist-info/METADATA +16 -0
  43. lightcone_cli-0.2.0.dist-info/RECORD +46 -0
  44. lightcone_cli-0.2.0.dist-info/WHEEL +4 -0
  45. lightcone_cli-0.2.0.dist-info/entry_points.txt +2 -0
  46. lightcone_cli-0.2.0.dist-info/licenses/LICENSE +29 -0
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # Activate Python virtual environment if it exists in the project directory
3
+ # This hook runs at SessionStart to ensure the venv is active for Claude Code
4
+
5
+ set -e
6
+
7
+ # Check if we have a project directory
8
+ if [ -z "$CLAUDE_PROJECT_DIR" ]; then
9
+ exit 0
10
+ fi
11
+
12
+ # Check for .venv in project directory
13
+ VENV_DIR="$CLAUDE_PROJECT_DIR/.venv"
14
+ if [ ! -d "$VENV_DIR" ]; then
15
+ exit 0
16
+ fi
17
+
18
+ # Determine activate script path based on OS
19
+ if [ -f "$VENV_DIR/bin/activate" ]; then
20
+ ACTIVATE_SCRIPT="$VENV_DIR/bin/activate"
21
+ elif [ -f "$VENV_DIR/Scripts/activate" ]; then
22
+ ACTIVATE_SCRIPT="$VENV_DIR/Scripts/activate"
23
+ else
24
+ exit 0
25
+ fi
26
+
27
+ # Capture environment before activation
28
+ BEFORE_PATH="$PATH"
29
+ BEFORE_VIRTUAL_ENV="${VIRTUAL_ENV:-}"
30
+
31
+ # Source the activation script
32
+ # shellcheck source=/dev/null
33
+ source "$ACTIVATE_SCRIPT"
34
+
35
+ # If CLAUDE_ENV_FILE is set, write environment changes to it
36
+ if [ -n "$CLAUDE_ENV_FILE" ]; then
37
+ # Write PATH update
38
+ echo "PATH=$PATH" >> "$CLAUDE_ENV_FILE"
39
+
40
+ # Write VIRTUAL_ENV
41
+ echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> "$CLAUDE_ENV_FILE"
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,140 @@
1
+ #!/bin/bash
2
+ # PostToolUse hook: Warn when agent runs a Python script that has an integrated recipe
3
+ # Triggers on Bash commands matching "python". Uses astra.helpers for correct YAML parsing.
4
+ # Only fires on specific recipe matches — silent otherwise (no Tier 2 noise).
5
+ # Parses --universe from the command to check the targeted universe; falls back to baseline.
6
+
7
+ # Read JSON input from stdin
8
+ input=$(cat)
9
+
10
+ # Extract the command that was run
11
+ command=$(echo "$input" | jq -r '.tool_input.command // empty')
12
+
13
+ # Exit silently if no command
14
+ if [ -z "$command" ]; then
15
+ exit 0
16
+ fi
17
+
18
+ # Quick filter: only care about commands containing "python"
19
+ if ! echo "$command" | grep -qE 'python[23]?\s'; then
20
+ exit 0
21
+ fi
22
+
23
+ # Skip lc/astra commands
24
+ if echo "$command" | grep -qE '(lc|astra)\s'; then
25
+ exit 0
26
+ fi
27
+
28
+ # Must be in an ASTRA project
29
+ if [ ! -f "astra.yaml" ]; then
30
+ exit 0
31
+ fi
32
+
33
+ # Use Python + astra.helpers to extract recipe commands and match against the agent's command.
34
+ # This correctly handles all YAML variants (block, inline, sub-analyses).
35
+ # Outputs a warning message if a match is found, or nothing if no match.
36
+ msg=$(python3 -c "
37
+ import sys, os, re
38
+
39
+ try:
40
+ from astra.helpers import load_yaml, get_outputs_with_recipes
41
+ from lightcone.engine.status import get_output_status
42
+ from pathlib import Path
43
+ except ImportError:
44
+ sys.exit(0)
45
+
46
+ try:
47
+ data = load_yaml('astra.yaml')
48
+ except Exception:
49
+ sys.exit(0)
50
+
51
+ recipes = get_outputs_with_recipes(data)
52
+ if not recipes:
53
+ sys.exit(0)
54
+
55
+ # Parse --universe from the agent's command; fall back to baseline
56
+ agent_cmd = sys.argv[1]
57
+ universe = 'baseline'
58
+ m = re.search(r'--universe[= ]\s*(\S+)', agent_cmd)
59
+ if m:
60
+ universe = m.group(1)
61
+
62
+ # Get status for the targeted universe
63
+ try:
64
+ status = get_output_status(Path('.'), universe)
65
+ except Exception:
66
+ status = {}
67
+
68
+ if not status:
69
+ sys.exit(0)
70
+
71
+ # Check if any recipe output is integrated (pending or materialized) in this universe
72
+ has_integrated = any(
73
+ status.get(o.get('id', ''), '') in ('pending', 'materialized')
74
+ for o in recipes
75
+ )
76
+ if not has_integrated:
77
+ # No integrated recipes in this universe — Write & Debug phase
78
+ sys.exit(0)
79
+
80
+ # Build map: normalized script path -> (output_id, status)
81
+ recipe_scripts = {}
82
+ for o in recipes:
83
+ cmd = o.get('recipe', {}).get('command', '')
84
+ out_id = o.get('id', '')
85
+ out_status = status.get(out_id, 'no_recipe')
86
+ if out_status not in ('pending', 'materialized'):
87
+ continue
88
+ # Extract .py path from recipe command
89
+ for part in cmd.split():
90
+ if part.endswith('.py'):
91
+ normalized = part.lstrip('./')
92
+ recipe_scripts[normalized] = (out_id, out_status)
93
+ recipe_scripts[os.path.basename(normalized)] = (out_id, out_status)
94
+ break
95
+
96
+ if not recipe_scripts:
97
+ sys.exit(0)
98
+
99
+ # Check the agent's command against recipe scripts
100
+ # Split on && || ; to handle chained commands
101
+ subcmds = re.split(r'&&|\|\||;', agent_cmd)
102
+
103
+ for subcmd in subcmds:
104
+ subcmd = subcmd.strip()
105
+ # Extract .py path from this sub-command
106
+ for token in subcmd.split():
107
+ if token.endswith('.py'):
108
+ agent_script = token.lstrip('./')
109
+ match = recipe_scripts.get(agent_script) or recipe_scripts.get(os.path.basename(agent_script))
110
+ if match:
111
+ out_id, out_status = match
112
+ if out_status == 'pending':
113
+ print(f'WARNING: You just ran the script for output \`{out_id}\` (status: pending in {universe}), which has an integrated recipe. Use \`lc run {out_id} --universe {universe}\` instead to ensure reproducibility.')
114
+ elif out_status == 'materialized':
115
+ print(f'NOTE: Output \`{out_id}\` already has results in {universe} from \`lc run\`. If regenerating, use \`lc run {out_id} --universe {universe}\` to keep results reproducible.')
116
+ sys.exit(0)
117
+ break
118
+ # Also handle python -m
119
+ parts = subcmd.split()
120
+ for i, p in enumerate(parts):
121
+ if p == '-m' and i + 1 < len(parts):
122
+ module_path = parts[i + 1].replace('.', '/') + '.py'
123
+ match = recipe_scripts.get(module_path) or recipe_scripts.get(os.path.basename(module_path))
124
+ if match:
125
+ out_id, out_status = match
126
+ if out_status == 'pending':
127
+ print(f'WARNING: You just ran the module for output \`{out_id}\` (status: pending in {universe}), which has an integrated recipe. Use \`lc run {out_id} --universe {universe}\` instead.')
128
+ elif out_status == 'materialized':
129
+ print(f'NOTE: Output \`{out_id}\` already has results in {universe} from \`lc run\`. If regenerating, use \`lc run {out_id} --universe {universe}\`.')
130
+ sys.exit(0)
131
+ break
132
+ " "$command" 2>/dev/null)
133
+
134
+ # If Python produced a message, return it as hook context
135
+ if [ -n "$msg" ]; then
136
+ escaped_msg=$(echo "$msg" | jq -Rs .)
137
+ echo "{\"hookSpecificOutput\": {\"hookEventName\": \"PostToolUse\", \"additionalContext\": $escaped_msg}}"
138
+ fi
139
+
140
+ exit 0
@@ -0,0 +1,140 @@
1
+ #!/bin/bash
2
+ # SessionStart hook: Show ASTRA analysis summary when entering a project
3
+ # Provides context about the current analysis state
4
+
5
+ # Read JSON input from stdin
6
+ input=$(cat)
7
+
8
+ # Get the current working directory
9
+ cwd=$(echo "$input" | jq -r '.cwd // empty')
10
+
11
+ if [ -z "$cwd" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ cd "$cwd" 2>/dev/null || exit 0
16
+
17
+ # Check for active lc-build loop (crash recovery)
18
+ if [ -f ".claude/ralph-loop.local.md" ]; then
19
+ loop_iter=$(grep '^iteration:' .claude/ralph-loop.local.md 2>/dev/null | awk '{print $2}')
20
+ loop_max=$(grep '^max_iterations:' .claude/ralph-loop.local.md 2>/dev/null | awk '{print $2}')
21
+ loop_session=$(grep '^session_id:' .claude/ralph-loop.local.md 2>/dev/null | awk '{print $2}')
22
+ # Read universe from dedicated frontmatter field (falls back gracefully for old state files)
23
+ loop_universe=$(grep '^universe:' .claude/ralph-loop.local.md 2>/dev/null | awk '{print $2}')
24
+ loop_universe="${loop_universe:-unknown}"
25
+
26
+ current_session="${CLAUDE_CODE_SESSION_ID:-}"
27
+
28
+ # If the loop belongs to a different active session, show informational message only.
29
+ # This prevents a concurrent session from accidentally resuming or cancelling another session's loop.
30
+ if [ -n "$loop_session" ] && [ -n "$current_session" ] && [ "$loop_session" != "$current_session" ]; then
31
+ loop_info="lc-build loop active in another session (universe: ${loop_universe}, iteration ${loop_iter:-?}/${loop_max:-?}). This session is unaffected — manage the loop from its original session."
32
+ escaped_info=$(echo "$loop_info" | jq -Rs .)
33
+ echo "{\"hookSpecificOutput\": {\"hookEventName\": \"SessionStart\", \"additionalContext\": $escaped_info}}"
34
+ else
35
+ loop_warning="Active lc-build loop detected (universe: ${loop_universe}, iteration ${loop_iter:-?}/${loop_max:-?})
36
+ Resume: /lc-build --universe ${loop_universe} Cancel: /cancel-ralph"
37
+ escaped_warning=$(echo "$loop_warning" | jq -Rs .)
38
+ echo "{\"hookSpecificOutput\": {\"hookEventName\": \"SessionStart\", \"additionalContext\": $escaped_warning}}"
39
+ fi
40
+ exit 0
41
+ fi
42
+
43
+ # Sync extraction model from ~/.lightcone/config.yaml to .claude/agents/lc-extractor.md
44
+ if [ -f ".claude/agents/lc-extractor.md" ] && [ -f "$HOME/.lightcone/config.yaml" ]; then
45
+ ext_model=$(grep '^extraction_model:' "$HOME/.lightcone/config.yaml" 2>/dev/null | awk '{print $2}' | tr -d "'\"")
46
+ # Default to sonnet if not configured
47
+ [ -z "$ext_model" ] && ext_model="sonnet"
48
+ if [ -n "$ext_model" ]; then
49
+ if ! grep -q "^model:" .claude/agents/lc-extractor.md 2>/dev/null; then
50
+ # Insert model field after description line
51
+ sed -i.bak '/^tools:/i\
52
+ model: '"$ext_model" .claude/agents/lc-extractor.md 2>/dev/null && rm -f .claude/agents/lc-extractor.md.bak
53
+ else
54
+ # Update existing model field
55
+ sed -i.bak 's/^model: .*/model: '"$ext_model"'/' .claude/agents/lc-extractor.md 2>/dev/null && rm -f .claude/agents/lc-extractor.md.bak
56
+ fi
57
+ else
58
+ # Empty model = inherit, remove model line if present
59
+ sed -i.bak '/^model: /d' .claude/agents/lc-extractor.md 2>/dev/null && rm -f .claude/agents/lc-extractor.md.bak
60
+ fi
61
+ fi
62
+
63
+ # Check if this is an ASTRA project (has astra.yaml)
64
+ if [ ! -f "astra.yaml" ]; then
65
+ exit 0
66
+ fi
67
+
68
+ # Check if astra command is available
69
+ if ! command -v astra &> /dev/null; then
70
+ # Provide minimal info without CLI
71
+ echo "{\"hookSpecificOutput\": {\"hookEventName\": \"SessionStart\", \"additionalContext\": \"This is an ASTRA project. The astra CLI is not installed - run 'pip install astra' to enable validation and other commands.\"}}"
72
+ exit 0
73
+ fi
74
+
75
+ # Gather analysis information
76
+ analysis_name=$(grep -m1 "^ name:" astra.yaml 2>/dev/null | sed 's/.*name: *"\?\([^"]*\)"\?/\1/' | tr -d '"')
77
+
78
+ # Count decisions
79
+ decision_count=$(grep -c "^ [a-z_]*:$" astra.yaml 2>/dev/null | head -1)
80
+ # More accurate: count keys under 'decisions:'
81
+ decision_count=$(awk '/^decisions:/{found=1; next} found && /^ [a-z_]+:/{count++} found && /^[a-z]/{exit} END{print count}' astra.yaml 2>/dev/null)
82
+
83
+ # Count universes
84
+ universe_count=$(ls -1 universes/*.yaml 2>/dev/null | wc -l | tr -d ' ')
85
+
86
+ # Check validation status
87
+ validation_result=$(astra validate astra.yaml 2>&1)
88
+ if [ $? -eq 0 ]; then
89
+ validation_status="valid"
90
+ else
91
+ validation_status="has errors"
92
+ fi
93
+
94
+ # Build summary
95
+ summary="ASTRA Project: ${analysis_name:-unnamed}
96
+ - Decisions: ${decision_count:-0}
97
+ - Universes: ${universe_count:-0}
98
+ - Validation: ${validation_status}
99
+ - Reference: For astra.yaml syntax and spec format, read .claude/guides/astra-reference.md; for CLI and execution, read .claude/guides/lightcone-cli-reference.md"
100
+
101
+ # If validation failed, add error summary
102
+ if [ "$validation_status" = "has errors" ]; then
103
+ # Get first few lines of errors
104
+ error_preview=$(echo "$validation_result" | head -5)
105
+ summary="$summary
106
+
107
+ Validation errors (run 'astra validate astra.yaml' for details):
108
+ $error_preview"
109
+ fi
110
+
111
+ # Add lc status if lc CLI is available
112
+ if command -v lc &> /dev/null; then
113
+ lc_status=$(lc status 2>&1)
114
+ lc_exit=$?
115
+ if [ $lc_exit -eq 0 ]; then
116
+ # Count outputs in each state
117
+ pending_count=$(echo "$lc_status" | grep -c "pending")
118
+ ok_count=$(echo "$lc_status" | grep -c "ok")
119
+ no_recipe_count=$(echo "$lc_status" | grep -c "no recipe")
120
+
121
+ summary="$summary
122
+
123
+ Materialization status:
124
+ - ok: ${ok_count}
125
+ - pending: ${pending_count}
126
+ - no recipe: ${no_recipe_count}"
127
+
128
+ if [ "$pending_count" -gt 0 ]; then
129
+ summary="$summary
130
+
131
+ ACTION REQUIRED: ${pending_count} output(s) have recipes but are not yet materialized. Use \`lc run\` to produce them."
132
+ fi
133
+ fi
134
+ fi
135
+
136
+ # Output as JSON
137
+ escaped_summary=$(echo "$summary" | jq -Rs .)
138
+ echo "{\"hookSpecificOutput\": {\"hookEventName\": \"SessionStart\", \"additionalContext\": $escaped_summary}}"
139
+
140
+ exit 0
@@ -0,0 +1,77 @@
1
+ #!/bin/bash
2
+ # PostToolUse hook: Auto-validate ASTRA files after modification
3
+ # Triggers on Write|Edit of astra.yaml or universes/*.yaml
4
+
5
+ # Read JSON input from stdin
6
+ input=$(cat)
7
+
8
+ # Extract the file path that was modified
9
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_response.filePath // empty')
10
+
11
+ # Exit silently if no file path
12
+ if [ -z "$file_path" ]; then
13
+ exit 0
14
+ fi
15
+
16
+ # Get the filename
17
+ filename=$(basename "$file_path")
18
+ dirpath=$(dirname "$file_path")
19
+ dirname=$(basename "$dirpath")
20
+
21
+ # Check if this is an ASTRA-related file
22
+ is_astra_file=false
23
+
24
+ # Check for astra.yaml (main analysis file)
25
+ if [ "$filename" = "astra.yaml" ]; then
26
+ is_astra_file=true
27
+ file_type="analysis"
28
+ fi
29
+
30
+ # Check for universe files (in universes/ directory)
31
+ if [ "$dirname" = "universes" ] && [[ "$filename" == *.yaml ]]; then
32
+ is_astra_file=true
33
+ file_type="universe"
34
+ fi
35
+
36
+ # Exit if not an ASTRA file
37
+ if [ "$is_astra_file" = false ]; then
38
+ exit 0
39
+ fi
40
+
41
+ # Find the project root (where astra.yaml lives)
42
+ if [ "$file_type" = "analysis" ]; then
43
+ project_root="$dirpath"
44
+ else
45
+ # For universe files, go up one level
46
+ project_root=$(dirname "$dirpath")
47
+ fi
48
+
49
+ # Check if astra command is available
50
+ if ! command -v astra &> /dev/null; then
51
+ # ASTRA CLI not installed, skip validation
52
+ exit 0
53
+ fi
54
+
55
+ # Run validation
56
+ cd "$project_root" 2>/dev/null || exit 0
57
+
58
+ if [ "$file_type" = "analysis" ]; then
59
+ result=$(astra validate astra.yaml 2>&1)
60
+ exit_code=$?
61
+ else
62
+ result=$(astra validate "$file_path" 2>&1)
63
+ exit_code=$?
64
+ fi
65
+
66
+ # Prepare response
67
+ if [ $exit_code -eq 0 ]; then
68
+ # Validation passed
69
+ echo "{\"hookSpecificOutput\": {\"hookEventName\": \"PostToolUse\", \"additionalContext\": \"ASTRA validation passed for $filename\"}}"
70
+ else
71
+ # Validation failed - provide context to Claude
72
+ # Escape the result for JSON (jq -Rs . adds quotes, so use it directly)
73
+ escaped_result=$(echo "ASTRA validation FAILED for $filename:\n$result" | jq -Rs .)
74
+ echo "{\"hookSpecificOutput\": {\"hookEventName\": \"PostToolUse\", \"additionalContext\": $escaped_result}}"
75
+ fi
76
+
77
+ exit 0
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: lc-build
3
+ description: >
4
+ Build an ASTRA analysis from spec to materialized results. Plans interactively,
5
+ then loops autonomously via ralph-wiggum until all outputs are verified.
6
+ allowed-tools: Read, Write, Edit, Glob, Grep, Bash(astra:*), Bash(lc:*), Bash(python:*), Bash(git:*), Bash(pip:*), Bash(mkdir:*), Bash(setup-lc-build:*), Agent, AskUserQuestion
7
+ argument-hint: "[DESCRIPTION] [--universe NAME] [--max-iterations N]"
8
+ ---
9
+
10
+ # /lc-build
11
+
12
+ Two-phase build: plan interactively with the user, then loop autonomously until done.
13
+
14
+ **Do NOT write or modify any code (scripts, astra.yaml, etc.) until the user approves the plan in Phase 1.** Phase 1 is read-only exploration and planning. Code changes only happen in Phase 2 (the loop).
15
+
16
+ ## Phase 0: Check for Interrupted Loop
17
+
18
+ Before anything else, check if a previous loop was interrupted:
19
+
20
+ ```
21
+ if .claude/ralph-loop.local.md exists:
22
+ ```
23
+
24
+ If it exists, ask the user via `AskUserQuestion`:
25
+ - "A previous lc-build loop was interrupted. Would you like to resume where it left off or start fresh?"
26
+ - Options: "Resume the loop", "Start fresh (discard previous state)"
27
+
28
+ **If resume:** Run the setup script in resume mode to claim the loop for this session:
29
+ ```
30
+ bash .claude/skills/lc-build/scripts/setup-lc-build.sh --resume
31
+ ```
32
+ Then jump straight to reading `.claude/ralph-loop.local.md` and following the loop prompt.
33
+
34
+ **If start fresh:** Delete the old state file (`rm .claude/ralph-loop.local.md`) and continue to Phase 1.
35
+
36
+ ## Phase 1: Setup & Plan
37
+
38
+ ### 1. Validate prerequisites
39
+
40
+ Run the setup script in validate mode:
41
+
42
+ ```
43
+ bash .claude/skills/lc-build/scripts/setup-lc-build.sh --validate --universe <UNIVERSE> --max-iterations <N>
44
+ ```
45
+
46
+ Default universe is `baseline`, default max-iterations is `25`. Parse these from the user's arguments.
47
+
48
+ If validation fails, fix issues before proceeding (create `astra.yaml` via `/lc-new`, fix validation errors, etc.).
49
+
50
+ ### 2. Create implementation plan
51
+
52
+ Read `astra.yaml`, `CLAUDE.md`, `.claude/guides/astra-reference.md`, `.claude/guides/lightcone-cli-reference.md`, `universes/<UNIVERSE>.yaml`, and any existing `scripts/` directory. If the user provided a description (e.g. `/lc-build focus on the fitting script first`), use it to guide the plan's priorities and ordering. Produce an ordered implementation plan and write it to `.lightcone/plans/build-plan-<UNIVERSE>.md`.
53
+
54
+ The plan must include:
55
+
56
+ 1. **Analysis overview** — project name, universe, input data, container, execution target
57
+ 2. **Dependency graph** — which outputs depend on which
58
+ 3. **Decision selections** — table of decisions and their selected values for this universe
59
+ 4. **Ordered build checklist** — for each output: script, decisions, dependencies, estimated cost
60
+ 5. **Verification checklist** — spec validation, decision-code alignment
61
+
62
+ ### 3. Present plan for approval
63
+
64
+ Print the plan contents and ask the user via `AskUserQuestion`:
65
+
66
+ - "Does this build plan look good?"
67
+ - Options: "Approve and start building", "Let me edit the plan first"
68
+
69
+ If the user wants changes, wait for them to edit `.lightcone/plans/build-plan-<UNIVERSE>.md` and re-present.
70
+
71
+ ## Phase 2: Activate Loop
72
+
73
+ Once the user approves the plan, activate the autonomous loop:
74
+
75
+ ```
76
+ bash .claude/skills/lc-build/scripts/setup-lc-build.sh --activate --universe <UNIVERSE> --max-iterations <N>
77
+ ```
78
+
79
+ This creates `.claude/ralph-loop.local.md` — the ralph-wiggum state file. The stop hook will now intercept exits and re-inject the build prompt.
80
+
81
+ **Begin the first iteration immediately.** Read `.claude/ralph-loop.local.md` — the rendered loop prompt is everything after the YAML frontmatter. Follow it: survey, decide what to do, work, commit, exit. On exit, the stop hook re-invokes you with the same prompt for subsequent iterations until you output `<promise>BUILD_COMPLETE</promise>` or hit max iterations.
82
+
83
+ ## References
84
+
85
+ - [Loop Prompt](./assets/loop-prompt.md) — the invariant prompt for each iteration
86
+ - [lightcone-cli Verify](../lc-verify/SKILL.md) — verification checks
87
+
88
+ ## Notes
89
+
90
+ - The setup script will attempt to install the ralph-loop plugin if missing (via marketplace update). If installation fails, it errors and cleans up — the loop cannot run without the stop hook.
91
+ - The build plan file (`.lightcone/plans/build-plan-<UNIVERSE>.md`) persists across crashes for easy resumption. It's deleted on successful completion.
92
+ - To cancel mid-loop: `/cancel-ralph`
@@ -0,0 +1,92 @@
1
+ You are inside a lc-build loop (universe: {{UNIVERSE}}). Each iteration: survey, work, commit, exit. The stop hook re-invokes you with this prompt until you're done.
2
+
3
+ ## Survey
4
+
5
+ Run these commands and read their output:
6
+
7
+ 1. `lc status --universe {{UNIVERSE}}` -- what's materialized, what's pending, what has no recipe
8
+ 2. `git log --oneline -10` -- what happened recently
9
+ 3. `astra validate astra.yaml` -- is the spec valid
10
+ 4. Read `.lightcone/plans/build-plan-{{UNIVERSE}}.md` -- your implementation plan (cross off completed items as you go)
11
+
12
+ ## Decide What to Do
13
+
14
+ **Follow the plan.** Read `.lightcone/plans/build-plan-{{UNIVERSE}}.md` and work on the next unchecked item. The plan was designed with the right ordering — shared utilities before scripts that use them, upstream outputs before downstream ones. Trust it.
15
+
16
+ If the plan is fully checked off or doesn't cover what `lc status` reveals, fall back to the status-based rules below.
17
+
18
+ ### `astra validate` fails → Fix the spec first
19
+
20
+ Always fix validation errors before doing anything else. Commit. Exit.
21
+
22
+ ### All outputs show `ok` → Verify & Complete
23
+
24
+ All outputs are materialized. Time to verify.
25
+
26
+ 1. **Inline checks:**
27
+ - `astra validate astra.yaml` passes
28
+ - `lc status --universe {{UNIVERSE}}` shows all `ok`
29
+ - Decision-code alignment: `grep -r "add_argument" scripts/` and compare against `astra info --decisions` — every decision must be a parameter, no hardcoded values
30
+ 2. **If any issues found:** fix them, re-materialize if needed, commit. Exit (loop continues).
31
+ 3. **If all clean:** Spawn a verification sub-agent with explicit steps (do not rely on skill dispatch — the sub-agent cannot invoke `/lc-verify` directly):
32
+ ```
33
+ Agent tool, subagent_type: general-purpose
34
+ Prompt: "Verify the spec, code, and results all agree for universe {{UNIVERSE}}. Run these checks in order:
35
+ 1. Spec validation: run `astra validate astra.yaml` — must pass with no errors.
36
+ 2. Materialization status: run `lc status --universe {{UNIVERSE}}` — every output must show `ok`.
37
+ 3. Decision-code alignment (most important): run `astra info --decisions` and `grep -r 'add_argument' scripts/`. Every decision in the spec must be accepted as a CLI parameter in the code, with no hardcoded values.
38
+ 4. Results match spec: for every output in astra.yaml, confirm `results/{{UNIVERSE}}/<output_id>.<ext>` exists and looks well-formed. For `type: metric` outputs, check for valid `{'value': ...}` JSON.
39
+ Report all findings with file paths and line numbers. If all checks pass, end your report with exactly: VERIFIED"
40
+ ```
41
+ 4. **If sub-agent reports issues:** fix them, commit. Exit (loop continues).
42
+ 5. **If sub-agent says VERIFIED:** Output exactly: `<promise>BUILD_COMPLETE</promise>`, then clean up the build plan (`rm .lightcone/plans/build-plan-{{UNIVERSE}}.md`).
43
+
44
+ ## Reference: How Work Gets Done
45
+
46
+ These are the kinds of work you'll do, guided by the plan. Not a rigid sequence — the plan determines the order.
47
+
48
+ ### Writing scripts
49
+
50
+ 1. **Write the script.** Parameterize all decisions from `astra.yaml` as command-line arguments (underscore convention: `stellar_mass_cut` → `--stellar_mass_cut`).
51
+ The script must contain real, functional logic that produces genuine results from actual input data. No `# TODO` stubs, no hardcoded dummy values standing in for computation, no `pass` in place of real logic, no synthetic/mock data generation when real data is specified. If you cannot implement the full logic (e.g., missing a library or unclear algorithm), document the blocker in the build plan and move on — do not ship a fake version.
52
+ 2. **Test locally:** `python scripts/<name>.py --decision1 value1 --decision2 value2` using values from `universes/{{UNIVERSE}}.yaml`.
53
+ Note: manual script runs may write to `results/` but do NOT register as materialized.
54
+ Only `lc run` creates the Dagster events that `lc status` recognizes.
55
+ 3. **Debug until it works.** Read tracebacks, check imports (`python -c "import module"`), verify decision parameter names match `astra.yaml`.
56
+ 4. **Commit** with a message describing what the script does.
57
+
58
+ ### Adding recipes & materializing
59
+
60
+ 1. **Add the recipe block** to `astra.yaml` under the output's `recipe:` key.
61
+ 2. **Validate:** `astra validate astra.yaml`
62
+ 3. **Check execution environment:** If the target is SLURM, check `echo $SLURM_JOB_ID`. If empty, you are on a login node — warn the user to start an interactive allocation (`salloc`) before running. Do not submit batch jobs during the build loop; interactive execution is required for fast iteration.
63
+ 4. **Run it:** `lc run <OUTPUT> --universe {{UNIVERSE}}`
64
+ 5. **If it fails:** Read the error output carefully and diagnose the root cause before retrying. Never re-run the same command without changing something first. Common causes:
65
+ - Container not built → `lc build`
66
+ - Upstream not materialized → materialize dependency first
67
+ - Script error inside container → fix the script, then re-run
68
+ If a second attempt also fails, note the failure in your commit message and in the build plan, then move on to other work. Come back to it in a later iteration with fresh context.
69
+ 6. **If it succeeds:** Verify the result file exists at `results/{{UNIVERSE}}/<output_id>.<ext>` and looks well-formed.
70
+ 7. **Commit** with a message noting what was materialized.
71
+
72
+ ## Rules
73
+
74
+ **Work on 1-3 things per iteration.** Do NOT try to clear the entire queue. Exit after substantial progress so the next iteration gets fresh context.
75
+
76
+ **Exit before compaction.** Exit the iteration when ANY of these apply:
77
+ - You have materialized an output or made a failed attempt at one
78
+ - You have written or substantially modified more than 2 scripts
79
+ - A command produced more than ~200 lines of output
80
+ - You are on your 3rd or later `lc run` invocation this iteration
81
+ - You have read the same file more than once this iteration
82
+ Do not wait until context feels tight. Exit early and often — the next iteration gets fresh context and costs nothing.
83
+
84
+ **Commit messages are memory.** The next iteration discovers what you did via `git log`. Write descriptive commit messages.
85
+
86
+ **Trust the spec.** `astra.yaml` is the source of truth. Don't ask permission, don't second-guess decisions. Build what it says.
87
+
88
+ **Update the plan.** After completing work, edit `.lightcone/plans/build-plan-{{UNIVERSE}}.md` to cross off completed items and add notes about what you learned.
89
+
90
+ **Document blockers.** If you hit something you can't resolve (missing data, ambiguous spec, external dependency), add it to the Open Questions section in `CLAUDE.md` and move on to other work.
91
+
92
+ **No placeholders.** Every script must perform real computation on real data. Code that fakes results — hardcoded return values, TODO stubs, synthetic data standing in for real inputs — is worse than no code at all. If you cannot implement something fully, skip it and document why in the build plan.