Skip to content

feat(plan): file-based revisions, free-form status, and optimistic locking#3274

Merged
Sayt-0 merged 1 commit into
mainfrom
feat/plan-toolset-improvements
Jun 26, 2026
Merged

feat(plan): file-based revisions, free-form status, and optimistic locking#3274
Sayt-0 merged 1 commit into
mainfrom
feat/plan-toolset-improvements

Conversation

@Sayt-0

@Sayt-0 Sayt-0 commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

Implements the three plan-toolset improvements from #3263: file-based plan revisions, a free-form status field, and version-based optimistic locking. The existing auto-incremented revision is reused as the version number.

New tools

Tool Purpose
export_plan_to_file Writes the plan content to a path and returns only metadata (never the body), so materialising a plan before editing costs no output tokens.
update_plan_from_file Commits new content read from a file, avoiding re-sending the whole body.
set_plan_status Sets the free-form status without rewriting the body (the plan must already exist).
get_plan_status Reads the status and revision without fetching the body.

read_plan and list_plans now also report status; write_plan, update_plan_from_file, set_plan_status and delete_plan accept an optional last_known_revision.

Issue expectations mapped

Issue ask Implementation
update_plan_from_file accepting a file path update_plan_from_file tool
export_plan_to_file writing content without returning it export_plan_to_file returns metadata only
Free-form status, no enum Plan.Status string, no validation
Read/write status independently of the body set_plan_status / get_plan_status (lightweight StatusView)
TUI surfaces status alongside the title new plantool renderer on single-plan calls
Auto-incremented version per write reuses the existing revision
Optional version guard on writes, conflict on mismatch last_known_revision on every write tool, VersionConflictError
Reads return the version read_plan, get_plan_status and export_plan_to_file return revision

Safety

Concern Handling
Wrong path streams unbounded data or blocks File reads reject directories and non-regular files (devices, named pipes) before opening, and are bounded by an io.LimitReader
Concurrent writes silently clobber each other Optimistic-lock compare-and-set runs under the storage mutex; a conflicting write or delete never mutates state

Tests

  • Plan package: status set/get/preserve, the export then edit then commit round-trip, file-read edge cases (missing, empty, directory, named pipe, oversized), conflict on every write tool, an 8-goroutine concurrency test (exactly one writer from a given revision wins, the rest conflict), and an extended cross-backend conformance suite.
  • TUI: the renderer surfaces status and title and passes conflict errors through; factory routing is locked in.
  • All pass with -race; lint is clean on the changed packages; the tree cross-compiles for Windows (the named-pipe test is //go:build unix).

Note for reviewers

The optimistic-lock parameter is named last_known_revision rather than the issue's last_known_version, for coherence with the existing revision field that reads already return (a caller reads revision and passes the same value back). If last_known_version is preferred, it is a small rename.

Implements #3263.

…cking

Implements the three improvements requested in issue #3263.

File-based revisions:
- export_plan_to_file writes a plan's content to disk and returns only
  metadata, never the body, so materialising a plan costs no output tokens.
- update_plan_from_file commits new content read from a file. File reads
  reject directories, non-regular files (devices, named pipes) and oversized
  files, and are bounded by an io.LimitReader.

Free-form status:
- Plan and Summary gain a free-form status string (no fixed vocabulary).
- set_plan_status and get_plan_status read and write the status without the
  body; status is also accepted on write_plan and update_plan_from_file.
- A new TUI renderer surfaces the status and title next to single-plan calls.

Optimistic locking:
- The existing auto-incremented revision is the version number. Reads return
  it; write_plan, update_plan_from_file, set_plan_status and delete_plan accept
  an optional last_known_revision that rejects the write with a conflict error
  on mismatch. The compare-and-set is atomic under the storage mutex.

Storage.Upsert now takes an UpsertRequest (nil fields preserve their previous
value) and Storage.Delete takes an optional expected revision.

Docs and the shared_plan example are updated to cover the new tools.
@Sayt-0 Sayt-0 requested a review from a team as a code owner June 26, 2026 14:51
@Sayt-0 Sayt-0 added area/tools For features/issues/fixes related to the usage of built-in and MCP tools area/tui For features/issues/fixes related to the TUI labels Jun 26, 2026
@aheritier aheritier added area/docs Documentation changes kind/feat PR adds a new feature (maps to feat:). Use on PRs only. labels Jun 26, 2026
@Sayt-0 Sayt-0 merged commit 06e754e into main Jun 26, 2026
14 checks passed
@Sayt-0 Sayt-0 deleted the feat/plan-toolset-improvements branch June 26, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation changes area/tools For features/issues/fixes related to the usage of built-in and MCP tools area/tui For features/issues/fixes related to the TUI kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants