Skip to content

Commit 13b772f

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 5 cartridges to 5-symbol ABI (Pattern B batch 8)
Pattern B migration (kategoria-mcp,laminar-mcp,linear-mcp,linode-mcp,matrix-mcp). All 5 ADR-0005+0006 exports added; boj_cartridge_invoke dispatches cartridge.json tools to Grade-D-Alpha stub bodies via cartridge_shim. Tests passing per cartridge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 07fdb29 commit 13b772f

10 files changed

Lines changed: 582 additions & 0 deletions

File tree

cartridges/kategoria-mcp/ffi/build.zig

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

10+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
11+
12+
const shim_mod = b.addModule("cartridge_shim", .{
13+
14+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
15+
16+
.target = target,
17+
18+
.optimize = optimize,
19+
20+
});
21+
1022
const ffi_mod = b.addModule("kategoria_mcp", .{
1123
.root_source_file = b.path("kategoria_ffi.zig"),
1224
.target = target,
1325
.optimize = optimize,
1426
});
1527

28+
ffi_mod.addImport("cartridge_shim", shim_mod);
29+
1630
const lib = b.addLibrary(.{
1731
.name = "kategoria_mcp",
1832
.root_module = ffi_mod,

cartridges/kategoria-mcp/ffi/kategoria_ffi.zig

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,51 @@ export fn kategoria_eval_challenge(level: u8, input: [*c]const u8) u8 {
2828
return 70; // Stub
2929
}
3030

31+
// ═══════════════════════════════════════════════════════════════════════
32+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
33+
// ═══════════════════════════════════════════════════════════════════════
34+
35+
const shim = @import("cartridge_shim");
36+
37+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "kategoria-mcp";
38+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
39+
40+
export fn boj_cartridge_init() callconv(.c) c_int {
41+
return 0;
42+
}
43+
44+
export fn boj_cartridge_deinit() callconv(.c) void {}
45+
46+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
47+
return CARTRIDGE_NAME_PTR;
48+
}
49+
50+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
51+
return CARTRIDGE_VERSION_PTR;
52+
}
53+
54+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
55+
export fn boj_cartridge_invoke(
56+
tool_name: [*c]const u8,
57+
json_args: [*c]const u8,
58+
out_buf: [*c]u8,
59+
in_out_len: [*c]usize,
60+
) callconv(.c) i32 {
61+
_ = json_args;
62+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
63+
64+
const body: []const u8 = if (shim.toolIs(tool_name, "kategoria_classify"))
65+
"{\"result\":{\"status\":\"stub\"}}"
66+
else if (shim.toolIs(tool_name, "kategoria_get_levels"))
67+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
68+
else if (shim.toolIs(tool_name, "kategoria_eval_challenge"))
69+
"{\"result\":{\"status\":\"stub\"}}"
70+
else
71+
return shim.RC_UNKNOWN_TOOL;
72+
73+
return shim.writeResult(out_buf, in_out_len, body);
74+
}
75+
3176
// ── Tests ──
3277

3378
test "classify rejects null input" {
@@ -42,3 +87,46 @@ test "classify returns bounded confidence" {
4287
test "levels matches clade taxonomy" {
4388
try std.testing.expectEqual(@as(u32, 12), kategoria_get_levels());
4489
}
90+
91+
// ═══════════════════════════════════════════════════════════════════════
92+
// ADR-0006 invoke dispatch tests
93+
// ═══════════════════════════════════════════════════════════════════════
94+
95+
test "boj_cartridge_name returns kategoria-mcp" {
96+
const n = std.mem.span(boj_cartridge_name());
97+
try std.testing.expectEqualStrings("kategoria-mcp", n);
98+
}
99+
100+
test "boj_cartridge_init returns 0" {
101+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
102+
}
103+
104+
test "invoke: each declared tool succeeds" {
105+
var buf: [256]u8 = undefined;
106+
const tools = [_][]const u8{
107+
"kategoria_classify",
108+
"kategoria_get_levels",
109+
"kategoria_eval_challenge",
110+
};
111+
for (tools) |t| {
112+
var len: usize = buf.len;
113+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
114+
try std.testing.expectEqual(@as(i32, 0), rc);
115+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
116+
}
117+
}
118+
119+
test "invoke: unknown tool returns -1" {
120+
var buf: [64]u8 = undefined;
121+
var len: usize = buf.len;
122+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
123+
try std.testing.expectEqual(@as(i32, -1), rc);
124+
}
125+
126+
test "invoke: buffer too small returns -3" {
127+
var buf: [4]u8 = undefined;
128+
var len: usize = buf.len;
129+
const rc = boj_cartridge_invoke("kategoria_classify", "{}", &buf, &len);
130+
try std.testing.expectEqual(@as(i32, -3), rc);
131+
try std.testing.expect(len > 4);
132+
}

cartridges/laminar-mcp/ffi/build.zig

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

10+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
11+
12+
const shim_mod = b.addModule("cartridge_shim", .{
13+
14+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
15+
16+
.target = target,
17+
18+
.optimize = optimize,
19+
20+
});
21+
1022
const ffi_mod = b.addModule("laminar_mcp", .{
1123
.root_source_file = b.path("laminar_ffi.zig"),
1224
.target = target,
1325
.optimize = optimize,
1426
});
1527

28+
ffi_mod.addImport("cartridge_shim", shim_mod);
29+
1630
const lib = b.addLibrary(.{
1731
.name = "laminar_mcp",
1832
.root_module = ffi_mod,

cartridges/laminar-mcp/ffi/laminar_ffi.zig

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,53 @@ export fn laminar_cancel_pipeline(pipeline_id: u32) i32 {
2929
return 0; // Stub
3030
}
3131

32+
// ═══════════════════════════════════════════════════════════════════════
33+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
34+
// ═══════════════════════════════════════════════════════════════════════
35+
36+
const shim = @import("cartridge_shim");
37+
38+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "laminar-mcp";
39+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
40+
41+
export fn boj_cartridge_init() callconv(.c) c_int {
42+
return 0;
43+
}
44+
45+
export fn boj_cartridge_deinit() callconv(.c) void {}
46+
47+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
48+
return CARTRIDGE_NAME_PTR;
49+
}
50+
51+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
52+
return CARTRIDGE_VERSION_PTR;
53+
}
54+
55+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
56+
export fn boj_cartridge_invoke(
57+
tool_name: [*c]const u8,
58+
json_args: [*c]const u8,
59+
out_buf: [*c]u8,
60+
in_out_len: [*c]usize,
61+
) callconv(.c) i32 {
62+
_ = json_args;
63+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
64+
65+
const body: []const u8 = if (shim.toolIs(tool_name, "laminar_create_pipeline"))
66+
"{\"result\":{\"status\":\"stub\"}}"
67+
else if (shim.toolIs(tool_name, "laminar_run_stage"))
68+
"{\"result\":{\"status\":\"stub\"}}"
69+
else if (shim.toolIs(tool_name, "laminar_get_status"))
70+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
71+
else if (shim.toolIs(tool_name, "laminar_cancel_pipeline"))
72+
"{\"result\":{\"status\":\"stub\"}}"
73+
else
74+
return shim.RC_UNKNOWN_TOOL;
75+
76+
return shim.writeResult(out_buf, in_out_len, body);
77+
}
78+
3279
// ── Tests ──
3380

3481
test "create rejects null name" {
@@ -42,3 +89,47 @@ test "run stage rejects invalid pipeline" {
4289
test "cancel rejects invalid pipeline" {
4390
try std.testing.expectEqual(@as(i32, -1), laminar_cancel_pipeline(0));
4491
}
92+
93+
// ═══════════════════════════════════════════════════════════════════════
94+
// ADR-0006 invoke dispatch tests
95+
// ═══════════════════════════════════════════════════════════════════════
96+
97+
test "boj_cartridge_name returns laminar-mcp" {
98+
const n = std.mem.span(boj_cartridge_name());
99+
try std.testing.expectEqualStrings("laminar-mcp", n);
100+
}
101+
102+
test "boj_cartridge_init returns 0" {
103+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
104+
}
105+
106+
test "invoke: each declared tool succeeds" {
107+
var buf: [256]u8 = undefined;
108+
const tools = [_][]const u8{
109+
"laminar_create_pipeline",
110+
"laminar_run_stage",
111+
"laminar_get_status",
112+
"laminar_cancel_pipeline",
113+
};
114+
for (tools) |t| {
115+
var len: usize = buf.len;
116+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
117+
try std.testing.expectEqual(@as(i32, 0), rc);
118+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
119+
}
120+
}
121+
122+
test "invoke: unknown tool returns -1" {
123+
var buf: [64]u8 = undefined;
124+
var len: usize = buf.len;
125+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
126+
try std.testing.expectEqual(@as(i32, -1), rc);
127+
}
128+
129+
test "invoke: buffer too small returns -3" {
130+
var buf: [4]u8 = undefined;
131+
var len: usize = buf.len;
132+
const rc = boj_cartridge_invoke("laminar_create_pipeline", "{}", &buf, &len);
133+
try std.testing.expectEqual(@as(i32, -3), rc);
134+
try std.testing.expect(len > 4);
135+
}

cartridges/linear-mcp/ffi/build.zig

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

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

29+
ffi_mod.addImport("cartridge_shim", shim_mod);
30+
1731
// Shared library
1832
const lib = b.addLibrary(.{
1933
.name = "linear_mcp",

cartridges/linear-mcp/ffi/linear_mcp_ffi.zig

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,59 @@ pub export fn linear_mcp_reset() void {
372372
}
373373

374374
// ---------------------------------------------------------------------------
375+
// ═══════════════════════════════════════════════════════════════════════
376+
// Standard ABI (ADR-0005 four symbols + ADR-0006 invoke)
377+
// ═══════════════════════════════════════════════════════════════════════
378+
379+
const shim = @import("cartridge_shim");
380+
381+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "linear-mcp";
382+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
383+
384+
export fn boj_cartridge_init() callconv(.c) c_int {
385+
return 0;
386+
}
387+
388+
export fn boj_cartridge_deinit() callconv(.c) void {}
389+
390+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
391+
return CARTRIDGE_NAME_PTR;
392+
}
393+
394+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
395+
return CARTRIDGE_VERSION_PTR;
396+
}
397+
398+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha stubs.
399+
export fn boj_cartridge_invoke(
400+
tool_name: [*c]const u8,
401+
json_args: [*c]const u8,
402+
out_buf: [*c]u8,
403+
in_out_len: [*c]usize,
404+
) callconv(.c) i32 {
405+
_ = json_args;
406+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
407+
408+
const body: []const u8 = if (shim.toolIs(tool_name, "linear_authenticate"))
409+
"{\"result\":{\"status\":\"stub\"}}"
410+
else if (shim.toolIs(tool_name, "linear_list_issues"))
411+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
412+
else if (shim.toolIs(tool_name, "linear_get_issue"))
413+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
414+
else if (shim.toolIs(tool_name, "linear_create_issue"))
415+
"{\"result\":{\"status\":\"stub\"}}"
416+
else if (shim.toolIs(tool_name, "linear_update_issue"))
417+
"{\"result\":{\"status\":\"stub\"}}"
418+
else if (shim.toolIs(tool_name, "linear_list_teams"))
419+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
420+
else if (shim.toolIs(tool_name, "linear_search_issues"))
421+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
422+
else
423+
return shim.RC_UNKNOWN_TOOL;
424+
425+
return shim.writeResult(out_buf, in_out_len, body);
426+
}
427+
375428
// Tests
376429
// ---------------------------------------------------------------------------
377430

@@ -459,3 +512,50 @@ test "slot exhaustion" {
459512
const new_slot = linear_mcp_authenticate("lin_api_reuse_slot_token_1234567");
460513
try std.testing.expect(new_slot >= 0);
461514
}
515+
516+
// ═══════════════════════════════════════════════════════════════════════
517+
// ADR-0006 invoke dispatch tests
518+
// ═══════════════════════════════════════════════════════════════════════
519+
520+
test "boj_cartridge_name returns linear-mcp" {
521+
const n = std.mem.span(boj_cartridge_name());
522+
try std.testing.expectEqualStrings("linear-mcp", n);
523+
}
524+
525+
test "boj_cartridge_init returns 0" {
526+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
527+
}
528+
529+
test "invoke: each declared tool succeeds" {
530+
var buf: [256]u8 = undefined;
531+
const tools = [_][]const u8{
532+
"linear_authenticate",
533+
"linear_list_issues",
534+
"linear_get_issue",
535+
"linear_create_issue",
536+
"linear_update_issue",
537+
"linear_list_teams",
538+
"linear_search_issues",
539+
};
540+
for (tools) |t| {
541+
var len: usize = buf.len;
542+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
543+
try std.testing.expectEqual(@as(i32, 0), rc);
544+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
545+
}
546+
}
547+
548+
test "invoke: unknown tool returns -1" {
549+
var buf: [64]u8 = undefined;
550+
var len: usize = buf.len;
551+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
552+
try std.testing.expectEqual(@as(i32, -1), rc);
553+
}
554+
555+
test "invoke: buffer too small returns -3" {
556+
var buf: [4]u8 = undefined;
557+
var len: usize = buf.len;
558+
const rc = boj_cartridge_invoke("linear_authenticate", "{}", &buf, &len);
559+
try std.testing.expectEqual(@as(i32, -3), rc);
560+
try std.testing.expect(len > 4);
561+
}

0 commit comments

Comments
 (0)