Skip to content

Commit dac5516

Browse files
claude[bot]claude
andauthored
fix(create-objectstack): install the skills bundle for one agent so a scaffolded project commits it once (#16401)
* fix(create-objectstack): install the skills bundle for one agent so a scaffolded project commits it once `skills add … --all` is shorthand for `--skill '*' --agent '*' -y`, and the `--agent '*'` half wrote the same 11-skill catalog to three destinations: `.agents/` (46 real files), `agent/` (46 real files, identical bodies) and `.claude/` (11 symlinks into `.agents/`). The template `.gitignore` excluded none of it, so a new project's first `git add -A` staged 22 `SKILL.md` paths plus 11 symlinks — measured against skills@1.5.23. The command now names one agent: `--skill '*' --agent claude-code -y`, which writes 46 real files to `.claude/skills/` and nothing else. Staged once, no symlinks, and a clone of that commit reads on every platform. Route A (keep `--all`, deny-list the duplicates) was built and cloned rather than reasoned about: ignoring `.agents/` leaves a cloner 11 dangling symlinks and zero readable SKILL.md; ignoring only `agent/` works on POSIX but commits symlinks that a `core.symlinks=false` clone materialises as plain files; and `--all --copy` fans out to 56 directories totalling 33.8 MB. A deny-list is also the wrong shape — the destination set belongs to the skills CLI and moves with its releases. The command and the printed hint now come from one builder in `src/skills-install.ts`, and the closing summary always prints an AI Skills block naming where the bundle landed plus the one-liner for other runtimes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG * docs(create-objectstack): the scaffolded project's own docs name the per-agent install form `templates/AGENTS.md` and the blank template README both told a project's future readers to run the bare `npx skills add objectstack-ai/objectstack/skills`, which prompts for agents interactively — picking "all" there re-creates the triplicate this card removes, inside the user's own repository. Both now name the single-agent form, say where the committed copy lives, and refuse `--all` with the reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6fa48b9 commit dac5516

9 files changed

Lines changed: 608 additions & 38 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"create-objectstack": minor
3+
---
4+
5+
Scaffolded projects now install the AI skills bundle for **one** agent runtime
6+
instead of every runtime the skills CLI knows, so the bundle is committed once.
7+
8+
**Route B of the two the card offered was taken**, and the choice was measured
9+
rather than argued. Against `skills@1.5.23` and the 11-skill catalog, the old
10+
`--all` (shorthand for `--skill '*' --agent '*' -y`) wrote the same bundle to
11+
three destinations — `.agents/` (46 real files, 604,102 B), `agent/` (46 real
12+
files, 602,682 B, identical bodies with re-serialised frontmatter) and
13+
`.claude/` (11 symlinks into `.agents/`). The template's `.gitignore` excluded
14+
none of it, so a new project's first `git add -A` staged 22 `SKILL.md` paths
15+
plus 11 symlinks. That reached the initial commit of a real app before anyone
16+
noticed.
17+
18+
The scaffolder now runs
19+
`npx skills add objectstack-ai/objectstack/skills --skill '*' --agent claude-code -y`,
20+
which writes 46 real files to `.claude/skills/` and nothing else: 11 staged
21+
`SKILL.md` paths, no symlinks, and a clone of that commit has readable skill
22+
files on every platform.
23+
24+
Route A (keep `--all`, exclude the duplicates in the template `_gitignore`) was
25+
built and cloned, not reasoned about, and both of its shapes were rejected.
26+
Ignoring `.agents/` and `agent/` while committing `.claude/` gives a fresh
27+
cloner 11 dangling symlinks and zero readable `SKILL.md`. Ignoring only
28+
`agent/` works on POSIX but commits 11 symlinks that a `core.symlinks=false`
29+
clone — git-for-Windows' default — materialises as ordinary files whose whole
30+
content is the link target. `--all --copy`, the other way to make `.claude/`
31+
real, fans out to 56 destination directories totalling 33.8 MB. A denylist is
32+
also the wrong shape regardless of which paths it names: this package does not
33+
choose the destination set, the skills CLI does, and it moves with that
34+
package's releases.
35+
36+
The cost is the multi-runtime default, and it is paid in the open: the closing
37+
summary now always prints an **AI Skills** block naming where the bundle landed
38+
and the one-line command for any other runtime, one agent at a time. The
39+
bundle is identical whichever agent is named.
40+
41+
Existing projects are unaffected. To shrink one that already carries the
42+
triplicate, delete `.agents/` and `agent/` and re-run the single-agent command
43+
above; `skills-lock.json` records source and hash, not paths, so it does not
44+
change.

packages/create-objectstack/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ you so rather than failing as an unknown template.
5656
every object name in the template is re-prefixed to match
5757
(`blank_note``my_app_note`).
5858
2. Installs dependencies (pnpm if available, otherwise npm).
59-
3. Installs the ObjectStack AI skills bundle for coding agents
60-
(`npx skills add objectstack-ai/objectstack/skills --all` — scoped to the
61-
curated `skills/` catalog).
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.
6265
4. Writes `AGENTS.md` and `.github/copilot-instructions.md` with the project
6366
conventions — unless the template ships its own.
6467

packages/create-objectstack/src/created-summary.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@
2828
//
2929
// 1. template copy + identity rewrite + AGENTS.md/copilot-instructions.md
3030
// 2. `<pm> install` -> pnpm-lock.yaml, node_modules/
31-
// 3. `npx skills add … --all` -> .agents/, agent/, .claude/,
32-
// skills-lock.json
31+
// 3. `npx skills add … --agent …` -> .claude/, skills-lock.json
3332
//
3433
// and the list was printed between (1) and (2). Phases 2 and 3 are third-party
3534
// processes whose outputs this package does not choose and cannot enumerate
36-
// ahead of time — the `skills` CLI fans out to every agent runtime it knows,
37-
// and that set changes with ITS releases, not ours. So any hand-maintained
38-
// list is not merely incomplete, it is unmaintainable: it drifts the next time
39-
// a dependency learns a new destination, silently, in the one direction that
35+
// ahead of time. Phase 3's destination set was the sharpest case: the run
36+
// measured above used `--all`, which fanned the catalog out to `.agents/`,
37+
// `agent/` AND `.claude/` — three trees, all committed, which is the defect
38+
// `skills-install.ts` now closes by naming one agent. Naming it does not make
39+
// the destination OURS to enumerate: the path is still the skills CLI's
40+
// choice, and it moves with ITS releases. So any hand-maintained list is not
41+
// merely incomplete, it is unmaintainable: it drifts the next time a
42+
// dependency learns a new destination, silently, in the one direction that
4043
// hides files rather than inventing them.
4144
//
4245
// Reading the directory afterwards is what makes the summary self-correcting.

packages/create-objectstack/src/index.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@
3939
* `--skip-install` there is no resolved version, so the template keeps `latest`
4040
* and its comment keeps telling the reader to pin by hand — true in that path.
4141
*
42-
* Finally we (best-effort) install the ObjectStack skills bundle via
43-
* `npx skills add objectstack-ai/objectstack/skills --all`.
44-
* The `/skills` subpath scopes discovery to the curated, customer-published
45-
* catalog — repo-internal skills (e.g. under `.claude/skills/`) must never
46-
* reach scaffolded projects.
42+
* Finally we (best-effort) install the ObjectStack skills bundle, for ONE
43+
* agent runtime (`skills-install.ts` carries the command and the measurement
44+
* behind that choice: `--all` writes the same bundle to three destinations and
45+
* the project then commits all three). The `/skills` subpath scopes discovery
46+
* to the curated, customer-published catalog — repo-internal skills (e.g.
47+
* under `.claude/skills/` in THIS repo) must never reach scaffolded projects.
4748
*
4849
* Only THEN is the "Created files" summary printed, and it is a walk of the
4950
* finished project directory rather than a list accumulated during the copy
@@ -72,6 +73,13 @@ import {
7273
import { lookupTemplate, templateNames } from './template-registry.js';
7374
import { readResolvedCliVersion, pinRuntimeImage } from './runtime-image.js';
7475
import { summarizeTree, describeEntry } from './created-summary.js';
76+
import {
77+
DEFAULT_SKILLS_AGENT,
78+
DEFAULT_SKILLS_DIR,
79+
EXAMPLE_OTHER_AGENT,
80+
SKILLS_INSTALL_COMMAND,
81+
skillsInstallHint,
82+
} from './skills-install.js';
7583
import { renderVersionBanner } from './banner.js';
7684

7785
const __filename = fileURLToPath(import.meta.url);
@@ -521,24 +529,26 @@ const program = new Command()
521529
}
522530

523531
// Which top-level paths belong to the skills install is measured, not
524-
// assumed: `skills add --all` fans the catalog out to every agent
525-
// runtime IT knows about (77 at the version measured), so the
526-
// destination set moves with that package's releases. Diffing the
527-
// directory across the call keeps the ⚠ marks correct without this file
528-
// carrying a list it cannot keep current.
532+
// assumed. Naming one agent makes the destination set small and
533+
// knowable (`skills-install.ts`), but it is still THEIR path to choose,
534+
// and it moves with that package's releases — so the ⚠ marks come from
535+
// diffing the directory across the call rather than from a list this
536+
// file would have to keep current.
529537
const beforeSkills = topLevelNames(targetDir);
538+
let skillsInstalled = false;
530539
if (!options.skipInstall && !options.skipSkills) {
531540
printStep('Installing AI skills for your coding agent...');
532541
try {
533-
execSync('npx -y skills add objectstack-ai/objectstack/skills --all', {
542+
execSync(SKILLS_INSTALL_COMMAND, {
534543
stdio: 'inherit',
535544
cwd: targetDir,
536545
});
546+
skillsInstalled = true;
537547
console.log('');
538548
} catch {
539549
printWarning(
540550
'Skills installation skipped. Run manually:\n' +
541-
' npx skills add objectstack-ai/objectstack/skills',
551+
` ${skillsInstallHint(DEFAULT_SKILLS_AGENT)}`,
542552
);
543553
console.log('');
544554
}
@@ -569,10 +579,29 @@ const program = new Command()
569579
console.log(chalk.dim(` ${devLabel.padEnd(labelWidth)}# Start development server`));
570580
console.log(chalk.dim(` ${validateLabel.padEnd(labelWidth)}# Verify metadata: schema + predicates + bindings`));
571581
console.log(chalk.dim(` ${' '.repeat(labelWidth)}# (run after every metadata edit — see AGENTS.md)`));
572-
if (options.skipInstall || options.skipSkills) {
573-
console.log('');
582+
// The skills block is printed on EVERY path, not only the skipped one.
583+
// The install now targets a single agent runtime, so a run that
584+
// succeeded still leaves every other runtime uninstalled — and a
585+
// capability that is absent has to say so, with the remedy, rather than
586+
// leave the reader to discover the gap. The command printed here is the
587+
// one this scaffolder ran, with a different agent name.
588+
console.log('');
589+
if (skillsInstalled) {
590+
console.log(chalk.bold(' AI Skills:'));
591+
console.log(
592+
chalk.dim(
593+
` Installed for ${DEFAULT_SKILLS_AGENT}${DEFAULT_SKILLS_DIR} — commit it with your project.`,
594+
),
595+
);
596+
console.log(chalk.dim(' Using a different agent? Add it one runtime at a time:'));
597+
console.log(chalk.dim(` ${skillsInstallHint(EXAMPLE_OTHER_AGENT)}`));
598+
console.log(chalk.dim(' (`npx skills add --help` lists every agent name)'));
599+
} else {
574600
console.log(chalk.bold(' AI Skills (recommended):'));
575-
console.log(chalk.dim(' npx skills add objectstack-ai/objectstack/skills'));
601+
console.log(chalk.dim(` ${skillsInstallHint(DEFAULT_SKILLS_AGENT)}`));
602+
console.log(
603+
chalk.dim(` Replace \`${DEFAULT_SKILLS_AGENT}\` with your own agent runtime if it differs.`),
604+
);
576605
}
577606
console.log('');
578607
} catch (error) {

0 commit comments

Comments
 (0)