retro
Skill7 starsChanged 27 days agoMITView on GitHub
Engineering retrospective from git metrics. Reports deployment frequency, release cycle span, churn hotspots, backlog health. Outputs narrative report with 3+ actionable items. Flags: --since, --path, explicit range argument.
193 other projects use a version of this.
Adopted by
Most recently taken up by
- rodeyseijkens/skillstheir own version, first seen Nov 8, 2026
- Yesterday-AI/ytstacktheir own version, first seen Nov 8, 2026
- gaoyu06/harnesstheir own version, first seen Nov 8, 2026
- Schepetkov/claude-skills-game-UEtheir own version, first seen Nov 8, 2026
- tornikegomareli/Skillstheir own version, first seen Nov 8, 2026
- devjiwonchoi/skillstheir own version, first seen Nov 8, 2026
Used with
Projects with it also use plan-tune, ios-clean and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
ShreyRaviSkillplan-tune
Self-tuning question sensitivity + developer psychographic for gstack (v1: observational). Review which AskUserQuestion prompts fire across gstack skills, set per-question preferences (never-ask / always-ask / ask-only-for-one-way), inspect the dual-track profile (what you declared vs what your behavior suggests), and enable/disable question tuning. Conversational interface — no CLI syntax required.
Also in 9 projects that use this
Saqlain-811Skillios-clean
Remove the DebugBridge SPM package and all #if DEBUG wiring from an iOS app. (gstack)
Also in 5 projects that use this
Saqlain-811Skillios-fix
Autonomous iOS bug fixer. (gstack)
Also in 5 projects that use this
seek888Skillpair-agent
Pair a remote AI agent with your browser. One command generates a setup key and prints instructions the other agent can follow to connect. Works with OpenClaw, Hermes, Codex, Cursor, or any agent that can make HTTP requests. The remote agent gets its own tab with scoped access (read+write by default, admin on request). Use when asked to "pair agent", "connect agent", "share browser", "remote browser", "let another agent use my browser", or "give browser access". (gstack) Voice triggers (speech-to-text aliases): "pair agent", "connect agent", "share my browser", "remote browser access".
Also in 11 projects that use this
ShreyRaviSkilllanding-report
Read-only queue dashboard for workspace-aware ship. Shows which VERSION slots are currently claimed by open PRs, which sibling Conductor workspaces have WIP work likely to ship soon, and what slot /ship would pick next. No mutations — just a snapshot. Use when asked to "landing report", "what's in the queue", "show me open PRs", or "which version do I claim next". (gstack)
Also in 8 projects that use this
timurgaleevSkilldocument-generate
Generate missing docs for a feature, module or project as tutorials, how-tos, reference and explanation.
Also in 7 projects that use this
- +1 more from Saqlain-811
- +2 more from ShreyRavi
Other versions193 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 193
- mattpocock/skills★ 309ksame name
- garrytan/gstack★ 141ksame name
- formbricks/formbricks★ 13ksame name
- ArcReel/ArcReel★ 6.3ksame name
- vinvcn/mattpocock-skills-zh-CN★ 5.5ksame name
- withcoral/coral★ 4.9ksame name
- bergside/awesome-design-skills★ 2.9ksame name
- MiniMax-AI/minimax-code★ 2.2ksame name
- DenchHQ/DenchClaw★ 1.7ksame name
- DenchHQ/ironclaw★ 1.7ksame name
183 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository58 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- a11y-auditSkill
- agent-benchmarkSkill
- api-auditSkill
- architectureSkill
- backlogSkill
- benchmarkSkill
- brainstormSkill
- buildSkill
- canarySkill
- ci-auditSkill
- code-auditSkill
- container-auditSkill
- content-auditSkill
- content-expandSkill
- content-fixSkill
- content-migrationSkill
- context-auditSkill
- db-auditSkill
- debugSkill
- dependency-auditSkill
- deploySkill
- design-reviewSkill
- designSkill
- docsSkill
- env-auditSkill
- executeSkill
- fix-testsSkill
- geo-auditSkill
- geo-fixSkill
- incidentSkill
- infra-auditSkill
- leadsSkill
- mutation-testSkill
- pentestSkill
- performance-auditSkill
- planSkill
- presentationSkill
- profile-sessionSkill
- receive-reviewSkill
- refactor-radarSkill
- refactorSkill
- release-docsSkill
- reviewSkill
- security-auditSkill
- seo-auditSkill
- seo-fixSkill
- shipSkill
- skill-evalSkill
- structure-auditSkill
- test-auditSkill
- tests-performanceSkill
- ui-design-teamSkill
- using-zuvoSkill
- worktreeSkill
- write-articleSkill
- write-e2eSkill
- write-testsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
25 rules. 0 are common, 10 are distinctive, and 15 are very specific to this project.
0
Common
Most of the 171 edited versions we read keep these.
10
Distinctive
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 34
Read ../../shared/includes/env-compat.md for agent dispatch patterns, path resolution, and progress tracking across all supported platforms.
- Distinctive: line 65
If commit count is < 10: output "Insufficient history for statistical metrics" and produce a qualitative-only report.
- Distinctive: line 69
Check for monorepo signals in the project root:
- Very specific: line 74
If any of these files exist AND no --path argument was provided:
- Very specific: line 76
If only ONE package was touched: auto-scope to that package with [AUTO-DECISION]: scoped to <package>.
- Distinctive: line 77
If multiple packages were touched:
- Very specific: line 81
If --path was provided: scope all git log commands with -- <path> and note the scope in the report.
- Very specific: line 87
Run these commands against the resolved <range> (append -- <path> if --path was provided).
- Very specific: line 104
If no release tag pattern can be identified, report: Deployment frequency unavailable — no reliable release tag pattern detected
- Very specific: line 134
Read memory/backlog.md if it exists — resolved per ../../shared/includes/backlog-protocol.md "Where the Backlog Lives" (the checkout's own …
- Very specific: line 136
If the file does not exist: note "No backlog tracked.
- Distinctive: line 138
If the file exists, count:
- Very specific: line 150
Read the skill usage log using the environment-aware path from run-logger.md (primary: ~/.zuvo/runs.log, Codex App fallback: …
- Distinctive: line 152
If the file does not exist at either path: note "No skill usage history found." Skip this section.
- Very specific: line 154
If the file exists, parse each line by splitting on tab characters (\t):
- Very specific: line 180
Read the per-task telemetry file zuvo:execute appends at Step 9b — one JSON line per finished task, schema documented in …
- Very specific: line 192
If the file does not exist: note "No per-task telemetry found." Skip this section — the same degradation shape Phase 3 already uses for a …
- Distinctive: line 195
If the file exists, parse it line by line.
- Very specific: line 476
If fewer than 3 items can be derived from the data, supplement with: "Run zuvo:code-audit .
- Distinctive: line 484
Create the report using a readable suffix:
- Very specific: line 485
Prefer tag-based names when previousTag / newTag are known: zuvo/reports/retro-YYYY-MM-DD-<previousTag>_<newTag>.md
- Distinctive: line 492
Use this exact structure:
- Very specific: line 537
Check for existing zuvo/reports/retro-*.md files.
- Distinctive: line 547
If no prior retro exists: note "First retro — run again next period for trends."
- Distinctive: line 572
If E13 (insufficient history) was triggered, show:
“Common” is measured: kept by at least 6 in 10 of the 171 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
zuvo:retro
Generate an engineering retrospective from git history, backlog state, and skill usage trends. Produces a narrative report with actionable items.
Scope: Post-release or periodic retrospective covering shipping velocity, code churn, backlog health, and quality trends. Out of scope: Actually fixing issues (use the suggested zuvo commands in the Actionable Items section).
Argument Parsing
| Input | Action |
| (empty) | Auto-detect window from memory/last-ship.json or last two git tags |
<range> | Explicit git range, e.g., v1.0.0..v1.2.0 |
--since <tag> | Start of retrospective window (open-ended, through HEAD) |
--path <dir> | Scope git analysis to a subdirectory (required for monorepos) |
Environment Compatibility
Dispatch follows ../../shared/includes/execution-policy.md through env-compat. Reuse existing
authorization within that policy; session restrictions take precedence. Run each required gate
and report its actual independence or an unmet requirement.
Read ../../shared/includes/env-compat.md for agent dispatch patterns, path resolution, and progress tracking across all supported platforms.
Mandatory File Loading
CORE FILES LOADED:
1. ../../shared/includes/env-compat.md -- READ
2. ../../shared/includes/run-logger.md -- READBoth files must be read before proceeding. If either is missing, note "DEGRADED -- [file] unavailable" in the report and continue with reduced capability.
Phase 0: Window Detection
Determine the retrospective window in this priority order:
- Explicit argument — if
<range>was provided (e.g.,v1.0.0..v1.2.0), use it directly. --since <tag>— if provided, window is<tag>..HEAD. No--untilflag is defined.memory/last-ship.json— read the file if it exists. Use therangefield (SHA-based, e.g.,"abc1234..def5678"). If the artifact uses a legacy version-based range, fall back to it.- Last two git tags — run
git describe --tags --abbrev=0twice (with--excludeon first result) to derive<prev-tag>..<latest-tag>. - Fallback — if fewer than two tags exist, use the last 30 commits: derive range as
HEAD~30..HEAD.
Count commits in window:
git log --oneline <range> | wc -lEdge Case E13 — Insufficient History
If commit count is < 10: output "Insufficient history for statistical metrics" and produce a qualitative-only report. Skip Phases 1 and 3 (no git metrics, no skill trends to aggregate). Use Phase 2 (backlog health) as the sole data source. Still produce Phase 4 actionable items and Phase 5 report.
Edge Case E14 — Monorepo Detection
Check for monorepo signals in the project root:
turbo.jsonnx.jsonpnpm-workspace.yaml
If any of these files exist AND no --path argument was provided:
- First, try auto-detection: run
git diff --name-only <range>and extract unique top-level directories matching workspace package paths. - If only ONE package was touched: auto-scope to that package with
[AUTO-DECISION]: scoped to <package>. - If multiple packages were touched:
- Interactive: list the affected packages and ask the user to pick one or confirm full-repo analysis.
- Non-interactive:
[AUTO-DECISION]: monorepo detected, analyzing all affected packages. Results may be noisy — re-run with --path for focused analysis.Proceed with full-repo analysis.
If --path was provided: scope all git log commands with -- <path> and note the scope in the report.
Phase 1: Git Metrics
Run these commands against the resolved <range> (append -- <path> if --path was provided).
Deployment Frequency
Count release tags within the window, not arbitrary tags.
Counting method:
git tag --sort=creatordate --merged HEAD | grep -E '^v[0-9]' | while read tag; do
TAG_DATE=$(git log -1 --format="%ai" "$tag")
# keep only tags whose date falls within the window
donePriority for identifying release tags:
- Tags matching a semver-like pattern (
v*.*.*,v[0-9]*) - Tags referenced by
memory/last-ship.json(newTag,previousTag) — cross-reference, not sole source - If no release tag pattern can be identified, report:
Deployment frequency unavailable — no reliable release tag pattern detected
Ignore unrelated tags (experimental, package-local, scratch tags). memory/last-ship.json covers only the most recent release; for multi-release windows, the git tag scan is the primary data source.
Release Cycle Span
Estimate the time span covered by the release window:
git log --format="%ai %H" <range>Use the earliest commit date in the window as the start and the latest tag date as the end. Report as: N days (earliest commit → latest tag). Note: this is the window span, not per-PR lead time — exact branch-to-merge lead time requires per-PR analysis with gh pr list.
Churn Hotspots
Find the top 5 most-changed files in the window:
git log --name-only --pretty=format: <range> | sort | uniq -c | sort -rn | head -5Git, deliberately — not CodeSift analyze_hotspots. A retrospective must run on any
checkout, including one with no index and no MCP server, and churn-by-commit-count is
exactly what this section reports. The frontmatter used to declare analyze_hotspots as
"KEY" alongside nine other tools that no step ever called; compute-preload reads that
field, so it was instructing agents to load ten tools for a skill that uses none.
Filter out empty lines. Report as a ranked list: filename (N changes).
Phase 2: Backlog Health
Read memory/backlog.md if it exists — resolved per ../../shared/includes/backlog-protocol.md "Where the Backlog Lives" (the checkout's own copy when git tracks it, else the MAIN checkout root, where a CWD-relative read from a linked worktree silently sees no backlog).
If the file does not exist: note "No backlog tracked. Run zuvo:review or zuvo:code-audit to populate." Skip all backlog metrics.
If the file exists, count:
- Open items — rows where Status =
OPEN - Critical items — OPEN rows where Severity =
CRITICAL - Resolved in window — rows where Status =
RESOLVEDand the Resolved/Updated date (not Added date) falls within the window. If backlog.md has no Resolved date column, skip this metric and note: "Resolved-in-window metric unavailable — backlog lacks Resolved date column." - Added in window — rows where Added date falls within the retrospective window
Also identify the oldest unresolved item (earliest Added date among OPEN rows) and report its ID and age in days.
Phase 3: Skill Usage Trends
Read the skill usage log using the environment-aware path from run-logger.md (primary: ~/.zuvo/runs.log, Codex App fallback: memory/zuvo-runs.log).
If the file does not exist at either path: note "No skill usage history found." Skip this section.
If the file exists, parse each line by splitting on tab characters (\t):
- Lines with 10 tab characters (11 fields): populate all fields:
DATE SKILL PROJECT CQ_SCORE Q_SCORE VERDICT TASKS DURATION NOTES BRANCH HEAD_SHA7 - Lines with 8 tab characters (9 fields): populate the first 9 fields as above, treat BRANCH and HEAD_SHA7 as
- - Lines that match neither pattern (e.g., pipe-delimited legacy entries like
2026-03-31T05:20:44Z | SKILL: brainstorm | ...): skip silently
DATE parsing: handle both T...Z (with Z suffix) and T... (without Z) formats as UTC.
Filter to entries where:
PROJECTmatches the current project directory basenameDATEfalls within the retrospective window
From the filtered entries, aggregate:
- Most-used skills — rank by frequency
- Average CQ score — parse
CQ_SCOREfield, skip-entries, average the numerator/denominator separately - Average Q score — same approach as CQ
- Pass/fail ratio — count PASS vs FAIL vs WARN verdicts
- Branch distribution: [branch: N runs, ...] (e.g.,
main: 8, feature/x: 2) — only display when at least one 11-field entry exists in the filtered window
Phase 3b: Per-Task Telemetry (optional)
Read the per-task telemetry file zuvo:execute appends at Step 9b — one JSON line per finished
task, schema documented in ../../shared/includes/session-state.md →
zuvo/context/task-telemetry.jsonl. This is a project-local, per-task counterpart to Phase 3's
HOME-global ~/.zuvo/runs.log: gate-failure counts, reviewer-route distribution, and retry hotspots
that Phase 3's one-line-per-run log cannot surface.
Resolve the file the same way the writer does: $ZUVO_OUTPUT_DIR, else <git root>/zuvo, then
context/task-telemetry.jsonl under it — no pwd fallback (deliberately removed from the writer;
do not reintroduce one here). No project-name filter — unlike runs.log (shared across every
project on the machine), this file is project-local by construction, so filtering would be wrong,
not merely redundant.
If the file does not exist: note "No per-task telemetry found." Skip this section — the same
degradation shape Phase 3 already uses for a missing runs.log.
If the file exists, parse it line by line. A blank or whitespace-only physical line — the normal
trailing newline of an append-only file, not a corrupt one — is not a record and is excluded from
BOTH counts below; every non-blank line is then either fully counted in records= or fully
counted in skipped=, never dropped in between. A kill between write and fsync can leave a
truncated trailing line, and this file is never rewritten to repair one (see session-state.md's
"Reader contract").
Three corruption shapes must each become a counted skip, because each of them aborts a naive
reader outside a try that wraps only json.loads, and an abort discards every record already
parsed — uncounted in both records= and skipped=, which is precisely the silent data loss the
Reader contract forbids:
- Unparseable text — plain garbage;
json.loadsraisesValueError. - Valid JSON that is not an object —
null, a bare number,[]. These parse fine and then raiseAttributeErrorat the first.get, so the guard must wrap the whole per-line body, not just the parse, and must reject a non-dictexplicitly. - A truncated multi-byte UTF-8 tail — the exact "crash between
writeandfsync" case. Text mode decodes eagerly, soUnicodeDecodeErroris raised by the iteration itself, before any per-linetryis reached. Read the file in binary and decode each line defensively.
Reading is also this reader's own responsibility, not the shell's: an unreadable/vanished path and
any other failure are reported by the reader with its partial counts intact. The || echo "[WARN]"
tail stays as a last resort, not the normal error path.
From the well-formed records, aggregate:
- Gate-failure counts — a gate field (
spec-review,quality-review,adversarial) lands in one of three states, never a plain pass/fail: failed (present, a string, and not the passing value), missing (absent,null, or any non-string JSON value — a bool/number/nested object stringified into"True"/"3"is malformed input, not a real verdict, and must never be compared as one), or passing.gate-failures=counts only the failed state;gate-missing=reports the missing state separately, so a pre-schema or truncated record can never inflate the failure tally. ThePASSprefix match onquality-review/adversarialis case-sensitive by contract — the writer always emits the literal uppercase form, so a lowercased value is itself a sign of a hand-edited or corrupt record and must fail, not silently pass. - Reviewer-route distribution — tally by
reviewer-route(cross-vendor,review-primary,review-alt,in-family-fallback,same-model-fallback,routing-failed).cross-vendoris the only clean route on a Claude/Codex host;in-family-fallbackcounts reviews that ran degraded on the writer's own vendor. The cutover is ONE instant, compared on each record'sat(ISO-8601 UTC, whole seconds; a fractional second is accepted and ignored): a record withatearlier than2026-09-28T15:23:38Z(commit 83b6b48c, the cross-vendor router) carries the older vocabulary, wherereview-primary/review-altwere in-family routes on Claude/Codex too. The reader below applies it: such a record is tallied underlegacy:<value>, one with no validatunderundated:<value>, every other record under its value — never re-mapped, and never merged with the new vocabulary. - Implementer-status tally (blocked/skipped reasons) — tally by
implementer-status(DONE,DONE_WITH_CONCERNS,NEEDS_CONTEXT,BLOCKED). - Failure-strategy distribution — bucketed into
halt,skip-and-continue,degraded,unknownandmissing. Two rules matter here:missingis its own bucket, never folded intohalt. The writer always emits the field, so an absent one means an OLD or CORRUPT record — reporting it ashaltwould present silence as a deliberate decision.degraded:<desc>collapses to onedegradedbucket, withdegraded-distinct-descriptions=<N>printed alongside — capped at 64 distinct descriptions tracked for that count; past the cap, further distinct text is tallied as an approximate(+N more)overflow instead of growing the tracked set without bound.<desc>is free text; keying the tally by it yields one entry per task on a long run — a distribution with no signal.
# >>> zuvo:retro-telemetry
RT_DIR="${ZUVO_OUTPUT_DIR:-}"
if [ -z "$RT_DIR" ]; then
_RT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [ -n "$_RT_ROOT" ]; then RT_DIR="$_RT_ROOT/zuvo"; fi
fi
RT_PATH="${RT_DIR:+$RT_DIR/context/task-telemetry.jsonl}"
if [ -z "$RT_PATH" ] || [ ! -f "$RT_PATH" ]; then
echo "No per-task telemetry found."
else
python3 - "$RT_PATH" <<'PY' \
|| echo "[WARN] per-task telemetry read failed — continuing (diagnostic, never a gate)"
import json, re, sys
# SCHEMA SSOT — every `F_* = "<key>"` below is a telemetry field name this reader
# touches, and they are declared HERE, once. Case (y) of tests/skill-suite/test-
# task-telemetry-contract.sh checks these literals against the
# `zuvo:telemetry-schema` table in shared/includes/session-state.md — the same
# discipline the WRITER's `K = [...]` follows, but as CONTAINMENT rather than the
# writer's equality diff, because this reader deliberately touches only a subset
# of the documented keys. Without that check a schema rename is invisible here:
# `rec.get(key, default)` never raises on a renamed key, so retro would report
# 100% gate failure forever and nothing would say so. Never inline a field name at
# a use site — the test forbids a literal `.get("<key>"` here for that reason.
F_SPEC = "spec-review"
F_QUALITY = "quality-review"
F_ADVERSARIAL = "adversarial"
F_ROUTE = "reviewer-route"
F_AT = "at"
F_STATUS = "implementer-status"
F_STRATEGY = "failure-strategy"
# Bound on distinct free-text `degraded:<desc>` tails tracked for the
# degraded-distinct-descriptions count below. Free text is author-controlled
# and a long-lived file could carry many distinct ones; past this many the
# reader stops trying to dedup exactly and reports the remainder as an
# approximate "+N more" overflow instead of growing the tracked set forever.
DEGRADED_DESC_CAP = 64
# The cross-vendor router's cutover — commit 83b6b48c, committed 2026-09-28T15:23:38Z (UTC). Before it,
# `review-primary`/`review-alt` were in-family routes on Claude/Codex hosts too; after it they are other
# hosts' clean routes. A record whose `at` is earlier is tallied under `legacy:<value>`, one with no valid
# `at` under `undated:<value>`: the two vocabularies are never merged into one count. `at` is the writer's
# ISO-8601 UTC shape, to the second or with a fractional second (`…:38.123Z` — some writers keep the
# sub-second part). It is compared by its first 19 characters, the whole seconds, so a string comparison
# orders it: with the fraction left in, `.` sorts before `Z` and a record written half a second AFTER the
# cutover would read as before it.
# Known limit: the split goes by WHEN a record was written, not by which router wrote it (records carry no
# router version). A record written after the cutover on a machine still running an older install means
# the old in-family route yet is counted as a clean one, so read the days right after a release as mixed.
ROUTE_CUTOVER = "2026-09-28T15:23:38Z"
path = sys.argv[1]
n = 0
skipped = 0
gate = {F_SPEC: 0, F_QUALITY: 0, F_ADVERSARIAL: 0}
gate_missing = {F_SPEC: 0, F_QUALITY: 0, F_ADVERSARIAL: 0}
reviewer_route = {}
implementer_status = {}
failure_strategy = {}
degraded_descs = set()
degraded_overflow = 0
def bump(tally, key):
tally[key] = tally.get(key, 0) + 1
def enum_str(rec, key):
val = rec.get(key)
return val if isinstance(val, str) and val else "unknown"
def route_key(rec):
route = enum_str(rec, F_ROUTE)
at = rec.get(F_AT)
if not isinstance(at, str) or not re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z", at):
return "undated:" + route
return ("legacy:" + route) if at[:19] + "Z" < ROUTE_CUTOVER else route
def gate_status(rec, key, mode):
# Same isinstance/non-empty guard as strategy_bucket() below: an ABSENT
# field and a non-string field (a bool/int/nested value that str() would
# otherwise stringify into a fake "True"/"3" verdict) are BOTH "missing",
# never "failed" — a pre-schema or truncated record must not inflate the
# failure tally, and a non-string value must never be compared as a verdict.
val = rec.get(key)
if not isinstance(val, str) or not val:
return "missing"
if mode == "exact":
return "passed" if val == "COMPLIANT" else "failed"
# mode == "prefix": case-sensitive BY CONTRACT, not an oversight — the
# writer always emits the literal uppercase "PASS ..." form (schema in
# session-state.md), so a lowercased/mixed-case value is itself a sign of
# a hand-edited or corrupt record and must fail, never silently pass.
return "passed" if val.startswith("PASS") else "failed"
def strategy_bucket(rec):
# An ABSENT failure-strategy is NOT a declared `halt`: the writer always emits
# the field, so absence means an old or corrupt record. `missing` keeps silence
# from being reported as a decision. `degraded:<desc>` is BUCKETED rather than
# keyed by its free-text description (one key per task is not a distribution);
# the distinct-description count is printed alongside so variety is not lost.
val = rec.get(F_STRATEGY)
if not isinstance(val, str) or not val:
return "missing", None
if val in ("halt", "skip-and-continue"):
return val, None
if val.startswith("degraded:"):
return "degraded", val[len("degraded:"):]
return "unknown", None
def emit():
print("records=%d skipped=%d" % (n, skipped))
print("gate-failures spec-review=%d quality-review=%d adversarial=%d"
% (gate[F_SPEC], gate[F_QUALITY], gate[F_ADVERSARIAL]))
print("gate-missing spec-review=%d quality-review=%d adversarial=%d"
% (gate_missing[F_SPEC], gate_missing[F_QUALITY], gate_missing[F_ADVERSARIAL]))
print("reviewer-route " + " ".join("%s=%d" % kv for kv in sorted(reviewer_route.items())))
print("implementer-status " + " ".join("%s=%d" % kv for kv in sorted(implementer_status.items())))
strategies = " ".join("%s=%d" % kv for kv in sorted(failure_strategy.items()))
if degraded_descs:
strategies += " degraded-distinct-descriptions=%d" % len(degraded_descs)
if degraded_overflow:
strategies += " (+%d more)" % degraded_overflow
print("failure-strategy " + strategies)
try:
# Binary, deliberately: a text-mode `for raw in fh` decodes EAGERLY, so a
# truncated multi-byte tail raises UnicodeDecodeError from the ITERATION —
# outside any per-line try — and kills the read after records already counted.
fh = open(path, "rb")
except OSError as exc:
# This reader owns its errors. Falling through to the shell's `|| echo` would
# discard the counts entirely and say nothing about which path failed.
print("per-task telemetry unreadable (%s) — skipping" % (exc,))
print("records=0 skipped=0")
sys.exit(0)
try:
with fh:
for raw in fh:
# The WHOLE per-line body is guarded, not just the parse: valid JSON
# that is not an object (`null`, `3`, `[]`) parses fine and then raises
# AttributeError at the first `.get`. Values are computed into locals
# first and committed only afterwards, so a line can never land in
# BOTH records= and skipped=.
try:
line = raw.decode("utf-8", "replace").strip()
if not line:
# A blank/whitespace-only physical line is NOT a record —
# the normal trailing newline of an append-only file, not
# a corrupt one. Excluded from BOTH records= and skipped=
# on purpose: the Reader contract counts every REAL line
# into one bucket or the other, and a blank line is not a
# real line to begin with.
continue
rec = json.loads(line)
if not isinstance(rec, dict):
raise ValueError("record is not a JSON object")
spec_status = gate_status(rec, F_SPEC, "exact")
quality_status = gate_status(rec, F_QUALITY, "prefix")
adversarial_status = gate_status(rec, F_ADVERSARIAL, "prefix")
route = route_key(rec)
status = enum_str(rec, F_STATUS)
strategy, desc = strategy_bucket(rec)
except Exception:
skipped += 1
continue
n += 1
if spec_status == "failed":
gate[F_SPEC] += 1
elif spec_status == "missing":
gate_missing[F_SPEC] += 1
if quality_status == "failed":
gate[F_QUALITY] += 1
elif quality_status == "missing":
gate_missing[F_QUALITY] += 1
if adversarial_status == "failed":
gate[F_ADVERSARIAL] += 1
elif adversarial_status == "missing":
gate_missing[F_ADVERSARIAL] += 1
bump(reviewer_route, route)
bump(implementer_status, status)
bump(failure_strategy, strategy)
if desc is not None:
if desc in degraded_descs or len(degraded_descs) < DEGRADED_DESC_CAP:
degraded_descs.add(desc)
else:
degraded_overflow += 1
except Exception as exc:
# A read that dies mid-file still reports what it already aggregated. Partial
# results plus the skip count beat the shell fallback's total silence.
print("per-task telemetry read aborted after %d records (%s)" % (n, exc))
emit()
sys.exit(0)
PY
fi
# <<< zuvo:retro-telemetryPhase 4: Actionable Items
Generate at least 3 specific, actionable items. Each item must:
- Reference a specific file, directory, or backlog item (not generic advice)
- Include a specific zuvo command
- State the reason derived from the data
Derivation rules (priority order):
- High-churn files — for each file in the churn hotspot list with N ≥ 5 changes: check whether a corresponding test file exists. If no test file found:
zuvo:write-tests <file>with reason "high-churn, N changes, no test coverage found". - Churn instability — for files with N ≥ 5 changes that already have tests:
zuvo:refactor <file>with reason "N changes suggest instability or unclear responsibilities". - Critical backlog items — for each OPEN CRITICAL item:
zuvo:backlog fix <ID>with reason "critical debt item open N days". - Old open items — if oldest open item is > 14 days:
zuvo:backlog prioritizewith reason "oldest item open N days, prioritization overdue". - Low CQ scores — if average CQ score < 79% of applicable:
zuvo:code-audit <top-churn-directory>with reason "CQ average below the 79% threshold". - Low Q scores — if average Q score < 53% of applicable:
zuvo:write-tests <top-churn-directory>with reason "Q average below the 53% threshold suggests test quality gaps".
If fewer than 3 items can be derived from the data, supplement with: "Run zuvo:code-audit . to establish a quality baseline" or "Run zuvo:test-audit . to identify test gaps."
Phase 5: Report and Output
Write Report File
Create the report using a readable suffix:
- Prefer tag-based names when
previousTag/newTagare known:zuvo/reports/retro-YYYY-MM-DD-<previousTag>_<newTag>.md - Otherwise fall back to a shortened SHA-based suffix (first 7 chars of
baseShaandreleaseCommitShafromlast-ship.json):zuvo/reports/retro-YYYY-MM-DD-<baseSha7>_<releaseCommitSha7>.md
This prevents collisions and keeps filenames readable. If zuvo/reports/ does not exist, create it.
Use this exact structure:
# Engineering Retrospective — YYYY-MM-DD
## Summary
[Tweetable one-liner: period, commits, releases, key metric]
## Shipping Velocity
- **Window:** <range> (<N> days, <N> commits)
- **Deployment frequency:** N releases in period (frequency: 1 per X days)
- **Release cycle span:** N days (earliest commit → latest tag)
- **Commits:** N total, N/day average
## Churn Hotspots
[Top 5 most-changed files with change count. Note if test file was found for each.]
| Rank | File | Changes | Has Tests |
|------|------|---------|-----------|
| 1 | src/orders/service.ts | 12 | No |
| 2 | src/auth/guard.ts | 8 | Yes |
## Backlog Health
- **Open items:** N (N critical, N high)
- **Added this period:** N
- **Resolved this period:** N
- **Oldest unresolved:** [item ID] — open N days
## Quality Trends
- **CQ scores:** avg N/<applicable> ([N runs in period], or "no runs.log data")
- **Q scores:** avg N/<applicable> ([N runs in period], or "no runs.log data")
- **Most-used skills:** [skill1 (N runs), skill2 (N runs)]
- **Pass/fail ratio:** N PASS, N WARN, N FAIL
## Actionable Items
1. `zuvo:write-tests src/orders/` — high-churn (12 changes), no test file found
2. `zuvo:refactor src/auth/guard.ts` — 8 changes suggest instability
3. `zuvo:backlog fix BD-007` — critical debt item open 21 days
## Comparison vs Prior Retro
[Delta table or "First retro — run again next period for trends"]Prior Retro Comparison
Check for existing zuvo/reports/retro-*.md files. If one or more exist, compare with the most recent:
| Metric | Prior | Current | Delta |
| Deployment frequency | N/period | N/period | +/-N |
| Release cycle span | N days | N days | +/-N days |
| Open backlog items | N | N | +/-N |
| Avg CQ score | N/<applicable> | N/<applicable> | +/-N |
| Avg Q score | N/<applicable> | N/<applicable> | +/-N |
If no prior retro exists: note "First retro — run again next period for trends."
Terminal Output Block
Print the RETRO COMPLETE block after writing the report:
RETRO COMPLETE
Window: v1.1.0..v1.2.0 (14 days, 47 commits)
Releases: 2 in period (frequency: 1 per week)
Cycle span: 14 days (earliest commit → latest tag)
Hotspots: src/orders/service.ts (12 changes), src/auth/guard.ts (8 changes)
Backlog: +5 added, -3 resolved, 12 open (2 critical)
Report: zuvo/reports/retro-2026-03-28-v1.1.0_v1.2.0.md
Actions:
1. zuvo:write-tests src/orders/ — high-churn, low coverage
2. zuvo:refactor src/auth/guard.ts — 8 changes suggest instability
3. zuvo:backlog fix BD-007 — critical debt item open 21 days
Run: <ISO-8601-Z> retro <project> - - <VERDICT> - 6-phase <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>
After printing this block, append the `Run:` line value (without the `Run: ` prefix) to the log file path resolved per `run-logger.md`.If E13 (insufficient history) was triggered, show:
RETRO COMPLETE [QUALITATIVE ONLY — <10 commits in window]
Window: HEAD~30..HEAD (fallback — fewer than 10 commits found)
Backlog: +N added, -N resolved, N open (N critical)
Report: zuvo/reports/retro-YYYY-MM-DD-<suffix>.md
Actions:
1. [derived from backlog only]
Run: <ISO-8601-Z> retro <project> - - <VERDICT> - qualitative <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>
After printing this block, append the `Run:` line value (without the `Run: ` prefix) to the log file path resolved per `run-logger.md`.Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

