Skip to content

Commit 225191c

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern A batch 4)
Pattern A migration (lsp-mcp, nesy-mcp, observe-mcp, proof-mcp, queues-mcp). boj_cartridge_invoke dispatch added via cartridge_shim. Tests passing per cartridge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3e78f5a commit 225191c

10 files changed

Lines changed: 439 additions & 0 deletions

File tree

cartridges/lsp-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ pub fn build(b: *std.Build) void {
99
const target = b.standardTargetOptions(.{});
1010
const optimize = b.standardOptimizeOption(.{});
1111

12+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
13+
14+
const shim_mod = b.addModule("cartridge_shim", .{
15+
16+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
17+
18+
.target = target,
19+
20+
.optimize = optimize,
21+
22+
});
23+
1224
const lsp_mod = b.addModule("lsp_ffi", .{
1325
.root_source_file = b.path("lsp_ffi.zig"),
1426
.target = target,
1527
.optimize = optimize,
1628
});
1729

30+
lsp_mod.addImport("cartridge_shim", shim_mod);
31+
1832
// ── Tests ────────────────────────────────────────────────────────
1933
const lsp_tests = b.addTest(.{
2034
.root_module = lsp_mod,

cartridges/lsp-mcp/ffi/lsp_ffi.zig

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,53 @@ pub export fn boj_cartridge_version() [*:0]const u8 {
240240
return "0.1.0";
241241
}
242242

243+
// ═══════════════════════════════════════════════════════════════════════
244+
// ADR-0006 dispatch (boj_cartridge_invoke, 5th standard symbol)
245+
// ═══════════════════════════════════════════════════════════════════════
246+
247+
const shim = @import("cartridge_shim");
248+
249+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha — each arm
250+
/// returns a stub JSON body shaped to the tool's intended response.
251+
export fn boj_cartridge_invoke(
252+
tool_name: [*c]const u8,
253+
json_args: [*c]const u8,
254+
out_buf: [*c]u8,
255+
in_out_len: [*c]usize,
256+
) callconv(.c) i32 {
257+
_ = json_args;
258+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
259+
260+
const body: []const u8 = if (shim.toolIs(tool_name, "lsp_start"))
261+
"{\"result\":{\"status\":\"stub\"}}"
262+
else if (shim.toolIs(tool_name, "lsp_initialize"))
263+
"{\"result\":{\"status\":\"stub\"}}"
264+
else if (shim.toolIs(tool_name, "lsp_open"))
265+
"{\"result\":{\"status\":\"stub\"}}"
266+
else if (shim.toolIs(tool_name, "lsp_change"))
267+
"{\"result\":{\"status\":\"stub\"}}"
268+
else if (shim.toolIs(tool_name, "lsp_close"))
269+
"{\"result\":{\"status\":\"stub\"}}"
270+
else if (shim.toolIs(tool_name, "lsp_hover"))
271+
"{\"result\":{\"status\":\"stub\"}}"
272+
else if (shim.toolIs(tool_name, "lsp_complete"))
273+
"{\"result\":{\"status\":\"stub\"}}"
274+
else if (shim.toolIs(tool_name, "lsp_goto_def"))
275+
"{\"result\":{\"status\":\"stub\"}}"
276+
else if (shim.toolIs(tool_name, "lsp_references"))
277+
"{\"result\":{\"status\":\"stub\"}}"
278+
else if (shim.toolIs(tool_name, "lsp_diagnostics"))
279+
"{\"result\":{\"status\":\"stub\"}}"
280+
else if (shim.toolIs(tool_name, "lsp_format"))
281+
"{\"result\":{\"status\":\"stub\"}}"
282+
else if (shim.toolIs(tool_name, "lsp_stop"))
283+
"{\"result\":{\"status\":\"stub\"}}"
284+
else
285+
return shim.RC_UNKNOWN_TOOL;
286+
287+
return shim.writeResult(out_buf, in_out_len, body);
288+
}
289+
243290
// ═══════════════════════════════════════════════════════════════════════
244291
// Tests
245292
// ═══════════════════════════════════════════════════════════════════════
@@ -300,3 +347,46 @@ test "max LSP sessions enforced" {
300347
}
301348
try std.testing.expectEqual(@as(c_int, -1), lsp_init());
302349
}
350+
351+
// ═══════════════════════════════════════════════════════════════════════
352+
// ADR-0006 invoke dispatch tests
353+
// ═══════════════════════════════════════════════════════════════════════
354+
355+
test "invoke: each declared tool succeeds" {
356+
var buf: [256]u8 = undefined;
357+
const tools = [_][]const u8{
358+
"lsp_start",
359+
"lsp_initialize",
360+
"lsp_open",
361+
"lsp_change",
362+
"lsp_close",
363+
"lsp_hover",
364+
"lsp_complete",
365+
"lsp_goto_def",
366+
"lsp_references",
367+
"lsp_diagnostics",
368+
"lsp_format",
369+
"lsp_stop",
370+
};
371+
for (tools) |t| {
372+
var len: usize = buf.len;
373+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
374+
try std.testing.expectEqual(@as(i32, 0), rc);
375+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
376+
}
377+
}
378+
379+
test "invoke: unknown tool returns -1" {
380+
var buf: [64]u8 = undefined;
381+
var len: usize = buf.len;
382+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
383+
try std.testing.expectEqual(@as(i32, -1), rc);
384+
}
385+
386+
test "invoke: buffer too small returns -3" {
387+
var buf: [4]u8 = undefined;
388+
var len: usize = buf.len;
389+
const rc = boj_cartridge_invoke("lsp_start", "{}", &buf, &len);
390+
try std.testing.expectEqual(@as(i32, -3), rc);
391+
try std.testing.expect(len > 4);
392+
}

cartridges/nesy-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ pub fn build(b: *std.Build) void {
99
const target = b.standardTargetOptions(.{});
1010
const optimize = b.standardOptimizeOption(.{});
1111

12+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
13+
14+
const shim_mod = b.addModule("cartridge_shim", .{
15+
16+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
17+
18+
.target = target,
19+
20+
.optimize = optimize,
21+
22+
});
23+
1224
const nesy_mod = b.addModule("nesy_ffi", .{
1325
.root_source_file = b.path("nesy_ffi.zig"),
1426
.target = target,
1527
.optimize = optimize,
1628
});
1729

30+
nesy_mod.addImport("cartridge_shim", shim_mod);
31+
1832
// ── Tests ────────────────────────────────────────────────────────
1933
const nesy_tests = b.addTest(.{
2034
.root_module = nesy_mod,

cartridges/nesy-mcp/ffi/nesy_ffi.zig

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,35 @@ pub export fn boj_cartridge_version() [*:0]const u8 {
9999
return "0.2.0";
100100
}
101101

102+
// ═══════════════════════════════════════════════════════════════════════
103+
// ADR-0006 dispatch (boj_cartridge_invoke, 5th standard symbol)
104+
// ═══════════════════════════════════════════════════════════════════════
105+
106+
const shim = @import("cartridge_shim");
107+
108+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha — each arm
109+
/// returns a stub JSON body shaped to the tool's intended response.
110+
export fn boj_cartridge_invoke(
111+
tool_name: [*c]const u8,
112+
json_args: [*c]const u8,
113+
out_buf: [*c]u8,
114+
in_out_len: [*c]usize,
115+
) callconv(.c) i32 {
116+
_ = json_args;
117+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
118+
119+
const body: []const u8 = if (shim.toolIs(tool_name, "nesy_harmonize"))
120+
"{\"result\":{\"status\":\"stub\"}}"
121+
else if (shim.toolIs(tool_name, "nesy_analyze_drift"))
122+
"{\"result\":{\"status\":\"stub\"}}"
123+
else if (shim.toolIs(tool_name, "nesy_reasoning_mode_info"))
124+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
125+
else
126+
return shim.RC_UNKNOWN_TOOL;
127+
128+
return shim.writeResult(out_buf, in_out_len, body);
129+
}
130+
102131
// ═══════════════════════════════════════════════════════════════════════
103132
// Protocol Types (from proven-nesy, added in v0.2.0)
104133
// ═══════════════════════════════════════════════════════════════════════
@@ -257,3 +286,37 @@ test "drift urgency" {
257286
try std.testing.expectEqual(@as(c_int, 1), nesy_drift_is_urgent(3)); // factual is urgent
258287
try std.testing.expectEqual(@as(c_int, 1), nesy_drift_is_urgent(5)); // catastrophic is urgent
259288
}
289+
290+
// ═══════════════════════════════════════════════════════════════════════
291+
// ADR-0006 invoke dispatch tests
292+
// ═══════════════════════════════════════════════════════════════════════
293+
294+
test "invoke: each declared tool succeeds" {
295+
var buf: [256]u8 = undefined;
296+
const tools = [_][]const u8{
297+
"nesy_harmonize",
298+
"nesy_analyze_drift",
299+
"nesy_reasoning_mode_info",
300+
};
301+
for (tools) |t| {
302+
var len: usize = buf.len;
303+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
304+
try std.testing.expectEqual(@as(i32, 0), rc);
305+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
306+
}
307+
}
308+
309+
test "invoke: unknown tool returns -1" {
310+
var buf: [64]u8 = undefined;
311+
var len: usize = buf.len;
312+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
313+
try std.testing.expectEqual(@as(i32, -1), rc);
314+
}
315+
316+
test "invoke: buffer too small returns -3" {
317+
var buf: [4]u8 = undefined;
318+
var len: usize = buf.len;
319+
const rc = boj_cartridge_invoke("nesy_harmonize", "{}", &buf, &len);
320+
try std.testing.expectEqual(@as(i32, -3), rc);
321+
try std.testing.expect(len > 4);
322+
}

cartridges/observe-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ pub fn build(b: *std.Build) void {
99
const target = b.standardTargetOptions(.{});
1010
const optimize = b.standardOptimizeOption(.{});
1111

12+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
13+
14+
const shim_mod = b.addModule("cartridge_shim", .{
15+
16+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
17+
18+
.target = target,
19+
20+
.optimize = optimize,
21+
22+
});
23+
1224
const obs_mod = b.addModule("observe_ffi", .{
1325
.root_source_file = b.path("observe_ffi.zig"),
1426
.target = target,
1527
.optimize = optimize,
1628
});
1729

30+
obs_mod.addImport("cartridge_shim", shim_mod);
31+
1832
// ── Tests ────────────────────────────────────────────────────────
1933
const obs_tests = b.addTest(.{
2034
.root_module = obs_mod,

cartridges/observe-mcp/ffi/observe_ffi.zig

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,37 @@ pub export fn boj_cartridge_version() [*:0]const u8 {
203203
return "0.1.0";
204204
}
205205

206+
// ═══════════════════════════════════════════════════════════════════════
207+
// ADR-0006 dispatch (boj_cartridge_invoke, 5th standard symbol)
208+
// ═══════════════════════════════════════════════════════════════════════
209+
210+
const shim = @import("cartridge_shim");
211+
212+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha — each arm
213+
/// returns a stub JSON body shaped to the tool's intended response.
214+
export fn boj_cartridge_invoke(
215+
tool_name: [*c]const u8,
216+
json_args: [*c]const u8,
217+
out_buf: [*c]u8,
218+
in_out_len: [*c]usize,
219+
) callconv(.c) i32 {
220+
_ = json_args;
221+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
222+
223+
const body: []const u8 = if (shim.toolIs(tool_name, "observe_register"))
224+
"{\"result\":{\"status\":\"stub\"}}"
225+
else if (shim.toolIs(tool_name, "observe_query_metrics"))
226+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
227+
else if (shim.toolIs(tool_name, "observe_query_logs"))
228+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
229+
else if (shim.toolIs(tool_name, "observe_query_traces"))
230+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
231+
else
232+
return shim.RC_UNKNOWN_TOOL;
233+
234+
return shim.writeResult(out_buf, in_out_len, body);
235+
}
236+
206237
// ═══════════════════════════════════════════════════════════════════════
207238
// Tests
208239
// ═══════════════════════════════════════════════════════════════════════
@@ -266,3 +297,38 @@ test "state transition validation" {
266297
try std.testing.expectEqual(@as(c_int, 0), obs_can_transition(0, 2)); // unconfigured -> query_ready
267298
try std.testing.expectEqual(@as(c_int, 0), obs_can_transition(3, 0)); // querying -> unconfigured
268299
}
300+
301+
// ═══════════════════════════════════════════════════════════════════════
302+
// ADR-0006 invoke dispatch tests
303+
// ═══════════════════════════════════════════════════════════════════════
304+
305+
test "invoke: each declared tool succeeds" {
306+
var buf: [256]u8 = undefined;
307+
const tools = [_][]const u8{
308+
"observe_register",
309+
"observe_query_metrics",
310+
"observe_query_logs",
311+
"observe_query_traces",
312+
};
313+
for (tools) |t| {
314+
var len: usize = buf.len;
315+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
316+
try std.testing.expectEqual(@as(i32, 0), rc);
317+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
318+
}
319+
}
320+
321+
test "invoke: unknown tool returns -1" {
322+
var buf: [64]u8 = undefined;
323+
var len: usize = buf.len;
324+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
325+
try std.testing.expectEqual(@as(i32, -1), rc);
326+
}
327+
328+
test "invoke: buffer too small returns -3" {
329+
var buf: [4]u8 = undefined;
330+
var len: usize = buf.len;
331+
const rc = boj_cartridge_invoke("observe_register", "{}", &buf, &len);
332+
try std.testing.expectEqual(@as(i32, -3), rc);
333+
try std.testing.expect(len > 4);
334+
}

cartridges/proof-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ pub fn build(b: *std.Build) void {
99
const target = b.standardTargetOptions(.{});
1010
const optimize = b.standardOptimizeOption(.{});
1111

12+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
13+
14+
const shim_mod = b.addModule("cartridge_shim", .{
15+
16+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
17+
18+
.target = target,
19+
20+
.optimize = optimize,
21+
22+
});
23+
1224
const proof_mod = b.addModule("proof_ffi", .{
1325
.root_source_file = b.path("proof_ffi.zig"),
1426
.target = target,
1527
.optimize = optimize,
1628
});
1729

30+
proof_mod.addImport("cartridge_shim", shim_mod);
31+
1832
// ── Tests ────────────────────────────────────────────────────────
1933
const proof_tests = b.addTest(.{
2034
.root_module = proof_mod,

0 commit comments

Comments
 (0)