Repository navigation
feat(cli,create-objectstack): os generate picklist, the src/picklists starter barrel, and a Picklists count in the metadata summary - #21167
Conversation
… starter barrel, and a Picklists count in the metadata summary os generate picklist NAME writes NAME.picklist.ts through definePicklist into src/picklists, collected under the picklists stack key. os init derives its wiring from the roster; the blank starter of create-objectstack gains the same empty barrel and config lines. collectMetadataStats counts picklists, printed in the Data: row of os validate, os build and os info. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…, with changesets Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…project sees, as measured Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 32 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 4594faf5e1e5e7a3659b2e410762ce57a1e7c9b6 && git checkout 4594faf5e1e5e7a3659b2e410762ce57a1e7c9b6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1371dc980cdf0d3128bee4a5441f2c6bec18f008 cd1910a947360721404bab9d960b4e955914d453 && git checkout -B drift-repro 1371dc980cdf0d3128bee4a5441f2c6bec18f008 && git merge --no-ff cd1910a947360721404bab9d960b4e955914d453
node scripts/docs-audit/affected-docs.mjs --json 1371dc980cdf0d3128bee4a5441f2c6bec18f008
|
Contract reviewServed-tier: Inputs read: card #21018 (body, claim Checks on the head, collapsed latest-per-name, converged at 11:44Z: 37 names, 33 Mergeability: ① Derived judgments(a) Item 1, the generator. The emitted file is (b) Item 2, the starter. Structural half holds. The blank config gains the import and Behavioural half: against the REPO build a fresh project validates and builds — (c) Item 3, docs. (d) Item 5, the stats row. (e) Pins and ablations. ② Semver level
③ Boundary flags
Remedy for the FAIL (no code change needed; a body edit leaves this head-sha standing): (1) add an Acceptance note naming the Implemented-by: VERDICT: FAIL |
…icklists barrel Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
|
Generated by Claude Code |
Contract reviewServed-tier: Re-review of PR #21167 after the FAIL record What moved. One commit, Checks on Mergeability. ① Derived judgments(a), (c), (d) and (e) carry from (b) carries with its FAIL cause removed. The structural half is as before. The behavioural half is now stated correctly in the PR: the Acceptance notes name the ② Semver levelCarries from ③ Boundary flags
Landability. The contract is met on this head. The one red is an advisory, non-required check, disclosed in the body and held by the seat, that cannot turn green before the release in #20639 publishes Implemented-by: VERDICT: PASS |
…e each decision in words instead of a tracker number (stage 1) (objectstack-ai#21172) Part of objectstack-ai#20752 Clause-②: no **Stage 1 of 5 of the `domain:cli` lane under the maintainer's A / A ruling (5902360492): the CLI, `cloud-connection` and `types` strings.** The card stays open for stages 2-5, so this PR carries no closing keyword. Text only: no exit code, error `code`, flag, field, HTTP status, export or control flow moves. ## What this does CLI help text, warnings, refusals and two harness errors sent the reader to a tracker number for the reason behind them. In form D, as the stages of the engine lane applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words. | Where (head line) | Cited | The text now says | |---|---|---| | `compile.ts:753`, `validate.ts:689` step line | 3366 | "Checking that every required capability has a provider installable in this edition..." | | `compile.ts:824` warning header | 3786 | "Undeclared authoring keys (N) — dropped at load; reported here, never refused" (the disposition commit 8186a70 chose: the schemas stay non-strict, the drop is reported, never fatal) | | `db/clean.ts:33` `--database` help | 6469 | "...then the one project database that os dev, os start and os migrate all resolve" | | `doctor.ts:1406` retired-key row | 2377 | "`referenceFilters` was removed from FieldSchema as a key no runtime read (ADR-0049 enforce-or-remove)" | | `meta/resync.ts:71` skip explanation | 8692 (404) | "on installs created before the seeder began stamping its default sets 'platform'" (commit 712e185: the seeder stamps `managed_by: 'platform'`, forward only) | | `meta/resync.ts:104` command description | 2705 | "(default permission sets, which boot seeds insert-once)" | | `migrate/duplicates.ts:859` description | 8725 | "...index tightenings, which os migrate plan does not report" (ruling 5377374267: the report lands here, plan's drift contract untouched) | | `migrate/duplicates.ts:861` description | 8686 | "Run it BEFORE the boot-time backfill that stamps untenanted seed rows with the install's organization" (ruling 5299880350) | | `migrate/multi-value-columns.ts:332` description | 11535 | "boot and os migrate plan only report such a column and never alter it unattended" (route C, recorded in 5405686794) | | `migrate/recorded-by.ts:69` description | 4556 | "...to NULL, the value a system-initiated write stores now" (ruling recorded in 5161102937) | | `migrate/summary-nulls.ts:81` description | 5749 | "...the insert-time seed, which now writes 0 for an empty child set" (5206990154) | | `serve.ts:4393` no-auth refusal | 3963 | "anonymous access to object data is always denied, with no setting that turns that off" | | `serve.ts:5953` organizations remedy | 4719 | "...deliberately not enough: accepting it made this wall depend on how the process was launched." | | `storage/orphans.ts:173` closing line | 10950 | "Reclaiming these bytes was decided against: deletes no longer strand files, and files stranded before that fix are left in place." (ruling 5386673556, closed not planned; the old line still called it a deferred decision) | | `test/helpers/serve-process.ts:569` harness error | 13062 | "...the port `serve.ts` PUBLISHES, which must be the port it BOUND, so this is a regression" | | `cloud-connection-route-ledger.ts:205/228/238/248` notes | 8976 | "`manage_metadata`, never merely a signed-in session" (commit e0695b5 dropped the any-session gate) | | `cloud-connection-route-ledger.ts:215` note | 9011 (404) | "`installedBy` / `storageDir` (a user id and a host filesystem path) are served only to a `manage_metadata` holder" (commit 01074e5) | | `cloud-connection-route-ledger.ts:264` note | 11863 | "a route and takes a ledger row, as trigger-api's host-mounted hook route does" | | `types/src/node.ts:383` undeclared-package note | 10943 (404) | "...passes its own `fallbackImport`, so the fallback resolves from the caller instead" (commit 46d34ab) | Citation only (the sentence already stated the decision): `dev.ts:233` (5148), `doctor.ts:221` (5673), `doctor.ts:1544` (5397), `migrate/duplicates.ts:858` (8928: the third sentence already says it never rewrites), `serve.ts:6021` (4818), `storage-driver.ts:149` and `:349` (3276), `serve-process.ts:546` (12525), `node.ts:393` (4719). Every cited card was read (REST, open or closed) before its string was rewritten. Three answer 404: 8692, 9011 and 10943. They were read through their landing commits (712e185, 01074e5, 46d34ab) and today's docblocks. ## Three ledger entries that were not tracker numbers `serve.ts` carried `objectstack-ai#111`, `objectstack-ai#666` and `objectstack-ai#444`: the 3-digit CSS colours of the unknown-hostname 404 page, the false-positive family the gate's own docblock names. They now read `#111111`, `#666666` and `#444444`, the remedy that docblock prescribes. The page renders the same. ## The source-hash header producer (patch round under claim amendment `5931899236`) `packages/cli/src/utils/i18n-extract.ts:2354` held the last two of the stage's occurrences (`objectstack-ai#12069`, `objectstack-ai#8765`). That literal is the header `renderSourceHashModule` writes as line 8 of every `LOCALE.source-hashes.generated.ts`. The seat carried it in this stage, answer A to the first report's open question. - **The rewrite, commit `d60b295649`:** the header now states what the two rulings decided, applied to the generated half. A leaf whose digest no longer matches its source is stale and serves the source text instead. The commit sha `09b4f4e4e` stays as provenance. - **The regeneration, commit `9d5f33f929`:** the 27 companions in 9 packages were regenerated with `node scripts/check-i18n-bundles.mjs --write`, never by hand. - The diff has exactly 27 files, one `@@ -8 +8 @@` hunk each, +27 / −27, with one distinct removed line and one distinct added line. - Every hash entry is byte-identical. - `check:i18n`: 9 packages in sync. `check:i18n-stale-fill`: no new stale fills. - **None of the 9 packages publishes the header:** 0 hits for the old or new header text in each package's `dist`, against a positive control of 2 to 8. So they take no changeset. `@objectstack/cli` ships the literal, and its existing `patch` changeset covers it. ## Re-pins - `meta/resync-skip-explanation.test.ts:44` asserted `objectstack-ai#8692`. It now asserts "before the seeder began stamping its default sets 'platform'". Ablation on the committed head (`scripts/ablation-replace.mjs`, WRAP mode): with that phrase replaced in `resync.ts` the file gave 1 failed / 6 passed; after the restore the blob matched HEAD (`a16ba21993`) and `git diff HEAD` was empty. - `test/build-json-undeclared-key-parity.e2e.test.ts:288` asserted the `(objectstack-ai#3786)` header. It now asserts the new header. Ablation: the first attempt used a replacement that was a substring of the anchor, and the tool refused it before running anything (count 2 → 2). The second, with a distinct marker, gave 1 failed / 5 passed. The restore matched HEAD (`7d24c878b8`) with `git diff HEAD` empty. The docblock transcript at line 12 keeps the old text, because it records a measurement at `4ceae8ab0`. - Neither pin was deleted. The test titles in test files that cite numbers are not ledgered and were left alone. ## Ledger (`scripts/doc-authoring-prose-id.baseline.json`) Recomputed with `node scripts/check-doc-authoring.mjs --census-ledger` (exit 0, no growth refusal) into a scratch file, then copied into place. The diff deletes 63 lines and adds none. Every row outside the three packages is byte-identical. After merging `origin/main` (`e35c40a525`) the recomputed ledger was byte-identical to the committed one. | | before (`fde553c509`) | after | |---|---|---| | `packages/cli` | 29 in 15 files | 0 | | `packages/cloud-connection` | 6 in 1 file | 0 | | `packages/types` | 2 in 1 file | 0 | | **three packages** | **37 in 17 files** | **0** | | whole ledger | 621 occurrences, 428 pairs, 169 files | 584 occurrences, 395 pairs, 152 files | `pnpm check:doc-authoring`: before, "521 pinned site(s) across 169 file(s) ... no growth, no burn-down unrecorded"; after the first round, "488 pinned site(s) across 153 file(s)"; after the patch round, at `9d5f33f929`, "487 pinned site(s) across 152 file(s) ... no growth, no burn-down unrecorded". The patch round's ledger diff deletes 4 lines (the `i18n-extract.ts` row) and adds none. ## Changeset `.changeset/20752-cli-strings-state-the-decision.md`: `patch` for `@objectstack/cli` and `@objectstack/types`. There is **no `@objectstack/cloud-connection` entry**, because the route ledger is not published. It is package-internal guard data that `index.ts` does not import. Measured after the build: "never merely a signed-in session" and `CLOUD_CONNECTION_ROUTE_LEDGER` are each in 0 files under `packages/cloud-connection/dist`, while the positive control `install-local` is in 6. The same measurement on `@objectstack/cli`: each new sentence is in `dist`, each old one (`capability providers (objectstack-ai#3366)`, `dropped at load (objectstack-ai#3786)`, `see issue objectstack-ai#10950`, `always denied (objectstack-ai#3963)`, `color: objectstack-ai#666;`) is in 0 files. `test/helpers/serve-process.ts` is not in `dist`. ## Text-only proof A TypeScript-AST skeleton of each changed `.ts` file compares `fde553c509` with `2ef3c98a9d`. In the skeleton every string literal and template text is one placeholder, consecutive literal operands of a `+` chain merge, and comments are never read. Result: 18 of 18 SAME, with token and literal-slot counts identical per file. Control: the same tool reports DIFF on `cli/src/utils/schema-migrate.ts` across `f20f669e17`, a real code change. ## Tests - Build: `turbo run build` over `./packages/*` and `./packages/*/*` under the verify lock, 71/71, before and again after the merge. - `@objectstack/cli` unit tier: 242 files / 3,435 tests passed, before and after the merge. `typecheck` exit 0, including `check:test-typecheck`. - `@objectstack/cli` integration tier (owed because the diff touches `test/helpers/serve-process.ts`): 69 files / 599 passed, 1 skipped, on `2ef3c98a9d`. Not re-run after the merge; the merge brings `start.ts` and a nightly e2e file, neither touching these strings. - Nightly tier (`OS_TEST_TIERS=nightly`), the two e2e files that assert the changed strings: `build-json-undeclared-key-parity.e2e.test.ts` and `serve-port-readback.e2e.test.ts`, 19/19 passed. - `@objectstack/types`: 23 files / 692 tests; `@objectstack/cloud-connection`: 30 files / 397 tests (the route-ledger conformance guard included); both `typecheck` exit 0. ## Gates `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths) at `c023fa0d00` derived 73 commands. All 73 ran one at a time from the worktree, each exit 0. `--ran` reports "73 derived famil(ies) accounted for — 73 run, 0 NOT-MEASURED". Among them: `check:doc-authoring` (above), `check:i18n` (9 packages in sync), `check:issue-citations`, `check:nul-bytes`, `check:dts-closure` (71 packages swept) and `check:dual-build-cjs-loads`. Narrowed lint: `eslint --no-inline-config --format json` over the 18 changed `.ts` files reported 18 files, 0 errors, 0 warnings (counts from eslint's JSON). The resolved `parserOptions` are `ecmaVersion: latest, sourceType: module`, with no `project` or `projectService`, so no type-aware rule runs and this diff cannot move an untouched file's verdict. Repo-wide `pnpm lint` is CI's. NOT MEASURED locally (CI's): the Test Core shards, Dogfood, Build Core and the workspace type-check lanes. The integration tier was not re-run on the merge commit. ## Acceptance notes - **Docs transcripts that quoted the old step line** now show the new text, byte-identical to the `printStep` argument in `compile.ts` and `validate.ts`, in the patch round (`d60b295649`): `content/docs/deployment/cli.mdx:608`, `content/docs/deployment/validating-metadata.mdx:699` and `content/docs/ui/react-pages.mdx:393`. - `check:docs-transcript-drift` passes. - PR objectstack-ai#21167 also edits `cli.mdx`, around line 1485 and later, so the hunks are disjoint. - `docs/audits/...` is a dated record and stays. - `packages/cli/CHANGELOG.md` quotes old lines in released entries. That file is release-owned and untouched. - Code comments and docblocks that cite these numbers are not runtime strings and are unchanged. - `origin/main` was merged once (`e35c40a525`). It touches `cli/src/commands/start.ts` and adds a nightly e2e file, and shares no file with this PR. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… text that says `os plugins` works (objectstack-ai#21306) Fixes objectstack-ai#21285 Clause-②: no ## What this does `packages/cli/package.json` listed `@oclif/plugin-help` and `@oclif/plugin-plugins` under `oclif.plugins`, but both were only `devDependencies`. oclif loads an `oclif.plugins` entry only when the same name is in `dependencies`, so neither ever loaded. This PR: - removes the `oclif.plugins` array (no other `oclif` key changes); - removes the two `devDependencies` (no consumer remains, see H1) and regenerates `pnpm-lock.yaml` with `pnpm install --lockfile-only`; - corrects every published text that described the array or said `os plugins` works (per-site table below); - rewrites `test/plugin-commands.test.ts` so it pins the new state instead of the dead array; - adds `.changeset/21285-drop-dead-oclif-plugins.md` (`patch`, `@objectstack/cli`). Maintainer ruling (verbatim): > 同意:`oclif.plugins`: 里面那两个插件只装在 devDependencies,所以从来没加载过,`os help` 和 `os plugins` 都不是可用命令。我建议删掉这两条配置。 `packages/spec/**` is untouched. That includes `cli-extension.zod.ts` and its generated page `content/docs/references/kernel/cli-extension.mdx`, which still say `os plugins install`. The spec-lane card objectstack-ai#21286 carries them, and it remains open. ## Behaviour: nothing an operator sees changes Reading script: `node packages/cli/bin/run.js` with `NODE_ENV` unset and `OCLIF_COLUMNS=120`, run from an empty directory. Each run's stdout, stderr and exit code were captured. The runs: - `--help`, `help`, `plugins`, `plugins install @acme/plugin-marketplace` and `frobnicate`; - `TOPIC --help` for each of the 32 root-level topics; - an `@oclif/core` `Config.load` dump: loaded plugins, all 65 command ids and all 75 topics. That is 114 files per reading. | Reading | Tree | Result | |---|---|---| | before | base `748b24072`, unmodified | `os --help`: 12 topics + 22 commands. `help`, `plugins` and `plugins install` exit 2 with `command ... not found`. Plugins loaded: `@objectstack/cli` only. | | **positive control** | base, with the two plugins added to `dependencies` (mutation through `scripts/ablation-replace.mjs`, restore proven: blob == HEAD, `git diff HEAD` empty) | **differs** in 9 files, plus 6 new ones. `os help` exits 0. `os plugins` exits 0 ("No plugins installed."). The command table gains `help` and 10 `plugins:*` ids (65 to 76). The root help gains the `plugins` topic and the `help` and `plugins` commands. So the reading catches a real difference. | | after | `cc13e2532` (array and devDependencies removed, lockfile regenerated) and `a593c8c62` (CLI rebuilt) | `diff -r` against before: **empty**, all 114 files, same sha256 over the concatenation (`338e1f0f...5c6f38`) | | final heads | `3c8442fc0` and `a1e72918c` (after merging main) | identical, except the two version strings `17.5.0` to `17.6.0`. Those come from main's Version Packages merge, not from this diff. | ## Hypotheses - **H1 (no consumer): holds. Both devDependencies are removed.** `git grep` finds `plugin-help` and `plugin-plugins` only in these places: - the array and the `devDependencies` block; - comments in `bin/run.js`, `doctor.ts` and `doctor-deprecation-hint-commands.test.ts`; - docs text; - the one test assertion. No import, `require`, script, fixture or other importer names them. In the lockfile, only the `packages/cli` importer referenced them. - **Lockfile comparison**, measured against both merge bases (`748b24072` and `5a9292e6f`), with the same result: - 14 package entries and 14 snapshots are removed and 0 added. Every removed entry is in the transitive closure of the two plugins: `@oclif/plugin-help@7.0.2`, `@oclif/plugin-plugins@7.0.3`, `hosted-git-info@7.0.2`, `isexe@3.1.5`, `lru-cache@10.4.3`, `npm@11.21.0`, `npm-package-arg@11.0.3`, `npm-run-path@5.3.0`, `object-treeify@4.0.1`, `path-key@4.0.0`, `proc-log@4.2.0`, `validate-npm-package-name@5.0.1`, `which@4.0.0` and `yarn@1.22.22`. - All 1379 kept snapshots and packages are byte-identical. - **DOWN count: 0.** Four names lose only a second, plugin-only version: `isexe` 3.1.5, `lru-cache` 10.4.3, `path-key` 4.0.0 and `which` 4.0.0. The versions every other consumer resolves are unchanged. - **H2 (only `dependencies` count): holds.** `@oclif/core` 5.1.2 `lib/config/plugin-loader.js` `loadCorePlugins` calls `findMatchingDependencies(rootPlugin.pjson.dependencies ?? {}, corePlugins)`. Measured three ways: - the before/after identity above; - the positive control above; - a standalone fixture root on 5.1.2: with `@acme/plugin-marketplace` listed in `oclif.plugins` plus `dependencies`, `marketplace:search` loads and runs. Moved to `devDependencies`, nothing loads. - **H3 (the site list is complete): holds, with no new site.** The PM's grep was re-run, then widened to `plugins install/uninstall/update/link/...` in space and colon forms, every `@oclif/plugin-*`, and `os|objectstack plugins|help`. Every hit is accounted for in the table below. The widened spellings found only three things beyond the card's sites: `bin/run.js:87` (the `plugins link` sentence, covered with the run.js site), the phrase "ObjectStack plugins" (prose, not a command), and one `CHANGELOG.md` line. - **H4 (the build-your-own-distribution route stays true): holds.** Decided from the loader, not the old text. Fixture distribution roots on `@oclif/core` 5.1.2 `Config.load`: - a root listing `@acme/plugin-marketplace` in both `oclif.plugins` and `dependencies` loads `marketplace:search` and runs it; - a root listing both `@objectstack/cli` and the extension that way loads 66 commands (this CLI's 65 plus `marketplace:search`); - devDependencies-only loads nothing. ## Per-site conclusions | Site | Conclusion | |---|---| | `packages/cli/package.json` `oclif.plugins` + 2 `devDependencies` | **Changed**: removed. | | `pnpm-lock.yaml` | **Changed**: regenerated by the tooling, comparison above. | | `content/docs/plugins/index.mdx` Step 3 callout (lines 400-408) | **Changed**. Its reason ("`plugin-plugins` sits in devDependencies") became false. It now says: no plugin manager; `os plugins ...` and `os help` are not commands; `os --help` is the help entry; the distribution route, kept because H4 holds; and the loader's `dependencies`-only rule. | | same page, "Once loaded, the new commands appear in `os --help`" | **Already true**: true of the distribution's own `os --help` (H4 fixture). | | `packages/cli/README.md` `### os plugins (oclif)` | **Changed**. Now `### os plugins and os help (not commands)`: no plugin manager; each exits 2; use `os --help`; link to the plugin-system section. | | `packages/cli/README.md` `## oclif Plugin System` (intro, step 3, "Install and use", comparison row) | **Changed**. `os plugins install` is gone. Step 3 and the example load through an `os` distribution listing the plugin in `oclif.plugins` + `dependencies`. | | `packages/cli/bin/run.js:84-97` | **Changed**. The reasoning now names "no plugin manager, no `oclif.plugins`, no `@oclif/plugin-plugins` dependency" instead of the devDependencies placement. The 34-entry count is re-measured (12 topics + 22 commands). | | `packages/cli/src/commands/doctor.ts:2377-2380` | **Changed** (comment): "no plugin supplies one (the package declares no `oclif.plugins`)". | | `packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts:15` | **Changed** (comment), same restatement. | | `packages/cli/test/plugin-commands.test.ts` | **Rewritten** to pin the new state. `oclif.plugins` is undefined, and no `@oclif/plugin-*` package appears in any dependency field. The command-discovery and bin pins are kept. The guard is not deleted (reverse verification below). | | `docs/qa/platform-checklist/areas/cli.json:658` (`cli.flag-command-error-ux`) | **Changed**. The source line now reads "no oclif.plugins at all, no plugin manager, no help command and no not-found plugin, so unknown commands hard-error". Revision 1 to 2, with a history entry. `pnpm check:platform-checklist` is green. | | `packages/spec/src/kernel/cli-extension.zod.ts:18` and `content/docs/references/kernel/cli-extension.mdx:21` | **Out of scope**. Spec-lane, carried by objectstack-ai#21286, untouched as the card directs. | | `packages/cli/CHANGELOG.md:1362` (the "`plugins link`ed TypeScript plugin ... `@oclif/plugin-plugins` sits in `devDependencies`" entry) | **Out of scope**. Release-owned, and true of the version it shipped with. Not edited in a code PR. | | "ObjectStack plugins" hits (`content/docs/ai/skills.mdx`, `api/error-handling-server.mdx`, `plugins/development.mdx`, `packages/core/src/types.ts`, `packages/types/README.md`, `skills/objectstack-platform/references/plugin-hooks.md`), root `CHANGELOG.md:1367` | **Not a site**: a case-insensitive match on prose, not an `os plugins` command. | ## Tests and gates (final head `a1e72918c` unless noted) - Gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `a1e72918c` derives **106** commands. All 106 were run with each exit code captured before any pipe, and **all 106 exit 0**. `--ran` reconciles them: `106 derived famil(ies) accounted for, 106 run, 0 NOT-MEASURED`. - The same 106 were also all green at `3c8442fc0`. - At `a593c8c62`, 4 needed a re-run. Three refused with exit 3 (`PREREQUISITE NOT MET`) before the packages they read were built: `check:skill-examples`, `check:dual-build-cjs-loads` and `check:i18n-coverage`. `check:slot-lookup` hit an ENOENT on a temp fixture that a concurrent CLI test deleted. All 4 were green on re-run. - `@objectstack/cli` unit tier (`vitest run --project unit --maxWorkers=2`): **243 files and 3440 tests passed** at `3c8442fc0`, and again at `a593c8c62`. The two edited test files were re-run at `a1e72918c`: 2 files and 16 tests passed. The last merge (`3c8442fc0` to `a1e72918c`) brought 4 main commits, none of which touch `packages/cli`. - Reverse verification on the rewritten test, with the fix committed. Each mutation went through `scripts/ablation-replace.mjs` (anchor hit, blob changed) with its restore proven (blob == HEAD, `git diff HEAD` empty): - re-adding the `oclif.plugins` array: `declares no oclif.plugins` goes **red** (`expected [ '@oclif/plugin-help', ... ] to be undefined`); - adding `@oclif/plugin-plugins` to `devDependencies`: `depends on no @oclif/plugin-* package` goes **red**. - `pnpm --filter @objectstack/cli typecheck` (at `a593c8c62`): exit 0. `plugin-commands.test.ts` is in `tsconfig.test.json`'s program and `doctor-deprecation-hint-commands.test.ts` in `tsconfig.json`'s (`--listFilesOnly`). - `--project integration` (at `a593c8c62`, run because the diff touches `bin/`): 69/70 files and 601/603 tests pass. The 1 failure is pre-existing: `test/published-entry-node-env-source-reroute.test.ts`, `CONTROL: neutralising the declaration in the child reproduces the card verbatim`. It fails identically on base `748b24072`, built (59/59 turbo cache), in a separate worktree. Cause: tsx 4.23.15's ESM API registers `./esm/index.mjs` relative to `dist/esm/api/index.cjs`. That resolves to the nonexistent `dist/esm/api/esm/index.mjs` (`oclif:config:ts-path` debug: "Could not find tsx. Skipping tsx registration"), so the control's trap never arms. It is unrelated to `oclif.plugins`. The integration tier was not re-run at the final head: the incoming main commits touch no `packages/cli/bin` or `src` file. It is declared to CI. - Lint, narrowed and measured: `eslint --no-inline-config --format json` over the 4 touched JS/TS files reports 4 files, 0 errors and 0 warnings. The other 6 touched files (`.md`, `.mdx`, `.json`, `.yaml`) are outside `eslint.config.mjs`'s `files` globs. The config enables no type-aware linting (no `parserOptions.project`), so this diff cannot move an untouched file's verdict. The full `pnpm lint` is CI's. ## Acceptance notes (not filed here; for the seat) - `packages/cli/README.md` `### Global` says `-v, --version` and `-h, --help`. Measured on the built entry: `os -h` and `os -v` exit 2 with `command -h not found` and `command -v not found`. Only `--help` and `--version` work. This is pre-existing and unrelated to this diff, so it is reported, not fixed. - `packages/cli/README.md` `### Plugin Management` says "There is no `os plugin` command group in v1". `os plugin build|sign|publish` is registered: the `plugin` topic is in `os --help`. This is pre-existing, and objectstack-ai#21167 also holds this file, so it is left untouched. - The tsx 4.23.15 ESM-API defect above. It reds that integration control leg on base too, so it will red `packages/cli`'s integration tier wherever that file runs. --- _Generated by [Claude Code](https://claude.ai/code/session_018gA1pE6eJtwHhqx72G8U9X)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…picklists included (objectstack-ai#21337) Fixes objectstack-ai#21018 Clause-②: no This PR is item 4 of objectstack-ai#21018, the last item on the card: the Tier H line in `skills/objectstack-platform/SKILL.md` that counts and lists the blank starter's generator barrels. The code half landed on `main` as `bcd68a29f3` (PR objectstack-ai#21167), and since that landing the sentence has been false. The claim for this PR is `5945859013` on the card; the dev session is `session_01VvcEokUG1tvVxkceYfR5XB`. ## Premise, checked on `origin/main` (`1caa603730`, then merged `222ecc27f9`) - `packages/create-objectstack/src/templates/blank/objectstack.config.ts` imports eight barrels and hands each to its stack key, in this order: `objects`, `views`, `actions`, `flows`, `dashboards`, `apps`, `skills`, `picklists`. `src/` of the template holds the same eight directories. - `os init` derives its wiring from the roster: `SCAFFOLD_WIRED_BARRELS` (`packages/cli/src/commands/init.ts:602`) maps `GENERATOR_SCAFFOLD_TARGETS`, which is `Object.entries(GENERATORS)` in `generate.ts` — eight generators: `object`, `view`, `action`, `flow`, `dashboard`, `app`, `skill`, `picklist`. - `packages/cli/test/create-objectstack-wiring-parity.test.ts` holds the blank template's import lines and stack-key lines equal to the `os init` rendering, so the template list and the roster cannot drift apart. - So the true sentence is "the eight generator barrels", with `picklists` last — the order the template file uses. The SKILL.md line said seven and listed seven. ## What changed One file, two lines, net zero lines: `skills/objectstack-platform/SKILL.md:194-195`. Before: ```text generic connector executors in `plugins:`; and the seven generator barrels (`objects`, `views`, `actions`, `flows`, `dashboards`, `apps`, `skills`), ``` After: ```text generic connector executors in `plugins:`; and the eight generator barrels (`objects`, `views`, `actions`, `flows`, `dashboards`, `apps`, `skills`, `picklists`), ``` This is the text PR objectstack-ai#21167's "Not in this PR" section proposed, byte for byte. ## The `skills/**` readings (both halves) | Reading | Before (`1caa603730`) | After (`82e06107cf`) | Delta | |:---|---:|---:|---:| | `skills/objectstack-platform/SKILL.md`, lines | 489 | 489 | 0 | | `skills/objectstack-platform/SKILL.md`, tokens (`ceil(utf8 bytes / 4)`, the ratchet's convention) | 5827 | 5830 | +3 (ceiling 5833, headroom 3) | | Whole published catalog, all 10 `skills/**/SKILL.md`, lines | 4397 | 4397 | 0 | | Whole published catalog, all 10 `skills/**/SKILL.md`, tokens | 52132 | 52135 | +3 | The 13 added bytes are the word `eight` for `seven` (same length) plus `, \`picklists\``. No re-wrap, no content removed, no ceiling moved. `node scripts/check-skills-token-ratchet.mjs` reads `skills/objectstack-platform/SKILL.md is 5830 tokens (ceiling 5833; headroom 3)` on `82e06107cf`. ## The sweep of `skills/**` Every file under `skills/` was grepped for a count word (six to nine) near barrel / generator / template / scaffold, for the word `barrel`, for `picklist`, and for `src/` directory listings. Only `SKILL.md:194-195` states a count or list that PR objectstack-ai#21167 made false. The other hits, each left alone: - `skills/objectstack-platform/SKILL.md:210-235`, the "Project Structure Conventions" tree: a generic convention listing (`objects`, `views`, `apps`, `flows`, `actions`, `dashboards`, `reports`, `datasets`, `i18n`, `handlers`, each marked optional). It never enumerated the template's barrels (it omitted `skills` already), so it states no count or list that is now wrong. - `skills/objectstack-platform/references/bootstrap.md:55-72` and `:163-180`: illustrative config examples with four barrels and one barrel. Examples, not a roster. - `skills/objectstack-platform/evals/config-plugins-ops.json:7`: an eval's expected output for a CRM config ("barrel imports … same for views / flows"). Not a roster. - `skills/objectstack-platform/SKILL.md:44-45`: the stack-key list already names `picklists` and `picklistExtensions`. Correct. ## Changeset None, declared with the `skip-changeset` label. The diff publishes nothing from any released package: no `package.json` under `packages/` or `apps/` names a skills path in its `files[]` (positive control: `@objectstack/spec`'s `files[]` lists `dist`, `json-schema`, …), and `create-objectstack` installs the catalog at scaffold time with `npx skills add objectstack-ai/objectstack/skills` (`packages/create-objectstack/src/skills-install.ts`), reading this repository directly rather than a bundled copy. ## Gates - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`, run with no paths, derived 25 families from the change set (1 path). The list is identical before and after the `origin/main` merge. - All 25 were run on the final head `82e06107cf`, each exit code captured before any pipe: 25 exit 0. `--ran` reconciliation: `✓ dispatch-gates --ran: 25 derived famil(ies) accounted for — 25 run, 0 NOT-MEASURED`. - On the pre-merge head `84fc471b7f` the same 25 were run once before: 24 exit 0 and one `PREREQUISITE NOT MET` (exit 3, `@objectstack/lint` `check:doc-formula-expressions`, the package was not built). After `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under the verify lock it exited 0; the merged-head run above includes it green. - Also attempted, outside the derived 25: `pnpm --filter @objectstack/spec run check:skill-examples`. It refused with `PREREQUISITE NOT MET` (exit 3: `packages/client-react/dist` holds no declarations). NOT MEASURED locally; it is declared to CI. The diff changes no `ts`/`tsx` fence, which is the only surface that gate reads. - `scripts/pm/check-skill-line-ratchet.mjs` is not applicable: its header excludes the published `skills/` catalog by design; the token ratchet above is the catalog's gate. - `pnpm lint` is CI's run; this diff touches one Markdown file, which is outside eslint's population (`eslint.config.mjs` lints `ts`/`tsx`/`js`/`mjs`), so nothing here moves a lint verdict. ## Acceptance notes - `skills/objectstack-platform/references/operations.md:26` reads "`os generate KIND` | Scaffold an object / view / flow / agent from a template" (KIND spelled there as a placeholder in angle brackets), while `skills/objectstack-ai/SKILL.md:344` says `os g agent` is retired. Pre-existing, not a count or list PR objectstack-ai#21167 touched, and outside this claim's purpose; noted, not filed. - The "Project Structure Conventions" tree in the same SKILL.md (see the sweep) lists neither `skills/` nor `picklists/`. It is a generic convention list with no count, so it is not false; noted for a future prose pass, not changed here. - The branch carries one merge commit of `origin/main` (`222ecc27f9`, two commits touching `scripts/pm/fleet-write/*` and `scripts/pm/issue-*.mjs`, none touching `skills/` or a gate this diff derives). The PR's net diff against `origin/main` is the one file, +2 / −2. - Tier H: this PR stays a draft; it lands only after an authorized approval is on record, through the owning seat. ## 维护者速读(草稿) ### 改了什么 平台技能包 `skills/objectstack-platform/SKILL.md` 里描述 blank 模板的那一句:把「七个生成器目录」改成「八个」,并在列表末尾补上 `picklists`。改两行、删两行,净零行;整个技能包行数不变(4397 行),token 读数 5827 → 5830(上限 5833,余量 3)。 ### 为什么改 上一个 PR(代码半,objectstack-ai#21167)落地后,`npm create objectstack` 新建的项目实际接了 8 个目录,多出的是 `src/picklists`(共享选项列表,`os generate picklist` 的产物)。技能文本还写 7 个:AI 读了会少认一个目录,不知道新生成的选项列表落在哪里、挂在哪个 stack 键下。 ### 风险与代价(含回滚) 只改一句说明文字,不改任何代码,不发任何 npm 包。技能目录由 `npx skills add` 从仓库直接拉取,所以合并后新建的项目立刻读到新句子;已建项目不受影响。回滚就是 revert 这一个 commit。本地派生的 25 个门禁全绿;`skills/**` 的 token 棘轮没动上限。 ### 席位意见 (留空,席位定稿时填写) ### 你要做的 在本 PR 上给一个 Approve(`skills/**` 是受管面 Tier H,需要维护者的批准记录);之后由席位负责落地,不用你再操作。 --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ Co-authored-by: Claude <noreply@anthropic.com>
… that the built os registers (objectstack-ai#21354) Fixes objectstack-ai#21310 Clause-②: no ## What changes `packages/cli/README.md` now says what the built `os` does. Every claim below was read off the built entry (`packages/cli/bin/run.js`, `@oclif/core` 5.1.2), run from an empty cwd: `os --help`, `os plugin --help`, every topic's `--help` and each documented command's `--help`. The credential sources were also measured against a local echo server. - **`### Global`** lists `--version` and `--help` only. It says there is no short form: `os -h` and `os -v` exit 2. It also names the commands where `-v` is the command's own flag. - **`### Plugin Management`** drops "There is no `os plugin` command group in v1". It lists the registered group instead: `os plugin build`, `os plugin sign` and `os plugin publish`. It notes that the group has no `install` (per ADR-0025's status line), and that `os plugin` is unrelated to `os plugins`. - **Two command-table rows were wrong:** `os init [name]` and `os dev [package]`. Both are rewritten. - **Cloud credentials and flags (patch round 1).** The Cloud section said every cloud command reads `os cloud login`'s session, or `--token` / `OS_CLOUD_API_KEY` and `--server` / `OS_CLOUD_URL`. A new `#### Credentials and server URL` table states, per command, the server-URL flag, the token flag and the stored session it uses. The typical publish flow now says that its `os environments create` step does not read the `os cloud login` session. - **`os serve --ui` (patch round 1)** now uses the `--help` wording: "Enable the bundled Console portal", in place of "Enable Studio UI". - **`.changeset/21310-cli-readme-flags.md`** is a `patch` for `@objectstack/cli`, because `README.md` is in the package's `files`. It now counts five false claims. No code, flag, environment variable, exit code or help page changes. `packages/cli/package.json` is untouched. ## Short flags: the README route (docs follow the implementation) Readings on the built entry from an empty cwd. "Before" is at `1caa60373`, the branch point after objectstack-ai#21167 landed. "After" is at `9bdb092ca`, this head. | argv | before | after | |---|---|---| | `os -h` | exit 2, `command -h not found` | exit 2, `command -h not found` (unchanged by design) | | `os -v` | exit 2, `command -v not found` | exit 2, `command -v not found` (unchanged by design) | | `os --help` | exit 0, 3712 bytes, md5 `1855676fe5a2bb87aa5871fc1bed196f` | byte-identical, same md5 | | `os --version` | exit 0, `@objectstack/cli/17.6.0 linux-x64 node-v22.22.0` | same | **The ruling's check.** The ruling: "show that no command already uses `-h` / `-v` as its own flag. If one does, choose the README route and say why." Six commands already own `-v`. Found by `git grep` for `char: 'v'` in `packages/cli/src`, then read back in each command's `--help`: - `-v` is `--verbose` on `os dev` (`dev.ts:212`), `os serve` (`serve.ts:1209`), `os start` (`start.ts:93`) and `os doctor` (`doctor.ts:1905`). - `-v` is `--version VALUE` on `os package publish` (`package/publish.ts:315`) and `os package install` (`package/install.ts:57`). No command owns `-h`. So the ruling sends `-v` down the README route. `-h` was eligible on its own, and it is dropped too, for the reasons in the four axes below. **What the alternative would have done.** These rows come from oclif's own predicates, `versionAddition` and `helpAddition` in `@oclif/core` 5.1.2 `lib/main.js`. They were evaluated in memory on this package's loaded `Config`, with `additionalVersionFlags: ["-v"]` and `additionalHelpFlags: ["-h"]` set on it. No file was written. | argv | today | with the two keys | |---|---|---| | `-v serve` | exit 2, `command -v not found` | version check true: prints the version, exits 0, **and serve never runs** | | `serve -v` | `--verbose` | `--verbose` (oclif checks only argv[0] for a version flag) | | `serve -h` | exit 2, `Nonexistent flag: -h` | help check true: prints help | **The four axes.** - **实际业务需求.** There is no measured pull. `git grep` for `os -h`, `os -v` and `objectstack -h|-v` over the whole tree (content/docs, skills, examples, packages, scripts) finds no occurrence. This README's `### Global` was the only text that named the short forms. - **项目长远合理性.** `-v` already has two meanings inside this CLI: verbose on four commands and a package version on two. Adding a third that applies only at argv[0] (print the CLI version) makes the flag's meaning depend on where it appears. Making the docs follow the implementation removes the false claim with no runtime change. - **防 AI 写代码犯错.** Today a mistaken `os -v serve` fails loudly with exit 2. With the key set, it would print a version line, exit 0 and start nothing, so a loud failure would become a silent one. The README now says the short forms do not exist, so an agent reading it uses `--help` and `--version`, which work in every position. - **创业阶段不扩散需求.** New flags would be a new capability with no measured pull, and the default is to keep scope tight. Making only `-h` work would also leave `### Global` asymmetric, with no measured user who needs it. ## `os plugin` — the commands, verbatim `os plugin --help` at `9bdb092ca`, exit 0. The output is byte-identical at `1caa60373`. ```text Compile a plugin into a signed-ready `.osplugin` artifact (ADR-0025 §3.4) USAGE $ os plugin COMMAND COMMANDS plugin build Compile a plugin into a signed-ready `.osplugin` artifact (ADR-0025 §3.4) plugin publish Publish a signed .osplugin to ObjectStack Cloud (ADR-0025 §3.4) plugin sign Sign a built .osplugin with a publisher Ed25519 key (ADR-0025 §3.4) ``` Usage lines: `os plugin build [DIR] [-e VALUE] [-o VALUE] [--minify]`, `os plugin sign ARTIFACT -k VALUE [--key-id VALUE] [-o VALUE]` and `os plugin publish [ARTIFACT] …`. There is no `install`, and that matches ADR-0025's status line, which says the code-plugin install half is unimplemented. ## Every README command-table row against `--help` Placeholders are spelled in capitals here (TYPE, NAME, ID). | Section | Row | Conclusion | |---|---|---| | Development | `os init [name]` | **Changed.** It said "in the current directory". `os init --help` says: "When provided, a new directory with this name is created; otherwise the current directory is used." The Quick Start's own `os init my-app` was a counterexample. | | Development | `os dev [package]` | **Changed.** It said "with hot reload". `os dev --help` says: "watch sources, rebuild the artifact, and restart the server on change". `dev.ts` records that the old "server will auto-reload" line "advertised a hot reload the runtime only partially performs". | | Development | `os serve [config]` | **Already true.** For "plugin auto-detection": `serve.ts:11` imports `isHostConfig` / `shouldBootWithLibrary` from `utils/plugin-detection.ts`, which detect a host config that carries instantiated plugins. The row leaves out the artifact fallback that `--help` leads with, but that is an omission, not a false claim. | | Build & Validate | `os compile [config]` | **Already true.** `-o` defaults to `dist/objectstack.json`. | | Build & Validate | `os validate [config]` | **Already true.** `--help` also mentions CEL expressions and widget bindings, which the row leaves out. | | Build & Validate | `os info [config]` | **Already true.** `info.ts:117` prints agents. | | Scaffolding | `os generate TYPE NAME` | **Left alone, per the ruling** (these rows are objectstack-ai#21167's, which landed as `bcd68a29f` before this branch's base). It is also already true: `--help` marks NAME optional, but `generate.ts` refuses a metadata type without a name ("Missing required argument"). NAME is optional only for the `types`, `client` and `migration` routes. | | Scaffolding | `os create TYPE [name]` | **Already true.** `--help` says "Create a new standalone kernel code plugin from a built-in template", with TYPE = plugin. | | Cloud | `os cloud login` | **Already true.** `-e/--email` and `-p/--password` skip the browser flow, and credentials go to `~/.objectstack/cloud.json`. | | Cloud | `os cloud whoami` / `os cloud logout` | **Already true.** Both are listed in `os cloud --help`. | | Cloud | `os environments create --org ID --name N` | **Already true.** Both flags are required in the usage line. There is no `projects` topic in `os --help`. | | Cloud | `os environments list` / `show ID` | **Already true.** | | Cloud | `os package publish [artifact]` | **Already true.** ARTIFACT defaults to `dist/objectstack.json`. | | Plugin Management | (prose) | **Changed** (see above). | | Quality | `os test [files]`, `os doctor`, `os lint [config]`, `os diff [before] [after]` | **Already true.** The usage lines match. | | Reference | `os explain [schema]` | **Already true.** | | CLI Options | `### Global` | **Changed** (see above). | | CLI Options | `os plugins` and `os help` (not commands) | **Already true since objectstack-ai#21306; not re-edited.** `os plugins` exits 2 with `command plugins not found`, and `os help` exits 2 with `command help not found`. `package.json` has no `oclif.plugins` and no `@oclif/plugin-*` dependency. | | Cloud | lead sentence: credentials from `os cloud login`, or `--token` / `OS_CLOUD_API_KEY` and `--server` / `OS_CLOUD_URL` | **Changed (patch round 1).** This holds only for `os package publish` and `os plugin publish`. The new per-command table is below. | | Cloud | typical flow: `os cloud login`, then `os environments create` | **Changed (patch round 1).** With only the `os cloud login` session present, `os environments create` exits 1 with `Authentication required. Please run os login or set OS_TOKEN environment variable.` The flow now says so at that step and names what the step reads instead. | | Cloud | "Set `OS_CLOUD_URL` (or `--server`)" | **Changed (patch round 1).** `os cloud login`, `os package publish` and `os environments` read `OS_CLOUD_URL`. The flag is `--server` on `os package publish` and `--url` on the other two. `os cloud whoami` / `logout` read neither. | | CLI Options | `### os serve` `--ui` | **Changed (patch round 1).** It said "Enable Studio UI". It now uses the `--help` wording: "Enable the bundled Console portal at /_console/ when @object-ui/console is installed (default: true)". | ## Cloud commands: flags, env vars and stored session, per command Read off each command's `--help` at `9bdb092ca`. The "stored session" column was measured, not taken from the help: `HOME` pointed at a temp dir holding only a `cloud.json`, or only a `credentials.json`, whose URL was a local echo server that logged each request's path and bearer. | Command | Server URL flag (env) | Token flag (env) | Stored session it authenticates with | |---|---|---|---| | `os cloud login` | `-u, --url` (`OS_CLOUD_URL`, default `https://cloud.objectos.ai`) | none: `-e, --email` / `-p, --password`, or the browser device flow | writes `~/.objectstack/cloud.json` | | `os cloud whoami`, `os cloud logout` | none (`--json` only) | none | read / delete `cloud.json` (`cloud/whoami.ts:26`, `cloud/logout.ts:29,39`) | | `os package publish` | `-s, --server` (`OS_CLOUD_URL`, default `https://cloud.objectos.ai`; with neither set, the URL in `cloud.json`) | `-t, --token` (`OS_CLOUD_API_KEY`, then `OS_TOKEN`) | `cloud.json`. With only `credentials.json`: exit 1, "Not logged in to ObjectStack Cloud. Run os cloud login first", and 0 requests. With only `cloud.json`: the request goes to its URL with its bearer. With `OS_CLOUD_API_KEY` or `OS_TOKEN`: the request carries that bearer. | | `os plugin publish` | `-s, --server` (`OS_CLOUD_URL`) | `-t, --token` (`OS_CLOUD_API_KEY`) | `cloud.json`, by the same precedence code as package publish (`plugin/publish.ts:170-178`). Code-read only; not run, because it needs a built `.osplugin`. | | `os environments list` / `show` / `create` / `bind` / `switch` | `-u, --url` (`OS_CLOUD_URL`); else the URL in `credentials.json`; else `http://localhost:3000` | `-t, --token` (`OS_TOKEN`) | `credentials.json`, the `os login` session. With only `cloud.json`, all five exit 1 with `Authentication required`, before any request. With only `credentials.json`, `list` sends `GET /api/v1/cloud/environments` with its bearer. `OS_TOKEN` works, and `OS_CLOUD_API_KEY` alone does not. | | `os package install` (a runtime command, not a cloud one) | `-r, --runtime` (`OS_RUNTIME_URL`, default `http://localhost:3000`) | none: `--email` / `--password` (`OS_RUNTIME_EMAIL` / `OS_RUNTIME_PASSWORD`) | none | | Also read, not in the README's Cloud section: `os whoami`, `os data *`, `os meta list/get/register/delete` | `-u, --url` (`OS_CLOUD_URL`) | `-t, --token` (`OS_TOKEN`) | `credentials.json`, through the same `createApiClient` (code-read) | | Also read: `os datasource introspect/list-tables/validate` | `-u, --url` (`OS_CLOUD_URL`, else `http://localhost:3000`) | `-t, --token` (`OS_TOKEN`) | none. These use flags and env only (`datasource/introspect.ts:10-13`, code-read). | | Also measured: `os login` / `os register` | `-u, --url` (`OS_RUNTIME_URL` for login, `OS_CLOUD_URL` for register; default `http://localhost:3000`) | none (email/password, or the device flow for login) | writes `credentials.json` | ## Acceptance notes These are out of scope. The last one is filed as objectstack-ai#21360; the others are not filed. - **Registered commands with no README row.** `build`, `start`, `verify`, `login`, `logout`, `register`, `whoami`, `migrate`, `data`, `datasource`, `db`, `i18n`, `meta`, `secret`, `storage`, `package install` and `environments bind/switch` have no row. These are omissions, not mismatches: the README does not claim to be complete, and `content/docs/deployment/cli.mdx` is the full reference. - **"Runtime plugins are bundled into the build artifact" is kept as written.** I did not re-measure it. Only the false clause in front of it was removed. - **For the seat — the README is now true, but the flow it documents has a gap.** After only `os cloud login`, `os environments create` refuses and says to run `os login`. `os login --help` says "For the hosted package registry, use `os cloud login` instead." This round changes no code, so the README states the gap rather than closing it. The seat filed it as objectstack-ai#21360. ## Verification - **Build.** `pnpm turbo run build --filter=!@objectstack/docs --concurrency=2` at `9bdb092ca`: 72/72 tasks, verify-lock `VERDICT command-exit 0`. - **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `9bdb092ca` gives 52 commands; the merged `main` added `check-dts-emitted.mjs --self-test`. All 52 exited 0 at `9bdb092ca`, each exit code captured before any pipe. The `--ran` reconciliation reads "52 derived, 52 run, 0 NOT-MEASURED, 0 UNRUN", and that zero is derived from recorded exit codes. - **`main` moved again after the last merge.** That happened while the gates ran: `96b12b589` (a pm-roster step in `lint.yml`) and `23365eaed` (spec). Neither touches `packages/cli` or this changeset. The merge queue rebuilds the PR on current `main`. - **Runtime unchanged.** `os --help` is byte-identical at `9bdb092ca` and at `1caa60373`: exit 0, 3712 bytes, md5 `1855676fe5a2bb87aa5871fc1bed196f`. `os -h` and `os -v` still exit 2. - **CLI unit tier.** `pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2`: 244 files and 3461 tests passed, `VERDICT command-exit 0`, at `4e7e91fd2`. Since then, `git diff 4e7e91f 9bdb092 -- packages/cli` touches only `packages/cli/README.md`, and no CLI test reads that file. The tests that mention a README read the README that `os create` emits. The integration tier is left to CI. - **CLI typecheck.** `pnpm --filter @objectstack/cli typecheck`: exit 0 at `4e7e91fd2`. --- _Generated by [Claude Code](https://claude.ai/code/session_018gA1pE6eJtwHhqx72G8U9X)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #21018
Clause-②: yes (widening)
This PR is the code half of #21018. It covers scope items 1, 2, 3 and 5 of the card body. Item 4, the "seven generator barrels" line in
skills/objectstack-platform/SKILL.md, is on a Tier H governed path. It follows in its own PR under a second claim, so #21018 remains open for it.Dispatched by the
domain:cliseat's PM under claim 5929373213. Dev sessionsession_01VvcEokUG1tvVxkceYfR5XB.Premise, checked on
origin/mainThe card waited on the runtime reader. That reader is on
mainas 88b484e (#21047), and these are the parts this PR relies on:picklistsandpicklistExtensionsare inMETADATA_ARRAY_KEYS(packages/objectql/src/engine.ts).picklistinto servedoptions(picklist-resolution.ts,resolvePicklistFieldsOnto).packages/objectql/src/plugin.ts).The repro below measures all of this end to end on a scaffolded project.
What changed
Item 1:
os generate picklist NAME(packages/cli/src/commands/generate.ts)osCLI compile / validate / lint path accepts*.picklist.ts,picklistsandpicklistExtensions, andos validaterefuses apicklistthat names no picklist (Scope 6 of #19518) #20825's branch (6ef78d3f29, reverted ine16359a9fa), re-read against the landed reader.src/picklists/NAME.picklist.tsthroughdefinePicklist. The file name comes frommetadataFileName, with no override.picklists(singularToPlural).namesObject: false, andrequiresis empty.optionsof its own.Item 2: scaffold wiring
os initderives its wiring from the roster (SCAFFOLD_WIRED_BARRELS), so theappandplugintemplates wiresrc/picklistswith no edit toinit.ts.create-objectstackgainssrc/picklists/index.ts, byte-identical to the empty barrelos initwrites. Itsobjectstack.config.tsgainsimport * as picklists from './src/picklists';andpicklists: exportsOf(picklists),.create-objectstack-wiring-parity.test.tsholds the two scaffolders equal.Item 3: docs
content/docs/deployment/cli.mdxgets the example line, the table row (picklist,src/picklists/,NAME.picklist.ts,picklists) and the "What it does" clause.packages/cli/README.mdgets the type list and a short paragraph.Item 5: the metadata summary (
packages/cli/src/utils/format.ts)MetadataStatsgainspicklists, andcollectMetadataStatscounts it through the existingauthoringRuleUnionStackfold, so both ADR-0130 D4 shapes are covered.printMetadataStatsrenders it in theData:row, the kind's own domain (domain: 'data'in the registry).picklistExtensionsentry is not counted as a list.MetadataStatsmember gainpicklists: 0:format.metadata-stats-package-fold.test.tsandprint-metadata-stats-zero-row.test.ts.Changesets
@objectstack/cli:minor. A new generator kind, andstats.picklistsis added to the--jsonoutput ofos validate,os buildandos info.create-objectstack:minor. The starter gains a wired barrel.Clause-②: yes (widening). Nothing is narrowed and nothing is renamed, so there is no ADR-0087 marker.Repro, before and after
Before, on
origin/main58a77db:After, on this branch:
On a project scaffolded before this change (its config wires no
src/picklists),os g picklist regionexits 0. It reportsNot wiredand prints the import line and thedefineStackkey to add.Tests
The pins are:
packages/cli/src/commands/generate-picklist.pin.test.ts:src/picklists, stack keypicklists,namesObject: false, norequires) and theNAME.picklist.tsfile name;os validateuses (bundle-require,BUNDLE_REQUIRE_EXTERNALS), parsed byPicklistSchema, under the item nameos greports;ObjectQL.registerAppunderpicklists, serving its options on aField.select({ picklist })field, and passingPicklistServedFieldSchema;It constructs
ObjectQL, so it lands in theintegrationtier.packages/cli/src/utils/format.metadata-stats-picklists.test.ts:Data: 1 Objects 2 Fields 1 Picklists;Data: 1 Objects 2 Fields.The runs, all on HEAD 5a53515 except where noted:
pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2packages/cli is not builtand passed afterpnpm --filter @objectstack/cli build.--project integration, the 8 integration files among the related testsgenerate-picklist.pin,create-objectstack-stack-reach,generate-stack-reach,info-detail-package-foldand 4 moreOS_TEST_TIERS=nightly, the related*.e2efilesgenerate-scaffolds-reach-stack.e2e: "os g picklistexits 0 and reports no wiring to add"pnpm --filter create-objectstack exec vitest runpnpm --filter @objectstack/cli typecheck(tsc --noEmit && check:test-typecheck)pnpm --filter create-objectstack typecheckAblation
The ablations ran on the committed fix (e63b1db). Each mutation went through
scripts/ablation-replace.mjswith the anchor counted on disk. Each restore was proven: the blob equals HEAD andgit diff HEADis empty.picklistrow deleted fromGENERATORS(anchor x1 → x0, 53 lines)generate-picklist.pin3 of 5.wiring-parity: imports and stack keys (2)generate-scaffold-validates19/19; the pin's file-name case and its no-list controlpicklists: exportsOf(picklists),deleted from the blank configwiring-parity: "hands every wired barrel to its stack key" (1 of 22)picklists: count(stack.picklists),deletedmetadata-stats-picklists5 andpackage-fold4 (itsZEROEScomparisons)print-metadata-stats-zero-row['Picklists', stats.picklists],deletedmetadata-stats-picklists"Data: row", andprint-metadata-stats-zero-row"every metric collectMetadataStats counts is rendered"Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run with no paths, derived 96 families. All 96 were run, each with its exit code recorded before any pipe.PREREQUISITE NOT MET(exit 3) because their build inputs were missing:check:skill-examples,check:dual-build-cjs-loads,check:i18n,check:i18n-coverageandcheck:i18n-walk-parity. All five passed once those inputs were built.--ranreconciliation:✓ dispatch-gates --ran: 96 derived famil(ies) accounted for — 96 run, 0 NOT-MEASURED.pnpm lint, as a proven narrowing (on HEAD 5a53515):isPathIgnored/calculateConfigForFile): all 8 touched.tsfiles are linted. The 4 touched.md/.mdxfiles are outside its population.--format json: 8 files, 0 errors, 0 warnings, with--no-inline-config.parserOptions.project, noprojectService, aseslint.config.mjsstates). This diff touches no lint config or baseline. So it cannot move the verdict on any file it does not touch.Not in this PR
Item 4 (Tier H). On
db48028f1a, lines 194–195 ofskills/objectstack-platform/SKILL.mdread:The proposed text, net zero lines:
Not carried here, as the card says: the
picklist-reference-unverifiedtrigger, and a danglingpicklistExtensions[].extend(folded into picklist kind: theosCLI compile / validate / lint path accepts*.picklist.ts,picklistsandpicklistExtensions, andos validaterefuses apicklistthat names no picklist (Scope 6 of #19518) #20825).Acceptance notes
The two starter README listings now name
src/picklists(commit90d55b1e13, added on contract review5930680827):Layoutbullet inpackages/create-objectstack/src/templates/blank/README.md, which ships into every new project;packages/create-objectstack/README.md.They ride this PR, not the Tier H item-4 PR, so two non-governed lines do not wait on a human approval.
Scaffold with repo distis red on this PR, and the PR is held until the next release publishes. This note was added by the seat.create-objectstack, then installs the project's framework from the npm registry. The template's^17.0.0range resolves to the published@objectstack/spec17.5.0, which predates thepicklistsstack key (addbbf0,.changeset/19518-picklist-kind.md, still unreleased). So the generated project'sdefineStackrefuses the key as unrecognized at the validate step.npm create objectstackrows and the end-to-end chain were measured on the repo build, not against the registry.pull_requestunder its path filter (packages/create-objectstack/**,docker/**and its own workflow file), and it is not red onmain.create-objectstackPR until that release.@objectstack/specchangelog already listsaddbbf0. Once it merges and publishes, a re-run of this job reads the new spec and the template is coherent with it.pm:blockedon chore: version packages #20639.On a project scaffolded by an earlier release, the not-wired hint prints
picklists: Object.values(picklists),, not the starter'sexportsOf(...)spelling. Both type-check once the barrel exports a list, which is the only state the hint is printed in.Generated by Claude Code