Skip to content

feat: adopt the @effected front-end kit in cli, mcp and the silk carrier - #698

Merged
C. Spencer Beggs (spencerbeggs) merged 21 commits into
mainfrom
feat/effected-front-end-kit
Sep 25, 2026
Merged

C. Spencer Beggs (spencerbeggs) merged 21 commits into
mainfrom
feat/effected-front-end-kit

Conversation

@spencerbeggs

Copy link
Copy Markdown
Contributor

Summary

@savvy-web/cli, @savvy-web/mcp and silk's carrier shims now run on the @effected front-end kit (@effected/engine 0.1.0, @effected/mcp 0.1.1, @effected/cli 0.8.0), replacing hand-rolled process-boundary code. Three things behave differently for people using the tools:

  • savvy output. Every log line now goes to stderr without the [12:00:00.000] INFO (#N): prefix. stdout carries only a command's result (✓ ⚠ ✗ • lines, coloured only on a TTY without NO_COLOR), JSON output and hook envelopes. Usage errors exit 64 instead of 1: the help text goes to stdout and the error to stderr, which is core effect/unstable/cli behaviour.
  • savvy-mcp results (mcp: retire SilkMarkdown and the registerSilkToolkit port (Claude Code only shows structuredContent to the model) #688). The markdown transcript is gone. A success returns the typed result in structuredContent, and content[0].text is the same object as JSON. Claude Code forwards only structuredContent to the model, so the transcript reached no model there. Clients that show content (Cursor, Copilot, MCP Apps hosts) now see JSON.
  • Version lines. Launched through @savvy-web/silk, savvy --version prints savvy v3.2.8 via @savvy-web/silk 4.2.8, and savvy-mcp's serverInfo.version gets the same suffix. This is guaranteed under pnpm only: npm's flat .bin can link the cli or mcp package's own bin instead of silk's shim.

What changed

mcp

  • main.ts resolves the project directory with LaunchContext.projectDir, which skips empty values and unsubstituted ${VAR} values, and then runs McpStdio.launch/teardown. The crash guards and dynamic imports stay, since the kit doesn't package them yet (effected#835).
  • ServerLayer is built on McpStdio.layer. The layer answers a non-JSON stdin line with -32700 and keeps logs off the wire.
  • Tools register through McpToolkit.layer(SilkToolkit, { strict: "annotated" }). No tool is annotated, so every tool still accepts the extra arguments Claude Code sends.
  • The registerSilkToolkit port of core's registerToolkit is deleted, along with SilkMarkdown and all per-tool markdown renderers.
  • Tool errors spread ToolFailure.fields. The barrel drops Remediation, composeRemediatedMessage, truncateEchoed and SilkMarkdown (a minor bump, per the mcp semver-surface convention).

cli

  • main.ts runs CliRuntime.main with Node as the platform and the kit-default CliLogger, and passes a render that prints a failure's message, or its tag and fields. The kit default would print a bare tag, so a failing savvy clean showed CleanError with no reason.
  • All 42 process.exitCode = 1 writes became CliExit.set(1).
  • Output was sorted command group by command group (lint, commit, repos, changeset, clean). The result a person ran the command for goes through a local Output helper on stdout. Progress and failure explanations stay as Effect.log* and go to stderr.
  • repos status --json now always prints one JSON document, including { "error": …, "clean": false } on a config error or a git failure. The gitmodules-drift monitor runs JSON.parse on it.
  • savvy check separates its three sections with a blank line.

silk and layering

  • Both shims pass { distribution: { name: "@savvy-web/silk", version } }.
  • The layering policy moved to packages/silk/layers.json on the kit's LayerPolicy schema. unconstrained replaces harness and also covers the private root. requiredEdges guards against a check that passes on an empty graph.
  • WorkspaceLayering.checkWorkspace checks the policy in silk's own tests, and e2e/workspace is deleted.
  • SourceBoundary tests pin three rules at source:
    • silk's half of the non-import invariant (only the two src/bin shims import cli or mcp);
    • "only bin/main/version touch process" in mcp;
    • "no process.exitCode/process.exit" in cli, plus a ratchet on the files that read process.

Things a reviewer should check

  • Stdout contract. The machine consumers still get what they expect:
    • savvy commit hook * still prints its JSON envelope.
    • changeset validate-file still prints file:line:col rule message lines, which the plugin hook greps for CSHnnn after merging stderr into stdout.
    • repos status --drift --json is always JSON.
    • repos sync is read by exit code only.
  • Help on usage errors. Help stays on stdout on purpose; @effected/cli has no switch to move it. The bin e2e pins the contract: exit 64, the error only on stderr, only help on stdout.
  • Bump types. mcp and cli are minor bumps with Breaking Changes sections (exit 64, the stdout split, and the mcp text channel and barrel removals), as the design chose. silk is a patch, plus the automatic re-pin.
  • Deleted tests. The prompt-injection "inert code span" tests went with the markdown, since there is no markdown left to escape.
  • Lockfile. pnpm 12.6 rewrote optional: flags in pnpm-lock.yaml alongside the new @effected edges.

Verification

All run on the final tree in the worktree:

  • pnpm test: 3226/3228 passed, 2 skipped (both pre-existing), and all coverage thresholds met.
  • pnpm build: green, and API Extractor reports zero errors and zero warnings for cli, mcp and silk.
  • pnpm typecheck, pnpm lint, pnpm lint:md, pnpm peers check: clean. The two lint warnings are __PACKAGE_VERSION__ notes on silk's shims; it is a build-time define, not a turbo env var.
  • pnpm test:hooks: 473/473 passed.
  • okfit validate: 0 errors.
  • Mutation checks: each new boundary test, the layering test, the --json error path and the lint init stdout test were checked by temporarily breaking the code and watching the test fail.
  • Manual runs:
    • savvy check: stderr empty.
    • savvy repos status --drift --json: parses as JSON.
    • savvy --version: prints the carrier suffix through silk.
    • savvy lint --bogus: exits 64.

Follow-ups

feat: adopt the @effected front-end kit in cli, mcp and the silk carrier

- Run savvy under CliRuntime: every log line on stderr, results through a readable Output helper on stdout, usage errors exit 64
- Launch savvy-mcp over McpStdio and register tools through McpToolkit; results are structuredContent with the same object as JSON text
- Name the carrier in savvy --version and serverInfo.version when launched through @savvy-web/silk
- Move the package-layering guard into silk on WorkspaceLayering and delete e2e/workspace

Closes #695, #688

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>

Closes #695
Closes #688

Signed-off-by: C. Spencer Beggs spencer@savvyweb.systems

- Spread ToolFailure.fields into every tagged tool error and compose messages with ToolFailure.message and truncate
- Drop Remediation, composeRemediatedMessage and truncateEchoed from the barrel; the kit owns them now

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Build ServerLayer on McpStdio.layer and accept a distribution that is rendered into serverInfo.version
- Resolve the project directory with LaunchContext.projectDir and run under McpStdio.launch and teardown
- Delete the hand-rolled project-root resolver and the e2e process helper in favour of the kit's testing module

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Pass the carrier distribution from the savvy-mcp shim so the server reports its version via @savvy-web/silk
- Hold the mcp process boundary with a SourceBoundary scan: only bin, main and version touch process

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Hand the program to CliRuntime.main with the Node platform at the edge and CliLogger dropping the timestamp prefix
- Exit 64 on a usage error instead of 1
- Name the carrier in savvy --version, and pass it from the silk savvy shim

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Replace every process.exitCode write in the repos and changeset commands with CliExit.set
- Pin the boundary with SourceBoundary: no exitCode or exit writes in src, and process reads ratcheted to today's files

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Write savvy lint check and init results through Output on stdout, with glyphs coloured only on a terminal
- Add Output.skip for items that are absent or not applicable

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Write repos results through Output on stdout and send each failure's explanation to stderr
- Keep repos status --json stdout a single JSON document on the config-error path, reporting error and clean false

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Write changeset check, validate, transform, deps, version, init and clean results through Output on stdout
- Keep a file read failure and the git-remote placeholder notice on stderr as diagnostics

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Run savvy under the kit-default CliLogger so stdout carries only command output, JSON and hook envelopes
- Separate the savvy check sections with a blank line

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Move layers.json into silk on the kit's policy schema, with required edges as the non-vacuity guard
- Check the live graph with WorkspaceLayering and the cli/mcp non-import invariant with SourceBoundary
- Delete the e2e/workspace harness and its hand-rolled graph walk

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Add a draft Decision for the front ends adopting @Effected engine, mcp and cli, and the stdout contract
- Point the layering concepts and CLAUDE.md files at silk's layers.json and tests now that e2e/workspace is gone

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
…lures

- Print the error document under --json when status or the drift check fails with a git error, not just a config error
- Render a propagated failure by its message or its tag and fields instead of a bare tag
- Pin the usage-error contract (help on stdout, error on stderr, exit 64) and correct two doc claims

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Drop the markdown transcript: content[0].text is now the structuredContent object as JSON, the only channel Claude Code forwards
- Replace the registerSilkToolkit port with McpToolkit.layer in annotated-strict mode so every tool stays lenient
- Re-pin the lifecycle e2e: a declared failure answers isError and leaves stderr empty

Closes #688

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
- Record McpToolkit registration, the structuredContent contract and the content-reading client trade-off in the okf concepts and mcp CLAUDE.md
- Tell the changeset and dependencies skills to read structuredContent fields instead of a transcript

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
The staged sync wrote the conventions and decisions indexes at the repo root instead of under okf/.

Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
@pullfrog

pullfrog Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

This run was cancelled 🛑

The workflow was cancelled before completion. Please check the link below for details.

Pullfrog  | View workflow run | via Pullfrog | 𝕏

@coderabbitai

coderabbitai Bot commented Sep 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 135 files, which is 35 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: efed8c58-d2bc-4f93-8744-7fc8e0ab92c3

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac58fc and eaa26ed.

⛔ Files ignored due to path filters (9)
  • .changeset/four-geese-relax.md is excluded by !.changeset/**
  • .changeset/six-plums-decide.md is excluded by !.changeset/**
  • .changeset/tidy-otters-launch.md is excluded by !.changeset/**
  • CLAUDE.md is excluded by !**/CLAUDE.md
  • e2e/CLAUDE.md is excluded by !**/CLAUDE.md
  • packages/cli/CLAUDE.md is excluded by !**/CLAUDE.md
  • packages/mcp/CLAUDE.md is excluded by !**/CLAUDE.md
  • packages/silk/CLAUDE.md is excluded by !**/CLAUDE.md
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (135)
  • e2e/silk/__test__/e2e/packed-install.e2e.test.ts
  • e2e/workspace/__test__/e2e/package-graph.e2e.test.ts
  • e2e/workspace/biome.json
  • e2e/workspace/package.json
  • e2e/workspace/tsconfig.json
  • e2e/workspace/turbo.json
  • okf/conventions/index.md
  • okf/conventions/mcp-tool-authoring.md
  • okf/conventions/package-layering.md
  • okf/decisions/carrier-pattern-package-graph.md
  • okf/decisions/effect-native-mcp-server.md
  • okf/decisions/front-ends-adopt-effected-kit.md
  • okf/decisions/index.md
  • okf/interfaces/index.md
  • okf/interfaces/layers-json.md
  • okf/interfaces/savvy-cli.md
  • okf/interfaces/savvy-mcp-tools.md
  • okf/log.md
  • okf/modules/cli.md
  • okf/modules/e2e.md
  • okf/modules/mcp.md
  • okf/modules/silk.md
  • packages/cli/__test__/boundaries.test.ts
  • packages/cli/__test__/capture.test.ts
  • packages/cli/__test__/changeset/check.test.ts
  • packages/cli/__test__/changeset/config-validate.test.ts
  • packages/cli/__test__/changeset/lint.test.ts
  • packages/cli/__test__/changeset/transform.test.ts
  • packages/cli/__test__/changeset/validate-file.test.ts
  • packages/cli/__test__/changeset/version.test.ts
  • packages/cli/__test__/check.test.ts
  • packages/cli/__test__/clean.test.ts
  • packages/cli/__test__/commands/changeset/deps-regen.test.ts
  • packages/cli/__test__/commands/lint/init-repos-ignore.test.ts
  • packages/cli/__test__/commands/repos/deregister.test.ts
  • packages/cli/__test__/commands/repos/note.test.ts
  • packages/cli/__test__/commands/repos/remove.test.ts
  • packages/cli/__test__/commands/repos/rename.test.ts
  • packages/cli/__test__/commands/repos/restore.test.ts
  • packages/cli/__test__/commands/repos/status.test.ts
  • packages/cli/__test__/commands/repos/sync.test.ts
  • packages/cli/__test__/commit/check.test.ts
  • packages/cli/__test__/commit/init.test.ts
  • packages/cli/__test__/e2e/bin.e2e.test.ts
  • packages/cli/__test__/failure-line.test.ts
  • packages/cli/__test__/lint/biome-schema-sync.test.ts
  • packages/cli/__test__/lint/check.test.ts
  • packages/cli/__test__/lint/init.test.ts
  • packages/cli/__test__/output.test.ts
  • packages/cli/__test__/utils/capture.ts
  • packages/cli/__test__/utils/exit.ts
  • packages/cli/__test__/version-line.test.ts
  • packages/cli/package.json
  • packages/cli/src/cli/index.ts
  • packages/cli/src/commands/changeset/commands/check.ts
  • packages/cli/src/commands/changeset/commands/config-validate.ts
  • packages/cli/src/commands/changeset/commands/deps-detect.ts
  • packages/cli/src/commands/changeset/commands/deps-regen.ts
  • packages/cli/src/commands/changeset/commands/init.ts
  • packages/cli/src/commands/changeset/commands/lint.ts
  • packages/cli/src/commands/changeset/commands/transform.ts
  • packages/cli/src/commands/changeset/commands/validate-file.ts
  • packages/cli/src/commands/changeset/commands/version.ts
  • packages/cli/src/commands/changeset/index.ts
  • packages/cli/src/commands/changeset/utils/config-gate.ts
  • packages/cli/src/commands/check.ts
  • packages/cli/src/commands/clean.ts
  • packages/cli/src/commands/commit/check.ts
  • packages/cli/src/commands/commit/constants.ts
  • packages/cli/src/commands/commit/init.ts
  • packages/cli/src/commands/commit/lint.ts
  • packages/cli/src/commands/lint/check.ts
  • packages/cli/src/commands/lint/init.ts
  • packages/cli/src/commands/repos/commands/add.ts
  • packages/cli/src/commands/repos/commands/deregister.ts
  • packages/cli/src/commands/repos/commands/note.ts
  • packages/cli/src/commands/repos/commands/pin.ts
  • packages/cli/src/commands/repos/commands/remove.ts
  • packages/cli/src/commands/repos/commands/rename.ts
  • packages/cli/src/commands/repos/commands/restore.ts
  • packages/cli/src/commands/repos/commands/status.ts
  • packages/cli/src/commands/repos/commands/sync.ts
  • packages/cli/src/commands/repos/index.ts
  • packages/cli/src/internal/failure-line.ts
  • packages/cli/src/internal/output.ts
  • packages/cli/src/internal/version-line.ts
  • packages/cli/src/main.ts
  • packages/cli/src/version.ts
  • packages/mcp/__test__/boundaries.test.ts
  • packages/mcp/__test__/e2e/server-lifecycle.e2e.test.ts
  • packages/mcp/__test__/e2e/utils/mcp-process.ts
  • packages/mcp/__test__/errors.test.ts
  • packages/mcp/__test__/project-root.test.ts
  • packages/mcp/__test__/server.harness.test.ts
  • packages/mcp/__test__/server.strict.test.ts
  • packages/mcp/__test__/server.test.ts
  • packages/mcp/__test__/toolkit.test.ts
  • packages/mcp/__test__/tools/biome-check.test.ts
  • packages/mcp/__test__/tools/changeset-deps-detect.test.ts
  • packages/mcp/__test__/tools/changeset-deps-regen.test.ts
  • packages/mcp/__test__/tools/changeset-inspect.test.ts
  • packages/mcp/__test__/tools/changeset-preview.test.ts
  • packages/mcp/__test__/tools/changeset-validate.test.ts
  • packages/mcp/__test__/tools/repos-inspect.test.ts
  • packages/mcp/__test__/tools/repos-manage.test.ts
  • packages/mcp/__test__/tools/turbo-inspect.test.ts
  • packages/mcp/__test__/tools/workspace-info.test.ts
  • packages/mcp/__test__/utils/harness.ts
  • packages/mcp/package.json
  • packages/mcp/src/errors.ts
  • packages/mcp/src/index.ts
  • packages/mcp/src/internal/project-root.ts
  • packages/mcp/src/main.ts
  • packages/mcp/src/markdown.ts
  • packages/mcp/src/server.ts
  • packages/mcp/src/tools/biome-check.ts
  • packages/mcp/src/tools/changeset-deps-detect.ts
  • packages/mcp/src/tools/changeset-deps-regen.ts
  • packages/mcp/src/tools/changeset-inspect.ts
  • packages/mcp/src/tools/changeset-preview.ts
  • packages/mcp/src/tools/changeset-validate.ts
  • packages/mcp/src/tools/md-inline.ts
  • packages/mcp/src/tools/repos-inspect.ts
  • packages/mcp/src/tools/repos-manage.ts
  • packages/mcp/src/tools/turbo-inspect.ts
  • packages/mcp/src/tools/workspace-info.ts
  • packages/silk/__test__/boundaries.test.ts
  • packages/silk/__test__/e2e/bins.e2e.test.ts
  • packages/silk/__test__/externals.test.ts
  • packages/silk/__test__/package-layering.test.ts
  • packages/silk/layers.json
  • packages/silk/src/bin/savvy-mcp.ts
  • packages/silk/src/bin/savvy.ts
  • plugins/silk/skills/changeset/SKILL.md
  • plugins/silk/skills/dependencies/SKILL.md

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Review on demand using usage pricing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@savvy-web-bot

savvy-web-bot Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Code Review

Current Commit: eaa26ed90e0803e80796a54e2b1878bdeb4e5cd1

Summary

This PR moves @savvy-web/cli, @savvy-web/mcp, and silk's carrier shims onto the @effected front-end kit, replacing hand-rolled process-boundary code. It's a large but well-organized change (144 files, net deletions) with a thorough description, a clean commit-by-commit progression, and fully green validation:

  • Tests — success
  • Code Quality (Biome) — success
  • Markdown — success
  • Conventional Commits — success
  • PR Title Validation — success

I reviewed the substantive source changes rather than the generated churn (lockfile, changelogs, okf indexes).

What holds up well

  • Stdout/stderr split (internal/output.ts, main.ts). The Output helper routes result lines through Console.log (stdout) while Effect.log* goes to stderr via the kit logger — a clean seam. Colour is gated on CliColor.enabled and only tints the glyph, so piped output stays stable.
  • FailureLine.render prefers a non-empty Error.message, then falls back to _tag + fields (fixing the CleanError-with-no-reason regression), then String.
  • repos status --json contract (repos/commands/status.ts). Both the ReposConfigError (invalid) and GitSubmoduleError paths emit a single { error, clean: false } JSON document under --json while keeping the human message on stderr — what the gitmodules-drift monitor's JSON.parse needs. The missing kind still exits 0 with an empty report.
  • MCP boundary (server.ts, main.ts, errors.ts). McpToolkit.layer(SilkToolkit, { strict: "annotated" }) registration, crash-guards-before-dynamic-import ordering preserved, and ToolFailure-based message composition are all sound and well documented with era-agnostic gotchas.
  • New guardrails. packages/silk/layers.json + package-layering.test.ts and the boundaries.test.ts SourceBoundary suites are genuinely non-vacuous — each carries a positive control (the layering test asserts an upward + sideways edge are both reported; requiredEdges guards against a graph that passes empty). A meaningful upgrade over the deleted e2e/workspace harness, moving the check into the top-of-graph package.

Notes (non-blocking)

The deferred follow-ups you list match what I'd flag — the no-op CliExit.set(1) before a propagated failure in config-gate.ts, and clean.ts printing a ✓ Removed N item(s) summary above the ⚠ failed lines before failing (so an all-failures run prints ✓ Removed 0 item(s)). Both are harmless since the runtime exits non-zero regardless, and CurrentDistribution/Output.summary being provided-but-unused are minor. None warrant an inline comment given they're tracked.

Nice work — the stdout contract, the JSON error documents, and the layering/source-boundary tests are the load-bearing parts and they're all solid. Approving.

🤖 Generated with Claude Code

@savvy-web-bot savvy-web-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All validation green; stdout/stderr contract, JSON error documents, and the new layering/source-boundary guardrails are sound. Deferred minors are tracked.

@spencerbeggs
C. Spencer Beggs (spencerbeggs) merged commit 695fc86 into main Sep 25, 2026
21 of 22 checks passed
@spencerbeggs
C. Spencer Beggs (spencerbeggs) deleted the feat/effected-front-end-kit branch September 25, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant