PR Flow Skill
Goal: make me see the mechanism of a PR — who triggers what, through which components, what
fans out where — when the diff is scattered across callbacks, jobs, services and entry points and
a linear reading path (pr-brief) isn't enough. Reviewing is a separate pass; this page builds the
model I review against.
Output: one self-contained HTML file per PR, opened in my browser: a left-to-right SVG diagram
where every box is clickable and opens a modal carrying the code it stands for — the diff
hunks, verbatim, and the out-of-diff excerpts with the lines that matter highlighted. The diagram
sits in a full-width canvas at 1:1 (the text keeps its size whatever the diagram's width), panned
by dragging and zoomed with Ctrl + wheel or the toolbar. The page embeds the repository's code
verbatim: it stays a local file, never published or uploaded.
The rules carried over from pr-brief — all of them apply
- Zero round-trip. Every box's modal embeds the code. Never "voir
x.rb:12" — embed x.rb:12.
- Describe, never evaluate. Facts ("aucun test sur
Account dans ce job", "sans plafond"),
never verdicts ("risque", "il faudrait", "attention"). If a fact looks alarming, state it and stop.
- French prose, source-code names verbatim, backticks on greppable identifiers in HTML parts
(
intro, headline, table cells, reading) — not in SVG title/lines, which are plain text.
- Title and description of the PR are not evidence. Derive everything from the branch.
- Strictly read-only on GitHub. Discovery, fetch, reading — nothing else.
- Branch mechanics: exactly those of
pr-brief, step 2 — read <skill-dir>/../pr-brief/SKILL.md
and apply them as written: local clone looked up under ~/workspaces, named ref
refs/pr-brief/<N> (never FETCH_HEAD), git show / git grep on the ref, temp files suffixed
with -<N>, never any checkout in a local clone (sibling worktree only if files must be on disk),
never isolation: "worktree", reuse a ref already at the PR's head and delete only the refs this
run created.
- Reuse the brief when it exists (
<skill-dir>/../pr-brief/blocks.py report <OWNER/REPO> <N> brief + .json, same head_sha). Its
blocks have the shape this skill uses, but they are cut per reading step, not per box: copy a
block when it matches one box, otherwise rebuild it with B.excerpt — cheap, since lines are
located by pattern. Its facts and its companion analysis (core PR, stacked base) are already
verified: reuse them. If the brief's builder <scratch>/build_<N>.py exists and is importable,
import its path constants and helpers instead of copying them.
What to draw
Draw as the engineer who has to live with the code, not as a decorator.
- Depict the mechanism, not its name. Columns are the stages a signal crosses (typically:
point d'entrée → écriture / événement → composant qui décide → effet), boxes are the concrete
things at each stage, edges are labelled with what actually travels (
perform_later,
user_ids: [id], × N users). A box named after a concept ("notification") says less than the
method, callback or table it really is.
- On agorize-front (Vue 3 Options API, Pinia), the stages are usually interaction · route →
composant (handler) → store (action, état) → API agorize-core → rendu. That order is a default,
not a rule: when the PR is driven by a payload (the API decides, the components read), put the
API before the store — order the columns so that most edges go left to right, and the edge labels are
what the code really does:
@click, $emit('select', id), xxxStore.fetchXxx(),
GET /api/v2/…, :items, beforeEach. The PRs worth a flow there are the ones where the
mechanism is spread: a store shared by several components or views, an event relayed across
several $emit levels, route guards and redirections choosing the path, SSR vs client behaviour,
a front + agorize-core pair forming one flow. A plain UI change stays a pr-brief.
- The agorize-core side: when the flow crosses the API, find and read the companion
agorize-core branch as
pr-brief describes (section "Companion agorize-core branch"), and give
the endpoint its own boxes — controller action, serializer — built with a second Blocks
instance (cwd = the agorize-core clone). Set "repo": "Agorize/agorize-core" and
"ref": <core head sha> on those context blocks so their GitHub link points at the right repo.
No companion branch → read the endpoint on agorize-core origin/master and say so in the intro.
- Include what the diff doesn't show. The paths that reach the new code by ricochet
(an existing
after_commit, a job that calls save!, a controller with nested attributes) and
the paths that don't reach it (insert_all, imports) — the second kind drawn with the
none edge, so "rien n'est enfilé" is visible, not implied. This is the part I cannot build
from GitHub.
- Where the work concentrates gets a box of its own: a
note-toned box for "aucun service
commun : deux requêtes, deux jeux de guards", a band for shared infrastructure (queue config).
Both open modals like any other box — the note shows the two pieces of code side by side.
- Match complexity to the stakes. A one-hop PR is three boxes; don't inventory the system.
Grep before drawing: every box must correspond to code you have opened.
- Label the arrows. An unlabelled arrow is "related somehow".
Two edges leaving the same box draw their labels at the same spot: put
"label_at": "end" on
one of them. Keep labels short: the gap
between two columns widens to the longest label that crosses it, so a 40-character label costs
250 px of width. A none edge's label is drawn inside its box, prefixed with ∅.
- Titles and lines wrap to the box width and the box grows; still, one idea per line.
Tones trace paths: a/b/c for the distinct trigger paths (one hue per path, consistently
from entry to effect), off for boxes that lead nowhere, note, band, plain. An edge without
tone is drawn in the neutral ink (k). More paths: declare a new key in tones with its
label and, optionally, "light": [stroke, fill] / "dark": [stroke, fill] (hex); without
colours it borrows those of a. Edge kinds:
solid (direct: callback, method call), dash (explicit call that replaces a bypassed
callback), none (∅ — nothing happens; add the label saying so).
What goes in each modal
One intro (1–3 descriptive sentences), then blocks, most load-bearing first:
- the diff hunks of the files the box stands for — verbatim, headers recomputed if trimmed;
- the out-of-diff excerpts that make the box true — the enclosing method, whole, with the
decisive lines in
highlight (the save!, the RETURNING, the guard that is or isn't there);
- facts with the command that establishes them and its real output, produced by
B.ran
(git grep … → aucun résultat, a generated SQL). Blocks.fact with a pasted output only for
a scenario run in a console. Never invent an output.
Build every block with blocks.py, which lives in the sibling skill: <skill-dir>/../pr-brief/blocks.py
(its full API — hunks, line, excerpt, the second-repo instance — is described in
pr-brief, step 3 and "Companion agorize-core branch")
(<skill-dir> is the directory this SKILL.md was loaded from). Write a builder script in the
scratchpad, run it from the clone that holds the ref — the same pattern as pr-brief, step 3:
import sys; sys.path.insert(0, "<skill-dir>/../pr-brief")
from blocks import Blocks
B = Blocks(ref="refs/pr-brief/<N>", diff_path="<scratch>/pr-<N>.diff")
B.diff("app/models/order_refund.rb") # all hunks
B.diff("app/views/x.html.erb", trim=(86, 105)) # new file, trimmed, header recomputed
B.ctx("app/jobs/foo_job.rb", [(26, 47)], highlight=[31, 46], note="save! → callback")
B.ctx("app/models/user.rb", [(73, 76), (290, 297)], highlight=[76, 296]) # segments
B.ran("Aucun test sur `Account` dans ce job.", "git grep -n Account refs/pr-brief/<N> -- spec/jobs")
A fact's output is never typed: B.ran executes the command and embeds what it printed.
The JSON spec — <report>.json
<report> is the path printed by python3 <skill-dir>/../pr-brief/blocks.py report <OWNER/REPO> <N> flow
(e.g. ~/.claude/skills/reports/PROD-7715-agorize-front-3334-flow) — same naming as pr-brief,
never built by hand.
mkdir -p the directory first, and keep the file (see the end of this page). The shape below
is shown as JSON; the builder script writes it as a Python dict, so true/false/null
become True/False/None there.
{
"repo": "Owner/repo", "number": 250, "url": "https://github.com/…/pull/250",
"head_sha": "…", // required: GitHub links in modals point at it
"title": "Flow notify_order_shipped", // page name — short noun phrase
"headline": "Qui déclenche `x`, et par où ça passe",
"lede": ["1–2 phrases : comment lire le schéma"],
"caption": "la phrase que le schéma démontre",
"aria": "description textuelle du schéma",
"tones": { "a": {"label": "chemin OrderShipment"}, "b": {"label": "chemin OrderRefund"} },
"legend": { "solid": "plein = after_commit", "dash": "tireté = appel explicite", "none": "aucune notification" },
"columns": [ {"id": "entry", "head": "Point d'entrée", "width": 310},
{"id": "event", "head": "Écriture · événement", "width": 250},
{"id": "job", "head": "Job décideur", "width": 300},
{"id": "effect","head": "Mails", "width": 220} ],
"nodes": [ // stacked top-down per column, in array order
{ "id": "gc", "col": "entry", "tone": "plain",
"title": "OrdersController#update", // plain text (SVG)
"lines": [ {"text": "backoffice · shipments_attributes", "style": "sub"} ],
"intro": "phrase descriptive (HTML, backticks ok)",
"blocks": [ /* diff | context | facts — same shape as pr-brief */ ] },
{ "id": "shp", "col": "event", "tone": "a", "title": "OrderShipment",
"lines": [ {"text": "after_commit on: :create", "style": "sub"},
{"text": "→ order_id, shipment_id", "style": "lab"} ],
"blocks": [ … ] },
{ "id": "note", "col": "job", "tone": "note", "sans": true, "gap": 18,
"title": "Aucun service commun entre les deux jobs",
"lines": [ {"text": "…", "style": "sans"} ], "blocks": [ … ] },
{ "id": "sq", "col": "*", "tone": "band", "sans": true, "title": "SolidQueue · config/queue.yml",
"lines": [ {"text": "…", "style": "sans"} ], "blocks": [ … ] }
],
"edges": [
{ "from": "gc", "to": "shp", "tone": "a", "kind": "solid" },
{ "from": "shp", "to": "gjob", "tone": "a", "kind": "solid", "label": "perform_later" },
{ "from": "bulk","to": "ujob", "tone": "b", "kind": "dash", "label": "perform_later" },
{ "from": "imp", "kind": "none", "label": "pas de callback — rien n'est enfilé" },
{ "from": "gjob","to": "mdj", "tone": "a", "label": "× N users", "label_at": "vertical" }
],
"table": { "title": "Par déclencheur : ce qui est enfilé, et ce qui borne N",
"head": ["Déclencheur", "Écriture", "Job enfilé", "Argument", "Effet", "Borne de N"],
"rows": [ {"node": "gc", "tone": "a", "cells": ["`OrdersController#update`", "…"]} ] },
"reading": ["2–3 puces : ce que le schéma montre — faits, pas verdicts"]
}
Node fields: y (absolute, overrides stacking), gap (extra space before), span (columns to
the right to cover), height, cue (diff/code, auto), modal_title, sans (sans-serif
title). Line styles: code (default mono), sub (muted), lab (tone-coloured), sans
(muted sans), text (sans, ink). Edge fields: label_at (start default / end / vertical
for a rotated label on the elbow), to_y, elbow_x — all three honoured on left-to-right and
right-to-left edges (vertical only left to right). A none edge takes no to. A node without blocks/intro is drawn
but not clickable — avoid that: a box I cannot open is a pointer.
The renderer stacks nodes per column in order, sizes boxes from their line count, routes edges
(straight when aligned, elbow otherwise, vertical inside a column), builds the legend from the
tones and kinds actually used, and refuses an edge or table row that names an unknown node.
Render and open
python3 <skill-dir>/render.py \
<report>.json \
<report>.html --open
render.py needs nothing beyond the Python standard library. --open shows the page in the
default browser on Linux and macOS alike — never call xdg-open or open yourself.
Then a one-line report in the chat: repo#number — titre — chemin du fichier. Everything else
lives in the page.
Keep the JSON: re-rendering after a renderer fix or a new node must not redo the analysis. Delete
the refs/pr-brief/… refs this run created (not those it reused) and any worktree when done; leave
the working tree as you found it.
1---2name: pr-flow3description: Draw the mechanism of a PR as an interactive flow page — triggers, the components they reach, what fans out where — with every box opening the diff hunks and out-of-diff code it stands for. Read-only, no judgement. Use when a PR is hard to hold in the head because the change is spread across callbacks, jobs, services or entry points, and a diagram would beat a reading path; complements pr-brief.4---56# PR Flow Skill78Goal: make me **see** the mechanism of a PR — who triggers what, through which components, what9fans out where — when the diff is scattered across callbacks, jobs, services and entry points and10a linear reading path (`pr-brief`) isn't enough. Reviewing is a separate pass; this page builds the11model I review against.1213Output: **one self-contained HTML file per PR**, opened in my browser: a left-to-right SVG diagram14where **every box is clickable** and opens a modal carrying the code it stands for — the diff15hunks, verbatim, and the out-of-diff excerpts with the lines that matter highlighted. The diagram16sits in a full-width canvas at 1:1 (the text keeps its size whatever the diagram's width), panned17by dragging and zoomed with Ctrl + wheel or the toolbar. The page embeds the repository's code18verbatim: it stays a local file, never published or uploaded.1920## The rules carried over from `pr-brief` — all of them apply2122- **Zero round-trip.** Every box's modal embeds the code. Never "voir `x.rb:12`" — embed `x.rb:12`.23- **Describe, never evaluate.** Facts ("aucun test sur `Account` dans ce job", "sans plafond"),24 never verdicts ("risque", "il faudrait", "attention"). If a fact looks alarming, state it and stop.25- **French prose, source-code names verbatim**, backticks on greppable identifiers in HTML parts26 (`intro`, `headline`, table cells, `reading`) — not in SVG `title`/`lines`, which are plain text.27- **Title and description of the PR are not evidence.** Derive everything from the branch.28- **Strictly read-only** on GitHub. Discovery, fetch, reading — nothing else.29- **Branch mechanics**: exactly those of `pr-brief`, step 2 — read `<skill-dir>/../pr-brief/SKILL.md`30 and apply them as written: local clone looked up under `~/workspaces`, named ref31 `refs/pr-brief/<N>` (never `FETCH_HEAD`), `git show` / `git grep` on the ref, temp files suffixed32 with `-<N>`, never any checkout in a local clone (sibling worktree only if files must be on disk),33 never `isolation: "worktree"`, reuse a ref already at the PR's head and delete only the refs this34 run created.35- **Reuse the brief when it exists** (`<skill-dir>/../pr-brief/blocks.py report <OWNER/REPO> <N> brief` + `.json`, same `head_sha`). Its36 blocks have the shape this skill uses, but they are cut per reading step, not per box: copy a37 block when it matches one box, otherwise rebuild it with `B.excerpt` — cheap, since lines are38 located by pattern. Its facts and its companion analysis (core PR, stacked base) are already39 verified: reuse them. If the brief's builder `<scratch>/build_<N>.py` exists and is importable,40 import its path constants and helpers instead of copying them.4142## What to draw4344Draw as the engineer who has to live with the code, not as a decorator.4546- **Depict the mechanism, not its name.** Columns are the stages a signal crosses (typically:47 *point d'entrée → écriture / événement → composant qui décide → effet*), boxes are the concrete48 things at each stage, edges are labelled with what actually travels (`perform_later`,49 `user_ids: [id]`, `× N users`). A box named after a concept ("notification") says less than the50 method, callback or table it really is.51- **On agorize-front** (Vue 3 Options API, Pinia), the stages are usually *interaction · route →52 composant (handler) → store (action, état) → API agorize-core → rendu*. That order is a default,53 not a rule: when the PR is driven by a payload (the API decides, the components read), put the54 API before the store — order the columns so that most edges go left to right, and the edge labels are55 what the code really does: `@click`, `$emit('select', id)`, `xxxStore.fetchXxx()`,56 `GET /api/v2/…`, `:items`, `beforeEach`. The PRs worth a flow there are the ones where the57 mechanism is spread: a store shared by several components or views, an event relayed across58 several `$emit` levels, route guards and redirections choosing the path, SSR vs client behaviour,59 a front + agorize-core pair forming one flow. A plain UI change stays a `pr-brief`.60- **The agorize-core side**: when the flow crosses the API, find and read the companion61 agorize-core branch as `pr-brief` describes (section "Companion agorize-core branch"), and give62 the endpoint its own boxes — controller action, serializer — built with a second `Blocks`63 instance (`cwd` = the agorize-core clone). Set `"repo": "Agorize/agorize-core"` and64 `"ref": <core head sha>` on those `context` blocks so their GitHub link points at the right repo.65 No companion branch → read the endpoint on agorize-core `origin/master` and say so in the intro.66- **Include what the diff doesn't show.** The paths that reach the new code by ricochet67 (an existing `after_commit`, a job that calls `save!`, a controller with nested attributes) and68 the paths that *don't* reach it (`insert_all`, imports) — the second kind drawn with the69 `none` edge, so "rien n'est enfilé" is visible, not implied. This is the part I cannot build70 from GitHub.71- **Where the work concentrates gets a box of its own**: a `note`-toned box for "aucun service72 commun : deux requêtes, deux jeux de guards", a `band` for shared infrastructure (queue config).73 Both open modals like any other box — the note shows the two pieces of code side by side.74- **Match complexity to the stakes.** A one-hop PR is three boxes; don't inventory the system.75 Grep before drawing: every box must correspond to code you have opened.76- **Label the arrows.** An unlabelled arrow is "related somehow".77 Two edges leaving the same box draw their labels at the same spot: put `"label_at": "end"` on78 one of them. Keep labels short: the gap79 between two columns widens to the longest label that crosses it, so a 40-character label costs80 250 px of width. A `none` edge's label is drawn inside its box, prefixed with ∅.81- **Titles and lines wrap** to the box width and the box grows; still, one idea per line.8283Tones trace paths: `a`/`b`/`c` for the distinct trigger paths (one hue per path, consistently84from entry to effect), `off` for boxes that lead nowhere, `note`, `band`, `plain`. An edge without85`tone` is drawn in the neutral ink (`k`). More paths: declare a new key in `tones` with its86`label` and, optionally, `"light": [stroke, fill]` / `"dark": [stroke, fill]` (hex); without87colours it borrows those of `a`. Edge kinds:88`solid` (direct: callback, method call), `dash` (explicit call that replaces a bypassed89callback), `none` (∅ — nothing happens; add the `label` saying so).9091## What goes in each modal9293One `intro` (1–3 descriptive sentences), then blocks, most load-bearing first:9495- the **diff hunks** of the files the box stands for — verbatim, headers recomputed if trimmed;96- the **out-of-diff excerpts** that make the box true — the enclosing method, whole, with the97 decisive lines in `highlight` (the `save!`, the `RETURNING`, the guard that is or isn't there);98- **facts** with the command that establishes them and its *real* output, produced by `B.ran`99 (`git grep … → aucun résultat`, a generated SQL). `Blocks.fact` with a pasted output only for100 a scenario run in a console. Never invent an output.101102Build every block with `blocks.py`, which lives in the sibling skill: `<skill-dir>/../pr-brief/blocks.py`103(its full API — `hunks`, `line`, `excerpt`, the second-repo instance — is described in104`pr-brief`, step 3 and "Companion agorize-core branch")105(`<skill-dir>` is the directory this `SKILL.md` was loaded from). Write a builder script in the106scratchpad, run it from the clone that holds the ref — the same pattern as `pr-brief`, step 3:107108```python109import sys; sys.path.insert(0, "<skill-dir>/../pr-brief")110from blocks import Blocks111B = Blocks(ref="refs/pr-brief/<N>", diff_path="<scratch>/pr-<N>.diff")112B.diff("app/models/order_refund.rb") # all hunks113B.diff("app/views/x.html.erb", trim=(86, 105)) # new file, trimmed, header recomputed114B.ctx("app/jobs/foo_job.rb", [(26, 47)], highlight=[31, 46], note="save! → callback")115B.ctx("app/models/user.rb", [(73, 76), (290, 297)], highlight=[76, 296]) # segments116B.ran("Aucun test sur `Account` dans ce job.", "git grep -n Account refs/pr-brief/<N> -- spec/jobs")117```118119A fact's `output` is never typed: `B.ran` executes the command and embeds what it printed.120121## The JSON spec — `<report>.json`122123`<report>` is the path printed by `python3 <skill-dir>/../pr-brief/blocks.py report <OWNER/REPO> <N> flow`124(e.g. `~/.claude/skills/reports/PROD-7715-agorize-front-3334-flow`) — same naming as `pr-brief`,125never built by hand.126127`mkdir -p` the directory first, and keep the file (see the end of this page). The shape below128is shown as JSON; the builder script writes it as a Python dict, so `true`/`false`/`null`129become `True`/`False`/`None` there.130131```jsonc132{133 "repo": "Owner/repo", "number": 250, "url": "https://github.com/…/pull/250",134 "head_sha": "…", // required: GitHub links in modals point at it135 "title": "Flow notify_order_shipped", // page name — short noun phrase136 "headline": "Qui déclenche `x`, et par où ça passe",137 "lede": ["1–2 phrases : comment lire le schéma"],138 "caption": "la phrase que le schéma démontre",139 "aria": "description textuelle du schéma",140141 "tones": { "a": {"label": "chemin OrderShipment"}, "b": {"label": "chemin OrderRefund"} },142 "legend": { "solid": "plein = after_commit", "dash": "tireté = appel explicite", "none": "aucune notification" },143144 "columns": [ {"id": "entry", "head": "Point d'entrée", "width": 310},145 {"id": "event", "head": "Écriture · événement", "width": 250},146 {"id": "job", "head": "Job décideur", "width": 300},147 {"id": "effect","head": "Mails", "width": 220} ],148149 "nodes": [ // stacked top-down per column, in array order150 { "id": "gc", "col": "entry", "tone": "plain",151 "title": "OrdersController#update", // plain text (SVG)152 "lines": [ {"text": "backoffice · shipments_attributes", "style": "sub"} ],153 "intro": "phrase descriptive (HTML, backticks ok)",154 "blocks": [ /* diff | context | facts — same shape as pr-brief */ ] },155 { "id": "shp", "col": "event", "tone": "a", "title": "OrderShipment",156 "lines": [ {"text": "after_commit on: :create", "style": "sub"},157 {"text": "→ order_id, shipment_id", "style": "lab"} ],158 "blocks": [ … ] },159 { "id": "note", "col": "job", "tone": "note", "sans": true, "gap": 18,160 "title": "Aucun service commun entre les deux jobs",161 "lines": [ {"text": "…", "style": "sans"} ], "blocks": [ … ] },162 { "id": "sq", "col": "*", "tone": "band", "sans": true, "title": "SolidQueue · config/queue.yml",163 "lines": [ {"text": "…", "style": "sans"} ], "blocks": [ … ] }164 ],165 "edges": [166 { "from": "gc", "to": "shp", "tone": "a", "kind": "solid" },167 { "from": "shp", "to": "gjob", "tone": "a", "kind": "solid", "label": "perform_later" },168 { "from": "bulk","to": "ujob", "tone": "b", "kind": "dash", "label": "perform_later" },169 { "from": "imp", "kind": "none", "label": "pas de callback — rien n'est enfilé" },170 { "from": "gjob","to": "mdj", "tone": "a", "label": "× N users", "label_at": "vertical" }171 ],172173 "table": { "title": "Par déclencheur : ce qui est enfilé, et ce qui borne N",174 "head": ["Déclencheur", "Écriture", "Job enfilé", "Argument", "Effet", "Borne de N"],175 "rows": [ {"node": "gc", "tone": "a", "cells": ["`OrdersController#update`", "…"]} ] },176 "reading": ["2–3 puces : ce que le schéma montre — faits, pas verdicts"]177}178```179180Node fields: `y` (absolute, overrides stacking), `gap` (extra space before), `span` (columns to181the right to cover), `height`, `cue` (`diff`/`code`, auto), `modal_title`, `sans` (sans-serif182title). Line styles: `code` (default mono), `sub` (muted), `lab` (tone-coloured), `sans`183(muted sans), `text` (sans, ink). Edge fields: `label_at` (`start` default / `end` / `vertical`184for a rotated label on the elbow), `to_y`, `elbow_x` — all three honoured on left-to-right and185right-to-left edges (`vertical` only left to right). A `none` edge takes no `to`. A node without `blocks`/`intro` is drawn186but not clickable — avoid that: a box I cannot open is a pointer.187188The renderer stacks nodes per column in order, sizes boxes from their line count, routes edges189(straight when aligned, elbow otherwise, vertical inside a column), builds the legend from the190tones and kinds actually used, and refuses an edge or table row that names an unknown node.191192## Render and open193194```195python3 <skill-dir>/render.py \196 <report>.json \197 <report>.html --open198```199200`render.py` needs nothing beyond the Python standard library. `--open` shows the page in the201default browser on Linux and macOS alike — never call `xdg-open` or `open` yourself.202203Then a one-line report in the chat: `repo#number — titre — chemin du fichier`. Everything else204lives in the page.205206Keep the JSON: re-rendering after a renderer fix or a new node must not redo the analysis. Delete207the `refs/pr-brief/…` refs this run created (not those it reused) and any worktree when done; leave208the working tree as you found it.