Repository navigation
CMUX-37: Phases 2-5 — Blueprints, snapshot --all, browser/markdown round-trip, agent registry rows - #79
Merged
Conversation
…ight minor fixes) New files: - Sources/WorkspacePlanCapture.swift -- shared plan walker extracted from WorkspaceSnapshotCapture; both snapshot and blueprint export now call it. - Sources/WorkspaceBlueprintFile.swift -- WorkspaceBlueprintFile + WorkspaceBlueprintIndex value types (JSON wrapper format, same Codable pattern as snapshots). - Sources/WorkspaceBlueprintStore.swift -- per-repo/per-user/built-in discovery, recency-sorted merge, atomic read/write. - Sources/WorkspaceBlueprintExporter.swift -- thin @mainactor struct wrapping WorkspacePlanCapture for live-workspace-to-blueprint export. - c11Tests/WorkspaceBlueprintFileCodableTests.swift -- round-trip Codable tests. - c11Tests/WorkspaceBlueprintStoreTests.swift -- store tests against temp dirs. Pre-flight fixes absorbed from Cycle-2 open items: - B-IM2: gate bonsplitController.selectTab with selectAllowed (mirrors ApplyOptions.select) so background applies don't steal tab focus. - Codex-P1: mirror metadata_override ApplyFailure to ApplyResult.warnings so CLI summary counts them correctly. - StepClock rename: Clock -> StepClock throughout executor to avoid shadowing the stdlib Clock protocol. - Claude-P5: detect multi-tab pane paneMetadata collision; emit pane_metadata_collision ApplyFailure + warning instead of silent overwrite. - WorkspaceSnapshotCapture.swift: delegate to WorkspacePlanCapture.capture; remove the now-redundant inline Walker.
Adds basic-terminal.json, side-by-side.json, and agent-room.json to Resources/Blueprints/ and registers the folder in the Xcode project so it lands in the app bundle (accessible via Bundle.main + "Blueprints" subdirectory for WorkspaceBlueprintStore.builtInBlueprintURLs()).
…picker Add workspace new and export-blueprint subcommands to c11 workspace dispatch. workspace new without --blueprint drops into a numbered interactive picker that fetches workspace.list_blueprints and dispatches workspace.apply on selection. workspace export-blueprint captures a live workspace layout to the user blueprint directory via workspace.export_blueprint. Also fix LiveWorkspaceSnapshotSource.defaultVersionString nonisolated annotation (main actor isolation was spuriously inherited from struct).
Add 5 xcstrings keys for all 7 locales (en, ja, ko, ru, uk, zh-Hans, zh-Hant): - workspace.blueprint.picker.prompt - workspace.blueprint.picker.noBlueprints - workspace.blueprint.source.repo / .user / .builtIn Update CLI picker to use String(localized:) at each call site.
Four tests exercising browser and markdown surface kinds through the apply -> capture -> verify cycle: - testBrowserSurfaceKindRoundTrips: verifies kind preserved, url=nil in headless - testMarkdownFilePathRoundTrips: verifies filePath round-trips (set at init) - testMixedThreeSurfacePlanRoundTrips: terminal+browser+markdown in one plan - testBlueprintExporterBrowserMarkdownKinds: exporter captures correct kinds
Add --all flag to c11 snapshot that captures every open workspace in one
call. CLI output: one OK line per workspace. Mutually exclusive with
--workspace and --out. Socket handler v2SnapshotCreate accepts all:true
and returns {snapshots: [{snapshot_id, path, surface_count, workspace_ref}]}.
Add three best-effort rows to AgentRestartRegistry.phase1: - codex: returns "codex --last\n" (resumes most recent session globally) - opencode: returns "opencode --continue\n" (best-effort) - kimi: returns "kimi --continue\n" (best-effort) These rows use --last/--continue flags rather than session IDs because those CLIs resume the most recent session globally. Best-effort is preferable to no resume for these tools. Also update testUnknownTerminalTypeReturnsNil to use "cursor" instead of "codex" (now a known type), and add 3 new test methods for the new rows.
codex resume --last is the correct CLI syntax; the prior codex --last would fail with "unknown command". Tests updated to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… test WorkspaceSpec and SurfaceSpec have no explicit CodingKeys so Swift synthesizes verbatim camelCase keys on the wire. The schema doc claimed snake_case (customColor as custom_color, etc.) and the blueprint test at line 94 used wrong nesting and a snake_case key (surface_ids). Fixed both. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
I1: Pass cwd to workspace.list_blueprints so per-repo .cmux/blueprints/
discovery works from the CLI blueprint picker.
I2: snapshot --all now checks for snap["error"] per workspace; prints
ERROR workspace=... reason=... and exits non-zero on any failure.
M1: snapshot help text now documents --all flag and mutual exclusivity.
M2: Single-workspace snapshot print now includes workspace= field for
consistency with --all output format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…guard
I3: Add WorkspaceSnapshotBrowserMarkdownRoundTripTests.swift to the
c11Tests PBXSourcesBuildPhase so it compiles in CI.
I4: Fix stale comment on WorkspaceBlueprintIndex.Source.user: path is
~/.config/cmux/blueprints/, not ~/.c11-blueprints/.
I5: Fix selectedIndex > 0 guard to >= 0 so tab index 0 (first tab) is
correctly selected when restored from a snapshot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both --continue flags were unverified against those CLIs. Reverts to bare "opencode\n" and "kimi\n" with updated comments. Tests updated to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uard S2: blueprintURLs(in:) now checks isRegularFile so directories named *.json cannot slip through the extension filter. S3: workspace new --json without --blueprint returns a JSON error instead of polluting stdout with the interactive picker UI. S4: workspace.export_blueprint errors on existing files rather than silently overwriting; --force flag added to allow explicit overwrite. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t→restore loop
Drives the full Phase 0+1+2 path against a tagged build (own bundle id,
own socket, own DerivedData) so it can quit & relaunch the app without
disturbing the operator's primary c11.
Loop: build → apply blueprint → snapshot → osascript quit → open
relaunch → restore → re-snapshot → diff plan shape (sorted surfaces,
layout SHA1). Tolerates the snapshot-writer drift codes
`metadata_override` and `working_directory_not_applied` as informational
warnings; any other failure code is fatal.
First run surfaced two real snapshot-writer bugs worth follow-up:
- SurfaceSpec.title double-written into metadata["title"]
- workingDirectory captured on the seed terminal that the executor
can't accept (cwd is fixed at workspace creation)
Round-trip is still structurally faithful — the layout fingerprint and
surface count match exactly across the quit/relaunch cycle.
Generates a blueprint with a synthesized claude.session_id and
terminal_type=claude-code metadata on s1 — the same shape the
operator-installed SessionStart hook would produce. With C11_SESSION_RESUME=1
threaded through restore, this walks the full Phase 1 path:
snapshot capture of reserved metadata keys
-> snapshot.restore with restart_registry=phase1
-> AgentRestartRegistry.phase1 synthesizes "cc --resume <uuid>\n"
-> TerminalPanel.sendText delivers the command to s1
-> verified present in s1 scrollback after restore
Two runtime quirks the test had to work around:
1. Background-launched apps (`open -g`) leave Ghostty surfaces detached
until something interacts with them. read-screen returns "Terminal
surface not found" in that state. The test sends a no-op (space +
ctrl-u) to attach the surface before reading.
2. The previous --skip-build run cleanly quit the tagged app. Re-runs now
`open -g` if the socket is missing, before the wait-for-socket loop.
Mocking the metadata directly avoids touching ~/.claude/settings.json,
preserving c11's "unopinionated about the terminal" principle: the test
validates CMUX-37's mechanism without requiring any operator-installed
hook.
The Debug build config had PRODUCT_MODULE_NAME=c11_DEV (derived from PRODUCT_NAME="c11 DEV"). The c11-unit scheme runs tests in Debug, so all 75+ test files that do @testable import c11 fail with "no such module 'c11'" -- the module was named c11_DEV. Pre-existing since PRODUCT_MODULE_NAME was explicitly set. Fix: set PRODUCT_MODULE_NAME=c11 in the Debug build config. The product name "c11 DEV" stays unchanged (dock/menu bar distinction preserved); only the Swift module name is normalized. Also update TomlSubsetParserTests.swift to the conditional import pattern used by the new Blueprint test files (belt-and-suspenders, correct for both module name schemes). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BenevolentFutures
added a commit
that referenced
this pull request
Apr 26, 2026
…Tests import Debug builds set PRODUCT_NAME="c11 DEV" which makes the Swift module name c11_DEV via the default PRODUCT_MODULE_NAME derivation. All 75+ test files use @testable import c11 which fails to find the module under the c11-unit scheme (mailbox-unit CI job) because it builds in Debug mode. Fix: explicitly set PRODUCT_MODULE_NAME=c11 so the module is always named c11 regardless of whether PRODUCT_NAME is "c11 DEV" or "c11". The app product name stays "c11 DEV" for dock/menu bar disambiguation. Also update TomlSubsetParserTests.swift to use the conditional import pattern introduced by PR #79's new Blueprint test files, as belt-and- suspenders defense. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WorkspaceApplyPlanCodableTests and WorkspaceLayoutExecutorAcceptanceTests used canImport(cmux_DEV)/import cmux — the pre-rename module names. After PRODUCT_MODULE_NAME=c11 landed the conditional compiled away entirely, leaving all referenced types (WorkspaceApplyPlan, SurfaceSpec, TabManager, etc.) unresolved. Update to canImport(c11_DEV)/c11 to match all other test files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…apture.swift Took the more informative doc comment from main explaining why defaultVersionString() must be nonisolated. The keyword itself is unchanged on both sides; PR-side walker structure (WorkspacePlanCapture extraction) preserved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BenevolentFutures
added a commit
that referenced
this pull request
Apr 26, 2026
* C11-14: P9 P8 P10 trivial nits
- P8: single clock read in LiveWorkspaceSnapshotSource.capture so the ULID
time prefix in snapshotId and the envelope's createdAt share one tick.
- P9: drop the displayTitle fallback claim from WorkspaceSnapshotCapture doc
comment; the walker only reads panelCustomTitles[panelId] and accepts nil.
- P10: trim terminalType in AgentRestartRegistry.init(rows:) for symmetry
with the trim already in resolveCommand; avoids an asymmetric-trim footgun.
Tests: add ULID-prefix-decodes-to-clock-millis invariant in
WorkspaceSnapshotCaptureTests; add trim-on-insert check in
AgentRestartRegistryTests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P3 registry identity by name
AgentRestartRegistry carries closures and is not Equatable. Identity now
lives on a required `name: String` minted at construction (`"phase1"` for
the canonical singleton, test-chosen names for fixtures). ApplyOptions.==
compares restartRegistry by name: both-nil is equal, both-non-nil-with-same-
name is equal, every other pair is unequal.
- Sources/AgentRestartRegistry.swift: add `name` stored property; require
it as an init parameter; mint `phase1` with `name: "phase1"`.
- Sources/WorkspaceApplyPlan.swift: replace the either-non-nil-is-unequal
branch with a name-match branch.
- c11Tests/AgentRestartRegistryTests.swift: pass `name:` to the two
custom-registry constructors already in the file.
- c11Tests/WorkspaceApplyPlanCodableTests.swift: add three equality
invariants (same name → equal, different names → unequal, nil vs non-nil
→ unequal).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: I6 shared claudeSessionId constant across app and CLI
Sources/WorkspaceMetadataKeys.swift is already pure Foundation and already
declares `SurfaceMetadataKeyName.claudeSessionId`. Adding it to the c11-cli
target's Sources build phase lets the claude-hook session-start path
reference the enum directly instead of duplicating the literal
"claude.session_id".
- GhosttyTabs.xcodeproj/project.pbxproj: new PBXBuildFile entry (c11-cli
build action) pointing at the existing fileRef; add to c11-cli Sources
list next to the other shared files (Mailbox*.swift, SkillInstaller.swift).
- CLI/c11.swift: replace the literal in the claude-hook session-start
metadata-write with SurfaceMetadataKeyName.claudeSessionId; drop the
"kept in lockstep by reader convention" commentary now that the link is
real.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P2 warn on unknown restart_registry wire names
Before: `AgentRestartRegistry.named("phase99")` silently returned nil, and
the v2 snapshot.restore handler fell back to Phase 0 (no cc --resume
synthesis) with no telemetry. A typo in the restart_registry param was
undetectable.
After: the handler detects the named-registry-resolves-to-nil case, captures
a pre-apply warning, and splices it into ApplyResult.warnings before
encoding. Operators see the warning in both JSON output and the CLI's
`warnings:` block. Restore still succeeds — forward-compat with Phase 5
rows is preserved, because an older binary should still land the layout
even if a future registry name is unrecognised.
- Sources/TerminalController.swift: capture preApplyWarnings when
AgentRestartRegistry.named returns nil for a non-nil wire name; prepend
to applyResult.warnings before JSON encode.
Note: a focused unit test for this warning would land cleanly in a
tests_v2/ subprocess test (live snapshot.restore call with an unknown
restart_registry). The tests_v2 handler test scaffold is out of scope for
this commit; noting as a follow-up under F1 in the plan.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P6 orphan-socket / wrong-owner advisory errors
c11 is single-user: the claude-hook writer treats three additional error
shapes as "no live c11 app on this machine for me" (advisory) instead of
`failed`:
- "Permission denied" — EPERM/EACCES from connecting to a socket whose
owner is another uid, or from unix(7) permission checks on the socket
directory.
- "Operation not permitted" — EPERM on stat/unlink of the socket path.
- "Resource temporarily unavailable" — EAGAIN variant when an orphaned
socket file exists but no listener accepts.
Extract the string-shape predicate into Sources/CLIAdvisoryConnectivity.swift
so c11Tests can unit-test the matcher directly. CLI/c11.swift's
isAdvisoryHookConnectivityError now delegates to the shared predicate after
unwrapping the CLIError message. Both the c11 app and c11-cli targets link
the shared file; no behavioural change for app code that never looked at
CLIError.
- Sources/CLIAdvisoryConnectivity.swift: new file with the string predicate.
- CLI/c11.swift: one-line delegator; new matches reach the CLI through it.
- c11Tests/CLIAdvisoryConnectivityTests.swift: three new-shape advisory
cases plus regression guards for the existing shapes and two negative
controls.
- GhosttyTabs.xcodeproj/project.pbxproj: wire the new source into c11,
c11-cli, and c11Tests Sources phases; register the fileRefs.
If multi-user c11 ever lands, revisit this call — wrong-owner should
stop being advisory there.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P1 header-only snapshot list summary
snapshot.list previously decoded the full WorkspaceApplyPlan per file just
to read envelope-level metadata + surface count. That gets slower as
snapshots accumulate in ~/.c11-snapshots/ and scales with embedded-plan
size (layout tree, surface metadata) rather than envelope size.
After: WorkspaceSnapshotStore.enumerate uses a JSONSerialization shallow
decoder that reads only snapshot_id, created_at, c11_version, origin,
surface_count, and probes plan.workspace.title + plan.surfaces.count
without instantiating the Codable tree. An explicit surface_count field
on the envelope caches the count so the fallback shallow probe only fires
on legacy files written before this change.
- Sources/WorkspaceSnapshot.swift: add optional `surfaceCount: Int?`
(wire: `surface_count`) to WorkspaceSnapshotFile. Optional preserves
backwards compat: pre-P1 files decode with nil, new files carry the
count.
- Sources/WorkspaceSnapshotCapture.swift: populate surfaceCount from
plan.surfaces.count in LiveWorkspaceSnapshotSource.capture.
- Sources/WorkspaceSnapshotStore.swift: new private SnapshotSummary type
+ readSummary(from:) that does the shallow decode. enumerate() now
emits rows from the summary, falling back to plan.surfaces.count when
the envelope's surface_count is absent.
Tests (WorkspaceSnapshotCaptureTests):
- testStoreListReadsExplicitSurfaceCountFromEnvelope — verifies the new
file path (envelope count wins, even when it disagrees with the plan).
- testStoreListFallsBackToPlanSurfacesCountWhenEnvelopeLacksKey — verifies
the legacy path (shallow probe into plan.surfaces.count).
Gates I8 (next commit): the same readSummary throw path will catch
unreadable rows and emit them with a `.unreadable` marker.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: I8 surface unreadable snapshot.list rows
Before: snapshot.list silently dropped malformed envelope files (truncated
JSON, not-a-snapshot files, etc.), leaving operators unable to see or
delete them. After: unreadable entries surface as rows with a best-effort
id (filename stem), `createdAt = .distantPast` (sorts to the bottom),
empty title, surface count 0, and a machine-readable `readability`
discriminator carrying the truncated parse error.
- Sources/WorkspaceSnapshot.swift: add `readability: Readability` field
(`.ok` / `.unreadable(String)`) to WorkspaceSnapshotIndex. Tagged
Codable: `{"status": "ok"}` or `{"status": "unreadable", "reason": ...}`.
Forward-compat: older clients that omit `readability` decode as `.ok`.
- Sources/WorkspaceSnapshotStore.swift: replace the silent `continue` with
a `catch` branch that synthesises an unreadable row. Reason truncated to
160 chars to keep the terminal table legible.
- CLI/c11.swift: runListSnapshots emits `UNREADABLE` in the title column
and `?` in the surfaces column for unreadable rows. Full reason prints
after the table under `unreadable: <id> [<path>] — <reason>`. Two new
localized strings: cli.listSnapshots.column.unreadable and
cli.listSnapshots.unreadable.prefix.
Tests (WorkspaceSnapshotCaptureTests):
- testStoreListSurfacesMalformedJSONAsUnreadableRow — verifies the
unreadable row is emitted with the right shape.
- testStoreListSortsHealthyBeforeUnreadable — sort invariant.
- testReadabilityRoundTripsThroughCodable — Codable tagged encoding.
- Retires the "silently skips malformed json" test; new behavior is
opposite by design.
The JSON `readability` field is surfaced by v2SnapshotList verbatim —
no handler change required, since WorkspaceSnapshotIndex is encoded
through its Codable conformance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: I2 restore --in-place
c11 restore --in-place replaces the current workspace's content with the
restored plan instead of creating a fresh workspace. Without the flag,
running c11 restore twice produces two duplicate workspaces with no
warning.
Implementation is deliberately minimum-viable: applyToExistingWorkspace
resolves the target, closes it via TabManager.closeWorkspace, and calls
the existing apply(_:options:dependencies:) path. The target workspace's
panels and splits are closed first; the new workspace inherits the plan.
Caveat: the workspace UUID changes (a fresh workspace is minted and the
prior one is closed). Preserving the original UUID + tab position is
follow-up F4 on the plan, not in scope for C11-14.
- Sources/WorkspaceLayoutExecutor.swift: replace the Phase-0 doc comment
that promised a non-existent applyToExistingWorkspace method with a
real implementation. invalid_params failure surfaces when the target
UUID does not resolve; the v2 handler maps that through ApplyResult.
- Sources/TerminalController.swift: v2SnapshotRestore accepts optional
in_place: Bool and target_workspace_id: UUID string. Branches to the
new executor method when both are supplied; synthesises an
invalid_params failure when in_place is set without a resolvable target.
- CLI/c11.swift: --in-place / --replace flag alias. The CLI resolves the
current workspace id via v2 workspace.current and threads it as
target_workspace_id. Help text (subcommandUsage("restore")) now lists
the flag and the behavioural note. Three localized English strings
(cli.restore.usage.inPlace, cli.restore.usage.inPlace.note).
- skills/c11/references/claude-resume.md: add --in-place usage example
with the UUID-change note.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P4 acceptance suite skips in Release
Both acceptance test methods rely on terminalPendingInput(_:), which
delegates to the DEBUG-only accessor TerminalSurface.pendingInitialInputForTests.
In Release builds the accessor returns nil and the assertions fail with
an opaque "got '' instead" message that is not actionable.
Guard each affected method with #if !DEBUG throw XCTSkip so the suite
reports a clean skip in Release instead of a noisy failure. The private
helper stays unchanged: returning nil in Release remains a defensive
fallback that XCTSkip prevents from ever being observed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P11 thread-safe pendingInitialInputForTests
TerminalSurface is an NSView, which AppKit convention puts on the main
actor but does not enforce at the compiler level. The DEBUG-only
pendingInitialInputForTests accessor reads pendingTextQueue without a
lock; an off-main caller would race the queue's mutation sites. Annotate
the accessor @mainactor so the compiler enforces the main-actor
invariant. Existing callers (the Phase 1 acceptance harness) already run
on the main actor so there's no behavioural change.
Rejected the alternative fix (wrap pendingTextQueue mutations with an
NSLock) because it extends lock discipline into the typing-latency hot
path pendingTextBytes rebalancing. @mainactor is lighter and matches
AppKit's actual threading model for NSView-derived state.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P7 browser-first / markdown-first acceptance fixtures
Phase 1 acceptance puts a terminal first in the layout. Phase 3 will
lean on browser- and markdown-first plans (Blueprints author them
directly), and the walker already handles them; there was no integration
test. P7 adds one fixture per non-terminal kind and two new acceptance
methods that exercise the full capture + write + read + convert +
restore loop.
Each new test asserts:
- The distinguishing surface field round-trips byte-equal (url for
browser, filePath for markdown).
- The trailing terminal still receives the synthesised
`cc --resume <session-id>` via the Phase 1 restart registry.
Both methods route through runMixedFirstFixtureRoundTrip to keep the
test bodies symmetric, and both carry the Release XCTSkip guard from P4
because they read the DEBUG-only pendingInitialInputForTests accessor.
- c11Tests/Fixtures/workspace-snapshots/browser-first-mixed.json (new)
- c11Tests/Fixtures/workspace-snapshots/markdown-first-mixed.json (new)
- c11Tests/WorkspaceSnapshotRoundTripAcceptanceTests.swift:
testCaptureAndRestoreBrowserFirstLayout
testCaptureAndRestoreMarkdownFirstLayout
plus the shared helper. Uses the existing loadFixturePlan /
resolveWorkspace / stringMetadataValue helpers; no new test
infrastructure.
Fixtures are loaded via #filePath + relative path (same pattern as the
existing mixed-claude-mailbox fixture) so no pbxproj edit is required.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: I5 followup — list-snapshots plain-table subprocess test
The earlier I5 fix replaced the broken `%s` printf in runListSnapshots
with `String.padding(toLength:withPad:startingAt:)` but did not add a
test because the c11Tests target can't easily exercise the CLI binary.
This adds the missing coverage via tests_v2/ (same socket-test pattern
the rest of the suite uses).
The test:
1. Creates a new workspace via the v2 client.
2. Captures a snapshot via `c11 snapshot --workspace <id>` (plain output).
3. Runs `c11 list-snapshots` and asserts the header lists all six
columns and the captured id + source=current appear in the row.
4. Runs `c11 list-snapshots --json` and asserts the JSON payload
contains the captured id with a non-negative integer surface_count
that matches an integer token in the plain-table row.
5. Cleans up the snapshot file and the workspace so repeated runs stay
idempotent. Skips cleanly when ~/.c11-snapshots is not writable.
CI's existing tests_v2/ workflow picks this up automatically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: P5 document claude.session_id threat model
Snapshot envelopes store claude.session_id values in cleartext under
~/.c11-snapshots/. The trident review asked for either (a) document the
threat model, (b) redact on capture, or (c) encrypt at rest via
Keychain. Operator decision captured on the C11-14 ticket is (a)
document-and-ship: session ids are transcript-lookup keys, not
credentials, and an attacker who can read the snapshot file already has
access to ~/.claude/projects/ on the same filesystem.
The new `Privacy and storage` section in skills/c11/references/claude-resume.md
spells out the threat model, why Keychain encryption was rejected, and
the hygiene recommendations for operators whose threat model is tighter
than c11's default assumption (restrict permissions, skip shared-volume
backups, delete snapshots after restore).
Docs-only commit; no code change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: sync Localizable.xcstrings for ja/uk/ko/zh-Hans/zh-Hant/ru
Adds translations for the two new English strings introduced in the C11-14
commits (CMUX-37 Phase 1 follow-up):
- cli.listSnapshots.column.unreadable
- cli.listSnapshots.unreadable.prefix
The other keys referenced in the plan (cli.restore.usage.inPlace,
.inPlace.note, .inPlaceAndPath, socket.snapshot.restore.unknownRegistry)
were not wrapped in String(localized:) by the Impl phase, so nothing to
sync for them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: trident-followup B1/B2/B3 + polish
Addresses blockers and polish from the 9-agent trident review pack at
notes/trident-review-C11-14-pack-20260424-1750. PR #77 blockers were
already fixed; these are follow-ups specific to this branch's diff.
Blockers
- F-B1 (single-workspace close no-op): `c11 restore --in-place` on a
window with one workspace used to duplicate. The prior order called
`TabManager.closeWorkspace` (which silently no-ops on `tabs.count <=
1`) and then `apply`, producing two workspaces. Flipped order: apply
first, then close. Tab count is always >= 2 at close time, so the
guard passes. Side benefit: a failed apply is now non-destructive
for the common case (old workspace intact, no phantom replacement).
Sources/WorkspaceLayoutExecutor.swift.
- F-B2 (wrong workspace from operator focus): `--in-place` now resolves
the target via the caller env (CMUX_WORKSPACE_ID / C11_WORKSPACE_ID)
before falling back to `workspace.current`. Background agents on a
non-focused surface previously hit `workspace.current`, which returns
the operator-selected workspace, not the agent's own. Destructive
commands cannot depend on operator focus per the CLAUDE.md socket
focus policy.
CLI/c11.swift.
- F-B3 (list-snapshots --json subcommand-local flag): help has always
advertised `c11 list-snapshots --json`; `runListSnapshots` was
rejecting it as an unknown flag. The new tests_v2 subprocess test
uses the local form and would fail on CI. `--json` is now accepted
both as the global pre-subcommand flag and as a subcommand-local
flag.
CLI/c11.swift.
Important + polish
- workspace_uuid_changed warning: `applyToExistingWorkspace` now
prepends a structured warning to `ApplyResult.warnings` so scripted
consumers see the UUID change in the same channel as pre-apply
warnings. Follow-up F4 will preserve UUIDs in place.
Sources/WorkspaceLayoutExecutor.swift.
- xcstrings keys for --in-place help: `cli.restore.usage.inPlace` and
`cli.restore.usage.inPlace.note` were referenced from
`String(localized:)` call sites but never added to
`Localizable.xcstrings`. Non-English builds fell back to the English
defaults. English-only entries added; translator pass picks up the
other six locales.
Resources/Localizable.xcstrings.
- WorkspaceSnapshotStore.enumerate doc comment: said "skipped silently"
even though I8 emits `unreadable` rows. Comment updated to reflect
the implementation.
Sources/WorkspaceSnapshotStore.swift.
- Em-dashes in user-facing CLI output replaced with colons: the
unreadable-row print and the unknown-restart_registry warning both
use plain punctuation now.
CLI/c11.swift, Sources/TerminalController.swift.
Tests
- c11Tests/WorkspaceLayoutExecutorAcceptanceTests.swift: +4 tests
exercising `applyToExistingWorkspace`:
- testInPlaceRestoreReplacesSingleWorkspaceWithoutDuplicating
(F-B1 regression: count == 1 after replace, original closed)
- testInPlaceRestoreReplacesTargetAndLeavesSiblingIntact
(multi-workspace happy path: count stable, sibling untouched)
- testInPlaceRestoreMissingTargetReturnsInvalidParams
(non-destructive failure on bogus UUID)
- testInPlaceRestoreValidationFailureLeavesTargetIntact
(unsupported version short-circuits before any state change)
Out of scope / surfaced for delegator
- I2 atomic rollback on post-close apply failure: the order flip
already covers the common failure mode; full atomicity (snapshot
the old, swap, rollback on partial apply) is F4 / Phase 2 scope.
- tests_v2 cmux-era socket paths and binary globs: pre-existing
pattern across the entire tests_v2 suite. Fixing one file creates
inconsistency; bulk migration is its own change.
- `readSummary` vs `read(from:)` created_at "disagreement" claim
(Standard Claude #4): both paths share the same two-format fallback
(workspaceSnapshotDateFormatter, then withInternetDateTime). Misread.
- `readSummary` "not truly header-only": JSONSerialization parses the
full envelope object tree. Performance scope; deferred.
- snapshot.list JSON timestamp precision (second-precision .iso8601):
consistency with the store's fractional-second writes. Deferred.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: trident-followup additional fixes
Three more concrete bugs called out by the review pack that I missed in
the first follow-up commit. These are real round-trip / output bugs,
not polish.
- Empty snapshot_id for `.json` files: a file literally named `.json`
produced an unreadable row with `snapshotId = ""` because
`deletingPathExtension().lastPathComponent` collapses to the empty
string. Fall back to the full filename (capped at 128 chars) so the
row is identifiable in the plain-table column. Test added in
WorkspaceSnapshotCaptureTests covers the degenerate filename.
Sources/WorkspaceSnapshotStore.swift,
c11Tests/WorkspaceSnapshotCaptureTests.swift.
(Crit Gemini #3, Std Codex #5, Crit Codex nit on filename stem.)
- `snapshot.list` JSON timestamp precision: the v2 handler encoded
`WorkspaceSnapshotIndex.createdAt` with `.iso8601`, which drops
subsecond precision. The store now writes fractional seconds via
`workspaceSnapshotDateFormatter`, so the list response was
silently truncating timestamps that the store had just written.
Switched the encoder to a custom strategy that uses the shared
formatter, matching the round-trip the rest of the store path
already provides.
Sources/TerminalController.swift.
(Crit Codex nit on `v2SnapshotList`.)
- Honest `readSummary` doc comment: the prior comment claimed nested
`plan.layout` and `plan.surfaces[].metadata` "stay in the JSON blob
untouched". `JSONSerialization.jsonObject` actually materialises the
full object graph into NSDictionary/NSArray; the win is skipping the
`WorkspaceApplyPlan` Codable decode, not avoiding the parse. Comment
reworded to describe the real cost shape so future readers don't
trust the false claim.
Sources/WorkspaceSnapshotStore.swift.
(Crit Gemini #1, Crit Codex nit on memory shape.)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* C11-14: extract skipIfReleaseBuild helper for round-trip tests
Std Claude #13: the four acceptance scenarios in
WorkspaceSnapshotRoundTripAcceptanceTests carried an identical 3-line
`#if !DEBUG / throw XCTSkip(...) / #endif` preamble. Extracted a
private `skipIfReleaseBuild()` helper so each call site is one line
and a future skip-reason change is one place, not four.
No behavior change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: nonisolated on defaultVersionString to satisfy strict concurrency
The default parameter in LiveWorkspaceSnapshotSource.init called
defaultVersionString() from a nonisolated context. Under the c11-unit
scheme (which enables strict concurrency checking), this failed because
the @mainactor isolation on the enclosing class made the static method
implicitly @mainactor too.
Adding nonisolated makes the default-parameter callsite legal without
requiring the caller to be on the main actor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: set PRODUCT_MODULE_NAME=c11 in Debug build; fix TomlSubsetParserTests import
Debug builds set PRODUCT_NAME="c11 DEV" which makes the Swift module name
c11_DEV via the default PRODUCT_MODULE_NAME derivation. All 75+ test files
use @testable import c11 which fails to find the module under the c11-unit
scheme (mailbox-unit CI job) because it builds in Debug mode.
Fix: explicitly set PRODUCT_MODULE_NAME=c11 so the module is always named
c11 regardless of whether PRODUCT_NAME is "c11 DEV" or "c11". The app
product name stays "c11 DEV" for dock/menu bar disambiguation.
Also update TomlSubsetParserTests.swift to use the conditional import
pattern introduced by PR #79's new Blueprint test files, as belt-and-
suspenders defense.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Resolves four conflicts after #78 (CMUX-37 Phase 1 follow-up) landed on main while this PR was open. All resolutions preserve both sides' intent. - Sources/WorkspaceSnapshotCapture.swift: keep PR's extracted WorkspacePlanCapture.capture(workspace:) call AND main's P8 single clock-read invariant (let now = clock()) so snapshotId ULID prefix and createdAt cannot diverge by a tick. - Sources/AgentRestartRegistry.swift: keep main's required name parameter (.init(name: "phase1", rows: [...])) and merge both doc comments — main's rationale for the trailing newline contract plus the PR's note on codex --last best-effort and opencode/kimi launch-fresh semantics. - c11Tests/AgentRestartRegistryTests.swift: keep both PR's three Phase 5 row tests (codex/opencode/kimi) and main's symmetric-trim invariant test. - GhosttyTabs.xcodeproj/project.pbxproj: union both sides' new test file refs (PR's three Blueprint/RoundTrip tests + main's CLIAdvisoryConnectivity test) in both PBXGroup and PBXSourcesBuildPhase.
This was referenced Apr 26, 2026
Merged
8 tasks
BenevolentFutures
added a commit
that referenced
this pull request
May 4, 2026
* CMUX-37: capture orchestrator handoff plan + lattice state Final-Push Plan section drafted by orchestrator pre-handoff covers all five workstreams (markdown blueprints, manifest layer, diagnostic cleanup, workspace --help routing, C11_SOCKET safety) with commit grouping, parallelization table, acceptance criteria, and Do-NOT-ship list. Delegator phase begins from this state. * CMUX-37: refresh plan — five-workstream close-out Captures the prior delegator session's plan rewrite plus the Lattice state changes (status/comments) that went with it. The plan replaces the older phase-based shape with a single five-workstream close-out PR: 1. Markdown blueprint format 2. Snapshot manifest layer for `--all` 3. Restore diagnostic cleanup 4. `c11 workspace --help` routing fix 5. CLI socket safety (`C11_SOCKET` + auto-discovery breadcrumb) Driven by the 2026-05-03 smoke validation report (/tmp/cmux-37-smoke-report.md) on tagged build cmux-37-smoke. Original ticket Phases 0–5 already shipped via PRs #75 and #79. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * CMUX-37: workspace --help routing — two-level dispatch for `c11 workspace <sub> --help` `c11 workspace new --help` previously printed `Unknown command 'workspace'` because the help dispatcher had no entry for `workspace`. Add a `case "workspace":` to subcommandUsage that recognizes the three real subcommands (apply, new, export-blueprint), thread `commandArgs` through so the sub-token resolves, and update dispatchSubcommandHelp to include the resolved subcommand in the header. Top-level usage gains the four workspace lines too. Workstream 4 of the five-workstream close-out (smoke report 2026-05-03). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * CMUX-37 Impl A: blueprint markdown parser + writer New `Sources/WorkspaceBlueprintMarkdown.swift` provides Foundation-only parse/serialise for the operator-approved markdown blueprint format (Obsidian-friendly frontmatter + fenced YAML layout block). A vendored tiny YAML subset parser handles the indented mapping/list grammar the schema needs without an external SwiftPM dependency. `WorkspaceBlueprintStore.read(url:)` and `write(_:to:)` now dispatch on the URL extension: `.md` files round-trip through the new parser, JSON files keep the existing path. `indexEntries` reads the markdown frontmatter to populate name and description (falling back to filename stem when the frontmatter is missing or unparseable), so .md blueprints appear in the picker with their authored titles. Round-trip semantics: name ↔ frontmatter title; description ↔ frontmatter description; workspace.customColor ↔ custom_color; workspace.title falls back to frontmatter title unless `workspace_title` is set explicitly. SurfaceSpec metadata, paneMetadata, and description are dropped on serialise — markdown is the hand-authored, legible form; fully-faithful captures stay on the JSON path. Per-repo and per-user discovery still scan only the legacy `.cmux` paths in this commit; the filesystem rename to `.c11`/`~/.config/c11/blueprints/` lands in the next commit alongside the exporter and CLI integration. Tests cover round-trip equality at the value level (single-leaf, horizontal split, nested split mirroring the operator schema), custom color preservation, separate workspace_title, schema-example parse, and error surface for missing layout section / missing fenced block / unknown surface kind / split without children. Plus a store-level .md round-trip and an updated .md picker test. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * CMUX-37 Impl A: c11 directory + markdown by default; legacy paths still read Switch the canonical blueprint write target from ~/.config/cmux/blueprints/<name>.json to ~/.config/c11/blueprints/<name>.md. Markdown is the default format; operators who still want JSON pass --format json (also threaded over the v2 workspace.export_blueprint method as a format param). Per-user discovery now scans both the new and the legacy directory; the new directory ships first so the picker prefers canonical files when both exist. Per-repo discovery walks .c11/blueprints/ alongside .cmux/blueprints/ at each ancestor — .c11/ entries lead the merged list. Legacy JSON envelopes at ~/.config/cmux/blueprints/*.json remain discoverable and applicable; the smoke acceptance criteria require this. runWorkspaceBlueprintNew and workspaceBlueprintPicker now sniff the file extension on --blueprint / picker selection. .md files are routed through a new pure workspace.parse_blueprint v2 method that parses content received in memory and returns the plan dict — keeping the markdown parser inside the app's link surface, not the CLI binary, and not turning the parse path into another arbitrary-file-read. .json files keep the existing in-process JSONSerialization path. Help text under `c11 workspace new --help` and `c11 workspace export-blueprint --help` is updated to name the c11 canonical paths, list both file extensions, document --format, and show an .md example. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * CMUX-37 Impl B: cli — honor C11_SOCKET; log auto-discovery to stderr Workstream 5 of the CMUX-37 close-out. Closes the wrong-socket footgun the smoke validator hit on 2026-05-03: orchestrator set C11_SOCKET=..., CLI read CMUX_SOCKET_PATH instead, fell through to auto-discovery via last-socket-path, and silently mutated the operator's live workspace. Changes: - Socket-path resolution precedence is now: --socket → C11_SOCKET → CMUX_SOCKET_PATH → CMUX_SOCKET → auto-discovery. Both legacy keys remain accepted for back-compat (no deprecation warning yet — the plan defers that). - Three call sites updated consistently: defaultSocketPath, run()'s env-var loop, claudeTeamsResolvedSocketPath. CLISocketSentryTelemetry also reads C11_SOCKET first so the breadcrumb context is accurate. - After socket.path.autodiscovered fires, c11 now prints one stderr line attributing the resolved socket. CLISocketPathResolver gains a best-effort discoverySourceHint() that names the pointer file (last-socket-path) when the resolver matched it, or the CMUX_TAG=... derivation when matching a tag-derived candidate. Falls back to a generic "(auto-discovered)" suffix when the source is unclear. - C11_QUIET_DISCOVERY=1 (any non-empty, non-"0" value) suppresses the stderr line. A global --quiet flag would touch every command's print loop; that's deferred per the plan's "Out of scope" note. - usage() gains a Socket Path subsection and the Environment block now documents C11_SOCKET, CMUX_SOCKET_PATH, CMUX_SOCKET, and C11_QUIET_DISCOVERY. New String(localized:) keys for the Translator phase: - cli.socket.autoDiscoveredWithSource — "c11: using socket %@ (auto-discovered from %@)" - cli.socket.autoDiscovered — "c11: using socket %@ (auto-discovered)" Build: c11-cli scheme compiles clean against impl-A's commit 7911a41 at HEAD. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * CMUX-37 Impl B: cli — classify expected restore diagnostics as info Workstream 3 of the CMUX-37 close-out, apply-side / CLI-side. The smoke validator on 2026-05-03 saw `c11 restore <id>` exit 0 but emit seven `failure:` lines for behaviors the executor deliberately reports on every clean round trip — six `metadata_override` (one per surface with a title) plus one `working_directory_not_applied` for the seed terminal. Operator framing: stop scaring automation that grep-greps for `failure:`. Approach: Option B from the plan (CLI-side reclassification). The ApplyFailure wire shape stays bit-exact — only the human-readable print classifies. A future caller with a different opinion can read the structured payload and ignore the classification. Changes (CLI/c11.swift): - Add CMUXCLI.FailureSeverity (.failure | .info) and two static helpers: failureSeverity(code:message:) and partitionFailures(_:). partitionFailures preserves the original order for stable output. - Two codes get reclassified as .info: - `metadata_override` — fires when SurfaceSpec carries both .title and metadata["title"]. The capture-side fix (next commit) strips the dup; this is the belt for the suspenders. - `working_directory_not_applied` whose message contains "seed terminal reuse" — fires on every restore whose first terminal had a captured cwd. The seed shell is already running by the time the executor sees the spec, so the cwd cannot apply. Other emission sites (browser/markdown split, in-pane creation) stay classified as real failures. - Three print sites updated to partition-then-print: - runWorkspaceApply (~L2975) - runWorkspaceBlueprintNew (~L3045) - runSnapshotRestore (~L3466) Real failures keep their existing prefix ("failures: N" or " failure: ..."); info-level entries print under "info: N" or " info: ...". Format consistency with the existing prefix per call site is preserved. - DEBUG-only test hooks (debugFailureSeverityForTesting, debugPartitionFailuresForTesting) on CMUXCLI for future c11-cli test infrastructure. CAUTION: detection of the seed-terminal case relies on the executor's emitted message containing the substring "seed terminal reuse". If Sources/WorkspaceLayoutExecutor.swift's reportWorkingDirectoryNotApplicable call for the seedTerminal context (around L656) ever changes its `context:` argument, update the matcher in failureSeverity(). Tests: skipped — CMUXCLI lives in the c11-cli target only; c11Tests links the c11 module, so unit-testing partitionFailures from there would require adding c11.swift (~17k LoC) to the test target's build phase or a new shared file with cross-target build-phase membership. The classifier is testable via the smoke fixture during the Validate phase (acceptance criterion: `c11 restore <id>` on the mixed 6-surface fixture prints `failures: 0`). DEBUG hooks are wired so future c11-cli test infrastructure can exercise the partition. No new String(localized:) keys. Existing print prefixes (`failures:`, `failure:`) are not localized in the surrounding code, so the new `info:` prefix follows that convention. Build: c11-cli scheme compiles clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * CMUX-37 Impl B: capture — drop redundant title metadata to silence override warnings Workstream 3 of the CMUX-37 close-out, capture-side. Pair to the preceding CLI-side reclassification (3629b31). Together they close the gap where every clean snapshot/restore round trip emitted six bogus `metadata_override` "failure:" lines. Root cause: - Sources/WorkspaceLayoutExecutor.swift has dedicated setters for the canonical `title` (workspace.setPanelCustomTitle) and `description` (a separate write) fields. After they fire, it iterates spec.metadata and re-writes everything via SurfaceMetadataStore in merge mode. When metadata["title"] is also set and spec.title is non-nil, the executor flags this as an override even though the values usually agree — the dup is a round-trip artifact, not a real conflict. - WorkspacePlanCapture reads `surfaceMetadata` (which contains the canonical "title" key, written through SurfaceMetadataStore by setPanelCustomTitle) AND sets SurfaceSpec.title from workspace.panelCustomTitles. Both make it onto the wire; the executor sees both and warns on every restore. Fix: - Add a private strippingRedundantCanonicalFields helper to WorkspacePlanCapture.Walker. It drops metadata["title"] (and metadata["description"]) when the metadata value matches the canonical SurfaceSpec field exactly — preserving the divergent case (operator wrote `set-metadata --key title --value "Foo"` separate from `set-title "Bar"`) where the override warning is a real signal. - Call the helper from walkPane before constructing SurfaceSpec. Description is unconditionally nil from capture today, so the description branch is a no-op for now; the symmetry is intentional for when capture starts promoting description into spec.description. Build: c11 scheme compiles clean against my WS3a commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * CMUX-37 Impl A: snapshot manifest on --all; restore polymorphic on manifest id `c11 snapshot --all` keeps writing N independent per-workspace files and now ALSO writes a manifest at `~/.c11-snapshots/sets/<set_id>.json` listing the inner ids with set-level metadata (set id, capture time, c11 version, which workspace was selected). The new `WorkspaceSnapshotSetFile` envelope is a pure pointer file — each inner snapshot stays independently restorable through the existing `c11 restore <inner-id>` path; nothing is duplicated. `c11 restore <id>` is now polymorphic. The CLI probes `~/.c11-snapshots/sets/<id>.json` first; when that exists the request goes to the new `snapshot.restore_set` v2 method, which reads the manifest and runs each inner snapshot through `WorkspaceLayoutExecutor` in capture order, re-establishing the selected workspace at the end (subject to the socket focus policy). `--in-place` is rejected for set restores: a set creates several fresh workspaces and there is no single target to replace. Path-on-disk targets are classified by content (`set_id` + `snapshots` → manifest, `snapshot_id` + `plan` → single snapshot) and staged into the matching default location before the v2 call. `c11 list-snapshots` grows two flags: `--sets` switches to the manifest table (SET_ID, CREATED_AT, COUNT, C11_VERSION); `--all` prints both tables back-to-back. The default output is unchanged. New v2 methods: `snapshot.restore_set`, `snapshot.list_sets`. The `sets/` subdirectory is naturally excluded from per-snapshot enumeration (the existing `enumerate(directory:source:)` is non-recursive). New unit test rounds the manifest envelope through Codable, asserts snake_case wire keys, and confirms `selected: false` is wire-omitted on the common case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WorkspaceBlueprintFile+WorkspaceBlueprintStore(per-repo / per-user / built-in search path merge) +WorkspaceBlueprintExporter+ three built-in starter blueprints (basic-terminal,side-by-side,agent-room).WorkspacePlanCaptureextracted as shared helper. CLI:c11 workspace new [--blueprint <path|name>](interactive numbered picker when no arg) andc11 workspace export-blueprint. Socket:workspace.list_blueprints,workspace.export_blueprint(withBLUEPRINT_ALREADY_EXISTSguard +--force). Picker UI strings localized for all 7 locales.c11 snapshot --allcaptures every workspace in one call (CLI + socketall:bool param). New tests: browser and markdown surface spec round-trips throughWorkspacePlanCapture.docs/workspace-apply-plan-schema.md— full field reference.~/.claude/skills/c11/SKILL.mdcovering snapshot/restore/blueprint commands and theagent.*.session_idconvention.AgentRestartRegistryrows for codex (codex --last), opencode (bare), kimi (bare) + tests. B1 fix: correct codex command (--lastvs wrong flag).isRegularFilecheck, JSON-mode picker error, overwrite guard.What's already shipped (Phase 0 + 1, PRs #75 + #77)
WorkspaceApplyPlan,WorkspaceLayoutExecutor,WorkspaceSnapshotCapture/Restore,AgentRestartRegistryclaude-code row, Phase 1 tests.Test plan
c11 snapshotandc11 snapshot --allproduce valid JSON snapshotsc11 restore <id>replays layoutc11 workspace list-blueprintsreturns built-in entriesc11 workspace newshows interactive picker;--blueprint basic-terminalopens single-terminal workspacec11 workspace export-blueprint --name foo --out /tmp/foo.jsonwrites valid blueprint file--forcereturnsBLUEPRINT_ALREADY_EXISTS;--forceoverwritesc11 workspace new --blueprint /tmp/foo.jsonopens the exported layout--jsonwithout--blueprintonworkspace newreturns JSON error (PICKER_NOT_SUPPORTED_IN_JSON_MODE)Tagged build for smoke-test
🤖 Generated with Claude Code