refactor: path-based library identity and server-aligned MCP trace#64
refactor: path-based library identity and server-aligned MCP trace#64lilhammerfun merged 5 commits intomainfrom
Conversation
The prompts table carried both canonical_name and a kind enum, but path already encoded category via the prefix and rename had no room in the model. Swap canonical_name + kind for a single path column with UNIQUE(org_id, path), and have the library API accept prompt_id or path as the lookup key while returning path in responses. Prompt PRs now support multiple operations in a single atomic request. The single-op fields (prompt_id/base_hash/content) move off prompt_prs into a new prompt_pr_operations table. POST /api/org/prompt-prs accepts an operations array with modify/rename/create/delete entries, and PUT applies all operations inside a transaction, bumping library_manifest and workspace revisions at the end. Trace open_pr_count joins through prompt_pr_operations. Seed and pump emit the new schema; sync_cmd reads path from the library response instead of canonical_name. The seed trace event array drops begin and complete since those types are no longer part of the MCP model.
Context previously used server-side long-lived member branches where
each user pushed PUT /context/file to their own branch before creating
a PR. Rename and delete had no first-class operation, and rebase
required explicit API calls. Move context to the same model used for
prompts: a single main row per path, plus PR-scoped operations.
context_files gains a stable context_id primary key with UNIQUE(ws_id,
path). context_branches and context_pr_files are removed; a new
context_pr_operations table stores per-PR modify/rename/create/delete
entries. The status CHECK for context_prs becomes (open, merged,
rejected, conflicted) so it matches the prompt PR vocabulary.
POST /api/workspaces/{ws_id}/context/prs now takes an operations array
and handleUpdatePr applies them inside a transaction, rolling back to
conflicted state if any base_hash no longer matches. The branches,
rebase, and PUT/DELETE file routes are removed from the router.
Seed and pump emit the new schema.
Replace the client-local TraceEvent shape with a struct that mirrors
the server's TraceEventInput one-to-one (ws_id, session_id, event_id,
type, timestamp, plus optional prompt_id/prompt_hash/constraint_id/
override_base_hash/reason/content/content_hash). trace.jsonl lines
are now valid POST /api/traces request body elements directly, no
translation needed at upload time.
Move trace storage from ~/.clumsies/log/{sha256}.jsonl to
~/.clumsies/workspaces/{ws_id}/trace.jsonl, where ws_id comes from
config.toml via workspace_config.resolveWorkspace. The SHA-256
workspaceId helper, LOG_DIR constant, and the unused stats functions
(computeWorkspaceStats, computePromptStats, etc.) are deleted.
appendTraceEvent now takes the event directly and derives the path
from event.ws_id. trace.cursor is bootstrapped to "0\n" on first
write so the upload worker has a stable cursor.
MCP gains a Session struct (ws_id, session_id, atomic event_counter)
held on the server State. handleSetup resolves the workspace binding
and initializes the session lazily; on resolveWorkspace failure it
returns a structured tool error telling the user to run 'clumsies
init'. handleSearch/handleLoad/handleRefer take session_ptr and
allocate event_ids via the counter. Handlers tolerate an absent
session by skipping the trace write, which keeps stateless test
fixtures working without a config.toml.
The seed SQL now inserts prompts with a path column instead of canonical_name and kind, and new library assertions exercise both GET ?prompt_id= and GET ?path= lookups. Prompt PR coverage is added end-to-end: create with modify, reject on stale base_hash and empty operations, maintainer accept, then rename followed by verification that prompt_id stays stable while path changes. Context PR tests are rewritten around the multi-op model. The branch endpoint, PUT /context/file, DELETE /context/file and rebase calls are deleted and replaced with regressions that assert those routes now return 404. Create flow goes through POST /context/prs with a create operation, merge brings the file onto main, and read is exercised via both ?path= and ?context_id= query parameters. The seed helper now falls back to `docker exec` into the docker-compose postgres container when psql is not on PATH, so the script can run on hosts without a local PostgreSQL client.
There was a problem hiding this comment.
Pull request overview
Refactors Hub library + workspace context APIs to use stable IDs with path-based identity, and aligns local MCP trace output with the server POST /api/traces TraceEventInput JSONL format.
Changes:
- Reworks prompts/context to use
prompt_id/context_idas stable PKs with mutablepath, plus multi-operation PR models for both prompts and context. - Updates MCP tracing to write server-compatible JSONL events under
~/.clumsies/workspaces/{ws_id}/with per-session monotonicevent_id. - Updates seeders and E2E coverage to match the new schema/endpoints and removed context-branch routes.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| test/hub-e2e.sh | Updates E2E flow for path/prompt_id lookups and multi-operation prompt/context PR APIs. |
| src/seed/reset.zig | Updates reset seeding for new prompts/context schema + PR operation tables. |
| src/seed/pump.zig | Updates background seed “pump” writes for context_id + prompt PR operations. |
| src/seed/faker.zig | Switches prompt identity generation from canonical name to path (.md). |
| src/seed/data.zig | Updates seeded status/event type constants (context PR statuses, trace events). |
| src/mcp/server.zig | Adds per-process session lifecycle management to MCP server state. |
| src/mcp/handlers.zig | Initializes workspace-bound session, allocates event IDs, and emits server-shaped trace events. |
| src/lib/trace.zig | Moves trace storage to per-workspace directory, adds cursor bootstrap, and serializes server-compatible JSONL lines. |
| src/hub/trace.zig | Updates trace stats queries and ingestion assumptions for the new PR operations model. |
| src/hub/server.zig | Removes deprecated context branch/file routes from the router. |
| src/hub/library.zig | Updates library endpoints to use path and support prompt_id/path lookups. |
| src/hub/db.zig | Updates DB schema for path-based prompts, context_id files, and PR operation tables. |
| src/hub/context.zig | Replaces branch-based context editing with operations-based context PR creation/merge/reject. |
| src/hub/collab.zig | Replaces single-prompt PR payloads with multi-operation prompt PRs and merge application. |
| src/commands/sync_cmd.zig | Syncs prompts by prompt_id and writes cache files using server-provided path. |
Comments suppressed due to low confidence (1)
src/lib/trace.zig:95
- Trace persistence behavior changed substantially (new per-workspace directory +
trace.cursorbootstrapping + append semantics), but the tests only cover path formatting and JSON serialization. Add a unit test that exercisesappendTraceEventend-to-end (creates dirs/cursor, appends multiple lines without truncation) to prevent regressions.
/// Append a trace event to ~/.clumsies/workspaces/{ws_id}/trace.jsonl.
/// The JSON format matches the server `POST /api/traces` TraceEventInput shape.
pub fn appendTraceEvent(allocator: std.mem.Allocator, event: TraceEvent) !void {
try ensureWorkspaceDir(allocator, event.ws_id);
try ensureCursorFile(allocator, event.ws_id);
const trace_path = try traceFilePath(allocator, event.ws_id);
defer allocator.free(trace_path);
var file = try std.fs.createFileAbsolute(trace_path, .{ .truncate = false });
defer file.close();
writeTraceEvent previously took &(session_ptr.* orelse return),
which is the address of a temporary copy of the optional payload
rather than the stored Session. Replace it with an explicit null
check followed by &session_ptr.*.?, so nextEventId() increments
the real counter on the server State. While here, drop the silent
catch {} on trace.appendTraceEvent and log the error with the event
type and session id so trace write failures stop being invisible.
applyPr for context PRs wrote UPDATE context_prs SET status =
'conflicted' inside the same transaction it was about to roll back,
so the conflicted marker never persisted. Both rename and modify
paths are fixed by removing the dead UPDATE entirely: a stale
base_hash now just returns 409 CONFLICT and leaves the PR open,
matching how prompt PRs already behave. The 'conflicted' value is
also removed from the context_prs status CHECK and from the seed
CONTEXT_PR_STATUSES list so the schema and prompt PR vocabulary
stay consistent.
|
@copilot review the latest commit |
Reviewed |
Summary
Two coordinated refactors bundled together because they both rewrite
the prompts/context schema and the seed module at once.
Library identity. prompts and context_files now use a stable
prompt_id/context_idprimary key plus a mutablepathcolumn.canonical_name, kind, branch_name, and the server-side long-lived
context branches are removed. Library lookups accept either
?prompt_id=or?path=, and PR endpoints (POST /api/org/prompt-prsand POST /api/workspaces/{ws_id}/context/prs) take an operations
array containing modify/rename/create/delete entries. The merge
handler applies all operations inside a single transaction; if any
base_hash no longer matches the library, the whole PR rolls back and
the PR is marked conflicted. This promotes rename, batch
reorganization, and file creation to first-class operations, and
replaces the asymmetric "context via server branches, prompts via
local overrides" split with a single client drafts → PR → review →
merge flow on both sides.
MCP trace format. Local trace.jsonl is now byte-compatible with
the server's POST /api/traces request body: each line is a complete
TraceEventInput, so an upload worker can pipe lines straight through
without translation. The SHA-256 workspaceId helper is gone — ws_id
comes from
config.tomlviaworkspace_config.resolveWorkspace(cwd)on the first memory.setup call, returning a structured tool error if
the workspace is not bound. Trace files move from
~/.clumsies/log/{hash}.jsonlto~/.clumsies/workspaces/{ws_id}/trace.jsonl, and atrace.cursorfile is bootstrapped to
0\non first write. MCP gains a Sessionstruct on the server State holding ws_id, session_id, and an atomic
event_counter, so every search/load/refer write gets a
monotonically-allocated event_id scoped to the MCP process lifetime.
Breaking change: existing databases must be dropped and reseeded.
The project is still pre-release and no migration SQL is provided.
This PR lands the server-side schema and APIs only; the client-side
drafts directory, upload worker, and cc-plugin updates are separate
efforts.
Test plan
zig build && zig build test && zig fmt --check src/— unittests and formatting pass
docker compose up -d postgres, startclumsies-hub, run./test/hub-e2e.sh ./zig-out/bin/clumsies-hub— full assertionsuite passes, covering library path/prompt_id lookups, prompt
PR modify + rename flow, context PR create/merge/reject,
reading context files by both
context_idandpath, andregressions that
GET /context/branches,PUT /context/file,DELETE /context/fileand the rebase route all return 404clumsies initin a sample dir, thenclumsies mcp, trigger arefer, confirm
~/.clumsies/workspaces/{ws_id}/trace.jsonlmatches the server TraceEventInput shape byte-for-byte