codeharness 0.6.1

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.
package/ralph/retro.sh ADDED
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/env bash
2
+ # retro.sh — Sprint Retrospective Report Generator
3
+ # Produces docs/quality/retro-report.md with sprint summary, verification
4
+ # effectiveness, and documentation health analysis.
5
+ # Must complete within 30 seconds (NFR20).
6
+ #
7
+ # Usage: ralph/retro.sh --project-dir DIR
8
+
9
+ set -e
10
+
11
+ PROJECT_DIR=""
12
+ GENERATE_COVERAGE=false
13
+ GENERATE_FOLLOWUP=false
14
+
15
+ show_help() {
16
+ cat << 'HELPEOF'
17
+ Sprint Retrospective — generate structured retro report
18
+
19
+ Usage:
20
+ ralph/retro.sh --project-dir DIR
21
+
22
+ Generates docs/quality/retro-report.md with:
23
+ 1. Sprint summary (stories, iterations, duration)
24
+ 2. Verification effectiveness (pass rates, iteration counts)
25
+ 3. Documentation health (doc-gardener findings)
26
+
27
+ Options:
28
+ --project-dir DIR Project root directory
29
+ --coverage Also generate docs/quality/test-coverage.md
30
+ -h, --help Show this help message
31
+ HELPEOF
32
+ }
33
+
34
+ while [[ $# -gt 0 ]]; do
35
+ case $1 in
36
+ -h|--help)
37
+ show_help
38
+ exit 0
39
+ ;;
40
+ --project-dir)
41
+ PROJECT_DIR="$2"
42
+ shift 2
43
+ ;;
44
+ --coverage)
45
+ GENERATE_COVERAGE=true
46
+ shift
47
+ ;;
48
+ --followup)
49
+ GENERATE_FOLLOWUP=true
50
+ shift
51
+ ;;
52
+ *)
53
+ echo "Unknown option: $1" >&2
54
+ exit 1
55
+ ;;
56
+ esac
57
+ done
58
+
59
+ if [[ -z "$PROJECT_DIR" ]]; then
60
+ echo "Error: --project-dir is required" >&2
61
+ exit 1
62
+ fi
63
+
64
+ PROGRESS_FILE="$PROJECT_DIR/ralph/progress.json"
65
+ VLOG_FILE="$PROJECT_DIR/ralph/verification-log.json"
66
+ OUTPUT_FILE="$PROJECT_DIR/docs/quality/retro-report.md"
67
+
68
+ mkdir -p "$(dirname "$OUTPUT_FILE")"
69
+
70
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
71
+
72
+ # ─── Gather data ──────────────────────────────────────────────────────────
73
+
74
+ # Sprint progress
75
+ total_stories=0
76
+ completed_stories=0
77
+ total_iterations=0
78
+
79
+ if [[ -f "$PROGRESS_FILE" ]]; then
80
+ total_stories=$(jq '.tasks | length' "$PROGRESS_FILE" 2>/dev/null || echo "0")
81
+ completed_stories=$(jq '[.tasks[] | select(.status == "complete")] | length' "$PROGRESS_FILE" 2>/dev/null || echo "0")
82
+ total_iterations=$(jq '[.tasks[].iterations // 0] | add // 0' "$PROGRESS_FILE" 2>/dev/null || echo "0")
83
+ fi
84
+
85
+ avg_iterations="N/A"
86
+ if [[ $completed_stories -gt 0 ]]; then
87
+ avg_iterations=$(echo "scale=1; $total_iterations / $completed_stories" | bc 2>/dev/null || echo "$((total_iterations / completed_stories))")
88
+ fi
89
+
90
+ # Verification log
91
+ v_total=0
92
+ v_pass=0
93
+ v_fail=0
94
+ pass_rate="N/A"
95
+
96
+ if [[ -f "$VLOG_FILE" ]]; then
97
+ v_total=$(jq '.events | length' "$VLOG_FILE" 2>/dev/null || echo "0")
98
+ v_pass=$(jq '[.events[] | select(.result == "pass")] | length' "$VLOG_FILE" 2>/dev/null || echo "0")
99
+ v_fail=$(jq '[.events[] | select(.result == "fail")] | length' "$VLOG_FILE" 2>/dev/null || echo "0")
100
+ if [[ $v_total -gt 0 ]]; then
101
+ pass_rate="$((v_pass * 100 / v_total))%"
102
+ fi
103
+ fi
104
+
105
+ # ─── Generate report ──────────────────────────────────────────────────────
106
+
107
+ {
108
+ echo "<!-- DO NOT EDIT MANUALLY — generated by retro.sh -->"
109
+ echo ""
110
+ echo "# Sprint Retrospective Report"
111
+ echo ""
112
+ echo "**Generated:** $timestamp"
113
+ echo ""
114
+
115
+ # ── Sprint Summary ──
116
+ echo "## Sprint Summary"
117
+ echo ""
118
+ echo "| Metric | Value |"
119
+ echo "|--------|-------|"
120
+ echo "| Stories completed | $completed_stories / $total_stories |"
121
+ echo "| Total verification iterations | $total_iterations |"
122
+ echo "| Average iterations per story | $avg_iterations |"
123
+ echo "| Verification checks | $v_total ($v_pass pass, $v_fail fail) |"
124
+ echo "| Pass rate | $pass_rate |"
125
+ echo ""
126
+
127
+ # ── Verification Effectiveness ──
128
+ echo "## Verification Effectiveness"
129
+ echo ""
130
+
131
+ if [[ -f "$PROGRESS_FILE" ]]; then
132
+ echo "### Per-Story Iteration Counts"
133
+ echo ""
134
+ echo "| Story | Title | Iterations | Status |"
135
+ echo "|-------|-------|------------|--------|"
136
+ jq -r '.tasks[] | "\(.id)\t\(.title)\t\(.iterations // 0)\t\(.status)"' "$PROGRESS_FILE" 2>/dev/null | \
137
+ while IFS=$'\t' read -r id title iters status; do
138
+ echo "| $id | ${title:0:40} | $iters | $status |"
139
+ done
140
+ echo ""
141
+ fi
142
+
143
+ if [[ -f "$VLOG_FILE" && $v_fail -gt 0 ]]; then
144
+ echo "### Common Failure Patterns"
145
+ echo ""
146
+ # Count failures per gate count
147
+ jq -r '.events[] | select(.result == "fail") | "gates_passed=\(.gates_passed)/\(.gates_total)"' "$VLOG_FILE" 2>/dev/null | \
148
+ sort | uniq -c | sort -rn | head -5 | while read -r count pattern; do
149
+ echo "- $count occurrences: $pattern"
150
+ done
151
+ echo ""
152
+ fi
153
+
154
+ # ── Documentation Health ──
155
+ echo "## Documentation Health"
156
+ echo ""
157
+
158
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
159
+ if [[ -f "$SCRIPT_DIR/doc_gardener.sh" ]]; then
160
+ doc_findings=$("$SCRIPT_DIR/doc_gardener.sh" --project-dir "$PROJECT_DIR" --json 2>/dev/null || echo '{"finding_count":0,"findings":[]}')
161
+ doc_count=$(echo "$doc_findings" | jq '.finding_count // 0' 2>/dev/null || echo "0")
162
+ echo "Doc-gardener findings: **$doc_count**"
163
+ echo ""
164
+
165
+ if [[ $doc_count -gt 0 ]]; then
166
+ echo "$doc_findings" | jq -r '.findings[] | "- [\(.type)] \(.message)"' 2>/dev/null
167
+ echo ""
168
+ fi
169
+ else
170
+ echo "Doc-gardener not available."
171
+ echo ""
172
+ fi
173
+
174
+ # ── Recommendations ──
175
+ echo "## Recommendations"
176
+ echo ""
177
+
178
+ if [[ $v_fail -gt 0 ]]; then
179
+ echo "- Review verification failures — $v_fail checks failed"
180
+ fi
181
+
182
+ if [[ $total_iterations -gt $((completed_stories * 2)) && $completed_stories -gt 0 ]]; then
183
+ echo "- High iteration count ($avg_iterations avg) — stories may need better AC definition"
184
+ fi
185
+
186
+ if [[ $completed_stories -lt $total_stories ]]; then
187
+ remaining=$((total_stories - completed_stories))
188
+ echo "- $remaining stories incomplete — carry forward to next sprint"
189
+ fi
190
+
191
+ if [[ $completed_stories -eq $total_stories && $total_stories -gt 0 ]]; then
192
+ echo "- All stories completed — sprint success"
193
+ fi
194
+
195
+ } > "$OUTPUT_FILE"
196
+
197
+ # ─── Coverage report ──────────────────────────────────────────────────────
198
+
199
+ if [[ "$GENERATE_COVERAGE" == "true" ]]; then
200
+ COV_FILE="$PROJECT_DIR/docs/quality/test-coverage.md"
201
+ STATE_FILE="$PROJECT_DIR/.claude/codeharness.local.md"
202
+
203
+ # Read baseline and current from state file
204
+ baseline_cov="N/A"
205
+ current_cov="N/A"
206
+ if [[ -f "$STATE_FILE" ]]; then
207
+ baseline_cov=$(grep -o 'baseline: *[0-9]*' "$STATE_FILE" 2>/dev/null | head -1 | awk '{print $2}')
208
+ current_cov=$(grep -o 'current: *[0-9]*' "$STATE_FILE" 2>/dev/null | head -1 | awk '{print $2}')
209
+ fi
210
+ baseline_cov="${baseline_cov:-N/A}"
211
+ current_cov="${current_cov:-N/A}"
212
+
213
+ {
214
+ echo "<!-- DO NOT EDIT MANUALLY — generated by retro.sh -->"
215
+ echo ""
216
+ echo "# Test Coverage Report"
217
+ echo ""
218
+ echo "**Generated:** $timestamp"
219
+ echo ""
220
+ echo "## Coverage Summary"
221
+ echo ""
222
+ echo "| Metric | Value |"
223
+ echo "|--------|-------|"
224
+ echo "| Baseline (sprint start) | ${baseline_cov}% |"
225
+ echo "| Final (sprint end) | ${current_cov}% |"
226
+
227
+ if [[ "$baseline_cov" != "N/A" && "$current_cov" != "N/A" ]]; then
228
+ delta=$((current_cov - baseline_cov))
229
+ echo "| Delta | +${delta}% |"
230
+ fi
231
+
232
+ echo ""
233
+ echo "## Per-Story Coverage Deltas"
234
+ echo ""
235
+ echo "| Story | Title | Before | After | Delta |"
236
+ echo "|-------|-------|--------|-------|-------|"
237
+
238
+ if [[ -f "$PROGRESS_FILE" ]]; then
239
+ jq -r '.tasks[] | "\(.id)\t\(.title)\t\(.coverage_delta.before // "N/A")\t\(.coverage_delta.after // "N/A")"' "$PROGRESS_FILE" 2>/dev/null | \
240
+ while IFS=$'\t' read -r id title before after; do
241
+ d=""
242
+ if [[ "$before" != "N/A" && "$after" != "N/A" && "$before" != "null" && "$after" != "null" ]]; then
243
+ d="+$((after - before))%"
244
+ fi
245
+ echo "| $id | ${title:0:30} | ${before}% | ${after}% | $d |"
246
+ done
247
+ fi
248
+ } > "$COV_FILE"
249
+
250
+ echo "[OK] Coverage report generated → $COV_FILE"
251
+ fi
252
+
253
+ # ─── Follow-up story generation ───────────────────────────────────────────
254
+
255
+ if [[ "$GENERATE_FOLLOWUP" == "true" ]]; then
256
+ FOLLOWUP_FILE="$PROJECT_DIR/docs/quality/retro-followup.md"
257
+ followup_num=0
258
+
259
+ {
260
+ echo "# Sprint Retrospective Follow-up Items"
261
+ echo ""
262
+ echo "**Generated:** $timestamp"
263
+ echo ""
264
+ echo "Review and approve items below before adding to next sprint."
265
+ echo ""
266
+ echo "| # | Type | Item | Source |"
267
+ echo "|---|------|------|--------|"
268
+
269
+ # Carry-forward: incomplete stories
270
+ if [[ -f "$PROGRESS_FILE" ]]; then
271
+ jq -r '.tasks[] | select(.status != "complete") | "\(.id)\t\(.title)"' "$PROGRESS_FILE" 2>/dev/null | \
272
+ while IFS=$'\t' read -r id title; do
273
+ followup_num=$((followup_num + 1))
274
+ echo "| $followup_num | carry-forward | Story $id: $title (pending/incomplete) | Sprint backlog |"
275
+ done
276
+ fi
277
+
278
+ # High-iteration stories: need better AC
279
+ if [[ -f "$PROGRESS_FILE" ]]; then
280
+ jq -r '.tasks[] | select(.iterations > 3) | "\(.id)\t\(.title)\t\(.iterations)"' "$PROGRESS_FILE" 2>/dev/null | \
281
+ while IFS=$'\t' read -r id title iters; do
282
+ followup_num=$((followup_num + 1))
283
+ echo "| $followup_num | story | Refine AC for $id ($title) — took $iters iterations | Retro analysis |"
284
+ done
285
+ fi
286
+
287
+ # Verification failures: enforcement gaps
288
+ if [[ -f "$VLOG_FILE" && $v_fail -gt 0 ]]; then
289
+ followup_num=$((followup_num + 1))
290
+ echo "| $followup_num | enforcement | Review $v_fail verification failures — check gate coverage | Verification log |"
291
+ fi
292
+
293
+ } > "$FOLLOWUP_FILE"
294
+
295
+ echo "[OK] Follow-up items generated → $FOLLOWUP_FILE"
296
+ fi
297
+
298
+ echo "[OK] Retro report generated → $OUTPUT_FILE"
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env bash
2
+ # validate_epic_docs.sh — Validate architectural docs at epic completion
3
+ # Checks that ARCHITECTURE.md exists and is current when all stories in an epic are verified.
4
+ #
5
+ # Usage: ralph/validate_epic_docs.sh --epic NAME --project-dir DIR --progress PATH
6
+
7
+ set -e
8
+
9
+ EPIC_NAME=""
10
+ PROJECT_DIR=""
11
+ PROGRESS_FILE=""
12
+
13
+ show_help() {
14
+ cat << 'HELPEOF'
15
+ Design-Doc Validation — validate architecture docs at epic completion
16
+
17
+ Usage:
18
+ ralph/validate_epic_docs.sh --epic NAME --project-dir DIR --progress PATH
19
+
20
+ Checks:
21
+ 1. All stories in the epic are complete
22
+ 2. ARCHITECTURE.md exists
23
+ 3. ARCHITECTURE.md was updated since the epic's code changes
24
+
25
+ Options:
26
+ --epic NAME Epic name to validate (e.g., "Epic 1: Auth")
27
+ --project-dir DIR Project root directory
28
+ --progress PATH Path to ralph/progress.json
29
+ -h, --help Show this help message
30
+ HELPEOF
31
+ }
32
+
33
+ while [[ $# -gt 0 ]]; do
34
+ case $1 in
35
+ -h|--help)
36
+ show_help
37
+ exit 0
38
+ ;;
39
+ --epic)
40
+ EPIC_NAME="$2"
41
+ shift 2
42
+ ;;
43
+ --project-dir)
44
+ PROJECT_DIR="$2"
45
+ shift 2
46
+ ;;
47
+ --progress)
48
+ PROGRESS_FILE="$2"
49
+ shift 2
50
+ ;;
51
+ *)
52
+ echo "Unknown option: $1" >&2
53
+ exit 1
54
+ ;;
55
+ esac
56
+ done
57
+
58
+ # ─── Validation ───────────────────────────────────────────────────────────
59
+
60
+ if [[ -z "$EPIC_NAME" ]]; then
61
+ echo "Error: --epic is required" >&2
62
+ exit 2
63
+ fi
64
+
65
+ if [[ -z "$PROJECT_DIR" ]]; then
66
+ echo "Error: --project-dir is required" >&2
67
+ exit 2
68
+ fi
69
+
70
+ if [[ -z "$PROGRESS_FILE" ]]; then
71
+ echo "Error: --progress is required" >&2
72
+ exit 2
73
+ fi
74
+
75
+ if [[ ! -f "$PROGRESS_FILE" ]]; then
76
+ echo "Error: progress file not found: $PROGRESS_FILE" >&2
77
+ exit 2
78
+ fi
79
+
80
+ # ─── Check 1: All stories in epic are complete ────────────────────────────
81
+
82
+ total_in_epic=$(jq --arg epic "$EPIC_NAME" \
83
+ '[.tasks[] | select(.epic == $epic)] | length' \
84
+ "$PROGRESS_FILE" 2>/dev/null || echo "0")
85
+
86
+ if [[ "$total_in_epic" == "0" ]]; then
87
+ echo "[FAIL] No stories found for epic: $EPIC_NAME" >&2
88
+ exit 1
89
+ fi
90
+
91
+ pending_in_epic=$(jq --arg epic "$EPIC_NAME" \
92
+ '[.tasks[] | select(.epic == $epic and .status != "complete")] | length' \
93
+ "$PROGRESS_FILE" 2>/dev/null || echo "0")
94
+
95
+ if [[ "$pending_in_epic" != "0" ]]; then
96
+ echo "[FAIL] Epic \"$EPIC_NAME\" is not complete — $pending_in_epic stories still pending" >&2
97
+ exit 1
98
+ fi
99
+
100
+ # ─── Check 2: ARCHITECTURE.md exists ─────────────────────────────────────
101
+
102
+ ARCH_FILE="$PROJECT_DIR/ARCHITECTURE.md"
103
+
104
+ if [[ ! -f "$ARCH_FILE" ]]; then
105
+ echo "[FAIL] ARCHITECTURE.md not found — epic \"$EPIC_NAME\" cannot be marked complete"
106
+ echo " → Create ARCHITECTURE.md documenting architectural decisions from this epic"
107
+ exit 1
108
+ fi
109
+
110
+ # ─── Check 3: ARCHITECTURE.md is current ─────────────────────────────────
111
+
112
+ # Get ARCHITECTURE.md last commit time
113
+ arch_commit_time=$(git -C "$PROJECT_DIR" log -1 --format="%ct" -- "$ARCH_FILE" 2>/dev/null || echo "0")
114
+
115
+ # Get the latest commit time across all tracked files (excluding ARCHITECTURE.md itself)
116
+ latest_code_time=$(git -C "$PROJECT_DIR" log -1 --format="%ct" -- . 2>/dev/null || echo "0")
117
+
118
+ # If code was committed after ARCHITECTURE.md, it may be stale
119
+ if [[ $latest_code_time -gt $arch_commit_time && $arch_commit_time -gt 0 ]]; then
120
+ echo "[FAIL] ARCHITECTURE.md is stale — code changed after architecture doc was last updated"
121
+ echo " Epic: $EPIC_NAME ($total_in_epic stories)"
122
+ echo " → Update ARCHITECTURE.md to reflect architectural decisions made during this epic"
123
+ exit 1
124
+ fi
125
+
126
+ # ─── All checks pass ─────────────────────────────────────────────────────
127
+
128
+ echo "[OK] Epic \"$EPIC_NAME\" — architecture docs validated ($total_in_epic stories verified)"
129
+ exit 0
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env bash
2
+ # DEPRECATED: Verification gates are now handled by the /harness-run sprint execution skill.
3
+ # This script is kept as a standalone diagnostic tool.
4
+ #
5
+ # verify_gates.sh — Verification gates for the Ralph loop
6
+ # Checks that a story has: Showboat proof, tests passing, coverage met, verification run.
7
+ # Previously called by ralph.sh after each iteration to decide: mark done or iterate again.
8
+ #
9
+ # Usage:
10
+ # ralph/verify_gates.sh --story-id ID --project-dir DIR --progress PATH
11
+ #
12
+ # Exit codes:
13
+ # 0 = all gates pass, story marked complete
14
+ # 1 = gates failed, story needs more work (iteration incremented)
15
+ # 2 = error (missing files, unknown story)
16
+
17
+ set -e
18
+
19
+ # Portable in-place sed (macOS uses -i '', Linux uses -i)
20
+ sed_i() {
21
+ if sed --version 2>/dev/null | grep -q GNU; then
22
+ sed -i "$@"
23
+ else
24
+ sed -i '' "$@"
25
+ fi
26
+ }
27
+
28
+ # ─── Arguments ────────────────────────────────────────────────────────────
29
+
30
+ STORY_ID=""
31
+ PROJECT_DIR=""
32
+ PROGRESS_FILE=""
33
+
34
+ show_help() {
35
+ cat << 'HELPEOF'
36
+ Verification Gates — check if a story is truly done
37
+
38
+ Usage:
39
+ ralph/verify_gates.sh --story-id ID --project-dir DIR --progress PATH
40
+
41
+ Gates checked:
42
+ 1. Showboat proof document exists for the story
43
+ 2. Tests have passed (session_flags.tests_passed)
44
+ 3. Coverage at 100% (session_flags.coverage_met)
45
+ 4. Verification has been run (session_flags.verification_run)
46
+
47
+ On pass: story marked complete in progress.json, session flags reset
48
+ On fail: iteration count incremented, failures listed
49
+
50
+ Options:
51
+ -h, --help Show this help message
52
+ HELPEOF
53
+ }
54
+
55
+ while [[ $# -gt 0 ]]; do
56
+ case $1 in
57
+ -h|--help)
58
+ show_help
59
+ exit 0
60
+ ;;
61
+ --story-id)
62
+ STORY_ID="$2"
63
+ shift 2
64
+ ;;
65
+ --project-dir)
66
+ PROJECT_DIR="$2"
67
+ shift 2
68
+ ;;
69
+ --progress)
70
+ PROGRESS_FILE="$2"
71
+ shift 2
72
+ ;;
73
+ *)
74
+ echo "Unknown option: $1" >&2
75
+ exit 2
76
+ ;;
77
+ esac
78
+ done
79
+
80
+ # ─── Validation ───────────────────────────────────────────────────────────
81
+
82
+ if [[ -z "$STORY_ID" || -z "$PROJECT_DIR" || -z "$PROGRESS_FILE" ]]; then
83
+ echo "[FAIL] Missing required arguments: --story-id, --project-dir, --progress" >&2
84
+ exit 2
85
+ fi
86
+
87
+ STATE_FILE="$PROJECT_DIR/.claude/codeharness.local.md"
88
+
89
+ if [[ ! -f "$STATE_FILE" ]]; then
90
+ echo "[FAIL] Harness state file not found: $STATE_FILE — harness not initialized" >&2
91
+ exit 2
92
+ fi
93
+
94
+ if [[ ! -f "$PROGRESS_FILE" ]]; then
95
+ echo "[FAIL] Progress file not found: $PROGRESS_FILE" >&2
96
+ exit 2
97
+ fi
98
+
99
+ # Check that the story exists in progress
100
+ STORY_EXISTS=$(jq -r --arg id "$STORY_ID" '.tasks[] | select(.id == $id) | .id' "$PROGRESS_FILE" 2>/dev/null)
101
+ if [[ -z "$STORY_EXISTS" || "$STORY_EXISTS" == "null" ]]; then
102
+ echo "[FAIL] Story $STORY_ID not found in $PROGRESS_FILE" >&2
103
+ exit 2
104
+ fi
105
+
106
+ # ─── Read proof path from progress ────────────────────────────────────────
107
+
108
+ PROOF_PATH=$(jq -r --arg id "$STORY_ID" \
109
+ '.tasks[] | select(.id == $id) | .verification.proof_path // ""' \
110
+ "$PROGRESS_FILE" 2>/dev/null)
111
+
112
+ if [[ -z "$PROOF_PATH" ]]; then
113
+ PROOF_PATH="verification/${STORY_ID}-proof.md"
114
+ fi
115
+
116
+ # ─── Gate Checks ──────────────────────────────────────────────────────────
117
+
118
+ FAILURES=""
119
+ GATES_PASSED=0
120
+ GATES_TOTAL=4
121
+
122
+ # Gate 1: Showboat proof exists
123
+ if [[ -f "$PROJECT_DIR/$PROOF_PATH" ]]; then
124
+ GATES_PASSED=$((GATES_PASSED + 1))
125
+ else
126
+ FAILURES="${FAILURES}\n - Showboat proof not found: $PROOF_PATH"
127
+ fi
128
+
129
+ # Gate 2: Tests passed
130
+ TESTS_PASSED=$(grep -o 'tests_passed: *[a-z]*' "$STATE_FILE" 2>/dev/null | head -1 | awk '{print $2}')
131
+ if [[ "$TESTS_PASSED" == "true" ]]; then
132
+ GATES_PASSED=$((GATES_PASSED + 1))
133
+ else
134
+ FAILURES="${FAILURES}\n - Tests not passed (tests_passed: ${TESTS_PASSED:-unknown})"
135
+ fi
136
+
137
+ # Gate 3: Coverage met
138
+ COVERAGE_MET=$(grep -o 'coverage_met: *[a-z]*' "$STATE_FILE" 2>/dev/null | head -1 | awk '{print $2}')
139
+ if [[ "$COVERAGE_MET" == "true" ]]; then
140
+ GATES_PASSED=$((GATES_PASSED + 1))
141
+ else
142
+ FAILURES="${FAILURES}\n - Test coverage not at 100% (coverage_met: ${COVERAGE_MET:-unknown})"
143
+ fi
144
+
145
+ # Gate 4: Verification run
146
+ VERIFICATION_RUN=$(grep -o 'verification_run: *[a-z]*' "$STATE_FILE" 2>/dev/null | head -1 | awk '{print $2}')
147
+ if [[ "$VERIFICATION_RUN" == "true" ]]; then
148
+ GATES_PASSED=$((GATES_PASSED + 1))
149
+ else
150
+ FAILURES="${FAILURES}\n - Verification not run (verification_run: ${VERIFICATION_RUN:-unknown})"
151
+ fi
152
+
153
+ # ─── Increment iteration count (always, on check) ─────────────────────────
154
+
155
+ increment_iteration() {
156
+ local updated
157
+ updated=$(jq --arg id "$STORY_ID" \
158
+ '(.tasks[] | select(.id == $id)).iterations = ((.tasks[] | select(.id == $id)).iterations // 0) + 1' \
159
+ "$PROGRESS_FILE" 2>/dev/null)
160
+ if [[ -n "$updated" ]]; then
161
+ echo "$updated" > "$PROGRESS_FILE"
162
+ fi
163
+ }
164
+
165
+ # ─── Verification log ─────────────────────────────────────────────────────
166
+
167
+ append_verification_log() {
168
+ local result="$1"
169
+ local log_file="$PROJECT_DIR/ralph/verification-log.json"
170
+ local timestamp
171
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
172
+
173
+ local event
174
+ event=$(jq -n \
175
+ --arg story_id "$STORY_ID" \
176
+ --arg result "$result" \
177
+ --argjson gates_passed "$GATES_PASSED" \
178
+ --argjson gates_total "$GATES_TOTAL" \
179
+ --arg timestamp "$timestamp" \
180
+ '{story_id: $story_id, result: $result, gates_passed: $gates_passed, gates_total: $gates_total, timestamp: $timestamp}')
181
+
182
+ if [[ -f "$log_file" ]]; then
183
+ log_data=$(jq --argjson event "$event" '.events += [$event]' "$log_file" 2>/dev/null)
184
+ if [[ -n "$log_data" ]]; then
185
+ echo "$log_data" > "$log_file"
186
+ fi
187
+ else
188
+ jq -n --argjson event "$event" '{events: [$event]}' > "$log_file"
189
+ fi
190
+ }
191
+
192
+ # ─── Decision ─────────────────────────────────────────────────────────────
193
+
194
+ if [[ $GATES_PASSED -eq $GATES_TOTAL ]]; then
195
+ # Log pass event
196
+ append_verification_log "pass"
197
+
198
+ # All gates pass — mark story complete
199
+ local_updated=$(jq --arg id "$STORY_ID" \
200
+ '(.tasks[] | select(.id == $id)).status = "complete"' \
201
+ "$PROGRESS_FILE" 2>/dev/null)
202
+ if [[ -n "${local_updated}" ]]; then
203
+ echo "${local_updated}" > "$PROGRESS_FILE"
204
+ fi
205
+
206
+ # Reset session flags for the next story
207
+ sed_i 's/tests_passed: true/tests_passed: false/' "$STATE_FILE"
208
+ sed_i 's/coverage_met: true/coverage_met: false/' "$STATE_FILE"
209
+ sed_i 's/verification_run: true/verification_run: false/' "$STATE_FILE"
210
+ sed_i 's/logs_queried: true/logs_queried: false/' "$STATE_FILE"
211
+
212
+ # Move exec-plan from active to completed (if exec-plans exist)
213
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
214
+ exec_plans_dir="$PROJECT_DIR/docs/exec-plans"
215
+ if [[ -f "$exec_plans_dir/active/$STORY_ID.md" && -f "$SCRIPT_DIR/exec_plans.sh" ]]; then
216
+ "$SCRIPT_DIR/exec_plans.sh" complete \
217
+ --story-id "$STORY_ID" \
218
+ --output-dir "$exec_plans_dir" \
219
+ --proof-path "$PROOF_PATH" 2>/dev/null || true
220
+ fi
221
+
222
+ echo "[PASS] Story $STORY_ID — all $GATES_TOTAL verification gates passed"
223
+ echo " → Story marked complete, session flags reset for next story"
224
+ exit 0
225
+ else
226
+ # Log fail event
227
+ append_verification_log "fail"
228
+
229
+ # Gates failed — increment iteration, report failures
230
+ increment_iteration
231
+
232
+ iterations=$(jq -r --arg id "$STORY_ID" \
233
+ '.tasks[] | select(.id == $id) | .iterations // 0' \
234
+ "$PROGRESS_FILE" 2>/dev/null)
235
+
236
+ echo "[BLOCKED] Story $STORY_ID — $GATES_PASSED/$GATES_TOTAL gates passed (iteration $iterations)"
237
+ echo -e " Failures:$FAILURES"
238
+ echo ""
239
+ echo " → Agent must fix failures and re-verify before story completion"
240
+ exit 1
241
+ fi