fix(cli): drop the deleted apps/cloud path from the publish help example - #15390
Conversation
`os package publish --help` printed its local-dev example as:
$ OS_CLOUD_URL=http://localhost:4000 os package publish # local dev (apps/cloud)
`apps/cloud` was deleted from this repository -- the reference cloud host
now lives in `objectstack-ai/cloud` -- so the parenthetical told a user to
run something against a directory absent from the tree they cloned. Unlike
the source comments swept alongside it, this string is user-facing output:
`static override examples` is printed verbatim by oclif under EXAMPLES.
The parenthetical is dropped rather than re-pointed at the other repo. The
example teaches that `OS_CLOUD_URL` overrides the control-plane URL, which
the `--server` flag documents a few lines above in the same output; which
directory happens to serve `localhost:4000` was never part of that, and a
`--help` reader is not looking for a file in a monorepo. Two things on the
tree already spell it that way: `content/docs/deployment/cli.mdx` publishes
this same example with no directory, and the sibling `os plugin publish`
gives the identical env-override example with no trailing comment at all.
No behaviour moves: no flag, argument, default or exit code changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
📓 Docs Drift CheckThis PR changes 1 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 22 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 3895ec18f04d17f5e87451ce00e78e42f97a9e0d && git checkout 3895ec18f04d17f5e87451ce00e78e42f97a9e0d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7 568b5341b5f3151e4275df89d7d0bbc0e2e19177 && git checkout -B drift-repro 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7 && git merge --no-ff 568b5341b5f3151e4275df89d7d0bbc0e2e19177
node scripts/docs-audit/affected-docs.mjs --json 5b2ad1b41af24a9dd4bf159802dcee3d527b7db7
|
Fixes #14806
The last remaining site on that card, and the only one its earlier PR deliberately held back.
What was wrong
packages/cli/src/commands/package/publish.tsended itsstatic override examplesblock with:apps/cloudwas deleted from this repository; the reference cloud host now lives inobjectstack-ai/cloud. The parenthetical told a reader to run something against a directory absent from the tree they cloned.Premise re-verified at the base commit, carrying the card's own positive control — the claim is a negative, so a non-empty result is what makes the absence a reading rather than a broken query:
Why this one was held back, and why it is a different review surface
The six sites that landed earlier were source comments — prose no one outside this repo reads. This is user-facing CLI output: oclif prints
examplesverbatim underEXAMPLES, so it reaches every user who runs--help. Two concrete consequences, both handled here rather than assumed away:@objectstack/cliis a published package, so this carries a changeset. The earlier comment-only PR correctly carried none; that reasoning does not transfer.The rendered before/after — the evidence that matters for a help string
Rendered from the published entry point (
packages/cli/bin/run.js, which loadsdist/), the whole 117-line help output differs by exactly one line:The source render (
bin/run-dev.js, throughsrc/) is byte-identical to thedist/render, so what a user gets from the published binary is what the diff shows.Why the parenthetical is dropped rather than marked out-of-repo
The house style the six landed sites used — naming the referent and marking it as living in the cloud repo — is right for a source comment, where a contributor may genuinely want to open that file. It is wrong here. A
--helpreader is a user of the CLI, not a contributor to this monorepo; telling them which repository an internal file lives in is noise in user-facing output.What the example teaches is that
OS_CLOUD_URLoverrides the control-plane URL — and the same output documents that a few lines above, under the-s, --serverflag with[default: https://cloud.objectos.ai, env: OS_CLOUD_URL]. Which directory happens to servelocalhost:4000was never part of it.Two things already on the tree spell it that way, and both were found before the choice was made:
content/docs/deployment/cli.mdx:1614publishes this same example as# against a local control plane— no directory. The docs already carried the directory-free spelling; the help string was the outlier.packages/cli/src/commands/plugin/publish.ts:51gives the identical env-override example with no trailing comment at all.Neither is byte-parity-gated against the help string, so these are precedent rather than obligation.
The pin: it exists, and it covers this exact line
packages/cli/src/commands/environments/environments.test.ts— the#10967 pin: examples resolve to a real command idblock — walks every command source underpackages/cli/src/commands/by TypeScript AST and asserts eachexamplesentry resolves to a registered command id. It pins the invocation shape, not the bytes, so the trailing prose is free to change; the pin does not move with the string, and its own hand-written control literal never carried the parenthetical.That was reasoned from the source, then measured, because a pin assumed to cover a line is worth nothing. On the committed implementation the example was mutated to name an unregistered id and the pin was re-run:
Restored with
git checkout HEAD -- ABSOLUTE_PATH(never the bare form, which restores from the index — i.e. the mutation), verified byte-identical to the HEAD blob with an emptygit diff HEAD, and re-run green:74 passed (74), with the verbose reporter namingpackage/publish.tsexplicitly. No rebuild leg is involved: the pin readssrc/text via the AST and walkssrc/commands/for ids, so it never resolves this package throughexports/dist.scripts/check-cli-command-ids.mjsalso reads this text. It does not. Its own verdict line says341 command-id literal(s) across 118 file(s) **outside packages/cli**, and its header states the design: "every oclif package is excluded from its own scan -- so the two never touch the same line." That gate is green here by exclusion, not on merit; the#10967vitest pin is the half that actually covers this file.Scope
One line of one file, plus the changeset. Nothing else was touched:
serve.ts(thekeep the two in synccross-repo obligation, now at:144) is byte-unchanged, as ruled — it is carded separately.apps/clouddirectory was added to satisfy the example.apps/cloud/apps/objectoswere grepped; the bare tokensobjectosandcloudnever were.objectos.ai,cloud.objectos.ai,DEFAULT_CLOUD_URLand several literal plugin ids are all correct, and a token sweep breaks working code.After-grep of the path across the repo:
packages/cli/src/commands/package/publish.tsno longer appears at all. Every remaining row is accounted for — the twoserve.tsrows above, the sibling card'sobjectql/src/plugin.ts:142,ROADMAP.mdanddocs/adr/**(historical records, never rewritten),observability/README.md:39(already says "see that repo's exporter"),spec/prompts/architecture.md:26(a product-strategy inventory),.gitignore:119(dead config, recorded and deliberately uncarded), and eight_console/apps/cloud_control/apps/cloud-controlrows that are a different path entirely.Verification
Everything below ran at the final commit
568b5341on a clean tree (git status --porcelainempty, local and remote at the same sha), so the green union describes the tree that is pushed.Gate union — derived, never hand-written.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, exit 0, 38 commands. Its provenance line confirms the answer is about this repo at this commit, and it took the change set from git itself (2 paths vs the merge base, three-dot).All 38 ran with the exit code captured before any pipe (
cmd > log 2>&1; ec=$?), nevercmd | tailthen$?. Result: 38/38 EXIT=0.Two of them first came back EXIT=3, read as NOT MEASURED and NOT as a pass, exactly as each says of itself:
pnpm check:dual-build-cjs-loads— "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ ... Runpnpm buildfirst. ⛔ This is NOT a pass: nothing was measured." (11 packages listed).pnpm check:i18n-coverage— "Nothing was compared: 12 config(s) did lint, but a partial round cannot judge the ratchet."Both were cleared by a full
pnpm buildat the same commit through the shared lock (VERDICT command-exit 0, 72/72 turbo tasks, 56 cached) and re-run: both EXIT=0.Six gates run BEYOND the derived union, because the derivation is a clue and not a specification:
pnpm check:cli-command-ids— a real CI gate (.github/workflows/lint.yml:2339) that the derivation did not name, added because it looked like a pin on this text. EXIT=0 — and its verdict is what falsified that guess:341 command-id literal(s) across 118 file(s) outside packages/cli. It excludes every oclif package from its own scan by design, so it is green here by exclusion, not on merit.check-changeset-fixed,check:authz-resolver,check:error-code-casing,check:filter-alias-parity,check:swallow-census-controls— whose rosters live under.changesetorpackages, directories my paths are in, where the tool states "the silence is not evidence in EITHER direction". All five EXIT=0.Affected package.
pnpm --filter @objectstack/cli typecheckEXIT=0 (tsc --noEmitpluscheck:test-typecheck: OK — @objectstack/cli's test layer compiles, so the test layer is genuinely covered rather than excluded). Full suitepnpm --filter @objectstack/cli exec vitest run --maxWorkers=2EXIT=0 — 245 test files passed, 2854 passed | 6 expected fail (2860), 1090s.Reverse verification of the pin — mutation and restore both proved on disk, quoted in full above. Predicted direction was RED and RED is what happened, naming this file. The restore leg was given the same treatment as the mutation leg:
git checkout HEAD -- ABSOLUTE_PATH, an emptygit diff HEAD, and agit hash-objectcomparison against the HEAD blob with an empty hash treated as failure rather than as "nothing to compare".ESLint — a declared, proven narrowing rather than a repo-wide run. Linted the changed paths only, with the three things that make a narrowing a measurement instead of a gap: (1) the receiving population comes from eslint's own config, not my guess — it reports the changeset
.mdas "File ignored because no matching configuration was supplied", sopublish.tsis the only file of the two in scope; (2) the file count is read from--format json— 2 entries; (3) the config is invariant for untouched files: this repo runs oneeslint.config.mjswhich never enables type-aware linting for any file (noparserOptions.project, no typed rules) — stated in that config and backed there by its own planted positive control — so a one-line string change inside one file cannot move any untouched file's verdict. Result:publish.ts0 errors, 0 warnings, EXIT=0. The repo-widepnpm lintis CI's run, not one I owe.🤖 Generated with Claude Code
https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Generated by Claude Code