Skip to content

refactor: path-based library identity and server-aligned MCP trace#64

Merged
lilhammerfun merged 5 commits intomainfrom
refactor/identity-and-trace-alignment
Apr 13, 2026
Merged

refactor: path-based library identity and server-aligned MCP trace#64
lilhammerfun merged 5 commits intomainfrom
refactor/identity-and-trace-alignment

Conversation

@lilhammerfun
Copy link
Copy Markdown
Owner

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_id primary key plus a mutable path column.
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-prs
and 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.toml via workspace_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}.jsonl to
~/.clumsies/workspaces/{ws_id}/trace.jsonl, and a trace.cursor
file is bootstrapped to 0\n on first write. MCP gains a Session
struct 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/ — unit
    tests and formatting pass
  • docker compose up -d postgres, start clumsies-hub, run
    ./test/hub-e2e.sh ./zig-out/bin/clumsies-hub — full assertion
    suite passes, covering library path/prompt_id lookups, prompt
    PR modify + rename flow, context PR create/merge/reject,
    reading context files by both context_id and path, and
    regressions that GET /context/branches, PUT /context/file,
    DELETE /context/file and the rebase route all return 404
  • Manual MCP end-to-end with a real workspace binding: run
    clumsies init in a sample dir, then clumsies mcp, trigger a
    refer, confirm ~/.clumsies/workspaces/{ws_id}/trace.jsonl
    matches the server TraceEventInput shape byte-for-byte

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.
Copilot AI review requested due to automatic review settings April 12, 2026 18:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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_id as stable PKs with mutable path, 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 monotonic event_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.cursor bootstrapping + append semantics), but the tests only cover path formatting and JSON serialization. Add a unit test that exercises appendTraceEvent end-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.
@lilhammerfun
Copy link
Copy Markdown
Owner Author

@copilot review the latest commit 738026e40d6667fc30f9fb5a30d3123c847bb79e, and check if fixing the previous bug you reviewed on the first round.

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 13, 2026

@copilot review the latest commit 738026e40d6667fc30f9fb5a30d3123c847bb79e, and check if fixing the previous bug you reviewed on the first round.

Reviewed 738026e. The writeTraceEvent fix is correct now: it unwraps the optional and increments the stored session counter, and trace write failures are no longer silently swallowed. For the old context-PR rollback bug, this commit resolves it by removing the rolled-back status = 'conflicted' write and standardizing on 409 CONFLICT while leaving the PR open, which is now consistent with the current schema. CI/Test/Hub E2E runs for 738026e are green.

@lilhammerfun lilhammerfun merged commit 33c087f into main Apr 13, 2026
6 of 7 checks passed
@lilhammerfun lilhammerfun deleted the refactor/identity-and-trace-alignment branch April 13, 2026 06:22
Copilot stopped work on behalf of lilhammerfun due to an error April 13, 2026 06:22
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.

3 participants