Skip to content

fix(create-objectstack): derive the "Created files" summary from the finished project, so every written path is named - #10559

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10323-created-files-summary
Aug 21, 2026
Merged

fix(create-objectstack): derive the "Created files" summary from the finished project, so every written path is named#10559
os-elon merged 1 commit into
mainfrom
claude/issue-10323-created-files-summary

Conversation

@os-elon

@os-elonos-elon commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes#10323

The scaffolder's "Created files" list named 12 files and omitted AGENTS.md, both lockfiles and two ~968 KB trees of agent instructions — then the same run closed by telling the user to review the skills it had never named.

① Measured first, then patched

Scaffolded with the real published CLI (npx -y create-objectstack@17.1.0 demo-app, exit code captured before any pipe: EXIT_CODE=0), then walked the result and diffed printed-against-disk:

printed summary entries : 12
paths written on disk : 18045
UNREACHABLE from summary: 18033
.agents/ 49 path(s) agent/ 49 path(s)
.claude/ 11 path(s) .github/ 1 path(s)
AGENTS.md 1 path(s) skills-lock.json 1 path(s)
pnpm-lock.yaml 1 path(s) node_modules/ 17920 path(s)

Re-derived at head with a local build of this package before any change: the same 12 lines, byte for byte.

The list could not have been right, because of where it was built. Three phases write into the project, in this order:

  1. template copy + identity rewrite + AGENTS.md / .github/copilot-instructions.md
  2. the package manager (pnpm install / npm install) → pnpm-lock.yaml, node_modules/
  3. npx skills add … --all.agents/, agent/, .claude/, skills-lock.json

…and the summary was printed between (1) and (2), from copyDir's collected array. Phases 2 and 3 are third-party processes whose outputs this package does not choose and cannot enumerate in advance — the skills CLI fans the catalog out to every agent runtime it knows about, a set that moves with its releases, not ours. So a hand-assembled list was not merely incomplete, it was unmaintainable in the one direction that hides files rather than inventing them.

The summary is now a walk of the finished project directory, printed once every write has landed. It is self-correcting: a path some future dependency writes appears in the summary with nobody editing this package.

The bar set on this card was reachability — every path the run writes must be findable from what it prints — explicitly not "make the list longer". Enumerating 18,045 lines satisfies reachability and is unreadable, so a directory over the collapse threshold becomes one line carrying its path, entry count and size. Single-child chains compress, so the line reads .agents/skills/ — the directory a reader would actually open — rather than .agents/.

Reverse verification, from real scaffold runs

Both runs are the real CLI against the same template, so the two trees are identical (18,045 paths); the entire difference is in what got printed.

Before (head, pre-change) — GAP_EXIT=1:

printed summary entries : 12
paths written on disk : 18045
UNREACHABLE from summary: 18033

After (head, post-change) — GAP_EXIT=0:

printed summary entries : 20
paths written on disk : 18045
UNREACHABLE from summary: 0

What the user now sees:

 Created files:
+ .dockerignore
+ .github/copilot-instructions.md
+ .gitignore
+ AGENTS.md
…
+ skills-lock.json ⚠ skills
+ src/objects/note.object.ts
+ tsconfig.json
+ .agents/skills/ 49 files, 722 KB ⚠ skills
+ .claude/skills/ 11 files, 426 B ⚠ skills
+ agent/skills/ 49 files, 721 KB ⚠ skills
+ node_modules/ over 2,000 files
⚠ Skill files run with your coding agent's full permissions.
Review the paths marked ⚠ above before letting an agent use them.

The security-flavoured advice now points at paths that are on screen. Note that the "Review skills before use; they run with full agent permissions." sentence is printed by the third-party skills CLI, not by this repo — which is why the fix here is to name the paths and tie our own warning to them, not to edit that sentence.

Which top-level paths belong to the skills install is measured, not hard-coded: the directory is diffed across the skills add call. A destination list spelled out in our source would rot the next time that dependency learns a new agent runtime — the same drift class this card is about.

Two behaviours worth calling out

  • The list moved from before the install to after it. That is the fix, not a side effect: printed earlier it cannot name phases 2 and 3. Early feedback is preserved by a factual progress line (✓ Template files written (12)) that claims to be a count, not an inventory.
  • Scaffolding into a non-empty current directory now prints Project contents: with a note, instead of claiming to have created files that were already there. create-objectstack my-app refuses a non-empty target, so the common path always says Created files:. Both were exercised against the real CLI.

Test — the property, never the count

created-summary.test.ts asserts that every path in the tree is named outright or covered by an ancestor directory line, quantified over whatever the fixture holds. Nothing asserts a file count: a 12 → 40 assertion rots the moment the template changes and gets re-baselined rather than investigated, which is the mechanism that produced the stale 12 in the first place. The suite carries a vacuity guard (a summary of the wrong directory would otherwise assert nothing and stay green) and a both-directions check that unreachablePaths() actually reports an uncovered path.

The fixture reproduces the measured layout — 11 skills, two real trees, .claude/skills/* as symlinks into .agents/skills/, and a node_modules/ past the measurement budget — rather than a smaller stand-in. That count is load-bearing: a 3-skill fixture sits under the collapse threshold and would have exercised the enumerate branch while the real tree takes the collapse branch. The first run of this suite caught exactly that and the fixture was corrected.

② The skill-tree writer — located, and the question answered. No layout change here.

Triage asked for this as a stop-and-report, and it is reported, not acted on.

The writer is not in this repository. It is the third-party skills CLI — skills@1.5.23, "The open agent skills ecosystem" — invoked from packages/create-objectstack/src/index.ts as npx -y skills add objectstack-ai/objectstack/skills --all. That is why a grep of packages/create-objectstack/src/*.ts never found it, and it confirms triage's warning that the finding rested on published-package output rather than a source line.

Is the duplication deliberate multi-agent fan-out? Yes — but not a decision anyone in this repo made per-directory. From that package's own agent registry:

eve: {name: "eve",displayName: "Eve",skillsDir: "agent/skills",detectInstalled: async()=>{constcwd=process.cwd();returnexistsSync(join(cwd,"agent"))&&packageJsonHasDependency(join(cwd,"package.json"),"eve");}},
  • .agents/skills/ — the canonical "universal" destination. 49 real files, source frontmatter verbatim.
  • .claude/skills/11 symlinks into .agents/skills/. Costs ~426 B, not a copy.
  • agent/skills/ — the destination for a different agent runtime, "Eve". 49 real files, 968 KB, zero symlinks. Genuinely a second copy.

All 11 SKILL.md files differ between the two real trees; every non-SKILL.md file is byte-identical (diff -r -q --exclude=SKILL.md → no differences). So the frontmatter re-serialization the issue noticed is the whole difference — block scalars flattened to flow style, and the name: key dropped in the agent/ copy.

The fork for the maintainer. Eve's own detectInstalled is false for a fresh scaffold (no agent/ directory, no eve dependency), so that copy does not land because anything detected Eve — it lands because we pass --all, which targets all 77 registered runtimes regardless of detection. The real decision is therefore about that flag, not about either directory:

  • keep --all — every runtime a user might adopt later is pre-populated, at ~968 KB per runtime that writes real files;
  • narrow to a detected or curated set — smaller scaffolds, and a user who later adopts another agent runs npx skills add themselves.

Nothing in this PR removes a copy or changes the layout, per triage's instruction. Two ~968 KB trees is exactly the shape that reads as waste and turns out to be deliberate fan-out; deleting one would break a second agent runtime silently, inside a scaffolded project, where nobody would trace the failure back here. The summary now simply discloses both, which is what this card asked for.

Also in this diff

  • template-consistency.test.ts — a comment-only correction. Its comment claimed copyDir's collected array is "what the CLI prints as Created files:", which this change makes false. The assertion is unchanged and still pins the copy; only the claim about what it means was corrected. Same package, same defect class (a drifted claim about this very summary), and no other in-flight PR touches that file.

Out of scope — filed rather than repaired here

Serial fences

Checked, not assumed: PR #10507 (blank template README) is merged and is an ancestor of this branch's base; PR #10522's file list is packages/cli/** plus its changeset — no overlap with this diff. Same package, different files.

Gates

Gate set re-derived after the final commit with node scripts/pm/dispatch-gates.mjs (no path arguments — the script derives the change set itself). All families it named were run at 9fe525cf6, all green; each quoted line is the gate's own verdict, and every exit code was captured before any pipe.

GateIts own verdict line
check:nul-bytescheck-nul-bytes: OK (scanned 6179 text file(s) -- 6179 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).
check:changeset-gate-self-tests✓ check-empty-changeset --self-test: 118 assertions … (+ 212 and 116 assertion self-tests)
check:objectui-changeset✓ objectui-changeset-digest --self-test: all checks passed
check-adr-0087-registration✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).
check-changeset-no-major✓ This diff introduces no \major` bump.`
check-empty-changeset✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
check-affected-docs✓ affected-docs self-test: 262 cases pass.
check:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new …
check:engine-double-contractcheck-engine-double-contract: OK — 342 pinned, 133 in the DEBT ledger, 2 exempt.
check:where-matcher✓ where-matcher conformance holds: 269 matcher(s) discovered … (161 refuse).
check:query-options-erasure✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new …
check:type-check-coveragecheck-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root), 13 in the DEBT ledger …
check:type-check-debtcheck-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 256.5s, 1924 raw tsc error(s) total, none above its recorded number.

dispatch-gates.mjs named five convention-triggered families the dispatch brief did not list — check:query-options-erasure, check:type-check-coverage, check:type-check-debt, check:engine-double-contract, check:where-matcher — all triggered by this diff adding a test file. All five were run and are in the table above. The ratchet half needed the built workspace closure, so turbo run build --filter=./packages/* --filter=./packages/*/* was run first (70 successful, 70 total).

Package suite at the same commit: TYPECHECK_EXIT=0, TEST_EXIT=0 (Test Files 9 passed (9), Tests 100 passed (100)), BUILD_EXIT=0.

All heavy verification ran through scripts/pm/os-verify-lock.sh; every run ended VERDICT command-exit 0. Nothing was run outside the lock except pnpm install (the documented worktree setup step) and the scaffold measurement runs themselves.


Generated by Claude Code

…finished project, not the template copy (#10323)
The summary was `copyDir`'s collected array, printed between the template copy
and `<pm> install`. Two of the three write phases run after that point, so it
could not name what they wrote. Measured against published 17.1.0
(`create-objectstack demo-app`, then a full walk of the result):
printed summary entries : 12
paths written on disk : 18045
UNREACHABLE from summary: 18033
.agents/ 49 agent/ 49 .claude/ 11
.github/ 1 AGENTS.md 1 skills-lock.json 1
pnpm-lock.yaml 1 node_modules/ 17920
Two ~968 KB trees of agent instructions landed unnamed while the same run
closed with "Review skills before use; they run with full agent permissions."
The summary now walks the project directory once every write has landed, so it
is self-correcting: a path some future dependency writes shows up with nobody
editing this package. Directories over the collapse threshold become one line
with path, entry count and size; the paths the skills installer created are
measured by diffing the directory across the call and marked so the permissions
warning points at them.
Same run after the change: 20 entries printed, 0 written paths unreachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

14 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • 8 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 68ca346446a47d94a1b60e7061a2a5dc60b29d27packageMentionDocs.

Which tree this was computed on

This run read content/docs from bbae39eabb1f8f5f64b33e0a2d6b60040b9c55f8 — the merge of head 9fe525cf6bd2fe588468adc509670948f1ab9e06 into base 68ca346446a47d94a1b60e7061a2a5dc60b29d27, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bbae39eabb1f8f5f64b33e0a2d6b60040b9c55f8 && git checkout bbae39eabb1f8f5f64b33e0a2d6b60040b9c55f8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 68ca346446a47d94a1b60e7061a2a5dc60b29d27 9fe525cf6bd2fe588468adc509670948f1ab9e06 && git checkout -B drift-repro 68ca346446a47d94a1b60e7061a2a5dc60b29d27 && git merge --no-ff 9fe525cf6bd2fe588468adc509670948f1ab9e06
node scripts/docs-audit/affected-docs.mjs --json 68ca346446a47d94a1b60e7061a2a5dc60b29d27

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 21, 2026
@os-elon
os-elon marked this pull request as ready for review August 21, 2026 04:15
@os-elon
os-elon added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 5a616d5Aug 21, 2026
37 checks passed
@os-elon
os-elon deleted the claude/issue-10323-created-files-summary branch August 21, 2026 04:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-elon@claude