Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/22659-scaffold-skills-from-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"create-objectstack": minor
---

feat(create-objectstack): a scaffolded project installs its AI skills from the `@objectstack/skills` package at its own `@objectstack/spec` version, never from the repository's `main` branch

Clause-②: no

- **The project depends on the catalog.** The blank template's `devDependencies` gain `@objectstack/skills`, at the same `^<version>` as every other `@objectstack/*` dependency, so a new project's dependency set changes: the skills are the ones written for the `@objectstack/spec` it installs.
- **The install reads `node_modules`.** After `<pm> install`, the scaffolder runs `npx -y skills@1.7.2 experimental_sync --agent claude-code --copy -y`, which copies the installed package's catalog into `.claude/skills/` as real files, once, beside a `skills-lock.json` that names the package as every skill's source. The GitHub `npx skills add objectstack-ai/objectstack/skills …` command is gone from the run, its summary and the `AGENTS.md` and `README.md` it writes into the project.
- **The summary says what happened.** It names the package version installed and prints the same command for re-installing after an upgrade of the `@objectstack/*` packages, and for any other agent runtime. When the install leaves no catalog to read, or a skill did not come from the package, the run says so instead of reporting the skills installed.
- **The flags keep their meaning.** `--skip-skills` skips the skills step. `--skip-install` installs no skills, because nothing is in `node_modules` yet; the summary names `<pm> install` and then the sync command.
- **To move a project created earlier onto the package:** add `@objectstack/skills` to `devDependencies` at your `@objectstack/spec` range and run your package manager's install. Remove the skills `skills add` installed — `npx skills remove objectstack-data -y` and so on for each `objectstack-*` skill, or `npx skills remove --all` if you installed no others — because the sync skips a skill it did not install and leaves the old copy in place. Then run `npx skills@1.7.2 experimental_sync --agent claude-code --copy -y` (name your own runtime after `--agent`).
73 changes: 65 additions & 8 deletions .github/workflows/scaffold-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,14 @@ jobs:

- name: 'Skills boundary: only the curated skills/ catalog installs'
# 15.1 third-party eval: the repo-internal dogfood-verification skill
# (.claude/skills/) leaked into scaffolded projects. Two leak paths,
# two assertions against THIS checkout:
# 1. The scaffolder/docs command is `skills add …/objectstack/skills
# --all` — the /skills subpath is the hard boundary, because the
# skills CLI's --all implies --skill '*' which INCLUDES
# (.claude/skills/) leaked into scaffolded projects. The scaffolder no
# longer reads GitHub — it syncs the installed @objectstack/skills
# package, asserted by the step after the pinned shape below — so
# these two assertions now guard the GitHub path the docs keep as the
# `next` channel, against THIS checkout:
# 1. The documented command is `skills add …/objectstack/skills …` —
# the /skills subpath is the hard boundary, because the skills
# CLI's --all implies --skill '*' which INCLUDES
# metadata.internal skills. Install from the subpath and assert
# set-equality with the curated catalog.
# 2. Interactive `skills add …/framework` (no --all) relies on
Expand Down Expand Up @@ -293,8 +296,10 @@ jobs:
# never RUNNING — a scaffolder that stops calling it, or an install path
# that no longer leaves a resolvable CLI behind.
#
# `--skip-skills` is kept: skills install after the pin, so dropping it
# would only add a network fetch, never coverage.
# No `--skip-skills` either: the scaffolder's skills step reads the
# catalog out of the node_modules this same install wrote, so this is
# the one project in the job whose skills a real user would get. The
# step after this one asserts them.
#
# The unpublished-version window is the one degradation, and it is
# evidenced rather than blanket. The install step above carries the
Expand Down Expand Up @@ -337,7 +342,7 @@ jobs:
# still catches a genuinely broken scaffolder install.
run: |
cd "$RUNNER_TEMP"
node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" pinned-probe --skip-skills
node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" pinned-probe
cd pinned-probe

if [ ! -f node_modules/@objectstack/cli/package.json ]; then
Expand Down Expand Up @@ -377,6 +382,58 @@ jobs:
fi
echo "pinned as expected: FROM tag == resolved CLI ($RESOLVED)"

- name: 'Skills: the scaffolder installed exactly the @objectstack/skills catalog'
# The pinned-shape project above was scaffolded the way a real user
# does it, skills included. The scaffolder syncs the catalog out of the
# @objectstack/skills package its own install put in node_modules — at
# the range it gives every @objectstack/* dependency, so the version of
# @objectstack/spec — into .claude/skills/ as real files (the measured
# shapes live in packages/create-objectstack/src/skills-install.ts).
# So the catalog this step compares against is the package's, read off
# the registry-installed node_modules, never this checkout's skills/:
# the same skill names, the same count, copied rather than linked.
#
# The unpublished-version window degrades the way the pinned-shape step
# does, and only that window: with nothing installed at all, that step
# has already classified the registry answer and failed on any other
# cause, so this step asserts the degraded shape — no skills from
# anywhere — instead of nothing. An install that resolved the CLI but
# no @objectstack/skills is a template that lost its dependency: red.
run: |
cd "$RUNNER_TEMP/pinned-probe"
PKG=node_modules/@objectstack/skills
if [ ! -f "$PKG/package.json" ]; then
if [ -f node_modules/@objectstack/cli/package.json ]; then
echo "::error::the scaffolder's install resolved @objectstack/cli but no @objectstack/skills, so this project got no skills catalog at its own version — the template no longer depends on the package"
exit 1
fi
if [ -e .claude/skills ]; then
echo "::error::nothing was installed, yet .claude/skills exists — the scaffolder installed skills from a source other than the @objectstack/skills package"
exit 1
fi
echo "::warning::no @objectstack/skills is installed — the unpublished-version window the pinned-shape step classified. Catalog EQUALITY is SKIPPED for this run; the degraded shape (no skills from any source) was asserted."
exit 0
fi

skills_in() { for d in "$1"/*/; do [ -f "${d}SKILL.md" ] && [ ! -L "${d%/}" ] && basename "$d"; done | sort; }
expected=$(skills_in "$PKG/dist/skills")
installed=$(skills_in .claude/skills)
echo "@objectstack/skills@$(node -p "require('./$PKG/package.json').version") catalog ($(echo "$expected" | grep -c .) skills):"; echo "$expected"
echo "installed in .claude/skills ($(echo "$installed" | grep -c .) skills):"; echo "$installed"
if [ -z "$expected" ]; then
echo "::error::the installed @objectstack/skills holds no dist/skills/<skill>/SKILL.md — the layout the scaffolder reads has moved"
exit 1
fi
if [ "$installed" != "$expected" ]; then
echo "::error::the skills the scaffolder installed are not the installed package's catalog (names and counts above)"
exit 1
fi
if [ -e .agents ] || [ -n "$(find .claude -type l)" ]; then
echo "::error::the scaffolder linked the catalog instead of copying it — a clone of this project would hold dangling links into node_modules"
exit 1
fi
echo "skills as expected: .claude/skills == the installed @objectstack/skills catalog"

- name: Build official runtime image from this checkout
# The scaffolded app's Dockerfile builds FROM
# ghcr.io/objectstack-ai/objectstack. Build that base HERE from
Expand Down
20 changes: 13 additions & 7 deletions packages/create-objectstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,19 @@ you so rather than failing as an unknown template.
**namespace** is derived from the project name (`my-app` → `my_app`) and
every object name in the template is re-prefixed to match
(`blank_note` → `my_app_note`).
2. Installs dependencies (pnpm if available, otherwise npm).
3. Installs the ObjectStack AI skills bundle for **one** coding agent —
`npx skills add objectstack-ai/objectstack/skills --skill '*' --agent claude-code -y`,
scoped to the curated `skills/` catalog. One agent, not `--all`, so the
bundle lands in exactly one directory (`.claude/skills/`) and a scaffolded
project commits it once; `src/skills-install.ts` carries the measurement.
The closing summary prints the same command for any other runtime.
2. Installs dependencies (pnpm if available, otherwise npm). They include
`@objectstack/skills`, the ObjectStack AI skills catalog, at the same
version as `@objectstack/spec`.
3. Installs that catalog for **one** coding agent, from `node_modules` —
`npx -y skills@1.7.2 experimental_sync --agent claude-code --copy -y` — so
the skills teach the version the project runs, never the repository's
unreleased `main`. One agent and `--copy`, so the catalog lands as real
files in exactly one directory (`.claude/skills/`) and a scaffolded project
commits it once; `src/skills-install.ts` carries the measurement. The
closing summary prints the same command for any other runtime, and for
re-installing after an upgrade. With `--skip-install` nothing is in
`node_modules` yet, so no skills are installed and the summary says how to
get them.
4. Writes `AGENTS.md` and `.github/copilot-instructions.md` with the project
conventions — unless the template ships its own.

Expand Down
Loading
Loading