Skip to content
Open
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
41 changes: 41 additions & 0 deletions .github/workflows/provider-guide-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Provider guide drift check

on:
pull_request:
branches:
- main
paths:
- "src/provider-guides/**/*.mdx"
- "scripts/drift-check/**"
- "package.json"
- "pnpm-lock.yaml"
- ".github/workflows/provider-guide-drift.yml"

jobs:
provider-guide-drift:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8

- name: Install dependencies
run: pnpm install
shell: bash

- name: Run drift check for changed provider guides
shell: bash
run: |
pnpm run drift-check -- \
--mode per-pr \
--changed-from "${{ github.event.pull_request.base.sha }}" \
--changed-to "${{ github.sha }}" \
--out /tmp/ampersand-provider-guide-drift \
--fail-on-error
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Agent instructions

## Provider guides

Before creating or editing provider guides in `src/provider-guides/`, read:

- `PROVIDER_GUIDE.md`
- `CONTRIBUTING.md`
- `scripts/drift-check/README.md`

Keep provider guides aligned with those files even when existing guides differ.

When provider guide work changes navigation, edit `src/generate-docs.ts` and regenerate `src/docs.json` with:

```shell
pnpm run gen-docs
```

Before finishing provider guide work, run the most specific drift check that applies:

```shell
pnpm run drift-check -- --mode provider --provider <providerKey> --out /tmp/ampersand-provider-drift
```

For broader provider-guide changes, run:

```shell
pnpm run drift-check -- --out /tmp/ampersand-docs-drift
```
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Claude instructions

Follow `AGENTS.md`.
110 changes: 110 additions & 0 deletions PROVIDER_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Provider guide standard

This guide is for contributors writing or editing provider guides in `src/provider-guides/`.

## Before you start

- Read `CONTRIBUTING.md` for shared writing style.
- Check the provider's catalog key and supported actions before writing claims.
- Use this file as the source of truth when existing guides conflict with it.

## File and provider mapping

Prefer naming the guide file after the provider catalog key, for example `src/provider-guides/hubspot.mdx`.

The drift check resolves a guide to a provider in this order:

1. `provider` in frontmatter
2. `scripts/drift-check/recipes.ts` slug override
3. case-insensitive catalog-key match
4. filename slug

Use `provider` frontmatter or a slug override when the public guide slug and catalog key are different strings. Case-only differences do not need a slug override.

Every guide should include frontmatter with a `title`.

```mdx
---
title: "HubSpot"
---
```

## Supported actions

Only list actions that are supported by the generated connector catalog.

Use these exact links when claiming support. The drift check matches them exactly.

```md
- [Read Actions](/read-actions)
- [Write Actions](/write-actions)
- [Proxy Actions](/proxy-actions)
- [Subscribe Actions](/subscribe-actions)
- [Search Actions](/search-actions)
```

Do not change those link labels to sentence case, such as `[Read actions]`, because the drift check will not detect the claim.

## Deep connector details

For deep connectors:

- Include a supported objects section.
- Link objects to provider documentation when object or schema docs are available.
- State clearly if incremental read is not supported.
- Add a provider-specific sample `amp.yaml` to `amp-labs/samples` and link to it from the guide.

Sample links should use this shape so drift check can validate them:

```md
https://github.com/amp-labs/samples/blob/main/<provider>/amp.yaml
```

## Provider setup

Include enough provider-side setup detail for a developer to complete the integration without guessing.

Cover these when they apply:

- how to create or access a developer app
- required OAuth redirect URLs, scopes, API keys, or secrets
- where to find credentials needed in Ampersand
- free trial, sandbox, developer instance, or test account links
- marketplace listing, app review, admin approval, or publication requirements

Use screenshots or GIFs when setup is visual or multi-step. Capture only the relevant UI and remove private, customer, or internal details.

## Navigation and generated files

Provider guide pages are registered manually in `src/generate-docs.ts`. After editing navigation, regenerate `src/docs.json` with:

```shell
pnpm run gen-docs
```

Do not run the full `pnpm run gen` just to update navigation unless you also intend to refresh generated API reference files.

## Verification

For one provider guide, run:

```shell
pnpm run drift-check -- --mode provider --provider <providerKey> --out /tmp/ampersand-provider-drift
```

CI also runs a PR-scoped drift check for changed provider guides. Running the provider check locally first catches the same class of guide-to-catalog issues before review.

For broad changes, run:

```shell
pnpm run drift-check -- --out /tmp/ampersand-docs-drift
```

The drift check currently enforces:

- undocumented providers
- guides that do not resolve to a catalog entry
- supported-action overclaims
- broken sample links

Other requirements in this guide still need reviewer judgment.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pnpm i

If you need to add a new page, add it to the `mintConfig` object in `src/generate-docs.ts`, and then follow the steps below for regenerating docs.json.

If you are adding or editing a provider guide, also follow [PROVIDER_GUIDE.md](./PROVIDER_GUIDE.md).

## Changing the URL of a page

If you are changing the URL of a page, be sure to add the old URL to the `redirects` section of the `mintConfig` object in `src/generate-docs.ts`, and then follow the steps below for regenerating docs.json.
Expand Down
3 changes: 2 additions & 1 deletion scripts/drift-check/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ pnpm run drift-check # full sweep, default ./drift-repor
pnpm run drift-check -- --out /tmp/out # custom output directory
pnpm run drift-check -- --mode provider --provider hubspot
pnpm run drift-check -- --fail-on-error # exit non-zero on any error finding
pnpm run drift-check -- --mode per-pr --changed-from "$BASE_SHA" --changed-to "$HEAD_SHA"
```

Both `--out X` and `-- --out X` (the pnpm forwarding form) work. Output is written to `drift-report.json` (canonical) and `drift-report.md` (human-readable rollup).

## Modes

- `full` (default): scan every guide; run the undocumented-provider check.
- `per-pr --changed <path>...`: scan only the listed `.mdx` files. Skips the undocumented check (needs a global view).
- `per-pr`: scan only changed `.mdx` files; skips the undocumented check (needs a global view). Pass the set as `--changed <path>...`, or as a commit range with `--changed-from <sha> --changed-to <sha>` (resolved via `git diff`, failing closed if the range cannot be computed).
- `provider --provider <catalogKey>`: scan a single guide. Useful while iterating.

## Finding types
Expand Down
54 changes: 53 additions & 1 deletion scripts/drift-check/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env tsx
import { parseArgs } from 'node:util';
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import {
fetchCatalog,
Expand All @@ -14,6 +15,50 @@ import { checkSampleLink } from './samples';
import { recipes } from './recipes';
import { writeReport, type Finding, type Report } from './report';

// per-pr mode takes the changed set either as explicit --changed paths or as a
// commit range (--changed-from/--changed-to) that we resolve with git here.
function resolveChangedPaths(values: {
changed?: string[];
'changed-from'?: string;
'changed-to'?: string;
}): string[] {
if (values.changed && values.changed.length > 0) {
return values.changed;
}
const from = values['changed-from'];
const to = values['changed-to'];
if (from && to) {
return gitChangedFiles(from, to);
}
throw new Error(
'per-pr mode requires either --changed <path>... or both --changed-from <sha> and --changed-to <sha>',
);
}

// Fail closed: a diff that cannot be computed (e.g. a shallow checkout missing a
// commit) must not be read as "nothing changed", which would let the gate pass
// on everything.
function gitChangedFiles(from: string, to: string): string[] {
let out: string;
try {
out = execFileSync(
'git',
['diff', '--name-only', '--diff-filter=ACMRT', from, to],
{ encoding: 'utf8' },
);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(
`Could not compute changed files from "git diff ${from} ${to}": ${message}. ` +
'Ensure the checkout has full history for both commits (actions/checkout fetch-depth: 0).',
);
}
return out
.split('\n')
.map((line) => line.trim())
.filter(Boolean);
}

async function main() {
// Accept both `drift-check --out X` and `pnpm run drift-check -- --out X`.
const args = process.argv.slice(2);
Expand All @@ -24,6 +69,8 @@ async function main() {
mode: { type: 'string', default: 'full' },
provider: { type: 'string' },
changed: { type: 'string', multiple: true },
'changed-from': { type: 'string' },
'changed-to': { type: 'string' },
out: { type: 'string', default: './drift-report' },
'fail-on-error': { type: 'boolean', default: false },
},
Expand All @@ -36,8 +83,13 @@ async function main() {
let guides = await scanGuides();

if (mode === 'per-pr') {
const changedSet = new Set((values.changed ?? []).map((p) => path.normalize(p)));
const changedSet = new Set(
resolveChangedPaths(values).map((p) => path.normalize(p)),
);
guides = guides.filter((g) => changedSet.has(path.normalize(g.docPath)));
if (guides.length === 0) {
console.log('No changed provider guides to check.');
}
} else if (mode === 'provider') {
if (!values.provider) {
throw new Error('--provider <key> required with --mode provider');
Expand Down
Loading