Uh oh!
There was an error while loading. Please reload this page.
fix(create-objectstack): derive the "Created files" summary from the finished project, so every written path is named - #10559
Conversation
…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
📓 Docs Drift Check14 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 8 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 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 |
Uh oh!
There was an error while loading. Please reload this page.
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: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:
AGENTS.md/.github/copilot-instructions.mdpnpm install/npm install) →pnpm-lock.yaml,node_modules/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 — theskillsCLI 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:After (head, post-change) —
GAP_EXIT=0:What the user now sees:
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
skillsCLI, 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 addcall. 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
✓ Template files written (12)) that claims to be a count, not an inventory.Project contents:with a note, instead of claiming to have created files that were already there.create-objectstack my-apprefuses a non-empty target, so the common path always saysCreated files:. Both were exercised against the real CLI.Test — the property, never the count
created-summary.test.tsasserts 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: a12 → 40assertion 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 thatunreachablePaths()actually reports an uncovered path.The fixture reproduces the measured layout — 11 skills, two real trees,
.claude/skills/*as symlinks into.agents/skills/, and anode_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
skillsCLI —skills@1.5.23, "The open agent skills ecosystem" — invoked frompackages/create-objectstack/src/index.tsasnpx -y skills add objectstack-ai/objectstack/skills --all. That is why a grep ofpackages/create-objectstack/src/*.tsnever 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:
.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.mdfiles differ between the two real trees; every non-SKILL.mdfile 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 thename:key dropped in theagent/copy.The fork for the maintainer. Eve's own
detectInstalledis false for a fresh scaffold (noagent/directory, noevedependency), 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:--all— every runtime a user might adopt later is pre-populated, at ~968 KB per runtime that writes real files;npx skills addthemselves.⛔ 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 claimedcopyDir's collected array is "what the CLI prints asCreated 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
objectstack initprints its "Created files" list beforepnpm install, so it never names pnpm-lock.yaml or node_modules/ #10557 —objectstack init(packages/cli/src/commands/init.ts) prints its own "Created files" list beforepnpm install, so it cannot namepnpm-lock.yamlornode_modules/. Same structural shape, materially smaller gap (that scaffolder installs no skills). Filed unassigned and not touched here: it is outside this card's surface, and PR fix(cli): scaffold both pnpm build-approval keys so a new project installs on pnpm 11 #10522 is modifying that exact file right now.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 at9fe525cf6, all green; each quoted line is the gate's own verdict, and every exit code was captured before any pipe.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 passedcheck-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.mjsnamed 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, soturbo 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 endedVERDICT command-exit 0. Nothing was run outside the lock exceptpnpm install(the documented worktree setup step) and the scaffold measurement runs themselves.Generated by Claude Code