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/bin/codeharness +9 -0
- package/dist/chunk-7ZD2ZNDU.js +540 -0
- package/dist/docker-CT57JGM7.js +33 -0
- package/dist/index.js +6104 -0
- package/package.json +39 -0
- package/ralph/AGENTS.md +38 -0
- package/ralph/bridge.sh +421 -0
- package/ralph/db_schema_gen.sh +109 -0
- package/ralph/doc_gardener.sh +352 -0
- package/ralph/drivers/claude-code.sh +160 -0
- package/ralph/exec_plans.sh +252 -0
- package/ralph/harness_status.sh +156 -0
- package/ralph/lib/circuit_breaker.sh +210 -0
- package/ralph/lib/date_utils.sh +60 -0
- package/ralph/lib/timeout_utils.sh +77 -0
- package/ralph/onboard.sh +83 -0
- package/ralph/ralph.sh +1006 -0
- package/ralph/retro.sh +298 -0
- package/ralph/validate_epic_docs.sh +129 -0
- package/ralph/verify_gates.sh +241 -0
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
|