Skip to content

Write actions: craftar mcp --allow-writes and the shared writers (spec 40 A+B) - #74

Merged
llima merged 20 commits into
mainfrom
feat/write-actions
Oct 24, 2026
Merged

llima merged 20 commits into
mainfrom
feat/write-actions

Conversation

@llima

@llima llima commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • Version bumped to 0.35.0 (minor). Merging publishes craftar@0.35.0 to npm after approval in the npm environment.
  • Part A — the shared writers (src/core/write-actions.ts): syncWorkspace, editRecipes and statusDigest. The writing half of applyAndReport and of the recipe commands now lives in the core and returns data; the CLI prints from it. Pins captured before the move (test/write-pins.test.ts, plus tools/list / initialize in test/pins/) pass unchanged after it. forgeLine and nextSyncLine move to src/core/report.ts.
  • Part B — craftar mcp --allow-writes: three more tools, sync, add_recipe and remove_recipe. Each call checks, in order, the pin, then a per-workspace consent, then the Forge (a remote Forge needs --fetch for sync), then runs the writer.
    • sync passes neither overwriteDrift nor overwritePaths: every drift, orphan-drift and collision is skipped and listed.
    • A result never carries file content or the registry's path.
    • Without the flag, the server answers byte for byte as before. The protocol module still imports no writer: test/mcp-imports.test.ts is unchanged.
  • The consent: craftar mcp allow-writes records it as a top-level writeConsent entry in registry.json (still schema: 1), and craftar mcp revoke-writes removes it.
    • allow-writes runs only in a terminal, asks with a [no] default (only y / yes records), and refuses a forge: or ref: that holds credentials.
    • The consent is bound to the forge, ref and profile it was given for.
    • workspaces forget and prune drop it with the workspace.
  • README: the Commands rows, a Letting it write. paragraph, the roadmap, and Upgrading › to 0.35.0.

Test plan

  • npm run typecheck — clean
  • npm test (--exclude test/ci.test.ts --maxWorkers=6) — 146 files passed, 1 skipped; 3486 tests passed, 11 skipped, before review. After the review rounds the builder's run gave 3491 passed. Oracle skipped (no fixture on this machine).
  • No emitted byte changes. No emitter, planner, status(), apply() or lock code changed. sync.ts only gains a fetchHint value for the MCP fetch-failure tail; the CLI's message is unchanged.
  • By hand, in a pseudo-terminal: for allow-writes, n, no, Enter and ended input refuse, maybe re-asks, y / yes record, and typed-ahead input is discarded. From a non-TTY shell it is refused. Also checked: revoke-writes twice, and a sync call over stdio with --allow-writes.
  • node-cli-reviewer: three rounds, the last clean (no blocker, no should-fix).
  • Not run: the server registered with --allow-writes in Claude Code and in Kiro, and how each presents a destructiveHint tool.

llima added 20 commits October 23, 2026 18:23
…s print, and mcp's tools/list

Spec 40 §12.1: captured before the writing half of applyAndReport and recipeCommand moves into the core, so the lift is proved byte for byte.
…--allow-writes

Spec 40 §5.2, §7.1: writeConsent is a top-level list of registry.json, so register() keeps it; forget and prune drop it with the workspace, and an empty list is omitted.
…riters of spec 40

Part A: the writing half of applyAndReport and recipeCommand as functions that return data, for the CLI and craftar mcp --allow-writes; forgeLine and nextSyncLine move into src/core/report.ts.

refactor(cli): print sync and the recipe commands from the core outcomes

Spec 40 §4.2: test/write-pins.test.ts passes unchanged.
A thrown error is printed through errorText, as before the lift; forgeLine keeps its doc comment.
Spec 40 §5.2: the consent is typed by a person in a terminal and names the Forge, ref and profile it allows; revoking needs no terminal.
An answer of n, no or anything else non-empty recorded it; the question is now confirmWrites, Enter and an ended input are no, anything but yes or no is asked again.
…ove_recipe

Spec 40 Part B: each writing call checks the pin, then this workspace's consent for the pinned Forge, ref and profile; sync skips and lists every hand edit; the protocol module still imports no writer.
- consent() returns codes (ok|registry-off|unreadable|none|{stale}); callWrite converts to messages
- isRemote(forge) replaces classifyForge(root, merged)
- editRecipes() merges addRecipe and removeRecipe
- sync uses mode: 'sync'
- recipe tools receive fetch param and include cachedCopyLine in warnings
- static import for allowWrites in test/mcp-cli.test.ts
- new test 15: remote Forge warnings in add_recipe

Mutations verified:
- (b) drop cached line → test 15 fails
- (c) map 'none' to 'ok' → tests 2 and 12 fail
…e upgrade note

Spec 40 Parts A and B: the Commands rows, the section on letting the AI coder write, the roadmap and the to-0.35.0 block.
The writers import the protocol's Pin as a type; the remote check before sync already decided fetch.
…e new Phase 1 line

The README of the previous commit broke two assertions of test/ui-readme.test.ts.
On Windows realpath and path.resolve spell a temp directory differently; the no-craftar.yaml refusal prints path.resolve, as craftar mcp's launch refusal does.
@llima
llima merged commit 2f8af6b into main Oct 24, 2026
8 checks passed
@llima
llima deleted the feat/write-actions branch October 24, 2026 10:00
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