Repository navigation
feat(create-objectstack): a scaffolded project installs its skills from @objectstack/skills at its spec version, never from GitHub main (#22659) - #23248
Conversation
…/skills Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
… installer Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
…ills-from-package
…k records it synced from the package The sync skips, exit 0, a skill directory it did not install (one from skills add) and leaves it in place, so neither the exit code nor the directory is the verdict. Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e108996bd44e62dcf0dfbdeebf11398b955827c8 && git checkout e108996bd44e62dcf0dfbdeebf11398b955827c8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6f4c518b012a3959ba6c7d414b5fa085ad135c17 610d0c6adf1f7f880812207ce9035bd042007de1 && git checkout -B drift-repro 6f4c518b012a3959ba6c7d414b5fa085ad135c17 && git merge --no-ff 610d0c6adf1f7f880812207ce9035bd042007de1
node scripts/docs-audit/affected-docs.mjs --json 6f4c518b012a3959ba6c7d414b5fa085ad135c17
|
Contract reviewServed-tier: Isolated review at this tier, owed because the diff edits Gate verdicts on the head (latest run per name, 42 names): 32 success, 9 skipped, 1 in progress at the reading ( ① Derived judgmentsThe workflow edit (
The installer — right.
The dependency — right.
The removed text — right.
The four surface-extension files — required by the Done-when, and in-lane.
Other derived judgments.
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读 · PR #23248(#22659)· 受管面 Tier H,等你合并或批准
改了什么
为什么改
风险与代价(含回滚)
席位意见
你要做的(一个动作)
Generated by Claude Code |
Fixes #22659
Clause-②: no
A project scaffolded by
create-objectstacknow gets its AI skills from the@objectstack/skillspackage it depends on, at its own@objectstack/specversion, instead of from the repository'smainbranch. This is the cli-lane third of ruling A on #22649 (record 6096124407).What changes
src/templates/blank/package.jsondevDependenciesgain"@objectstack/skills": "^18.0.0". At scaffold timepkg-utils.tsrewrites it to the same^VERSIONas every other@objectstack/*dependency, so a project's skills version equals its spec version. The fixed group publishes both at one version (71 members,create-objectstackamong them).node_modules.src/skills-install.tsbuilds the command in one function,skillsSyncArgs(agent). The scaffolder runsnpx -y skills@1.7.2 experimental_sync --agent claude-code --copy -y. The summary prints the same command without the first-y, for re-installing after an upgrade and for another runtime. The GitHub constantSKILLS_CATALOGis gone. No GitHub fetch remains anywhere on the scaffolder's path; the only network fetch left isnpxdownloading the pinnedskillsCLI from npm.src/index.tsruns the skills step only afterPM installsucceeded. It reads the package's catalog (node_modules/@objectstack/skills/dist/skills/SKILL/SKILL.md) before the sync. After the sync it checks that every catalog skill landed in.claude/skills/as a real directory, and thatskills-lock.jsonrecords it as synced from the package. If any check fails, the run warns and prints the remedy, and the summary does not say "Installed". The checks are needed because the sync exits 0 both when there is no package ("Nothing to sync.") and when it skips a skill thatskills addput there earlier. In the second case the old copy stays in place, so the directory alone proves nothing.AGENTS.mdandREADME.mdthe scaffolder writes into a project, and this package's README, now give the package command. Before, they gavenpx skills add objectstack-ai/objectstack/skills, which readsmain. A new case intemplate-consistency.test.tsholds all three to the exact command the scaffolder runs, pin included..github/workflows/scaffold-e2e.yml, the pinned-shape project is now scaffolded without--skip-skills. A new step checks that.claude/skillsequals the installed@objectstack/skillscatalog, by names and count, and that the skills were copied, not linked. During the unpublished-version window it degrades the same way the pinned-shape step does. The existing "Skills boundary" step stays, and now guards the GitHubnextchannel that the docs keep.The installer choice:
experimental_sync --copy, measuredPR #22691 measured the layout and found it fits. I measured it again with
skills@1.7.2against the published@objectstack/skills@18.0.0-next.0. The tarball has 69 entries, with 10 skills underpackage/dist/skills/SKILL/SKILL.md. I installed it into throwaway projects with npm and with pnpm:experimental_sync --agent claude-code -y.agents/skills/pointing intonode_modules/@objectstack/skills/dist/skills/, plus 10 symlinks in.claude/skills/pointing into.agents/skills/. 0 realSKILL.mdoutsidenode_modules.experimental_sync --agent claude-code --copy -y.claude/skills/with 10 skills, 64 real files, 0 symlinks.diff -ragainst the package copy: identical except the catalog README. Plusskills-lock.json, every entrysource: @objectstack/skills,sourceType: node_modules,version: 18.0.0-next.0. Same result under pnpm's symlinkednode_modules. Re-running it is idempotent.experimental_sync --agent '*' --copy -ySKILL.md, 48 MB.No skills found,Nothing to sync., exit 0.claude/skills/objectstack-datathatskills addinstalled (locksourceType: github)Skipped objectstack-data from @objectstack/skills: installed with \skills add` from objectstack-ai/objectstack,Synced 9 skills, exit 0. The old copy and its lock entry stay. Afternpx skills remove objectstack-data -y, or afternpx skills remove --all` (which cleared all 10 directories and the lock), a re-sync gives a tree identical to the package.--agent no-such-agentValid agents: …The linked default does not work for a project that commits its skills.
node_modulesis gitignored, so a clone gets only dangling links.--copygives exactly the one-agent destination rule from theskills-install.tsheader: real files, in one directory, committed once.experimental_syncscans only the project's direct dependencies. I listed every direct dependency of the template at the versions a scaffold resolves (@objectstack/*@18.0.0-next.0,typescript@5.9.3,vitest@4.0.18). Only@objectstack/skillsships any skills, and none declares askillsfield. So the synced set is exactly that package's catalog.The CLI is pinned to
skills@1.7.2, the version measured.experimental_is that tool's own word for an unstable interface, and an unpinnednpx skillswould change the layout under every scaffold without notice.Registry and ranges
@objectstack/skillsdist-tags:{ latest: 18.0.0-next.0, next: 18.0.0-next.0 }. Published versions:0.0.0-stageand18.0.0-next.0.create-objectstackdist-tags:{ rc: 17.0.0-rc.6, latest: 17.7.0, next: 18.0.0-next.0 }, the same as@objectstack/spec.^18.0.0does not admit18.0.0-next.N, but no project installs that literal.rewriteProjectIdentitycallssyncObjectStackDeps(pkg, readCliVersion()), which rewrites every@objectstack/*range to the scaffolder's own^VERSION. A scaffold from this tree writes^18.0.0-next.0. A real scaffold resolved@objectstack/skills@18.0.0-next.0(below).npm create objectstack@nextrunscreate-objectstack@18.0.0-next.0. I read its publisheddist/index.js:SKILLS_CATALOG = "objectstack-ai/objectstack/skills", and its template has no@objectstack/skills. So it fetches skills from GitHubmain. Once this ships in the next prerelease of the fixed group,@nextwrites@objectstack/skills: ^18.0.0-next.Nand installs that version's catalog.npm create objectstack(latest, 17.7.0) does not change: it has no skills dependency, and no 17.x@objectstack/skillsis needed. I did not change the prerelease range.The scaffold e2e: what it installs from, and the count and names
Hermetic, in this package's suite (
scaffold-skills-single-copy.test.ts, 22 cases, 4.2 s of tests). It runs the real CLI throughtsx, withpnpmandnpxstubbed onPATH. Thepnpm installstub installs@objectstack/skillsonly when the project'spackage.jsondeclares it, in the published layout, with a 3-skill stub catalog. Thenpxstub implements the measuredexperimental_syncdiscovery, its destination rows and its skip of a skill it did not install. It refuses (exit 3) any argv nobody measured: anyskills add, the unpinned CLI, or an unknown agent. The test asserts:@objectstack/spec's range;listSkills(node_modules/@objectstack/skills/dist/skills)== the stub catalog, with the same count;SKILL.mdper skill, 0 symlinks;Vacuity cases: the linked shape stages 6 links and 0
SKILL.md, and its clone is 6 dangling links; "Nothing to sync" exits 0.Registry, in CI. The new
scaffold-e2e.ymlstep reads the catalog from the registry-installednode_modules/@objectstack/skills.Real, run locally once on this branch. Steps:
create-objectstack pinned-probe, with no flags, so its ownpnpm installran against the registry.Synced 10 skillsfrom@objectstack/skills, and the Dockerfile was pinned to18.0.0-next.0.run:script, taken out of the YAML, underbash -ewithRUNNER_TEMPpointed at that project. It printedcatalog (10 skills)==installed in .claude/skills (10 skills)forobjectstack-ai,objectstack-api,objectstack-automation,objectstack-data,objectstack-formula,objectstack-i18n,objectstack-platform,objectstack-query,objectstack-uiandobjectstack-upgrade, and exited 0.git init && git add -Athen staged 10SKILL.md, all under.claude/, and 0 symlinks. The generatedAGENTS.mdandREADME.mdcontain noskills add objectstack-aiand do contain the package command.--skip-installand--skip-skillsBoth are tested end to end in the same file:
--skip-install: nonpxcall, nonode_modules, no.claude. The summary says "Not installed yet … Afterpnpm install:" and gives the command.--skip-skills: the install runs (the catalog is innode_modules), no sync runs, and the summary prints "AI Skills (recommended)" with the command.objectstack-datafromskills add. The sync skips that skill, and the run reports "1 of 3 skill(s) … (objectstack-data)" instead of "Installed for".Reverse verification
I applied each mutation with
scripts/ablation-replace.mjs(wrap mode, restore on exit). The script also had its owntrap … EXIT INT TERMthat restores fromHEADand compares blob hashes. Both ran onscaffold-skills-single-copy.test.tsat the final head,610d0c6adf. The earlier run at026b6bc8fbgave the same direction. The subject issrc: the test spawnstsx src/index.tsand imports./skills-install.jsfromsrc, so there is nodistleg.c1862cd7199a→525b315283bdskills add objectstack-ai/objectstack/skills --skill '*' --agent AGENT -y)8ec0100b6330→1e948aaf99baAfter both legs, each path's blob equals
HEADandgit diff HEADis empty. Both ablations go red, as expected.Gates, at the final head
610d0c6adfI derived the list with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackwith no paths: 103 families, the same set as at026b6bc8fb. I ran each with its exit code captured before any pipe, and reconciled with--ran: "103 derived, 103 run, 0 NOT-MEASURED, 0 UNRUN". All 103 exited 0, including:check:pm-dispatch-gates(fast tier: 1833 cases pass, 6 slow sections deferred);check:template-version-sync,check:cross-package-test-inputs,check:published-files,check:doc-authoring,check:nul-bytes;check:workflow-step-name-quoting,check:workflow-status-functions,check:pm-governed-merges;check:dual-build-cjs-loadsandcheck:lean-entry-closure, measured on a fully built tree;origin/main.Six of them overlapped my ablation run in time, so I re-ran those six on the clean tree. They exited 0 again.
Also run:
check:published-readme-links0 andcheck:published-readme-exports0, because the published README changed.Package, under the shared verify lock:
pnpm --filter 'create-objectstack^...' build0;pnpm --filter create-objectstack build0 (check-dts-emitted: 1/1);typecheck0;vitest run: 18 files, 274 tests passed.Template stamping:
node scripts/sync-template-versions.mjsprinted "blank/package.json already pins ^18.0.0 across 9 @objectstack/* dep(s)" (8 on the base tree) and left the tree clean. So the new entry is stamped with the rest, and the script is unchanged.Lint, as a measured narrowing at
610d0c6adf:eslint --no-inline-config --format jsonover the 7 TS files this PR touches gave 7 files, 0 errors, 0 warnings. The population iseslint.config.mjsjudging., thepnpm lintcommand; none of the 7 is ignored (an ignored file would carry the "File ignored" warning). Invariance: the config enables no type-aware linting (its own note says there is noparserOptions.projectand there are no typed rules), so this diff cannot change the verdict on any untouched file. The repo-wide run is left to CI.Not run locally, left to CI:
@objectstack/cli'stest/scaffold-emission-policy.e2e.test.tsreads the templatepackage.jsonas a declared cross-package input. It compares only third-party ranges (thirdPartyOnlydrops every@objectstack/name).Files beyond the claim's listed surface
The card requires the GitHub command to disappear from the scaffolder's output, and four files outside the claim had to change for that:
src/templates/AGENTS.mdandsrc/templates/blank/README.md;template-consistency.test.ts, which importedSKILLS_CATALOG, andstarter-comments-self-contained.test.ts, which pinned the README's command.All of these are in
packages/create-objectstack. No other package, noskills/**, nocontent/docs/**, and no script was touched.Docs
No
content/docs/**file is edited here. The devx-lane docs PR #23220 for #22668 has already landed onmain. It describes the scaffolder of 18.0.0-next.0, which installs frommain, and tells the reader to replace that with the package andnpx skills experimental_sync --agent claude-code -y:content/docs/getting-started/build-with-claude-code.mdx:60-67content/docs/getting-started/your-first-project.mdx:47-54Once this lands, the next prerelease's scaffolder does that itself. Two follow-ups belong to #22668, not this PR:
packages/skills/README.mdgive the linked sync, without--copy. The scaffolder runs with--copy, and theAGENTS.mdit writes says to keep--copy, because the links point into the gitignorednode_modules(measured above).Acceptance notes
docs/qa/platform-checklist/areas/cli.jsonkeeps its "skills boundary probe" item,skills add CHECKOUT/skills --all --copy. That probe checks the GitHub channel, not the scaffolder. It is not changed here.skills@1.7.2pin means measuring again. Thenpxstub'sMEASURED_CLIis written out separately from the source, so moving the pin inskills-install.tsalone turns the e2e red.Generated by Claude Code