skills/repeat-until-settled/SKILL.md
Repeatedly invokes a target skill until its output settles — meaning the target makes no further substantive changes or recommendations. Detects cycles (oscillation between two or more states) and stalls (inner skill keeps recommending but changes don't land) and handles each appropriately. Optionally chains to a follow-up skill after settling. Use when one pass of a skill is rarely enough and each pass tends to uncover more work until eventually there's none — e.g. "repeat-until-settled improve-code-structure then release".
npx skillsauth add shhac/skills repeat-until-settledInstall 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.
Run a target skill in a loop, classify each iteration's outcome, and exit on settle, cycle resolution, max-cap, or user decision at a stall pause.
The skill takes one argument string of the form:
<inner-skill-invocation> [then <follow-up-invocation>] [max=N]
Examples:
improve-code-structureimprove-code-structure then releaseimprove-code-structure src/auth.ts then releasesimplify the lib/ directory then commitimprove-code-structure max=10 then releaseApply these steps in order; each operates on the result of the previous one. This is the canonical recipe — do not improvise.
max=N token (literal, with =, anywhere in the string). If present, set max = N and remove the token from the string. The literal max=N form is the only recognized cap syntax — natural-language phrases like "up to 10 times" are NOT parsed automatically, because they collide too easily with inner-skill args meaning the same words. If the user wrote a natural-language cap, surface a one-line note: "I see what looks like an iteration cap in your request. Please re-invoke with max=N to apply it, or confirm you want me to ignore it."then (case-insensitive, with whitespace on both sides) in the remaining string.then separators in the original string: only the first is honored. Report any extras as ignored.Without an explicit max=, there is no maximum — the loop runs until settled, cycled, or stalled-out at the user-pause threshold.
You are the orchestrator running an outer loop over a target skill.
Copy this checklist into your first response and tick items off as you progress:
- [ ] Parse arguments (inner skill, optional follow-up, optional `max=N`)
- [ ] Pick state-capture recipe and capture `START_STATE`
- [ ] Iteration loop: capture pre → invoke inner skill → capture post → classify
- [ ] On Settled or Cycled-stay or Cycled-forward-resolved: produce convergence summary
- [ ] On Settled or Cycled-forward-resolved (with user OK): invoke follow-up if specified
For five worked examples covering settle, cycle-by-stay, cycle-by-forward-escape, a non-code (manuscript) project, and a stall pause, see references/examples.md.
Before the first iteration:
START_STATE using the chosen recipe.STATES = [] (cycle detection will compare against this).iteration = 0, consecutive_stalls = 0, total_stalls = 0, forward_escape_attempts = 0, first_pause_count = 0.ITERATION_LOG = [] (per-iteration data for the convergence summary).For each iteration:
iteration. If a max was specified and iteration > max, exit the loop with Partial convergence (max-cap).pre = current state (apply the chosen recipe from references/state-capture.md).post = current state (same recipe). Append post to STATES.ITERATION_LOG: the iteration number, outcome (after step 7), summary tag, and the recipe's change-metric (diff stats for git, file count for filesystem, word-count delta for text projects, etc.). Needed for the convergence summary.| Outcome | Condition | Action |
|------------|--------------------------------------------------------------------------------------------------------|-------------|
| Settled | post == pre AND summary has a positive empty/done signal (see Settle predicate) | → settle |
| Stalled | post == pre AND summary is ambiguous or had pending work | → stall |
| Cycled | post == STATES[i] for some i < iteration - 1 AND summary similar to that iteration's | → cycle |
| Continuing | otherwise | → continue |
Counter invariants (apply on every iteration, regardless of outcome):
consecutive_stalls: incremented in → stall; reset to 0 in → continue, → cycle, and after the user responds to a stall pause. Untouched by → settle (loop exits).total_stalls: incremented in → stall; never reset. Used by Stall handling to detect the deeper-stall threshold.first_pause_count: incremented in Stall handling when the first-threshold pause is reached; never reset. Used together with total_stalls to detect the deeper-stall threshold.forward_escape_attempts: managed inside → cycle only.STATES: post is appended on every iteration (already done in step 5 of the loop).Exit the loop. Proceed to the exit-reason dispatch.
Increment consecutive_stalls. If it reaches 3, run Stall handling (pause and ask the user). Otherwise proceed to the next iteration without resetting the counter.
Run Cycle resolution.
Proceed to the next iteration.
Settle requires both signals:
post == pre per the chosen recipe.If state delta is zero but the summary is ambiguous (recommendations exist but were deferred, status is unstated, terse text like "done." with no breakdown), classify as Stalled, not Settled. Premature settle exits the loop and may invoke a follow-up against incomplete work.
When post matches STATES[i] for some i < iteration - 1 AND the inner skill's summary at iteration i is substantively similar to the current summary, the loop has cycled. (Summary similarity matters — two unrelated iterations can produce identical states by coincidence; without summary confirmation, the orchestrator would resolve a false cycle.)
This skill never goes backward. No git reset, no checkout to a prior SHA, no overwriting newer files with older versions. Cycles resolve forward, with one of two outcomes:
The current state is a fixed point — both sides of the cycle are valid and the inner skill is oscillating between equally-good options. This is the most common cycle pattern in LLM-driven loops.
Settled-via-cycle. The convergence summary records both sides' reasoning so the user sees what was at stake and why staying was reasonable.The alternate state in the cycle has clearly stronger reasoning AND it isn't the current state — the inner skill is converging on the worse of the two options.
forward_escape_attempts. On the next iteration, prepend a directive to the inner skill: "You have oscillated between state A and state B. State {favored} is preferred because {asymmetric-coverage reason}. Make the forward changes needed to land at {favored} and stay there." Continue the loop. If forward_escape_attempts >= 2 (the inner skill ignored the directive and produced another cycle), surface to the user — autonomous escape is not working.When neither default applies — typically because the two sides are both substantive but disagree on a value judgment (style preference, scope tradeoff) the user should weigh in on.
A stall is an iteration where the inner skill produced recommendations or had pending work, but no changes landed. Stalls usually indicate trouble at the orchestration layer, not the inner skill — verification keeps failing, user keeps rejecting, a tool keeps erroring, the same recommendation gets re-proposed but never applied.
Track two counters:
consecutive_stalls — resets after the user responds to a pause.total_stalls — monotonic; never reset until the loop exits.When consecutive_stalls reaches 3, increment first_pause_count. If first_pause_count >= 2 OR total_stalls >= 6, jump to the Deeper-stall threshold below instead of the first-threshold pause — at this point retry has demonstrably not worked. Otherwise, pause using the first-threshold template — offers retry / skip / abandon / change scope. After the user responds, reset consecutive_stalls = 0.
Triggered when first_pause_count >= 2 OR total_stalls >= 6. Pause using the deeper-threshold template — drops retry, recommends change scope or abandon. After the user responds, reset consecutive_stalls = 0 (but first_pause_count and total_stalls keep accumulating).
If the user's response isn't a clear match to one of the offered options ("ok", "sure", "yeah whatever"), state your interpretation back before acting: "I read that as retry — please confirm or tell me a different option." Do not proceed on a guess. Treat ambiguous responses as a pause continuation, not a fresh decision.
When the loop exits, classify the exit reason and dispatch:
| Exit reason | Summary header | Follow-up |
|----------------------------------------------|-----------------------|------------------------------------------|
| Settled | Convergence summary | Invoke automatically. |
| Settled-via-cycle (Stay) | Convergence summary | Ask user before invoking — they may want to verify the cycled state. |
| Forward-escape resolved (later settled) | Convergence summary | Invoke automatically — the resolution landed forward, the state is the natural outcome. |
| Partial: max-cap hit | Partial convergence | Skip; surface to user. |
| Partial: abandoned at stall pause | Partial convergence | Skip; surface to user. |
| Partial: cycle surfaced, user picked abandon | Partial convergence | Skip; surface to user. |
| Partial: forward-escape failed (≥2 attempts) | Partial convergence | Skip; surface to user. |
| Partial: indeterminate (non-interactive harness, pause needed) | Partial convergence | Skip; state written to .ai-cache/repeat-until-settled-state.md for resume. |
The "Forward-escape resolved" row applies when forward-escape was invoked at some point but the loop later reached Settled — that's a clean exit, the cycle was a passing turbulence, not a terminal state.
Output the convergence summary template, substituting the header from the dispatch table (Convergence summary for clean exits, Partial convergence for partial ones). Data for the iteration log comes from ITERATION_LOG captured during the per-iteration loop — do not invent it.
Partial: indeterminate, write the loop state and the pending question to a discoverable location (e.g. .ai-cache/repeat-until-settled-state.md), and stop. The user can re-invoke later with the answer.git reset, no git checkout to a prior SHA, no overwriting newer files with older versions. Cycle resolution moves forward only (stay or forward-escape).<inner> then <follow-up> only. Multi-step chains are not supported.development
Audit a codebase's module boundaries — enumerate modules, map their seams (import edges between modules), produce a layered topology diagram, and classify each module as narrow, hub-by-design, or accidental hub (with separate flags for cycles, layer violations, and uncertain import graphs). Outputs a diagram plus a flagged-for-review list; does not change code. Use when assessing whether abstractions live at the right boundaries, before/after a refactor to verify the boundaries improved, or when an unfamiliar codebase needs an architectural map. Not for intra-module refactoring (see improve-code-structure), bug hunting, or feature work.
testing
Investigate and solve problems using a team of specialist agents. Use when facing complex, multi-faceted problems that benefit from parallel research and structured implementation.
tools
Sync a forked repository with its upstream. Fetches both remotes, shows divergence, resets shared branches to upstream, re-merges local-only branches, cleans up branches already merged upstream, and pushes. Use when upstream has accepted PRs or moved ahead and you need to bring your fork in line.
data-ai
Manage stacked branches — rebase cascades, detect landed PRs, show stack status. Use when branches are stacked (B on A on main), trunk has advanced, a mid-stack branch changed, or a PR has landed and descendants need rebasing. Lightweight alternative to Graphite that infers the stack from git history.