wrap

star 0

This skill should be used when the user says `/vibe-wrap` (or `/vibe-wrap:wrap`) and wants a session-end handoff doc that reads the breadcrumb trail sibling vibe plugins already left, surfaces what shipped + what's uncommitted + what's unpushed, and gates commit + push interactively. Multi-repo aware (read wide, mutate narrow): "What shipped" + "Still unpushed" span every sibling repo that had commits in the session window, while the commit/push gates stay scoped to the current repo. Reads breadcrumbs, sibling session-logs / friction / wins, git state across repos, and the active decision-log backend. Writes a markdown wrap doc to `docs/session-wraps/<ts>.md` (fallback `.vibe-wrap/wraps/<ts>.md`) and prints inline. Bumper-lanes invariant — every gate defaults to no-action and has a clear skip path. Flags: `--inline-only`, `--bridge`, `--session-window <hours>`, `--repos <p1,p2,...>`, `--repo-roots <dir>`, `--no-multi-repo`.

estevanhernandez-stack-ed By estevanhernandez-stack-ed schedule Updated 6/9/2026

name: wrap description: This skill should be used when the user says /vibe-wrap (or /vibe-wrap:wrap) and wants a session-end handoff doc that reads the breadcrumb trail sibling vibe plugins already left, surfaces what shipped + what's uncommitted + what's unpushed, and gates commit + push interactively. Multi-repo aware (read wide, mutate narrow): "What shipped" + "Still unpushed" span every sibling repo that had commits in the session window, while the commit/push gates stay scoped to the current repo. Reads breadcrumbs, sibling session-logs / friction / wins, git state across repos, and the active decision-log backend. Writes a markdown wrap doc to docs/session-wraps/<ts>.md (fallback .vibe-wrap/wraps/<ts>.md) and prints inline. Bumper-lanes invariant — every gate defaults to no-action and has a clear skip path. Flags: --inline-only, --bridge, --session-window <hours>, --repos <p1,p2,...>, --repo-roots <dir>, --no-multi-repo.

wrap — Read the trail, render the handoff, gate the rest

The user-facing session wrap. Read the breadcrumb trail the toolkit already left, surface what shipped + what's uncommitted + what's unpushed, render a durable handoff doc, then surface the commit / push / decision-log / bridge gates interactively. The doc always lands. The gates are bumpers, never walls.

Read ../guide/SKILL.md first for shared behavior — voice rules, the bumper-lanes invariant, persona adaptation, friction-trigger contract, namespace isolation, and ecosystem-aware composition.

Before you start

  • Session logging. Call session-logger.start("wrap", <cwd>) at command start, session-logger.end(<entry>) at command end. Per ../session-logger/SKILL.md.
  • Persona + mode. Read shared.preferences.persona and shared.preferences.mode from the unified profile per the guide. Persona shapes the prose voice; mode shapes pacing. The bumper-lanes invariant never changes.
  • Hard dependency: Python 3.11+. If the runtime is older or absent, fail at command start with a clear next step. Git CLI is a soft dependency — if absent, the wrap still renders from breadcrumbs + sibling reads + decision-log, with a one-line note that git state is unavailable.
  • The division of labor. scripts/render-wrap.py does the deterministic read + render + gate-state computation. It performs no mutations. This SKILL drives the interactive gestures (commit, push, decision-log append, bridge) based on the gate state the script reports. Keep that boundary — the script is reproducible; the gates are conversational.

How the user invokes

/vibe-wrap
/vibe-wrap:wrap

Flags:

Flag Default Purpose
--inline-only off Skip the file write. The wrap still prints inline; gates still surface.
--bridge off Force the dashboard-bridge gate to register eligible regardless of threshold (still MCP-only).
--session-window <hours> 4 Fallback window when ${CLAUDE_SESSION_ID} is unavailable or yields no breadcrumbs.
--repos <p1,p2,...> (discovery) Explicit repo set for the read-wide pass — bypasses sibling discovery. Each path is read (filtered to git repos), plus the current repo.
--repo-roots <dir> parent of cwd Override the discovery scan root for sibling repos.
--no-multi-repo off Disable multi-repo discovery; read the current repo only (the v0.1.0 shape).

Step 1 — Decision-log first-run (interactive, once)

Before rendering, resolve the decision-log backend. The render script is read-only and will report backend: "pending" if no config exists — it never triggers the interactive picker. This SKILL owns the first-run gesture.

At command start, import the dispatcher and call active_backend(cwd). If it returns None (first-run pending) AND the session is interactive:

  • Run the first-run picker per references/decision-log-backends.md § First-run UX. Four options, recommended default labeled, [skip — disabled] as the fourth choice.
  • Persist the user's choice to the chosen config path.
  • Then proceed to render.

If active_backend(cwd) returns a real backend, skip the picker — config already exists.

Step 2 — Render (deterministic)

Run the render core, passing the resolved session UUID:

python ${CLAUDE_PLUGIN_ROOT}/skills/wrap/scripts/render-wrap.py \
  --session-id ${CLAUDE_SESSION_ID} \
  [--session-window <hours>] \
  [--inline-only] \
  [--bridge] \
  [--repos <p1,p2,...>] \
  [--repo-roots <dir>] \
  [--no-multi-repo]

The script:

  1. Resolves the session window (earliest breadcrumb ts, else now − window-hours).
  2. Runs the reader scripts. Git state is read WIDE via multi-repo-state.py, which discovers sibling git repos under the scan root (default = parent of cwd) and reports per-repo state for every repo that had ≥1 commit in the window — git-state.py remains the single-repo core it calls. read-breadcrumbs.py and read-sibling-state.py run as before.
  3. Reads decisions via the decision-log dispatcher (read-only), scoped to the current repo.
  4. Assembles the markdown wrap from assets/wrap-template.md.
  5. Writes to docs/session-wraps/<YYYY-MM-DD-HHmm>.md (fallback .vibe-wrap/wraps/<ts>.md if no docs/) in the current repo only, unless --inline-only.
  6. Emits the rendered doc, then a fenced JSON block tagged ```VIBE-WRAP-GATE-STATE describing which gates are eligible, the multi-repo discovery summary, and sibling repos' read-only state under gates.other_repos.

Print the rendered wrap doc inline to the user. Then parse the gate-state JSON block — do not show that block to the user; it's the SKILL's control channel.

The wrap doc has six sections: What shipped, Decisions logged, Friction signals captured, Still uncommitted, Still unpushed, Session bounds. Each renders an empty-state line when there's nothing to report — never a silent gap.

Multi-repo shape (v0.2.0 — read wide, mutate narrow):

  • What shipped leads with Across N repos this session (M commits total): a, b, c. followed by a per-repo subsection (repo name, commit count, recent commits — capped at 10/repo with a truncation note). The current repo is flagged (current).
  • Still unpushed reports the current repo's push status actionably, then lists any sibling repo ahead of its remote as informational only — vibe-wrap never offers to push another repo.
  • Still uncommitted stays scoped to the current repo (it owns the commit gate). Sibling dirty state is surfaced read-only under gates.other_repos in the gate-state JSON.

See references/gate-design.md § Read wide, mutate narrow for the boundary contract.

Step 3 — Gates (interactive, default no-action)

Read references/gate-design.md for the full per-gate contract. Surface each gate only when the gate-state block marks it eligible: true. Every gate defaults to no-action. Pressing enter or typing n skips it.

All four gates are scoped to the current repo only (mutate narrow). The gate-state gates.commit / gates.push reflect the current repo's state. gates.other_repos is a read-only array of sibling repos' uncommitted/unpushed state — surface it informationally if it helps the user, but never offer to commit or push a sibling repo. There is no gate for another repo.

Gate 1 — Commit

When gates.commit.eligible is true:

  1. List the uncommitted files (already in the wrap doc's "Still uncommitted" section).
  2. If gates.commit.suppressed_reason is set (detached HEAD / mid-rebase), do NOT offer the commit gate — surface the reason as state to resolve manually and skip to the next eligible gate.
  3. Ask commit these? [y/N]. Default N.
  4. On y: draft a commit message from the wrap summary. Ask accept message? [y/N/edit]. On y stage the listed files and commit; on edit open $EDITOR; on N skip.
  5. Secret-pattern path: for each entry in gates.commit.secret_matches, fire an additional commit despite secret-pattern match? [y/N] per references/secret-patterns.md. Default N excludes the matched file. Record any override in the wrap doc's notes.
  6. Friction wiring (rows from ../guide/references/friction-triggers.md § /vibe-wrap):
    • Declined (n/enter) with uncommitted files present AND a SessionEnd nudge fired earlier this session → default_overridden / low:
      echo '{"sessionUUID":"<uuid>","command":"wrap","friction_type":"default_overridden","confidence":"low","symptom":"declined commit gate; <N> file(s) left uncommitted"}' | python ${CLAUDE_PLUGIN_ROOT}/skills/friction-logger/scripts/log.py
      
    • Commit message rewritten (edit, and >50% of the message body changed between the draft and the accepted version) → artifact_rewritten / medium, with symptom quoting the draft's and the accepted message's first lines.

Never git add -A. Never --no-verify. Never amend. Never empty-commit.

Gate 2 — Push

When gates.push.eligible is true (local ahead of a tracked remote, no unusual git state):

  1. Name the remote from gates.push.remote and the ahead count from gates.push.ahead.
  2. Ask push to <remote>? [y/N]. Default N.
  3. Multi-remote: after the answer, offer one follow-up push to a different remote? [y/N] per gate-design.md § Multi-remote path. List remotes excluding the upstream just handled; let the user pick.
  4. Friction wiring: if the user declines (n) while ahead-of-remote was non-zero → default_overridden / low:
    echo '{"sessionUUID":"<uuid>","command":"wrap","friction_type":"default_overridden","confidence":"low","symptom":"declined push gate; <ahead> commit(s) ahead of <remote>"}' | python ${CLAUDE_PLUGIN_ROOT}/skills/friction-logger/scripts/log.py
    

Force-push is never offered. Diverged branches surface as state to resolve manually.

Gate 3 — Decision-log write

When gates.decision_log.eligible is true (active backend is not disabled / pending / unknown):

  1. Ask the backend-specific prompt per gate-design.md § Gate 3 (e.g., append a session-end decision to your Markdown decision log? [y/N]).
  2. On y: build the decision dict — title from the wrap summary, body excerpt, link = wrap doc path, project_tag = bound 626Labs project ID (MCP) or repo name (file backends) or null (unbound). Call the dispatcher's append(decision, cwd).
  3. On append failure ({"ok": False}), surface the error string and continue. The wrap doc still stands.

Gate 4 — Dashboard bridge

When gates.bridge.eligible is true (backend is 626labs-mcp AND threshold met):

  1. Name the signals from gates.bridge.threshold_signals (decisions logged, commits in window, bridge flag).
  2. Ask bridge strategic context to the dashboard's Architect AI? [y/N]. Default N.
  3. On y: call the auto-detected decision-log MCP's bridge tool — the recognized one is the 626Labs dashboard (mcp__626labs-cloud__bridge_context_to_architect) — with the wrap summary as context. If the MCP is unreachable at gate time, surface a one-line note and skip silently. The MCP is optional; absence is never an error.
  4. Friction wiring: if the user declines (n) when the threshold fired organically (threshold_signals.decisions_logged >= 1 or commits_in_window > 2) → complement_rejected / high, with complement_involved set to the bridge tool. Strong signal — the threshold may be too loose, or the prompt copy doesn't land the stakes:
    echo '{"sessionUUID":"<uuid>","command":"wrap","friction_type":"complement_rejected","confidence":"high","complement_involved":"mcp__626labs-cloud__bridge_context_to_architect","symptom":"declined bridge gate after threshold fired"}' | python ${CLAUDE_PLUGIN_ROOT}/skills/friction-logger/scripts/log.py
    
    If eligibility came only from --bridge (threshold_signals.bridge_flag true, neither organic signal present) and the user declines, log default_overridden / low instead — the flag is a valid override path; aggregate use signals the threshold may be too tight.

Bridge is opt-in per gesture even when the threshold fires. Never autonomous.

Step 4 — Write back gate outcomes, then close

Gate outcomes write-back

The render at Step 2 captured the session as of render time. The gates resolve minutes later — a decision logs at gate time, a commit lands, a push fires. Without a write-back the durable artifact is permanently wrong about its own session: it says No decisions logged this session. even when Gate 3 just logged one. The wrap doc is what a future session reads; it has to tell the truth about the run that produced it.

So after every gate in Step 3 has resolved (accepted, declined, or skipped), and before calling session-logger.end(), append a ## Gate outcomes block to the wrap doc this run wrote.

  • Skip entirely when --inline-only — no file was written, so there's nothing to append to.
  • Target the wrap-doc path from the gate-state JSON's wrap_doc_path. If it's null (the earlier write failed), skip silently — never recreate the file.
  • Pure append. Open the file, append the block below. Never rewrite the rendered sections above it — the render half stays reproducible; this is the one conversational addition.

Emit one row per gate that was eligible this run (the gate-state block marks each eligible). A gate that never surfaced gets no row — don't pad with "not eligible" lines. Use the real resolved values, not placeholders:

## Gate outcomes

- Commit: committed N file(s) as `<sha>` — or — declined, left uncommitted — or — skipped (detached HEAD / mid-rebase)
- Push: pushed N commit(s) to `<remote>` — or — declined, left unpushed
- Decision log: logged to `<backend>` (ref `<id>`) — or — declined — or — append failed: `<error>`
- Bridge: bridged strategic context to the dashboard Architect — or — declined

The decision-log row carries the backend name and, on a successful append, the ref ID the dispatcher returned (e.g., the 626Labs dashboard decision ID). The commit row carries the short SHA of the commit the gate produced. These are the facts a future reader needs and the next /vibe-wrap:evolve-wrap cycle parses.

Close

End with a one-line handoff:

  • If the doc was written: Wrap saved to <path>. plus a note on any gates the user declined (still uncommitted / still unpushed).
  • If --inline-only: Wrap printed inline (no file written).
  • Call session-logger.end() with complements_invoked listing each sibling whose state the wrap pulled from, plus the gates the user accepted.

Edge cases (all six PRD cases handled by the script + this flow)

  • Empty session — zero breadcrumbs, zero git activity. The doc says so in each section's empty-state line and exits without error.
  • No git remotegates.push.eligible is false; the "Still unpushed" section notes local-only state. No push prompt.
  • Detached HEADgate-state.unusual_git_state.detached_head is true; commit/push gates are suppressed with a reason. The doc still renders.
  • Mid-rebase — same suppression path via mid_rebase.
  • Multiple remotes — the push gate names the upstream first, then offers the different-remote follow-up.
  • Secret-pattern match — surfaced in "Still uncommitted" with a WARNING marker; the commit gate fires the extra confirmation per match.

Performance budget (spec Open Issue #5)

Target <10s for the non-interactive portion: trail reader ≤4s, decision-log read ≤2s, render ≤1s. Gates are interactive and not budgeted. If a subsystem ships over budget against a real session, flag it for /evolve-wrap.

Friction triggers

The per-gate triggers are wired inline at their call sites in Step 3 — commit decline + commit-message rewrite (Gate 1), push decline (Gate 2), bridge decline (Gate 4). That's where the observable behavior happens, so that's where friction-logger.log() fires. The rows below cover the triggers that don't sit at a gate:

  • First-run picker (Step 1): user picks a non-default decision-log backend → default_overridden / medium (symptom names the picked backend). First-run only — never fires once config is persisted.
  • Command-start complement decline: user declines a Pattern #13 complement offer → complement_rejected / high (complement_involved = the sibling SKILL).
  • --inline-only override when the file-write was the recommended path → sequence_revised / low.
  • Re-wrap within 10 minutes of a prior /vibe-wrap for the same session → sequence_revised / medium (symptom carries both invocation timestamps).
  • Universal: repeat_question / rephrase_requested per the guide's universal triggers — only with a quoted prior in symptom.

Full contract: ../guide/references/friction-triggers.md § /vibe-wrap. Confidence is fixed per trigger; never override at log time. The invocation shape is identical everywhere — pipe a partial entry JSON (sessionUUID, command, friction_type, confidence, symptom, optional complement_involved) to python ${CLAUDE_PLUGIN_ROOT}/skills/friction-logger/scripts/log.py. The sessionUUID is the one session-logger.start() returned at command start.

Reference

Install via CLI
npx skills add https://github.com/estevanhernandez-stack-ed/vibe-wrap --skill wrap
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
estevanhernandez-stack-ed
estevanhernandez-stack-ed Explore all skills →