Skip to content

Commit c537217

Browse files
committed
Add cartridge template for new cartridges
1 parent 67046ce commit c537217

7 files changed

Lines changed: 453 additions & 0 deletions

File tree

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# Cartridge Template
2+
3+
This template provides a starting point for creating new cartridges for the BoJ server.
4+
5+
## Structure
6+
7+
```
8+
cartridge-template/
9+
├── README.md # This file
10+
├── cartridge.json # Cartridge metadata
11+
├── abi/
12+
│ └── README.adoc # ABI layer documentation
13+
├── ffi/
14+
│ ├── README.adoc # FFI layer documentation
15+
│ ├── build.zig # Build configuration
16+
│ └── cartridge_ffi.zig # FFI implementation
17+
└── adapter/
18+
└── README.adoc # Adapter layer documentation
19+
```
20+
21+
## Getting Started
22+
23+
1. **Copy the Template**:
24+
```bash
25+
cp -r templates/cartridge-template cartridges/my-new-cartridge
26+
```
27+
28+
2. **Update Metadata**:
29+
- Edit `cartridge.json` to reflect your cartridge's metadata.
30+
- Update the `name`, `version`, `description`, and other fields.
31+
32+
3. **Implement the ABI Layer**:
33+
- Define the abstract interfaces and types in the `abi/` directory.
34+
- Use Idris2 for type safety and correctness.
35+
36+
4. **Implement the FFI Layer**:
37+
- Implement the foreign function interface in the `ffi/` directory.
38+
- Use Zig for high-performance bindings.
39+
40+
5. **Implement the Adapter Layer**:
41+
- Implement the actual functionality in the `adapter/` directory.
42+
- Use Zig for the adapter layer.
43+
44+
6. **Add Tests**:
45+
- Add tests to the `ffi/` directory to ensure your cartridge works as expected.
46+
- Use Zig's testing framework.
47+
48+
7. **Update Documentation**:
49+
- Update the `README.adoc` files in each directory to reflect your cartridge's purpose, boundaries, invariants, and execution surfaces.
50+
51+
## Cartridge Metadata
52+
53+
The `cartridge.json` file contains metadata about your cartridge. Here's an example:
54+
55+
```json
56+
{
57+
"name": "my-new-cartridge",
58+
"version": "0.1.0",
59+
"description": "A brief description of your cartridge",
60+
"domain": "Domain",
61+
"tier": "Ayo",
62+
"protocols": ["MCP", "REST"],
63+
"auth": {
64+
"method": "none"
65+
},
66+
"ports": {
67+
"allowed": [],
68+
"denied": []
69+
},
70+
"tools": [
71+
{
72+
"name": "tool_name",
73+
"description": "A brief description of the tool",
74+
"inputSchema": {
75+
"type": "object",
76+
"properties": {},
77+
"required": []
78+
}
79+
}
80+
]
81+
}
82+
```
83+
84+
## FFI Implementation
85+
86+
The `ffi/cartridge_ffi.zig` file contains the FFI implementation. Here's a basic template:
87+
88+
```zig
89+
// SPDX-License-Identifier: PMPL-1.0-or-later
90+
// Copyright (c) 2026 Your Name <your.email@example.com>
91+
92+
const std = @import("std");
93+
const shim = @import("cartridge_shim.zig");
94+
95+
const CARTRIDGE_NAME_PTR: [*:0]const u8 = "my-new-cartridge";
96+
const CARTRIDGE_VERSION_PTR: [*:0]const u8 = "0.1.0";
97+
98+
export fn boj_cartridge_init() callconv(.c) c_int {
99+
return 0;
100+
}
101+
102+
export fn boj_cartridge_deinit() callconv(.c) void {}
103+
104+
export fn boj_cartridge_name() callconv(.c) [*:0]const u8 {
105+
return CARTRIDGE_NAME_PTR;
106+
}
107+
108+
export fn boj_cartridge_version() callconv(.c) [*:0]const u8 {
109+
return CARTRIDGE_VERSION_PTR;
110+
}
111+
112+
export fn boj_cartridge_invoke(
113+
tool_name: [*c]const u8,
114+
json_args: [*c]const u8,
115+
out_buf: [*c]u8,
116+
in_out_len: [*c]usize,
117+
) callconv(.c) i32 {
118+
_ = json_args;
119+
120+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
121+
122+
const body: []const u8 = if (shim.toolIs(tool_name, "tool_name"))
123+
"{\"result\":{}}"
124+
else
125+
return shim.RC_UNKNOWN_TOOL;
126+
127+
return shim.writeResult(out_buf, in_out_len, body);
128+
}
129+
130+
// Tests
131+
test "boj_cartridge_name returns my-new-cartridge" {
132+
const n = std.mem.span(boj_cartridge_name());
133+
try std.testing.expectEqualStrings("my-new-cartridge", n);
134+
}
135+
136+
test "boj_cartridge_version returns semver" {
137+
const v = std.mem.span(boj_cartridge_version());
138+
try std.testing.expectEqualStrings("0.1.0", v);
139+
}
140+
141+
test "boj_cartridge_init returns 0" {
142+
try std.testing.expectEqual(@as(c_int, 0), boj_cartridge_init());
143+
}
144+
145+
test "invoke unknown tool returns -1" {
146+
var buf: [256]u8 = undefined;
147+
var len: usize = buf.len;
148+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
149+
try std.testing.expectEqual(@as(i32, -1), rc);
150+
}
151+
152+
test "invoke known tool writes JSON and returns 0" {
153+
var buf: [256]u8 = undefined;
154+
var len: usize = buf.len;
155+
const rc = boj_cartridge_invoke("tool_name", "{}", &buf, &len);
156+
try std.testing.expectEqual(@as(i32, 0), rc);
157+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
158+
}
159+
160+
test "invoke with too-small buffer returns -3 and sets required length" {
161+
var buf: [4]u8 = undefined;
162+
var len: usize = buf.len;
163+
const rc = boj_cartridge_invoke("tool_name", "{}", &buf, &len);
164+
try std.testing.expectEqual(@as(i32, -3), rc);
165+
try std.testing.expect(len > 4);
166+
}
167+
```
168+
169+
## Build Configuration
170+
171+
The `ffi/build.zig` file contains the build configuration. Here's a basic template:
172+
173+
```zig
174+
// SPDX-License-Identifier: PMPL-1.0-or-later
175+
176+
const std = @import("std");
177+
178+
pub fn build(b: *std.Build) void {
179+
const target = b.standardTargetOptions(.{});
180+
const optimize = b.standardOptimizeOption(.{});
181+
182+
const ffi_mod = b.addModule("my_new_cartridge", .{
183+
.root_source_file = b.path("cartridge_ffi.zig"),
184+
.target = target,
185+
.optimize = optimize,
186+
});
187+
188+
const lib = b.addLibrary(.{
189+
.name = "my_new_cartridge",
190+
.root_module = ffi_mod,
191+
.linkage = .dynamic,
192+
});
193+
lib.linkLibC();
194+
b.installArtifact(lib);
195+
196+
const tests = b.addTest(.{ .root_module = ffi_mod });
197+
tests.linkLibC();
198+
const run_tests = b.addRunArtifact(tests);
199+
const test_step = b.step("test", "Run FFI tests");
200+
test_step.dependOn(&run_tests.step);
201+
}
202+
```
203+
204+
## Documentation
205+
206+
Update the `README.adoc` files in each directory to reflect your cartridge's purpose, boundaries, invariants, and execution surfaces. Here's an example for the `ffi/` directory:
207+
208+
```adoc
209+
= My New Cartridge FFI Layer
210+
211+
== Purpose
212+
213+
This layer provides the foreign function interface for the My New Cartridge cartridge. It bridges the Idris2 ABI layer with the Zig adapter layer.
214+
215+
== Boundaries
216+
217+
- **Input**: JSON arguments from the MCP bridge.
218+
- **Output**: JSON responses to the MCP bridge.
219+
- **Dependencies**: None.
220+
221+
== Invariants
222+
223+
- The FFI layer must be thread-safe.
224+
- The FFI layer must handle errors gracefully.
225+
- The FFI layer must validate input arguments.
226+
227+
== Execution Surfaces
228+
229+
- **Entry Points**: `boj_cartridge_init`, `boj_cartridge_deinit`, `boj_cartridge_name`, `boj_cartridge_version`, `boj_cartridge_invoke`.
230+
- **Error Handling**: Returns appropriate error codes for invalid inputs or failed operations.
231+
- **Testing**: Unit tests for each function.
232+
```
233+
234+
## Testing
235+
236+
Run the tests using the following command:
237+
238+
```bash
239+
cd ffi && zig build test
240+
```
241+
242+
## Building
243+
244+
Build the shared library using the following command:
245+
246+
```bash
247+
cd ffi && zig build
248+
```
249+
250+
## License
251+
252+
This cartridge is licensed under the PMPL-1.0-or-later license. See the LICENSE file for more information.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
= My New Cartridge ABI Layer
2+
3+
== Purpose
4+
5+
This layer defines the abstract interfaces and types for the My New Cartridge cartridge. It ensures type safety and provides a clear contract for the rest of the system.
6+
7+
== Boundaries
8+
9+
- **Input**: Type definitions and interfaces from the Idris2 ABI layer.
10+
- **Output**: Type-safe bindings for the FFI layer.
11+
- **Dependencies**: None.
12+
13+
== Invariants
14+
15+
- The ABI layer must be type-safe.
16+
- The ABI layer must provide a clear contract for the FFI layer.
17+
- The ABI layer must be well-documented.
18+
19+
== Execution Surfaces
20+
21+
- **Entry Points**: Type definitions and interfaces.
22+
- **Error Handling**: Type errors are caught at compile time.
23+
- **Testing**: Type checking and formal proofs.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
= My New Cartridge Adapter Layer
2+
3+
== Purpose
4+
5+
This layer contains the actual implementations of the tools and functionalities provided by the My New Cartridge cartridge.
6+
7+
== Boundaries
8+
9+
- **Input**: JSON arguments from the FFI layer.
10+
- **Output**: JSON responses to the FFI layer.
11+
- **Dependencies**: None.
12+
13+
== Invariants
14+
15+
- The adapter layer must be thread-safe.
16+
- The adapter layer must handle errors gracefully.
17+
- The adapter layer must validate input arguments.
18+
19+
== Execution Surfaces
20+
21+
- **Entry Points**: Tool implementations.
22+
- **Error Handling**: Returns appropriate error codes for invalid inputs or failed operations.
23+
- **Testing**: Unit tests for each tool.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "my-new-cartridge",
3+
"version": "0.1.0",
4+
"description": "A brief description of your cartridge",
5+
"domain": "Domain",
6+
"tier": "Ayo",
7+
"protocols": ["MCP", "REST"],
8+
"auth": {
9+
"method": "none"
10+
},
11+
"ports": {
12+
"allowed": [],
13+
"denied": []
14+
},
15+
"tools": [
16+
{
17+
"name": "tool_name",
18+
"description": "A brief description of the tool",
19+
"inputSchema": {
20+
"type": "object",
21+
"properties": {},
22+
"required": []
23+
}
24+
}
25+
]
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
= My New Cartridge FFI Layer
2+
3+
== Purpose
4+
5+
This layer provides the foreign function interface for the My New Cartridge cartridge. It bridges the Idris2 ABI layer with the Zig adapter layer.
6+
7+
== Boundaries
8+
9+
- **Input**: JSON arguments from the MCP bridge.
10+
- **Output**: JSON responses to the MCP bridge.
11+
- **Dependencies**: None.
12+
13+
== Invariants
14+
15+
- The FFI layer must be thread-safe.
16+
- The FFI layer must handle errors gracefully.
17+
- The FFI layer must validate input arguments.
18+
19+
== Execution Surfaces
20+
21+
- **Entry Points**: `boj_cartridge_init`, `boj_cartridge_deinit`, `boj_cartridge_name`, `boj_cartridge_version`, `boj_cartridge_invoke`.
22+
- **Error Handling**: Returns appropriate error codes for invalid inputs or failed operations.
23+
- **Testing**: Unit tests for each function.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
3+
const std = @import("std");
4+
5+
pub fn build(b: *std.Build) void {
6+
const target = b.standardTargetOptions(.{});
7+
const optimize = b.standardOptimizeOption(.{});
8+
9+
const ffi_mod = b.addModule("my_new_cartridge", .{
10+
.root_source_file = b.path("cartridge_ffi.zig"),
11+
.target = target,
12+
.optimize = optimize,
13+
});
14+
15+
const lib = b.addLibrary(.{
16+
.name = "my_new_cartridge",
17+
.root_module = ffi_mod,
18+
.linkage = .dynamic,
19+
});
20+
lib.linkLibC();
21+
b.installArtifact(lib);
22+
23+
const tests = b.addTest(.{ .root_module = ffi_mod });
24+
tests.linkLibC();
25+
const run_tests = b.addRunArtifact(tests);
26+
const test_step = b.step("test", "Run FFI tests");
27+
test_step.dependOn(&run_tests.step);
28+
}

0 commit comments

Comments
 (0)