From 7ec51863d5bd7206c6b5e74c8e0095222e014949 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 01:41:05 +0000 Subject: [PATCH 1/4] fix(create-objectstack): converge scaffold docs on canonical host, drop dead monorepo refs Fixes #10990: templates/AGENTS.md, blank/Dockerfile, blank/docker-compose.yml still pointed at objectstack.com (wrong domain) or the unratified docs.objectstack.ai alias; converge all three on the ruled canonical origin https://objectstack.ai. Fixes #11022: blank/README.md's last two monorepo-only references (ADR-0097, and an unlinked mention of skills/ 'in the ObjectStack framework repo') are rewritten self-contained, with the ADR-0097 line pointing at the public connectors doc and the skills line pointing at the followable 'npx skills add' form the scaffolder's own closing output already uses. Extends starter-comments-self-contained.test.ts (#10324) with the pin obligations both cards call for: assertion 4 pins host convergence via the same shippedFiles() walker (no other gate's population reaches these template files), and a fifth MONOREPO_ONLY pattern closes the prose-shaped hole the four syntactic patterns missed. The self-retiring EXCLUDED entry for blank/README.md is removed now that the file cites nothing monorepo-only. --- .../starter-comments-self-contained.test.ts | 89 ++++++++++++++++--- .../src/templates/AGENTS.md | 2 +- .../src/templates/blank/Dockerfile | 2 +- .../src/templates/blank/README.md | 12 +-- .../src/templates/blank/docker-compose.yml | 2 +- 5 files changed, 85 insertions(+), 22 deletions(-) diff --git a/packages/create-objectstack/src/starter-comments-self-contained.test.ts b/packages/create-objectstack/src/starter-comments-self-contained.test.ts index 181f72a14d..328e64bafa 100644 --- a/packages/create-objectstack/src/starter-comments-self-contained.test.ts +++ b/packages/create-objectstack/src/starter-comments-self-contained.test.ts @@ -37,10 +37,28 @@ // lines rather than imported, because importing a root script into this package // would widen this suite's declared cross-package read radius to buy nothing. // -// Host CONVERGENCE is deliberately not asserted here — the tree still carries -// two non-canonical docs hostnames and they are another card's (#10990). This -// pin only judges URLs already on the canonical origin, so the two cards cannot -// collide. +// Assertion 3 only judges URLs already on the canonical origin; host +// CONVERGENCE — is the origin the RULED one at all — is assertion 4 (#10990). +// It could not have lived anywhere else: `scripts/check-published-readme-links.mjs` +// prescribes the same canonical origin, but its population is publishable +// packages' PUBLISHED markdown, which never reaches this package's templates +// — `templates/AGENTS.md` is a template file under `src/`, not a package +// README, and `Dockerfile` / `docker-compose.yml` are not markdown at all. +// `shippedFiles()` below is the one walker in the repo that already +// enumerates exactly what a scaffold ships, so the host pin belongs here. +// +// ## A fifth `MONOREPO_ONLY` pattern, found while fixing #11022 +// +// `blank/README.md` named "the ObjectStack framework repo" as the home of +// `skills/` — a monorepo-only reference in PROSE rather than in one of the +// four syntactic shapes (ADR id / issue number / script path / package path) +// the first four patterns matched. Nothing in this file's original four +// patterns could have caught it, which is why removing the ADR-0097 reference +// this same README carried and deleting the (now self-retired) `EXCLUDED` +// entry for it would otherwise have let assertion 1 pass with that second, +// still-unfollowable line untouched. See the fifth `MONOREPO_ONLY` entry +// below for how it is scoped to avoid the reader's own project also being +// called "a monorepo" (`blank/pnpm-workspace.yaml`, correctly). import { describe, it, expect } from 'vitest'; import fs from 'node:fs'; @@ -52,17 +70,15 @@ const templateRoot = path.resolve(HERE, 'templates'); const contentDocs = path.resolve(HERE, '..', '..', '..', 'content', 'docs'); /** - * The blank template's README is scanned by nothing here yet: it still carries - * an ADR identifier of its own, and it is owned by other cards in the same - * family (a scaffolding-guidance fix was in flight over it while this landed). - * - * The exclusion is SELF-RETIRING rather than permanent — the last assertion - * fails the moment the README stops needing it, so whoever cleans that file is - * told, in their own run, to delete this entry and let the file be scanned. - * A silent exemption over the most-read file in the tree is the failure this - * shape exists to avoid. + * Nothing is excluded — every shipped file is scanned. `blank/README.md` used + * to carry a self-retiring entry here ("still carries an ADR identifier; owned + * by another card") while its ADR-0097 reference and its unlinked "ObjectStack + * framework repo" reference were another card's (#11022); both are gone now, + * so the retirement fired as designed and this map goes back to empty rather + * than staying around as a silent exemption over the most-read file in the + * tree. */ -const EXCLUDED = new Map([['blank/README.md', 'still carries an ADR identifier; owned by another card']]); +const EXCLUDED = new Map(); /** Text files the scaffolder copies into the user's project. */ function shippedFiles(): string[] { @@ -88,6 +104,17 @@ const MONOREPO_ONLY = [ { label: 'a bare issue number', re: /(^|[^\w/])#\d{3,6}\b/ }, { label: 'a repo build-script path', re: /\bscripts\/[\w.-]+\.(?:mjs|mts|cjs|ts|js)\b/ }, { label: 'a monorepo package path', re: /\bpackages\/[a-z0-9][\w-]*\//i }, + // #11022: `blank/README.md` named "the ObjectStack framework repo" as the + // home of `skills/`, unlinked — a reader with only their own scaffolded + // project has no way to reach it. The first four patterns are syntactic + // identifiers (an ADR id, an issue number, a repo-relative path); this one + // is the same class of defect in prose form, so it is spelled to the + // FRAMEWORK'S OWN NAME next to a "repo" word, not to that one sentence — + // it survives a reword. Deliberately narrower than a bare "repo" or + // "monorepo" match: this template tree also calls the READER'S OWN project + // "a monorepo root" (`blank/pnpm-workspace.yaml`), which is a correct, + // self-contained, followable statement about a directory they do have. + { label: 'a reference to the ObjectStack repo as an unlinked location', re: /\bObjectStack (?:framework )?(?:mono)?repo\b/i }, ]; const read = (rel: string) => fs.readFileSync(path.join(templateRoot, rel), 'utf8'); @@ -188,6 +215,40 @@ describe('shipped template comments are followable by a stranger', () => { } }); + // ── assertion 4: no non-canonical docs host ships into a project ──────── + // #10990: three shipped lines cited `objectstack.com` (not even a + // redirecting alias — a different, wrong domain) or `docs.objectstack.ai` + // (an accepted-but-unratified alias per + // scripts/check-published-readme-links.mjs's DOCS_HOSTS) instead of the + // ruled canonical origin (maintainer ruling, 2026-08-21). Full reasoning on + // why this population needed its own pin is in the file header above. + it.each(shippedFiles())('%s cites no non-canonical docs host', (rel) => { + // Restated, not imported, for the same cross-package-read-radius reason + // assertion 3's candidate list gives above. This is every host + // `scripts/check-published-readme-links.mjs`'s DOCS_HOSTS classifies as + // this docs site (canonical + redirecting aliases) MINUS the canonical + // origin itself, plus `objectstack.com` — a different, wrong domain that + // is not in that set at all, so it is not a "host" in that gate's sense, + // just a mistake this population has actually shipped. + const NON_CANONICAL_DOCS_HOSTS = [ + 'docs.objectstack.ai', + 'www.objectstack.ai', + 'www.docs.objectstack.ai', + 'protocol.objectstack.ai', + 'www.protocol.objectstack.ai', + 'objectstack.com', + ]; + const re = new RegExp( + `https?://(?:${NON_CANONICAL_DOCS_HOSTS.map((h) => h.replace(/\./g, '\\.')).join('|')})\\b`, + ); + const hit = re.exec(read(rel)); + expect( + hit, + `${rel} cites ${JSON.stringify(hit?.[0])}, a non-canonical docs host. Converge ` + + 'on https://objectstack.ai (maintainer ruling, 2026-08-21).', + ).toBeNull(); + }); + // ── the exclusion is live, or it is gone ───────────────────────────────── it.each([...EXCLUDED.keys()])('%s still needs its exclusion', (rel) => { const text = read(rel); diff --git a/packages/create-objectstack/src/templates/AGENTS.md b/packages/create-objectstack/src/templates/AGENTS.md index 36aa94c4b6..c4b5ed0aa9 100644 --- a/packages/create-objectstack/src/templates/AGENTS.md +++ b/packages/create-objectstack/src/templates/AGENTS.md @@ -96,6 +96,6 @@ Skills are triggered automatically based on task context: ## Learn More -- [ObjectStack Documentation](https://objectstack.com/docs) +- [ObjectStack Documentation](https://objectstack.ai/docs) - [GitHub: objectstack-ai/objectstack](https://github.com/objectstack-ai/objectstack) - [Skills CLI](https://skills.sh/) — Manage AI skills across agents diff --git a/packages/create-objectstack/src/templates/blank/Dockerfile b/packages/create-objectstack/src/templates/blank/Dockerfile index 11599be626..0431670690 100644 --- a/packages/create-objectstack/src/templates/blank/Dockerfile +++ b/packages/create-objectstack/src/templates/blank/Dockerfile @@ -7,7 +7,7 @@ # my-app # # Or run the full app + Postgres stack: see docker-compose.yml. -# Docs: https://docs.objectstack.ai/docs/deployment/self-hosting +# Docs: https://objectstack.ai/docs/deployment/self-hosting # ── Build stage: compile TypeScript metadata to the artifact ───────── FROM node:22-slim AS build diff --git a/packages/create-objectstack/src/templates/blank/README.md b/packages/create-objectstack/src/templates/blank/README.md index 36f8d3fb1d..ab302ca65b 100644 --- a/packages/create-objectstack/src/templates/blank/README.md +++ b/packages/create-objectstack/src/templates/blank/README.md @@ -79,9 +79,11 @@ can call an external system from a flow as pure metadata — no host code: Add a `connectors:` entry that names one of these `provider`s and the `automation` capability materializes it into a live, dispatchable connector at -boot (ADR-0097); a flow's `connector_action` node then calls it. To add a brand -connector (e.g. Slack), install its package and add `new ConnectorSlackPlugin()` -to `plugins:`; to drop a provider, remove its plugin. +boot — see [Automation → Connectors](https://objectstack.ai/docs/automation/connectors) +for how that materialization works; a flow's `connector_action` node then +calls it. To add a brand connector (e.g. Slack), install its package and add +`new ConnectorSlackPlugin()` to `plugins:`; to drop a provider, remove its +plugin. > **Security — declarative MCP over stdio.** An `mcp` connector whose transport > spawns a local process (`stdio`) is denied by default, because the command @@ -136,5 +138,5 @@ covered in [Self-Hosted Deployment](https://objectstack.ai/docs/deployment/self- - Add a flow or automation: see `objectstack-automation`. - Add an AI agent: see `objectstack-ai`. -Skills live in `skills/` in the ObjectStack framework repo and in the in-IDE -assistant catalog. +Skills are installed with `npx skills add objectstack-ai/objectstack/skills` +(see `AGENTS.md`) and also show up in the in-IDE assistant catalog. diff --git a/packages/create-objectstack/src/templates/blank/docker-compose.yml b/packages/create-objectstack/src/templates/blank/docker-compose.yml index d57511597f..6118dcc8ed 100644 --- a/packages/create-objectstack/src/templates/blank/docker-compose.yml +++ b/packages/create-objectstack/src/templates/blank/docker-compose.yml @@ -4,7 +4,7 @@ # POSTGRES_PASSWORD / OS_AUTH_SECRET / OS_SECRET_KEY (generate secrets with # `openssl rand -hex 32`), then `docker compose up -d`. # -# Docs: https://docs.objectstack.ai/docs/deployment/self-hosting +# Docs: https://objectstack.ai/docs/deployment/self-hosting services: app: From d4bfa10cd4413eca287ccf602d216bb4df8ebed0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 01:51:25 +0000 Subject: [PATCH 2/4] chore: add changeset for #10990 + #11022 --- ...ffold-docs-canonical-host-and-dead-refs.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .changeset/scaffold-docs-canonical-host-and-dead-refs.md diff --git a/.changeset/scaffold-docs-canonical-host-and-dead-refs.md b/.changeset/scaffold-docs-canonical-host-and-dead-refs.md new file mode 100644 index 0000000000..a4b7554258 --- /dev/null +++ b/.changeset/scaffold-docs-canonical-host-and-dead-refs.md @@ -0,0 +1,34 @@ +--- +"create-objectstack": patch +--- + +fix(create-objectstack): converge scaffold docs on the canonical host and drop the last two dead monorepo references (#10990, #11022) + +A freshly scaffolded project shipped a handful of text lines a reader with +only their own project — no monorepo, no `docs/adr/`, no issue tracker — +could not follow: + +- `templates/AGENTS.md` linked `https://objectstack.com/docs`, a domain that + is not this project's docs site at all (not even a redirecting alias). +- `templates/blank/Dockerfile` and `templates/blank/docker-compose.yml` both + linked `https://docs.objectstack.ai/...`, an accepted-but-unratified alias. + All three now point at the ruled canonical origin, `https://objectstack.ai` + (maintainer ruling, 2026-08-21). +- `templates/blank/README.md` cited `ADR-0097` and named "the ObjectStack + framework repo" as the home of `skills/` — both rewritten self-contained, + keeping the fact each was carrying: the connector-materialization line now + links the public [Automation → Connectors](https://objectstack.ai/docs/automation/connectors) + page, and the skills line now names the followable + `npx skills add objectstack-ai/objectstack/skills` install the scaffolder's + own closing output already uses. + +`packages/create-objectstack/src/starter-comments-self-contained.test.ts` +(#10324) gains two pin obligations these two fixes call for: a host-convergence +assertion driven by the same `shippedFiles()` walker that already enumerates +everything a scaffold ships (no other repo gate's population reaches these +template files — `check:published-readme-links` reads publishable packages' +published markdown only), and a fifth `MONOREPO_ONLY` pattern that catches a +prose-shaped reference to this repo ("the ObjectStack framework repo") the +first four, syntax-shaped patterns could not. The self-retiring `EXCLUDED` +entry for `blank/README.md` is removed now that the file cites nothing +monorepo-only. From cf9a767b363d83969f050cabf579a9906cdf8f65 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 02:16:29 +0000 Subject: [PATCH 3/4] fix(create-objectstack): pin the two RATIONALE facts #11022's rewrites carry blank/README.md's rewritten connectors and skills lines were unguarded by assertion 2: deleting the 'at boot' materialization clause or the 'npx skills add objectstack-ai/objectstack/skills' install command left assertions 1/3/4 all green, since none of them notice a fact quietly disappearing rather than an unfollowable reference reappearing. Add a blank/README.md entry to RATIONALE with one fact per rewritten line, matched loosely enough that rewording stays free. --- .../src/starter-comments-self-contained.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/create-objectstack/src/starter-comments-self-contained.test.ts b/packages/create-objectstack/src/starter-comments-self-contained.test.ts index 328e64bafa..336fd290fe 100644 --- a/packages/create-objectstack/src/starter-comments-self-contained.test.ts +++ b/packages/create-objectstack/src/starter-comments-self-contained.test.ts @@ -166,6 +166,19 @@ describe('shipped template comments are followable by a stranger', () => { { what: 'that declaring it is required rather than optional', re: /required|refuses/i }, ], }, + { + // #11022's two rewrites (ADR-0097 -> a public docs link; "the ObjectStack + // framework repo" -> the followable install form) are RATIONALE entries + // too, for the same reason blank/objectstack.config.ts and + // note.object.ts already are: assertions 1/3/4 only ever check that + // something UNFOLLOWABLE is absent or that a present URL resolves -- + // none of them notice a fact quietly disappearing along the way. + file: 'blank/README.md', + facts: [ + { what: 'that a provider-bound connector is materialized into a live, dispatchable connector at boot (not written by hand)', re: /materializ\w*[^.]*?\bat\s+boot\b/i }, + { what: 'the followable skills install command (`npx skills add objectstack-ai/objectstack/skills`)', re: /npx skills add objectstack-ai\/objectstack\/skills/i }, + ], + }, ]; for (const { file, facts } of RATIONALE) { From f50d8b0effea24c10ce24364d4f4bd7cae25afa0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 02:35:55 +0000 Subject: [PATCH 4/4] fix(create-objectstack): reword gate mentions to avoid the cross-package-test-inputs literal collector check:cross-package-test-inputs' flat literal collector matches ANY backtick/quote-wrapped repo-relative path anywhere in a file's text, including prose comments -- it cannot tell a real specifier from a mention. Three comments named scripts/check-published-readme-links.mjs in backticks while explaining why assertion 4 could not live in that gate, which the collector read as an escaping dependency and demanded a declared glob for. The test does not actually read that file (its host list is restated, not imported, exactly like assertion 3's candidate list already is), so the correct fix is to stop spelling the path, not to declare a false dependency -- this package's own entry in that gate's script documents the identical trade for a prior case (scripts/sync-template-versions.mjs while it was still only a mention: 'rewording ... into unquoted prose unforced a live radius'). Renamed the three sites to 'the published-readme-links gate' with no scripts/ prefix and no quoting. --- .../starter-comments-self-contained.test.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/create-objectstack/src/starter-comments-self-contained.test.ts b/packages/create-objectstack/src/starter-comments-self-contained.test.ts index 336fd290fe..e781717bb6 100644 --- a/packages/create-objectstack/src/starter-comments-self-contained.test.ts +++ b/packages/create-objectstack/src/starter-comments-self-contained.test.ts @@ -39,7 +39,7 @@ // // Assertion 3 only judges URLs already on the canonical origin; host // CONVERGENCE — is the origin the RULED one at all — is assertion 4 (#10990). -// It could not have lived anywhere else: `scripts/check-published-readme-links.mjs` +// It could not have lived anywhere else: the published-readme-links gate // prescribes the same canonical origin, but its population is publishable // packages' PUBLISHED markdown, which never reaches this package's templates // — `templates/AGENTS.md` is a template file under `src/`, not a package @@ -231,18 +231,18 @@ describe('shipped template comments are followable by a stranger', () => { // ── assertion 4: no non-canonical docs host ships into a project ──────── // #10990: three shipped lines cited `objectstack.com` (not even a // redirecting alias — a different, wrong domain) or `docs.objectstack.ai` - // (an accepted-but-unratified alias per - // scripts/check-published-readme-links.mjs's DOCS_HOSTS) instead of the - // ruled canonical origin (maintainer ruling, 2026-08-21). Full reasoning on - // why this population needed its own pin is in the file header above. + // (an accepted-but-unratified alias per the published-readme-links gate's + // DOCS_HOSTS) instead of the ruled canonical origin (maintainer ruling, + // 2026-08-21). Full reasoning on why this population needed its own pin is + // in the file header above. it.each(shippedFiles())('%s cites no non-canonical docs host', (rel) => { // Restated, not imported, for the same cross-package-read-radius reason - // assertion 3's candidate list gives above. This is every host - // `scripts/check-published-readme-links.mjs`'s DOCS_HOSTS classifies as - // this docs site (canonical + redirecting aliases) MINUS the canonical - // origin itself, plus `objectstack.com` — a different, wrong domain that - // is not in that set at all, so it is not a "host" in that gate's sense, - // just a mistake this population has actually shipped. + // assertion 3's candidate list gives above. This is every host the + // published-readme-links gate's DOCS_HOSTS classifies as this docs site + // (canonical + redirecting aliases) MINUS the canonical origin itself, + // plus `objectstack.com` — a different, wrong domain that is not in that + // set at all, so it is not a "host" in that gate's sense, just a mistake + // this population has actually shipped. const NON_CANONICAL_DOCS_HOSTS = [ 'docs.objectstack.ai', 'www.objectstack.ai',