feat: add automated PDCA loop, domain adapters, cost tracking, DAG renderer
- skills/run: automated PDCA execution loop with --start-from, --dry-run - skills/artifact-routing: inter-phase artifact protocol with context injection - skills/act-phase: structured review→fix pipeline with cycle feedback - skills/domains: domain adapter system (writing, code, research) - skills/cost-tracking: per-agent cost estimation, budget enforcement - lib/archeflow-dag.sh: ASCII DAG renderer from JSONL events - lib/archeflow-report.sh: updated with DAG section, cycle diff, --dag/--summary flags
This commit is contained in:
@@ -1,26 +1,52 @@
|
||||
#!/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>]
|
||||
# Usage: ./lib/archeflow-report.sh <events.jsonl> [--output <file.md>] [--dag] [--summary]
|
||||
#
|
||||
# Reads a JSONL event file and produces a structured Markdown report showing
|
||||
# the full orchestration process: phases, decisions, reviews, fixes, metrics.
|
||||
#
|
||||
# Flags:
|
||||
# --output <file.md> Write report to file instead of stdout
|
||||
# --dag Output ONLY the ASCII DAG (for quick terminal viewing)
|
||||
# --summary Output a one-line summary (for session logs)
|
||||
#
|
||||
# Requires: jq
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <events.jsonl> [--output <file.md>]" >&2
|
||||
echo "Usage: $0 <events.jsonl> [--output <file.md>] [--dag] [--summary]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
EVENT_FILE="$1"
|
||||
OUTPUT=""
|
||||
shift
|
||||
|
||||
if [[ "${2:-}" == "--output" && -n "${3:-}" ]]; then
|
||||
OUTPUT="$3"
|
||||
fi
|
||||
OUTPUT=""
|
||||
MODE="full" # full | dag | summary
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--output)
|
||||
OUTPUT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--dag)
|
||||
MODE="dag"
|
||||
shift
|
||||
;;
|
||||
--summary)
|
||||
MODE="summary"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is required but not installed." >&2
|
||||
@@ -45,7 +71,74 @@ 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
|
||||
# --summary mode: one-line output and exit
|
||||
if [[ "$MODE" == "summary" ]]; then
|
||||
if [[ -n "$RUN_COMPLETE" ]]; then
|
||||
STATUS=$(echo "$RUN_COMPLETE" | jq -r '.data.status // "unknown"')
|
||||
CYCLES=$(echo "$RUN_COMPLETE" | jq -r '.data.cycles // "?"')
|
||||
# Handle both agents_total and agents field names
|
||||
AGENTS=$(echo "$RUN_COMPLETE" | jq -r '.data.agents_total // .data.agents // "?"')
|
||||
FIXES=$(echo "$RUN_COMPLETE" | jq -r '.data.fixes_total // .data.fixes // "?"')
|
||||
DURATION_MS=$(echo "$RUN_COMPLETE" | jq -r '.data.duration_ms // "0"')
|
||||
if [[ "$DURATION_MS" != "0" && "$DURATION_MS" != "null" ]]; then
|
||||
DURATION_MIN=$(( DURATION_MS / 60000 ))
|
||||
echo "[${STATUS}] ${TASK} — ${CYCLES} cycles, ${AGENTS} agents, ${FIXES} fixes (~${DURATION_MIN}min) [${RUN_ID}]"
|
||||
else
|
||||
echo "[${STATUS}] ${TASK} — ${CYCLES} cycles, ${AGENTS} agents, ${FIXES} fixes [${RUN_ID}]"
|
||||
fi
|
||||
else
|
||||
echo "[in-progress] ${TASK} [${RUN_ID}]"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --dag mode: output DAG and exit
|
||||
if [[ "$MODE" == "dag" ]]; then
|
||||
if [[ -x "${SCRIPT_DIR}/archeflow-dag.sh" ]]; then
|
||||
"${SCRIPT_DIR}/archeflow-dag.sh" "$EVENT_FILE" "$@"
|
||||
else
|
||||
echo "Error: archeflow-dag.sh not found at ${SCRIPT_DIR}/archeflow-dag.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Full report mode ---
|
||||
|
||||
# Collect cycle data for cycle diff section
|
||||
CYCLE_BOUNDARIES=$(events_of_type "cycle.boundary" | jq -r '.data.cycle' 2>/dev/null || true)
|
||||
CYCLE_COUNT=0
|
||||
if [[ -n "$CYCLE_BOUNDARIES" ]]; then
|
||||
CYCLE_COUNT=$(echo "$CYCLE_BOUNDARIES" | grep -c '[0-9]' 2>/dev/null || true)
|
||||
CYCLE_COUNT=${CYCLE_COUNT:-0}
|
||||
fi
|
||||
|
||||
# Collect review findings per cycle for diff
|
||||
# A cycle's reviews are between two cycle.boundary events (or between start and first boundary)
|
||||
collect_cycle_findings() {
|
||||
# Returns JSON array of {cycle, archetype, findings[]} for all review.verdict events
|
||||
jq -s '
|
||||
# Assign cycle number to each event based on cycle.boundary positions
|
||||
(
|
||||
[.[] | select(.type == "cycle.boundary") | .seq] | sort
|
||||
) as $boundaries |
|
||||
[.[] | select(.type == "review.verdict")] |
|
||||
[.[] | {
|
||||
seq: .seq,
|
||||
archetype: (.data.archetype // .agent // "unknown"),
|
||||
verdict: .data.verdict,
|
||||
findings: (.data.findings // []),
|
||||
cycle: (
|
||||
.seq as $s |
|
||||
if ($boundaries | length) == 0 then 1
|
||||
else
|
||||
([1] + [$boundaries | to_entries[] | select(.value < $s) | .key + 2] | max)
|
||||
end
|
||||
)
|
||||
}]
|
||||
' "$EVENT_FILE"
|
||||
}
|
||||
|
||||
generate_report() {
|
||||
cat <<HEADER
|
||||
# Process Report: ${TASK}
|
||||
@@ -63,11 +156,17 @@ HEADER
|
||||
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 // "?"')
|
||||
# Handle both agents_total and agents field names
|
||||
AGENTS=$(echo "$RUN_COMPLETE" | jq -r '.data.agents_total // .data.agents // "?"')
|
||||
FIXES=$(echo "$RUN_COMPLETE" | jq -r '.data.fixes_total // .data.fixes // "?"')
|
||||
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 ))
|
||||
if [[ "$DURATION_MS" != "0" && "$DURATION_MS" != "null" ]]; then
|
||||
DURATION_MIN=$(( DURATION_MS / 60000 ))
|
||||
DURATION_DISPLAY="~${DURATION_MIN} min"
|
||||
else
|
||||
DURATION_DISPLAY="n/a"
|
||||
fi
|
||||
|
||||
cat <<TABLE
|
||||
| Field | Value |
|
||||
@@ -77,7 +176,7 @@ HEADER
|
||||
| **Agents** | ${AGENTS} |
|
||||
| **Fixes** | ${FIXES} |
|
||||
| **Shadows** | ${SHADOWS} |
|
||||
| **Duration** | ~${DURATION_MIN} min |
|
||||
| **Duration** | ${DURATION_DISPLAY} |
|
||||
|
||||
TABLE
|
||||
fi
|
||||
@@ -95,6 +194,21 @@ TABLE
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
# Process Flow (DAG)
|
||||
echo "## Process Flow"
|
||||
echo ""
|
||||
echo '```'
|
||||
if [[ -x "${SCRIPT_DIR}/archeflow-dag.sh" ]]; then
|
||||
"${SCRIPT_DIR}/archeflow-dag.sh" "$EVENT_FILE" --no-color
|
||||
else
|
||||
echo "(DAG renderer not available)"
|
||||
fi
|
||||
echo '```'
|
||||
echo ""
|
||||
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
# Phase sections — iterate through phase transitions
|
||||
echo "## Phases"
|
||||
echo ""
|
||||
@@ -183,7 +297,7 @@ TABLE
|
||||
SHADOW=$(echo "$event" | jq -r '.data.shadow // "unknown"')
|
||||
ACTION=$(echo "$event" | jq -r '.data.action // "unknown"')
|
||||
|
||||
echo "- **Shadow** ⚠️ ${ARCHETYPE}: ${SHADOW} → ${ACTION}"
|
||||
echo "- **Shadow** ${ARCHETYPE}: ${SHADOW} → ${ACTION}"
|
||||
echo ""
|
||||
;;
|
||||
|
||||
@@ -203,6 +317,65 @@ TABLE
|
||||
|
||||
done < "$EVENT_FILE"
|
||||
|
||||
# Cycle Comparison section (only if multiple cycles detected)
|
||||
if [[ "$CYCLE_COUNT" -ge 2 ]]; then
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "## Cycle Comparison"
|
||||
echo ""
|
||||
|
||||
# Collect all review findings with cycle assignment
|
||||
CYCLE_FINDINGS=$(collect_cycle_findings)
|
||||
|
||||
# Get unique cycle numbers
|
||||
CYCLE_NUMS=$(echo "$CYCLE_FINDINGS" | jq -r '[.[].cycle] | unique | .[]')
|
||||
|
||||
# Compare consecutive cycles
|
||||
PREV_CYCLE=""
|
||||
for CURR_CYCLE in $CYCLE_NUMS; do
|
||||
if [[ -n "$PREV_CYCLE" ]]; then
|
||||
echo "### Cycle ${PREV_CYCLE} → Cycle ${CURR_CYCLE}"
|
||||
echo ""
|
||||
|
||||
# Get findings for each cycle as JSON arrays
|
||||
PREV_FINDINGS=$(echo "$CYCLE_FINDINGS" | jq --argjson c "$PREV_CYCLE" \
|
||||
'[.[] | select(.cycle == $c) | .findings[] | {desc: .description, sev: .severity}]' 2>/dev/null || echo "[]")
|
||||
CURR_FINDINGS=$(echo "$CYCLE_FINDINGS" | jq --argjson c "$CURR_CYCLE" \
|
||||
'[.[] | select(.cycle == $c) | .findings[] | {desc: .description, sev: .severity}]' 2>/dev/null || echo "[]")
|
||||
|
||||
# Compute new, resolved, and persistent findings
|
||||
DIFF_OUTPUT=$(jq -rn --argjson prev "$PREV_FINDINGS" --argjson curr "$CURR_FINDINGS" '
|
||||
def descs: [.[].desc];
|
||||
($prev | descs) as $pd |
|
||||
($curr | descs) as $cd |
|
||||
($curr | [.[] | select(.desc as $d | $pd | all(. != $d))]) as $new |
|
||||
($prev | [.[] | select(.desc as $d | $cd | all(. != $d))]) as $resolved |
|
||||
($curr | [.[] | select(.desc as $d | $pd | any(. == $d))]) as $persistent |
|
||||
(
|
||||
(if ($new | length) > 0 then
|
||||
["**New findings:**"] + [$new[] | "- [" + .sev + "] " + .desc]
|
||||
else [] end) +
|
||||
(if ($resolved | length) > 0 then
|
||||
["", "**Resolved findings:**"] + [$resolved[] | "- [" + .sev + "] " + .desc]
|
||||
else [] end) +
|
||||
(if ($persistent | length) > 0 then
|
||||
["", "**Persistent findings:**"] + [$persistent[] | "- [" + .sev + "] " + .desc]
|
||||
else [] end)
|
||||
) | .[]
|
||||
' 2>/dev/null || true)
|
||||
|
||||
if [[ -n "$DIFF_OUTPUT" ]]; then
|
||||
echo "$DIFF_OUTPUT"
|
||||
else
|
||||
echo "(No findings to compare)"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
PREV_CYCLE="$CURR_CYCLE"
|
||||
done
|
||||
fi
|
||||
|
||||
# Artifacts list from run.complete
|
||||
if [[ -n "$RUN_COMPLETE" ]]; then
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user