---
name: generalize-pattern
description: Take one detected upstream candidate, strip every client-specific detail, apply the generalized pattern to a template repo, and produce a reviewable PR body. Use as the apply phase of the upstream-harvest loop, one candidate at a time.
argument-hint: "[candidate-id]"
disable-model-invocation: true
---

# Generalize Pattern

Apply phase of the upstream-harvest loop. Given **one** candidate (from
`detect-upstream-candidates`) and the **template repo**, produce a single, fully
generalized change to the template plus the PR body and ledger entry.

Operates on exactly one candidate so each becomes its own independently reviewable PR.

## Inputs

`$ARGUMENTS` — the **candidate id** to apply (one of the ids emitted by
`detect-upstream-candidates`). If empty, ask which candidate to generalize.

The orchestrator (`harvest-upstream-patterns`) also provides, as context:

- **Candidate** — the full record for `$ARGUMENTS`: `id`, `intent`, `target_files`,
  `sources[]`, `corroboration_count`, `confidence`, `rationale`.
- **Template root** — the repo to modify (the harvest target).

## Step 1 — Read both sides

1. Read the source snippet(s) referenced in `candidate.sources[]`. Read **only** the
   eligible files named; do not wander into denylisted paths.
2. Read the corresponding `target_files` in the template. Understand how the template
   currently does this (or that it does not), so the change fits the template's existing
   structure and idioms — not the client's.

## Step 2 — Generalize (strip every client specific)

Apply `${CLAUDE_PLUGIN_ROOT}/skills/harvest-upstream-patterns/references/generalization-contract.md` in full. The
change written into the template must contain **zero** client specifics. Replace, in
particular:

- Client/company/project names, repo names, team handles → template placeholders.
- Hostnames, IPs, URLs, bucket/dataset/schema names → `<placeholder>` tokens.
- Concrete secrets, tokens, keys, passwords → **never** included; reference via env/secret
  manager exactly as the template already does.
- Client-specific values (ports, paths, sources, schedules, business rules) → the template's
  conventional default or a documented placeholder.
- Client business logic → excluded; harvest only the generalizable pattern, not the payload.

If a part of the pattern cannot be expressed without a client specific, **drop that part**.
If the whole candidate cannot be generalized, abort this candidate and record why — do not
propose a half-generalized change.

Match the template's style (formatting, naming, comment density, file layout). The result
should read as if written by the template's author.

## Step 3 — Apply to the template

Write the generalized change into the template's `target_files` only. Keep the diff minimal
and focused on this single candidate's `intent`. Do not opportunistically refactor or fold
in other candidates.

## Step 4 — Self-check the diff (guard)

Before writing the PR body, re-scan your own diff:

- No client name, hostname, URL, or value token remains. (Mechanically: the diff should not
  contain any client identifier from `candidate.sources[]`.)
- No secret-shaped strings.
- No denylisted file was touched.
- The change is self-contained and portable (no dangling reference to a client-only file).

If any check fails, fix or drop the candidate. This in-skill self-check is the first line
of defence. The orchestrating workflow **must** also run an equivalent mechanical grep as a
gate before opening the PR — see
`${CLAUDE_PLUGIN_ROOT}/skills/harvest-upstream-patterns/references/required-ci-gates.md`.
Do not rely on CI alone: catch leaks here.

## Step 5 — Write the PR body

Fill `${CLAUDE_PLUGIN_ROOT}/skills/harvest-upstream-patterns/assets/pr-body-template.md`:

- `<intent>` and `<rationale>`.
- **Provenance — anonymized.** Do **not** put client repo names, URLs, or PR numbers in the
  PR body. State only the aggregate: "Harvested from 1 client deployment" or, for
  `confidence: corroborated`, lead with the **🔁 Corroborated by N independent client
  deployments** banner. The precise `candidate.sources[]` refs are recorded only in the
  ledger (internal to the template repo) — see Step 6. The template repo is read by a wider
  audience than any single client repo, so naming a client here would leak the relationship.
- **"What was generalized / stripped"**: enumerate exactly what client-specific content you
  removed or replaced and with what. This is how the reviewer verifies the strip was clean.

## Step 6 — Append the ledger entry

Append a new item under the `entries:` root key in the template's
`.github/harvest-ledger.yml` (2-space indent — match the existing items; do not add a
bare top-level list item outside the key):

```yaml
entries:
  # ... existing entries ...
  - id: <candidate-id>
    status: proposed
    confidence: <single|corroborated>
    corroboration_count: <N>
    sources:
      - <org/repo#pr>
    intent: <intent>
```

## Output

State the branch (`harvest/<id>`), the files changed, the confidence, and a one-line
summary of what was stripped. In CI this hands off to `peter-evans/create-pull-request`;
locally, leave the change staged on its own branch for review.

## Rules

- One candidate, one focused change, one PR. Never bundle.
- Zero client specifics in the output. When in doubt, drop the part (or the candidate).
- Never modify denylisted files. Never include secret values.
- Match the template's conventions, not the client's.
- Corroboration affects priority and the propose threshold upstream — it does **not** relax
  the stripping standard here. A corroborated change is generalized just as strictly.
