Event-sourced orchestration logging: JSONL events with parent relationships form a DAG for causal reconstruction of agent flows. Includes bash event emitter (jq-based) and markdown report generator.
223 lines
6.5 KiB
Bash
Executable File
223 lines
6.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# archeflow-report.sh — Generate a Markdown process report from ArcheFlow JSONL events.
|
|
#
|
|
# Usage: ./lib/archeflow-report.sh <events.jsonl> [--output <file.md>]
|
|
#
|
|
# Reads a JSONL event file and produces a structured Markdown report showing
|
|
# the full orchestration process: phases, decisions, reviews, fixes, metrics.
|
|
#
|
|
# Requires: jq
|
|
|
|
set -euo pipefail
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
echo "Usage: $0 <events.jsonl> [--output <file.md>]" >&2
|
|
exit 1
|
|
fi
|
|
|
|
EVENT_FILE="$1"
|
|
OUTPUT=""
|
|
|
|
if [[ "${2:-}" == "--output" && -n "${3:-}" ]]; then
|
|
OUTPUT="$3"
|
|
fi
|
|
|
|
if ! command -v jq &> /dev/null; then
|
|
echo "Error: jq is required but not installed." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$EVENT_FILE" ]]; then
|
|
echo "Error: Event file not found: $EVENT_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Helper: extract events by type
|
|
events_of_type() {
|
|
jq -c "select(.type == \"$1\")" "$EVENT_FILE"
|
|
}
|
|
|
|
# Extract run metadata
|
|
RUN_START=$(events_of_type "run.start" | head -1)
|
|
RUN_COMPLETE=$(events_of_type "run.complete" | head -1)
|
|
RUN_ID=$(echo "$RUN_START" | jq -r '.run_id // "unknown"')
|
|
TASK=$(echo "$RUN_START" | jq -r '.data.task // "unknown"')
|
|
WORKFLOW=$(echo "$RUN_START" | jq -r '.data.workflow // "unknown"')
|
|
TEAM=$(echo "$RUN_START" | jq -r '.data.team // "unknown"')
|
|
|
|
# Generate report
|
|
generate_report() {
|
|
cat <<HEADER
|
|
# Process Report: ${TASK}
|
|
|
|
> Auto-generated from ArcheFlow event log.
|
|
> Run: \`${RUN_ID}\` | Workflow: \`${WORKFLOW}\` | Team: \`${TEAM}\`
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
HEADER
|
|
|
|
# Overview table from run.complete
|
|
if [[ -n "$RUN_COMPLETE" ]]; then
|
|
STATUS=$(echo "$RUN_COMPLETE" | jq -r '.data.status // "unknown"')
|
|
CYCLES=$(echo "$RUN_COMPLETE" | jq -r '.data.cycles // "?"')
|
|
AGENTS=$(echo "$RUN_COMPLETE" | jq -r '.data.agents_total // "?"')
|
|
FIXES=$(echo "$RUN_COMPLETE" | jq -r '.data.fixes_total // "?"')
|
|
SHADOWS=$(echo "$RUN_COMPLETE" | jq -r '.data.shadows // "0"')
|
|
DURATION_MS=$(echo "$RUN_COMPLETE" | jq -r '.data.duration_ms // "0"')
|
|
DURATION_MIN=$(( DURATION_MS / 60000 ))
|
|
|
|
cat <<TABLE
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| **Status** | ${STATUS} |
|
|
| **PDCA Cycles** | ${CYCLES} |
|
|
| **Agents** | ${AGENTS} |
|
|
| **Fixes** | ${FIXES} |
|
|
| **Shadows** | ${SHADOWS} |
|
|
| **Duration** | ~${DURATION_MIN} min |
|
|
|
|
TABLE
|
|
fi
|
|
|
|
# Config from run.start
|
|
CONFIG=$(echo "$RUN_START" | jq -r '.data.config // empty')
|
|
if [[ -n "$CONFIG" ]]; then
|
|
echo "### Configuration"
|
|
echo '```json'
|
|
echo "$CONFIG" | jq .
|
|
echo '```'
|
|
echo ""
|
|
fi
|
|
|
|
echo "---"
|
|
echo ""
|
|
|
|
# Phase sections — iterate through phase transitions
|
|
echo "## Phases"
|
|
echo ""
|
|
|
|
CURRENT_PHASE=""
|
|
|
|
# Process all events chronologically
|
|
while IFS= read -r event; do
|
|
TYPE=$(echo "$event" | jq -r '.type')
|
|
PHASE=$(echo "$event" | jq -r '.phase')
|
|
AGENT=$(echo "$event" | jq -r '.agent // ""')
|
|
TS=$(echo "$event" | jq -r '.ts')
|
|
|
|
# Phase header on transition
|
|
if [[ "$PHASE" != "$CURRENT_PHASE" && "$TYPE" != "run.start" && "$TYPE" != "run.complete" ]]; then
|
|
CURRENT_PHASE="$PHASE"
|
|
PHASE_UPPER=$(echo "$PHASE" | tr '[:lower:]' '[:upper:]')
|
|
echo "### ${PHASE_UPPER}"
|
|
echo ""
|
|
fi
|
|
|
|
case "$TYPE" in
|
|
agent.complete)
|
|
ARCHETYPE=$(echo "$event" | jq -r '.data.archetype // .agent // "unknown"')
|
|
DURATION=$(echo "$event" | jq -r '.data.duration_ms // 0')
|
|
TOKENS=$(echo "$event" | jq -r '.data.tokens // 0')
|
|
SUMMARY=$(echo "$event" | jq -r '.data.summary // "no summary"')
|
|
ARTIFACTS=$(echo "$event" | jq -r '(.data.artifacts // []) | join(", ")')
|
|
DURATION_S=$(( DURATION / 1000 ))
|
|
|
|
echo "**${ARCHETYPE}** (${DURATION_S}s, ${TOKENS} tokens)"
|
|
echo ": ${SUMMARY}"
|
|
if [[ -n "$ARTIFACTS" ]]; then
|
|
echo ": Artifacts: ${ARTIFACTS}"
|
|
fi
|
|
echo ""
|
|
;;
|
|
|
|
decision)
|
|
WHAT=$(echo "$event" | jq -r '.data.what // "unknown"')
|
|
CHOSEN=$(echo "$event" | jq -r '.data.chosen // "unknown"')
|
|
RATIONALE=$(echo "$event" | jq -r '.data.rationale // ""')
|
|
|
|
echo "**Decision: ${WHAT}**"
|
|
echo ": Chosen: ${CHOSEN}"
|
|
if [[ -n "$RATIONALE" ]]; then
|
|
echo ": Rationale: ${RATIONALE}"
|
|
fi
|
|
|
|
# List alternatives if present
|
|
ALTS=$(echo "$event" | jq -r '(.data.alternatives // [])[] | " - ~" + .id + "~ " + .label + " — " + .reason_rejected')
|
|
if [[ -n "$ALTS" ]]; then
|
|
echo ": Rejected:"
|
|
echo "$ALTS"
|
|
fi
|
|
echo ""
|
|
;;
|
|
|
|
review.verdict)
|
|
ARCHETYPE=$(echo "$event" | jq -r '.data.archetype // .agent // "unknown"')
|
|
VERDICT=$(echo "$event" | jq -r '.data.verdict // "unknown"')
|
|
VERDICT_UPPER=$(echo "$VERDICT" | tr '[:lower:]' '[:upper:]' | tr '_' ' ')
|
|
|
|
echo "**${ARCHETYPE}** → ${VERDICT_UPPER}"
|
|
|
|
# List findings
|
|
echo "$event" | jq -r '(.data.findings // [])[] | " - [" + .severity + "] " + .description' 2>/dev/null || true
|
|
echo ""
|
|
;;
|
|
|
|
fix.applied)
|
|
SOURCE=$(echo "$event" | jq -r '.data.source // "unknown"')
|
|
FINDING=$(echo "$event" | jq -r '.data.finding // "unknown"')
|
|
FILE=$(echo "$event" | jq -r '.data.file // ""')
|
|
LINE=$(echo "$event" | jq -r '.data.line // ""')
|
|
|
|
if [[ -n "$FILE" && "$LINE" != "null" && -n "$LINE" ]]; then
|
|
echo "- **Fix** (${SOURCE}): ${FINDING} — \`${FILE}:${LINE}\`"
|
|
else
|
|
echo "- **Fix** (${SOURCE}): ${FINDING}"
|
|
fi
|
|
;;
|
|
|
|
shadow.detected)
|
|
ARCHETYPE=$(echo "$event" | jq -r '.data.archetype // "unknown"')
|
|
SHADOW=$(echo "$event" | jq -r '.data.shadow // "unknown"')
|
|
ACTION=$(echo "$event" | jq -r '.data.action // "unknown"')
|
|
|
|
echo "- **Shadow** ⚠️ ${ARCHETYPE}: ${SHADOW} → ${ACTION}"
|
|
echo ""
|
|
;;
|
|
|
|
cycle.boundary)
|
|
CYCLE=$(echo "$event" | jq -r '.data.cycle // "?"')
|
|
MAX=$(echo "$event" | jq -r '.data.max_cycles // "?"')
|
|
MET=$(echo "$event" | jq -r '.data.met // false')
|
|
NEXT=$(echo "$event" | jq -r '.data.next_action // "unknown"')
|
|
|
|
echo ""
|
|
echo "---"
|
|
echo ""
|
|
echo "**Cycle ${CYCLE}/${MAX}** — exit condition met: ${MET} → ${NEXT}"
|
|
echo ""
|
|
;;
|
|
esac
|
|
|
|
done < "$EVENT_FILE"
|
|
|
|
# Artifacts list from run.complete
|
|
if [[ -n "$RUN_COMPLETE" ]]; then
|
|
echo ""
|
|
echo "---"
|
|
echo ""
|
|
echo "## Artifacts"
|
|
echo ""
|
|
echo "$RUN_COMPLETE" | jq -r '(.data.artifacts // [])[] | "- `" + . + "`"'
|
|
fi
|
|
}
|
|
|
|
if [[ -n "$OUTPUT" ]]; then
|
|
generate_report > "$OUTPUT"
|
|
echo "Report written to: $OUTPUT" >&2
|
|
else
|
|
generate_report
|
|
fi
|