feat: Phase 4 (v1.0) + v1.1 — tier system, CLI, devtools, snapshot, tier warnings - #8
Merged
Merged
Conversation
…ier warnings Phase 4 (v1.0) shipped: - @acture/build-tier: build-step plugin (esbuild + vite) that mirrors @stable / @experimental / @internal / @deprecated [reason] JSDoc tags on defineCommand calls into runtime tier metadata. Injects per-file Symbol('acture.internal') for @internal commands so cross-module dispatch is rejected by the registry. - @acture/cli: `acture compare-schemas` for CI gating with classifications per research-5 §6.1 (--fail-on, --allow-description-edits per-invocation, json/ text output, git-ref loading via `git show <ref>:.acture/snapshot.json`). - @acture/devtools: embeddable <Inspector /> React component and instrumentRegistry dispatch-log helper. Three tabs: commands (tier-filter + search), dispatch log (ring-buffered), when-clause evaluator. Mounted in the greenfield example behind a toggle. - Core gains deprecationReason + internalToken on CommandRecord (rule of three: mcp, ai-vercel, devtools / cli) and DispatchOptions { internalToken } on dispatch. Adapters (mcp, ai-vercel) now prepend [DEPRECATED — <reason>] when deprecationReason is set, falling back to bare [DEPRECATED]. - All 13 packages bumped to 1.0.0; npm pack --dry-run clean. v1.1 increment on top: - core: enableTierWarnings(registry, options?) — wraps dispatch to console.warn once per @experimental command on first dispatch. Suppressible via ACTURE_SUPPRESS_EXPERIMENTAL_WARNINGS=1 or { enabled: false }. Idempotent, WeakMap-keyed, disposer for test isolation. Per research-5 §7.3. - @acture/cli: `acture snapshot <config>` subcommand — load a registry config (default-exporting Registry, awaits Promise<Registry>) and emit the JSON envelope compare-schemas reads. --out, --tiers options. TypeScript-config hint points at Node --experimental-strip-types or tsx. - core and @acture/cli bumped to 1.1.0; other packages stay at 1.0.0. Tests: 288 package tests pass (was 185 at Phase 3 end; +103 across 6 new test files in build-tier, cli, devtools, plus tier + tier-warnings + result tests in core). 36 example tests still green. Greenfield example builds clean with the Inspector wired in. Docs: - docs/phase-4-reflection.md and docs/v1_1-reflection.md document the gates. - docs/implementation_plan.md and docs/v1_plan.md mark Phase 4 DONE. - docs/next_session.md rewritten as a v1.2 planning prompt. - README.md and AGENTS.md updated for the 13-package v1.1 state. - New READMEs for @acture/cli, @acture/build-tier, @acture/devtools. - Skills updated: acture-tier-system (compare-schemas + snapshot + tier warnings sections) and acture-command-record-shape (deprecationReason, internalToken now in canonical spec).
CI's tsc caught that err()'s return type is Result<never> (the discriminated union, not just the failure branch), so r.error needs an r.ok === false narrow before access. Local typecheck passes after the fix.
thorwhalen
added a commit
that referenced
this pull request
May 15, 2026
Two backlog increments shipped together (user delegated the scope call: "fix what's fixable autonomously"). Full write-up in docs/v1_9-reflection.md. Part A — codemods README/CLI polish + AI-codemod-recipe doc (closes the docs/backlog/codemods-polish-and-tier-mirror.md file): - acture-codemods CLI: the ambiguous "No files matched" error is now three distinct messages (no target / path doesn't exist / no TS files). --help gained Modes (--list/--manifest) and Exit-codes sections. +3 CLI disambiguation tests (52 → 55). minor changeset. - acture-codemods README: full rewrite. Documents every --option key for all five codemods, --manifest vs --list, --files-from, exit codes, from-a-clone invocation. (The npx-404 finding was already resolved by reality — the package is published.) - docs/ai-codemod-recipe.md: research-4 #8. The Codemod contract, the conservative-codemod discipline, a ts-morph skeleton, a prompt recipe. - .d.ts tier mirror: deliberately NOT built. Zero concrete type-level tier consumers; rule-of-three gated. Rationale documented in the acture-build-tier README. - .changeset/README.md: fixed — described a dropped fixed group and an obsolete 0.x quirk. Part B — greenfield agent-track skills (per-step skills below the acture-greenfield foundation): - acture-greenfield-state-model: Step 1 in detail (the four hard constraints on the state shape, deterministic id generation, StateAdapter seam). - acture-greenfield-bootstrap: concrete file-by-file walk-through of the foundation's four-step sequence, grounded in the graph-editor worked example. - acture-greenfield foundation wired to point at both sub-skills. 422 package tests + 41 example tests green. Hard-don'ts audit clean. Pending changeset: acture-codemods minor (no cascade).
thorwhalen
added a commit
that referenced
this pull request
Aug 26, 2026
Review repair. The PR claimed a 5.x matrix was impossible; it is not, and
the gap it left was demonstrable.
`packages/forms-rjsf` publishes `@rjsf` peers at `^5.20.0 || ^6.0.0` but
nothing automated exercised the 5.x half, so a value that is legal on 6.x
and illegal on 5.x shipped green. Reproduced: change `liveValidate={false}`
to `liveValidate="onChange"` (6.x widened the type to
`'onChange' | 'onBlur' | boolean`) and typecheck + all 17 tests + CI stay
green, while the same source against real @rjsf/core@5.24.13 declarations
gives `TS2769 ... Type 'string' is not assignable to type
'boolean | undefined'`. A consumer the peer range invites gets a hard
compile error nothing here could catch.
The impossibility claim was overstated. pnpm matching peers by package name
rules out two alias trees inside ONE install; a CI matrix is two INSTALLS.
`scripts/pin-rjsf-5x.mjs` rewrites the three `@rjsf` devDependencies to the
5.x clause READ OFF the declared peer range, drops the 6.x-only
`@rjsf/shadcn` and its smoke test, and a plain `pnpm install` resolves a
plain 5.x tree — no aliases, no packageExtensions. The new `rjsf5` CI job
runs it, asserts a real 5.x tree resolved (`--verify` — a job silently
testing the wrong major is this bug again), then typechecks and tests.
Verified end to end in a throwaway worktree: @rjsf/core@5.24.13 resolved,
`tsc --noEmit` clean, `rjsf-form.test.tsx` 4/4 — and red on the
`liveValidate` mutation above. The script no-ops if the range ever narrows
to 6-only, so the matrix retires with the promise it checks.
Three guards added, one strengthened; each mutation-tested:
- Hard-don't #8 had no test. Promoting `@rjsf/shadcn` from `devDependencies`
to `dependencies` — the obvious "make the theme just work" edit — pulled a
UI kit into every consumer's install with nothing red. Now
`@rjsf/shadcn` is asserted absent from `dependencies` and
`peerDependencies`, and the package is asserted to declare no runtime
dependencies at all. Mutation: 2 failed | 15 passed.
- `peerDependencyRules.ignoreMissing: [tailwindcss]` disables missing-peer
detection workspace-wide and permanently (pnpm honours no scoped form —
`tailwindcss-animate>tailwindcss` and `@rjsf/shadcn>tailwindcss` were both
tried). Compensating check: no workspace package may declare a
`tailwindcss` peer while the suppression is in place, and the block
retires itself if the rule is removed. Both halves mutation-tested.
- "submits through the theme" passed on an adapter that ignored the injected
`form` — @rjsf/core's form submits identically, so only one of the two
theme tests guarded the feature. Both now assert theme identity through
one `expectThemedInput` helper keyed on @rjsf/core's `form-control` class
rather than on a Tailwind class that can move between releases. Mutation
(`form ?? Form` -> `Form`): 1 failed -> 2 failed.
`FormImpl` is recomputed per render, so a non-stable `form` prop remounts
the form and discards in-progress input. Memoizing cannot fix it — the
changed identity arrives as the prop, and React reconciles by element type —
so the requirement is documented on the prop, in the README and in the
palette-design skill instead.
README, test docstrings and the skill no longer state the impossibility;
they say both majors run in CI, which is now true.
forms-rjsf 11 -> 17 tests. Workspace: 601 passed across 21 packages,
`pnpm build` / `pnpm typecheck` / `pnpm test` all exit 0, `pnpm peers check`
clean.
Claude-Session: https://claude.ai/code/session_0191cTtmnhFbC2ZKNtgxHXye
thorwhalen
added a commit
that referenced
this pull request
Aug 26, 2026
* feat(forms-rjsf): accept @rjsf 6.x and make the theme injectable `@rjsf/shadcn` — the theme every frontend in this ecosystem wants — is published on the 6.x line only, peering `@rjsf/core@^6`. The adapter's peer range was `^5.20.0`, so the two could not be installed together: an npm peer conflict at install time, before a line of form code got written. The adapter was unusable by exactly the consumer it was written for. Peers move to `^5.20.0 || ^6.0.0` rather than to 6-only. No adapter code was needed to span the two majors, because every API it touches is shape-identical across them: `@rjsf/core`'s default export and `FormProps`, `@rjsf/validator-ajv8`'s default export, and the `schema` / `formData` / `validator` / `liveValidate` / `onSubmit` / children props. The one signature that did change widened rather than moved — `liveValidate` went from `boolean` to `'onChange' | 'onBlur' | boolean`, so the `false` we pass is still valid. This follows the convention set by #55, which declared `ai@^5 || ^6 || ^7` while dev-testing only the newest. Reaching the shadcn theme needed a seam that did not exist. The README already told readers to "pass your own `Form` from the themed package", and there was no prop that accepted one — the core `Form` was imported and rendered directly. `<RjsfForm />` now takes an optional `form` (`ComponentType<FormProps>`, the type every RJSF theme's default export already has), defaulting to `@rjsf/core`'s. That is hard-don't #8's prescribed slot API: `@rjsf/shadcn` is a devDependency for the smoke test only, never a runtime or peer dependency, so no UI kit is bundled. Verification, since a peer range is a promise: - 6.x is what the dev tree installs and CI tests. New smoke test renders and submits through the real `@rjsf/shadcn` Form and asserts the theme actually rendered (its Tailwind classes, not `@rjsf/core`'s bare `form-control`). - 5.x was exercised by hand, once: `@rjsf/{core,utils,validator-ajv8}` pinned to `^5.24.0`, `pnpm install`, `@rjsf/core@5.24.13` resolved — `rjsf-form.test.tsx` 4/4 green and `tsc --noEmit` clean over the source. It is not re-checked per commit, and the tests say so. - It cannot be, in one dev tree: pnpm matches peers by package NAME, so an `npm:`-aliased 5.x install silently binds the 6.x `@rjsf/utils` (measured — `@rjsf+core@5.24.13_@rjsf+utils@6.8.0_...` appears in `node_modules/.pnpm`) and the "5.x matrix" would be 6.x wearing a 5.x label. `packageExtensions`, the documented fix, is not honoured by pnpm 11.1.1 from either `pnpm-workspace.yaml` or `package.json#pnpm`. - A guard pins the declared range against the major actually installed and against the major `@rjsf/shadcn` peers on, so this drift has to be deliberate next time. Mutation-tested three ways (narrow the peer range / ignore the injected form / drift the declared range) — each turns it red. `peerDependencyRules.ignoreMissing: [tailwindcss]` keeps `pnpm peers check` clean: `@rjsf/shadcn` pulls `tailwindcss-animate`, whose peer range is the malformed `">=3.0.0 || insiders"` and is therefore unmet whatever is installed. forms-rjsf 3 -> 11 tests. Workspace: 595 passed across 21 packages, build + typecheck clean. `minor` changeset. Closes #57 Claude-Session: https://claude.ai/code/session_0191cTtmnhFbC2ZKNtgxHXye * fix(forms-rjsf): actually test the 5.x half of the declared peer range Review repair. The PR claimed a 5.x matrix was impossible; it is not, and the gap it left was demonstrable. `packages/forms-rjsf` publishes `@rjsf` peers at `^5.20.0 || ^6.0.0` but nothing automated exercised the 5.x half, so a value that is legal on 6.x and illegal on 5.x shipped green. Reproduced: change `liveValidate={false}` to `liveValidate="onChange"` (6.x widened the type to `'onChange' | 'onBlur' | boolean`) and typecheck + all 17 tests + CI stay green, while the same source against real @rjsf/core@5.24.13 declarations gives `TS2769 ... Type 'string' is not assignable to type 'boolean | undefined'`. A consumer the peer range invites gets a hard compile error nothing here could catch. The impossibility claim was overstated. pnpm matching peers by package name rules out two alias trees inside ONE install; a CI matrix is two INSTALLS. `scripts/pin-rjsf-5x.mjs` rewrites the three `@rjsf` devDependencies to the 5.x clause READ OFF the declared peer range, drops the 6.x-only `@rjsf/shadcn` and its smoke test, and a plain `pnpm install` resolves a plain 5.x tree — no aliases, no packageExtensions. The new `rjsf5` CI job runs it, asserts a real 5.x tree resolved (`--verify` — a job silently testing the wrong major is this bug again), then typechecks and tests. Verified end to end in a throwaway worktree: @rjsf/core@5.24.13 resolved, `tsc --noEmit` clean, `rjsf-form.test.tsx` 4/4 — and red on the `liveValidate` mutation above. The script no-ops if the range ever narrows to 6-only, so the matrix retires with the promise it checks. Three guards added, one strengthened; each mutation-tested: - Hard-don't #8 had no test. Promoting `@rjsf/shadcn` from `devDependencies` to `dependencies` — the obvious "make the theme just work" edit — pulled a UI kit into every consumer's install with nothing red. Now `@rjsf/shadcn` is asserted absent from `dependencies` and `peerDependencies`, and the package is asserted to declare no runtime dependencies at all. Mutation: 2 failed | 15 passed. - `peerDependencyRules.ignoreMissing: [tailwindcss]` disables missing-peer detection workspace-wide and permanently (pnpm honours no scoped form — `tailwindcss-animate>tailwindcss` and `@rjsf/shadcn>tailwindcss` were both tried). Compensating check: no workspace package may declare a `tailwindcss` peer while the suppression is in place, and the block retires itself if the rule is removed. Both halves mutation-tested. - "submits through the theme" passed on an adapter that ignored the injected `form` — @rjsf/core's form submits identically, so only one of the two theme tests guarded the feature. Both now assert theme identity through one `expectThemedInput` helper keyed on @rjsf/core's `form-control` class rather than on a Tailwind class that can move between releases. Mutation (`form ?? Form` -> `Form`): 1 failed -> 2 failed. `FormImpl` is recomputed per render, so a non-stable `form` prop remounts the form and discards in-progress input. Memoizing cannot fix it — the changed identity arrives as the prop, and React reconciles by element type — so the requirement is documented on the prop, in the README and in the palette-design skill instead. README, test docstrings and the skill no longer state the impossibility; they say both majors run in CI, which is now true. forms-rjsf 11 -> 17 tests. Workspace: 601 passed across 21 packages, `pnpm build` / `pnpm typecheck` / `pnpm test` all exit 0, `pnpm peers check` clean. Claude-Session: https://claude.ai/code/session_0191cTtmnhFbC2ZKNtgxHXye
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@acture/build-tier),acture compare-schemasCLI gating (@acture/cli), embeddable<Inspector />(@acture/devtools),deprecationReason/internalTokenonCommandRecord, and the[DEPRECATED — <reason>]banner in MCP / AI adapters. All 13 packages at 1.0.0.enableTierWarnings(registry)in core (warn-once-per-@experimental-dispatch, env-var suppressible) and theacture snapshotCLI subcommand (registry config → JSON envelope). Core + cli bumped to 1.1.0.npm pack --dry-runclean for every changed package.What changed
@acture/build-tier,@acture/cli,@acture/devtools.deprecationReason,internalTokenfields onCommandRecord; addedDispatchOptions { internalToken? }ondispatch; runtime rejects external@internaldispatches withinternal_dispatch_denied; newenableTierWarnings(registry, options?)export.@acture/mcpand@acture/ai-vercelprepend[DEPRECATED — <reason>]whendeprecationReasonis set.App.tsxnow mounts the Inspector behind a toggle button.next_session.mdrewritten as a v1.2 planning prompt.Test plan
pnpm -r --filter "./packages/*" buildclean for all 13 packages.pnpm test— 288 package tests pass.pnpm --filter @acture/example-graph-editor build && pnpm --filter @acture/example-graph-editor test— example builds and 7 integration tests pass.npm pack --dry-runclean for every changed package.acture-hard-dontsskill) — clean. Seedocs/phase-4-reflection.md§4 anddocs/v1_1-reflection.md§"Hard-don'ts audit".README.md+AGENTS.mdand adds a command toexamples/greenfield/graph-editor/without readingpackages/. Deferred to v1.2 release-gate perdocs/phase-4-reflection.md§5.Acceptance criteria (Phase 4 §"Acceptance test") — all ✅
Receipts in
docs/phase-4-reflection.md§"Phase 4 acceptance criteria — receipts".@experimental→tier: 'experimental'at build timeregistry.toMCPServer()excludes experimental by defaultregistry.toMCPServer({ tiers: [stable, experimental] })includes it@deprecateddescription starts with[DEPRECATED — use X instead]@internalrejects external dispatchcompare-schemasclassifies removed command as MAJOR--allow-description-editsdowngrades description-only to MINOR--fail-on majorexits non-zeronpm pack --dry-runclean at 1.0.0 (now 1.1.0 for core + cli)