.agents/skills/oat-wrap-up/SKILL.md
Use when preparing a shipping digest or weekly/biweekly wrap-up summarizing OAT projects and merged PRs over a time window. Reads local summary files and GitHub PR metadata; writes a version-controlled markdown report.
npx skillsauth add tkstang/open-agent-toolkit oat-wrap-upInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Read local OAT project summaries plus merged PRs over a time window, synthesize a shipping digest (features, bug fixes, new user-facing capabilities), and write a markdown report to a version-controlled directory.
Use when:
Don't use when:
oat-project-summary instead.oat project archive sync first so the digest reflects the full team's work. The skill warns if this looks undone but will not auto-run sync.Parse from $ARGUMENTS:
--since YYYY-MM-DD: (optional) Explicit window start date (inclusive).--until YYYY-MM-DD: (optional) Explicit window end date (inclusive). Defaults to today.--past-week: (optional) Named shortcut — last 7 days ending today.--past-2-weeks: (optional) Named shortcut — last 14 days ending today.--past-month: (optional) Named shortcut — last 30 days ending today.--output <path>: (optional) Override the report destination. Bypasses the archive.wrapUpExportPath config.--dry-run: (optional) Print the report to stdout instead of writing a file.Exactly one time-range specifier is required: either a named range (--past-week / --past-2-weeks / --past-month) OR --since (with optional --until). Named ranges and --since are mutually exclusive.
.oat/).oat project archive sync has been run recently so teammates' archived projects are hydrated into .oat/projects/archived/. The skill warns if archive.s3Uri is configured but the local archive has no .oat-archive-source.json metadata files.gh CLI authenticated for the current repository (needed for merged-PR fetching via gh api graphql).OAT MODE: Wrap-Up
Purpose: Produce a date-ranged shipping digest over OAT-tracked projects and merged PRs.
BLOCKED Activities:
oat project archive sync — it is a user-gated prerequisite, not a side effect of this skill.ALLOWED Activities:
gh api graphql.--dry-run).Self-Correction Protocol:
If you catch yourself:
oat project archive sync → STOP, the skill only warns; the user runs sync themselves.Recovery:
Print the phase banner once at start:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ OAT ▸ WRAP-UP ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Print each step indicator at the start of that step, not all at once upfront:
[1/9] Resolving inputs…[2/9] Resolving config…[3/9] Discovering summary files…[4/9] Parsing + filtering summaries…[5/9] Deduping by project…[6/9] Fetching merged PRs…[7/9] Cross-referencing PRs to OAT projects…[8/9] Synthesizing report…[9/9] Writing report…After step 9, print the final banner on a single line:
OAT ▸ WRAP-UP ▸ DONE — <N> summaries + <M> PRs → <path>
On --dry-run, replace <path> with stdout (dry-run).
Before any step indicator fires, check whether the local archive likely needs to be hydrated. Resolve the projects root and probe for archived metadata:
set -eu
PROJECTS_ROOT="${OAT_PROJECTS_ROOT:-$(oat config get projects.root 2>/dev/null || echo ".oat/projects/shared")}"
PROJECTS_ROOT="${PROJECTS_ROOT%/}"
ARCHIVE_DIR="$(dirname "$PROJECTS_ROOT")/archived"
S3_URI="$(oat config get archive.s3Uri 2>/dev/null || true)"
if [ -n "$S3_URI" ] && [ -d "$ARCHIVE_DIR" ]; then
if ! find "$ARCHIVE_DIR" -maxdepth 2 -name '.oat-archive-source.json' 2>/dev/null | grep -q . ; then
printf '⚠️ archive.s3Uri is configured but no archived snapshots are hydrated locally.\n'
printf ' Run "oat project archive sync" first so teammates archived projects are visible to the wrap-up.\n'
printf ' (Proceeding with active projects and the version-controlled summaries directory only.)\n'
fi
elif [ -n "$S3_URI" ]; then
printf '⚠️ archive.s3Uri is configured but %s does not exist.\n' "$ARCHIVE_DIR"
printf ' Run "oat project archive sync" first so archived projects are available.\n'
printf ' (Proceeding with active projects and the version-controlled summaries directory only.)\n'
fi
The [ -d "$ARCHIVE_DIR" ] guard protects against parent shells inheriting set -o pipefail, where find on a missing directory could fail the pipeline. The differentiated branch also gives clearer guidance when the directory is missing entirely versus merely empty of metadata.
Do NOT block on this warning. Continue to Step 1.
Parse $ARGUMENTS and resolve the time window into concrete SINCE and UNTIL dates:
--past-week / --past-2-weeks / --past-month) is provided, compute:
UNTIL = today (ISO YYYY-MM-DD in the local timezone)SINCE = UNTIL minus 7 / 14 / 30 days respectivelyWINDOW_LABEL = the range name (past-week / past-2-weeks / past-month)--since YYYY-MM-DD is provided, use it directly. --until defaults to today when omitted. WINDOW_LABEL = custom.--since are provided, fail with Error: --past-week / --past-2-weeks / --past-month are mutually exclusive with --since.Error: specify either --past-week / --past-2-weeks / --past-month or --since YYYY-MM-DD.SINCE and UNTIL parse as YYYY-MM-DD and that SINCE <= UNTIL.Resolve --output if present; otherwise leave it null (the destination will be computed in Step 2 from config).
Resolve --dry-run to a boolean.
Print the resolved window for reproducibility:
Window: SINCE → UNTIL (WINDOW_LABEL)
Resolve the configuration surface via oat config get:
PROJECTS_ROOT="${OAT_PROJECTS_ROOT:-$(oat config get projects.root 2>/dev/null || echo ".oat/projects/shared")}"
PROJECTS_ROOT="${PROJECTS_ROOT%/}"
SUMMARY_EXPORT_PATH="$(oat config get archive.summaryExportPath 2>/dev/null || true)"
WRAPUP_EXPORT_PATH="$(oat config get archive.wrapUpExportPath 2>/dev/null || true)"
if [ -z "$WRAPUP_EXPORT_PATH" ]; then
WRAPUP_EXPORT_PATH=".oat/repo/reference/wrap-ups"
fi
REPO_NAME_WITH_OWNER="$(gh repo view --json nameWithOwner --jq .nameWithOwner)"
SUMMARY_EXPORT_PATH may be empty. When empty, Step 3 skips that discovery location.WRAPUP_EXPORT_PATH falls back to .oat/repo/reference/wrap-ups because oat config get returns empty when unset. This fallback lives in the skill, not in the CLI, to match the sibling archive.summaryExportPath behavior.REPO_NAME_WITH_OWNER must resolve successfully — fail fast if gh repo view errors (auth issue or wrong cwd).If --output was set in Step 1, use it as the final destination. Otherwise the destination is:
<repoRoot>/<WRAPUP_EXPORT_PATH>/<UNTIL>-wrap-up-<WINDOW_LABEL>.md
Build a candidate set from three local locations. Use Glob for each and collect absolute paths:
${PROJECTS_ROOT}/*/summary.md AND multi-scope .oat/projects/*/*/summary.md (some repos use scope subdirectories under .oat/projects/)..oat/projects/archived/*/summary.md. Also include timestamp-suffixed variants like .oat/projects/archived/*-[0-9]*/summary.md (created by resolveUniqueArchivePath when the same project is archived twice).${SUMMARY_EXPORT_PATH}/*.md — only if SUMMARY_EXPORT_PATH is non-empty. Files here follow the {YYYYMMDD}-{project}.md naming produced by the completion workflow.Collect every candidate path into a single list before filtering in Step 4.
For each candidate file:
oat_generated: true, ORYYYYMMDD-{project}.md pattern (export directory format).
Drop any file that matches neither check.oat_last_updated (YYYY-MM-DD).{YYYYMMDD} prefix parsed from the filename or parent directory. Format the prefix as a date.mtime (last-resort fallback).project_name or similar explicit field if present.-20260403120000).{YYYYMMDD}- for the export format).[SINCE, UNTIL] (inclusive).The result is a list of {projectName, date, path, source, content} records where source is one of active, archived, exported.
The same project can appear in multiple locations (e.g., an active project that was also exported to the version-controlled directory). Dedupe with these rules:
oat_last_updated frontmatter value (or resolved date if frontmatter is missing).source in this order: active > archived > exported. Rationale: the active project is the freshest copy when work is still in progress; the local archive is the next most recent; the version-controlled export is a historical snapshot.The result is a list of at most one summary per project, all within the time window.
Fetch merged PRs in the window via gh api graphql. Reuse the search-string pattern from packages/cli/src/commands/repo/pr-comments/collect/collect-comments.ts:198-205:
SEARCH_QUERY="is:pr is:merged merged:${SINCE}..${UNTIL} repo:${REPO_NAME_WITH_OWNER}"
Paginate using endCursor / hasNextPage — do not stop at the first page. Capture for each PR: number, title, author login, mergedAt, labels, and body (for cross-reference scanning in Step 7).
Example single-page call (the agent should loop until hasNextPage is false):
gh api graphql \
-f query='query($searchQuery: String!, $first: Int!, $after: String) {
search(query: $searchQuery, type: ISSUE, first: $first, after: $after) {
pageInfo { hasNextPage endCursor }
nodes {
... on PullRequest {
number
title
author { login }
mergedAt
labels(first: 20) { nodes { name } }
body
}
}
}
}' \
-f searchQuery="$SEARCH_QUERY" \
-F first=25
Store the flattened PR list for Step 7.
For each merged PR, determine whether it is already "claimed" by an included summary:
#<number>, github.com/.../pull/<number>, or the bare PR number when adjacent to keywords like "PR", "merged", "ships in".pr_number → summary.projectName for every match.Partition the merged PRs into two groups:
Compose the markdown report by reading each included summary and synthesizing (not concatenating) its Overview and What Was Implemented sections into the report's narrative sections. Apply the summary template's section-omission rule — omit any report section with no content.
Use the skeleton at references/report-template.md as the scaffold. Key synthesis rules:
Follow-up Items section across all included summaries. Dedupe similar items.If a section would be empty (e.g., no bug fixes shipped during the window), omit it entirely — do not include a "None" placeholder.
Compose the final markdown document with this frontmatter at the top:
---
oat_wrap_up: true
oat_generated: true
window_since: { SINCE }
window_until: { UNTIL }
window_label: { WINDOW_LABEL }
generated_at: { ISO 8601 UTC timestamp }
---
Followed by # Wrap-up: {SINCE} to {UNTIL} as the H1 and then the synthesized sections from Step 8.
Destination logic:
--dry-run is set, write the report to stdout. Do not create or modify any file.--output was explicitly set in Step 1, write to that path (create parent directories if needed).<repoRoot>/<WRAPUP_EXPORT_PATH>/<UNTIL>-wrap-up-<WINDOW_LABEL>.md (create parent directories if needed).After the write (or stdout output), print the final banner:
OAT ▸ WRAP-UP ▸ DONE — <N_summaries> summaries + <N_prs> PRs → <path_or_stdout>
Do NOT commit the resulting file. If the user wants it committed, they run git add and git commit themselves, or use an automation wrapper (see references/automation-recipes.md).
/oat-wrap-up --past-week
/oat-wrap-up --past-2-weeks --dry-run
/oat-wrap-up --since 2026-03-20 --until 2026-04-01
/oat-wrap-up --past-month --output /tmp/march-wrapup.md
Summarize what shipped this past week across OAT projects and merged PRs.
Give me a biweekly digest — the last 14 days of shipped work — and write it
to the usual wrap-ups directory.
Produce a wrap-up for the window 2026-03-20 to 2026-04-01 and print it to stdout
so I can review before committing it.
references/report-template.mdCronCreate, Codex host scheduling, plain cron): references/automation-recipes.mdoat project archive sync at packages/cli/src/commands/project/archive/index.ts:244packages/cli/src/commands/repo/pr-comments/collect/collect-comments.ts:198-205.oat/templates/summary.mdarchive.wrapUpExportPath (managed via oat config set archive.wrapUpExportPath <path>)gh repo view fails with an auth error:
gh auth status and authenticate against the current repository's host.origin remote.Report is empty or "no summaries found":
SINCE and UNTIL.ls .oat/projects/archived/ should list directories. If empty and archive.s3Uri is configured, run oat project archive sync first..oat/projects/*/*/summary.md and/or ${SUMMARY_EXPORT_PATH}/*.md contain files whose oat_last_updated falls inside the window.Report is missing PRs that clearly merged in the window:
gh is authenticated and scoped to the right repo via gh repo view --json nameWithOwner.merged:${SINCE}..${UNTIL} (inclusive on both ends) — some date-boundary surprises come from the mergedAt timestamp being in UTC.hasNextPage: false, extend the pagination loop.archive.wrapUpExportPath returns empty from oat config get but the skill wrote to a different path:
.oat/repo/reference/wrap-ups — the config layer itself returns empty for unset values, consistent with the sibling archive.summaryExportPath behavior.oat config set archive.wrapUpExportPath <path> or use --output <path> for a one-off override."Shipped via OAT projects" is empty or "Other merged PRs" contains PRs that clearly correspond to included summaries (false negative):
#<n>, github.com/.../pull/<n>, or a bare PR number adjacent to keywords like "PR", "merged", "ships in"), or when a sibling pr/ directory inside the project references the PR. OAT summaries as authored today (see .oat/templates/summary.md) do not inline PR numbers, so in repos that have not adopted the convention of citing PRs in summaries, the cross-reference will often find zero matches and all merged PRs will land in "Other merged PRs".pr/ under each project directory with PR metadata so the sibling scan has data to match against.git log) to close the gap.A PR was partitioned into "Shipped via OAT projects" but the narrative does not mention it (false positive):
#<number> pattern can false-positive when a summary references a heading anchor like #42, a footnote marker, or a legacy issue number that happens to match a PR number in the window. Step 7's hits are advisory, not authoritative.SINCE/UNTIL dates and printed for reproducibility.archive.wrapUpExportPath with the documented fallback when unset.oat_last_updated within the window and deduped by project with the documented precedence.--dry-run) with correct frontmatter.--dry-run).documentation
Use when OAT implementation changes and repository reference docs must be synchronized. Updates .oat/repo/reference to match current behavior.
business
Merge multiple analysis artifacts into a single coherent report with provenance tracking. Reads existing artifacts from /deep-research, /analyze, and /compare.
testing
Use when the user questions or suspects an agent claim is wrong. Adversarially gathers evidence to verify or refute the claim using the best sources available in the current environment.
tools
Use when prioritizing backlog work or evaluating a roadmap. Produces value-effort ratings, dependency mapping, and execution recommendations.