Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions .claude/skills/ci-prep/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
name: ci-prep
description: Prepares the current branch for CI by running the exact same steps locally and fixing issues. If CI is already failing, fetches the GH Actions logs first to diagnose. Use before pushing, when CI is red, or when the user says "fix ci".
argument-hint: "[--failing] [optional job name to focus on]"
---

<!-- agent-pmo:74cf183 -->

# CI Prep

Prepare the current state for CI. If CI is already failing, fetch and analyze the logs first.

> **Repo context.** This is `eleventy-plugin-techdoc` — a plain-JavaScript (ES modules, Node >= 18) Eleventy (11ty) plugin/theme published to **npm**, plus a scaffolder CLI (`bin/init.js`, run as `npx eleventy-plugin-techdoc`). Build is a cross-platform **Makefile** with the 7 standard targets: `make build` (`npm pack --dry-run`), `make test` (CLI `--version` contract + Playwright e2e + `coverage-thresholds.json` gate, fail-fast), `make lint` (ESLint + Prettier `--check` + Shipwright manifest validation), `make fmt` (Prettier write; `CHECK=1` for check), `make clean`, `make ci` (lint + test + build), `make setup` (`npm ci` + playwright install). CI (`.github/workflows/ci.yml`) calls `npm ci` then `make lint → make test → make build`. Tests are **Playwright** e2e in `tests/` driving the `sample_website/` fixture. Lint/format are **ESLint** (`eslint .`, via `npm run lint`) and **Prettier** (`npm run fmt` / `fmt:check`).

## Arguments

- `--failing` — Indicates a GitHub Actions run is already failing. When present, you MUST execute **Step 1** before doing anything else.
- Any other argument is treated as a job name to focus on (but all failures are still reported).

If `--failing` is NOT passed, skip directly to **Step 2**.

## Step 1 — Fetch failed CI logs (only when `--failing`)

You MUST do this before any other work.

```bash
BRANCH=$(git branch --show-current)
PR_JSON=$(gh pr list --head "$BRANCH" --state open --json number,title,url --limit 1)
```

If the JSON array is empty, **stop immediately**:

> No open PR found for branch `$BRANCH`. Create a PR first.

Otherwise fetch the logs:

```bash
PR_NUMBER=$(echo "$PR_JSON" | jq -r '.[0].number')
gh pr checks "$PR_NUMBER"
RUN_ID=$(gh run list --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')
gh run view "$RUN_ID"
gh run view "$RUN_ID" --log-failed
```

Read **every line** of `--log-failed` output. For each failure note the exact file, line, and error message. If a job name argument was provided, prioritize that job but still report all failures.

## Step 2 — Analyze the CI workflow

1. Find the CI workflow file. It is `.github/workflows/ci.yml` (job `verify`). Also look for any other workflow triggered on `pull_request` or `push`.
2. Read the workflow file completely. Parse every job and every step.
3. Extract the ordered list of commands the CI actually runs. For this repo `.github/workflows/ci.yml` runs, in order:
- `npm ci` — frozen install (never bare `npm install`).
- `make lint` — `npm run lint` (`eslint .`) + `npm run fmt:check` (`prettier --check .`) + `npx --yes @nimblesite/shipwright-validate-manifest shipwright.json`.
- `make test` — the private `_cli_contract` recipe (`node bin/init.js --version` must print exactly `eleventy-plugin-techdoc <version>`; `node bin/init.js --version --json` must conform to `manifestVersion: 1`, `name`, `kind: "cli"`, `version`, `language`), then Playwright e2e (**only if both `tests/` and `sample_website/` exist**, else skipped with a notice), then the private `_coverage_check` recipe (threshold from `coverage-thresholds.json`).
- `make build` — `npm pack --dry-run` (validates the publishable package assembles).
4. Note any environment variables, matrix strategies, or conditional steps that affect execution (e.g. the `tests/` + `sample_website/` guard inside `make test`; `CI=true` forces Playwright `forbidOnly`/retries/single worker via `playwright.config.js`).

**Do NOT assume the steps.** Extract what `.github/workflows/ci.yml` _actually does_ and run exactly those commands (today: `npm ci` then `make lint`, `make test`, `make build`). If you find extra targets beyond the 7 standard Makefile targets, flag them in your final report.

### Release workflow blocker scan

`.github/workflows/release.yml` exists (tag-triggered npm publish via Trusted Publisher OIDC). Scan it before broad local CI. These are critical blockers and must be fixed before release work is considered CI-ready:

- Tag-triggered jobs checking out `ref: main` instead of the tagged SHA. (This repo checks out the tagged ref and stamps version in the runner working tree only — `package.json` and `shipwright.json` are stamped from the tag and **never committed**.)
- Any `git commit`, `git push`, branch mutation, or tag mutation during release.
- Version bump commits after the tag already exists.
- Ad hoc `sed` version stamping of structured files instead of stamping via `npm version --no-git-tag-version` / a `node` rewrite of `shipwright.json`.
- A release that publishes without the pre-publish CLI version gate (`node bin/init.js --version` must equal `eleventy-plugin-techdoc <tag-version>`).
- A publish step that uses an `NPM_TOKEN` instead of Trusted Publisher OIDC (`id-token: write`, `--provenance`).

## Step 3 — Run each CI step locally, in order

Work through failures in this priority order:

1. **Formatting** — run Prettier first to clear noise (`make fmt`, i.e. `npm run fmt`)
2. **Lint violations** — fix the code pattern (`eslint .` via `npm run lint`)
3. **Contract/validation failures** — Shipwright manifest validation and the CLI `--version` / `--version --json` contracts must pass (the latter two are the `_cli_contract` recipe inside `make test`)
4. **Runtime / test failures** — fix source code to satisfy the Playwright e2e tests

For each command extracted from the CI workflow:

1. Run the command exactly as CI would run it (adjusting only for local environment differences like not needing `actions/checkout`).
2. If the step fails, **stop and fix the issues** before continuing to the next step.
3. After fixing, re-run the same step to confirm it passes.
4. Move to the next step only after the current one succeeds.

Concretely, run locally, in CI order:

```bash
npm ci # frozen install (matches CI)
make fmt # auto-format first to clear noise (npm run fmt)
make lint # eslint . + prettier --check . + Shipwright manifest validation
make test # _cli_contract (--version / --version --json) + Playwright e2e + coverage gate
make build # npm pack --dry-run
# or run all three gates at once:
make ci # lint + test + build
```

### Hard constraints

- **NEVER modify test files** — fix the source code, not the tests
- **NEVER add suppressions** (`// eslint-disable`, `/* eslint-disable */`)
- **NEVER delete or ignore failing tests**
- **NEVER remove assertions**

If stuck on the same failure after 5 attempts, ask the user for help.

## Step 4 — Report

- List every step that was run and its result (pass/fail/fixed).
- If any step could not be fixed, report what failed and why.
- Confirm whether the branch is ready to push.

## Step 5 — Remote CI follow-up (only when `--failing`)

Once all CI steps pass locally:

1. Report the local fixes and exact commands that now pass.
2. Do not commit or push. The user owns source-control writes.
3. If the user pushes, monitor the new run until completion or failure.
4. Upon failure, go back to Step 1.

## Rules

- **Always read the CI workflow first.** Never assume what commands CI runs.
- Do not commit or push from this skill.
- Fix issues found in each step before moving to the next
- Never skip steps or suppress errors
- If the CI workflow has multiple jobs, run all of them (respecting dependency order)
- Skip steps that are CI-infrastructure-only (checkout, setup-node actions, cache steps, artifact uploads) — focus on the actual `make lint` / `make test` / `make build` commands

## Success criteria

- Every command that CI runs has been executed locally and passed
- All fixes are applied to the working tree
- The CI passes successfully (if you are correcting an existing failure)
85 changes: 85 additions & 0 deletions .claude/skills/code-dedup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
name: code-dedup
description: Searches for duplicate code, duplicate tests, and dead code, then safely merges or removes them. Use when the user says "deduplicate", "find duplicates", "remove dead code", "DRY up", or "code dedup". Requires test coverage — refuses to touch untested code.
---

<!-- agent-pmo:74cf183 -->

# Code Dedup

Carefully search for duplicate code, duplicate tests, and dead code across the repo. Merge duplicates and delete dead code — but only when test coverage proves the change is safe.

> **Repo context — READ THIS FIRST.** `eleventy-plugin-techdoc` is **plain, untyped JavaScript (ES modules, Node >= 18)** — there is no TypeScript, no `tsconfig.json`, and no static type checker. The source lives in `lib/` (`lib/index.js`, `lib/filters/`, `lib/plugins/`, `lib/shortcodes/`, `lib/virtual-templates.js`), the CLI in `bin/init.js`, plus `templates/` (`.njk`) and `assets/` (`css/`, `js/`). Tests are **Playwright** e2e run via `make test` (driving `sample_website/`); lint is `make lint` (ESLint + Prettier check + Shipwright manifest validation). The coverage gate reads `coverage-thresholds.json` inside `make test`. Because the codebase is **untyped JavaScript**, the hard gate below (Prerequisite 3) forces this skill to **REFUSE to dedup**. Do not work around that gate.

## Prerequisites — hard gate

Before touching ANY code, verify these conditions. If any fail, stop and report why.

1. Run `make test` — the CLI version contract, all Playwright e2e tests, and the coverage gate must pass. If anything fails, stop. Do not dedup a broken codebase. (Note: `make test` only runs Playwright when the `tests/` and `sample_website/` fixtures exist; if they are absent there is no e2e coverage at all, so dedup is not safe — stop.)
2. Re-confirm the green baseline from `make test`. The coverage gate reads `coverage-thresholds.json` (`default_threshold` is currently `0` because per-line instrumentation is not wired yet — Playwright e2e is the only coverage). A `0%` floor means the gate cannot catch a coverage regression, which makes silent breakage even harder to detect. Treat the effectively-absent coverage floor as a reason to be _more_ conservative, not less.
3. Verify the project uses **static typing**. This repo is **untyped JavaScript**, so: **STOP. Refuse to dedup.** Print exactly:
> "This codebase has no static type checking. Deduplication without types is reckless — too high a risk of silent breakage. Add type checking first."
4. **Confirm the deslop MCP server is available** (see "Required tooling" below). The deslop `language` filter supports only `csharp`, `rust`, and `python` — it does **not** support JavaScript. So even setting aside Prerequisite 3, a real structural duplicate scan is not available for this repo; any scan would be best-effort grep only. See the fallback rule under "Required tooling".

## Required tooling — the deslop MCP server

This skill is built around the **deslop** MCP server. **Supported languages: `csharp`, `rust`, `python` only.** For a JavaScript project like this one, deslop's clustering is **not available**; you MUST report this limitation up front rather than pretending you ran a real duplicate scan. Fall back to careful symbol-level grep and ESLint output, and clearly label the scan as "best-effort, no deslop".

When deslop is available for a supported language, the canonical workflow calls these tools in order: `mcp__deslop__session-config` → `mcp__deslop__rescan` → `mcp__deslop__top-offenders` → `mcp__deslop__report-query` → `mcp__deslop__cluster-by-id` → `mcp__deslop__report-for-file` / `mcp__deslop__report-for-range` → `mcp__deslop__find-similar` (before writing any replacement) → `mcp__deslop__rescan` (after each change). **None of that applies here because this repo is JavaScript** — deslop cannot index it.

**Bucket interpretation (for reference only):** `identical` > `nearly_identical` > `loosely_similar` > `same_behavior`.

## Steps

For THIS repo, the hard gate at Prerequisite 3 means the only correct outcome is to **refuse and report**. The steps below describe the general procedure; do not execute the mutating steps on this untyped-JavaScript codebase.

```
Dedup Progress:
- [ ] Step 1: Prerequisites evaluated (tests green? typed? deslop supported?) — for this repo: untyped JS → REFUSE
- [ ] Step 2: Dead code scan (best-effort, no deslop) — report only, do not delete
- [ ] Step 3: Duplicate code scan (no deslop for JS) — report only, do not merge
- [ ] Step 4: Duplicate test scan (no deslop for JS) — report only, do not delete
- [ ] Step 5: SKIPPED — refusing to mutate untyped JavaScript
- [ ] Step 6: SKIPPED
```

### Step 1 — Inventory test coverage

1. Run `make test` to confirm a green Playwright baseline. The recorded coverage floor is the `default_threshold` from `coverage-thresholds.json` (currently `0`, since per-line instrumentation is not wired yet).
2. Identify which files/modules in `lib/`, `bin/`, `templates/`, and `assets/` are exercised by the e2e tests in `tests/`. Only files exercised by tests are even candidates for dedup — and even then, see the untyped-JS refusal.

### Step 2 — Scan for dead code (best-effort, report only)

Search for code that is never called, never imported, never referenced.

1. Look for unused exports, unused functions, unused variables in `lib/`, `bin/init.js`, and `assets/js/`.
2. Use ESLint output where available (`npx eslint .`) — it flags unused variables/imports.
3. For each candidate: **grep the entire codebase** for references (including `tests/`, `templates/*.njk`, `sample_website/`, config files). Only mark as dead if truly zero references. Remember `.njk` templates reference filters/shortcodes/collections by string name, and `assets/` JS is loaded by templates — a symbol with zero JS-import references may still be live via a template.
4. List all dead code found with file paths and line numbers. Do NOT delete (untyped JS → refusing to mutate).

### Step 3 — Scan for duplicate code (best-effort, no deslop)

deslop does not support JavaScript, so state that up front and run a **best-effort** scan only: symbol-level grep for repeated function/method bodies across `lib/` and `bin/init.js`, plus ESLint output. Flag every finding as `(no-deslop fallback)`. Do NOT merge anything — untyped JS.

### Step 4 — Scan for duplicate tests (best-effort, no deslop)

Best-effort grep across `tests/**/*.spec.js` for near-identical Playwright test bodies. Flag findings as `(no-deslop fallback)`. Do NOT delete anything — untyped JS.

### Step 5 — Apply changes

**SKIPPED for this repo.** Untyped JavaScript fails the Prerequisite 3 hard gate; do not remove dead code, merge duplicates, or delete duplicate tests.

### Step 6 — Final verification

Not applicable — no changes were made. Re-running `make lint` and `make test` should show the same green baseline as Step 1.

## Rules

- **Untyped code = refuse to dedup.** This repo is untyped JavaScript, which is too dangerous to dedup. Types are the safety net that catches breakage at compile time; without them, silent runtime errors are near-certain. This is the governing rule for this repo.
- **Deslop does not support JavaScript = best-effort scan, declared up front.** Say so in the first message of any report and label every finding as `(no-deslop fallback)`. Never pretend a structural scan ran.
- **No test coverage = do not touch.** If a file is not exercised by the e2e tests, leave it alone entirely.
- **Coverage must not drop.** The `coverage-thresholds.json` floor is `0` (no per-line instrumentation), so the gate cannot prove coverage stayed flat — another reason to refuse mutation.
- **One change at a time.** (Only relevant on a typed repo.) Make one dedup change, run `make test`, verify, never batch.
- **When in doubt, leave it.** False dedup is worse than duplication.
- **Preserve public API surface.** Do not change exported plugin/filter/shortcode/collection names or the CLI's behavior — sites and the scaffolder depend on them. `.njk` templates and `sample_website/` bind to these by name.
- **Three similar lines is fine.** Do not abstract trivial duplication. Only substantial shared logic (>10 lines) or 3+ copies would ever justify a merge — and even then, not on untyped JS.
Loading
Loading