Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,12 @@ jobs:
- name: Build quick check comment message
id: build-quick-message
continue-on-error: true # Comment is cosmetic — don't fail the job
env:
# CRITERIA and HISTORICAL_CONTEXT passed via env block to avoid
# backtick command substitution — LLM evidence text contains
# `npm test`, `git diff` etc. which bash executes if inline-expanded
CRITERIA: ${{ steps.eval-candidate.outputs.criteria }}
HISTORICAL_CONTEXT: ${{ steps.historical.outputs.context }}
run: |
IS_BOOTSTRAPPING="${{ steps.compare.outputs.is_bootstrapping }}"
BASELINE="${{ steps.compare.outputs.baseline }}"
Expand All @@ -654,9 +660,7 @@ jobs:
TOTAL_TOKENS="${{ steps.eval-candidate.outputs.total_tokens }}"
EST_COST="${{ steps.eval-candidate.outputs.est_cost }}"
TOKENS_PER_POINT="${{ steps.eval-candidate.outputs.tokens_per_point }}"
CRITERIA="${{ steps.eval-candidate.outputs.criteria }}"
SCENARIO_NAME="${{ steps.select-scenario.outputs.scenario_name }}"
HISTORICAL_CONTEXT="${{ steps.historical.outputs.context }}"

# N/A-aware metric formatting (prevents "N/As" display bug)
format_metric() {
Expand Down Expand Up @@ -855,7 +859,7 @@ jobs:
if: |
github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'merge-ready')
needs: [validate, cleanup-old-comments]
needs: [validate]

steps:
- name: Checkout PR branch
Expand Down Expand Up @@ -1394,6 +1398,12 @@ jobs:

- name: Build full evaluation comment message
id: build-full-message
env:
# CRITERIA and HISTORICAL_CONTEXT passed via env block to avoid
# backtick command substitution — LLM evidence text contains
# `npm test`, `git diff` etc. which bash executes if inline-expanded
CRITERIA: ${{ steps.eval-candidate.outputs.criteria }}
HISTORICAL_CONTEXT: ${{ steps.historical.outputs.context }}
run: |
IS_BOOTSTRAPPING="${{ steps.compare.outputs.is_bootstrapping }}"
BASELINE="${{ steps.compare.outputs.baseline }}"
Expand All @@ -1410,14 +1420,12 @@ jobs:
STATUS="${{ steps.compare.outputs.status }}"
EMOJI="${{ steps.compare.outputs.emoji }}"
VERDICT="${{ steps.compare.outputs.verdict }}"
CRITERIA="${{ steps.eval-candidate.outputs.criteria }}"
SDP_SCORE="${{ steps.eval-candidate.outputs.sdp_score }}"
SDP_EXTERNAL="${{ steps.eval-candidate.outputs.sdp_external }}"
SDP_EXTERNAL_CHANGE="${{ steps.eval-candidate.outputs.sdp_external_change }}"
SDP_ROBUSTNESS="${{ steps.eval-candidate.outputs.sdp_robustness }}"
SDP_INTERPRETATION="${{ steps.eval-candidate.outputs.sdp_interpretation }}"
SCENARIO_NAME="${{ steps.select-scenario.outputs.scenario_name }}"
HISTORICAL_CONTEXT="${{ steps.historical.outputs.context }}"

if [ "$IS_BOOTSTRAPPING" = "true" ]; then
{
Expand Down
10 changes: 2 additions & 8 deletions .github/workflows/pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,8 @@ jobs:

## Existing PR Comments to Address

If there are existing comments, address them in your review:

Review comments (on specific lines):
$(cat /tmp/review-comments.json 2>/dev/null || echo "[]")

General PR comments:
$(cat /tmp/issue-comments.json 2>/dev/null || echo "[]")

Claude Code Action provides existing PR comments in context automatically.
If there are existing comments, address them in your review.
When addressing comments, reference them: "Regarding @user's question about X: ..."

## Review Focus Areas
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ This is a **meta-repository** - it contains the SDLC Wizard documentation and au
- No package dependencies to install
- No traditional unit tests (bash scripts only)

### Test Dependencies
- `bash` (3.x+ on macOS, 4.x+ on Linux)
- `python3` with `yaml` module (used for YAML parsing in tests)
- `jq` (used for JSON processing in tests and workflows)

## Commands

| Command | Purpose |
Expand Down
2 changes: 1 addition & 1 deletion plans/AUTO_SELF_UPDATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -923,4 +923,4 @@ _Updated: 2026-02-11_

**Summary:** 6/8 items DONE, 1 SKIPPED (by design), 1 DEFERRED (research needed).

**Item 23 decision:** The only remaining blocker is mutation testing research (Item 21), which was deferred as it requires deep investigation into SDLC document mutation methodology. All other prerequisites for phased workflow re-enablement are met. User decides when to tackle Item 21 and whether it blocks re-enablement.
**Item 23 decision:** Re-enablement is blocked until the full E2E pipeline passes end-to-end in CI, confirming that evaluation, scoring, observability, and PR comments all work together in a real GitHub Actions run. Mutation testing (Item 21) remains deferred and does not block re-enablement. All prerequisite items are complete — the gate is now operational verification, not feature work.
156 changes: 156 additions & 0 deletions tests/test-workflow-triggers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -808,10 +808,166 @@ test_fail_on_regression_no_continue_on_error() {
fi
}

# ============================================
# CI Comment Safety Tests
# ============================================
# These tests ensure untrusted LLM output (criteria evidence)
# is NOT assigned via ${{ }} inline in bash (backtick injection).

# Test 45: CRITERIA is passed via env block, not inline ${{ }} in bash
test_criteria_not_inline_expanded() {
WORKFLOW="$REPO_ROOT/.github/workflows/ci.yml"

if [ ! -f "$WORKFLOW" ]; then
fail "CI workflow file not found (needed for criteria safety test)"
return
fi

# Check that CRITERIA is NOT set via inline ${{ }} in a bash variable assignment
# Bad: CRITERIA="${{ steps.eval-candidate.outputs.criteria }}"
# Good: env: CRITERIA: ${{ steps.eval-candidate.outputs.criteria }}
if grep -E 'CRITERIA="\$\{\{' "$WORKFLOW"; then
fail "CRITERIA uses inline \${{ }} expansion (backticks in LLM evidence text execute as commands)"
else
pass "CRITERIA is not inline-expanded in bash (safe from backtick injection)"
fi
}

# Test 46: Comment-building steps use env block for untrusted outputs
test_comment_steps_use_env_block() {
WORKFLOW="$REPO_ROOT/.github/workflows/ci.yml"

if [ ! -f "$WORKFLOW" ]; then
fail "CI workflow file not found (needed for env block test)"
return
fi

# Both "Build quick check comment message" and "Build full evaluation comment message"
# should have an env: block that includes CRITERIA
QUICK_HAS_ENV=false
FULL_HAS_ENV=false

# Use python for reliable multi-line YAML parsing
python3 -c "
import yaml, sys
with open('$WORKFLOW') as f:
wf = yaml.safe_load(f)
for job_name, job in wf.get('jobs', {}).items():
for step in job.get('steps', []):
name = step.get('name', '')
env = step.get('env', {})
if 'Build quick check comment message' in name:
if 'CRITERIA' in env:
print('QUICK_ENV_OK')
if 'Build full evaluation comment message' in name:
if 'CRITERIA' in env:
print('FULL_ENV_OK')
" > /tmp/env_check_result.txt 2>&1

if grep -q "QUICK_ENV_OK" /tmp/env_check_result.txt; then
QUICK_HAS_ENV=true
fi
if grep -q "FULL_ENV_OK" /tmp/env_check_result.txt; then
FULL_HAS_ENV=true
fi

if [ "$QUICK_HAS_ENV" = true ] && [ "$FULL_HAS_ENV" = true ]; then
pass "Both comment-building steps pass CRITERIA via env block (safe)"
else
if [ "$QUICK_HAS_ENV" = false ]; then
fail "Quick check comment step missing CRITERIA in env block"
fi
if [ "$FULL_HAS_ENV" = false ]; then
fail "Full evaluation comment step missing CRITERIA in env block"
fi
fi
}

test_criteria_not_inline_expanded
test_comment_steps_use_env_block

test_quick_check_comment_continue_on_error
test_quick_check_post_comment_continue_on_error
test_fail_on_regression_no_continue_on_error

# ============================================
# Full E2E Dependency Compatibility Tests
# ============================================
# These tests ensure the e2e-full-evaluation job can actually run
# when triggered by the 'labeled' event (merge-ready label).

# Test 47: e2e-full-evaluation must not depend on jobs that skip on 'labeled' events
test_full_eval_deps_run_on_labeled() {
WORKFLOW="$REPO_ROOT/.github/workflows/ci.yml"

if [ ! -f "$WORKFLOW" ]; then
fail "CI workflow file not found"
return
fi

# Parse the workflow to check that every job in e2e-full-evaluation's 'needs'
# does NOT have a condition that excludes 'labeled' events
python3 -c "
import yaml, sys
with open('$WORKFLOW') as f:
wf = yaml.safe_load(f)
jobs = wf.get('jobs', {})
full_eval = jobs.get('e2e-full-evaluation', {})
needs = full_eval.get('needs', [])
if isinstance(needs, str):
needs = [needs]

blocked = []
for dep in needs:
dep_job = jobs.get(dep, {})
condition = str(dep_job.get('if', ''))
# If the dependency skips on 'labeled' events, full-eval can never run
if \"event.action != 'labeled'\" in condition:
blocked.append(dep)

if blocked:
print('BLOCKED_BY:' + ','.join(blocked))
else:
print('DEPS_OK')
" > /tmp/full_eval_deps.txt 2>&1

if grep -q "DEPS_OK" /tmp/full_eval_deps.txt; then
pass "e2e-full-evaluation dependencies all run on 'labeled' events"
else
BLOCKERS=$(grep "BLOCKED_BY:" /tmp/full_eval_deps.txt | sed 's/BLOCKED_BY://')
fail "e2e-full-evaluation depends on jobs that skip on 'labeled': $BLOCKERS (full eval can never run)"
fi
}

test_full_eval_deps_run_on_labeled

# ============================================
# PR Review Prompt Hygiene Tests
# ============================================
# Ensure the review prompt doesn't contain shell
# constructs that won't expand in YAML strings.

# Test 48: pr-review prompt must not use $(cat ...) in YAML prompt field
test_review_prompt_no_shell_subst() {
WORKFLOW="$REPO_ROOT/.github/workflows/pr-review.yml"

if [ ! -f "$WORKFLOW" ]; then
fail "pr-review.yml file not found"
return
fi

# $(cat ...) in a YAML 'prompt: |' field is dead code —
# YAML strings don't execute shell commands.
# claude-code-action provides comments through its own mechanism.
if grep -E '\$\(cat ' "$WORKFLOW"; then
fail "pr-review.yml prompt contains \$(cat ...) — won't expand in YAML string (dead code)"
else
pass "pr-review.yml prompt has no shell command substitution in YAML strings"
fi
}

test_review_prompt_no_shell_subst

echo ""
echo "=== Results ==="
echo "Passed: $PASSED"
Expand Down