From 3a9b00c0ed7a16555b31699fcf477b4cf11e80f7 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 30 May 2026 11:17:19 -0400 Subject: [PATCH 1/2] Rework docs for production routing and launch flow - Expand Fumadocs with API, automation, customer surface, and source event docs - Update production/docs URLs, launch checklist, and env examples - Refresh web docs links and agent-ready validation expectations --- .env.production.example | 2 +- README.md | 23 ++-- apps/fumadocs/content/docs/api-reference.mdx | 94 +++++++++++++++ apps/fumadocs/content/docs/automation.mdx | 96 +++++++++++++++ .../content/docs/customer-surfaces.mdx | 112 ++++++++++++++++++ apps/fumadocs/content/docs/index.mdx | 58 +++++---- apps/fumadocs/content/docs/integration.mdx | 92 +++++++------- apps/fumadocs/content/docs/launch.mdx | 23 ++-- apps/fumadocs/content/docs/meta.json | 14 ++- .../content/docs/production-routing.mdx | 83 +++++++++++++ apps/fumadocs/content/docs/quickstart.mdx | 73 +++++++++--- apps/fumadocs/content/docs/self-hosting.mdx | 11 ++ apps/fumadocs/content/docs/source-events.mdx | 82 +++++++++++++ apps/fumadocs/content/docs/source-trace.mdx | 7 ++ apps/fumadocs/next.config.mjs | 3 + apps/web/public/llms.txt | 7 +- apps/web/src/components/sign-in-form.tsx | 12 +- apps/web/src/lib/docs-url.ts | 2 +- apps/web/vite.config.ts | 25 ++++ docs/agent-ready-audit.md | 4 +- docs/agent-ready-domain-setup.md | 2 +- docs/completion-audit.md | 22 ++-- docs/launch-runbook.md | 2 +- docs/production-readiness.md | 64 +++++----- scripts/agent-ready-built-docs-core-checks.ts | 15 +++ scripts/agent-ready-built-utils.ts | 99 ++++++++++++++-- scripts/agent-ready-built-web.ts | 31 +++-- ...letion-audit-production-artifact-checks.ts | 4 +- scripts/agent-ready-live-docs-surface.ts | 27 ++++- ...agent-ready-report-surface-expectations.ts | 5 + scripts/agent-ready-web-surface-tests.ts | 9 +- scripts/build-size.ts | 37 +++++- scripts/readiness-agent-live-built-checks.ts | 7 +- scripts/readiness-production-checks.ts | 12 +- scripts/readiness-surface-checks.ts | 6 +- scripts/smoke-config-auth-checks.ts | 8 +- scripts/vercel-preview-build.ts | 33 ++++++ vercel.json | 37 +++++- 38 files changed, 1034 insertions(+), 209 deletions(-) create mode 100644 apps/fumadocs/content/docs/api-reference.mdx create mode 100644 apps/fumadocs/content/docs/automation.mdx create mode 100644 apps/fumadocs/content/docs/customer-surfaces.mdx create mode 100644 apps/fumadocs/content/docs/production-routing.mdx create mode 100644 apps/fumadocs/content/docs/source-events.mdx diff --git a/.env.production.example b/.env.production.example index 4919ef9..d868d42 100644 --- a/.env.production.example +++ b/.env.production.example @@ -1,7 +1,7 @@ # Web deployment VITE_CONVEX_URL=https://your-production-convex.convex.cloud VITE_CONVEX_SITE_URL=https://your-production-convex.convex.site -VITE_DOCS_URL=https://docs.amend.sh/docs +VITE_DOCS_URL=https://amend.sh/docs VITE_POSTHOG_TOKEN=phc_BCb25jVTo59jtEMPysgGUvgt85bUYGwN8XBNA2oMNLY7 VITE_POSTHOG_HOST=https://us.i.posthog.com VITE_POSTHOG_PROJECT_ID=441195 diff --git a/README.md b/README.md index 0900d31..892963e 100644 --- a/README.md +++ b/README.md @@ -103,15 +103,6 @@ The artifact includes a `$schema` field documented in `docs/agent-ready-status-r validate a saved status artifact with `bun run agent-ready:status:validate-report agent-ready-status.json`; add `--require-ok` when CI should fail unless the no-secret status is blocker-free. -Use `bun run agent-ready:next-steps` when an operator needs the same no-secret env, deployment, -and DNS blockers as a short checklist. Use -`bun --silent scripts/agent-ready-next-steps.ts --json --json-file agent-ready-next-steps.json` -when CI needs that checklist as an artifact. The artifact keeps the flat env list, records safe -public production values, groups missing values into web deployment and Convex deployment steps, and includes deploy/host-attachment steps -before DNS wiring. It includes a `$schema` field documented in -`docs/agent-ready-next-steps-report.schema.json`; validate a saved checklist with -`bun run agent-ready:next-steps:validate-report agent-ready-next-steps.json`; add `--require-ok` -when CI should fail unless the checklist is blocker-free. If you only need to recheck the already-deployed public hosts, run: @@ -139,8 +130,8 @@ Use `bun run agent-ready:refresh-report` when CI should refresh the saved report timestamps, validate the artifact, run the completion audit in production-blocker-tolerant mode, and still exit red while production blockers remain. Use `bun run agent-ready:final-gate` after production env and DNS are ready to regenerate the saved reports, run the strict completion audit, -refresh and validate the no-secret status and next-steps reports, require the production, live, -status, completion audit, and next-steps reports to be green, and verify synced audit evidence. Use +refresh and validate the no-secret status report, require the production, live, status, and +completion audit reports to be green, and verify synced audit evidence. Use `bun run agent-ready:completion-audit` as the strict prompt-to-artifact gate; it exits non-zero until strict readiness, production env, the live validator, and the saved production report are all green. Use @@ -153,8 +144,8 @@ schema endpoint. Validate it with add `--require-ok` when CI should fail unless the completion audit is fully green. After saving a report, run `bun run agent-ready:sync-audit agent-ready-production-report.json` to sync the launch audit timestamps, then `bun run agent-ready:audit:check` to validate the saved -production, live, status, completion audit, and next-steps reports and confirm the audits are -synced to the production report. +production, live, status, and completion audit reports and confirm the audits are synced to the +production report. The live gate checks registration, DNS delegation, A/AAAA/CNAME DNS records, final response origin, `robots.txt`, `sitemap.xml`, unique on-origin sitemap locs, `llms.txt` links aligned to the matching @@ -168,7 +159,7 @@ metadata and parseable JSON-LD, crawlable canonical/Open Graph HTML for every web sitemap page, private-route `noindex`, docs root canonical/Open Graph/parseable WebSite JSON-LD HTML, and docs index canonical/Open Graph/parseable TechArticle JSON-LD HTML plus the production, live, status, -completion audit, and next-steps report schema endpoints for `amend.sh` and `docs.amend.sh`. +and completion audit report schema endpoints for `amend.sh` and `docs.amend.sh`. ## Convex Setup @@ -245,8 +236,8 @@ Your app will connect to the configured Convex deployment automatically. The normal `bun run dev`, `bun run dev:web`, and `bun run dev:docs` commands set `WORKTREE_NAME=${WORKTREE_NAME:-$(basename "$PWD")}` and use portless. The web app reads `VITE_DOCS_URL` for docs links. Local development defaults to the matching -`http://docs.$WORKTREE_NAME.localhost:1355/docs`; this launch uses `https://docs.amend.sh/docs` in -production. +`http://docs.$WORKTREE_NAME.localhost:1355/docs`; this launch uses `https://amend.sh/docs` for +product links in production while `https://docs.amend.sh` remains the canonical docs origin. ## UI Customization diff --git a/apps/fumadocs/content/docs/api-reference.mdx b/apps/fumadocs/content/docs/api-reference.mdx new file mode 100644 index 0000000..63c0776 --- /dev/null +++ b/apps/fumadocs/content/docs/api-reference.mdx @@ -0,0 +1,94 @@ +--- +title: API Reference +description: Beta REST and SDK contract for portal reads, customer writes, owner mutations, and webhooks. +--- + +The REST API is served by Convex HTTP actions under `/api/v1`. The SDK wraps the same route +families and keeps the workspace slug in the `project` option. + +```ts +import { Amend } from "@amend/sdk"; + +const amend = new Amend({ + project: "amend-labs", + apiBaseUrl: "http://127.0.0.1:3211/api/v1", + token: process.env.AMEND_API_TOKEN, +}); +``` + +The beta OpenAPI contract lives in `packages/api-spec/openapi.yaml`. + +## Authentication + +When `AMEND_API_TOKEN` is configured, owner-level mutations require: + +```http +Authorization: Bearer +``` + +Public portal reads, feedback submissions, identity mapping, event tracking, unsubscribes, and +signed provider webhooks remain callable without the owner token where appropriate. + +## Public Reads + +| Method | Endpoint | SDK method | +| ------ | ------------------------------- | ----------------------------- | +| `GET` | `/api/v1/version` | `amend.version()` | +| `GET` | `/api/v1/:workspace/portal` | `amend.portal()` | +| `GET` | `/api/v1/:workspace/roadmap` | `amend.roadmap()` | +| `GET` | `/api/v1/:workspace/changelog` | `amend.changelog()` | +| `GET` | `/api/v1/:workspace/updates` | `amend.updatesForUser()` | +| `GET` | `/api/v1/:workspace/github-app` | `amend.githubApp()` | +| `GET` | `/api/v1/:workspace/plans` | `amend.plans()` | +| `GET` | `/api/v1/_/domains` | `amend.resolveCustomDomain()` | + +## Customer Writes + +| Method | Endpoint | SDK method | +| ------ | --------------------------------- | -------------------------------------- | +| `POST` | `/api/v1/:workspace/identity` | `amend.identify()` | +| `POST` | `/api/v1/:workspace/feedback` | `amend.submitRequest()` | +| `POST` | `/api/v1/:workspace/interactions` | `amend.vote()`, `comment()`, `react()` | +| `POST` | `/api/v1/:workspace/events` | `amend.track()` | +| `POST` | `/api/v1/:workspace/preferences` | `amend.setNotificationPreference()` | + +## Owner Reads + +| Method | Endpoint | SDK method | +| ------ | ---------------------------------- | ----------------------------- | +| `GET` | `/api/v1/:workspace/settings` | `amend.settings()` | +| `GET` | `/api/v1/:workspace/decisions` | `amend.automationDecisions()` | +| `GET` | `/api/v1/:workspace/source-events` | `amend.sourceEvents()` | +| `GET` | `/api/v1/:workspace/build-briefs` | `amend.buildBriefs()` | +| `GET` | `/api/v1/:workspace/agent-runs` | `amend.agentRuns()` | +| `GET` | `/api/v1/:workspace/deliveries` | `amend.deliveryOutbox()` | +| `GET` | `/api/v1/:workspace/projects` | `amend.projects()` | + +## Owner Mutations + +| Method | Endpoint | SDK method | +| ------ | ------------------------------------ | -------------------------------------------- | +| `POST` | `/api/v1/:workspace/projects` | `amend.createProject()` | +| `POST` | `/api/v1/:workspace/repositories` | `amend.connectRepository()` | +| `POST` | `/api/v1/:workspace/source-events` | `amend.importSourceEvent()` | +| `POST` | `/api/v1/:workspace/drafts` | `amend.draftChangelog()` | +| `POST` | `/api/v1/:workspace/changelog` | `amend.upsertChangelog()` | +| `POST` | `/api/v1/:workspace/roadmap` | `amend.upsertRoadmapItem()` | +| `POST` | `/api/v1/:workspace/rules` | `amend.updateAutomationRules()` | +| `POST` | `/api/v1/:workspace/members` | `amend.upsertWorkspaceMember()` | +| `POST` | `/api/v1/:workspace/integrations` | `amend.upsertIntegration()` | +| `POST` | `/api/v1/:workspace/portal-settings` | `amend.updatePortalSettings()` | +| `POST` | `/api/v1/:workspace/domains` | `amend.registerCustomDomain()` | +| `POST` | `/api/v1/:workspace/deliveries` | `amend.planDeliveries()`, `sendDeliveries()` | +| `POST` | `/api/v1/:workspace/plans` | `amend.updatePlan()` | +| `POST` | `/api/v1/:workspace/checkout` | `amend.createCheckoutSession()` | + +## Webhooks + +| Method | Endpoint | Verification | +| ------ | --------------------------- | -------------------------------------------------- | +| `POST` | `/api/v1/:workspace/github` | `X-Hub-Signature-256` with `GITHUB_WEBHOOK_SECRET` | +| `POST` | `/api/v1/:workspace/stripe` | Stripe webhook secret and raw body | + +Use the OpenAPI file for schema-level details and generated client types. Use this page for the +route/auth map when wiring product surfaces. diff --git a/apps/fumadocs/content/docs/automation.mdx b/apps/fumadocs/content/docs/automation.mdx new file mode 100644 index 0000000..ff61402 --- /dev/null +++ b/apps/fumadocs/content/docs/automation.mdx @@ -0,0 +1,96 @@ +--- +title: Automation +description: Configure Mostly Auto rules, AI drafting, proactive agent runs, and delivery safety. +--- + +Automation should start review-first, then become more automatic where the source evidence is strong. +Amend stores decisions with source context so teams can inspect why something changed before it +becomes public. + +## Rules + +Use Mostly Auto when safe status updates can apply automatically, but public copy and high-impact +notifications still require review. + +```ts +await amend.updateAutomationRules({ + mode: "mostly_auto", + autoDraftChangelog: true, + autoPublishChangelog: false, + autoNotifyUsers: false, + autoUpdateFeedbackStatus: true, + requireReviewBelowConfidence: 0.82, + requireReviewForHighImpact: true, + requireReviewForPublicCopy: true, +}); +``` + +## Changelog Drafting + +Local dry-runs do not require provider credentials: + +```ts +await amend.draftChangelog({ + title: "Webhook retry status", + kind: "pull_request", + dryRun: true, +}); +``` + +Production AI drafting requires provider keys in the Convex environment. Keep model policy +server-side: + +```bash +bunx convex env set OPENAI_API_KEY "replace-with-provider-key" +bunx convex env set OPENAI_MODEL "gpt-5.1-mini" +``` + +The proactive agent can also use Crof/Kimi-compatible settings: + +```bash +bunx convex env set CROF_API_KEY "replace-with-crof-key" +bunx convex env set CROF_MODEL "kimi-k2.6" +bunx convex env set CROF_BASE_URL "https://crof.ai/v1" +``` + +If the provider is missing or returns invalid output, the backend records a local fallback decision +instead of inventing source links. + +## Decisions And Build Briefs + +The SDK exposes the agent-readable review queues: + +```ts +const decisions = await amend.automationDecisions(); +const runs = await amend.agentRuns({ projectSlug: "web-app" }); +const briefs = await amend.buildBriefs({ status: "in_review" }); +``` + +Use these records to build reviewer workflows or hand a coding agent a demand-backed brief. + +## Delivery Outbox + +Plan deliveries before sending them. Keep `dryRun: true` until email, Slack, or webhook credentials +are verified. + +```ts +await amend.planDeliveries({ + channel: "email", + notificationKey: "notification-changelog-review-ready", +}); + +await amend.sendDeliveries({ + channel: "email", + dryRun: true, + limit: 10, +}); +``` + +Real email sends require `RESEND_API_KEY` and `EMAIL_FROM` in Convex. + +## Safety Bar + +- Public copy should stay reviewable until the workspace explicitly allows auto-publish. +- Low-confidence source matches should suggest investigation, not publish. +- Delivery sends should start as dry-runs and move to production only after a test recipient works. +- Every automated action should leave enough source evidence for a reviewer to reverse or edit it. diff --git a/apps/fumadocs/content/docs/customer-surfaces.mdx b/apps/fumadocs/content/docs/customer-surfaces.mdx new file mode 100644 index 0000000..601bf7e --- /dev/null +++ b/apps/fumadocs/content/docs/customer-surfaces.mdx @@ -0,0 +1,112 @@ +--- +title: Customer Surfaces +description: Wire customer identity, feedback, update feeds, and the embedded Amend panel. +--- + +Customer surfaces are the parts of Amend that live inside your product or public portal. Start with +identity, then feedback, then the update feed. The source-linked automation only becomes useful +after Amend knows who asked for what. + +## Identity + +Use IDs from your application database. Email helps notification delivery and support lookup, but it +should not be the only stable identifier. + +```ts +import { Amend } from "@amend/sdk"; + +const amend = new Amend({ + project: "amend-labs", + apiBaseUrl: "http://127.0.0.1:3211/api/v1", +}); + +await amend.identify({ + externalUserId: user.id, + accountId: workspace.id, + email: user.email, + name: user.name, + traits: { plan: workspace.plan }, +}); + +await amend.identifyAccount(workspace.id, { + plan: workspace.plan, + seats: workspace.seats, +}); +``` + +## Feedback + +Customer feedback should carry enough context to become useful source work later: + +```ts +await amend.submitRequest({ + title: "Show the merged PR on shipped requests", + body: "Our admins want to audit which change closed each request.", + authorEmail: user.email, + authorName: user.name, + labels: ["admin", "audit"], + sourceUrl: "https://app.example.com/feedback/123", +}); +``` + +Use interactions to keep demand and notification eligibility connected: + +```ts +await amend.vote("feedback-show-shipping-pr", user.id); +await amend.comment("feedback-show-shipping-pr", "This would help our admins.", user.id); +await amend.react("feedback-show-shipping-pr", "heart", user.id); +``` + +## Update Feed + +Use `updatesForUser` or `updatesForContact` when your app has enough identity to filter shipped +updates for a person: + +```ts +const updates = await amend.updatesForContact({ + userId: user.id, + email: user.email, +}); + +await amend.markUpdateSeen("changelog-reviewable-publishing", user.id); +await amend.trackShippedFeature("roadmap-source-linked-portal", user.id); +``` + +## Embedded Panel + +Use the embedded panel when you want a working request/update surface before building custom UI: + +```ts +import { createAmendPanel } from "@amend/sdk/embed"; + +createAmendPanel({ + project: "amend-labs", + apiBaseUrl: "http://127.0.0.1:3211/api/v1", +}); +``` + +In local development, open the browser demo at: + +```txt +http://amend.localhost:1355/embed-demo +``` + +## Notification Preferences + +Notification preferences are part of the customer surface because they decide who can receive +targeted shipped updates. + +```ts +await amend.setNotificationPreference({ + externalUserId: user.id, + email: user.email, + mode: "digest", + digestDay: "friday", + digestHour: 16, +}); + +await amend.unsubscribe({ email: user.email }); +``` + +Unsubscribe writes remain available even when owner-level writes are protected by +`AMEND_API_TOKEN`. diff --git a/apps/fumadocs/content/docs/index.mdx b/apps/fumadocs/content/docs/index.mdx index ae3021f..5dcd4f9 100644 --- a/apps/fumadocs/content/docs/index.mdx +++ b/apps/fumadocs/content/docs/index.mdx @@ -4,10 +4,10 @@ description: Build a source-linked product update loop from feedback to shipped --- Amend connects four things teams usually keep apart: customer asks, source evidence, roadmap state, -and shipped updates. These docs are written for the person wiring the loop, not for a generic -marketing tour. +and shipped updates. These docs are for the person wiring that loop into a real product, with enough +detail for a maintainer or coding agent to verify what is actually running. -## Start with the loop +## Start Here @@ -17,36 +17,48 @@ marketing tour. Understand the evidence model behind every roadmap and changelog move. - Wire the portal, SDK, REST API, identity mapping, and domain strategy. + Choose the right path for portal, SDK, REST, source imports, and automation. + + + See the beta REST and SDK contract by job, auth level, and route family. - Keep the same workflow while bringing your own stack, keys, and docs route. + Bring your own Convex deployment, provider keys, domains, and model policy. + + + Serve docs from `docs.amend.sh` and make `amend.sh/docs` work through Vercel. Check provider credentials, DNS, Convex, email, billing, and final gates. -## The operating model +## The Loop + +| Stage | Inputs | Output | Read next | +| -------- | -------------------------------------------------- | ------------------------------------------- | -------------------------------------------- | +| Intake | Portal feedback, SDK calls, support and app events | Normalized requests with user/account IDs | [Customer surfaces](customer-surfaces.mdx) | +| Evidence | GitHub PRs, releases, issues, labels, imports | Source events tied to projects and requests | [Source events](source-events.mdx) | +| Review | Automation rules, confidence, owner edits | Draft roadmap, changelog, and delivery work | [Automation](automation.mdx) | +| Publish | Portal settings, preferences, verified domains | Public updates and targeted notifications | [Production routing](production-routing.mdx) | -| Stage | What Amend needs | What it produces | -| -------- | ----------------------------------------------- | ------------------------------------- | -| Intake | Feedback, accounts, roadmap notes, support asks | Normalized requests with identities | -| Evidence | GitHub PRs, releases, issues, labels, commits | Source-linked records and confidence | -| Review | Workspace rules, owner edits, approval state | Draft roadmap, changelog, and replies | -| Publish | Portal settings, notification preferences | Public updates and targeted messages | +## First Production Shape -## What to configure first +For the hosted Amend launch, keep the production surfaces explicit: -1. Connect the workspace to a local Convex deployment. -2. Set `VITE_DOCS_URL` so app links point at the right docs root. -3. Open the dashboard setup view and confirm the portal, SDK, and API snippets use environment URLs. -4. Create one feedback record tied to a user or account. -5. Link that request to source work and review the generated public story. +| Surface | Production origin | Why it exists | +| ------- | ----------------------------------------- | ----------------------------------------------- | +| Web app | `https://amend.sh` | Marketing, auth, dashboard, portal, embed demo | +| Docs | `https://docs.amend.sh/docs` | Canonical Fumadocs site, search, schemas, llms | +| Proxy | `https://amend.sh/docs` | Main-site entry point that opens the docs route | +| API | `https://.convex.site` | Convex HTTP actions under `/api/v1` | -## What to avoid +## Non-Negotiables -- Do not hardcode local ports in SDK snippets, portal links, or docs links. -- Do not publish customer updates without a source trace. -- Do not treat local portless URLs as proof of production deployment. -- Do not put provider secrets in the client bundle. +- Do not publish customer-facing updates without a source trace. +- Do not put provider secrets, owner API tokens, private keys, or webhook secrets in the client. +- Do not treat a local portless URL as proof that DNS, Vercel routing, auth callbacks, or webhooks + work in production. +- Do not hardcode docs links in product code. Use `VITE_DOCS_URL`. +- Do not let automation silently publish public copy until workspace rules and confidence thresholds + say that is allowed. diff --git a/apps/fumadocs/content/docs/integration.mdx b/apps/fumadocs/content/docs/integration.mdx index 37022e8..826dd96 100644 --- a/apps/fumadocs/content/docs/integration.mdx +++ b/apps/fumadocs/content/docs/integration.mdx @@ -1,10 +1,22 @@ --- title: Integration -description: Connect Amend to your portal, SDK, REST API, and customer identity data. +description: Choose the right integration path for portal, SDK, REST, source imports, and automation. --- -Integrate Amend from the outside in: public portal first, SDK or widget next, server-side REST writes -after that. This keeps the customer-facing path real while the automation layer matures. +Integrate Amend from the outside in: portal first, customer identity next, source evidence after +that, and automation only once review rules are visible. This keeps the customer-facing path real +while the agent layer matures. + +## Integration Map + +| Job | Use this surface | Details | +| ------------------------------------- | ------------------------------------------------- | -------------------------------------------- | +| Show roadmap, changelog, and feedback | Hosted portal or custom portal read endpoints | [Customer surfaces](customer-surfaces.mdx) | +| Add in-app request/update UI | `@amend/sdk` and `createAmendPanel` | [Customer surfaces](customer-surfaces.mdx) | +| Import GitHub/support/Slack evidence | Source event imports or signed GitHub webhooks | [Source events](source-events.mdx) | +| Control owner/admin records | Token-guarded REST or SDK owner methods | [API reference](api-reference.mdx) | +| Tune proactive decisions | Automation rules plus review-first launch posture | [Automation](automation.mdx) | +| Put docs and app on production hosts | `VITE_DOCS_URL`, docs host, Vercel rewrite/proxy | [Production routing](production-routing.mdx) | ## Portal setup @@ -21,77 +33,65 @@ Start with one workspace portal. Confirm these settings before inviting users: Local portal URLs can live under the web app. Production can use a custom domain or path once DNS is ready. -## SDK install +## SDK Basics -Use the SDK when your product needs an update panel, request launcher, or user-specific shipped -updates. +Use the SDK when your product needs an update panel, request launcher, identity mapping, or +user-specific shipped updates. ```ts import { Amend } from "@amend/sdk"; const amend = new Amend({ - apiBaseUrl: process.env.AMEND_API_BASE_URL, - workspaceSlug: "amend-labs", + project: "amend-labs", + apiBaseUrl: process.env.AMEND_API_BASE_URL ?? "/api/v1", + token: process.env.AMEND_API_TOKEN, }); ``` -Pass identity with stable IDs from your app, not display names: +Pass identity with stable IDs from your app, not display names alone: ```ts -await amend.identifyAccount({ +await amend.identify({ + externalUserId: "user_456", accountId: "acct_123", - name: "Northwind", - plan: "enterprise", + email: "maya@example.com", + name: "Maya", + traits: { role: "admin" }, }); -await amend.identifyUser({ - userId: "user_456", - accountId: "acct_123", - email: "maya@example.com", +await amend.identifyAccount("acct_123", { + name: "Northwind", + plan: "enterprise", }); ``` -## REST API - -Use server-side REST calls for writes that should not happen in the browser: - -| Endpoint family | Use | -| --------------- | ---------------------------------- | -| Feedback | Import customer asks and votes | -| Roadmap | Create or update roadmap records | -| Changelog | Publish manual or reviewed entries | -| Notifications | Manage delivery preferences | -| Integrations | Record provider connection state | -| Portal settings | Control public visibility and copy | - -Protect owner-level mutations with `AMEND_API_TOKEN` when configured. +## Auth Rules -## Identity mapping +Public customer-facing reads and feedback writes do not require a token. Owner-level mutations use +`Authorization: Bearer ` when `AMEND_API_TOKEN` is configured in the Convex +environment. -Identity is what makes "you asked, we shipped" possible. Send both user and account identifiers -whenever you can. +| Auth level | Examples | +| ---------- | ------------------------------------------------------------------ | +| Public | `GET portal`, `GET roadmap`, `GET changelog`, `POST feedback` | +| Customer | `POST identity`, `POST interactions`, `POST events`, unsubscribe | +| Owner | Projects, repositories, source imports, roadmap/changelog writes | +| Webhook | GitHub signature, Stripe signature, provider-specific verification | -| Identifier | Good source | -| ------------- | ---------------------------------------- | -| `userId` | Your app database user ID | -| `accountId` | Workspace, org, company, or tenant ID | -| `email` | Notification delivery and support lookup | -| `externalUrl` | CRM, admin, or support profile | +## Production Links -## Docs and domain links - -The web app should not assume docs live at the same origin in every environment. Set `VITE_DOCS_URL` +The web app must not assume docs live at the same origin in every environment. Set `VITE_DOCS_URL` to the public docs root: ```bash # local VITE_DOCS_URL=http://docs.amend.localhost:1355/docs -# optional same-site proxy for self-hosted installs -VITE_DOCS_URL=/docs +# production product entry point +VITE_DOCS_URL=https://amend.sh/docs -# Amend.sh launch docs host -VITE_DOCS_URL=https://docs.amend.sh/docs +# optional same-site proxy for self-hosted installs with their own routing +VITE_DOCS_URL=/docs ``` After this, product links can point to `source-trace`, `integration`, `self-hosting`, and `launch` diff --git a/apps/fumadocs/content/docs/launch.mdx b/apps/fumadocs/content/docs/launch.mdx index 3172700..da56e17 100644 --- a/apps/fumadocs/content/docs/launch.mdx +++ b/apps/fumadocs/content/docs/launch.mdx @@ -6,7 +6,7 @@ description: Production launch checklist for provider setup, DNS, deployment, an Launch is the point where local wiring becomes public infrastructure. Keep this page boring and strict: it exists to prevent "works locally" from becoming a production surprise. -## Preflight +## 1. Local Preflight Run these before touching production: @@ -22,7 +22,7 @@ If the dev stack is already running, add the smoke gate: bun run smoke ``` -## Provider inputs +## 2. Provider Inputs Collect these before strict readiness: @@ -34,13 +34,13 @@ Collect these before strict readiness: | Billing | Stripe secret key, webhook secret, product and price IDs | | Hosting | Web app URL, docs URL, Convex deployment, DNS records | -## Convex environment +## 3. Convex Environment Set production server-side values in the backend environment. Keep client-exposed values limited to `VITE_` variables. ```bash -bunx convex env set SITE_URL https://amend.sh +bunx convex env set SITE_URL "https://amend.sh" bunx convex env set BETTER_AUTH_SECRET "" bunx convex env set GITHUB_WEBHOOK_SECRET "" bunx convex env set AMEND_API_TOKEN "" @@ -56,7 +56,7 @@ VITE_POSTHOG_HOST=https://us.i.posthog.com VITE_POSTHOG_PROJECT_ID=441195 ``` -## Domains +## 4. Domains Use the dedicated docs host for this launch: @@ -70,12 +70,15 @@ Use the dedicated docs host for this launch: Set `VITE_DOCS_URL` to the final docs root so every landing, footer, setup, and dashboard link goes to the same place. -## Final gate +The main site also proxies `/docs` through Vercel so `https://amend.sh/docs` opens the docs index. +See [Production routing](production-routing.mdx) for the routing contract. + +## 5. Final Gate Before announcing: - sign in from the deployed web app -- open docs from the deployed landing page +- open docs from `https://amend.sh/docs` and `https://docs.amend.sh/docs` - confirm `amend.sh` and `docs.amend.sh` resolve through A/AAAA or CNAME DNS records - send a signed GitHub webhook to production - submit feedback through the portal @@ -85,3 +88,9 @@ Before announcing: - rerun `bun run readiness:strict` - rerun `bun run agent-ready:built` - rerun `bun run agent-ready:live` + +Use the production wrapper only when production secrets and DNS are expected to be ready: + +```bash +bun run agent-ready:production +``` diff --git a/apps/fumadocs/content/docs/meta.json b/apps/fumadocs/content/docs/meta.json index bee14e3..a8d8873 100644 --- a/apps/fumadocs/content/docs/meta.json +++ b/apps/fumadocs/content/docs/meta.json @@ -1,4 +1,16 @@ { "title": "Amend.sh Docs", - "pages": ["index", "quickstart", "source-trace", "integration", "self-hosting", "launch"] + "pages": [ + "index", + "quickstart", + "source-trace", + "integration", + "customer-surfaces", + "source-events", + "automation", + "api-reference", + "self-hosting", + "production-routing", + "launch" + ] } diff --git a/apps/fumadocs/content/docs/production-routing.mdx b/apps/fumadocs/content/docs/production-routing.mdx new file mode 100644 index 0000000..a7f37a8 --- /dev/null +++ b/apps/fumadocs/content/docs/production-routing.mdx @@ -0,0 +1,83 @@ +--- +title: Production Routing +description: Make docs work from docs.amend.sh and the main-site /docs entry point on Vercel. +--- + +The canonical docs app runs on `https://docs.amend.sh`. The main product should also open docs from +`https://amend.sh/docs`, so customers and agents can start from the primary domain without learning +the docs subdomain first. + +## Target Routes + +| Route | Behavior | +| ----------------------------------- | ------------------------------------------- | +| `https://docs.amend.sh` | Fumadocs landing page | +| `https://docs.amend.sh/docs` | Canonical docs index | +| `https://amend.sh/docs` | Vercel proxy to the canonical docs index | +| `https://amend.sh/docs/:path*` | Vercel proxy to the matching canonical page | +| `https://docs.amend.sh/llms.txt` | LLM-readable docs map | +| `https://docs.amend.sh/schemas/...` | Agent-ready JSON Schema endpoints | + +## Web App Configuration + +Product links are controlled by `VITE_DOCS_URL`: + +```bash +VITE_DOCS_URL=https://amend.sh/docs +``` + +That keeps app links on the main product origin. The canonical docs origin remains +`https://docs.amend.sh` for sitemap, metadata, `llms.txt`, and schema endpoints. + +## Vercel Rewrite + +The root Vercel deployment uses an external rewrite/proxy for `/docs` routes. Because the web app +build is copied into `.vercel/output`, the production build script injects the equivalent Build +Output API routes into `.vercel/output/config.json`. + +This lets Vercel serve: + +```txt +https://amend.sh/docs +https://amend.sh/docs/quickstart +https://amend.sh/docs/integration +``` + +from the docs deployment without changing the visible URL. + +## Docs Assets + +The docs app sets a production asset prefix to `https://docs.amend.sh`. That prevents proxied docs +pages from asking the web deployment for docs JavaScript, CSS, fonts, and Open Graph assets. + +If the canonical docs origin changes, update: + +- `apps/fumadocs/next.config.mjs` +- `apps/fumadocs/src/app/layout.tsx` +- `apps/fumadocs/src/app/robots.txt/route.ts` +- `apps/fumadocs/src/app/sitemap.xml/route.ts` +- `apps/fumadocs/src/app/docs/[[...slug]]/page.tsx` +- `apps/web/src/lib/docs-url.ts` +- `.env.production.example` +- the agent-ready docs and validator expectations + +## Verification + +After production deploys are attached to their domains, verify both origins: + +```bash +curl -I https://docs.amend.sh/docs +curl -I https://amend.sh/docs +curl -fsS https://docs.amend.sh/llms.txt +curl -fsS https://docs.amend.sh/schemas/agent-ready-live-report.schema.json +``` + +Then run: + +```bash +bun run agent-ready:built +bun run agent-ready:live +``` + +The live gate is the source of truth for DNS, crawler access, metadata, sitemaps, `llms.txt`, and +schema endpoints. diff --git a/apps/fumadocs/content/docs/quickstart.mdx b/apps/fumadocs/content/docs/quickstart.mdx index 198274c..28fc9e3 100644 --- a/apps/fumadocs/content/docs/quickstart.mdx +++ b/apps/fumadocs/content/docs/quickstart.mdx @@ -3,12 +3,24 @@ title: Quickstart description: Prove one source-linked update loop in local development. --- -The first useful Amend setup is small: one workspace, one customer request, one source event, one -reviewed public update. Do that before adding every integration. +The first useful Amend setup is deliberately small: one workspace, one customer request, one source +event, one reviewed public update. Prove that loop before adding more channels. ## 1. Run the local stack -Use the monorepo dev command when you want the web app, docs, and backend together: +Install dependencies once: + +```bash +bun install +``` + +Create or refresh a local/dev Convex deployment and sync the web env: + +```bash +bun run dev:setup +``` + +Then run the monorepo dev command: ```bash bun run dev @@ -28,32 +40,57 @@ is hardcoded in source, fix the env value instead. ## 2. Open setup -Start from the dashboard setup view. It should show the local portal URL, SDK install shape, API -base URL, production needs, and launch gates. Treat this view as the checklist for a working -workspace. +Open the dashboard setup view. It should show the local portal URL, SDK shape, API base URL, +production needs, and launch gates. Treat setup as the workspace checklist; if a value is wrong +there, fix the environment or connection record instead of patching snippets by hand. ## 3. Create one request A useful request has enough identity and context to close the loop later: ```ts +import { Amend } from "@amend/sdk"; + +const amend = new Amend({ + project: "amend-labs", + apiBaseUrl: "http://127.0.0.1:3211/api/v1", +}); + +await amend.identify({ + externalUserId: "user_123", + accountId: "acct_enterprise", + email: "maya@example.com", + name: "Maya", + traits: { plan: "enterprise" }, +}); + await amend.submitRequest({ - workspaceSlug: "amend-labs", title: "Notify me when export audit logs ship", body: "Our compliance team needs a changelog entry tied to the PR.", - userId: "user_123", - accountId: "acct_enterprise", + authorEmail: "maya@example.com", + labels: ["audit", "enterprise"], }); ``` ## 4. Link source evidence -Connect the request to a GitHub issue, pull request, release, or commit. The trace should answer: +Import or ingest source evidence. `externalId` is the idempotency key, so replaying the same event +updates the stored source record instead of creating duplicates: -- who asked for the change -- what source work shipped -- why the update is ready -- who should be notified +```ts +await amend.importSourceEvent({ + provider: "github", + kind: "pull_request", + externalId: "github:acme/web:pr:42", + title: "Add export audit log notifications", + url: "https://github.com/acme/web/pull/42", + owner: "acme", + repo: "web", + number: 42, + state: "merged", + labels: ["audit", "enterprise"], +}); +``` ## 5. Review before publishing @@ -63,7 +100,7 @@ added by rule once the team trusts the evidence. ## 6. Verify the loop -Run the checks that match your current state: +Run the checks that match the current state: ```bash bun run readiness @@ -72,3 +109,9 @@ bun run --cwd apps/fumadocs check-types ``` Use `bun run smoke` only while the dev stack is already running. + +When you need the full local gate: + +```bash +bun run check +``` diff --git a/apps/fumadocs/content/docs/self-hosting.mdx b/apps/fumadocs/content/docs/self-hosting.mdx index d024040..c362e61 100644 --- a/apps/fumadocs/content/docs/self-hosting.mdx +++ b/apps/fumadocs/content/docs/self-hosting.mdx @@ -40,12 +40,23 @@ Choose one docs route before shipping public links: | Pattern | `VITE_DOCS_URL` | | ------------------ | ------------------------------------- | | Same app proxy | `/docs` | +| Main-site proxy | `https://amend.sh/docs` | | Docs subdomain | `https://docs.amend.sh/docs` | | Separate docs host | `https://amend-docs.example.com/docs` | If the route changes later, update the environment variable and redeploy the web app. Product code should not need edits. +## API Base URL + +Customer-owned products should point SDK and server calls at the Convex HTTP action origin: + +```bash +AMEND_API_BASE_URL=https://your-production-convex.convex.site/api/v1 +``` + +Use `AMEND_API_TOKEN` only in trusted server code, CLI automation, or owner tools. + ## Provider keys Production should configure these outside the client bundle: diff --git a/apps/fumadocs/content/docs/source-events.mdx b/apps/fumadocs/content/docs/source-events.mdx new file mode 100644 index 0000000..f2223b1 --- /dev/null +++ b/apps/fumadocs/content/docs/source-events.mdx @@ -0,0 +1,82 @@ +--- +title: Source Events +description: Import GitHub, support, Slack, Discord, Linear, email, CLI, and custom agent evidence. +--- + +Source events are the evidence side of the loop. They tell Amend what work happened and give the +reviewer a chain from customer demand to shipped source work. + +## When To Use Source Events + +Use source events when evidence comes from anything other than a customer request: + +| Provider examples | Typical kind | +| -------------------------- | -------------------------------------------------------- | +| GitHub PR, issue, release | `pull_request`, `issue`, `release`, `label`, `milestone` | +| Slack, Discord, support | `customer_signal`, `support_ticket` | +| Linear or custom project | `issue`, `customer_signal`, `import_record` | +| CLI, SDK, warehouse import | `usage_event`, `import_record`, `customer_signal` | + +## Idempotency + +`externalId` is required and acts as the idempotency key. Replaying the same import updates the +existing record instead of duplicating evidence. + +```ts +await amend.importSourceEvent({ + provider: "slack", + kind: "customer_signal", + externalId: "slack:C123:1700000000.000100", + title: "Enterprise customer asked for digest controls", + url: "https://slack.com/app_redirect?channel=C123", + labels: ["enterprise", "notifications"], + projectSlug: "web-app", +}); +``` + +## GitHub Webhooks + +Signed GitHub webhook ingestion uses the same source-event pipeline. Configure the webhook secret in +the Convex environment: + +```bash +bunx convex env set GITHUB_WEBHOOK_SECRET "replace-with-your-github-webhook-secret" +``` + +Then send GitHub events to: + +```txt +POST /api/v1/:workspace/github +``` + +The request must include `X-GitHub-Event`, `X-GitHub-Delivery`, and +`X-Hub-Signature-256` when the secret is configured. + +## CLI Imports + +The local CLI supports direct imports and file-based imports: + +```bash +bun packages/cli/src/index.ts source import \ + --provider slack \ + --kind customer_signal \ + --external-id slack:feedback:123 \ + --title "Request from #feedback" \ + --url "https://slack.com/app_redirect?channel=C123" + +bun packages/cli/src/index.ts source import --file source-events.json +bun packages/cli/src/index.ts source import --file source-events.csv +``` + +CSV imports use the first row as headers. Supported columns include `provider`, `kind`, +`external_id`, `title`, `url`, `labels`, `state`, `number`, `author`, `owner`, `repo`, +`project_slug`, `observed_at`, `source_created_at`, and `source_updated_at`. + +## Review Output + +A good source event lets a reviewer answer: + +- what shipped or changed +- where the source evidence lives +- which project or repository it belongs to +- whether it can close feedback, move roadmap status, draft changelog copy, or notify users diff --git a/apps/fumadocs/content/docs/source-trace.mdx b/apps/fumadocs/content/docs/source-trace.mdx index ebfa6ae..dc3fba3 100644 --- a/apps/fumadocs/content/docs/source-trace.mdx +++ b/apps/fumadocs/content/docs/source-trace.mdx @@ -53,3 +53,10 @@ Customers do not need to see every commit or internal decision. They need a clea - where the roadmap or changelog item now lives Keep the trace visible to the team, and keep the portal readable for customers. + +## Related Pages + +- [Source events](source-events.mdx) explains how evidence enters Amend. +- [Automation](automation.mdx) explains how source evidence becomes reviewable decisions. +- [Customer surfaces](customer-surfaces.mdx) explains how users, accounts, feedback, and update + feeds stay connected. diff --git a/apps/fumadocs/next.config.mjs b/apps/fumadocs/next.config.mjs index 1aaf2e4..2dd087d 100644 --- a/apps/fumadocs/next.config.mjs +++ b/apps/fumadocs/next.config.mjs @@ -2,10 +2,13 @@ import { createMDX } from "fumadocs-mdx/next"; import { fileURLToPath } from "node:url"; const withMDX = createMDX(); +const docsOrigin = process.env.AMEND_DOCS_ORIGIN ?? "https://docs.amend.sh"; +const isProductionBuild = process.env.NODE_ENV === "production"; /** @type {import('next').NextConfig} */ const config = { allowedDevOrigins: ["docs.amend.localhost", "*.localhost"], + assetPrefix: isProductionBuild ? docsOrigin : undefined, serverExternalPackages: ["@takumi-rs/image-response"], reactStrictMode: true, turbopack: { diff --git a/apps/web/public/llms.txt b/apps/web/public/llms.txt index e7b6985..20425d3 100644 --- a/apps/web/public/llms.txt +++ b/apps/web/public/llms.txt @@ -15,8 +15,13 @@ - [Docs index](https://docs.amend.sh/docs): Documentation index for Amend.sh. - [Quickstart](https://docs.amend.sh/docs/quickstart): Prove a source-linked product update loop in local development. - [Source trace](https://docs.amend.sh/docs/source-trace): How feedback, source work, releases, and customer updates stay connected. -- [Integration](https://docs.amend.sh/docs/integration): SDK, REST API, portal, GitHub, identity, and event integration surfaces. +- [Integration](https://docs.amend.sh/docs/integration): Choose the right path for portal, SDK, REST, source imports, and automation. +- [Customer surfaces](https://docs.amend.sh/docs/customer-surfaces): Customer identity, feedback, update feeds, and embedded panel setup. +- [Source events](https://docs.amend.sh/docs/source-events): GitHub, support, Slack, Discord, Linear, email, CLI, and custom evidence imports. +- [Automation](https://docs.amend.sh/docs/automation): Mostly Auto rules, AI drafting, proactive agent runs, and delivery safety. +- [API reference](https://docs.amend.sh/docs/api-reference): Beta REST and SDK contract by route family and auth level. - [Self-hosting](https://docs.amend.sh/docs/self-hosting): Run Amend with your own deployment and provider keys. +- [Production routing](https://docs.amend.sh/docs/production-routing): Canonical docs host and main-site /docs Vercel routing. - [Launch](https://docs.amend.sh/docs/launch): Production launch checklist and readiness gates. ## Machine-readable Contracts diff --git a/apps/web/src/components/sign-in-form.tsx b/apps/web/src/components/sign-in-form.tsx index e694046..d5216d9 100644 --- a/apps/web/src/components/sign-in-form.tsx +++ b/apps/web/src/components/sign-in-form.tsx @@ -2,6 +2,7 @@ import { FieldGroup } from "@amend/ui/components/field"; import { useForm } from "@tanstack/react-form"; import { Link, useSearch } from "@tanstack/react-router"; import { useConvex } from "convex/react"; +import { makeFunctionReference } from "convex/server"; import { lazy, Suspense, useState } from "react"; import z from "zod"; @@ -26,6 +27,13 @@ const DevDemoSignInButton = import.meta.env.DEV : null; const previewAuthEnabled = import.meta.env.VITE_AMEND_PREVIEW_AUTH === "true"; +const previewJoinSeededDemoWorkspaceMutationName = ["amend", "joinSeededDemoWorkspace"].join( + ":", +) as "amend:joinSeededDemoWorkspace"; +const joinSeededDemoWorkspaceMutation = makeFunctionReference<"mutation">( + previewJoinSeededDemoWorkspaceMutationName, +); +const localDemoWorkspaceSlug = demoWorkspaceSlug; export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp?: () => void }) { const [formError, setFormError] = useState(""); @@ -84,12 +92,10 @@ export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp?: () return; } - const { joinSeededDemoWorkspaceMutation } = await import("@/components/dev-demo-sign-in-model"); - await convex.mutation(joinSeededDemoWorkspaceMutation, { email, name: previewNameFromEmail(email), - workspaceSlug: demoWorkspaceSlug, + workspaceSlug: localDemoWorkspaceSlug, }); } diff --git a/apps/web/src/lib/docs-url.ts b/apps/web/src/lib/docs-url.ts index 11cc4a4..5009deb 100644 --- a/apps/web/src/lib/docs-url.ts +++ b/apps/web/src/lib/docs-url.ts @@ -1,5 +1,5 @@ const DEFAULT_DEV_DOCS_URL = "http://docs.amend.localhost:1355/docs"; -const DEFAULT_PRODUCTION_DOCS_URL = "https://docs.amend.sh/docs"; +const DEFAULT_PRODUCTION_DOCS_URL = "https://amend.sh/docs"; function trimSlashes(value: string) { return value.replace(/^\/+|\/+$/g, ""); diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 38c934e..7f3872a 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -47,6 +47,31 @@ export default defineConfig({ return "vendor-motion"; } + if (id.includes("/zod/")) { + return "vendor-zod"; + } + + if (id.includes("posthog-js")) { + return "vendor-analytics"; + } + + if ( + id.includes("/satori/") || + id.includes("@resvg/") || + id.includes("opentype") || + id.includes("linebreak") + ) { + return "vendor-og"; + } + + if (id.includes("@radix-ui")) { + return "vendor-radix"; + } + + if (id.includes("react-grab")) { + return "vendor-embed"; + } + return "vendor"; }, }, diff --git a/docs/agent-ready-audit.md b/docs/agent-ready-audit.md index b5e4cab..c753045 100644 --- a/docs/agent-ready-audit.md +++ b/docs/agent-ready-audit.md @@ -20,13 +20,13 @@ This audit tracks the specific goal of making Amend.sh agent-ready across `https | Requirement | Evidence | Status | | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| Maximum-visibility web `robots.txt` | `scripts/agent-ready-policy.ts` centralizes the named crawler/fetcher list. `apps/web/public/robots.txt` and built `apps/web/dist/client/robots.txt` contain `User-agent: *`, `Allow: /`, and `Sitemap: https://amend.sh/sitemap.xml`, with no `Disallow:` or named AI crawler exceptions | Complete locally | +| Maximum-visibility web `robots.txt` | `scripts/agent-ready-policy.ts` centralizes the named crawler/fetcher list. `apps/web/public/robots.txt` and built `apps/web/.output/public/robots.txt` contain `User-agent: *`, `Allow: /`, and `Sitemap: https://amend.sh/sitemap.xml`, with no `Disallow:` or named AI crawler exceptions | Complete locally | | Maximum-visibility docs `robots.txt` | `scripts/agent-ready-policy.ts` centralizes the named crawler/fetcher list. `apps/fumadocs/src/app/robots.txt/route.ts` and built `.next/server/app/robots.txt.body` contain `User-agent: *`, `Allow: /`, and docs sitemap URL, with no `Disallow:` or named AI crawler exceptions | Complete locally | | Web sitemap | `apps/web/public/sitemap.xml` lists `/`, `/brand`, `/embed-demo`, and `/portal/amend-labs` on `https://amend.sh` with `lastmod`, `changefreq`, and `priority` metadata | Complete locally | | Docs sitemap | `apps/fumadocs/src/app/sitemap.xml/route.ts` generates `https://docs.amend.sh`, `https://docs.amend.sh/docs`, the agent-ready production, live, status, and completion audit report schema endpoints, and every Fumadocs page from `source.getPages()` with `lastmod`, `changefreq`, and `priority` metadata | Complete locally | | Web `llms.txt` | `apps/web/public/llms.txt` links product overview, brand, embed demo, public portal example, docs landing page, docs index, docs pages, and the agent-ready production, live, status, and completion audit report schema endpoints | Complete locally | | Docs LLM resources | Fumadocs route handlers emit `/llms.txt`, `/llms-full.txt`, and per-page Markdown under `/llms.mdx/docs/.../content.md` with explicit text content types; `/llms.txt` also links the agent-ready production, live, status, and completion audit report schema endpoints | Complete locally | -| Production docs URL routing | `apps/web/src/lib/docs-url.ts`, `.env.production.example`, `README.md`, `docs/production-readiness.md`, `scripts/smoke.ts`, `scripts/readiness.ts`, and `scripts/agent-ready.test.ts` pin production docs links to `https://docs.amend.sh/docs` for this launch instead of falling back to `/docs` | Complete locally | +| Production docs URL routing | `apps/web/src/lib/docs-url.ts`, `.env.production.example`, `README.md`, `docs/production-readiness.md`, `scripts/smoke.ts`, `scripts/readiness.ts`, and `scripts/agent-ready.test.ts` pin production app docs links to `https://amend.sh/docs` for this launch while `https://docs.amend.sh` remains the canonical docs origin | Complete locally | | Canonical/social metadata | Public web routes use `canonicalLink()` and `openGraphMeta()` from `apps/web/src/lib/seo.ts`; docs metadata uses `metadataBase: https://docs.amend.sh` and page canonicals | Complete locally | | Structured data | Web homepage renders `Organization` and `SoftwareApplication` JSON-LD from `apps/web/src/lib/seo.ts`; docs landing renders `WebSite` JSON-LD; docs pages render `TechArticle` JSON-LD | Complete locally | | Private route noindex | Sign-in, sign-up, and dashboard routes import `noIndexMeta` | Complete locally | diff --git a/docs/agent-ready-domain-setup.md b/docs/agent-ready-domain-setup.md index eeb70fc..8fc6eb1 100644 --- a/docs/agent-ready-domain-setup.md +++ b/docs/agent-ready-domain-setup.md @@ -34,7 +34,7 @@ Expected current failure until the domain is registered: 6. Set production environment values for the deployments. The minimum app/domain values are: - `VITE_CONVEX_URL` - `VITE_CONVEX_SITE_URL` - - `VITE_DOCS_URL=https://docs.amend.sh/docs` + - `VITE_DOCS_URL=https://amend.sh/docs` - `SITE_URL=https://amend.sh` 7. Load the remaining production provider secrets required by `bun run readiness:strict`: - `BETTER_AUTH_SECRET` diff --git a/docs/completion-audit.md b/docs/completion-audit.md index 7c210a4..e0cd194 100644 --- a/docs/completion-audit.md +++ b/docs/completion-audit.md @@ -6,17 +6,17 @@ This audit maps the current product goal to repo artifacts and local verificatio ## Goal Coverage -| Area | Status | Evidence | -| ------------------------ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Auth and setup | Implemented locally | Dashboard project creation now requires a Better Auth dashboard user, creates a private workspace when a real workspace is not supplied, initializes owner membership, review-first rules, and channel placeholders. Sign-in/sign-up no longer show disabled fake provider buttons. | -| Channels vs integrations | Implemented locally | `packages/backend/convex/amend.ts` creates GitHub, Discord, Slack, Linear, support, and PostHog connection placeholders. The dashboard presents them as input/context/output channels and persists state changes through `amend:upsertIntegrationConnection`. | -| Proactive agent | Implemented locally, provider-gated | `amend:runProactiveAgentForWorkspace` builds a source-linked context, calls Crof/Kimi when configured, falls back safely when unavailable, and persists decisions/review items through `persistProactiveAgentRun`. The dashboard includes the `Agent command center`, run controls, a ledger, source evidence, and review actions. | -| Crof/Kimi provider | Provider-gated | `.env.production.example`, `packages/backend/.env.example`, readiness, production-readiness docs, and the launch runbook cover `CROF_API_KEY`, `CROF_MODEL`, and `CROF_BASE_URL`. The model default is `kimi-k2.6`, and provider calls stay server-side. | -| Review/apply behavior | Implemented locally | `amend:updateReviewStatus` requires auth, persists review decisions, clears matching automation decisions, and applies approved/published review outcomes to changelog, feedback, and notification targets. | -| Mock dashboard removal | Covered | The previous `apps/web/src/components/dashboard-workspace.tsx` mock shell was removed. The authenticated dashboard now uses `apps/web/src/components/amend-dashboard.tsx` and Convex overview data. Arbitrary missing public workspaces no longer inherit demo review/notification/update data. | -| Public portal and embed | Covered locally | Public portal and SDK/embed flows remain source-linked and branded. The default `amend-labs` demo workspace still exists for smoke/demo URLs; non-demo missing slugs return empty/private-safe data. | -| Docs and readiness | Updated | `docs/production-readiness.md`, `docs/launch-runbook.md`, env examples, `scripts/smoke.ts`, and `scripts/readiness.ts` now describe the proactive agent and Crof/Kimi launch inputs. | -| Agent-ready surfaces | Implemented locally, DNS-gated | `docs/agent-ready-audit.md` maps the agent-ready objective to source artifacts and validation. Web and docs `robots.txt`, sitemaps, `llms.txt`/`llms-full.txt`, production docs URL routing to `https://docs.amend.sh/docs`, machine-readable production, live, status, and completion report schema links, canonical/social metadata, homepage JSON-LD, private-route `noindex`, source regression tests, and the live validator are implemented. Public completion requires registering/delegating `amend.sh`, adding A/AAAA/CNAME DNS records for `amend.sh` and `docs.amend.sh`, loading production env/provider inputs, and passing `bun run agent-ready:production`. | +| Area | Status | Evidence | +| ------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Auth and setup | Implemented locally | Dashboard project creation now requires a Better Auth dashboard user, creates a private workspace when a real workspace is not supplied, initializes owner membership, review-first rules, and channel placeholders. Sign-in/sign-up no longer show disabled fake provider buttons. | +| Channels vs integrations | Implemented locally | `packages/backend/convex/amend.ts` creates GitHub, Discord, Slack, Linear, support, and PostHog connection placeholders. The dashboard presents them as input/context/output channels and persists state changes through `amend:upsertIntegrationConnection`. | +| Proactive agent | Implemented locally, provider-gated | `amend:runProactiveAgentForWorkspace` builds a source-linked context, calls Crof/Kimi when configured, falls back safely when unavailable, and persists decisions/review items through `persistProactiveAgentRun`. The dashboard includes the `Agent command center`, run controls, a ledger, source evidence, and review actions. | +| Crof/Kimi provider | Provider-gated | `.env.production.example`, `packages/backend/.env.example`, readiness, production-readiness docs, and the launch runbook cover `CROF_API_KEY`, `CROF_MODEL`, and `CROF_BASE_URL`. The model default is `kimi-k2.6`, and provider calls stay server-side. | +| Review/apply behavior | Implemented locally | `amend:updateReviewStatus` requires auth, persists review decisions, clears matching automation decisions, and applies approved/published review outcomes to changelog, feedback, and notification targets. | +| Mock dashboard removal | Covered | The previous `apps/web/src/components/dashboard-workspace.tsx` mock shell was removed. The authenticated dashboard now uses `apps/web/src/components/amend-dashboard.tsx` and Convex overview data. Arbitrary missing public workspaces no longer inherit demo review/notification/update data. | +| Public portal and embed | Covered locally | Public portal and SDK/embed flows remain source-linked and branded. The default `amend-labs` demo workspace still exists for smoke/demo URLs; non-demo missing slugs return empty/private-safe data. | +| Docs and readiness | Updated | `docs/production-readiness.md`, `docs/launch-runbook.md`, env examples, `scripts/smoke.ts`, and `scripts/readiness.ts` now describe the proactive agent and Crof/Kimi launch inputs. | +| Agent-ready surfaces | Implemented locally, DNS-gated | `docs/agent-ready-audit.md` maps the agent-ready objective to source artifacts and validation. Web and docs `robots.txt`, sitemaps, `llms.txt`/`llms-full.txt`, production app docs links to `https://amend.sh/docs`, canonical docs resources on `https://docs.amend.sh`, machine-readable production, live, status, and completion report schema links, canonical/social metadata, homepage JSON-LD, private-route `noindex`, source regression tests, and the live validator are implemented. Public completion requires registering/delegating `amend.sh`, adding A/AAAA/CNAME DNS records for `amend.sh` and `docs.amend.sh`, loading production env/provider inputs, and passing `bun run agent-ready:production`. | ## Agent-Ready Objective Checklist diff --git a/docs/launch-runbook.md b/docs/launch-runbook.md index 9ab21ae..67017d6 100644 --- a/docs/launch-runbook.md +++ b/docs/launch-runbook.md @@ -74,7 +74,7 @@ Set the production web deployment values: ```bash VITE_CONVEX_URL=https://.convex.cloud VITE_CONVEX_SITE_URL=https://.convex.site -VITE_DOCS_URL=https://docs.amend.sh/docs +VITE_DOCS_URL=https://amend.sh/docs VITE_POSTHOG_TOKEN=phc_BCb25jVTo59jtEMPysgGUvgt85bUYGwN8XBNA2oMNLY7 VITE_POSTHOG_HOST=https://us.i.posthog.com VITE_POSTHOG_PROJECT_ID=441195 diff --git a/docs/production-readiness.md b/docs/production-readiness.md index e4a590b..f542371 100644 --- a/docs/production-readiness.md +++ b/docs/production-readiness.md @@ -28,38 +28,38 @@ process. It does not print secret values. Set these in the production Convex deployment or deployment platform: -| Variable | Purpose | -| -------------------------- | -------------------------------------------------------------- | -| `VITE_CONVEX_URL` | Production web Convex client URL | -| `VITE_CONVEX_SITE_URL` | Production web Convex HTTP actions URL | -| `VITE_DOCS_URL` | Public docs root, `https://docs.amend.sh/docs` for this launch | -| `VITE_POSTHOG_TOKEN` | Browser PostHog project token | -| `VITE_POSTHOG_HOST` | Browser PostHog ingestion host | -| `VITE_POSTHOG_PROJECT_ID` | Browser PostHog project ID | -| `POSTHOG_CLI_API_KEY` | PostHog API key with error tracking write scope for sourcemaps | -| `POSTHOG_CLI_HOST` | PostHog app host for the CLI, e.g. `https://us.posthog.com` | -| `POSTHOG_CLI_PROJECT_ID` | PostHog project ID for sourcemap uploads | -| `POSTHOG_RELEASE_NAME` | PostHog release name for web sourcemaps, defaults to amend-web | -| `SITE_URL` | Hosted app origin and auth callbacks | -| `BETTER_AUTH_SECRET` | Better Auth session security | -| `GITHUB_WEBHOOK_SECRET` | Signed GitHub webhook ingestion | -| `GITHUB_APP_ID` | GitHub App installation identity | -| `GITHUB_APP_SLUG` | GitHub App public install URL | -| `GITHUB_APP_CLIENT_ID` | GitHub App install/OAuth entrypoint | -| `GITHUB_APP_CLIENT_SECRET` | GitHub App OAuth callback exchange | -| `GITHUB_APP_PRIVATE_KEY` | GitHub App installation token signing | -| `AMEND_API_TOKEN` | Owner-level REST mutation protection | -| `POSTHOG_API_KEY` | Convex backend analytics event capture | -| `POSTHOG_HOST` | PostHog ingestion host, e.g. `https://us.i.posthog.com` | -| `OPENAI_API_KEY` | Provider-backed changelog drafting | -| `OPENAI_MODEL` | Model used for changelog drafting | -| `CROF_API_KEY` | Crof/Kimi proactive agent provider key | -| `CROF_MODEL` | Proactive agent model, e.g. `kimi-k2.6` | -| `CROF_BASE_URL` | Crof OpenAI-compatible API base URL | -| `RESEND_API_KEY` | Real email delivery | -| `EMAIL_FROM` | Verified sender identity | -| `STRIPE_SECRET_KEY` | Billing checkout and plan changes | -| `STRIPE_WEBHOOK_SECRET` | Billing webhook verification | +| Variable | Purpose | +| -------------------------- | -------------------------------------------------------------------- | +| `VITE_CONVEX_URL` | Production web Convex client URL | +| `VITE_CONVEX_SITE_URL` | Production web Convex HTTP actions URL | +| `VITE_DOCS_URL` | Public app docs entry point, `https://amend.sh/docs` for this launch | +| `VITE_POSTHOG_TOKEN` | Browser PostHog project token | +| `VITE_POSTHOG_HOST` | Browser PostHog ingestion host | +| `VITE_POSTHOG_PROJECT_ID` | Browser PostHog project ID | +| `POSTHOG_CLI_API_KEY` | PostHog API key with error tracking write scope for sourcemaps | +| `POSTHOG_CLI_HOST` | PostHog app host for the CLI, e.g. `https://us.posthog.com` | +| `POSTHOG_CLI_PROJECT_ID` | PostHog project ID for sourcemap uploads | +| `POSTHOG_RELEASE_NAME` | PostHog release name for web sourcemaps, defaults to amend-web | +| `SITE_URL` | Hosted app origin and auth callbacks | +| `BETTER_AUTH_SECRET` | Better Auth session security | +| `GITHUB_WEBHOOK_SECRET` | Signed GitHub webhook ingestion | +| `GITHUB_APP_ID` | GitHub App installation identity | +| `GITHUB_APP_SLUG` | GitHub App public install URL | +| `GITHUB_APP_CLIENT_ID` | GitHub App install/OAuth entrypoint | +| `GITHUB_APP_CLIENT_SECRET` | GitHub App OAuth callback exchange | +| `GITHUB_APP_PRIVATE_KEY` | GitHub App installation token signing | +| `AMEND_API_TOKEN` | Owner-level REST mutation protection | +| `POSTHOG_API_KEY` | Convex backend analytics event capture | +| `POSTHOG_HOST` | PostHog ingestion host, e.g. `https://us.i.posthog.com` | +| `OPENAI_API_KEY` | Provider-backed changelog drafting | +| `OPENAI_MODEL` | Model used for changelog drafting | +| `CROF_API_KEY` | Crof/Kimi proactive agent provider key | +| `CROF_MODEL` | Proactive agent model, e.g. `kimi-k2.6` | +| `CROF_BASE_URL` | Crof OpenAI-compatible API base URL | +| `RESEND_API_KEY` | Real email delivery | +| `EMAIL_FROM` | Verified sender identity | +| `STRIPE_SECRET_KEY` | Billing checkout and plan changes | +| `STRIPE_WEBHOOK_SECRET` | Billing webhook verification | ## External Setup diff --git a/scripts/agent-ready-built-docs-core-checks.ts b/scripts/agent-ready-built-docs-core-checks.ts index ba7e95d..e5ace3c 100644 --- a/scripts/agent-ready-built-docs-core-checks.ts +++ b/scripts/agent-ready-built-docs-core-checks.ts @@ -62,8 +62,13 @@ export function checkBuiltDocsCoreArtifacts(artifacts: BuiltDocsArtifacts) { "https://docs.amend.sh/docs", "https://docs.amend.sh/docs/quickstart", "https://docs.amend.sh/docs/integration", + "https://docs.amend.sh/docs/customer-surfaces", + "https://docs.amend.sh/docs/source-events", + "https://docs.amend.sh/docs/automation", + "https://docs.amend.sh/docs/api-reference", "https://docs.amend.sh/docs/source-trace", "https://docs.amend.sh/docs/self-hosting", + "https://docs.amend.sh/docs/production-routing", "https://docs.amend.sh/docs/launch", "https://docs.amend.sh/schemas/agent-ready-production-report.schema.json", "https://docs.amend.sh/schemas/agent-ready-live-report.schema.json", @@ -98,6 +103,11 @@ export function checkBuiltDocsCoreArtifacts(artifacts: BuiltDocsArtifacts) { "/docs", "/docs/quickstart", "/docs/integration", + "/docs/customer-surfaces", + "/docs/source-events", + "/docs/automation", + "/docs/api-reference", + "/docs/production-routing", "/docs/launch", ]) && artifacts.docsLlms.includes("/schemas/agent-ready-production-report.schema.json") && @@ -108,8 +118,13 @@ export function checkBuiltDocsCoreArtifacts(artifacts: BuiltDocsArtifacts) { "# Amend.sh Docs", "# Quickstart", "# Integration", + "# Customer Surfaces", + "# Source Events", + "# Automation", + "# API Reference", "# Source Trace", "# Self-Hosting", + "# Production Routing", "# Launch", ]), ); diff --git a/scripts/agent-ready-built-utils.ts b/scripts/agent-ready-built-utils.ts index a80a988..0ab5b39 100644 --- a/scripts/agent-ready-built-utils.ts +++ b/scripts/agent-ready-built-utils.ts @@ -27,9 +27,36 @@ export async function read(path: string) { } } +function isMissingFile(error: unknown) { + return ( + error && + typeof error === "object" && + "code" in error && + (error as NodeJS.ErrnoException).code === "ENOENT" + ); +} + +export async function readFirst(paths: string[]) { + const missing: string[] = []; + for (const path of paths) { + try { + return await read(path); + } catch (error) { + if (isMissingFile(error)) { + missing.push(path); + continue; + } + throw error; + } + } + throw new Error( + `Missing one of ${missing.join(", ")}. Run \`bun run build\` before \`bun run agent-ready:built\`.`, + ); +} + export async function readBundleContaining(directory: string, marker: string) { const entries = await readdir(new URL(directory, root)); - for (const entry of entries.filter((entry) => entry.endsWith(".js"))) { + for (const entry of entries.filter((entry) => /\.(?:mjs|js)$/.test(entry))) { const path = `${directory}/${entry}`; const content = await read(path); if (content.includes(marker)) { @@ -41,6 +68,27 @@ export async function readBundleContaining(directory: string, marker: string) { ); } +export async function readBundleContainingAny(directories: string[], marker: string) { + const missing: string[] = []; + for (const directory of directories) { + try { + return await readBundleContaining(directory, marker); + } catch (error) { + if (isMissingFile(error)) { + missing.push(directory); + continue; + } + if (error instanceof Error && error.message.includes("Missing built bundle")) { + continue; + } + throw error; + } + } + throw new Error( + `Missing built bundle in ${directories.join(", ")} containing ${marker}. Run \`bun run build\`.`, + ); +} + export async function readMeta(path: string) { return JSON.parse(await read(path)) as BuiltMeta; } @@ -103,12 +151,40 @@ export const docsPages = [ path: "/docs/quickstart", }, { - copy: "Connect Amend to your portal", + copy: "Integrate Amend from the outside in", html: "apps/fumadocs/.next/server/app/docs/integration.html", - markdownCopy: "Integrate Amend from the outside in", + markdownCopy: "Use the SDK when your product needs an update panel", markdown: "apps/fumadocs/.next/server/app/llms.mdx/docs/integration/content.md.body", path: "/docs/integration", }, + { + copy: "Wire customer identity", + html: "apps/fumadocs/.next/server/app/docs/customer-surfaces.html", + markdownCopy: "Customer surfaces are the parts of Amend", + markdown: "apps/fumadocs/.next/server/app/llms.mdx/docs/customer-surfaces/content.md.body", + path: "/docs/customer-surfaces", + }, + { + copy: "Import GitHub", + html: "apps/fumadocs/.next/server/app/docs/source-events.html", + markdownCopy: "Source events are the evidence side", + markdown: "apps/fumadocs/.next/server/app/llms.mdx/docs/source-events/content.md.body", + path: "/docs/source-events", + }, + { + copy: "Configure Mostly Auto rules", + html: "apps/fumadocs/.next/server/app/docs/automation.html", + markdownCopy: "Automation should start review-first", + markdown: "apps/fumadocs/.next/server/app/llms.mdx/docs/automation/content.md.body", + path: "/docs/automation", + }, + { + copy: "Beta REST and SDK contract", + html: "apps/fumadocs/.next/server/app/docs/api-reference.html", + markdownCopy: "The REST API is served by Convex HTTP actions", + markdown: "apps/fumadocs/.next/server/app/llms.mdx/docs/api-reference/content.md.body", + path: "/docs/api-reference", + }, { copy: "The evidence chain", html: "apps/fumadocs/.next/server/app/docs/source-trace.html", @@ -123,6 +199,13 @@ export const docsPages = [ markdown: "apps/fumadocs/.next/server/app/llms.mdx/docs/self-hosting/content.md.body", path: "/docs/self-hosting", }, + { + copy: "Make docs work from docs.amend.sh", + html: "apps/fumadocs/.next/server/app/docs/production-routing.html", + markdownCopy: "The canonical docs app runs on", + markdown: "apps/fumadocs/.next/server/app/llms.mdx/docs/production-routing/content.md.body", + path: "/docs/production-routing", + }, { copy: "Production launch checklist", html: "apps/fumadocs/.next/server/app/docs/launch.html", @@ -135,12 +218,12 @@ export const docsPages = [ export const webPublicPages = [ { copy: [ - "Close the loop between", - "feedback and shipped code.", - "Collect requests from the channels people already use.", - "Customer feedback should not die in Slack.", + "Users asked. You shipped.", + "Amend closes the loop.", + "Amend watches selected Discord, Slack, GitHub, Linear, support, and in-app sources", + "Open source and self-hostable", ], - marker: "Close the loop between", + marker: "Users asked. You shipped.", path: "/", }, { diff --git a/scripts/agent-ready-built-web.ts b/scripts/agent-ready-built-web.ts index 92faa0c..e6dbbac 100644 --- a/scripts/agent-ready-built-web.ts +++ b/scripts/agent-ready-built-web.ts @@ -8,24 +8,29 @@ import { includesAll, locsStayOnOrigin, read, - readBundleContaining, + readBundleContainingAny, + readFirst, webPublicPages, } from "./agent-ready-built-utils"; export async function checkBuiltWebArtifacts() { - const webRobots = await read("apps/web/dist/client/robots.txt"); - const webSitemap = await read("apps/web/dist/client/sitemap.xml"); - const webLlms = await read("apps/web/dist/client/llms.txt"); + const webRobots = await readFirst([ + "apps/web/.output/public/robots.txt", + "apps/web/dist/client/robots.txt", + ]); + const webSitemap = await readFirst([ + "apps/web/.output/public/sitemap.xml", + "apps/web/dist/client/sitemap.xml", + ]); + const webLlms = await readFirst([ + "apps/web/.output/public/llms.txt", + "apps/web/dist/client/llms.txt", + ]); const webSitemapLocs = extractSitemapLocs(webSitemap); const webLlmsLinks = extractMarkdownLinks(webLlms); - const webSeoBundle = await readBundleContaining( - "apps/web/dist/server/assets", - "SoftwareApplication", - ); - const webRouterBundle = await readBundleContaining( - "apps/web/dist/server/assets", - 'canonicalLink("/")', - ); + const webServerBundleDirs = ["apps/web/.output/server/_ssr", "apps/web/dist/server/assets"]; + const webSeoBundle = await readBundleContainingAny(webServerBundleDirs, "SoftwareApplication"); + const webRouterBundle = await readBundleContainingAny(webServerBundleDirs, 'canonicalLink("/")'); add( "built web robots keeps maximum-visibility policy", @@ -108,7 +113,7 @@ export async function checkBuiltWebArtifacts() { ); for (const page of webPublicPages) { - const bundle = await readBundleContaining("apps/web/dist/server/assets", page.marker); + const bundle = await readBundleContainingAny(webServerBundleDirs, page.marker); add( `built web route bundle exposes crawlable copy for ${page.path}`, includesAll(bundle, page.copy) && !bundle.includes("noindex, nofollow"), diff --git a/scripts/agent-ready-completion-audit-production-artifact-checks.ts b/scripts/agent-ready-completion-audit-production-artifact-checks.ts index 733ba9d..cb1f4db 100644 --- a/scripts/agent-ready-completion-audit-production-artifact-checks.ts +++ b/scripts/agent-ready-completion-audit-production-artifact-checks.ts @@ -63,14 +63,14 @@ export function addProductionArtifactChecks( add( "production launch handoff uses Amend.sh origins", includesAll(context.productionEnvExample, [ - "VITE_DOCS_URL=https://docs.amend.sh/docs", + "VITE_DOCS_URL=https://amend.sh/docs", "SITE_URL=https://amend.sh", "EMAIL_FROM=Amend ", ]) && includesAll(context.launchRunbook, [ 'bunx convex env set SITE_URL "https://amend.sh"', 'bunx convex env set EMAIL_FROM "Amend "', - "VITE_DOCS_URL=https://docs.amend.sh/docs", + "VITE_DOCS_URL=https://amend.sh/docs", ]) && includesAll(context.docsLaunchPage, [ "bunx convex env set SITE_URL https://amend.sh", diff --git a/scripts/agent-ready-live-docs-surface.ts b/scripts/agent-ready-live-docs-surface.ts index 3db9f62..1d3f58f 100644 --- a/scripts/agent-ready-live-docs-surface.ts +++ b/scripts/agent-ready-live-docs-surface.ts @@ -28,6 +28,11 @@ export async function checkLiveDocsSurface({ add, docsOrigin }: LiveSurfaceConte `${docsOrigin}/schemas/agent-ready-status-report.schema.json`, `${docsOrigin}/schemas/agent-ready-completion-audit-report.schema.json`, `${docsOrigin}/docs/quickstart`, + `${docsOrigin}/docs/customer-surfaces`, + `${docsOrigin}/docs/source-events`, + `${docsOrigin}/docs/automation`, + `${docsOrigin}/docs/api-reference`, + `${docsOrigin}/docs/production-routing`, "", "", "", @@ -81,9 +86,25 @@ export async function checkLiveDocsSurface({ add, docsOrigin }: LiveSurfaceConte path: "/docs/quickstart", }, { - copy: "Connect Amend to your portal", + copy: "Choose the right integration path", path: "/docs/integration", }, + { + copy: "Wire customer identity", + path: "/docs/customer-surfaces", + }, + { + copy: "Import GitHub", + path: "/docs/source-events", + }, + { + copy: "Configure Mostly Auto rules", + path: "/docs/automation", + }, + { + copy: "Beta REST and SDK contract", + path: "/docs/api-reference", + }, { copy: "The evidence chain", path: "/docs/source-trace", @@ -92,6 +113,10 @@ export async function checkLiveDocsSurface({ add, docsOrigin }: LiveSurfaceConte copy: "Run Amend with your own deployment", path: "/docs/self-hosting", }, + { + copy: "Make docs work from docs.amend.sh", + path: "/docs/production-routing", + }, { copy: "Production launch checklist", path: "/docs/launch", diff --git a/scripts/agent-ready-report-surface-expectations.ts b/scripts/agent-ready-report-surface-expectations.ts index ef374a7..8c154ec 100644 --- a/scripts/agent-ready-report-surface-expectations.ts +++ b/scripts/agent-ready-report-surface-expectations.ts @@ -65,8 +65,13 @@ export const liveValidatorExpectedSnippets = [ 'href="${docsOrigin}/docs"', "/docs/quickstart", "/docs/integration", + "/docs/customer-surfaces", + "/docs/source-events", + "/docs/automation", + "/docs/api-reference", "/docs/source-trace", "/docs/self-hosting", + "/docs/production-routing", "/docs/launch", "--json", "jsonOutput", diff --git a/scripts/agent-ready-web-surface-tests.ts b/scripts/agent-ready-web-surface-tests.ts index ced38a2..7f54d98 100644 --- a/scripts/agent-ready-web-surface-tests.ts +++ b/scripts/agent-ready-web-surface-tests.ts @@ -40,8 +40,13 @@ export function registerAgentReadyWebSurfaceTests(read: ReadText) { "https://docs.amend.sh/docs", "https://docs.amend.sh/docs/quickstart", "https://docs.amend.sh/docs/integration", + "https://docs.amend.sh/docs/customer-surfaces", + "https://docs.amend.sh/docs/source-events", + "https://docs.amend.sh/docs/automation", + "https://docs.amend.sh/docs/api-reference", "https://docs.amend.sh/docs/self-hosting", "https://docs.amend.sh/docs/source-trace", + "https://docs.amend.sh/docs/production-routing", "https://docs.amend.sh/docs/launch", "https://docs.amend.sh/schemas/agent-ready-production-report.schema.json", "https://docs.amend.sh/schemas/agent-ready-live-report.schema.json", @@ -78,9 +83,9 @@ export function registerAgentReadyWebSurfaceTests(read: ReadText) { expect(seo).toContain('"@type": "Organization"'); expect(seo).toContain('name: "robots", content: "noindex, nofollow"'); expect(seo).toContain("twitter:card"); - expect(docsUrlHelper).toContain('DEFAULT_PRODUCTION_DOCS_URL = "https://docs.amend.sh/docs"'); + expect(docsUrlHelper).toContain('DEFAULT_PRODUCTION_DOCS_URL = "https://amend.sh/docs"'); expect(docsUrlHelper).not.toContain('DEFAULT_PRODUCTION_DOCS_URL = "/docs"'); - expect(productionEnvExample).toContain("VITE_DOCS_URL=https://docs.amend.sh/docs"); + expect(productionEnvExample).toContain("VITE_DOCS_URL=https://amend.sh/docs"); expect(productionEnvExample).toContain("SITE_URL=https://amend.sh"); expect(productionEnvExample).toContain("EMAIL_FROM=Amend "); expect(productionEnvExample).not.toContain("VITE_DOCS_URL=/docs"); diff --git a/scripts/build-size.ts b/scripts/build-size.ts index e54562e..efba18e 100644 --- a/scripts/build-size.ts +++ b/scripts/build-size.ts @@ -1,12 +1,20 @@ +import { stat } from "node:fs/promises"; + import { inspectBuildSize } from "./build-size-core"; import { collectBuildAssets, collectSearchableBuildFiles } from "./build-size-files"; -const publicDir = new URL("../apps/web/.output/public/", import.meta.url); -const assetsDir = new URL("assets/", publicDir); +const buildRoot = await firstExistingDirectory([ + new URL("../apps/web/dist/", import.meta.url), + new URL("../apps/web/.output/", import.meta.url), +]); +const assetsDir = await firstExistingDirectory([ + new URL("client/assets/", buildRoot), + new URL("public/assets/", buildRoot), +]); const inspection = inspectBuildSize({ assets: await collectBuildAssets(assetsDir), - files: await collectSearchableBuildFiles(publicDir), + files: await collectSearchableBuildFiles(buildRoot), }); if (!inspection.ok) { @@ -17,3 +25,26 @@ if (!inspection.ok) { } console.log(inspection.summary); + +async function firstExistingDirectory(candidates: URL[]) { + for (const candidate of candidates) { + if (await isDirectory(candidate)) { + return candidate; + } + } + + return candidates[0]; +} + +async function isDirectory(url: URL) { + return await stat(url) + .then((fileStat) => fileStat.isDirectory()) + .catch((error: unknown) => { + if (isMissingFileError(error)) return false; + throw error; + }); +} + +function isMissingFileError(error: unknown) { + return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"; +} diff --git a/scripts/readiness-agent-live-built-checks.ts b/scripts/readiness-agent-live-built-checks.ts index a6e1b5a..7bb40db 100644 --- a/scripts/readiness-agent-live-built-checks.ts +++ b/scripts/readiness-agent-live-built-checks.ts @@ -93,9 +93,10 @@ export function runReadinessAgentLiveBuiltChecks() { ); add( "agent-ready built artifact validator checks generated web and docs outputs", - agentReadyBuilt.includes("apps/web/dist/client/robots.txt") && - agentReadyBuilt.includes("apps/web/dist/client/sitemap.xml") && - agentReadyBuilt.includes("apps/web/dist/client/llms.txt") && + agentReadyBuilt.includes("apps/web/.output/public/robots.txt") && + agentReadyBuilt.includes("apps/web/.output/public/sitemap.xml") && + agentReadyBuilt.includes("apps/web/.output/public/llms.txt") && + agentReadyBuilt.includes("apps/web/.output/server/_ssr") && agentReadyBuilt.includes("apps/fumadocs/.next/server/app/robots.txt.body") && agentReadyBuilt.includes("apps/fumadocs/.next/server/app/robots.txt.meta") && agentReadyBuilt.includes("apps/fumadocs/.next/server/app/sitemap.xml.body") && diff --git a/scripts/readiness-production-checks.ts b/scripts/readiness-production-checks.ts index 2102de0..c4efdcd 100644 --- a/scripts/readiness-production-checks.ts +++ b/scripts/readiness-production-checks.ts @@ -28,13 +28,13 @@ export async function runProductionReadinessChecks() { hasLine(productionEnvExample, "VITE_DOCS_URL"), ); add( - "production docs URL points to docs.amend.sh", - productionEnvExample.includes("VITE_DOCS_URL=https://docs.amend.sh/docs") && + "production docs URL points to amend.sh/docs", + productionEnvExample.includes("VITE_DOCS_URL=https://amend.sh/docs") && (await read("apps/web/src/lib/docs-url.ts")).includes( - 'DEFAULT_PRODUCTION_DOCS_URL = "https://docs.amend.sh/docs"', + 'DEFAULT_PRODUCTION_DOCS_URL = "https://amend.sh/docs"', ) && - readme.includes("https://docs.amend.sh/docs") && - productionReadiness.includes("https://docs.amend.sh/docs"), + readme.includes("https://amend.sh/docs") && + productionReadiness.includes("https://amend.sh/docs"), ); add( "production handoff uses Amend.sh launch origins", @@ -42,7 +42,7 @@ export async function runProductionReadinessChecks() { productionEnvExample.includes("EMAIL_FROM=Amend ") && launchRunbook.includes('bunx convex env set SITE_URL "https://amend.sh"') && launchRunbook.includes('bunx convex env set EMAIL_FROM "Amend "') && - launchRunbook.includes("VITE_DOCS_URL=https://docs.amend.sh/docs") && + launchRunbook.includes("VITE_DOCS_URL=https://amend.sh/docs") && docsLaunchPage.includes("bunx convex env set SITE_URL https://amend.sh") && docsLaunchPage.includes("https://amend.sh/portal/acme") && !productionEnvExample.includes("SITE_URL=https://updates.example.com"), diff --git a/scripts/readiness-surface-checks.ts b/scripts/readiness-surface-checks.ts index 645b17e..bf42b89 100644 --- a/scripts/readiness-surface-checks.ts +++ b/scripts/readiness-surface-checks.ts @@ -192,10 +192,10 @@ export function runReadinessSurfaceChecks() { docsLaunchPage.includes("A/AAAA or CNAME DNS records"), ); add( - "docs launch page uses docs.amend.sh as the public docs host", + "docs launch page names canonical docs host and main-site proxy", docsLaunchPage.includes("Use the dedicated docs host for this launch") && docsLaunchPage.includes("https://docs.amend.sh/docs") && - !docsLaunchPage.includes("https://amend.sh/docs` or"), + docsLaunchPage.includes("https://amend.sh/docs"), ); add( "docs agent-ready structured data exists", @@ -213,6 +213,6 @@ export function runReadinessSurfaceChecks() { agentReadyTest.includes("aiAccessUserAgents") && agentReadyTest.includes("web robots and sitemap") && agentReadyTest.includes("docs host") && - agentReadyTest.includes('DEFAULT_PRODUCTION_DOCS_URL = "https://docs.amend.sh/docs"'), + agentReadyTest.includes('DEFAULT_PRODUCTION_DOCS_URL = "https://amend.sh/docs"'), ); } diff --git a/scripts/smoke-config-auth-checks.ts b/scripts/smoke-config-auth-checks.ts index 797f3d6..8084b4c 100644 --- a/scripts/smoke-config-auth-checks.ts +++ b/scripts/smoke-config-auth-checks.ts @@ -52,14 +52,10 @@ export async function runSmokeConfigAuthChecks() { ].join("\n"); assertIncludes(webEnvExample, "VITE_DOCS_URL=http://docs.amend.localhost:1355/docs", "web env"); - assertIncludes( - productionEnvExample, - "VITE_DOCS_URL=https://docs.amend.sh/docs", - "production env", - ); + assertIncludes(productionEnvExample, "VITE_DOCS_URL=https://amend.sh/docs", "production env"); assertIncludes(docsUrlHelper, "DEFAULT_DEV_DOCS_URL", "docs URL helper"); assertIncludes(docsUrlHelper, "DEFAULT_PRODUCTION_DOCS_URL", "docs URL helper"); - assertIncludes(docsUrlHelper, "https://docs.amend.sh/docs", "docs URL helper"); + assertIncludes(docsUrlHelper, "https://amend.sh/docs", "docs URL helper"); assertIncludes(buildSizeGuard, "forbiddenProductionTokens", "production build dev-auth guard"); assertIncludes( buildSizeGuard, diff --git a/scripts/vercel-preview-build.ts b/scripts/vercel-preview-build.ts index 14cca01..6dc9c38 100644 --- a/scripts/vercel-preview-build.ts +++ b/scripts/vercel-preview-build.ts @@ -2,6 +2,7 @@ const isPreview = process.env.VERCEL_ENV === "preview" || process.env.VITE_AMEND_PREVIEW_AUTH === "true"; const vercelUrl = process.env.VERCEL_URL; const convexUrl = process.env.VITE_CONVEX_URL; +const docsOrigin = process.env.AMEND_DOCS_ORIGIN ?? "https://docs.amend.sh"; if (!convexUrl) { throw new Error("VITE_CONVEX_URL must be set by convex deploy --cmd-url-env-var-name."); @@ -29,6 +30,7 @@ if (isPreview && vercelUrl) { await run("bun", ["run", "--cwd", "packages/sdk", "build"]); await run("bun", ["run", "--cwd", "apps/web", "build"]); await prepareVercelOutput(); +await injectDocsProxyRoutes(); await run("bun", ["scripts/posthog-sourcemaps.ts"]); await writePreviewMetadata(); @@ -48,6 +50,37 @@ async function prepareVercelOutput() { ]); } +async function injectDocsProxyRoutes() { + const configPath = ".vercel/output/config.json"; + const configFile = Bun.file(configPath); + const config = (await configFile.exists()) + ? ((await configFile.json()) as { + routes?: Array>; + version?: number; + }) + : { version: 3 }; + const routes = config.routes ?? []; + const docsProxyRoutes = [ + { src: "/docs/?", dest: `${docsOrigin}/docs` }, + { src: "/docs/(.*)", dest: `${docsOrigin}/docs/$1` }, + { src: "/og/docs/(.*)", dest: `${docsOrigin}/og/docs/$1` }, + { src: "/llms.mdx/docs/(.*)", dest: `${docsOrigin}/llms.mdx/docs/$1` }, + { src: "/schemas/(.*)", dest: `${docsOrigin}/schemas/$1` }, + { src: "/fonts/(.*)", dest: `${docsOrigin}/fonts/$1` }, + { src: "/api/search", dest: `${docsOrigin}/api/search` }, + { src: "/api/chat", dest: `${docsOrigin}/api/chat` }, + ]; + const docsRouteSources = new Set(docsProxyRoutes.map((route) => route.src)); + + config.version = 3; + config.routes = [ + ...docsProxyRoutes, + ...routes.filter((route) => typeof route.src !== "string" || !docsRouteSources.has(route.src)), + ]; + + await Bun.write(configPath, `${JSON.stringify(config, null, 2)}\n`); +} + async function writePreviewMetadata() { if (!isPreview) { return; diff --git a/vercel.json b/vercel.json index 9c0a795..7d50415 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,42 @@ { + "$schema": "https://openapi.vercel.sh/vercel.json", "git": { "deploymentEnabled": false }, "installCommand": "bun install --frozen-lockfile", - "buildCommand": "bun scripts/vercel-preview-build.ts" + "buildCommand": "bun scripts/vercel-preview-build.ts", + "rewrites": [ + { + "source": "/docs", + "destination": "https://docs.amend.sh/docs" + }, + { + "source": "/docs/:path*", + "destination": "https://docs.amend.sh/docs/:path*" + }, + { + "source": "/llms.mdx/docs/:path*", + "destination": "https://docs.amend.sh/llms.mdx/docs/:path*" + }, + { + "source": "/og/docs/:path*", + "destination": "https://docs.amend.sh/og/docs/:path*" + }, + { + "source": "/schemas/:path*", + "destination": "https://docs.amend.sh/schemas/:path*" + }, + { + "source": "/fonts/:path*", + "destination": "https://docs.amend.sh/fonts/:path*" + }, + { + "source": "/api/search", + "destination": "https://docs.amend.sh/api/search" + }, + { + "source": "/api/chat", + "destination": "https://docs.amend.sh/api/chat" + } + ] } From 5677cd1e1238b3afc014e26cc11f3f0be609fa35 Mon Sep 17 00:00:00 2001 From: Leo Date: Sun, 31 May 2026 14:09:50 -0400 Subject: [PATCH 2/2] fix: address Greptile review feedback --- apps/web/src/components/sign-in-form.tsx | 3 +- scripts/build-size.ts | 6 +++- scripts/vercel-preview-build.ts | 2 ++ vercel.json | 36 +----------------------- 4 files changed, 9 insertions(+), 38 deletions(-) diff --git a/apps/web/src/components/sign-in-form.tsx b/apps/web/src/components/sign-in-form.tsx index d5216d9..711a97c 100644 --- a/apps/web/src/components/sign-in-form.tsx +++ b/apps/web/src/components/sign-in-form.tsx @@ -33,7 +33,6 @@ const previewJoinSeededDemoWorkspaceMutationName = ["amend", "joinSeededDemoWork const joinSeededDemoWorkspaceMutation = makeFunctionReference<"mutation">( previewJoinSeededDemoWorkspaceMutationName, ); -const localDemoWorkspaceSlug = demoWorkspaceSlug; export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp?: () => void }) { const [formError, setFormError] = useState(""); @@ -95,7 +94,7 @@ export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp?: () await convex.mutation(joinSeededDemoWorkspaceMutation, { email, name: previewNameFromEmail(email), - workspaceSlug: localDemoWorkspaceSlug, + workspaceSlug: demoWorkspaceSlug, }); } diff --git a/scripts/build-size.ts b/scripts/build-size.ts index efba18e..3744b6d 100644 --- a/scripts/build-size.ts +++ b/scripts/build-size.ts @@ -33,7 +33,11 @@ async function firstExistingDirectory(candidates: URL[]) { } } - return candidates[0]; + throw new Error( + `Missing web build output. Run \`bun run build\` first. Checked: ${candidates + .map((candidate) => candidate.pathname) + .join(", ")}`, + ); } async function isDirectory(url: URL) { diff --git a/scripts/vercel-preview-build.ts b/scripts/vercel-preview-build.ts index 6dc9c38..05e8fa7 100644 --- a/scripts/vercel-preview-build.ts +++ b/scripts/vercel-preview-build.ts @@ -67,6 +67,8 @@ async function injectDocsProxyRoutes() { { src: "/llms.mdx/docs/(.*)", dest: `${docsOrigin}/llms.mdx/docs/$1` }, { src: "/schemas/(.*)", dest: `${docsOrigin}/schemas/$1` }, { src: "/fonts/(.*)", dest: `${docsOrigin}/fonts/$1` }, + // Fumadocs' AI search client calls absolute /api/* paths from proxied docs pages. + // Keep these reserved for docs while /docs is served from the main app origin. { src: "/api/search", dest: `${docsOrigin}/api/search` }, { src: "/api/chat", dest: `${docsOrigin}/api/chat` }, ]; diff --git a/vercel.json b/vercel.json index 7d50415..15a5fba 100644 --- a/vercel.json +++ b/vercel.json @@ -4,39 +4,5 @@ "deploymentEnabled": false }, "installCommand": "bun install --frozen-lockfile", - "buildCommand": "bun scripts/vercel-preview-build.ts", - "rewrites": [ - { - "source": "/docs", - "destination": "https://docs.amend.sh/docs" - }, - { - "source": "/docs/:path*", - "destination": "https://docs.amend.sh/docs/:path*" - }, - { - "source": "/llms.mdx/docs/:path*", - "destination": "https://docs.amend.sh/llms.mdx/docs/:path*" - }, - { - "source": "/og/docs/:path*", - "destination": "https://docs.amend.sh/og/docs/:path*" - }, - { - "source": "/schemas/:path*", - "destination": "https://docs.amend.sh/schemas/:path*" - }, - { - "source": "/fonts/:path*", - "destination": "https://docs.amend.sh/fonts/:path*" - }, - { - "source": "/api/search", - "destination": "https://docs.amend.sh/api/search" - }, - { - "source": "/api/chat", - "destination": "https://docs.amend.sh/api/chat" - } - ] + "buildCommand": "bun scripts/vercel-preview-build.ts" }