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:
2026-04-03 11:20:14 +02:00
parent 1753e69a9f
commit b6df3d19fd
7 changed files with 2259 additions and 12 deletions

View File

@@ -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 ""