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.
- lightcone/cli/__init__.py +16 -0
- lightcone/cli/claude/lightcone/agents/lc-extractor.md +114 -0
- lightcone/cli/claude/lightcone/guides/astra-reference.md +290 -0
- lightcone/cli/claude/lightcone/guides/lightcone-cli-reference.md +75 -0
- lightcone/cli/claude/lightcone/guides/ui-brand.md +86 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_git_commit_hook.py +303 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_hook.py +894 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_prepare_commit_msg.py +142 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_session_init_hook.py +83 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_utils.py +457 -0
- lightcone/cli/claude/lightcone/scripts/activate-venv.sh +44 -0
- lightcone/cli/claude/lightcone/scripts/check-lc-run.sh +140 -0
- lightcone/cli/claude/lightcone/scripts/session-start.sh +140 -0
- lightcone/cli/claude/lightcone/scripts/validate-on-save.sh +77 -0
- lightcone/cli/claude/lightcone/skills/lc-build/SKILL.md +92 -0
- lightcone/cli/claude/lightcone/skills/lc-build/assets/loop-prompt.md +92 -0
- lightcone/cli/claude/lightcone/skills/lc-build/scripts/setup-lc-build.sh +240 -0
- lightcone/cli/claude/lightcone/skills/lc-feedback/SKILL.md +94 -0
- lightcone/cli/claude/lightcone/skills/lc-migrate/SKILL.md +98 -0
- lightcone/cli/claude/lightcone/skills/lc-new/SKILL.md +183 -0
- lightcone/cli/claude/lightcone/skills/lc-verify/SKILL.md +53 -0
- lightcone/cli/claude/lightcone/templates/CLAUDE.md +32 -0
- lightcone/cli/commands.py +2327 -0
- lightcone/cli/plugin.py +34 -0
- lightcone/engine/__init__.py +42 -0
- lightcone/engine/assets.py +418 -0
- lightcone/engine/container.py +370 -0
- lightcone/engine/io_manager.py +27 -0
- lightcone/engine/runner.py +1017 -0
- lightcone/engine/site_registry.py +142 -0
- lightcone/engine/status.py +135 -0
- lightcone/engine/targets.py +68 -0
- lightcone/engine/tree.py +245 -0
- lightcone/eval/__init__.py +25 -0
- lightcone/eval/build.py +148 -0
- lightcone/eval/cli.py +176 -0
- lightcone/eval/graders.py +192 -0
- lightcone/eval/harness.py +265 -0
- lightcone/eval/models.py +117 -0
- lightcone/eval/report.py +214 -0
- lightcone/eval/sandbox.py +394 -0
- lightcone_cli-0.2.0.dist-info/METADATA +16 -0
- lightcone_cli-0.2.0.dist-info/RECORD +46 -0
- lightcone_cli-0.2.0.dist-info/WHEEL +4 -0
- lightcone_cli-0.2.0.dist-info/entry_points.txt +2 -0
- 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.
|