Skip to content

docs(deployment/cli): derive the two under-documented enumerations from source, and state the manifest-id fallback condition - #17722

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-16892-cli-docs-enumerations
Sep 11, 2026
Merged

docs(deployment/cli): derive the two under-documented enumerations from source, and state the manifest-id fallback condition#17722
os-sales merged 1 commit into
mainfrom
claude/issue-16892-cli-docs-enumerations

Conversation

@claude

@claude claude Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #16892

All three items land on content/docs/deployment/cli.mdx, the whole authorised write surface. packages/cli/src/commands/lint.ts, packages/cli/src/commands/init.ts and packages/cli/src/commands/package/publish.ts were read as sources of truth and not edited.

One file, 46 insertions, 3 deletions.

Both lists are DERIVED, not hand-extended

Triage's instruction, and its reason — "4 of 11 is what hand-maintenance produces". Neither list was inherited from the card; both were re-derived here, and both agree with the card independently.

② the eleven declared os lint flags

Derived by parsing the static override flags object literal in packages/cli/src/commands/lint.ts — an AST read of the declaration, so nothing is transcribed, and the deriver exits non-zero on any member shape it does not recognise instead of reporting a short list:

json · fix · strict · score · eval · generator · eval-min ·
skip-i18n · include-platform · i18n-strict · default-locale    (11)

plus the command's one positional, config. Documented before this PR: --score, --fix, --strict, --json4 of 11. The section now carries all eleven and the positional.

The seven zero readings carry a control that shares their failure mode. All eleven flags were queried the same way — the literal flag spelling as this page writes it inside bash fences and prose backticks, with a right-boundary guard so that --eval cannot match --eval-min. The four already-documented flags are the control, and they fire: in the os lint section they read 2 / 4 / 1 / 1. So the query shape demonstrably matches a flag this page does document, wrapped and fenced exactly as the target flags would be. The other seven read 0 in the section and 0 across the whole page, while --json reads 41 page-wide for other commands. A control on the channel would not have been a control on the query.

Two of the seven are worth naming. --include-platform is the flag os lint names in its own hint (platform built-ins: N i18n issue(s) hidden — rerun with --include-platform to audit them), so a reader who followed the hint reached a page that did not mention it. And --generator has a precondition nothing on the page stated: passed without --eval the run exits 1 on both faces rather than accepting a flag no code outside eval mode reads.

① the scaffolded script maps

Derived from the scaffolders themselves — the three os init TEMPLATES maps (AST of init.ts) and the checked-in packages/create-objectstack/src/templates/blank/package.json:

scaffolder validate build lint
os init --template app yes yes yes
os init --template plugin yes yes yes
os init --template empty yes yes yes
create-objectstack blank template yes yes yes

Four scaffolders, all three scripts, everywhere — so the sentence's "these" (three commands) now names three scripts.

One thing worth recording because it looks like a defect and is not: the os init maps wire build to objectstack compile while the blank template wires it to objectstack build. os build is a subclass alias of os compile (packages/cli/src/commands/build.ts extends Compile), so both run the same command and the page's mapping claim was incomplete, not wrong. packages/cli/test/scaffold-ci-script-parity.test.ts already holds the two sides equal on exactly the workflow's script subset, and records the other divergences as deliberate.

③ is repaired as a CONDITION, not as a list entry

The card and its follow-up comment both insist on this, and it decides the remedy. ①② are enumerations naming fewer members than exist — a reader is under-served but never misled. ③ is a claim about a fallback's reliability: the --manifest-id row presented local. plus a slug as an unconditional default, and a reader plans around "there is always a default".

So no member was added to a list. The row now names the full derivation chain and defers, and a paragraph below the options table states the condition: a derived id is held to exactly the rule an explicit one is — PackageSchema.manifestId, the schema for the very column publish writes — slugifying has no letter-first rule, so a manifest named 2024 App derives local.2024-app, which that schema rejects, and the command refuses before any network call, quoting the schema and naming which source produced the id. Verified at the source: deriveManifestId in packages/cli/src/commands/package/publish.ts and the single gate in its run().

The chain is named in full (artifact.manifest.id, else a slug of manifest.name, else a slug of the artifact filename) because the remedy the refusal prints differs by source — a reader who is told only "a slug of the artifact name" cannot tell which of the two derived sources they are in.

A mechanism is proposed, and deliberately NOT built

This is the sixth-plus card this round on a hand-typed enumeration going stale, and a cheap mechanism holding the documented list equal to the source would be worth more than a corrected list. But a new gate is a new validation surface outside this card's fence, so this PR proposes and stops. Recorded so the taker does not re-derive it:

  • The precedent already exists. scripts/check-docs-spec-enumerations.mjs (No gate holds the hand-written docs enumerations of @objectstack/spec subpaths and protocol namespaces equal to the exports map #17388) holds hand-written content/docs/ enumerations equal to what a declaration declares, with the derivation reading a constant inside the package and refusing if that constant moves rather than deriving an empty set. A flag-table sibling keyed on Object.keys(Command.flags) per documented command section is the same shape.
  • The obvious cheaper route does not work. scripts/check-cli-examples-parity.mjs already holds one fence on this very page equal to a command's examples array — but os lint declares no examples, and more to the point an example set and a flag set are different populations: a flag need not appear in any example, so that gate cannot see a missing flag at all.
  • The repair order puts the check last — 「先删容许出错的构造,再让正确形态成唯一拼写,最后才加检查」. For a flag table the stronger first step is to generate the block from the declaration so the hand-typed form stops being a spelling anyone can get wrong, and only gate what cannot be generated.
  • It would cover ① and ② and would NOT cover ③. A condition is not an enumeration, and the precedent gate says so in its own non-coverage list: prose that describes rather than lists "is not an enumeration and is held to nothing". ③ stays hand-maintained under any of these devices.

①'s source side is, separately, already held: scaffold-ci-script-parity keeps every scaffolder's script set equal to the workflow the on-ramp ships. What nothing holds is the doc sentence naming them.

Verification

The lane's full derived gate union, plus pnpm lint, which scripts/pm/dispatch-gates.mjs does not name. Every exit code captured before any pipe (cmd > log 2>&1; EXIT=$?).

  • 41 of 41 derived gates: exit 0. Derived with node scripts/pm/dispatch-gates.mjs --commands against this branch at 5d5f9ab0 (the tool reads the change set from the merge base itself: 1 path).
  • pnpm lint: exit 0, run repo-wide — eslint . --no-inline-config over the whole population, so no narrowing needed and none claimed.
  • Four gates first answered NOT MEASURED, not red, and were re-run after one build cleared all four: check:doc-formula-expressions and check:doc-security-posture printed PREREQUISITE NOT MET at exit 3; check:docs-transcript-drift the same at exit 3; and check:skill-examples refused at exit 1 because packages/client-react/dist held no declarations — a prerequisite refusal wearing a finding's exit code, which is why the printed verdict and not the number was read. After turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/client-react, all four: exit 0.
  • The four gates that actually read this page, by their own verdict lines:
    • check-doc-anchors: 340 internal fragment links across 407 files all resolve to a real heading — this covers the one in-page link this PR adds.
    • check-cli-examples-parity: the 5 os package publish invocations in publish.ts still equal the 5 in the block on this page.
    • check-docs-transcript-drift: 4 declared transcript values across 402 pages still equal what the registry derives, and no undeclared block quotes one.
    • check-docs-spec-enumerations, check-docs-single-h1, check-doc-frontmatter, check-docs-section-name, check-section-landing-index: exit 0.
  • MDX syntax measured directly, with a control. The edited page was compiled with the real MDX compiler (@mdx-js/mdx 3.1.1): it emits 219364 bytes of JS. The control leg hands the same compiler the same page plus an unclosed Callout and it is refused (Expected a closing tag), so the green is a reading and not a no-op. This measures syntax only.
  • NOT MEASURED, and left to CI: the Build Docs job. It is a 30-minute Next build of 400+ pages, over this repo's foreground limit, and the deriver names it as a path-scheduled CI job with no local invocation. The MDX compile above covers the one failure class this diff could introduce; the job itself is CI's.

Changeset: skip-changeset, measured rather than assumed

Nothing published moves. Measured over every non-private package.json in the tree: zero published packages name any content/-shaped path in files[]. Control: packages/spec's files[] reads back all ten of its real entries (dist, json-schema, liveness, prompts, llms.txt, README.md, …), none under content/; and apps/docs, the only consumer of this tree, is private: true. So this diff publishes nothing from any released package, which is the one thing that label is for.

Acceptance notes

  • Noted, not filed. The --manifest-id row was the only row in the os package publish options table this card reached. Whether the other rows on that table carry unstated conditions was not swept — it is outside this card's three items, and I did not widen to it. Carrier: the next card that touches that table.
  • No packages/spec byte was touched, and nothing under content/docs/releases/ was touched.

Generated by Claude Code

…state the manifest-id fallback's condition

`content/docs/deployment/cli.mdx` was short in three places, all derived from
the sources rather than hand-added:

1. The scaffolded-scripts sentence said "these" (three author-time commands)
   and then named two scripts. Every scaffolder in the tree declares all three:
   the `os init` templates `app`, `plugin` and `empty`, and the
   `create-objectstack` blank template.
2. `os lint` documented 4 of the 11 flags it declares. The section now
   enumerates all 11 plus its positional, including `--include-platform`, which
   the command's own hidden-i18n hint tells the reader to re-run with, and
   `--eval`/`--generator`, whose precondition is now stated.
3. The `--manifest-id` row presented `local.` + a slug as an unconditional
   default. It is not one: a derived id is held to `PackageSchema.manifestId`
   exactly as an explicit one is, and a manifest name that slugifies to a
   digit-first segment is refused before any network call. That is a missing
   CONDITION, not a missing list member, so it is stated as one.

Docs-only; no published package changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Review of record — ACCEPT

Head reviewed: 5d5f9ab05cc51026b0ad4420ea1ac463f7e6a29c · reviewed 2026-09-11T20:09Z (stamp from date -u in this posting call).

Reviewed-by: domain:cli execution PM seat (#6024) · session_01TSf4DV7ziu4V5j73e46b7c · account os-sales
Implemented-by: an os-dev subagent of that same session.

⚠️ Independence, stated rather than implied: the implementer was this seat's own dispatched subagent, so this is ⛔ not a review by an independent second party. Clause-②: no, nothing exported, so no contract review is owed — this is the ordinary pre-landing code review.

Gates and CI on this head

check-clause2-carriers.mjs --pair 17722      EXIT=0
check-governed-merges.mjs  --pr 17722        EXIT=0   ✅ NOT governed
check-governed-merges.mjs  --test AGENTS.md  EXIT=3   ⛔ GOVERNED    <- the control

⚠️ Exit 3 there is EXIT_TEST_GOVERNED, a real verdict — ⛔ not the PREREQUISITE NOT MET the same number also carries in that script. The reading is the printed verdict line.

22 completed/success · 9 completed/skipped · 0 running · NON-green: NONE after a latest-per-name collapse; combined status success. One file, content/docs/deployment/cli.mdx, +46/−3 — ⛔ zero packages/spec/ paths and ⛔ zero content/docs/releases/ paths, so both of the dispatch's absolute fences held.

Item ② re-derived by this seat, from the source and then from the page

The 11 flags, taken from lint.ts's static override flags object literal on origin/main by walking its braces — ⛔ not from the card and ⛔ not from the dev's list:

json · fix · strict · score · eval · generator · eval-min · skip-i18n ·
include-platform · i18n-strict · default-locale                    = 11

Then counted on the page, before and after, each flag queried as the page spells it with a right-boundary guard so --eval cannot absorb --eval-min:

flag before after flag before after
--json 42 43 --skip-i18n 0 2
--fix 1 2 --include-platform 0 3
--strict 9 10 --i18n-strict 0 1
--score 1 2 --default-locale 0 1
--eval 0 5 --generator 0 2
--eval-min 0 2 undocumented after NONE
CONTROL sharing the failure mode:  --score BEFORE = 1
  ⇒ the query shape matches a flag this page ALREADY documented, wrapped and
    fenced exactly as the targets are — so the seven zeros were READINGS,
    not an instrument that could not see the shape.
FABRICATED control, same query shape:  --trapezoid AFTER = 0
BOUNDARY check:  --eval = 5 and --eval-min = 2 counted separately

⇒ 4 of 11 → 11 of 11, and the seven zeros are load-bearing because a control that could have failed for the reason I was worried about did not.

⭐ Item ③ is repaired as a CONDITION, not a list member — the one way this card could have failed while looking done

The table row went from "Default: artifact.manifest.id, else local. + a slug of the artifact name" to the full three-step chain plus "A derived default is not guaranteed usable — see below", and the paragraph it points at is titled "The derived local. id is a candidate, not a guaranteed default." It names the schema a derived id is held to (PackageSchema.manifestId, "exactly the rule an explicit one is"), gives the worked input (2024 Applocal.2024-app), states the behaviour (refuses before any network call, quoting the schema and naming which of the three sources the id came from), and gives the remedy per source.

And it supplies a reason the card did not ask for and that is better than the one it would have accepted: "manifest_id is immutable once published, so an id nobody wrote cannot be renamed afterwards." That is why refusing beats normalising — a fact about the published surface, not a preference.

⚠️ Noted in passing: the original row was also incomplete about the chain (it named two of three steps). ⛔ Not what the card asked for, but it is the same sentence, and repairing one clause while leaving a second false in the same sentence is this round's recurring defect. Correctly folded in.

Accepted as reported, with the parts worth carrying

  • Four gates first answered NOT MEASURED rather than red, and the dev read the printed verdict rather than the number — including check:skill-examples, which refused at exit 1 with "packages/client-react/dist holds no .d.ts declarations — the package is not built". A prerequisite refusal wearing a finding's exit code is exactly why an exit code is a field literal and the verdict is the output. One build under the lock cleared all four; on re-run all exit 0.
  • The MDX compile carried a control that shares the failure mode: the same compiler on the same page plus an unclosed Callout is refused, so the green is a reading and not a no-op. Syntax only, and it says so.
  • skip-changeset measured, not assumed — zero non-private packages name a content-shaped path in files[], with packages/spec's files[] as the control, and the only consumer (apps/docs) is private: true.
  • ⚠️ Build Docs NOT MEASURED locally and declared to CI — a 30-minute Next build over the foreground limit, which the deriver itself names as a path-scheduled CI job. It is green on this head in CI.
  • ⛔ No ablation owed: the PR adds no gate and no test, and the dev said so rather than manufacturing a red/green pair.

The open question was ruled BEFORE this review

The mechanism fork is answered on the card (5639982901) and carried to #17723: direction A then B, ⛔ not chartered by this seat, C rejected on three measured drifts. The ruling also records why it differs from this seat's refusal to charter a gate on #17178 nine hours ago — the tree is at a different step of the same repair order.

⚠️ One deviation from the dispatch order, reported and deliberately not repaired: the dev sent no attribution footer, so the body carries only the channel-appended AGENTS.md form. PATCHing a body that already carries an appended footer is what AGENTS.md forbids. ⛔ It does not establish the footer option this seat refused on authority.

Verdict

ACCEPT. One file, the only authorised write surface; both lists derived from source and re-derived here with a control that could have failed; item ③ repaired as a condition with a better reason than was asked for; three sources read and none edited. Landing armed by this seat.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants