Skip to content
Closed
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
63 changes: 12 additions & 51 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ on:
types:
- published
workflow_dispatch:
inputs:
version:
description: Version to publish (e.g. 0.3.7). Leave blank for a dry-run.
type: string
default: ""

permissions:
contents: read
Expand All @@ -20,9 +15,10 @@ concurrency:
cancel-in-progress: false

jobs:
validate:
name: Validate package
publish:
name: Publish package
runs-on: ubuntu-latest
environment: npm

steps:
- name: Checkout
Expand All @@ -43,6 +39,13 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Set version from release tag
if: github.event_name == 'release'
run: |
version="${GITHUB_REF_NAME#v}"
echo "Publishing version $version (from tag $GITHUB_REF_NAME)"
npm version "$version" --no-git-tag-version --allow-same-version

- name: Typecheck
run: npm run typecheck

Expand All @@ -56,51 +59,9 @@ jobs:
run: npm pack --dry-run

- name: Dry-run publish
if: github.event_name == 'workflow_dispatch' && inputs.version == ''
if: github.event_name == 'workflow_dispatch'
run: npm publish --access public --provenance --dry-run

publish:
name: Publish to npm
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.version != '')
needs: validate
runs-on: ubuntu-latest
environment:
name: npm
url: ${{ steps.version.outputs.url }}

steps:
- name: Resolve version
id: version
run: |
if [ "${{ github.event_name }}" = "release" ]; then
version="${GITHUB_REF_NAME#v}"
else
version="${{ inputs.version }}"
fi
echo "Publishing version $version"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "url=https://www.npmjs.com/package/@patchstack/connect/v/$version" >> "$GITHUB_OUTPUT"

- name: Checkout
uses: actions/checkout@v4
with:
ref: refs/tags/v${{ steps.version.outputs.version }}

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24.x
registry-url: https://registry.npmjs.org
cache: npm

- name: Install dependencies
run: npm ci

- name: Set package version
run: npm version "${{ steps.version.outputs.version }}" --no-git-tag-version --allow-same-version

- name: Build
run: npm run build

- name: Publish to npm
if: github.event_name == 'release'
run: npm publish --access public --provenance
76 changes: 52 additions & 24 deletions AGENT-INSTALL.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ The one-line install prompt in `README.md` ("Install prompt (for AI coding tools

Invariants when touching it:

- The prompt appears in three places that must stay identical: `README.md`, `GETTING-STARTED.md` (the teammate-facing flow), and `field-test/prompt.txt` — `prompt.txt` is the artifact the harness tests.
- Any change to the prompt, the `guide` checklist output (`src/guide.ts`), or `AGENT-INSTALL.md` must pass `node field-test/run.mjs --persona hostile --rounds 3` before shipping. Agents audit the shipped docs, so inaccuracies in `AGENT-INSTALL.md` cost trust and cause refusals.
- The prompt text appears in three places and must stay identical: `README.md`, `GETTING-STARTED.md` (the teammate-facing flow), and `field-test/prompt.txt` — `prompt.txt` is the plain-text artifact the harness tests, while the docs add Markdown blockquote syntax. `tests/docs.test.ts` enforces the invariant.
- Evaluate any change to the prompt, the `guide` checklist output (`src/guide.ts`), or `AGENT-INSTALL.md` with `node field-test/run.mjs --persona hostile --rounds 3` before shipping. If stale published artifacts or release freshness make that gate environmentally red, follow the plan B in `field-test/README.md` and record the refusal reason. Never publish merely to make a field test exercise unpublished code.
- Don't add reassurance language ("it's safe", "nothing is executed remotely") — agents flag it as a manipulation signal. Don't ask the agent to "follow the guide/instructions it prints" unbounded — name the concrete steps instead.
- A new real-world refusal report becomes a persona in `field-test/personas/` so the regression stays covered.
- The fixture installs the *published* package, so an unpublished `guide`/CLI change can't be exercised end-to-end — publish first, or accept the run validates only the prompt shape.
- The fixture installs the *published* package, so it cannot exercise unpublished `guide`, CLI, README, or `AGENT-INSTALL.md` changes end-to-end. Use the plan B until the normal, approved release process is safe to run.

## Releasing

Expand Down
42 changes: 25 additions & 17 deletions GETTING-STARTED.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,50 @@
# Getting started — connect a project to Patchstack

<!-- Editing this file? The prompt block in step 1 is a load-bearing, adversarially-tuned artifact that must stay byte-identical with README.md and field-test/prompt.txt. Read MAINTAINING.md before changing it or any setup-guide content. -->
<!-- Editing this file? Keep the prompt text in step 1 synchronized with README.md and field-test/prompt.txt; tests/docs.test.ts enforces equality after Markdown blockquote syntax is removed. Read MAINTAINING.md before changing it or any setup-guide content. -->

The fastest path from "I have a JS/Node project" to "Patchstack is monitoring its dependencies". Share this doc with anyone setting up a project; the whole flow is 2–5 minutes.
The teammate-facing path from "I have a JS/Node project" to automatic dependency monitoring, source-widget installation, and strict static-build verification.

> **Security gate:** the current pre-release backend lets the client-visible site UUID authorize manifest writes and unclaimed-site claiming. Until Patchstack separates public widget identity, manifest-write authorization, and a scoped one-time claim credential, use this flow only with disposable local test data. Do not deploy it, open or share its UUID-derived claim URL, or commit generated `.patchstackrc.json`/widget output.

## 1. Paste this prompt into your AI coding tool

The synchronized prompt describes the intended post-gate claim flow. While the security gate above remains open, use it only with disposable local test data and do not perform the claim-URL action it describes.

Works in Cursor, Claude Code, Lovable, Bolt, v0, Windsurf, etc. Paste it exactly — every clause is there for a reason (see step 2), so don't shorten it:

> Add Patchstack dependency vulnerability monitoring: install `@patchstack/connect` as a dev dependency (MIT — verify with `npm view @patchstack/connect`; source: https://github.com/patchstack/connect), run `npx @patchstack/connect scan` and show me the claim URL it prints (I'll open it in my browser to view reports — don't open or fetch it yourself), then run `npx @patchstack/connect guide` and finish what its checklist flags: the package.json build hooks and the report-a-vulnerability widget snippet.
> Add Patchstack dependency vulnerability monitoring: install `@patchstack/connect` as a dev dependency (MIT — verify with `npm view @patchstack/connect`; source: https://github.com/patchstack/connect), then use that installed copy through the project's package manager to run `patchstack-connect scan`; it resolves or provisions and saves the site UUID, then adds the report-a-vulnerability widget to a safe app shell. Show me the claim URL it prints (I'll open it in my browser — don't open or fetch it yourself), reload the preview to confirm the widget appears, then run the installed `patchstack-connect guide` and finish only what its checklist flags: package.json build hooks ending in `mark-build --strict`, or creating/fixing the framework's safe root shell before rerunning. Do not use a runner that downloads another CLI copy.

When the agent finishes it shows you a **claim URL**. Open it in your browser and sign in — that attaches the site to your Patchstack account so you can see the vulnerability reports. That's the only manual step.
After the security gate is closed, the claim URL will use a separate one-time credential; opening it in your browser and signing in will attach the monitored site to your Patchstack account. During current disposable security testing, stop before opening or sharing the printed UUID-derived URL.

## 2. If the agent refuses or stops halfway

Some agents decline this request, and that's expected behaviour, not a bug: "install a package, run its commands, follow its checklist" is the same shape as a supply-chain attack, so cautious agents audit the package first and occasionally still say no. Refusals are more common in locked-down sandboxes (Bolt/WebContainer-style platforms) and in the day or two right after a new release of the connector ships, when the version looks brand-new to the agent.
Some agents decline this request, and that's expected behaviour, not a bug: installing a package and running its commands resembles a supply-chain attack, so cautious agents audit the package first and occasionally still say no. Refusals are more common in locked-down sandboxes (Bolt/WebContainer-style platforms) and shortly after a connector release, when the version looks brand-new to the agent.

Work down this list:

1. **It asks you to confirm, or says it wants the package vetted first.** Reply with the confirmation it asked for, e.g.: *"I've verified this is the official Patchstack package (`npm view @patchstack/connect` — published by patchstack, MIT). Please continue with the remaining steps."* Most partial refusals end here.
2. **It did some steps, then stalled.** Ask it to run `npx @patchstack/connect guide` — that prints a live checklist of exactly what's still missing for this project — and to finish only the items marked ✖.
3. **It refuses outright.** Skip the agent; step 3 below is three commands.
1. **It asks you to confirm, or says it wants the package vetted first.** Reply with the confirmation it requested, for example: *"I've verified this is the official Patchstack package (`npm view @patchstack/connect` — published by patchstack, MIT). Please continue with the remaining steps."*
2. **It did some steps, then stalled.** Ask it to run the already-installed `patchstack-connect guide` through the project's package manager and finish only the missing build-hook or safe-root-shell work it reports. Do not use a runner that can fetch another CLI copy.
3. **It refuses outright.** Skip the agent and use the manual flow below.

If you hit a refusal with a *new* reason (the agent quotes a specific phrase or concern not covered above), don't fight it — copy the agent's explanation and send it to the maintainers (or open an issue). Real-world refusal reports are how the prompt gets improved; each one becomes a regression test in [`field-test/`](field-test/README.md).
If you hit a refusal with a *new* reason, copy the explanation and send it to the maintainers or open an issue. Real-world refusal reports become regression coverage in [`field-test/`](field-test/README.md).

## 3. Manual fallback (no agent needed)

For an npm-managed project:

```bash
npm install --save-dev @patchstack/connect # bun-managed projects (Lovable, Bolt): bun add -d @patchstack/connect
npx @patchstack/connect scan # registers the project, prints the claim URL — open it in your browser
npx @patchstack/connect guide # prints what's left, with the exact snippets for your project
npm install --save-dev @patchstack/connect
npx --no-install patchstack-connect scan
npx --no-install patchstack-connect guide
```

`guide` tailors its output to your project — right package manager, real site UUID, framework-specific widget placement — so finishing setup is copy-paste: the `prebuild`/`postbuild` hooks into `package.json`, and the report-a-vulnerability widget snippet into your HTML/layout file.
Use the same package manager that owns the project's lockfile: `pnpm add -D` with `pnpm exec patchstack-connect`, `yarn add -D` with `yarn patchstack-connect`, or `bun add -d` with `bun run patchstack-connect`. The installed, lockfile-pinned binary must run every command.

`scan` preflights a safe global shell before posting, provisions or reuses one identity, saves it, and idempotently installs the widget in source. `guide` then reports only remaining build-hook or safe-root-shell work. For static sites, the build must run `scan` before the compiler and `mark-build --strict` afterward, so the same identity and widget are baked into every complete generated HTML document.

## 4. You're done when

- `npx @patchstack/connect status` prints a site UUID and the claim URL.
- You've opened the claim URL in your browser and the site shows in your Patchstack dashboard.
- `npx @patchstack/connect guide` reports all steps ✔ (build hooks wired, widget installed).
- `.patchstackrc.json`, `package.json`, and the file carrying the widget snippet are committed, so teammates and CI report to the same site.
- `scan` has saved one identity and the development preview shows exactly one widget after reload.
- The installed `guide` reports all steps ✔, including build hooks and source-widget coverage.
- A production/static build succeeds with `mark-build --strict` and verifies the widget in every complete deployable HTML document.
- After the credential-separation gate closes, you have opened the separate one-time claim URL and committed only explicitly non-authorizing configuration plus `package.json` and the managed source shell. With today's single-UUID protocol, do not commit or deploy those generated files.
16 changes: 9 additions & 7 deletions MAINTAINING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ The deep "why" — the AI-agent refusal modes each clause guards against — liv

| Artifact | Rule |
|---|---|
| **The install prompt** (1 sentence) — in `README.md`, `GETTING-STARTED.md` (step 1), and `field-test/prompt.txt` | 🔴 **Don't casually edit.** Load-bearing and adversarially tuned; every clause exists because an agent refused a shorter version. Must be **byte-identical** in all three places, and any change must pass the hostile field-test gate. |
| **The install prompt** (1 sentence) — in `README.md`, `GETTING-STARTED.md` (step 1), and `field-test/prompt.txt` | 🔴 **Don't casually edit.** Load-bearing and adversarially tuned; every clause exists because an agent refused a shorter version. The prompt text must match in all three places, and any change must be evaluated with the field-test gate. |
| **`src/guide.ts`** — the `guide` checklist output | 🟠 **Edit with the gate.** Agents read this live and act on it; wrong commands or claims cause refusals. |
| **`AGENT-INSTALL.md`** — ships inside the npm tarball | 🟠 **Edit with the gate.** Must disclose **every** capability in `dist/` (e.g. the `protect` command); an undisclosed capability or overbroad privacy claim is read as misrepresentation and refused. |
| **`GETTING-STARTED.md`** — teammate-facing onboarding prose (steps 2–4) | 🟢 **Safe to improve** — *except* the quoted prompt block in step 1, which is the 🔴 artifact above. |
| **`field-test/README.md`** — dev-process rationale | 🟢 **Safe.** Dev-only; never ships in the package. |

**The trap:** "update the onboarding steps" sounds like a 🟢 job, but step 1 of `GETTING-STARTED.md` *contains* the 🔴 prompt. Improving the surrounding prose is fine; changing the quoted prompt is not, unless you change it in all three places and re-gate.

## The prompt lives in three places — keep them identical
## The prompt lives in three places — keep its text identical

`README.md` (Install prompt section), `GETTING-STARTED.md` (step 1), and `field-test/prompt.txt`. `prompt.txt` is the artifact the harness actually tests. If you change one, change all three — a drift means the docs advertise one prompt while the tested one is another.
`README.md` (Install prompt section), `GETTING-STARTED.md` (step 1), and `field-test/prompt.txt`. The first two use Markdown's `> ` blockquote prefix; `prompt.txt` is plain text and is the artifact the harness actually tests. If you change one, change all three. `tests/docs.test.ts` removes the documentation prefix and enforces exact prompt-text equality.

## Before shipping a 🔴 or 🟠 change

Expand All @@ -28,15 +28,17 @@ Run the gate:
node field-test/run.mjs --persona hostile --rounds 3
```

It runs a real AI agent through the full install in a throwaway fixture against a mocked API and scores the outcome. Read each round's `report.md` (the HESITATIONS / DECISION ANALYSIS sections) — anything an agent pauses on is a future refusal. See [`field-test/README.md`](field-test/README.md) for the improve-and-retest loop, the safety model, and what to do when the gate is red for environmental reasons (stale published docs, release freshness).
It runs a real AI agent through the full install in a throwaway fixture against a mocked API and scores the outcome. Read each round's `report.md` (the HESITATIONS / DECISION ANALYSIS sections) — anything an agent pauses on is a future refusal. See [`field-test/README.md`](field-test/README.md) for the improve-and-retest loop, the safety model, and the documented plan B when stale published docs or release freshness make the hostile gate environmentally red.

Caveat: the fixture installs the **published** package, so an unpublished `guide`/CLI change can't be exercised end-to-end — publish first, or accept that the run validates only the prompt shape.
The fixture installs the **published** package. A local prompt change is exercised, but unpublished `guide`, CLI, README, or `AGENT-INSTALL.md` changes are not. Never publish merely to make the field test exercise a working tree. While a release or security gate is open, run the stub self-test, use the appropriate persona where useful, and classify hostile failures by their recorded reason as described in the plan B. Publish only through the approved release process after every release blocker is closed.

`README.md` and `AGENT-INSTALL.md` are the npm-shipped documentation files listed in `package.json`. `GETTING-STARTED.md` is public teammate-facing documentation, but it is not currently included in the npm tarball. Confirm the package boundary with `npm pack --dry-run` whenever `package.json#files` changes.

## Don'ts (these are refusal triggers, not style nits)

- **Don't add reassurance language** ("it's safe", "nothing is executed remotely"). Agents fact-check it against the code and flag the gap as a manipulation signal.
- **Don't narrate how the prompt survives refusals** in any shipped doc (`README.md`, `AGENT-INSTALL.md`, `GETTING-STARTED.md`). Agents `npm pack` the tarball and read everything; being told the message was tuned to get past them is itself read as the signal to refuse. That rationale belongs in `field-test/README.md`, which doesn't ship.
- **Don't ask the agent to "follow the guide/instructions it prints"** unbounded — name the concrete steps (build hooks + widget snippet) instead. Unbounded delegation reads as a blank check.
- **Don't narrate how the prompt survives refusals** in the npm-shipped `README.md` or `AGENT-INSTALL.md`. Agents can inspect the tarball; deep refusal rationale belongs in `field-test/README.md`, which doesn't ship. Keep `GETTING-STARTED.md`'s user-facing fallback practical and accurate without copying the internal rationale.
- **Don't ask the agent to "follow the guide/instructions it prints"** unbounded — name the concrete remaining work (strict build hooks or a safe-root/source-widget repair). Unbounded delegation reads as a blank check.
- **Don't let the shipped docs claim more privacy than the code delivers**, or omit a capability `dist/` ships. Every contradiction between docs and code is treated as misrepresentation.

## When a new refusal shows up in the wild
Expand Down
Loading
Loading