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'sdetect_nodes()). - Tracked in the Refactoring Notes'
merge-requests.md(docs/agents/issue-tracker.mdnames no native-label tracker) → drop the entry once resolved, either way.docs/agents/issue-tracker.mdnames a native-label tracker → nothing to remove there; closing the issue (above) already takes it out of the open-refactor:candidateremembered setrefactor-scanreads. - PHP-version reversal (scan step 3 also reports these) → an existing entry in the Refactoring Notes'
out-of-scope/<node>.mdnames aBlocked bycondition 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 noBlocked byfield, 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:prioritycandidate 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 throughrefactor-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'sSecret history scanfield todone— this scan runs at most once per target (refactor-scan/SKILL.mdstep 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.mdnames a native-label tracker (GitHub, GitLab) → nothing to remember here —refactor-implementstep 5'sCloses #<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'sPending candidates— this candidate now has an MR, so the resume marker no longer applies. Create-modeis normally already set — decided once, duringloop-config's own interview (skills/continuous-refactoring/references/loop-config-interview.md), and written byrefactor-implementwhen it createdbookkeeping.md. Narrow fallback only:bookkeeping.mdpredates this convention andCreate-modeis genuinely unset → record whatrefactor-implementused 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.mdwith terms that crystallised this pass. - Write the Refactoring Notes'
bookkeeping.md'sFulfilled nodesandSkip 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.mdside 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).