# Refactor Learn

> The suite's only writer of bookkeeping — acts on refactor-scan's reconciliation findings and refactor-implement's freshly opened merge request, records the ledger, ADRs, CONTEXT.md, and the last-run stamp.

- Skill: `art4/refactor-learn` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add art4/refactor-learn`
- Raw SKILL.md: https://api.skillmd.com/api/skills/art4/refactor-learn/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Art4 (https://skillmd.com/u/art4)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/art4/refactor-learn

---


# Refactor Learn

The only skill that writes suite bookkeeping: the Refactoring Notes' `merge-requests.md` (only when `docs/agents/issue-tracker.md` names no native-label tracker — otherwise this data lives on the tracker), the Refactoring Notes' `out-of-scope/`, ADRs, `CONTEXT.md`, the Refactoring Notes' `bookkeeping.md`, and issue labels. Every other lifecycle skill may read these; only this one writes them.

The orchestrator calls this skill up to **twice** a pass: an **early call**, right after `refactor-scan`, only when it produced findings; and a **closing call**, always, at the end. The split exists because `refactor-prioritize` reads the ledger to decide whether two MRs are already open — a finding this pass just resolved has to be written back before that check runs.

**Land every write below via a dedicated bookkeeping branch/MR off the default branch — never a direct commit.** Before writing, in either call: confirm you aren't still on the candidate branch `refactor-implement` left checked out — these writes aren't part of that review. Pull the default branch's latest, then create (or reuse, if one from an earlier interrupted pass is still open) a small bookkeeping branch off it, commit the writes there, and open (or update) that MR, using the create-mode policy at `skills/continuous-refactoring/references/opening-a-merge-request.md`.

**Exception — native-tracker in-flight fold-in**: `docs/agents/issue-tracker.md` names a native-label tracker, and `refactor-implement` opened a candidate MR this same pass → the closing call's writes ride that branch as a follow-up commit, no separate bookkeeping MR — stay checked out on it. `loop-config` is this exception's narrowest case (the file exists nowhere else yet). The same applies to the early call's own **fold-in still owed** finding below — that candidate's branch already exists from an earlier pass, so its writes ride there too, never the dedicated bookkeeping branch. Doesn't otherwise apply to the early call (no candidate branch exists yet for any other finding type), a pass with no candidate MR, or a non-native tracker — those keep using the dedicated bookkeeping branch below.

**Finding the bookkeeping branch — deterministic, no memory required, never search for a name.** Named `refactor-learn/bookkeeping-N` (N starting at 1, never reused). Algorithm: `skills/refactor-learn/references/bookkeeping-branch.md`.

**Before deleting or abandoning any branch carrying an unmerged bookkeeping write**, land that record first: `skills/refactor-learn/references/never-delete-without-record.md`.

## Process

### Early call — findings only (from `refactor-scan`, if any)

Runs only when scan produced findings; the closing call still happens regardless, at the end. These are bookkeeping writes too — land via the dedicated bookkeeping branch/MR, except **fold-in still owed** below, which rides the candidate's own branch (the exception above).

For each finding:

- Merged → mark the candidate `done`, close the issue.
- Closed without merge → closing comments support a structural rejection (a maintainer gave a load-bearing reason) → mark `wontfix`, close the issue, file a learned rejection under the Refactoring Notes' `out-of-scope/`; otherwise ask the human before deciding. Load-bearing reason is a minimum PHP version the target doesn't meet → also record it machine-parseably (`**Blocked by:** PHP >= X.Y`) so a later pass detects the reversal automatically (`tooling_tree.py`'s `detect_nodes()`).
- Tracked in the Refactoring Notes' `merge-requests.md` (`docs/agents/issue-tracker.md` names no native-label tracker) → drop the entry once resolved, either way. `docs/agents/issue-tracker.md` names a native-label tracker → nothing to remove there; closing the issue (above) already takes it out of the open-`refactor:candidate` remembered set `refactor-scan` reads.
- **PHP-version reversal** (scan step 3 also reports these) → an existing entry in the Refactoring Notes' `out-of-scope/<node>.md` names a `Blocked by` condition the target now satisfies. Remove that file — the rejection is reversed, the node is proposable again on its own merits (not thereby fulfilled). Never for a rejection with no `Blocked by` field, or one scan didn't report as satisfied — those stay rejected until a human (or agent with a stated reason) removes them by hand.
- **Fold-in still owed** (scan step 3's new finding — a still-open, still-draft candidate MR from an earlier interrupted pass) → check out that candidate's own branch (the exception above, not the dedicated bookkeeping branch), perform the same fold-in writes the closing call would (*Then, regardless of which branch...* below), then mark it ready for review as that list's last step — completing what the interrupted pass never finished. The candidate issue itself isn't closed by this — that still waits for the MR to actually merge, an ordinary "Merged" finding on some future pass.
- **Secret history scan finding** (scan step 4c) → file a `refactor:priority` candidate issue per finding, same three-field shape (Where/Problem/Signal) any other candidate issue uses: Where is the file/line, Problem is the scanner's own rule/finding id and a short description — **the secret's value redacted** — Signal is Security (`skills/refactor-prioritize/references/signals.md`). Filed directly rather than surfaced through `refactor-prioritize`'s Select mode — nothing to explore, the finding is already concrete. Once every finding from this pass's scan is filed (zero findings counts as "every finding filed" too), write the Refactoring Notes' `bookkeeping.md`'s `Secret history scan` field to `done` — this scan runs at most once per target (`refactor-scan/SKILL.md` step 4c), so this write never repeats.

`done`/`wontfix` are the shared triage-role labels (`docs/agents/triage-labels.md`), not suite-specific — closing the issue is what takes it out of the backlog.

A pass that only makes this call (no fresh candidate this run) is still a complete pass.

### Closing call — always, at the end of the pass

Given a freshly opened MR (from `refactor-implement`, if the pass got that far):

- `docs/agents/issue-tracker.md` names a native-label tracker (GitHub, GitLab) → nothing to remember here — `refactor-implement` step 5's `Closes #<n>` on the MR is already the durable record, the tracker's own native issue↔PR cross-reference (`docs/adr/0026-drop-delivered-label-use-native-pr-linkage.md`); no label to apply. Otherwise remember it in the Refactoring Notes' `merge-requests.md`: URL, candidate issue, tooling-tree node name (blank for structural), base branch.
- Clear the Refactoring Notes' `bookkeeping.md`'s `Pending candidates` — this candidate now has an MR, so the resume marker no longer applies.
- `Create-mode` is normally already set — decided once, during `loop-config`'s own interview (`skills/continuous-refactoring/references/loop-config-interview.md`), and written by `refactor-implement` when it created `bookkeeping.md`. Narrow fallback only: `bookkeeping.md` predates this convention and `Create-mode` is genuinely unset → record what `refactor-implement` used this pass and treat it as decided from here on, don't re-derive it every pass.

**Which branch**: the fold-in exception's condition met → stay on the candidate's branch, commit there. Otherwise → the dedicated bookkeeping branch/MR (open one even with no candidate MR this pass; never assume the current checkout is safe to write to). `loop-config`-in-flight (before that candidate merges) is just this exception's narrowest case — `Create-mode` is already set by `refactor-implement`'s own `loop-config` exception, nothing left for this call to record there normally.

Then, regardless of which branch the writes above rode:

- Record an ADR (`docs/adr/`) for any decision a future scan must not re-litigate (see `/domain-modeling`).
- Update `CONTEXT.md` with terms that crystallised this pass.
- Write the Refactoring Notes' `bookkeeping.md`'s `Fulfilled nodes` and `Skip streak` — unconditionally, last. Algorithm, including the parser-vs-fallback overwrite rules and a worked example: `skills/refactor-learn/references/fulfilled-nodes-write.md`.
- **Last of all**: the branch these writes just landed on carries a candidate MR still marked draft (`opening-a-merge-request.md`'s *Draft candidate MRs* — opened as one this same pass, or resumed via the early call's **fold-in still owed** finding above) → mark it ready for review now that every fold-in write above is actually pushed (`gh pr ready` / `glab mr update <n> --ready`). Not draft (the ordinary non-native-tracker/dedicated-branch case) → nothing to do here.

## Fallback

- **`/domain-modeling`**: installed → use its discipline for the ADR/`CONTEXT.md` side effects. Otherwise skip with a note — the ledger, label, and stamp writes are inline and suite-internal, run regardless. Crash-safe.

## Completion criterion

**Early call:** every finding is resolved (`done`, `wontfix` + out-of-scope entry, a PHP-version reversal's file removed, a fold-in-still-owed candidate's MR marked ready for review, every secret-history-scan finding filed as a `refactor:priority` candidate with `Secret history scan` written `done`, or an explicit "asked the human, waiting"), the remembered set reflects it before `refactor-prioritize` runs, and every write went out through a branch — the dedicated bookkeeping branch (opened as an MR, or — no forge/remote available — handed to the human per `opening-a-merge-request.md`) for every finding type except fold-in-still-owed, which rides the candidate's own already-open branch instead (the exception above).

**Closing call:** a freshly delivered candidate (if any) is remembered (its MR's `Closes #<n>` link, or the ledger, whichever applies) with `Pending candidates` cleared, `Fulfilled nodes` and `Skip streak` are written (full re-derivation when the parser ran, additive/narrow otherwise), a candidate MR left in draft by this pass is marked ready for review, and every write went out through a branch — the candidate's own already-open branch (native tracker, MR opened this pass), the dedicated bookkeeping one, or the `loop-config` candidate's own as that exception's narrowest case — never a direct commit to the default branch (opened as an MR where forge access exists).

