Host Repository Integration & Compliance Remediation
Overview
Bringing foreign code — a vendored library, a generated client, another team's
folder, an AI-authored module — into a repository fails for a boring reason:
the code is judged by the gates CI actually runs, not by the config files
sitting in the repo root. Those two things drift constantly.
Success is a target path that passes the same commands CI runs, at the same
tool versions, with a diff a reviewer can actually read.
The core discipline: the CI workflow is the source of truth; config files
are only evidence. A repo can carry a .eslintrc.json that no job invokes
and a ruff.toml whose select list CI overrides on the command line. Audit
what runs, then conform to it.
Prerequisites
The host repository root (where the standards live) and the target
path (what must comply). If the user names only one, ask which is which —
guessing inverts the entire audit.
A clean or committed git state in the host repo. Step 5 writes files.
Python >= 3.10 for scripts/extract_repo_rules.py.
Package managers only need to be installed for Step 6. Steps 1-5 are static
analysis and work offline.
Workflow
Step 1: Extract the host repo's real rules
Run the scanner from the host repository root:
python3 scripts/extract_repo_rules.py <host-repo-root> --json > /tmp/repo_rules.json
It inventories formatter/linter/type-checker configs, hook managers
(.pre-commit-config.yaml, .husky/, lefthook.yml), CONTRIBUTING.md
requirements, and — most importantly — parses .github/workflows/*.yml,
.gitlab-ci.yml, and .circleci/config.yml into an ordered list of the
run: commands that gate a merge. Read the JSON; do not re-derive this by
hand.
Resolve the three conflicts it reports before going further:
config_not_in_ci — a config file no CI job invokes. Treat it as
advisory. Do not remediate against it; say so in the report.
ci_not_in_config — CI runs a tool with no config file, so the tool's
defaults are the standard. Do not invent a config file to "fix" this.
competing_formatters — two formatters that will fight over the same
files (Prettier + Biome, Black + ruff format, gofmt + gofumpt). Pick the
one CI invokes and disable the other for the target path. Running both
produces churn on every save, forever.
Step 2: Pin the tool versions CI uses
Version skew is the single most common cause of "it's clean locally but red in
CI". Formatters change their output between minor versions.
Take versions in this order of authority: the lockfile
(package-lock.json, uv.lock, poetry.lock, Cargo.lock, go.sum) →
the rev: field in .pre-commit-config.yaml → the pinned action input
(actions/setup-node node-version, astral-sh/setup-uv version) →
the manifest range (devDependencies, [tool.ruff]). A manifest range is a
last resort — ^3.2.0 does not tell you what CI resolved.
The scanner reports these as tool_versions. If a version is unresolvable,
record it as UNPINNED in the report and use the repo's own runner
(npx --no-install, uv run, pre-commit run) rather than a globally
installed binary, which is almost certainly a different version.
- Step 3: Catalog the delta — read only, no writes
Run every gate from Step 1 against the target path in check mode
(
--check, --dry-run, --diff, -l) and record violations by gate.
Nothing is modified in this step.
Beyond tool output, check what linters do not catch:
- Naming and placement — file/directory casing (kebab vs. snake vs.
camel), and whether tests sit beside sources or in a parallel tree. Infer
from the majority convention in the host repo, not from one example.
- License headers — compare against the header on the host repo's own
recently-added files, not against
LICENSE.
- Forbidden imports — dependency-boundary rules from
import/no-restricted-paths,
ruff flake8-tidy-imports, depguard, or an ARCHITECTURE.md.
- Docstring/JSDoc conventions — presence and style, where CI enforces it
(
pydocstyle, eslint-plugin-jsdoc).
Classify each violation auto-fixable, mechanical, or manual. That split
drives Steps 4 and 5. See references/toolchain_fixers.md for the exact
check-mode and fix-mode flags per tool, and for which rule classes each fixer
genuinely resolves versus only reports.
- Step 4: Order the fixes correctly
Fix order is not cosmetic — the wrong order produces a file that fails the
gate that already passed. Apply in exactly this sequence:
- Codemods / syntax upgrades (
pyupgrade, ts-migrate, go fix) —
these rewrite AST shapes and invalidate anything downstream.
- Import sorting (
isort, ruff --select I --fix, eslint import/order --fix) — changes line counts, so it must precede anything
line-sensitive.
- Linter autofix (
ruff check --fix, eslint --fix, clippy --fix) —
may introduce code that is correct but unformatted.
- Formatter (
prettier --write, ruff format, black, gofmt) —
always last, because the formatter must have the final say on layout.
Running the formatter before the linter is the classic mistake: eslint --fix
will happily reflow what Prettier just formatted, and CI then fails on
formatting. Type-checkers (mypy, tsc) are never fixers — they run in
Step 6 as verification only.
- Step 5: Generate the two scripts
Write both into the target's parent directory, executable, and show the user
the contents before running anything.
remediate_compliance.sh — the ordered fixes from Step 4. It must:
- Take a checkpoint first: refuse to run on a dirty tree unless
--force, and
create a git stash entry or a pre-remediate/<timestamp> branch so the
user can get back. State the exact undo command in the script's output.
- Scope every command to the target path. Never invoke a repo-wide fixer —
a 4,000-file reformat buries the 40 files under review.
- Use
git mv for renames, never mv, so history survives.
- Run one gate per step, echoing the gate name, and stop on first failure
(
set -euo pipefail) so a partial run is diagnosable.
- Inject license headers idempotently — check for the marker before writing,
or reruns stack duplicate headers.
run_ci_preflight.sh — replays the Step 1 CI commands in CI order, in check
mode, against the target path. Exit 0 = the target would pass. Emit each
gate's name, command, and pass/fail so the failing gate is obvious.
Copy assets/preflight_checklist.json beside them as the machine-readable
gate manifest both scripts read, so the gate list lives in one place.
- Step 6: Verify, then report
Run
run_ci_preflight.sh. If gates still fail, they are the manual class
from Step 3 — do not loop on autofixers, which have already converged.
Fill in assets/compliance_report_template.md: per-gate status, what was
auto-fixed, what needs a human, unresolved config/CI conflicts from Step 1,
any UNPINNED tools, and the CONTRIBUTING.md items a script cannot verify
(commit message format, changelog entry, coverage threshold, sign-off).
Examples
Example 1: Vendored SDK folder in a Python monorepo
Input: "make vendor/acme-client/ comply with our repo."
Expected behavior: Scanner finds ruff + mypy in CI, and a .pre-commit-config.yaml
pinning ruff at v0.6.9 while the venv has 0.9.2 → Step 2 pins to
0.6.9 via pre-commit run. Also finds a .prettierrc no workflow invokes →
flagged config_not_in_ci, not remediated. remediate_compliance.sh runs
ruff check --fix then ruff format, scoped to vendor/acme-client/.
mypy reports 12 missing annotations → manual, listed in the report.
Example 2: The "passes locally, fails in CI" report
Input: "lint passes on my machine but CI fails on this folder."
Expected behavior: Skip to Step 2. Diff the local binary version against the
lockfile/rev: pin. The usual finding is a globally installed formatter a
minor version ahead of CI. Fix by routing through the repo's runner, not by
reformatting the code.
Error Handling
No CI workflows found — fall back to hook manager, then config files, and
state the reduced confidence in the report. Do not fabricate a pipeline.
CI uses a composite/reusable action (uses: with no run:) — the gates
are in another repo. Record the action ref as UNRESOLVED_EXTERNAL and ask
the user for the command list rather than guessing.
Monorepo, multiple toolchains — resolve the config nearest the target
path; nearest wins over root. If the target spans two packages with
different toolchains, split the audit per package.
Target is generated code — check for a generated/ exclusion in the
linter config first. If CI already excludes it, the correct outcome is no
remediation; report that rather than reformatting a generated file that
will be overwritten.
Dirty working tree — stop before Step 5. Do not stash the user's
uncommitted work without explicit confirmation.
A fixer rewrites more than the target path — abort, restore from the
Step 5 checkpoint, and re-scope. Never hand back a diff wider than requested.
Missing or unreadable reference/script in this skill — name the file and
fall back to the tool's own --help. Do not reconstruct the flag tables from
memory; a wrong --check flag silently rewrites files during Step 3, which
is meant to be read-only.
extract_repo_rules.py reports unresolved_aliases — CI runs a script
alias whose body could not be resolved (a custom binary, or a nested
Makefile target). Open the script and read it. An unresolved alias is a
blind spot, not an absent gate; never report a pass while one is outstanding.
Anti-Patterns to Avoid
Trusting config files over CI. A .eslintrc no job runs is not a
standard. Conform to the commands that gate the merge.
Running fixers before cataloging. Step 3 is read-only for a reason: once
autofixers run, the original violation set is unrecoverable and the report
becomes fiction.
Repo-wide formatting. Scope to the target. A reformat of untouched files
is an unreviewable diff and will be rejected.
Formatter before linter. See Step 4 — guarantees a CI formatting failure.
Installing tools to make gates pass. Adding a dependency changes the
host repo's contract. Report the missing tool; let the user decide.
Treating mypy/tsc as fixers. They verify. Auto-inserting # type: ignore
or any to silence them defeats the gate.
Silently "improving" business logic while remediating style. Compliance
changes and behavior changes must not share a commit.
Reference Files
scripts/extract_repo_rules.py: scans a repo root for configs, hooks,
CONTRIBUTING.md gates and CI run: commands; emits JSON with
tool_versions and the three conflict classes. Run in Step 1.
scripts/remediate_compliance.sh: checkpoint-guarded, correctly ordered
fix runner. Used as the template emitted in Step 5.
references/toolchain_fixers.md: per-tool check-mode and fix-mode flags,
what each fixer actually resolves, and competing-formatter pairs. Read in
Steps 3 and 4.
references/ci_workflow_patterns.md: extracting gates from GitHub
Actions, GitLab CI and CircleCI — matrices, composite actions, working-directory,
and where versions are pinned. Read in Steps 1 and 2.
assets/compliance_report_template.md: the Step 6 report shape.
assets/preflight_checklist.json: machine-readable gate manifest emitted
in Step 5 and consumed by both generated scripts.
Output Format
Deliver: (1) the resolved gate list with tool versions and their pin source;
(2) the violation catalog from Step 3, split auto-fixable / mechanical /
manual; (3) remediate_compliance.sh and run_ci_preflight.sh, shown before
execution; (4) the Step 6 pre-flight result per gate; and (5) the completed
compliance report. State explicitly which gates were verified by running
versus inferred statically — never present an inferred pass as a
verified one.
1---2name: integrate-repo3description: Audits an unintegrated file, folder, or vendored dependency against the host repository's real quality gates, then emits an ordered remediation script and a local CI pre-flight runner. TRIGGER when the user says 'integrate repo', 'make this folder compliant', 'match the repo style', 'align external code with our conventions', 'remediate to match CONTRIBUTING.md', 'make it pass CI locally', 'why does CI fail but lint passes locally', or drops vendored/generated/AI-authored code into a repo and asks to bring it up to standard. DO NOT TRIGGER for scaffolding a brand-new repository or service (use a project-scaffolding skill), for resolving git merge conflicts, for refactoring business logic or fixing test failures unrelated to repo standards, or for authoring the CI pipeline itself rather than conforming to it.4license: Apache-2.05---67- Host Repository Integration & Compliance Remediation89- Overview10Bringing foreign code — a vendored library, a generated client, another team's11folder, an AI-authored module — into a repository fails for a boring reason:12the code is judged by the gates CI actually runs, not by the config files13sitting in the repo root. Those two things drift constantly.1415Success is a target path that passes the *same commands CI runs, at the same16tool versions*, with a diff a reviewer can actually read.1718The core discipline: **the CI workflow is the source of truth; config files19are only evidence.** A repo can carry a `.eslintrc.json` that no job invokes20and a `ruff.toml` whose `select` list CI overrides on the command line. Audit21what runs, then conform to it.2223- Prerequisites24- The **host repository root** (where the standards live) and the **target25 path** (what must comply). If the user names only one, ask which is which —26 guessing inverts the entire audit.27- A clean or committed git state in the host repo. Step 5 writes files.28- Python >= 3.10 for `scripts/extract_repo_rules.py`.29- Package managers only need to be installed for Step 6. Steps 1-5 are static30 analysis and work offline.3132- Workflow3334- Step 1: Extract the host repo's real rules35Run the scanner from the host repository root:36```37python3 scripts/extract_repo_rules.py <host-repo-root> --json > /tmp/repo_rules.json38```39It inventories formatter/linter/type-checker configs, hook managers40(`.pre-commit-config.yaml`, `.husky/`, `lefthook.yml`), `CONTRIBUTING.md`41requirements, and — most importantly — parses `.github/workflows/*.yml`,42`.gitlab-ci.yml`, and `.circleci/config.yml` into an ordered list of the43`run:` commands that gate a merge. Read the JSON; do not re-derive this by44hand.4546Resolve the three conflicts it reports before going further:47- **`config_not_in_ci`** — a config file no CI job invokes. Treat it as48 advisory. Do not remediate against it; say so in the report.49- **`ci_not_in_config`** — CI runs a tool with no config file, so the tool's50 defaults are the standard. Do not invent a config file to "fix" this.51- **`competing_formatters`** — two formatters that will fight over the same52 files (Prettier + Biome, Black + `ruff format`, gofmt + gofumpt). Pick the53 one CI invokes and disable the other for the target path. Running both54 produces churn on every save, forever.5556- Step 2: Pin the tool versions CI uses57Version skew is the single most common cause of "it's clean locally but red in58CI". Formatters change their output between minor versions.5960Take versions in this order of authority: the lockfile61(`package-lock.json`, `uv.lock`, `poetry.lock`, `Cargo.lock`, `go.sum`) →62the `rev:` field in `.pre-commit-config.yaml` → the pinned action input63(`actions/setup-node` `node-version`, `astral-sh/setup-uv` `version`) →64the manifest range (`devDependencies`, `[tool.ruff]`). A manifest range is a65last resort — `^3.2.0` does not tell you what CI resolved.6667The scanner reports these as `tool_versions`. If a version is unresolvable,68record it as `UNPINNED` in the report and use the repo's own runner69(`npx --no-install`, `uv run`, `pre-commit run`) rather than a globally70installed binary, which is almost certainly a different version.7172- Step 3: Catalog the delta — read only, no writes73Run every gate from Step 1 against the target path in **check mode**74(`--check`, `--dry-run`, `--diff`, `-l`) and record violations by gate.75Nothing is modified in this step.7677Beyond tool output, check what linters do not catch:78- **Naming and placement** — file/directory casing (kebab vs. snake vs.79 camel), and whether tests sit beside sources or in a parallel tree. Infer80 from the majority convention in the host repo, not from one example.81- **License headers** — compare against the header on the host repo's own82 recently-added files, not against `LICENSE`.83- **Forbidden imports** — dependency-boundary rules from `import/no-restricted-paths`,84 `ruff` `flake8-tidy-imports`, `depguard`, or an `ARCHITECTURE.md`.85- **Docstring/JSDoc conventions** — presence and style, where CI enforces it86 (`pydocstyle`, `eslint-plugin-jsdoc`).8788Classify each violation `auto-fixable`, `mechanical`, or `manual`. That split89drives Steps 4 and 5. See `references/toolchain_fixers.md` for the exact90check-mode and fix-mode flags per tool, and for which rule classes each fixer91genuinely resolves versus only reports.9293- Step 4: Order the fixes correctly94Fix order is not cosmetic — the wrong order produces a file that fails the95gate that already passed. Apply in exactly this sequence:96971. **Codemods / syntax upgrades** (`pyupgrade`, `ts-migrate`, `go fix`) —98 these rewrite AST shapes and invalidate anything downstream.992. **Import sorting** (`isort`, `ruff --select I --fix`, `eslint100 import/order --fix`) — changes line counts, so it must precede anything101 line-sensitive.1023. **Linter autofix** (`ruff check --fix`, `eslint --fix`, `clippy --fix`) —103 may introduce code that is correct but unformatted.1044. **Formatter** (`prettier --write`, `ruff format`, `black`, `gofmt`) —105 **always last**, because the formatter must have the final say on layout.106107Running the formatter before the linter is the classic mistake: `eslint --fix`108will happily reflow what Prettier just formatted, and CI then fails on109formatting. Type-checkers (`mypy`, `tsc`) are never fixers — they run in110Step 6 as verification only.111112- Step 5: Generate the two scripts113Write both into the target's parent directory, executable, and **show the user114the contents before running anything**.115116`remediate_compliance.sh` — the ordered fixes from Step 4. It must:117- Take a checkpoint first: refuse to run on a dirty tree unless `--force`, and118 create a `git stash` entry or a `pre-remediate/<timestamp>` branch so the119 user can get back. State the exact undo command in the script's output.120- Scope every command to the target path. Never invoke a repo-wide fixer —121 a 4,000-file reformat buries the 40 files under review.122- Use `git mv` for renames, never `mv`, so history survives.123- Run one gate per step, echoing the gate name, and stop on first failure124 (`set -euo pipefail`) so a partial run is diagnosable.125- Inject license headers idempotently — check for the marker before writing,126 or reruns stack duplicate headers.127128`run_ci_preflight.sh` — replays the Step 1 CI commands in CI order, in check129mode, against the target path. Exit `0` = the target would pass. Emit each130gate's name, command, and pass/fail so the failing gate is obvious.131132Copy `assets/preflight_checklist.json` beside them as the machine-readable133gate manifest both scripts read, so the gate list lives in one place.134135- Step 6: Verify, then report136Run `run_ci_preflight.sh`. If gates still fail, they are the `manual` class137from Step 3 — do not loop on autofixers, which have already converged.138139Fill in `assets/compliance_report_template.md`: per-gate status, what was140auto-fixed, what needs a human, unresolved config/CI conflicts from Step 1,141any `UNPINNED` tools, and the `CONTRIBUTING.md` items a script cannot verify142(commit message format, changelog entry, coverage threshold, sign-off).143144- Examples145146- Example 1: Vendored SDK folder in a Python monorepo147Input: "make `vendor/acme-client/` comply with our repo."148Expected behavior: Scanner finds `ruff` + `mypy` in CI, and a `.pre-commit-config.yaml`149pinning `ruff` at `v0.6.9` while the venv has `0.9.2` → Step 2 pins to150`0.6.9` via `pre-commit run`. Also finds a `.prettierrc` no workflow invokes →151flagged `config_not_in_ci`, not remediated. `remediate_compliance.sh` runs152`ruff check --fix` then `ruff format`, scoped to `vendor/acme-client/`.153`mypy` reports 12 missing annotations → `manual`, listed in the report.154155- Example 2: The "passes locally, fails in CI" report156Input: "lint passes on my machine but CI fails on this folder."157Expected behavior: Skip to Step 2. Diff the local binary version against the158lockfile/`rev:` pin. The usual finding is a globally installed formatter a159minor version ahead of CI. Fix by routing through the repo's runner, not by160reformatting the code.161162- Error Handling163- **No CI workflows found** — fall back to hook manager, then config files, and164 state the reduced confidence in the report. Do not fabricate a pipeline.165- **CI uses a composite/reusable action** (`uses:` with no `run:`) — the gates166 are in another repo. Record the action ref as `UNRESOLVED_EXTERNAL` and ask167 the user for the command list rather than guessing.168- **Monorepo, multiple toolchains** — resolve the config nearest the target169 path; nearest wins over root. If the target spans two packages with170 different toolchains, split the audit per package.171- **Target is generated code** — check for a `generated/` exclusion in the172 linter config first. If CI already excludes it, the correct outcome is *no173 remediation*; report that rather than reformatting a generated file that174 will be overwritten.175- **Dirty working tree** — stop before Step 5. Do not stash the user's176 uncommitted work without explicit confirmation.177- **A fixer rewrites more than the target path** — abort, restore from the178 Step 5 checkpoint, and re-scope. Never hand back a diff wider than requested.179- **Missing or unreadable reference/script in this skill** — name the file and180 fall back to the tool's own `--help`. Do not reconstruct the flag tables from181 memory; a wrong `--check` flag silently rewrites files during Step 3, which182 is meant to be read-only.183- **`extract_repo_rules.py` reports `unresolved_aliases`** — CI runs a script184 alias whose body could not be resolved (a custom binary, or a nested185 Makefile target). Open the script and read it. An unresolved alias is a186 blind spot, not an absent gate; never report a pass while one is outstanding.187188- Anti-Patterns to Avoid189- **Trusting config files over CI.** A `.eslintrc` no job runs is not a190 standard. Conform to the commands that gate the merge.191- **Running fixers before cataloging.** Step 3 is read-only for a reason: once192 autofixers run, the original violation set is unrecoverable and the report193 becomes fiction.194- **Repo-wide formatting.** Scope to the target. A reformat of untouched files195 is an unreviewable diff and will be rejected.196- **Formatter before linter.** See Step 4 — guarantees a CI formatting failure.197- **Installing tools to make gates pass.** Adding a dependency changes the198 host repo's contract. Report the missing tool; let the user decide.199- **Treating `mypy`/`tsc` as fixers.** They verify. Auto-inserting `# type: ignore`200 or `any` to silence them defeats the gate.201- **Silently "improving" business logic** while remediating style. Compliance202 changes and behavior changes must not share a commit.203204- Reference Files205- **scripts/extract_repo_rules.py**: scans a repo root for configs, hooks,206 `CONTRIBUTING.md` gates and CI `run:` commands; emits JSON with207 `tool_versions` and the three conflict classes. Run in Step 1.208- **scripts/remediate_compliance.sh**: checkpoint-guarded, correctly ordered209 fix runner. Used as the template emitted in Step 5.210- **references/toolchain_fixers.md**: per-tool check-mode and fix-mode flags,211 what each fixer actually resolves, and competing-formatter pairs. Read in212 Steps 3 and 4.213- **references/ci_workflow_patterns.md**: extracting gates from GitHub214 Actions, GitLab CI and CircleCI — matrices, composite actions, `working-directory`,215 and where versions are pinned. Read in Steps 1 and 2.216- **assets/compliance_report_template.md**: the Step 6 report shape.217- **assets/preflight_checklist.json**: machine-readable gate manifest emitted218 in Step 5 and consumed by both generated scripts.219220- Output Format221Deliver: (1) the resolved gate list with tool versions and their pin source;222(2) the violation catalog from Step 3, split auto-fixable / mechanical /223manual; (3) `remediate_compliance.sh` and `run_ci_preflight.sh`, shown before224execution; (4) the Step 6 pre-flight result per gate; and (5) the completed225compliance report. State explicitly which gates were **verified by running**226versus **inferred statically** — never present an inferred pass as a227verified one.