diff --git a/.agents/README.md b/.agents/README.md new file mode 100644 index 00000000..00dc2048 --- /dev/null +++ b/.agents/README.md @@ -0,0 +1,13 @@ +# AgentPMO integration + +AgentPMO is cloned separately at `../AgentPMOWorkflow`. The installed templates come from [Nimblesite/AgentPMOWorkflow](https://github.com/Nimblesite/AgentPMOWorkflow/tree/372ce7fafc82305d60be901892d965c7cbe0ce3c), revision `372ce7fafc82305d60be901892d965c7cbe0ce3c`. The upstream MIT notice is retained in `skills/AGENTPMO-LICENSE`. + +All seven template skills are in `skills/`: `fix-bug`, `ci-prep`, `submit-pr`, `upgrade-packages`, `code-dedup`, `spec-check`, and `website-audit`. Their workflows are preserved with repository context added; package-upgrade examples are limited to NuGet and npm. The root `AGENTS.md` maps upstream Makefile commands to this repository's existing commands. + +The light setup adds CodeQL for C#, website JavaScript, and Actions, and AgentPMO's grouped Dependabot staging model. The existing CI pipeline is retained as `.github/workflows/ci.yml`, with cancellation of superseded runs and an explicit F# test step. + +Dependabot's sweep uses the trusted base workflow and only handles same-repository PRs authored and triggered by Dependabot. It checks out the staging branch, merges Git data, and never executes files from the incoming PR. Updates reach `main` through a separately reviewed consolidation PR. + +Full standards enforcement, dashboard scheduling, extra developer tools, coverage-policy changes, repository restructuring, and release-workflow changes are outside this light integration. Existing application code and tests are unchanged. + +To update the integration later, compare these files with the pinned upstream templates and retain this scope; do not run the full setup or standards-enforcement workflow automatically. diff --git a/.agents/skills/AGENTPMO-LICENSE b/.agents/skills/AGENTPMO-LICENSE new file mode 100644 index 00000000..5ca533af --- /dev/null +++ b/.agents/skills/AGENTPMO-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Nimblesite + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.agents/skills/ci-prep/SKILL.md b/.agents/skills/ci-prep/SKILL.md new file mode 100644 index 00000000..e3368eba --- /dev/null +++ b/.agents/skills/ci-prep/SKILL.md @@ -0,0 +1,124 @@ +--- +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". +--- + + +## RestClient.Net context + +The CI entry point is `.github/workflows/ci.yml`; inspect its actual dotnet commands. Root `AGENTS.md` lists the local equivalents. Get Actions run IDs from `gh pr view --json statusCheckRollup` when a branch-filtered run lookup returns nothing. + +# CI Prep + +Prepare the current state for CI. If CI is already failing, fetch and analyze the logs first. + +## 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. Look in `.github/workflows/` for `ci.yml`, `build.yml`, `test.yml`, `checks.yml`, `main.yml`, `pull_request.yml`, or any 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. In a spec-compliant repo this is `make lint → make test → make build` (REPO-STANDARDS-SPEC [MAKE-TARGETS]), but the actual CI may use `npm`, `cargo`, `dotnet`, raw shell commands, or anything else. Extract what is *actually there*. +4. Note any environment variables, matrix strategies, or conditional steps that affect execution. + +**Do NOT assume the steps are `make lint`, `make test`, `make build`.** The actual CI may run different commands, in a different order. Extract what the CI *actually does*. If you find extra targets beyond the 7 in [MAKE-TARGETS] (e.g. `make fmt-check`, `make coverage-check`), flag them in your final report — they should be consolidated by the agent-pmo skill. + +### Release workflow blocker scan + +If `.github/workflows/release.yml` exists, 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. +- 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 a first-class stamper/build input. +- Missing tests that pass a test version into the same stamper used by release. +- Native VSIX releases without Node `22.x`, `npx vsce package --target `, one VSIX per + target, target-suffixed filenames, and package-content verification. +- VS Code native-binary activation that reads or mutates PATH, uses package-manager/global installs + as normal startup sources, or copies bundled VSIX binaries after install. + +## Step 3 — Run each CI step locally, in order + +Work through failures in this priority order: + +1. **Formatting** — run auto-formatters first to clear noise +2. **Compilation errors** — must compile before lint/test +3. **Lint violations** — fix the code pattern +4. **Runtime / test failures** — fix source code to satisfy the test + +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. + +### Hard constraints + +- **NEVER modify test files** — fix the source code, not the tests +- **NEVER add suppressions** (`#[allow(...)]`, `// eslint-disable`, `#pragma warning disable`) +- **NEVER use `any` in TypeScript** to silence type errors +- **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/python/rust actions, cache steps, artifact uploads) — focus on the actual build/test/lint 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 and existing failure) diff --git a/.agents/skills/code-dedup/SKILL.md b/.agents/skills/code-dedup/SKILL.md new file mode 100644 index 00000000..bb734d4d --- /dev/null +++ b/.agents/skills/code-dedup/SKILL.md @@ -0,0 +1,98 @@ +--- +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. +--- + + +## RestClient.Net context + +This is a C# and F# library repository. Use the existing MSTest/F# suites and .NET analyzers for verification. The light integration does not install Deslop or introduce a new coverage policy; check whether the scanner and required coverage evidence are available before applying this skill. Resolve `make test`/`make lint` to the existing commands in root `AGENTS.md`. + +# Code Dedup + +Find 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. + +## Prerequisites — hard gate + +Stop and report if any of these fail: + +1. **Tests green.** Run `make test` — it is fail-fast AND enforces the coverage threshold from `coverage-thresholds.json` (REPO-STANDARDS-SPEC [TEST-RULES]). Non-zero exit = stop. Never dedup a broken or under-covered codebase. Note the current coverage % — it is the floor and must not drop. +2. **Static typing.** Rust/Go/C#/F#/Dart/Java/Kotlin are typed by default. TypeScript needs `"strict": true`. Python needs Basilisk configured (REPO-STANDARDS-SPEC [LINT-PYTHON-BASILISK]). **Untyped JS or untyped Python: refuse** — print "No static type checking. Dedup without types is too risky. Add type checking first." +3. **deslop reachable** (see below), or the CLI fallback declared. + +## Required tooling — deslop + +This skill is driven by **deslop** (docs: https://deslop.live/docs/for-ai/). It is the duplicate scanner — do not substitute grep or eyeballing. **Supported languages: `csharp`, `rust`, `python`, `dart`.** + +**The MCP server is PREFERRED.** Use it when available. Key tools: +- `mcp__deslop__top-offenders` — worst clusters first (primary input to Step 3). +- `mcp__deslop__report-query` — AND-filter by `bucket`, `path_contains`, `language`, `min_score`, `min_size`; paginate with `offset`/`limit`. +- `mcp__deslop__cluster-by-id` — full record for one cluster before merging. +- `mcp__deslop__report-for-file` / `report-for-range` — narrow to a file/range during merge planning. +- `mcp__deslop__find-similar` — call BEFORE writing any replacement. Reuse if `signals.fused ≥ 0.85` or bucket `identical`/`nearly_identical`; write new if `fused < 0.6` or empty; bias to reuse in between. +- `mcp__deslop__rescan` — refresh the index; call after each merge/deletion to confirm the cluster is gone. + +**If the MCP server is unavailable, run the CLI instead** (same `.deslop.toml`, same report): +- `deslop .` — full workspace scan; reads `.deslop.toml`, writes `deslop-report.json`, exits 3 when duplication exceeds the stored threshold. **This is exactly the CI gate** — the threshold lives in `.deslop.toml`, never a CLI/YAML number ([CI-DESLOP]). +- `deslop . --no-fail-over` — same scan, but never fails on breach; local inspection only. + +Parse **only `deslop-report.json`** (the `.txt`/`.html` outputs are for humans). Decision fields: `metrics.duplication_percent`, `metrics.threshold.breached`, `clusters[].weight` (sorted desc), `clusters[].signals.fused`, `clusters[].bucket`. Re-run `deslop .` after each change in place of `rescan`. + +**Buckets** (act in this order): `identical` > `nearly_identical` > `loosely_similar` > `same_behavior`. `identical` is pure copy-paste and safest to merge. `same_behavior` needs human judgement — only on explicit request. + +**Unsupported language** (not csharp/rust/python/dart): say so up front, label every finding `(no-deslop fallback)`, and use the language analyzer + symbol-level grep. Never pretend a structural scan ran. + +**Rule:** every cluster you act on must be cited in the final report by its cluster ID, bucket, and score/`fused`. No anonymous "found some duplicates". + +## Steps + +``` +Dedup Progress: +- [ ] Step 1: Prerequisites passed (tests green, coverage noted, typed, deslop MCP or CLI confirmed) +- [ ] Step 2: Dead code scan complete +- [ ] Step 3: Duplicate code scan complete via deslop +- [ ] Step 4: Duplicate test scan complete via deslop (filtered to test paths) +- [ ] Step 5: Changes applied — each merge preceded by find-similar, followed by rescan/re-run +- [ ] Step 6: Verification passed (tests green, coverage stable, deslop confirms targeted clusters gone) +``` + +### Step 1 — Inventory coverage +Confirm the green baseline from the prerequisites. Only files WITH coverage are candidates — leave untested files alone. + +### Step 2 — Scan for dead code +Find code never called, imported, or referenced. Use the language's own signal first: Rust/Go compiler warnings, `make lint` analyzer output (C#/F#/Dart), TS `noUnusedLocals`, zero-import functions in Python. For each candidate, grep the whole codebase (tests, scripts, configs) — only dead if truly zero references. List with file:line. Do NOT delete yet. + +### Step 3 — Scan for duplicate code (deslop) +1. MCP: `top-offenders`, then `report-query { bucket: "identical" }`, then `nearly_identical`, then `loosely_similar`. CLI: run `deslop .` and read clusters from `deslop-report.json`, worst `weight` first. +2. For each cluster you intend to act on, fetch the full record (`cluster-by-id` / the report entry) and read every occurrence at its byte ranges. deslop measures structure, not semantics — if occurrences differ on a subtle condition or default, leave them and note "false positive". +3. Record `{ cluster_id, bucket, score, occurrences[], decision, rationale }`. Do NOT merge yet. + +### Step 4 — Scan for duplicate tests (deslop) +Same as Step 3, filtered to test paths: `report-query { path_contains: "test" }` (repeat for `spec`/`Tests`/`_test`), or filter `deslop-report.json` clusters by occurrence path. Keep the more thorough test (the integration/whole-app test wins if CLAUDE.md says so). Flag shared test fixtures/helpers as merge candidates rather than deletions. + +### Step 5 — Apply changes (one at a time) +Cycle per change: **change → `make test` → check coverage → continue or revert.** + +**5a. Dead code:** delete, then `make test`. Non-zero exit = revert. + +**5b. Merge duplicate code:** pick ONE cluster (worst `identical` first). Call `find-similar` before writing the replacement — reuse an existing canonical if it returns one. Extract shared logic, update call sites, `make test`. Tests fail = revert (subtle semantic difference). Coverage drops = add tests first. Then `rescan` (or re-run `deslop .`) to confirm the cluster is gone and no new one appeared. + +**5c. Duplicate tests:** delete the redundant test (keep the thorough one), `make test`. Coverage drop = revert (it covered something the other didn't). Confirm the cluster is gone. + +### Step 6 — Final verification +1. `make lint` — linters + format check pass. +2. `make test` — green AND coverage ≥ the Step 1 floor. +3. Final `rescan` / `deslop .` — every cluster you acted on resolved, top-offender list shorter than at Step 3 start. +4. Report: every cluster ID acted on (bucket, score, occurrences merged/deleted), the new top-offenders list, and final coverage vs baseline. + +## Rules + +- **deslop is the scanner when supported** (csharp/rust/python/dart). MCP preferred, CLI acceptable. Cite every cluster by ID, bucket, and score. Unreachable MCP → use the CLI; never silently fall back to grep on a supported language. +- **Unsupported language = best-effort scan, declared up front** and labelled `(no-deslop fallback)`. +- **No coverage = do not touch.** You cannot safely dedup what you cannot verify. +- **Coverage must not drop.** The Step 1 floor is sacred — revert anything that lowers it. +- **Untyped JS/Python = refuse.** Types are the safety net. +- **One change at a time.** Never batch dedup changes before testing. +- **When in doubt, leave it.** False dedup is worse than duplication. +- **Preserve public API.** Internal refactoring only — no signature/export changes external code depends on. +- **Trivial duplication is fine.** Only dedup substantial shared logic (>10 lines) or 3+ copies. diff --git a/.agents/skills/fix-bug/SKILL.md b/.agents/skills/fix-bug/SKILL.md new file mode 100644 index 00000000..33c1597d --- /dev/null +++ b/.agents/skills/fix-bug/SKILL.md @@ -0,0 +1,69 @@ +--- +name: fix-bug +description: Fix a bug using test-driven development. Use when the user reports a bug, describes unexpected behavior, wants to fix a defect, or says something is broken. Enforces a strict test-first workflow where a failing test must be written and verified before any fix is attempted. +--- + + +## RestClient.Net context + +Use the MSTest or F# test project that owns the defect. Run a focused regression with `dotnet test --configuration Release --filter FullyQualifiedName~`. Follow the user's existing authorization when moving from the demonstrated failure to the fix. See the test and Docker notes in the root `AGENTS.md`. + +# Bug Fix Skill — Test-First Workflow + +You MUST follow this exact workflow. Do NOT skip steps. Do NOT fix the bug before writing a failing test. + +## Step 1: Understand the Bug + +- Read the bug description: $ARGUMENTS +- Investigate the codebase to understand the relevant code +- Identify the root cause (or narrow down candidates) +- Summarize your understanding of the bug to the user before proceeding + +## Step 2: Write a Failing Test + +- Write a test that **directly exercises the buggy behavior** +- The test must assert the **correct/expected** behavior — so it FAILS against the current broken code +- The test name should clearly describe the bug (e.g., `test_orange_color_not_applied_to_head`) +- Use the project's existing test framework and conventions + +## Step 3: Run the Test — Confirm It FAILS + +- Run ONLY the new test (not the full suite) +- **Verify the test FAILS** and that it fails **because of the bug**, not for some other reason (typo, import error, wrong selector, etc.) +- If the test passes: your test does not capture the bug. Go back to Step 2 +- If the test fails for the wrong reason: fix the test, not the code. Go back to Step 2 +- **Repeat until the test fails specifically because of the bug** + +## Step 4: Show Failure to User + +- Show the user the test code and the failure output +- Explicitly ask: "This test fails because of the bug. Can you confirm this captures the issue before I fix it?" +- **STOP and WAIT for user acknowledgment before proceeding** +- Do NOT continue to Step 5 until the user confirms + +## Step 5: Fix the Bug + +- Make the **minimum change** needed to fix the bug +- Do not refactor, clean up, or "improve" surrounding code +- Do not change the test + +## Step 6: Run the Test — Confirm It PASSES + +- Run the new test again +- **Verify it PASSES** +- If it fails: go back to Step 5 and adjust the fix +- **Repeat until the test passes** + +## Step 7: Run the Full Test Suite + +- Run ALL tests to make sure nothing else broke +- If other tests fail: fix the regression without breaking the new test +- Report the final result to the user + +## Rules + +- NEVER fix the bug before the failing test is written and confirmed +- NEVER skip asking the user to acknowledge the test failure +- NEVER modify the test to make it pass — modify the source code +- If you cannot write a test for the bug, explain why and ask the user how to proceed +- Keep the fix minimal — one bug, one fix, one test diff --git a/.agents/skills/spec-check/SKILL.md b/.agents/skills/spec-check/SKILL.md new file mode 100644 index 00000000..2f980069 --- /dev/null +++ b/.agents/skills/spec-check/SKILL.md @@ -0,0 +1,332 @@ +--- +name: spec-check +description: Audit spec/plan documents against the codebase. Ensures every spec section has implementing code, tests, and matching logic. Use when the user says "check specs", "spec audit", or "verify specs". +--- + + +## RestClient.Net context + +Use any spec or plan documents actually present or explicitly supplied by the user. This light setup does not introduce spec IDs or require rewriting existing documentation. Relevant product documentation is in `README.md`, `Exhaustion/README.md`, `Outcome/README.md`, and `RestClient.Net.OpenApiGenerator/README.md`. + +# spec-check + +> **Portable skill.** This skill adapts to the current repository. The agent MUST inspect the repo structure and use judgment to apply these instructions appropriately. + +Audit spec/plan documents against the codebase. Ensures every spec section has implementing code, tests, and that the code logic matches the spec. + +## Arguments + +- `$ARGUMENTS` — optional spec name or ID to check (e.g., `AUTH-TOKEN-VERIFY` or `repo-standards`). If empty, check ALL specs. Spec IDs are descriptive slugs, NEVER numbered (see Step 1). + +## Instructions + +Follow these steps exactly. Be strict and pedantic. Stop on the first failure. + +--- + +### Step 1: Validate spec ID structure + +Before checking code/test references, verify that the specs themselves are well-formed. + +1. Find all spec documents (see locations in Step 2). +2. Extract every section ID using the regex `\[([A-Z][A-Z0-9]*(-[A-Z0-9]+)+)\]`. +3. **Flag invalid IDs:** + - Numbered IDs (`[SPEC-001]`, `[REQ-003]`, `[CI-004]`) — must be renamed to descriptive hierarchical slugs. + - Single-word IDs (`[TIMEOUT]`) — must have a group prefix. + - IDs with trailing numbers (`[FEAT-AUTH-01]`) — the number is meaningless, remove it. +4. **Check group clustering:** The first word of each ID is its group. All sections in the same group MUST appear together (adjacent) in the document. If they're scattered, flag it. +5. **Check for missing IDs:** Any heading that defines a requirement or behavior should have an ID. Flag headings in spec files that look like they define behavior but lack an ID. + +If any ID violations are found, report them all and **STOP**: +``` +SPEC ID VIOLATIONS: + +- docs/specs/AUTH-SPEC.md line 12: [SPEC-001] → rename to descriptive ID (e.g., [AUTH-LOGIN]) +- docs/specs/AUTH-SPEC.md line 30: [AUTH-TOKEN-VERIFY] and [AUTH-LOGIN] are not adjacent (scattered group) +- docs/specs/CI-SPEC.md line 5: "## Coverage thresholds" has no spec ID + +Fix spec IDs first, then re-run spec-check. +``` + +If all IDs are valid, proceed to Step 2. + +--- + +### Step 2: Find all spec/plan documents + +Search for markdown files that contain spec sections with IDs. Look in these locations: + +- `docs/*.md` +- `docs/**/*.md` +- `SPEC.md` +- `PLAN.md` +- `specs/*.md` + +Use Glob to find candidate files, then use Grep to confirm they contain spec IDs. + +**Spec ID patterns** — IDs appear in square brackets, typically at the start of a heading or section line. Match this regex pattern: + +``` +\[([A-Z][A-Z0-9]*(-[A-Z0-9]+)+)\] +``` + +Spec IDs are **hierarchical descriptive slugs, NEVER numbered.** The format is `[GROUP-TOPIC]` or `[GROUP-TOPIC-DETAIL]`. The first word is the **group** — all sections sharing the same group MUST appear together in the spec's table of contents. IDs are uppercase, hyphen-separated, unique across the repo, and MUST NOT contain sequential numbers. + +The hierarchy depth varies by repo: two words for simple repos (`[AUTH-LOGIN]`), three for most (`[AUTH-TOKEN-VERIFY]`), four for complex domains (`[AUTH-OAUTH-REFRESH-FLOW]`). The hierarchy mirrors the spec document's heading structure. + +Examples of valid spec IDs (note how groups cluster): +- `[AUTH-LOGIN]`, `[AUTH-TOKEN-VERIFY]`, `[AUTH-TOKEN-REFRESH]` — all in the AUTH group +- `[CI-TIMEOUT]`, `[CI-LINT]`, `[CI-RELEASE]` — all in the CI group +- `[LINT-ESLINT]`, `[LINT-RUFF]` — all in the LINT group +- `[FEAT-DARK-MODE]`, `[FEAT-SEARCH-FILTER]` — all in the FEAT group + +Examples of INVALID spec IDs: +- `[SPEC-001]` — numbered, meaningless +- `[FEAT-AUTH-01]` — trailing number +- `[REQ-003]` — sequential index, no group hierarchy +- `[CI-004]` — numbered, tells the reader nothing +- `[TIMEOUT]` — no group prefix, ungrouped + +For each file, extract every spec ID and its associated section title (the heading text after the ID) and the full section content (everything until the next heading of equal or higher level). + +--- + +### Step 3: Filter specs + +- If `$ARGUMENTS` is non-empty, filter the discovered specs: + - If it matches a spec ID exactly (e.g., `AUTH-TOKEN-VERIFY`), check only that spec. + - If it matches a partial name (e.g., `repo-standards`), check all specs in files whose path contains that string. +- If `$ARGUMENTS` is empty, process ALL discovered specs. + +If filtering produces zero specs, report an error: +``` +ERROR: No specs found matching "$ARGUMENTS". Discovered spec files: [list them] +``` + +--- + +### Step 4: Check each spec section + +For EACH spec section that has an ID, perform checks A, B, and C below. **Stop on the first failure.** + +#### Check A: Code references the spec ID + +Search the entire codebase for the spec ID string, **excluding** these directories: +- `docs/` +- `node_modules/` +- `.git/` +- `*.md` files (markdown is docs, not code) + +Use Grep with the literal spec ID (e.g., `[AUTH-TOKEN-VERIFY]`) to find references in code files. + +Code files should contain comments referencing the spec ID. The search must catch **all** comment styles across languages: + +**C-style `//` comments** (JavaScript, TypeScript, Rust, C#, F#, Java, Kotlin, Go, Swift, Dart): +- `// Implements [AUTH-TOKEN-VERIFY]` +- `// [AUTH-TOKEN-VERIFY]` +- `// Tests [AUTH-TOKEN-VERIFY]` (also counts as a code reference) +- `/// Implements [AUTH-TOKEN-VERIFY]` (doc comments) + +**Hash `#` comments** (Python, Ruby, Shell/Bash, YAML, TOML): +- `# Implements [AUTH-TOKEN-VERIFY]` +- `# [AUTH-TOKEN-VERIFY]` +- `# Tests [AUTH-TOKEN-VERIFY]` + +**HTML/XML comments** (HTML, CSS, SVG, XML, XAML, JSX templates): +- `` +- `` + +**ML-style comments** (F#, OCaml): +- `(* Implements [AUTH-TOKEN-VERIFY] *)` + +**Lua comments:** +- `-- Implements [AUTH-TOKEN-VERIFY]` + +**CSS comments:** +- `/* Implements [AUTH-TOKEN-VERIFY] */` + +**The key rule:** any comment in any language containing the exact spec ID string (e.g., `[AUTH-TOKEN-VERIFY]`) counts as a valid code reference. The Grep search uses the literal spec ID string, so it naturally matches all comment styles. Do NOT restrict the search to specific comment prefixes — just search for the spec ID string itself. + +**If NO code files reference the spec ID:** + +``` +SPEC VIOLATION: [AUTH-TOKEN-VERIFY] "Section Title" has no implementing code. + +Every spec section must have at least one code file that references it via a comment +containing the spec ID (e.g., `// Implements [AUTH-TOKEN-VERIFY]`). + +ACTION REQUIRED: Add a comment referencing [AUTH-TOKEN-VERIFY] in the file(s) that implement +this spec section, then re-run spec-check. +``` + +**STOP HERE. Do not continue to other checks.** + +#### Check B: Tests reference the spec ID + +Search test files for the spec ID. Test files are found in: +- `test/` +- `tests/` +- `**/*.test.*` +- `**/*.spec.*` +- `**/*_test.*` +- `**/test_*.*` +- `**/*Tests.*` +- `**/*Test.*` + +Use Grep to search these locations for the literal spec ID string. + +Tests should contain the spec ID in comments, test names, or annotations. The search must catch **all** test frameworks across languages: + +**JavaScript/TypeScript** (Jest, Mocha, Vitest, Playwright): +- `// Tests [AUTH-TOKEN-VERIFY]` +- `describe('[AUTH-TOKEN-VERIFY] Authentication flow', () => ...)` +- `test('[AUTH-TOKEN-VERIFY] should verify token', () => ...)` +- `it('[AUTH-TOKEN-VERIFY] verifies token', () => ...)` + +**Python** (pytest, unittest): +- `# Tests [AUTH-TOKEN-VERIFY]` +- `def test_auth_token_verify_flow():` +- `class TestAuthTokenVerify:` + +**Rust:** +- `// Tests [AUTH-TOKEN-VERIFY]` +- `#[test] // Tests [AUTH-TOKEN-VERIFY]` + +**C#** (xUnit, NUnit, MSTest): +- `// Tests [AUTH-TOKEN-VERIFY]` +- `[Fact] // Tests [AUTH-TOKEN-VERIFY]` +- `[Test] // Tests [AUTH-TOKEN-VERIFY]` +- `[TestMethod] // Tests [AUTH-TOKEN-VERIFY]` + +**F#** (xUnit, Expecto): +- `// Tests [AUTH-TOKEN-VERIFY]` +- `[] // Tests [AUTH-TOKEN-VERIFY]` +- `testCase "[AUTH-TOKEN-VERIFY] description" <| fun () ->` + +**Java/Kotlin** (JUnit, TestNG): +- `// Tests [AUTH-TOKEN-VERIFY]` +- `@Test // Tests [AUTH-TOKEN-VERIFY]` + +**Go:** +- `// Tests [AUTH-TOKEN-VERIFY]` +- `func TestAuthTokenVerify(t *testing.T) { // Tests [AUTH-TOKEN-VERIFY]` + +**Swift** (XCTest): +- `// Tests [AUTH-TOKEN-VERIFY]` +- `func testAuthTokenVerify() { // Tests [AUTH-TOKEN-VERIFY]` + +**Dart** (flutter_test): +- `// Tests [AUTH-TOKEN-VERIFY]` +- `test('[AUTH-TOKEN-VERIFY] description', () { ... });` + +**Ruby** (RSpec, Minitest): +- `# Tests [AUTH-TOKEN-VERIFY]` +- `describe '[AUTH-TOKEN-VERIFY] Authentication' do` +- `it '[AUTH-TOKEN-VERIFY] verifies token' do` + +**Shell** (bats, shunit2): +- `# Tests [AUTH-TOKEN-VERIFY]` +- `@test "[AUTH-TOKEN-VERIFY] description" {` + +**The key rule:** same as Check A — search for the literal spec ID string in test files. Any occurrence of the exact spec ID in a test file counts. Do NOT restrict to specific patterns — just search for the spec ID string itself. + +**If NO test files reference the spec ID:** + +``` +SPEC VIOLATION: [AUTH-TOKEN-VERIFY] "Section Title" has no tests. + +Every spec section must have corresponding tests that reference the spec ID. + +ACTION REQUIRED: Add tests for [AUTH-TOKEN-VERIFY] with a comment or test name containing +the spec ID, then re-run spec-check. +``` + +**STOP HERE. Do not continue to other checks.** + +#### Check C: Code logic matches the spec + +This is the most critical check. You must: + +1. **Read the spec section content carefully.** Understand exactly what behavior, logic, ordering, conditions, and constraints the spec describes. + +2. **Read the implementing code.** Use the references found in Check A to locate the implementing files. Read the relevant functions/sections. + +3. **Compare spec vs. code.** Be SENSITIVE and PEDANTIC. Check for: + - **Ordering violations** — If the spec says A happens before B, the code must do A before B. + - **Missing conditions** — If the spec says "only when X", the code must have that condition. + - **Extra behavior** — If the code does something the spec doesn't mention, flag it only if it contradicts the spec. + - **Wrong logic** — If the spec says "greater than" but code uses "greater than or equal", that's a violation. + - **Missing steps** — If the spec describes 5 steps but code only implements 3, that's a violation. + - **Wrong defaults** — If the spec says "default to X" but code defaults to Y, that's a violation. + +4. **If the code deviates from the spec**, report a detailed error: + +``` +SPEC VIOLATION: [AUTH-TOKEN-VERIFY] Code does not match spec. + +SPEC SAYS: +> "The authentication flow must verify the token expiry before checking permissions" +> (from docs/specs/AUTH-SPEC.md, line 42) + +CODE DOES: +> `if (hasPermission(user)) { verifyToken(token); }` (src/auth.ts:42) + +DEVIATION: The code checks permissions BEFORE verifying token expiry. +The spec explicitly requires token expiry verification FIRST. + +ACTION REQUIRED: Reorder the logic in src/auth.ts to verify token expiry +before checking permissions, as specified in [AUTH-TOKEN-VERIFY]. +``` + +**STOP HERE. Do not continue to other specs.** + +5. **If the code matches the spec**, this check passes. Move to the next spec. + +--- + +### Step 5: Report results + +#### On failure (any check fails): + +Output ONLY the first violation found. Use the exact error format shown above. Do not summarize other specs. Do not offer to fix the code. Just report the violation. + +End with: +``` +spec-check FAILED. Fix the violation above and re-run. +``` + +#### On success (all specs pass): + +Output a summary table: + +``` +spec-check PASSED. All specs verified. + +| Spec ID | Title | Code References | Test References | Logic Match | +|----------------|--------------------------|-----------------|-----------------|-------------| +| [AUTH-TOKEN-VERIFY] | Authentication flow | src/auth.ts | tests/auth.test.ts | PASS | +| [RATE-LIMIT-CONFIG] | Rate limiting | src/rate.ts | tests/rate.test.ts | PASS | +| ... | ... | ... | ... | ... | + +Checked N spec sections across M files. All have implementing code, tests, and matching logic. +``` + +--- + +## Search strategy summary + +1. **Validate spec IDs:** Check all IDs are hierarchical, descriptive, grouped, and non-numbered +2. **Find spec files:** Glob for `docs/**/*.md`, `SPEC.md`, `PLAN.md`, `specs/**/*.md` +3. **Extract spec IDs:** Grep for `\[[A-Z][A-Z0-9]*(-[A-Z0-9]+)+\]` in those files +4. **Find code refs:** Grep for the literal spec ID in all files, excluding `docs/`, `node_modules/`, `.git/`, `*.md` +5. **Find test refs:** Grep for the literal spec ID in test directories and test file patterns +6. **Read and compare:** Read the spec section content and the implementing code, compare logic + +## Key principles + +- **Fail fast.** Stop on the first violation. One fix at a time. +- **Be pedantic.** If the spec says it, the code must do it. No "close enough". +- **Quote everything.** Always quote the spec text and the code in error messages so the developer sees exactly what's wrong. +- **Be actionable.** Every error must tell the developer what file to change and what to do. +- **Exclude docs from code search.** Markdown files are documentation, not implementation. Only search actual code files for spec references. +- **No numbered IDs.** Spec IDs are hierarchical descriptive slugs (`[AUTH-TOKEN-VERIFY]`), NEVER sequential numbers (`[SPEC-001]`). The first word is the group — sections sharing a group must be adjacent in the TOC. If you encounter numbered or ungrouped IDs, flag them as a violation. diff --git a/.agents/skills/submit-pr/SKILL.md b/.agents/skills/submit-pr/SKILL.md new file mode 100644 index 00000000..c632dc1b --- /dev/null +++ b/.agents/skills/submit-pr/SKILL.md @@ -0,0 +1,57 @@ +--- +name: submit-pr +description: Creates a pull request with a well-structured description after verifying CI passes. Use when the user asks to submit, create, or open a pull request. +--- + + +## RestClient.Net context + +The default branch is `main`. Fetch it before comparing `origin/main...HEAD`. Use `.github/pull_request_template.md`. This light integration uses the dotnet commands in `.github/workflows/ci.yml` and root `AGENTS.md` in place of `make ci`. Respect the user's current authorization for commits, pushes, and merging. + +# Submit PR + +Create a pull request for the current branch with a well-structured description. + +⚠️ **GIT IS ALLOWED HERE — this is the exception to the repo-wide "no git" rule, and pretty much the only one.** For the purpose of *submitting and monitoring PRs* you MAY run `git add` / `git commit` / `git push` and the `gh` PR commands — to open the PR, push fixes that turn a red pipeline green, and enable/observe auto-merge. That is the entire licence: everything else (`checkout`, `merge`, `rebase`, force-push, history rewrites, cutting new branches) stays forbidden. **One ironclad condition: NEVER stamp yourself as co-author** — no `Co-Authored-By` trailer, no agent attribution, ever. This condition is never overridable. ⚠️ + +## Steps + +*NOTE: if you already ran make ci in this session and it passed, you can skip step 1.* + +1. Run `make ci` — must pass completely before creating PR +2. **Generate the diff against main.** Run `git diff main...HEAD > /tmp/pr-diff.txt` to capture the full diff between the current branch and the head of main. This is the ONLY source of truth for what the PR contains. **Warning:** the diff can be very large. If the diff file exceeds context limits, process it in chunks (e.g., read sections with `head`/`tail` or split by file) rather than trying to load it all at once. +3. **Derive the PR title and description SOLELY from the diff.** Read the diff output and summarize what changed. Ignore commit messages, branch names, and any other metadata — only the actual code/content diff matters. +4. Write PR body using the template in `.github/pull_request_template.md` +5. Fill in (based on the diff analysis from step 3): + - TLDR: one sentence + - What Was Added: new files, features, deps + - What Was Changed/Deleted: modified behaviour + - How Tests Prove It Works: specific test names or output + - Spec/Doc Changes: if any + - Breaking Changes: yes/no + description +6. Use `gh pr create` with the filled template +7. **Enable auto-merge where possible.** Right after creating the PR, run `gh pr merge --auto --squash` so GitHub squash-merges it the instant all required checks pass (and deletes the branch) — no manual click. This is best-effort: it needs auto-merge allowed on the repo ([GITHUB-MERGE]) and branch protection requiring status checks. If it errors (auto-merge disabled, no required checks, or the PR is already mergeable), note it and continue — **never block on it**. **Auto-merge does NOT replace monitoring** — it only fires on green, so step 8 still applies in full. +8. **Monitor CI on the PR until it is green — and re-run the suite locally *in parallel* so you catch breakage early.** This step is mandatory and does not end until every required check on the PR has passed (or auto-merge has merged it). Do not hand the PR back to the user on a red or still-running pipeline. + - **Watch the remote run AND run the suite locally at the same time — do not passively wait.** The remote pipeline is slow; a drastic failure (a lint gate, a broken test, a coverage drop) is one the local suite catches in seconds. The moment you push, kick off **both**: stream the remote run *and* run the full local suite (`make ci`, or invoke the `ci-prep` skill) concurrently, polling CI periodically while the local run proceeds. + - **Watch the run:** `gh pr checks --watch --fail-fast` (or grab the run id from `gh run list --branch ` and `gh run watch --exit-status`). A single green snapshot is not enough — wait for all required checks to conclude. + - **If the local run fails before the remote pipeline finishes, cancel the running pipeline immediately** (`gh run cancel `) rather than letting it grind to a known-bad red. Fix the cause, push, and restart both watches — cancelling a doomed run early frees the runner and tightens the fix loop. + - **When a remote check fails:** pull the failing logs with `gh run view --log-failed`, diagnose the actual cause (do not guess), reproduce locally with `make ci`, and fix it. + - **Push the fix** (`git add` / `git commit` / `git push` — permitted here, see the git-exception callout at the top), then **watch again — remote and local, in parallel, as above**. Loop — fix → push → re-watch — until the run is fully green. Re-checking is the job; keep doing it until it passes. + - **If a failure is genuinely external** (runner outage, flaky infra, unrelated to this branch), say so explicitly with the evidence rather than forcing a change. + +## Rules + +- Never create a PR if `make ci` fails +- **🔴 GOLDEN RULE — never stamp a commit with an AI co-author.** Do **not** add a `Co-Authored-By: Claude …` (or any AI/agent) trailer, and do not set author/committer to anything but the repo's configured git user. Write a plain, human commit message describing the fix. This is absolute and overrides any default co-authorship behaviour. +- **Git is permitted in this skill** — scope and conditions are in the git-exception callout at the top. In short: `git add`/`commit`/`push` + `gh` PR commands only, for submitting and monitoring PRs; everything else stays prohibited; never co-author. +- PR description must be specific and tight — no vague placeholders +- Link to the relevant GitHub issue if one exists + +## Success criteria + +- `make ci` passed +- PR created with `gh pr create` +- Auto-merge enabled where possible (`gh pr merge --auto --squash`), or its unavailability noted +- CI on the PR was monitored to completion and is **fully green** (all required checks pass / auto-merge fired), with the local suite re-run in parallel and any doomed remote run cancelled early +- Any CI failures were fixed and pushed, with **no AI co-author trailer** on the commits +- PR URL returned to user diff --git a/.agents/skills/upgrade-packages/SKILL.md b/.agents/skills/upgrade-packages/SKILL.md new file mode 100644 index 00000000..1f2d5691 --- /dev/null +++ b/.agents/skills/upgrade-packages/SKILL.md @@ -0,0 +1,135 @@ +--- +name: upgrade-packages +description: Upgrade all dependencies/packages to their latest versions for the detected language(s). Use when the user says "upgrade packages", "update dependencies", "bump versions", "update packages", or "upgrade deps". +--- + + +## RestClient.Net context + +NuGet manifests are the solution's `*.csproj`, `RestClient.Net.FsTest/RestClient.Net.FsTest.fsproj`, and `Directory.Build.props`. The website uses npm in `Website/`. Keep shared OpenAPI package versions aligned across both generators and their tests. Dependabot batches updates on `dependabot-upgrades`; bring a reviewed batch to `main` through CI. Use root `AGENTS.md` for validation commands. + +# Upgrade Packages + +Upgrade all project dependencies to their latest compatible (or latest major, if `--major`) versions. + +## Arguments + +- `--check-only` — List outdated packages without upgrading. Stop after Step 2. +- `--major` — Include major version bumps (breaking changes). Without this flag, stay within semver-compatible ranges. +- Any other argument is treated as a specific package name to upgrade (instead of all packages). + +## Step 1 — Detect language and package manager + +Inspect the repo root and subdirectories for manifest files. Identify ALL that apply: + +| Manifest file | Language | Package manager | +|---|---|---| +| `package.json` | Node.js / TypeScript | npm / yarn / pnpm (check lockfile) | +| `*.csproj` / `*.fsproj` / `*.sln` | C# / F# | NuGet (dotnet) | +| `Directory.Build.props` | C# / F# | NuGet (dotnet) | + +If multiple languages are present, process each one in order. + +**If you cannot detect any manifest file, stop and tell the user.** + +## Step 2 — List outdated packages + +Run the appropriate command to list what's outdated BEFORE upgrading anything. Show the user what will change. + +### Node.js (npm) +```bash +npm outdated +``` +If using yarn: `yarn outdated`. If using pnpm: `pnpm outdated`. + +**Read the docs:** +- npm: https://docs.npmjs.com/cli/v10/commands/npm-update +- yarn: https://yarnpkg.com/cli/up +- pnpm: https://pnpm.io/cli/update + +### C# / F# (NuGet) +```bash +dotnet list package --outdated +``` +For transitive dependencies too: `dotnet list package --outdated --include-transitive` + +**Read the docs:** https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-list-package + +## Step 3 — Read the official upgrade docs + +**Before running any upgrade command, you MUST fetch and read the official documentation URL listed above for the detected package manager.** Use WebFetch to retrieve the page. This ensures you use the correct flags and understand the behavior. Do not guess at flags or options from memory. + +## Step 4 — Upgrade packages + +Run the upgrade. If a specific package name was given as an argument, upgrade only that package. + +### Node.js (npm) +```bash +npm update # semver-compatible (within package.json ranges) +# --major flag: +npx npm-check-updates -u && npm install # bump package.json to latest majors +``` +If using yarn: `yarn up` / `yarn up -R '**'`. If using pnpm: `pnpm update` / `pnpm update --latest`. + +### C# / F# (NuGet) +There is NO single `dotnet upgrade-all` command. You must upgrade each package individually: +```bash +# For each outdated package from Step 2: +dotnet add package # upgrades to latest +# Or with specific version: +dotnet add package --version +``` +For `Directory.Build.props`, edit the version numbers directly in the XML. + +**Read the docs:** https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-add-package + +Alternatively, use the dotnet-outdated global tool: +```bash +dotnet tool install --global dotnet-outdated-tool +dotnet outdated --upgrade +``` +**Read the docs:** https://github.com/dotnet-outdated/dotnet-outdated + +## Step 5 — Verify the upgrade + +After upgrading, run the project's build and test suite to confirm nothing broke: + +```bash +make ci +``` + +If `make ci` is not available, run whatever build/test commands the project uses (check the Makefile, CI workflow, or CLAUDE.md). + +If tests fail: +1. Read the failure output carefully +2. Check the changelog / migration guide for the upgraded packages (fetch the release notes URL if available) +3. Fix breaking changes in the code +4. Re-run tests +5. If stuck after 3 attempts on the same failure, report it to the user with the error details and the package that caused it + +## Step 6 — Report + +Provide a summary: + +- Packages upgraded (old version -> new version) +- Packages skipped (and why, e.g., major version bump without `--major` flag) +- Build/test result after upgrade +- Any breaking changes that were fixed +- Any packages that could not be upgraded (with error details) + +## Rules + +- **Always list outdated packages first** before upgrading anything +- **Always read the official docs** for the package manager before running upgrade commands +- **Always run tests after upgrading** to catch breakage immediately +- **Never remove packages** unless they were explicitly deprecated and replaced +- **Never downgrade packages** unless rolling back a broken upgrade +- **Never modify lockfiles manually** (package-lock.json, yarn.lock, Cargo.lock, etc.) — let the package manager regenerate them +- **Commit nothing** — leave changes in the working tree for the user to review + +## Success criteria + +- All outdated packages upgraded to latest compatible (or latest major if `--major`) +- Build passes +- Tests pass +- User has a clear summary of what changed diff --git a/.agents/skills/website-audit/SKILL.md b/.agents/skills/website-audit/SKILL.md new file mode 100644 index 00000000..990cd96f --- /dev/null +++ b/.agents/skills/website-audit/SKILL.md @@ -0,0 +1,192 @@ +--- +name: website-audit +description: Audits a website for SEO, AI search performance, structured data, mobile usability, broken links, and social media cards. Fixes issues found. Use when the user mentions "audit website", "SEO", "fix search ranking", "AI search", "structured data", "social media cards", or "website performance". +--- + + +## RestClient.Net context + +The website is in `Website/`, uses Eleventy and `eleventy-plugin-techdoc`, and builds with `npm ci` then `npm run build` from that directory. Edit sources, not `Website/_site/`. Use `npx @11ty/eleventy --serve --port=8081` for an isolated preview: the existing npm dev command terminates processes on port 8080. The deployment workflow is `.github/workflows/deploy-website.yml`. + +# Website Audit + +> ⚠️ **OPERATE AUTONOMOUSLY. DO NOT STOP TO ASK THE USER QUESTIONS.** Make the +> reasonable default decision, document it in the final report, and keep going. Never +> block the audit waiting on user input — pick the sensible option (e.g. canonical URL, +> license, copy wording) and proceed. The only output the user wants is finished work +> plus a report of what you decided. No clarifying questions. No mid-run check-ins. ⚠️ + +Performs a comprehensive website audit and fixes issues affecting search visibility and AI discoverability. + +Copy this checklist and track your progress: + +``` +Audit Progress: +- [ ] Step 1: Read guidelines +- [ ] Step 2: Audit AI search readiness +- [ ] Step 3: Audit SEO and keywords +- [ ] Step 4: Audit crawling and indexing +- [ ] Step 5: Audit broken links and canonicalization +- [ ] Step 6: Audit mobile usability +- [ ] Step 7: Audit structured data +- [ ] Step 8: Audit social media cards +- [ ] Step 9: Audit For Unsubstantiated Claims +- [ ] Step 10: Audit Design Compliance +- [ ] Step 11: Test with Playwright +- [ ] Step 12: Report findings +``` + +- **Theme:** dev-tool/docs sites MUST use [`eleventy-plugin-techdoc`](https://github.com/Nimblesite/eleventy-plugin-techdoc) on Eleventy 3.x. Verify it is the theme in use, and **upgrade it (and `@11ty/eleventy`) to the latest version** before auditing, then rebuild and audit the upgraded output. +- Check the outputted HTML/CSS/JavaScript AFTER the website is generated by the static content generator. - Don't just check the static content before the website is generated. +- Fix issues at the core where the static content templates are stored - not in the outputted HTML (e.g. _site) +- Never manually edit the generated website content directly +- ENSURE THE FOOTER HAS A copyright link to nimblesite.co + +## Step 1 — Read guidelines + +Fetch and read each of these before auditing. These are the authoritative references for every step that follows. + +- [Google's guidance on using generative AI content](https://developers.google.com/search/docs/fundamentals/using-gen-ai-content) +- [Top ways to ensure content performs well in Google's AI experiences](https://developers.google.com/search/blog/2025/05/succeeding-in-ai-search) +- [SEO Starter Guide](https://developers.google.com/search/docs/fundamentals/seo-starter-guide) + +If the repo has a business plan doc, take it into account + +Identify the website source files in the repo. Determine the framework (static site generator, Next.js, Hugo, etc.) so you know where to find templates, metadata, and content. + +## Step 2 — Audit AI search readiness + +Apply the guidance from the AI search article. Check: + +1. **Content quality** — Is content original, expert-level, and comprehensive? Flag thin or duplicated pages. +2. **Clear structure** — Do pages use descriptive headings, lists, and concise answers to likely questions? +3. **Entity clarity** — Are key terms, products, and concepts defined clearly so AI can extract them? +4. **Freshness signals** — Are dates, update timestamps, and authorship present? + +Fix issues directly in the source files. For each fix, note what changed and why. + +## Step 3 — Audit SEO and keywords + +1. Search [Google Trends](https://trends.google.com/home) for trending keywords related to the website's content. +2. Review each page's ``, `<meta name="description">`, and `<h1>` tags. +3. Check for keyword opportunities — can trending terms be naturally inserted into headings, descriptions, or body content? +4. Verify each page has a unique, descriptive title (50-60 chars) and meta description (150-160 chars). +5. Check image `alt` attributes describe the image content and include relevant keywords where natural. + +Apply the [SEO Starter Guide](https://developers.google.com/search/docs/fundamentals/seo-starter-guide) principles. Fix issues directly. + +## Step 4 — Audit crawling and indexing + +Reference: [Overview of crawling and indexing topics](https://developers.google.com/search/docs/crawling-indexing) + +1. **robots.txt** — Locate and review it. Verify it doesn't block important pages. Reference: [robots.txt spec](https://developers.google.com/search/docs/crawling-indexing/robots-txt) +2. **Sitemap** — Locate the sitemap (or sitemap index). Verify all important pages are listed and no dead URLs are included. Reference: [Sitemap guidelines](https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps) +3. **Meta robots tags** — Check for unintended `noindex` or `nofollow` directives on pages that should be indexed. + +Note: robots.txt and sitemaps are often auto-generated. If so, check the generator config rather than the output file. + +## Step 5 — Audit broken links and canonicalization + +Reference: [What is canonicalization](https://developers.google.com/search/docs/crawling-indexing/canonicalization) + +1. Check all internal links resolve to valid pages (no 404s). +2. Verify `<link rel="canonical">` tags are present and point to the correct URL. +3. Check for duplicate content accessible via multiple URLs (with/without trailing slash, www vs non-www). +4. Verify redirects use 301 (permanent) not 302 (temporary) where appropriate. + +## Step 6 — Audit mobile usability + +Reference: [Mobile-first indexing best practices](https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing) + +1. Verify the `<meta name="viewport">` tag is present and correctly configured. +2. Check that content is identical between mobile and desktop (mobile-first indexing requires this). +3. Verify touch targets are adequately sized (min 48x48px). +4. Check font sizes are readable without zooming (min 16px body text). + +## Step 7 — Audit structured data + +Reference: [Structured data guidelines](https://developers.google.com/search/docs/appearance/structured-data/sd-policies) + +1. Check for existing JSON-LD `<script type="application/ld+json">` blocks. +2. Verify the structured data matches the page content (no misleading markup). +3. Add missing structured data where appropriate: + - **Organization/Person** on the homepage + - **Article/BlogPosting** on blog posts (with author, datePublished, dateModified) + - **BreadcrumbList** for navigation + - **FAQ** for pages with question/answer content +4. Validate JSON-LD syntax is correct. + +## Step 8 — Audit social media cards + +Reference: [Implementing Social Media Preview Cards](https://documentation.platformos.com/use-cases/implementing-social-media-preview-cards) + +Check every page template includes: + +**Open Graph (Facebook/LinkedIn):** +- `og:title`, `og:description`, `og:image`, `og:url`, `og:type` + +**Twitter Card:** +- `twitter:card`, `twitter:title`, `twitter:description`, `twitter:image` + +Verify `og:image` dimensions are at least 1200x630px. Fix missing or incomplete tags. + +## Step 9 - Audit For Unsubstantiated Claims + +Ensure that all claims are backed up with a link to a reputable source. As an example, this claim isn't valid as content unless it links to an authority that found this through research + +> Research shows teams with strong DevEx perform 4-5x better across speed, quality, and engagement + +Search for the authoritative URL and add a link to the URL. If it is not available, change the claim to something that can be substatiated. + +## Step 10 — Audit Design Compliance + +Read the design system docs and view the design screens in the designsystem folder. + +## Step 11 — Test with Playwright + +Build and run the website locally using `make website-run` (or the project's equivalent dev server command). + +**Desktop tests (1280x720):** + +1. Navigate to the homepage — take a screenshot. +2. Navigate to each major section — verify pages load without errors. +3. Check the browser console for JavaScript errors. +4. Verify all navigation links work. + +**Mobile tests (375x667, iPhone SE):** + +1. Resize the browser to mobile dimensions. +2. Navigate to the homepage — take a screenshot. +3. Verify the layout is responsive (no horizontal overflow, readable text). +4. Test navigation menu (hamburger menu if applicable). + +If any page fails to load or has console errors, fix the issue and retest. + +## Step 12 — Report findings + +Summarize the audit results: + +``` +## Website Audit Report + +### Fixed +- [List each issue fixed with file and line reference] + +### Warnings (manual review needed) +- [Issues that need human judgment] + +### Passed +- [Areas that passed audit with no issues] + +### Screenshots +- [Reference Playwright screenshots taken] +``` + +## Rules + +- **Operate autonomously — never stop to ask the user questions.** Make the reasonable default decision, record it in the report, and keep going to completion. +- **Fix issues directly** — don't just report them. Only flag issues as warnings when they require human judgment (e.g., content tone, keyword selection). +- **One step at a time** — complete each step before moving to the next. +- **Preserve existing content** — improve structure and metadata without rewriting the author's voice. +- **No keyword stuffing** — keywords must read naturally in context. +- **Respect the framework** — edit templates/configs, not generated output files. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..53b69eb8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,41 @@ +# agent-pmo:372ce7f +version: 2 + +updates: + - package-ecosystem: github-actions + directory: / + target-branch: dependabot-upgrades + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + github-actions: + patterns: ["*"] + + - package-ecosystem: nuget + directory: / + target-branch: dependabot-upgrades + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + nuget: + applies-to: version-updates + patterns: ["*"] + nuget-security: + applies-to: security-updates + patterns: ["*"] + + - package-ecosystem: npm + directory: /Website + target-branch: dependabot-upgrades + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + npm: + applies-to: version-updates + patterns: ["*"] + npm-security: + applies-to: security-updates + patterns: ["*"] diff --git a/.github/workflows/pr-build.yml b/.github/workflows/ci.yml similarity index 81% rename from .github/workflows/pr-build.yml rename to .github/workflows/ci.yml index 16fbbb64..c639a213 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,24 @@ -name: PR Build +# agent-pmo:372ce7f +name: CI on: pull_request: branches: - main +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - build-and-test: + ci: + name: CI + if: github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout code @@ -47,9 +58,11 @@ jobs: - name: Run all tests with code coverage run: dotnet test RestClient.sln --configuration Release --no-build --verbosity normal --logger "console;verbosity=detailed" --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Threshold=100 DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ThresholdType=line,branch,method DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ThresholdStat=total + - name: Run F# tests outside the solution + run: dotnet test RestClient.Net.FsTest/RestClient.Net.FsTest.fsproj --configuration Release --verbosity normal + - name: Cleanup Docker containers if: always() run: | cd Samples/NucliaDbClient docker compose down -v --remove-orphans || true - diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..1648fd6e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,155 @@ +# agent-pmo:372ce7f +name: CodeQL + +# CodeQL static security analysis ([GITHUB-CODE-SCANNING]). +# +# SEPARATE from ci.yml on purpose: CodeQL feeds GitHub code-scanning alerts and +# needs `security-events: write` + a weekly schedule, while ci.yml owns +# lint/test/build. It does NOT overlap with `make lint` (style/correctness) or +# dependency-review (vulnerable packages) — CodeQL finds vulnerable CODE. Never +# add security-rule linter plugins that re-cover CodeQL: no doubling up. +# +# THE MATRIX BELOW IS TAILORED PER REPO BY THE agent-pmo SKILL. The skill +# intersects (languages actually in this repo) with (languages CodeQL supports +# AT THE TIME THE SKILL RUNS — checked live, not from a frozen list) and writes +# one matrix entry per language in that intersection. Keep `actions` always (it +# scans the workflow files themselves). If the intersection is empty, the skill +# deletes this file. Action SHAs are kept current by the github-actions +# Dependabot group ([GITHUB-DEPENDABOT]). +on: + pull_request: + branches: [main] + schedule: + # Weekly, so newly-published CodeQL queries re-scan even without a push. + - cron: "27 4 * * 1" + # Release workflows can call this with gate=true to scan the exact + # released SHA with the current query set and BLOCK publishing on any + # High/Critical finding. The PR scan covers the diff, the weekly scan covers + # query drift, the gated call covers the released commit itself — as a HARD + # gate, not advice: a finding FAILS the release. This replaces the old + # standalone `push: [tags]` scan, which could only file alerts AFTER the + # artifact had already shipped — useless as a gate. [GITHUB-CODE-SCANNING] + # This light setup leaves the existing publishing workflows unchanged. + workflow_call: + inputs: + gate: + description: >- + When true (release calls), fail the job on any High/Critical finding so + the calling release workflow cannot publish. PR/weekly runs leave this + false and stay advisory (the PR check-failure threshold governs merges). + type: boolean + default: false + +permissions: + contents: read + +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + # Code scanning (SARIF upload) requires GitHub Advanced Security on PRIVATE + # repos. Gating on public visibility lets a private repo skip cleanly (no red + # X) and self-enable the moment it is made public — no follow-up edit needed. + # Dependabot PRs are excluded: they are swept into `dependabot-upgrades` by + # dependabot-automerge.yml and never merge to main directly, so scanning them + # only burns the matrix on a bump we discard — CodeQL runs on the + # consolidation PR instead. ([GITHUB-DEPENDABOT]) + if: github.event.repository.visibility == 'public' && github.actor != 'dependabot[bot]' + permissions: + security-events: write + actions: read + contents: read + strategy: + fail-fast: false + matrix: + # TAILORED BY THE SKILL — one entry per (repo language ∩ CodeQL-supported + # at runtime). `build-mode: none` suits interpreted langs + rust + csharp. + # Compiled langs that need a real build (go, java-kotlin, c-cpp) use + # `build-mode: autobuild` (or manual). NOT supported: Dart/Flutter, F#. + include: + - language: actions # scans the workflow files themselves + build-mode: none + - language: javascript-typescript + build-mode: none + - language: csharp + build-mode: none + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + category: "/language:${{ matrix.language }}" + # Drop SARIF on disk so the gate step can read it. `upload` stays on + # (default) so alerts still post to code scanning on every run. + output: sarif-results + # Release gate. `security-severity` is the 0-10 CVSS-style score CodeQL + # attaches to each security rule; >= 7.0 == High or Critical. Enforced ONLY + # on gated (release) calls — PR/weekly runs skip this and stay advisory. + # Caveat: this reads freshly produced SARIF, which does NOT reflect alert + # dismissals — a dismissed false positive re-blocks until excluded via a + # CodeQL config. FAILS CLOSED: missing/malformed SARIF errors, never passes. + # [GITHUB-CODE-SCANNING] + - name: Enforce no high/critical findings (release gate) + if: inputs.gate + shell: bash + env: + SARIF_DIR: sarif-results + SEVERITY_THRESHOLD: '7.0' + run: |- + set -euo pipefail + shopt -s nullglob + # Fail closed: no SARIF means we cannot prove the code is clean. + sarifs=( "${SARIF_DIR}"/*.sarif ) + if [ "${#sarifs[@]}" -eq 0 ]; then + echo "::error::CodeQL gate: no SARIF in ${SARIF_DIR}; cannot verify findings — failing closed." + exit 1 + fi + offenders=0 + for sarif in "${sarifs[@]}"; do + if ! jq -e '.runs' "${sarif}" >/dev/null 2>&1; then + echo "::error::CodeQL gate: ${sarif} is not valid SARIF (no .runs) — failing closed." + exit 1 + fi + # Observability: a clean scan logs results=0 with a non-zero + # severity_rules count, proving real SARIF was parsed. + jq -r --arg f "${sarif##*/}" ' + ([ (.runs[].tool.driver.rules // [])[], + (.runs[].tool.extensions[]?.rules // [])[] ]) as $rules + | "CodeQL gate: \($f): results=\([.runs[].results[]?]|length) severity_rules=\([$rules[]|select(.properties["security-severity"])]|length)" + ' "${sarif}" + # CodeQL puts query rules in tool.extensions[].rules (driver.rules is + # empty in CodeQL output); union both, then keep results >= threshold. + hits="$(jq -r --argjson t "${SEVERITY_THRESHOLD}" ' + .runs[] + | ( [ (.tool.driver.rules // [])[], + (.tool.extensions[]?.rules // [])[] ] + | map({ key: .id, + value: ((.properties["security-severity"] // "0") | tonumber) }) + | from_entries + ) as $severity + | .results[] + | select( ($severity[.ruleId] // 0) >= $t ) + | .ruleId + ' "${sarif}" | sort | uniq -c | sort -rn)" + if [ -n "${hits}" ]; then + echo "::error::High/critical CodeQL findings in ${sarif}:" + echo "${hits}" + offenders=$((offenders + 1)) + fi + done + if [ "${offenders}" -gt 0 ]; then + echo "::error::CodeQL gate failed — release blocked. Fix or dismiss-and-exclude the findings, then re-tag." + exit 1 + fi + echo "CodeQL gate passed: nothing at or above severity ${SEVERITY_THRESHOLD}." diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 00000000..2a582a14 --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,88 @@ +# agent-pmo:372ce7f +name: Dependabot auto-merge + +# Sweeps EVERY Dependabot PR into the long-lived `dependabot-upgrades` staging +# branch, no questions asked ([GITHUB-DEPENDABOT]). Two kinds of PR land here: +# +# * VERSION updates -> Dependabot opens them against `dependabot-upgrades` +# directly (.github/dependabot.yml `target-branch`). +# * SECURITY updates -> GitHub IGNORES `target-branch` for these and ALWAYS +# opens them against the default branch (`main`). So this workflow also +# triggers on `main` and folds the security bump into the SAME staging +# branch — nothing is ever left sitting on `main` waiting for a human. +# +# Merge strategy: the incoming branch ALWAYS clobbers what is already staged +# (`git merge -X theirs`). Successive bumps of the same lock-file never conflict- +# stall: the latest bump wins, every time. Nothing reaches `main` this way — the +# full build/test (ci.yml) + CodeQL (codeql.yml) gate the single +# `dependabot-upgrades -> main` consolidation PR, which is where review and the +# expensive matrix actually run. ci.yml/codeql.yml deliberately SKIP Dependabot +# PRs (they would only burn the matrix on a bump we immediately sweep away). +# +# Lives at the repo root so it is present on `dependabot-upgrades` (cut from +# main): the workflow is read from the PR's base branch, so +# BOTH `main` and the staging branch must carry this file. +on: + # Use the trusted base workflow and its write token. Never run PR code. + pull_request_target: + types: [opened, synchronize, reopened] + branches: + - dependabot-upgrades + - main + +permissions: + contents: write + pull-requests: write + +jobs: + sweep: + name: Clobber-merge into dependabot-upgrades + if: >- + github.actor == 'dependabot[bot]' && + github.event.pull_request.user.login == 'dependabot[bot]' && + github.event.pull_request.head.repo.full_name == github.repository + # Deliberately the standard runner, NOT a larger/paid one: a trivial merge + # bot must not consume CI minutes meant for the real build matrix. + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out the staging branch + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: dependabot-upgrades + fetch-depth: 0 + + - name: Clobber-merge the bump and retire the PR + env: + PR_URL: ${{ github.event.pull_request.html_url }} + PR_HEAD: ${{ github.event.pull_request.head.ref }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + # Pull the bump branch into a stable local ref we can re-merge. + git fetch origin "+refs/heads/${PR_HEAD}:refs/remotes/origin/${PR_HEAD}" + # Re-merge onto the LIVE staging tip and retry: concurrent Dependabot + # PRs race to push here, so each run rebases on whatever already landed + # and the incoming branch always wins conflicts (-X theirs). + for attempt in 1 2 3 4 5; do + git fetch origin "+refs/heads/dependabot-upgrades:refs/remotes/origin/dependabot-upgrades" + git reset --hard "origin/dependabot-upgrades" + git merge -X theirs --no-edit "origin/${PR_HEAD}" \ + -m "build(deps): clobber-merge ${PR_HEAD} into dependabot-upgrades" + if git push origin "HEAD:dependabot-upgrades"; then + break + fi + if [ "$attempt" = "5" ]; then + echo "::error::could not push to dependabot-upgrades after 5 attempts" + exit 1 + fi + sleep 5 + done + # Retire the PR + its branch: the bump is already staged, so the PR + # (whether it targeted main or the staging branch) has served its + # purpose. `|| true` — GitHub may have auto-closed it on the push. + gh pr close "$PR_URL" --delete-branch \ + --comment "Swept into \`dependabot-upgrades\` (latest bump clobbers previous)." \ + || git push origin --delete "$PR_HEAD" || true diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..17c6953e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# RestClient.Net — Agent Instructions +<!-- agent-pmo:372ce7f --> + +This is a C# and F# library repository containing RestClient.Net, Outcome, the Exhaustion analyzer, OpenAPI/MCP generators, and samples. `Website/` contains the Eleventy documentation site. + +## AgentPMO scope + +This repository uses a light AgentPMO integration: skills, existing CI, CodeQL, and staged Dependabot updates. Use the installed skills in `.agents/skills/` for relevant tasks. Follow the user's requested scope and existing authorization; a skill does not authorize a broader standards rollout. The upstream clone is separate at `../AgentPMOWorkflow`. + +## Commands + +The existing dotnet commands are the local equivalents of the Makefile commands in upstream skills. Read `.github/workflows/ci.yml` for the exact CI sequence. + +| Purpose | Command | +| --- | --- | +| Setup | `dotnet tool restore` and `dotnet restore RestClient.sln` | +| Build | `dotnet build RestClient.sln --configuration Release --no-restore /warnaserror` | +| Analysis | `dotnet build RestClient.sln --configuration Release --no-restore /p:RunAnalyzers=true /p:TreatWarningsAsErrors=true` | +| Format check | `dotnet csharpier --check .` | +| Format | `dotnet csharpier .` | +| Solution tests | `dotnet test RestClient.sln --configuration Release --no-build` | +| F# tests outside the solution | `dotnet test RestClient.Net.FsTest/RestClient.Net.FsTest.fsproj --configuration Release` | +| Mutation tests | Run `dotnet stryker --break-at 100` in `RestClient.Net.CsTest/` | +| Website build | Run `npm ci` and `npm run build` in `Website/` | + +Use .NET 8 and 9 runtimes for the repository's targets. Run focused tests while iterating, then the relevant CI checks. Preserve the existing assertions and regression coverage. Generated sample code is regenerated by the existing build targets. + +## Git and CI + +Use a feature branch and a PR to `main`; derive the PR title and description from the diff with `origin/main`. Follow the user's authorization for committing, pushing, and merging. Monitor the latest PR commit's checks and resolve failures before merging. Do not add AI co-author trailers. + +Dependabot updates accumulate on `dependabot-upgrades`; ordinary CI and CodeQL run on the consolidation PR to `main`, not on each bot bump. Never publish packages or create release tags as part of an ordinary PR. + +## Integration-test isolation + +The Nuclia integration fixture starts Docker Compose and removes its project's containers and volumes. If the host already has services, use an isolated `COMPOSE_PROJECT_NAME` and Compose configuration with non-conflicting ports before running it. Keep the heap-limited child processes in the Exhaustion regression tests intact.