Skip to content

Commit 5da2c0f

Browse files
feat(create-objectstack): a scaffolded project installs its skills from @objectstack/skills at its spec version, never from GitHub main (#22659) (#23248)
Fixes #22659 Clause-②: no A project scaffolded by `create-objectstack` now gets its AI skills from the `@objectstack/skills` package it depends on, at its own `@objectstack/spec` version, instead of from the repository's `main` branch. This is the cli-lane third of ruling A on #22649 (record 6096124407). ## What changes - **The template depends on the catalog.** `src/templates/blank/package.json` `devDependencies` gain `"@objectstack/skills": "^18.0.0"`. At scaffold time `pkg-utils.ts` rewrites it to the same `^VERSION` as 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-objectstack` among them). - **One installer, reading `node_modules`.** `src/skills-install.ts` builds the command in one function, `skillsSyncArgs(agent)`. The scaffolder runs `npx -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 constant `SKILLS_CATALOG` is gone. No GitHub fetch remains anywhere on the scaffolder's path; the only network fetch left is `npx` downloading the pinned `skills` CLI from npm. - **The run checks before it reports.** `src/index.ts` runs the skills step only after `PM install` succeeded. 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 that `skills-lock.json` records 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 that `skills add` put there earlier. In the second case the old copy stays in place, so the directory alone proves nothing. - **The scaffolded guides follow.** The `AGENTS.md` and `README.md` the scaffolder writes into a project, and this package's README, now give the package command. Before, they gave `npx skills add objectstack-ai/objectstack/skills`, which reads `main`. A new case in `template-consistency.test.ts` holds all three to the exact command the scaffolder runs, pin included. - **The scaffold e2e asserts the catalog.** In `.github/workflows/scaffold-e2e.yml`, the pinned-shape project is now scaffolded without `--skip-skills`. A new step checks that `.claude/skills` equals the installed `@objectstack/skills` catalog, 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 GitHub `next` channel that the docs keep. ## The installer choice: `experimental_sync --copy`, measured PR #22691 measured the layout and found it fits. I measured it again with `skills@1.7.2` against the published `@objectstack/skills@18.0.0-next.0`. The tarball has 69 entries, with 10 skills under `package/dist/skills/SKILL/SKILL.md`. I installed it into throwaway projects with npm and with pnpm: | command | what it writes | |---|---| | `experimental_sync --agent claude-code -y` | 10 symlinks in `.agents/skills/` pointing into `node_modules/@objectstack/skills/dist/skills/`, plus 10 symlinks in `.claude/skills/` pointing into `.agents/skills/`. 0 real `SKILL.md` outside `node_modules`. | | `experimental_sync --agent claude-code --copy -y` | `.claude/skills/` with 10 skills, 64 real files, 0 symlinks. `diff -r` against the package copy: identical except the catalog README. Plus `skills-lock.json`, every entry `source: @objectstack/skills`, `sourceType: node_modules`, `version: 18.0.0-next.0`. Same result under pnpm's symlinked `node_modules`. Re-running it is idempotent. | | `experimental_sync --agent '*' --copy -y` | 54 agent directories, 540 `SKILL.md`, 48 MB. | | the same with no package installed | `No skills found`, `Nothing to sync.`, exit 0 | | the same over a `.claude/skills/objectstack-data` that `skills add` installed (lock `sourceType: 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. After `npx skills remove objectstack-data -y`, or after `npx skills remove --all` (which cleared all 10 directories and the lock), a re-sync gives a tree identical to the package. | | `--agent no-such-agent` | exit 1, and the CLI prints `Valid agents: …` | The linked default does not work for a project that commits its skills. `node_modules` is gitignored, so a clone gets only dangling links. `--copy` gives exactly the one-agent destination rule from the `skills-install.ts` header: real files, in one directory, committed once. `experimental_sync` scans 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/skills` ships any skills, and none declares a `skills` field. 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 unpinned `npx skills` would change the layout under every scaffold without notice. ## Registry and ranges - `@objectstack/skills` dist-tags: `{ latest: 18.0.0-next.0, next: 18.0.0-next.0 }`. Published versions: `0.0.0-stage` and `18.0.0-next.0`. `create-objectstack` dist-tags: `{ rc: 17.0.0-rc.6, latest: 17.7.0, next: 18.0.0-next.0 }`, the same as `@objectstack/spec`. - The template's literal `^18.0.0` does not admit `18.0.0-next.N`, but no project installs that literal. `rewriteProjectIdentity` calls `syncObjectStackDeps(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). - Today, `npm create objectstack@next` runs `create-objectstack@18.0.0-next.0`. I read its published `dist/index.js`: `SKILLS_CATALOG = "objectstack-ai/objectstack/skills"`, and its template has no `@objectstack/skills`. So it fetches skills from GitHub `main`. Once this ships in the next prerelease of the fixed group, `@next` writes `@objectstack/skills: ^18.0.0-next.N` and 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/skills` is 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 through `tsx`, with `pnpm` and `npx` stubbed on `PATH`. The `pnpm install` stub installs `@objectstack/skills` only when the project's `package.json` declares it, in the published layout, with a 3-skill stub catalog. The `npx` stub implements the measured `experimental_sync` discovery, its destination rows and its skip of a skill it did not install. It refuses (exit 3) any argv nobody measured: any `skills add`, the unpinned CLI, or an unknown agent. The test asserts: - the dependency is at `@objectstack/spec`'s range; - installed set == `listSkills(node_modules/@objectstack/skills/dist/skills)` == the stub catalog, with the same count; - one directory, one `SKILL.md` per skill, 0 symlinks; - the lock names the package; - a clone of the commit is readable; - the summary names the package version and the re-install command, and no GitHub source. 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.yml` step reads the catalog from the registry-installed `node_modules/@objectstack/skills`. - **Real, run locally once on this branch.** Steps: 1. With the repo-built bin, `create-objectstack pinned-probe`, with no flags, so its own `pnpm install` ran against the registry. 2. The sync reported `Synced 10 skills` from `@objectstack/skills`, and the Dockerfile was pinned to `18.0.0-next.0`. 3. I ran the new workflow step's `run:` script, taken out of the YAML, under `bash -e` with `RUNNER_TEMP` pointed at that project. It printed `catalog (10 skills)` == `installed in .claude/skills (10 skills)` for `objectstack-ai`, `objectstack-api`, `objectstack-automation`, `objectstack-data`, `objectstack-formula`, `objectstack-i18n`, `objectstack-platform`, `objectstack-query`, `objectstack-ui` and `objectstack-upgrade`, and exited 0. 4. `git init && git add -A` then staged 10 `SKILL.md`, all under `.claude/`, and 0 symlinks. The generated `AGENTS.md` and `README.md` contain no `skills add objectstack-ai` and do contain the package command. ## `--skip-install` and `--skip-skills` Both are tested end to end in the same file: - `--skip-install`: no `npx` call, no `node_modules`, no `.claude`. The summary says "Not installed yet … After `pnpm install`:" and gives the command. - `--skip-skills`: the install runs (the catalog is in `node_modules`), no sync runs, and the summary prints "AI Skills (recommended)" with the command. - A third case: the install leaves no catalog. The run prints "node_modules holds no @objectstack/skills catalog" and never prints "Installed for". - A fourth case: the project is scaffolded into the current directory, which already holds an `objectstack-data` from `skills 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 own `trap … EXIT INT TERM` that restores from `HEAD` and compares blob hashes. Both ran on `scaffold-skills-single-copy.test.ts` at the final head, `610d0c6adf`. The earlier run at `026b6bc8fb` gave the same direction. The subject is `src`: the test spawns `tsx src/index.ts` and imports `./skills-install.js` from `src`, so there is no `dist` leg. | leg | mutation (on disk) | result | |---|---|---| | control | none | 22 passed | | A: drop the template dependency | anchor x1 → x0, blob `c1862cd7199a` → `525b315283bd` | 10 failed / 12 passed. The catalog case fails with "the stub install did not put the catalog in node_modules: expected [] to deeply equal [ 'objectstack-data', …(2) ]" | | B: point the installer back at GitHub (`skills add objectstack-ai/objectstack/skills --skill '*' --agent AGENT -y`) | anchor x1 → x0, replacement x0 → x1, blob `8ec0100b6330` → `1e948aaf99ba` | 13 failed / 9 passed. The catalog case fails with "expected [] to have a length of 3 but got +0" | After both legs, each path's blob equals `HEAD` and `git diff HEAD` is empty. Both ablations go red, as expected. ## Gates, at the final head `610d0c6adf` - I derived the list with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` with no paths: 103 families, the same set as at `026b6bc8fb`. 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-loads` and `check:lean-entry-closure`, measured on a fully built tree; - the three changeset gates against `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-links` 0 and `check:published-readme-exports` 0, because the published README changed. - Package, under the shared verify lock: - `pnpm --filter 'create-objectstack^...' build` 0; - `pnpm --filter create-objectstack build` 0 (`check-dts-emitted: 1/1`); - `typecheck` 0; - `vitest run`: 18 files, 274 tests passed. - Template stamping: `node scripts/sync-template-versions.mjs` printed "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 json` over the 7 TS files this PR touches gave 7 files, 0 errors, 0 warnings. The population is `eslint.config.mjs` judging `.`, the `pnpm lint` command; 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 no `parserOptions.project` and 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`'s `test/scaffold-emission-policy.e2e.test.ts` reads the template `package.json` as a declared cross-package input. It compares only third-party ranges (`thirdPartyOnly` drops 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: - the two project guides the scaffolder writes: `src/templates/AGENTS.md` and `src/templates/blank/README.md`; - the two tests in this package that pinned the old command: `template-consistency.test.ts`, which imported `SKILLS_CATALOG`, and `starter-comments-self-contained.test.ts`, which pinned the README's command. All of these are in `packages/create-objectstack`. No other package, no `skills/**`, no `content/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 on `main`. It describes the scaffolder of 18.0.0-next.0, which installs from `main`, and tells the reader to replace that with the package and `npx skills experimental_sync --agent claude-code -y`: - `content/docs/getting-started/build-with-claude-code.mdx:60-67` - `content/docs/getting-started/your-first-project.mdx:47-54` Once this lands, the next prerelease's scaffolder does that itself. Two follow-ups belong to #22668, not this PR: - Those two passages need a sentence for the versions after 18.0.0-next.0. - The docs and `packages/skills/README.md` give the linked sync, without `--copy`. The scaffolder runs with `--copy`, and the `AGENTS.md` it writes says to keep `--copy`, because the links point into the gitignored `node_modules` (measured above). ## Acceptance notes - `docs/qa/platform-checklist/areas/cli.json` keeps 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. - Moving the `skills@1.7.2` pin means measuring again. The `npx` stub's `MEASURED_CLI` is written out separately from the source, so moving the pin in `skills-install.ts` alone turns the e2e red. --- _Generated by [Claude Code](https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8258181 commit 5da2c0f

13 files changed

Lines changed: 857 additions & 363 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"create-objectstack": minor
3+
---
4+
5+
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
6+
7+
Clause-②: no
8+
9+
- **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.
10+
- **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.
11+
- **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.
12+
- **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.
13+
- **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`).

‎.github/workflows/scaffold-e2e.yml‎

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,14 @@ jobs:
235235
236236
- name: 'Skills boundary: only the curated skills/ catalog installs'
237237
# 15.1 third-party eval: the repo-internal dogfood-verification skill
238-
# (.claude/skills/) leaked into scaffolded projects. Two leak paths,
239-
# two assertions against THIS checkout:
240-
# 1. The scaffolder/docs command is `skills add …/objectstack/skills
241-
# --all` — the /skills subpath is the hard boundary, because the
242-
# skills CLI's --all implies --skill '*' which INCLUDES
238+
# (.claude/skills/) leaked into scaffolded projects. The scaffolder no
239+
# longer reads GitHub — it syncs the installed @objectstack/skills
240+
# package, asserted by the step after the pinned shape below — so
241+
# these two assertions now guard the GitHub path the docs keep as the
242+
# `next` channel, against THIS checkout:
243+
# 1. The documented command is `skills add …/objectstack/skills …` —
244+
# the /skills subpath is the hard boundary, because the skills
245+
# CLI's --all implies --skill '*' which INCLUDES
243246
# metadata.internal skills. Install from the subpath and assert
244247
# set-equality with the curated catalog.
245248
# 2. Interactive `skills add …/framework` (no --all) relies on
@@ -293,8 +296,10 @@ jobs:
293296
# never RUNNING — a scaffolder that stops calling it, or an install path
294297
# that no longer leaves a resolvable CLI behind.
295298
#
296-
# `--skip-skills` is kept: skills install after the pin, so dropping it
297-
# would only add a network fetch, never coverage.
299+
# No `--skip-skills` either: the scaffolder's skills step reads the
300+
# catalog out of the node_modules this same install wrote, so this is
301+
# the one project in the job whose skills a real user would get. The
302+
# step after this one asserts them.
298303
#
299304
# The unpublished-version window is the one degradation, and it is
300305
# evidenced rather than blanket. The install step above carries the
@@ -337,7 +342,7 @@ jobs:
337342
# still catches a genuinely broken scaffolder install.
338343
run: |
339344
cd "$RUNNER_TEMP"
340-
node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" pinned-probe --skip-skills
345+
node "$GITHUB_WORKSPACE/packages/create-objectstack/bin/create-objectstack.js" pinned-probe
341346
cd pinned-probe
342347
343348
if [ ! -f node_modules/@objectstack/cli/package.json ]; then
@@ -377,6 +382,58 @@ jobs:
377382
fi
378383
echo "pinned as expected: FROM tag == resolved CLI ($RESOLVED)"
379384
385+
- name: 'Skills: the scaffolder installed exactly the @objectstack/skills catalog'
386+
# The pinned-shape project above was scaffolded the way a real user
387+
# does it, skills included. The scaffolder syncs the catalog out of the
388+
# @objectstack/skills package its own install put in node_modules — at
389+
# the range it gives every @objectstack/* dependency, so the version of
390+
# @objectstack/spec — into .claude/skills/ as real files (the measured
391+
# shapes live in packages/create-objectstack/src/skills-install.ts).
392+
# So the catalog this step compares against is the package's, read off
393+
# the registry-installed node_modules, never this checkout's skills/:
394+
# the same skill names, the same count, copied rather than linked.
395+
#
396+
# The unpublished-version window degrades the way the pinned-shape step
397+
# does, and only that window: with nothing installed at all, that step
398+
# has already classified the registry answer and failed on any other
399+
# cause, so this step asserts the degraded shape — no skills from
400+
# anywhere — instead of nothing. An install that resolved the CLI but
401+
# no @objectstack/skills is a template that lost its dependency: red.
402+
run: |
403+
cd "$RUNNER_TEMP/pinned-probe"
404+
PKG=node_modules/@objectstack/skills
405+
if [ ! -f "$PKG/package.json" ]; then
406+
if [ -f node_modules/@objectstack/cli/package.json ]; then
407+
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"
408+
exit 1
409+
fi
410+
if [ -e .claude/skills ]; then
411+
echo "::error::nothing was installed, yet .claude/skills exists — the scaffolder installed skills from a source other than the @objectstack/skills package"
412+
exit 1
413+
fi
414+
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."
415+
exit 0
416+
fi
417+
418+
skills_in() { for d in "$1"/*/; do [ -f "${d}SKILL.md" ] && [ ! -L "${d%/}" ] && basename "$d"; done | sort; }
419+
expected=$(skills_in "$PKG/dist/skills")
420+
installed=$(skills_in .claude/skills)
421+
echo "@objectstack/skills@$(node -p "require('./$PKG/package.json').version") catalog ($(echo "$expected" | grep -c .) skills):"; echo "$expected"
422+
echo "installed in .claude/skills ($(echo "$installed" | grep -c .) skills):"; echo "$installed"
423+
if [ -z "$expected" ]; then
424+
echo "::error::the installed @objectstack/skills holds no dist/skills/<skill>/SKILL.md — the layout the scaffolder reads has moved"
425+
exit 1
426+
fi
427+
if [ "$installed" != "$expected" ]; then
428+
echo "::error::the skills the scaffolder installed are not the installed package's catalog (names and counts above)"
429+
exit 1
430+
fi
431+
if [ -e .agents ] || [ -n "$(find .claude -type l)" ]; then
432+
echo "::error::the scaffolder linked the catalog instead of copying it — a clone of this project would hold dangling links into node_modules"
433+
exit 1
434+
fi
435+
echo "skills as expected: .claude/skills == the installed @objectstack/skills catalog"
436+
380437
- name: Build official runtime image from this checkout
381438
# The scaffolded app's Dockerfile builds FROM
382439
# ghcr.io/objectstack-ai/objectstack. Build that base HERE from

‎packages/create-objectstack/README.md‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,19 @@ you so rather than failing as an unknown template.
5555
**namespace** is derived from the project name (`my-app` → `my_app`) and
5656
every object name in the template is re-prefixed to match
5757
(`blank_note` → `my_app_note`).
58-
2. Installs dependencies (pnpm if available, otherwise npm).
59-
3. Installs the ObjectStack AI skills bundle for **one** coding agent —
60-
`npx skills add objectstack-ai/objectstack/skills --skill '*' --agent claude-code -y`,
61-
scoped to the curated `skills/` catalog. One agent, not `--all`, so the
62-
bundle lands in exactly one directory (`.claude/skills/`) and a scaffolded
63-
project commits it once; `src/skills-install.ts` carries the measurement.
64-
The closing summary prints the same command for any other runtime.
58+
2. Installs dependencies (pnpm if available, otherwise npm). They include
59+
`@objectstack/skills`, the ObjectStack AI skills catalog, at the same
60+
version as `@objectstack/spec`.
61+
3. Installs that catalog for **one** coding agent, from `node_modules` —
62+
`npx -y skills@1.7.2 experimental_sync --agent claude-code --copy -y` — so
63+
the skills teach the version the project runs, never the repository's
64+
unreleased `main`. One agent and `--copy`, so the catalog lands as real
65+
files in exactly one directory (`.claude/skills/`) and a scaffolded project
66+
commits it once; `src/skills-install.ts` carries the measurement. The
67+
closing summary prints the same command for any other runtime, and for
68+
re-installing after an upgrade. With `--skip-install` nothing is in
69+
`node_modules` yet, so no skills are installed and the summary says how to
70+
get them.
6571
4. Writes `AGENTS.md` and `.github/copilot-instructions.md` with the project
6672
conventions — unless the template ships its own.
6773

0 commit comments

Comments
 (0)