Skip to content
Back to skills

Consolidation Fix

ASecurity

Read consolidation-report.md and queue ready_to_propose duplication patterns as task-store tasks, one task per pattern, each carrying a strangler-fig (build -> coexist -> eliminate) execution plan and a per-finding HITL classification. Requires consolidation-scan to have run first.

  • 14 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 2, 2026
ai-agentsrustbashgit

Security analysis

A100/100

Scanned September 30, 2026

npx -y skills add app-vitals/shipwright --skill consolidation-fix --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Consolidation Fix?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Consolidation Fix
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/app-vitals-consolidation-fix/badge)](https://www.skillsdirectory.com/skills/app-vitals-consolidation-fix)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: consolidation-fix
description: Read consolidation-report.md and queue ready_to_propose duplication patterns as task-store tasks, one task per pattern, each carrying a strangler-fig (build -> coexist -> eliminate) execution plan and a per-finding HITL classification. Requires consolidation-scan to have run first.
---

# Consolidation Fix

Read the latest `consolidation-report.md` and queue focused, human-reviewable tasks
for patterns that `consolidation-scan` promoted to `ready_to_propose`. Each task
proposes a canonical shape for one stabilized duplication pattern plus a
strangler-fig execution plan broken into small, separate PR-sized steps — never a
single sweeping diff. Findings are never turned into direct PRs; they always become
task-store tasks that `dev-task` (or a human, for HITL tasks) picks up later.

This is the `consolidation-scan` counterpart that mirrors `entropy-fix`'s
relationship to `entropy-scan` — same dedup mechanism, same task-store fields, same
queue-only contract. The two patrols (entropy and consolidation) remain fully
decoupled: this skill never promotes a consolidation finding into an entropy-scan
rule, and it never asks entropy-fix to do anything. That graduation/promotion
mechanism is explicitly out of scope.

**Prerequisites:** Run `/consolidation-scan` first to produce `consolidation-report.md`.

> **Task store setup:** This skill pushes findings to the Shipwright task store. If
> `SHIPWRIGHT_TASK_STORE_URL` or `SHIPWRIGHT_TASK_STORE_TOKEN` is missing, invoke
> `/shipwright:task-store` for setup instructions.

---

## Setup: Parse Arguments

Before starting, check for flags:

- `--dry-run` — print what tasks would be queued without querying the task store for
  dedup or writing any tasks
- `--pattern {fingerprint}` — queue only the candidate whose fingerprint matches
  (full or unambiguous prefix)

> **Note:** Queueing is the only mode. There is no PR mode and no `--queue` flag —
> every run queues tasks. `--dry-run` shows a preview and stops without touching the
> task store.

---

## Step 1: Verify consolidation-report.md Exists

1. Look for `consolidation-report.md` in the project root.
2. If it does not exist, print:
   ```
   No consolidation-report.md found. Run /consolidation-scan first to generate a report.
   ```
   Then stop.
3. Read the report.

---

## Step 2: Filter to `ready_to_propose` Entries Only

1. Parse the report's `## Ready to Propose` section. Each entry there is, by
   construction, already `status: ready_to_propose` — `consolidation-scan` never
   writes a `tracking` entry to this file (see its Step 9/Constraints). Do not
   re-derive or re-check `occurrence_count`/`consecutive_stable_runs` thresholds
   here; the report's presence of an entry already means the gate was met.
2. If `--pattern` was passed, filter to only the entry whose `**Fingerprint:**`
   matches. If no entry matches, print: "No ready_to_propose entry with fingerprint
   `{pattern}`. Nothing to queue." and stop. If the prefix matches more than one
   entry — fingerprints are truncated hashes (per `consolidation-scan`'s Step 5.3),
   so prefix collisions across two different entries in the same report are
   possible — print all matching fingerprints and stop, asking the caller to
   disambiguate:
   ```
   Ambiguous --pattern `{pattern}` matches {N} entries:
     {fingerprint-1} — {pattern description}
     {fingerprint-2} — {pattern description}
     ...
   Re-run with a longer prefix to disambiguate.
   ```
3. For each remaining entry, extract: fingerprint, pattern description, occurrence
   list (`file:line — note`), proposed canonical shape, first/last seen, and
   stability history.
4. If the report has no entries under `## Ready to Propose` (i.e. it prints "No
   candidates have stabilized enough to propose yet..."), print:
   ```
   No ready_to_propose candidates in consolidation-report.md. Nothing to queue.
   Run /consolidation-scan again in future weeks to let more candidates stabilize.
   ```
   Then stop.

---

## Step 3: Cross-Check consolidation-decisions.md (Once More Before Queueing)

Even though `consolidation-scan` already checks the decisions registry before
promoting a candidate to `ready_to_propose` (its Step 7), the registry can change
between when the report was generated and when this skill runs — a human may have
reviewed a prior `consolidation-fix` PR and added a new accepted-debt entry since.
Never trust the report's promotion decision as still current; re-check live.

See `references/decisions-registry.md` for the shared decisions-registry pattern this
mirrors.

1. Check for `.claude/shipwright/consolidation-decisions.md` in the project root.
2. **If it does not exist**, treat this as "no suppressions configured" — the same
   graceful no-op `consolidation-scan` uses. Print: "No consolidation-decisions.md
   found — no suppressions configured." and continue to Step 4 with an empty
   suppression list. This is expected and not an error.
3. **If it exists**, load and parse it the same generic, defensive way
   `consolidation-scan`'s Step 1 does: read each `###` entry for its **Pattern**,
   **Decision**, **Rationale**, and **Revisit** fields where present, but do not
   hardcode assumptions about the registry's exact heading/field layout beyond
   those four fields — skip any entry you can't confidently interpret rather than
   failing the whole load.
4. For each `ready_to_propose` entry from Step 2, compare its pattern description
   against every suppression entry's **Pattern** field. If a suppression clearly
   matches:
   - If the suppression's **Revisit** condition has been met (a stated date has
     passed, a stated occurrence/churn threshold has now been exceeded, or a stated
     triggering event has occurred), the candidate is no longer suppressed —
     continue queueing it normally.
   - Otherwise, **skip this candidate** — do not queue a task for it. Print:
     `Skipping {fingerprint} — accepted as debt per consolidation-decisions.md ("{pattern name}")`.
5. This is a judgment-based match, not exact string equality — read both
   descriptions and decide whether they describe the same duplication, the same
   comparison style Step 3 of `consolidation-scan` uses when surveying.
6. For each entry that survives the decisions-registry check above, also run it
   through the pre-filing verification checklist —
   `references/pre-filing-verification.md` (relative to the plugin root) — before it
   proceeds any further. The registry check above only re-verifies suppression
   status; it says nothing about whether the occurrence list itself is still
   accurate, since the report is a snapshot that may already be stale by the time
   this skill runs. Treat `references/pre-filing-verification.md` as canonical for
   how to apply the checklist. Per its four checks:
   - For each occurrence (`file:line`) in the candidate's occurrence list, verify
     the file/line still exists and the duplication pattern is still actually
     present there (Checklist Items 1–2). If every occurrence has been resolved or
     no longer matches the described pattern, drop the candidate entirely — do not
     queue a task for it. Print: `Skipping {fingerprint} — stale, no remaining
     occurrences match the described pattern`. If only some occurrences are stale,
     drop those from the occurrence list and proceed with the remaining ones (the
     Step 6 execution plan and Step 8 task JSON are built from the surviving
     occurrences only).
   - Route candidates whose occurrences can't be confirmed by a literal check to
     HITL rather than assuming they're safe to drop (Checklist Item 3) — this feeds
     into the `hitl` computation in Step 7.
   - Checklist Item 4 (task ID / branch collisions) is satisfied by this skill's
     own Step 5 dedup check; no separate action is needed here beyond noting the
     overlap.
   This runs once, here in Step 3, so both the `--dry-run` preview (Step 4) and the
   real queue path (Step 5 onward) operate on the same already-verified candidate
   set.

---

## Step 4: Dry-Run Output (if --dry-run)

If `--dry-run` was passed, print a preview and stop without querying or writing to
the task store:

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CONSOLIDATION FIX — DRY RUN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Would queue {N} tasks:

  1. consolidation-{fingerprint-or-slug}-{repo-slug}-{YYYY-Www}
     Pattern: {pattern description}
     Occurrences: {count} sites across {list of unique file paths}
     Canonical shape: {one-line summary of proposed canonical shape}
     Strangler-fig steps: {count} PR-sized steps (build -> coexist -> eliminate)
     HITL: {true|false} (classification per Step 7)

  2. ...

No tasks written to task store.
Re-run without --dry-run to queue tasks.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

Stop after printing.

---

## Step 5: Dedup Check Against the Task Store

This mirrors `entropy-fix`'s dedup mechanism (its Step 6q.1) exactly — same
queries, same filters, no new task-store schema or fields.

First, detect the current repo from git: run `git remote get-url origin` and strip
the `https://github.com/` (or `git@github.com:`, stripping the `.git` suffix) prefix
to get the `org/repo` value — e.g. `app-vitals/shipwright`. This is the `repo` value
used both to scope the dedup queries below and, unchanged, as the task JSON's `repo`
field in Step 6 — compute it once here and reuse it there.

Derive `repo-slug` from it too: the last path segment, lowercased — e.g.
`app-vitals/shipwright` → `shipwright`. This slug is used in task IDs throughout
this skill (Step 6, Step 8) to keep IDs unique per repo.

Run (URL-encode the detected repo, e.g. `app-vitals%2Fshipwright`). `GET /tasks`
defaults to `limit=50` and truncates silently unless the caller raises it — pass an
explicit high `limit=1000` on both queries:
```bash
curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks?status=pending&repo={url-encoded-repo}&limit=1000" | jq .
curl -sf -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
  "$SHIPWRIGHT_TASK_STORE_URL/tasks?status=in_progress&repo={url-encoded-repo}&limit=1000" | jq .
```

The `&repo=` filter scopes dedup to tasks for the repo currently being scanned —
without it, a pattern active for one repo would incorrectly block or interfere with
dedup for a different repo.

**Pagination guard — do not silently proceed with a partial result set.** `limit=1000`
covers the common case, but a repo can still have more than 1000 active tasks. After
each call, check the response's `total` field against the number of tasks actually
returned (`.tasks | length`): if `total <= tasks.length`, the page is complete. If
`total > tasks.length`, the first `limit=1000` page did not cover every active task —
re-run the same query with `&offset=1000` (then `&offset=2000`, and so on) and merge
the `.tasks` arrays until `tasks.length` (summed across pages) `>= total`. Only treat
the dedup query as complete once every page has been fetched this way; never build the
"already active" set (below) from a page that fails this check.

Parse both `.tasks` arrays. From the combined results, collect tasks where:
- `source == "shipwright"`, OR
- `title` starts with `"Consolidation:"` (this skill's title-prefix, mirroring
  entropy-fix's `"Entropy fix:"` prefix)

Extract the fingerprint (or pattern slug) from existing tasks by parsing the `id`
field (format: `consolidation-{fingerprint-or-slug}-{repo-slug}-{YYYY-Www}`) or from
the `branch` field (format: `feat/consolidation-{fingerprint-or-slug}-...`). Build a
set of "already active" fingerprints/slugs.

For each `ready_to_propose` candidate: if its fingerprint (or slug) is in the
"already active" set, skip it. Print: `Skipping {fingerprint} — task already active`.

No new task-store schema is introduced anywhere in this skill — every field used in
Step 6's task JSON is a field the task store already supports for any other task
(`id`, `title`, `source`, `repo`, `branch`, `layer`, `status`, `hitl`, `description`).

---

## Step 6: Build the Strangler-Fig Proposal Per Candidate

For each candidate that survives Steps 3 and 5, draft the canonical-shape proposal
and its execution plan before building the task JSON in Step 8.

1. **Canonical-shape proposal.** Start from the report's "Proposed canonical shape"
   paragraph for this entry (written by `consolidation-scan`) and firm it up into a
   concrete interface/implementation sketch: what the single shared implementation
   looks like, where it should live (an existing shared module/service if one
   already fits, or a clearly-named new one), and its call signature.
2. **Strangler-fig execution plan.** Break the migration into small, separate
   PR-sized steps — never one sweeping diff that touches every call site at once.
   The three phases, in order:
   - **Build.** Land the canonical implementation in isolation — new code only, no
     call sites changed yet. This PR is low-risk because nothing depends on it yet.
   - **Coexist.** Migrate call sites one at a time (or in small batches), each as
     its own PR: point one occurrence at the canonical implementation while the old
     implementation(s) at the remaining occurrences stay in place. Both the
     migrated and not-yet-migrated call sites must keep passing the same tests
     throughout — this is the safety property that makes the migration
     interruptible and revertable at every step, matching the precedent set by
     AI-agent-driven migration playbooks (e.g. Devin's playbooks, Sourcegraph's
     Agentic Batch Changes): validate the approach on one instance first, then
     checkpoint each subsequent PR against this same plan before rolling out to the
     rest, rather than improvising a different approach partway through.
   - **Eliminate.** Once every occurrence has been migrated and validated, remove
     the old implementation(s) in a final small PR (a pure deletion — low risk since
     by that point nothing else references the old code). This step is covered by
     the task's single `hitl` classification (Step 7) like every other step; it does
     not get its own separate `hitl` value.
   - Cap each step's estimated diff size in the task description so the executor
     (human or `dev-task`) doesn't bundle multiple call-site migrations into one PR.
     A one-line rule of thumb: **one call site (or a small batch of near-identical
     ones) per PR**, not "migrate everything in one branch."
3. Keep the full occurrence list from the report attached to the plan — each step
   in the coexist phase should reference which specific occurrence(s) it covers, so
   a human reading the task later can tell which sites are done and which remain.

---

## Step 7: Classify the `hitl` Field (Per-Finding Judgment)

There is no numeric backstop beyond the heuristic below — no rigid file-count or
line-count threshold alone forces a task to HITL, beyond the "~5 call sites"
guidance stated explicitly here. This mirrors `entropy-fix`'s `per-finding`
classification style for `duplicated_utility`/`architecture_layering` (its Step
6q.4) — judge each candidate on its own facts, at runtime, using the same reading
comparison approach `consolidation-scan` uses to compare shapes.

Evaluate each candidate independently:

- **`hitl: false`** (autonomous) when there is a **single, clear canonical shape**
  with **existing precedent elsewhere in the codebase** — i.e. the proposed
  canonical implementation isn't a novel design, it mirrors a pattern the codebase
  already uses successfully somewhere else, there's no real ambiguity about which
  shape is "the" canonical one, and the occurrence count is small enough that the
  strangler-fig plan stays a handful of straightforward PRs.
- **`hitl: true`** (needs a human) when **any** of the following hold:
  - **Multiple plausible canonical shapes** exist and reasonable engineers could
    disagree about which one to converge on (e.g. two different call sites already
    imply two different but equally valid interfaces).
  - The migration **crosses service or repo boundaries** — e.g. the duplicated
    logic lives in both `plugins/shipwright/` and `agent/`, or spans two
    repositories entirely, where a shared abstraction requires coordinating a
    dependency or publishing surface that doesn't exist yet.
  - There are **more than roughly five call sites** — beyond that scale, the risk
    of a coexist-phase regression slipping through, and the coordination cost of
    reviewing that many small PRs, is high enough to warrant a human sequencing the
    rollout rather than an autonomous executor working through the list unattended.
- No default lean either way — judge each candidate on its own facts. This is a
  judgment call made at runtime, never a count-based heuristic beyond the "~5 call
  sites" guidance above, which is itself a heuristic for human judgment to weigh,
  not a hard automatic cutoff (e.g. 4 call sites spanning two services can still be
  `hitl: true`, and 6 call sites of a trivially mechanical single-shape rename can
  still reasonably be judged `hitl: false` if every other factor points that way).

---

## Step 8: Build Task JSON

For each remaining candidate, build a task object. Reuse the `repo` and
`repo-slug` values detected in Step 5 — do not re-derive them:

```json
{
  "id": "consolidation-{fingerprint-or-slug}-{repo-slug}-{YYYY-Www}",
  "title": "Consolidation: {pattern description}",
  "source": "consolidation-fix",
  "repo": "<repo, as detected in Step 5>",
  "branch": "feat/consolidation-{fingerprint-or-slug}-{short-description}",
  "layer": "Shared",
  "status": "pending",
  "hitl": <true | false — computed per Step 7>,
  "description": "<occurrence list + canonical-shape proposal + strangler-fig step plan — see below>"
}
```

The `description` field must give `dev-task` (or the HITL executor) enough context
to execute the whole migration without re-reading `consolidation-report.md`:

```
Consolidation patrol finding: {fingerprint} — {pattern description}

Occurrences ({count} total):
- {file_path}:{line_range} — {brief note on this instance}
{Include the full occurrence list from the report. If there are more than 20, include the first 20 and append: "(+N more — re-run /consolidation-scan to see all)"}

Proposed canonical shape:
{the firmed-up canonical-shape proposal from Step 6.1}

Strangler-fig execution plan (separate PRs, do not bundle):
1. Build — {what the canonical implementation PR contains, where it lives}
2. Coexist — {step per call site or small batch, each its own PR; note both old and new must keep passing the same tests at every step}
   ...
N. Eliminate — {final PR removing the old implementation(s), once every occurrence is migrated}

Fingerprint: {fingerprint} | First seen: {firstSeen} | Last seen: {lastSeen} | HITL: {hitl}
```

The `{repo-slug}` segment is the last path segment of the detected `repo` value
(from Step 5), lowercased — e.g. `app-vitals/shipwright` → `shipwright`. It
namespaces the task ID per repo so the same pattern scanned in two different repos
in the same week never collides.

The `{YYYY-Www}` suffix in the task ID uses ISO week format, same convention as
`entropy-fix`. Compute from the current date:
- Year: 4-digit year
- `W`: literal `W`
- Week number: 2-digit zero-padded ISO week number (01–53)
- Example: `consolidation-a1b2c3d4-shipwright-2026-W29`

`fingerprint-or-slug`: prefer a short prefix of the report's fingerprint hash (e.g.
first 8 hex chars) for uniqueness; `short-description`: lowercase, hyphens, max 5
words from the pattern description.

---

## Step 9: Write and Append

1. Write all task objects to `/tmp/consolidation-tasks-{unix-timestamp}.json` as a
   JSON array.
2. Run:
   ```bash
   curl -sf -X POST \
     -H "Authorization: Bearer $SHIPWRIGHT_TASK_STORE_TOKEN" \
     -H "Content-Type: application/json" \
     "$SHIPWRIGHT_TASK_STORE_URL/tasks/bulk" \
     --data-binary @/tmp/consolidation-tasks-{unix-timestamp}.json | jq .
   ```
3. Delete the temp file after appending.

---

## Step 10: Print Summary

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CONSOLIDATION FIX — QUEUED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  QUEUED     {N} tasks   ({A} autonomous, {H} HITL)
  SKIPPED    {N} candidates (already active)
  SUPPRESSED {N} candidates (accepted as debt per consolidation-decisions.md)

Tasks queued:
  consolidation-{fingerprint-or-slug}-{repo-slug}-{YYYY-Www} — {pattern description}  [hitl: {true|false}]
  ...

{If any skipped:}
Skipped (already active):
  {fingerprint} — task already in queue or in progress

{If any suppressed:}
Suppressed (accepted as debt):
  {fingerprint} — {consolidation-decisions.md entry name}

Run /shipwright:dev-task to execute autonomous tasks. HITL tasks are picked up via
/shipwright:hitl.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

Stop after printing — this is the sole final output.

---

## Error Handling

- **Task-store query fails** (dedup in Step 5): log the failure and stop. Do not
  queue tasks without a dedup pass, or you risk duplicate tasks for the same
  pattern.
- **Bulk append fails** (`/tasks/bulk` non-2xx): log the response body and stop. Do
  not retry blindly; re-running the skill is idempotent because the dedup check
  will skip already-queued patterns.
- **No ready_to_propose candidates**: handled in Step 2 — print the "nothing to
  queue" message and stop.
- **consolidation-report.md missing**: handled in Step 1 — print the "run
  /consolidation-scan first" message and stop.

---

## Constraints (Do Not Violate)

- **One task per stabilized pattern** — never bundle multiple `ready_to_propose`
  entries into one task.
- **Queue only, no PR creation** — this skill never opens PRs and never leaves the
  base branch. It only writes tasks to the task store; the actual fix lands later
  via `dev-task` or `/shipwright:hitl`.
- **No code changes made directly by this skill.** It drafts a proposal and a plan
  in the task description; it does not implement any part of the migration itself.
- **No new task-store schema.** Every field in the task JSON (Step 8) is a field
  the task store already supports for any other task — `id`, `title`, `source`,
  `repo`, `branch`, `layer`, `status`, `hitl`, `description`. This skill introduces
  no new fields.
- **Cross-check consolidation-decisions.md live, every run** — never trust that a
  `ready_to_propose` entry in the report is still un-suppressed; a human may have
  added a new accepted-debt entry since the report was generated (Step 3).
- **This skill does not re-run consolidation-scan.** It only reads the most
  recently generated `consolidation-report.md`; if the report is stale, re-run
  `/consolidation-scan` separately first — this skill never triggers that itself.
- **No graduation/promotion to entropy-scan.** Entropy patrol (`entropy-scan`/
  `entropy-fix`) and consolidation patrol (`consolidation-scan`/`consolidation-fix`)
  stay fully decoupled by explicit decision — this skill never writes an
  entropy-scan rule, principles.md entry, or any cross-system reference. If a
  consolidation pattern seems worth graduating into a named entropy rule someday,
  that requires a separate, deliberate decision outside this skill's scope.
- **No cascade** — only queue what's in the current `consolidation-report.md`. Do
  not re-scan during a run.
- **`consolidation-report.md` is not checked off or rewritten here** — a queued
  task only means a fix is scheduled. `consolidation-scan` fully overwrites the
  report on its next run; this skill never edits it.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.