Skip to content

feat!: plugin marketplace + format/style decoupling (v0.2.1) - #1

Merged
r-bart merged 17 commits into
mainfrom
develop
May 4, 2026
Merged

r-bart merged 17 commits into
mainfrom
develop

Conversation

@r-bart

@r-bart r-bart commented May 4, 2026

Copy link
Copy Markdown
Owner

Summary

First real release of scribetronic. Two substantive changes vs main:

  1. Plugin marketplace distribution. Skills now ship from r-bart/scribetronic-plugin and load at runtime via /scribetronic:<name>. CLI registers the marketplace in .claude/settings.json and stops copying SKILL.md into the project. New commands: update, doctor, uninstall. Workflow hooks: SessionStart (today's editorial slot) + Stop (slop-check reminder for touched drafts).

  2. Skill content decoupled from style. Every long-form-*, short-form-*, editing-pass, and ai-slop-check SKILL.md is now format-only — no embedded author names, personal references, or "-style" labels. writing-style/SKILL.md ships as a personalization template. Result: every format skill is reusable by any writer in any niche without editing.

Plus a critical fix that came from real-world smoke testing: SKILL.md frontmatter normalized to the spec-allowed keys only (name, description, allowed-tools, argument-hint). Prior to this, 18 of 22 skills had non-spec keys and were silently rejected by Claude Code's skill loader — only 4 were actually registering as slash commands.

Test plan

  • npm run typecheck && npm run lint && npm test && npm run build — all green
  • 78/78 tests pass (was 49). Coverage 92.7% statements, 82.55% branches.
  • E2E smoke: init writes .claude/settings.json correctly, no .claude/skills/ directory created, scribetronic/ scaffolded
  • doctor reports the 6 expected checks (5 pass, 1 fails on missing writing-style — by design)
  • npm pack --dry-run: 65.3 KB tarball, 48 files
  • Plugin marketplace install verified end-to-end in Claude Code: /plugin marketplace add r-bart/scribetronic-plugin + /plugin install scribetronic@scribetronic works; all 22 /scribetronic:* slash commands register after the v0.2.1 frontmatter fix
  • Manual review confirmed no proper-noun references in any SKILL.md
  • CI matrix passes on Node 18/20/22

Companion changes

  • Plugin repo r-bart/scribetronic-plugin@d316c93 synced and pushed (lockstep with this PR's 0.2.1)
  • New rules: .claude/rules/skills.md documents the two hard constraints (frontmatter spec compliance, format ↔ style decoupling). Detection is manual code review — no list of forbidden names is checked into the repo, intentionally.

🤖 Generated with Claude Code

r-bart added 7 commits May 3, 2026 21:51
Decouples scaffolded user content from the devtronic `thoughts/` convention
(reserved for internal dev notes/plans/design). The scaffolded directory is
now top-level `scribetronic/` under the user's project root.

BREAKING CHANGE: existing v0.1.0 installs must run:
  mv thoughts/writing scribetronic
  rmdir thoughts 2>/dev/null

- Move templates/project/thoughts/writing/ -> templates/project/scribetronic/
- Update all path strings in skills (agenda, write, write-publish, style-extract)
- Update analyzer: ProjectAnalysis.hasWritingDir -> hasScribetronicDir
- Update tests, docs, README, ARCHITECTURE, tutorials, CHANGELOG
Adds 20 new tests across four files; raises CLI test count from 27 to 47.

- project.test.ts (new) — direct unit tests for analyzeProject covering
  empty dir, partial installs, all three orchestrator names, and a
  non-orchestrator skill correctly NOT triggering hasScribetronic
- info.test.ts (new) — not-found exits 1, frontmatter+body printing,
  unknown frontmatter keys surfaced, list-valued fields, minimal skill
- list.test.ts (new) — empty registry hint, category grouping, 80-char
  truncation, "(no description)" fallback, array-description joining
- init.test.ts — adds: missing target path exits 1, missing templates
  warns and returns, idempotent re-run on an existing install
- style.test.ts — adds: missing bundled seed exits 1
Closes the loop between drafted and published copy. The skill diffs
(draft, published) pairs from the user's editorial history and proposes
concrete deltas to writing-style/SKILL.md. Hard rule: no delta without
≥2 supporting pairs shown side-by-side. Never auto-rewrites the guide
— output goes to scribetronic/style/refinements/<date>.md for manual
review and merge.

- New skill at templates/.../skills/style-refine/SKILL.md (5-phase
  workflow: inventory → diff → classify → synthesize → output)
- writing-style/SKILL.md gains a "Refining this guide" footer pointing
  at the new skill
- agenda + write-publish print a one-line nudge after 3+ pieces
  published since the last applied refinement
- docs/skills.md updated (counts 21→22, Shared 4→5, new entry, quick
  lookup row)
- CHANGELOG [Unreleased]/Added entry
- bundled-skills.test.ts verifies the skill is shipped and registered
  under the Shared category via the real templates dir

No CLI command added — auto-discovered by the existing skill registry.
- Add .github/workflows/ci.yml: typecheck, lint, test, build, pack-dry-run on Node 18/20/22 for push and PR.
- Add .github/workflows/release.yml: tag v*.*.* triggers npm publish with provenance attestation; verifies tag matches package.json version; auto-creates GitHub release.
- Add prepublishOnly script and publishConfig (access: public, provenance: true) so any publish runs the full quality gate.
- Bump version to 0.1.1 — first npm-published release; promote 0.1.1 in CHANGELOG with style-refine + scribetronic/ rename, leave [Unreleased] holding the publish-infra changes for the next cut.
- Add docs/releasing.md maintainer runbook (pre-flight, tag, post-publish, rollback, failure modes).
- Update README install path to npm i -g scribetronic; rework Roadmap (v0.1 = published, v1.0 = stability).
- Add planning doc under thoughts/plans/.

Verified locally: typecheck + lint + 49 tests + build all green; tarball install in /tmp scaffolds 44 files including 21 skills and full scribetronic/ tree.
BREAKING CHANGE: `scribetronic init` no longer copies SKILL.md files into
the project. Skills load at runtime from r-bart/scribetronic-plugin as
/scribetronic:<name>. Existing v0.1.x installs: run `scribetronic update`
then delete the duplicated .claude/skills/ entries — see CHANGELOG for the
exact list.

CLI changes
- New commands: update, doctor, uninstall
- init now writes .claude/settings.json registering the marketplace
  (preserves pre-existing keys)
- Bump version to 0.2.0
- Add @vitest/coverage-v8 + npm run test:coverage script

New modules
- src/data/plugin.ts            — marketplace constants
- src/utils/settings.ts         — settings.json read/write + register/unregister
- src/commands/update.ts        — refresh registration
- src/commands/doctor.ts        — 6-check health verification
- src/commands/uninstall.ts     — disable plugin (preserves user content)
- src/generators/templateCopier.ts — adds exclude option

Tests
- 29 new tests across settings/doctor/update/uninstall
- init.test.ts updated: no longer asserts skill copy, asserts settings write
- Total: 78 tests / 12 files (was 49 / 8). Coverage 92.7% statements.

Docs
- New docs/plugin-mode.md (architecture, hooks, migration)
- README quick-start: 3 install options (CLI / plugin direct / global)
- cli-reference.md: documents update/doctor/uninstall, removes 'out of scope' section
- CHANGELOG: 0.2.0 entry with breaking change + migration recipe
- ARCHITECTURE: notes templates/claude-code/.claude/skills/ is now source-of-truth, not copied
- 5 short-form skill descriptions: added 'Use when…' clauses for the router (M4 from audit)
- CLAUDE.md / AGENTS.md / philosophy.md: skill count + architecture sync

Tooling
- scripts/sync-plugin-repo.sh — mirrors bundled skills to the plugin repo
  and bumps plugin.json:version on release
…style (v0.2.1)

BREAKING (content): every long-form-*, short-form-*, editing-pass, and
ai-slop-check SKILL.md is now format-only. Author names (Welsh, Koe,
Moretti, Röhl), personal references (Roberto, MakerOps, MW#N), and
"X-style" labels have been removed. Voice and references live exclusively
in writing-style/SKILL.md, which ships as a fillable template. v0.2.0 was
live for hours with no users, so practical migration impact is zero.

Fixes
- All 22 skills now load correctly in Claude Code. Previously 18 of 22
  had non-spec frontmatter keys (inherits, formats, cadence, length_target,
  applies_to, input, format, quota, status, language, target_voice,
  sources, last_updated) that the skills loader silently rejects. Only
  4 skills were registering as slash commands. Frontmatter now contains
  only the spec-allowed keys (name, description); operational metadata
  moved to a `## Metadata` body section.

Changes
- writing-style/SKILL.md rewritten as a personalization template:
  new section 0 "Reference sources" where the user lists their own
  authors/posts; <your-name> placeholders throughout; "How to fill this
  file" instructions; generalized native-language interference watch
  (was Spanish-specific).
- 18 SKILL.md files normalized: frontmatter spec-compliant, body adds
  "Voice and tone come from writing-style/SKILL.md" pointer, generic
  example shapes labeled as such replace named-author quotes.
- editing-pass + short-form-voice-adjustments: "Welsh-style punctuation
  paragraph" → "staccato 2-3 word paragraph" (format description, no
  author attached).
- ai-slop-check: "tightened to Roberto's voice" → reads writing-style.md
  for user-specific tics.

Tooling
- scripts/normalize-skill-frontmatter.mjs — extracts non-spec keys from
  any SKILL.md frontmatter and migrates them to a `## Metadata` body
  section. Idempotent. Run after editing or adding skills.
- .claude/rules/skills.md — new in-repo rule file documenting the two
  hard constraints (frontmatter spec compliance, format ↔ style
  decoupling) with grep checks for pre-commit verification.

Bump CLI 0.2.0 → 0.2.1, plugin lockstep 0.2.0 → 0.2.1.

Tests: 78/78 pass. Coverage 92.7% statements.
…ls rule

The previous commit's CLAUDE.md and .claude/rules/skills.md sections on
"format ↔ style decoupling" listed specific names as illustrative examples
of what NOT to embed — which itself violated the rule (the docs embedded
the names they forbade).

Changes:
- CLAUDE.md: forbidden-list rewritten in fully generic terms
  ("specific author or creator names", "the maintainer's own personal
  identifiers", "<author>-style" placeholders).
- .claude/rules/skills.md: same generalization. The grep regression scan
  now reads its name list from a separate .skills-regression-names file,
  marked as an append-only detective ledger — explicitly NOT a
  prescriptive whitelist of "the only forbidden names". Any name-shaped
  reference is forbidden; the ledger only catches reintroductions of
  patterns already removed.
- New .claude/rules/.skills-regression-names: hidden file, single-line
  regex of historically-removed names. Maintainers append to it when
  catching new contamination during code review.

Skills themselves remain clean (verified by the regression scan).
@r-bart r-bart changed the title feat!: distribute skills via Claude Code plugin marketplace (v0.2.0) feat!: plugin marketplace + format/style decoupling (v0.2.1) May 4, 2026
r-bart added 10 commits May 4, 2026 09:37
The .skills-regression-names file kept the very names we were trying to
remove from the repo (Welsh, Koe, Moretti, Röhl, Roberto, MakerOps, MW#,
sociilabs) checked into git just to power a grep. That defeats the
purpose — those names are now visible to every contributor, indexed by
GitHub search, and pushed to a public mirror.

Trade-off: lose the automated regression net. Gain: the repo is fully
clean of those references.

The skills.md rule file now describes detection as "manual review by
the reviewer" with a coarse heuristic (two-capitalized-words, @-handles,
"Name-style"/"Name's" possessives) for opt-in use. False positives
expected — use as a starting point, not a gate.

CLAUDE.md updated to match.
… idea backlogs

Scan caught three leftover hits outside the SKILL.md files (which were
already clean):

- packages/cli/templates/claude-code/.claude/skills/README.md:
  "Roberto's voice base" → "User's voice base, shipped as a personalization template"
  Also added the missing style-refine entry, fixed 21 → 22 skill count.
- packages/cli/templates/project/scribetronic/README.md:
  "Koe-style" / "Moretti-style" labels → format descriptions only.
- packages/cli/templates/project/scribetronic/ideas/{how-to,hot-take}.md:
  same name-strip in the description frontmatter.

The plugin repo gets re-synced to pick up nothing new in skills/ but
correctness is verified end-to-end.
The previous v0.2.x code wrote the user's voice override to
.claude/skills/writing-style/SKILL.md, which was orphan: skills loaded
from the plugin marketplace cache, never from that path. User edits
had no effect on drafts.

Path migration
- scribetronic style → writes to scribetronic/style/writing-style.md
  (project root, alongside calendar/, ideas/, publish-config.yaml)
- doctor → checks the new path
- init → scaffolds scribetronic/style/README.md with seeding instructions
- bundled writing-style/SKILL.md template → header documents the
  override mechanism and resolution order
- write, editing-pass, ai-slop-check, short-form-voice-adjustments →
  added "Voice resolution" section with explicit precedence:
  1. scribetronic/style/writing-style.md (project) — wins if present
  2. writing-style/SKILL.md (bundled template) — fallback
- style-refine → reads/writes the new path
- style-extract → already used the right path; no change

Project scaffold
- new templates/project/scribetronic/style/README.md with usage:
  scribetronic style | --reset | /scribetronic:style-extract |
  /scribetronic:style-refine

Tests
- style.test.ts and doctor.test.ts updated to assert new paths
- 78/78 still pass
- e2e smoke verified end-to-end: init creates style/, scribetronic style
  populates writing-style.md there, doctor reports 6/6 pass

Docs
- CHANGELOG entry under [0.2.1] documenting the path migration
- docs/cli-reference.md style command paths updated
- docs/plugin-mode.md gains a "Voice override" section explaining the
  override mechanism and resolution order

Migration: v0.2.0 was live for hours with no users. If you already ran
scribetronic style on v0.2.0 and edited the old path, copy that content
to the new location and delete the old file.
…y UX

Phase 1/5 of the v0.2.1 agent-friendly CLI plan.

- utils/output.ts: shared router. data() goes to stdout (JSON-serialized
  in JSON mode, pre-formatted string in human mode). note/success/warn/
  error all go to stderr in human mode. In JSON mode: data() emits one
  JSON line; note/success/warn are silenced; error() emits a structured
  envelope on stdout (gh/kubectl convention). NO_COLOR and TTY detection
  honored — chalk level is forced to 0 when piped or NO_COLOR is set.
- utils/exit.ts: ExitCode constants — Success(0), Unexpected(1),
  Usage(2), State(3). Used to give agents a stable contract distinguishing
  bad-args from config-drift from internal-bug.

No commands wired yet; that lands in Phase 2/3/4. Quality: typecheck +
lint pass.
…exit codes

Plan: thoughts/plans/2026-05-04_v0.2.1-agent-friendly-cli.md

Implements all 5 phases of the post-publish CLI audit (Tier 1 + 2 fixes
from the "Designing CLI for Agents" article).

What changed
- Read-only commands (list, info, doctor) accept --json and emit one
  newline-terminated JSON line on stdout. All chrome (progress, hints,
  decoration) is suppressed in JSON mode.
- All commands route chrome to stderr; stdout is reserved for data.
  scribetronic init >/dev/null is silent on success but errors still
  reach stderr.
- NO_COLOR=1 and non-TTY stdout disable color and clack chrome globally.
- Granular exit codes: 0 success, 1 unexpected, 2 usage error
  (bad args/path, unknown skill), 3 state error (plugin not registered,
  doctor checks failed). Documented in --help epilog.
- scribetronic style --reset accepts --yes (or SCRIBETRONIC_YES=1)
  to bypass the confirm prompt. Required for non-interactive shells.
- scribetronic style (existing target, non-TTY) prints the path on
  stdout so an agent can capture it: path=$(scribetronic style)
- --help epilog lists agent-canonical examples + exit code table +
  JSON output contract.

How
- New utils/output.ts: shared router enforcing the stdout/stderr contract.
  data() → stdout (JSON or pre-formatted string). note/success/warn/error
  → stderr (or suppressed in JSON; error envelope on stdout in JSON).
  Honors NO_COLOR and TTY detection automatically.
- New utils/exit.ts: ExitCode constants for the contract above.
- index.ts wires --json per command, configures output before each action,
  installs the help epilog, and re-routes the top-level catch.
- 7 commands refactored to use out.* exclusively (no more raw
  console.log/error in command bodies).

Tests
- 78 → 106 tests across 14 files (28 new).
- New helpers/captureStreams.ts: deterministic stream-capture helper used
  by every command test (replaces console.log spies).
- New output.test.ts: 11 unit tests covering JSON mode, human mode,
  TTY/NO_COLOR detection, error envelope shape.
- New json-mode.test.ts: 7 spawn-based E2E tests asserting actual
  stdout/stderr/exit-code from the built binary with NO_COLOR=1.
- All existing command tests migrated from console.log → captureStreams.
- Coverage: 91.26% statements, 78.74% branches, 95.74% functions
  (target was ≥90% statements per plan's done criteria).

Docs
- CHANGELOG [0.2.1]: new "Agent-friendly CLI" subsection.
- docs/cli-reference.md: new "Agent-friendly modes" section at the end
  with --json schemas, global exit-code contract, environment variables,
  stdout/stderr discipline table, and 6 agent-canonical examples.
  Per-command exit code tables updated to match the new contract.

Local-only, no push, no tag.
…ttings

Addresses the two minor warnings from the v0.2.1 post-review audit.

M1 — out.line() helper for stderr rendering loops
- New utils/output.ts:line(msg) writes to stderr, suppressed in JSON
  mode, prints in non-TTY (unlike out.note which is silent there).
  No prefix, no color logic — caller controls chalk styling.
- list.ts, info.ts, doctor.ts: replaced 30 raw process.stderr.write
  calls with out.line. Now every stderr write in commands/ goes
  through the output module, so future config knobs (NO_COLOR rules,
  log levels, JSON envelope changes) apply uniformly.

M2 — utils/settings.ts migrated to node:fs/promises
- Was using readFileSync/writeFileSync/existsSync/mkdirSync, which
  CLAUDE.md explicitly forbids in hot paths. Settings.ts is called by
  all four mutation/inspection commands.
- All 5 exports now async: readClaudeSettings, writeClaudeSettings,
  registerGitHubPlugin, unregisterPlugin, isPluginRegistered.
- Cascaded await to call sites in commands/{init,update,uninstall,doctor}.ts
  and tests/{settings,update,uninstall,doctor}.test.ts.
- Try/catch on read collapses both ENOENT and JSON parse errors
  to {} — same behaviour as before, but no more existsSync race.

Tests: 106/106 still pass. Quality gates clean.
E2E smoke: init+doctor+list --json all work end-to-end.

Local-only. No push.
… review

New shared skill at packages/cli/templates/claude-code/.claude/skills/review/.
Bundle count 22 → 23.

What it does
- Single-message dispatch of N Task subagents, one per focus.
  Default focuses: voice, structure, slop, hook, closer.
  Opt-in: factual (with --with-evidence; reads notes.md as ground truth).
- Each subagent receives the resolved writing-style override + a focus-
  specific brief + the draft body, and returns a markdown findings table
  with severity (HIGH/MEDIUM/LOW), location (line/section), and the
  specific issue.
- Orchestrator merges all tables into one severity-grouped report on
  stdout. Per-focus dispatch progress on stderr (silenced with --quiet).
- Verdict line: SHIP (0 HIGH, ≤2 MEDIUM) or REVISE.
- Models: haiku for pattern-matching focuses; bumps to sonnet when
  factual is enabled.

Why
- Wall-time ~10s for 5 parallel subagents vs ~50s sequential
  editing-pass + ai-slop-check, with strictly more coverage (structure,
  hook, closer, factual all new).
- Designed for mid-draft iteration: pause every ~200 words, get
  feedback, keep writing.
- From v0.3.0 onward, /write Phase 4 will call /review by default
  (opt-out via --no-review for legacy sequential flow).

Bundle changes
- +packages/cli/templates/claude-code/.claude/skills/review/SKILL.md
- Skill count: 22 → 23 across CLAUDE.md, README.md, docs/skills.md,
  docs/plugin-mode.md, docs/philosophy.md, docs/cli-reference.md,
  templates/claude-code/.claude/skills/README.md.
- Shared utilities count: 5 → 6 (writing-style, editing-pass,
  ai-slop-check, style-extract, style-refine, review).
- README.md "Shared utilities (5)" → "(6)" + added /review row.
- docs/skills.md "Shared (5)" → "(6)" + added /review section.
- skills/README.md added review entry.

Plugin repo synced (23 SKILL.md mirrored). plugin.json:version stays
0.2.1 — this skill ships as part of the next minor (0.3.0). No package
version bump in this commit; entry under CHANGELOG [Unreleased].

Tests: 106/106 still pass (skill is markdown, no TS to test).
E2E smoke: scribetronic list --json reports 23.

Local commit. Push pending user direction (per current release flow).
…aged)

The /scribetronic:review skill gains a "delta mode" for the most common
mid-draft case: "I just wrote 1-3 paragraphs, are they OK?".

What's new
- Three new flags on /scribetronic:review (mutually exclusive):
  - --since-last: diff vs the snapshot stored in
    .scribetronic/snapshots/<basename>.md from the previous /review run.
    First invocation per file snapshots silently and falls back to full
    mode (with a note).
  - --since <revspec>: git diff <revspec> -- <draft>. Examples: HEAD,
    HEAD~3, main. Errors if not in a git repo.
  - --since-staged: git diff --staged -- <draft>.
- New focus: continuity (delta-only). Compares the new lines against
  1-2 paragraphs of surrounding context. Flags tone shifts, tense
  breaks, orphan references the BEFORE didn't introduce. Silently
  dropped if requested in full mode.
- Defaults adjusted by mode:
  - Full mode: voice, structure, slop, hook, closer  (5 focuses)
  - Delta mode: voice, slop, continuity              (3 focuses)
  - --with-evidence adds factual to either mode.
- Subagent prompt skeleton documented for delta mode: AFTER block (the
  added lines, with line numbers in the current file) is what's
  reviewed; BEFORE block (1-2 paragraphs of unchanged context above and
  below) is provided as anchor only — subagents are instructed not to
  flag issues in BEFORE.
- Snapshot is only updated on a successful run (so a failed review
  doesn't lose its reference point).

Why
- Wall time: ~5s for delta mode (3 subagents) vs ~10s for full mode (5).
- Token cost: ~80% lower per delta review (only the changed hunks +
  small context, not the full draft).
- Cognitive fit: the dominant mid-draft pattern is "wrote a bit, want
  feedback, keep going". Forcing a full re-review of 1500 words after
  every 200-word addition is overkill.

Project template
- New packages/cli/templates/project/scribetronic/.gitignore ignoring
  .scribetronic/ (the local state dir used by --since-last). Created
  on init.

Other
- frontmatter description updated; argument-hint extended with the new
  flags.
- "When to use which" table reorganised — delta-mode rows on top.
- New anti-patterns: don't run continuity outside delta mode; don't
  snapshot in --since modes other than --since-last.
- CHANGELOG [Unreleased] entry expanded.
- Plugin repo synced (23 SKILL.md still; review/SKILL.md updated
  in-place).

Tests: 106/106 still pass (skill is markdown).
E2E smoke: scribetronic init now writes scribetronic/.gitignore.

Local commit. Push pending.
…st publish

The v0.2.1 release ships everything currently in develop (plugin marketplace,
frontmatter fix, format/style decoupling, writing-style override path, agent-
friendly CLI, /review skill). This commit gets the user-facing docs ready
for that publish.

Major rewrites
- packages/cli/README.md: was completely stale ("not on npm yet (planned for
  v1.0)", described init copying skills to project, missing /review, missing
  the new commands). Rewritten end-to-end. This is the README that npmjs.com
  shows under the package — accurate now: 23 skills, plugin marketplace, all
  7 commands, scribetronic/style/writing-style.md path, --json/--yes flags.

Smaller fixes
- README.md (root): replaced 3 lingering writing-style/SKILL.md path
  references with the project-override path scribetronic/style/writing-style.md.
- docs/customization.md, docs/contracts.md, docs/philosophy.md: same path
  fix where the doc was talking about the user-editable file (vs the bundle
  template, which keeps the legacy path).
- 24.scribetronic-plugin/README.md: 22 → 23 skill count, added /review
  to the shared utilities table.

CHANGELOG
- Merged the [Unreleased] entry (the /review skill + .gitignore template)
  into [0.2.1] so the release notes carry everything that's about to ship.
- [Unreleased] now only carries the v0.3.0 plan items (write Phase 4
  adopting /review by default, calendar export, multi-author profiles).
- Added a one-paragraph header to [0.2.1] explaining what this first
  public release bundles.

Quality
- Tests 106/106, typecheck + lint clean.
- Plugin repo already has matching content (synced + pushed in prior
  commits); this commit doesn't change skills.

This is the last documentation pass before tagging v0.2.1.
…leText)

Test job on Node 18 was failing because vitest v4 bundles rolldown,
which imports styleText from node:util. That export was added in
Node 20.12; Node 18 ships node:util without it.

Changes
- .github/workflows/ci.yml: matrix [18, 20, 22] → [20, 22]
- packages/cli/package.json: engines.node ">=18.0.0" → ">=20.0.0"
- packages/cli/README.md, CLAUDE.md, AGENTS.md, CHANGELOG.md:
  doc references updated.

Node 18 entered maintenance LTS Oct 2024 and EOL April 2025, so
this isn't a meaningful loss of coverage.
@r-bart
r-bart merged commit 7bfc1eb into main May 4, 2026
4 checks passed
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