Skip to content

feat: Phase 4 (v1.0) + v1.1 — tier system, CLI, devtools, snapshot, tier warnings - #8

Merged
thorwhalen merged 2 commits into
mainfrom
feat/phase-4-v1.1
May 13, 2026
Merged

thorwhalen merged 2 commits into
mainfrom
feat/phase-4-v1.1

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

Summary

  • Phase 4 (v1.0): ships the tier-system enforcement (@acture/build-tier), acture compare-schemas CLI gating (@acture/cli), embeddable <Inspector /> (@acture/devtools), deprecationReason / internalToken on CommandRecord, and the [DEPRECATED — <reason>] banner in MCP / AI adapters. All 13 packages at 1.0.0.
  • v1.1 increment: adds enableTierWarnings(registry) in core (warn-once-per-@experimental-dispatch, env-var suppressible) and the acture snapshot CLI subcommand (registry config → JSON envelope). Core + cli bumped to 1.1.0.
  • 288 package tests + 36 example tests, all green. Greenfield builds clean with the Inspector wired in. npm pack --dry-run clean for every changed package.

What changed

  • New packages (3): @acture/build-tier, @acture/cli, @acture/devtools.
  • Core: added deprecationReason, internalToken fields on CommandRecord; added DispatchOptions { internalToken? } on dispatch; runtime rejects external @internal dispatches with internal_dispatch_denied; new enableTierWarnings(registry, options?) export.
  • Adapters: @acture/mcp and @acture/ai-vercel prepend [DEPRECATED — <reason>] when deprecationReason is set.
  • Greenfield example: App.tsx now mounts the Inspector behind a toggle button.
  • Docs: Phase 4 + v1.1 reflection docs, README + AGENTS.md refresh, new READMEs for the 3 new packages, skill updates for tier-system and command-record-shape, next_session.md rewritten as a v1.2 planning prompt.

Test plan

  • pnpm -r --filter "./packages/*" build clean 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-run clean for every changed package.
  • Hard-don'ts audit (per acture-hard-donts skill) — clean. See docs/phase-4-reflection.md §4 and docs/v1_1-reflection.md §"Hard-don'ts audit".
  • Fresh-agent test: a clean agent reads only README.md + AGENTS.md and adds a command to examples/greenfield/graph-editor/ without reading packages/. Deferred to v1.2 release-gate per docs/phase-4-reflection.md §5.

Acceptance criteria (Phase 4 §"Acceptance test") — all ✅

Receipts in docs/phase-4-reflection.md §"Phase 4 acceptance criteria — receipts".

# Criterion Status
1 @experimental → tier: 'experimental' at build time ✅
2 registry.toMCPServer() excludes experimental by default ✅
3 registry.toMCPServer({ tiers: [stable, experimental] }) includes it ✅
4 @deprecated description starts with [DEPRECATED — use X instead] ✅
5 @internal rejects external dispatch ✅
6 compare-schemas classifies removed command as MAJOR ✅
7 --allow-description-edits downgrades description-only to MINOR ✅
8 --fail-on major exits non-zero ✅
9 Devtools inspector renders in greenfield ✅
10 npm pack --dry-run clean at 1.0.0 (now 1.1.0 for core + cli) ✅

…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
thorwhalen merged commit 62caee6 into main May 13, 2026
1 check passed
@thorwhalen
thorwhalen deleted the feat/phase-4-v1.1 branch May 13, 2026 20:04
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant