rtfct 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.project/adrs/001-use-bun-typescript.md +52 -0
  2. package/.project/guardrails.md +65 -0
  3. package/.project/kanban/backlog.md +7 -0
  4. package/.project/kanban/done.md +240 -0
  5. package/.project/kanban/in-progress.md +11 -0
  6. package/.project/kickstart.md +63 -0
  7. package/.project/protocol.md +134 -0
  8. package/.project/specs/requirements.md +152 -0
  9. package/.project/testing/strategy.md +123 -0
  10. package/.project/theology.md +125 -0
  11. package/CLAUDE.md +119 -0
  12. package/README.md +143 -0
  13. package/package.json +31 -0
  14. package/src/args.ts +104 -0
  15. package/src/commands/add.ts +78 -0
  16. package/src/commands/init.ts +128 -0
  17. package/src/commands/praise.ts +19 -0
  18. package/src/commands/regenerate.ts +122 -0
  19. package/src/commands/status.ts +163 -0
  20. package/src/help.ts +52 -0
  21. package/src/index.ts +102 -0
  22. package/src/kanban.ts +83 -0
  23. package/src/manifest.ts +67 -0
  24. package/src/presets/base.ts +195 -0
  25. package/src/presets/elixir.ts +118 -0
  26. package/src/presets/github.ts +194 -0
  27. package/src/presets/index.ts +154 -0
  28. package/src/presets/typescript.ts +589 -0
  29. package/src/presets/zig.ts +494 -0
  30. package/tests/integration/add.test.ts +104 -0
  31. package/tests/integration/init.test.ts +197 -0
  32. package/tests/integration/praise.test.ts +36 -0
  33. package/tests/integration/regenerate.test.ts +154 -0
  34. package/tests/integration/status.test.ts +165 -0
  35. package/tests/unit/args.test.ts +144 -0
  36. package/tests/unit/kanban.test.ts +162 -0
  37. package/tests/unit/manifest.test.ts +155 -0
  38. package/tests/unit/presets.test.ts +295 -0
  39. package/tsconfig.json +19 -0
@@ -0,0 +1,494 @@
1
+ /**
2
+ * The Zig Codex — Sacred Patterns for Zig Development
3
+ */
4
+
5
+ import type { Preset } from "./index";
6
+
7
+ const TESTING_STRATEGY_MD = `# Zig Testing Strategy
8
+
9
+ *The Rites of Verification for Zig*
10
+
11
+ ## The Sacred Command
12
+
13
+ \`\`\`bash
14
+ zig build test
15
+ \`\`\`
16
+
17
+ This single incantation runs all tests in the project. The Machine Spirit shall invoke it frequently.
18
+
19
+ ## Test Organization
20
+
21
+ Tests live alongside the code they test. This is the Zig way.
22
+
23
+ \`\`\`zig
24
+ // src/math.zig
25
+ const std = @import("std");
26
+ const testing = std.testing;
27
+
28
+ pub fn add(a: i32, b: i32) i32 {
29
+ return a + b;
30
+ }
31
+
32
+ test "add returns sum of two numbers" {
33
+ try testing.expectEqual(@as(i32, 5), add(2, 3));
34
+ }
35
+
36
+ test "add handles negative numbers" {
37
+ try testing.expectEqual(@as(i32, 0), add(-5, 5));
38
+ }
39
+
40
+ test "add handles overflow" {
41
+ // This should be tested if overflow behavior matters
42
+ const result = @addWithOverflow(std.math.maxInt(i32), 1);
43
+ try testing.expect(result[1] == 1); // overflow occurred
44
+ }
45
+ \`\`\`
46
+
47
+ ## The build.zig Test Step
48
+
49
+ \`\`\`zig
50
+ const std = @import("std");
51
+
52
+ pub fn build(b: *std.Build) void {
53
+ const target = b.standardTargetOptions(.{});
54
+ const optimize = b.standardOptimizeOption(.{});
55
+
56
+ // Main executable
57
+ const exe = b.addExecutable(.{
58
+ .name = "myapp",
59
+ .root_source_file = b.path("src/main.zig"),
60
+ .target = target,
61
+ .optimize = optimize,
62
+ });
63
+ b.installArtifact(exe);
64
+
65
+ // Unit tests
66
+ const unit_tests = b.addTest(.{
67
+ .root_source_file = b.path("src/main.zig"),
68
+ .target = target,
69
+ .optimize = optimize,
70
+ });
71
+
72
+ const run_unit_tests = b.addRunArtifact(unit_tests);
73
+ const test_step = b.step("test", "Run unit tests");
74
+ test_step.dependOn(&run_unit_tests.step);
75
+ }
76
+ \`\`\`
77
+
78
+ ## Testing with Allocators
79
+
80
+ Always use the testing allocator in tests — it detects memory leaks:
81
+
82
+ \`\`\`zig
83
+ test "allocation test" {
84
+ const allocator = std.testing.allocator;
85
+
86
+ const slice = try allocator.alloc(u8, 100);
87
+ defer allocator.free(slice);
88
+
89
+ // If you forget the defer, the test fails with a leak report
90
+ }
91
+ \`\`\`
92
+
93
+ ## Comptime Testing
94
+
95
+ Test comptime functions at comptime:
96
+
97
+ \`\`\`zig
98
+ fn comptimeAdd(comptime a: i32, comptime b: i32) i32 {
99
+ return a + b;
100
+ }
101
+
102
+ test "comptime add" {
103
+ // This is evaluated at compile time
104
+ comptime {
105
+ const result = comptimeAdd(2, 3);
106
+ if (result != 5) @compileError("comptime add failed");
107
+ }
108
+
109
+ // This also works
110
+ try std.testing.expectEqual(@as(i32, 5), comptime comptimeAdd(2, 3));
111
+ }
112
+ \`\`\`
113
+
114
+ ## Coverage Doctrine
115
+
116
+ - All public functions must have at least one test
117
+ - Edge cases (empty, zero, max, min) must be tested explicitly
118
+ - Error paths must be tested — verify errors are returned correctly
119
+ - Comptime functions need comptime verification
120
+
121
+ ---
122
+
123
+ *The tests do not lie. Praise the Machine Spirit.*
124
+ `;
125
+
126
+ const GUARDRAILS_MD = `# Zig Guardrails — Forbidden Heresies
127
+
128
+ *Zig-specific patterns to avoid*
129
+
130
+ ## Memory Heresies
131
+
132
+ ### 1. Forgetting to Free
133
+ Every allocation needs a corresponding free. Use \`defer\` immediately:
134
+
135
+ \`\`\`zig
136
+ // HERESY
137
+ const ptr = try allocator.create(MyStruct);
138
+ // ... code that might return early or error
139
+ allocator.destroy(ptr); // might never be reached!
140
+
141
+ // SACRED
142
+ const ptr = try allocator.create(MyStruct);
143
+ defer allocator.destroy(ptr);
144
+ // Now it's always freed, even on error
145
+ \`\`\`
146
+
147
+ ### 2. Use-After-Free
148
+ The allocator owns freed memory. Never access it:
149
+
150
+ \`\`\`zig
151
+ // HERESY
152
+ allocator.free(slice);
153
+ const x = slice[0]; // undefined behavior!
154
+
155
+ // SACRED
156
+ defer allocator.free(slice); // free at end of scope
157
+ const x = slice[0]; // valid access
158
+ \`\`\`
159
+
160
+ ### 3. Unbounded Allocations
161
+ Always use bounded allocators in production:
162
+
163
+ \`\`\`zig
164
+ // HERESY in production
165
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
166
+
167
+ // SACRED — use with memory limit
168
+ var gpa = std.heap.GeneralPurposeAllocator(.{
169
+ .enable_memory_limit = true,
170
+ }){};
171
+ gpa.setMemoryLimit(1024 * 1024 * 100); // 100MB limit
172
+ \`\`\`
173
+
174
+ ## Allocator Wisdom
175
+
176
+ Choose the right allocator for the job:
177
+
178
+ | Allocator | Use Case |
179
+ |-----------|----------|
180
+ | \`std.testing.allocator\` | Tests only — detects leaks |
181
+ | \`std.heap.page_allocator\` | Large, long-lived allocations |
182
+ | \`std.heap.GeneralPurposeAllocator\` | General use with safety checks |
183
+ | \`std.heap.ArenaAllocator\` | Request-scoped, bulk-free |
184
+ | \`std.heap.FixedBufferAllocator\` | Stack-based, no syscalls |
185
+
186
+ ## Comptime Heresies
187
+
188
+ ### 1. Comptime Side Effects
189
+ Comptime code must be pure — no I/O, no runtime state:
190
+
191
+ \`\`\`zig
192
+ // HERESY
193
+ comptime {
194
+ std.debug.print("hello", .{}); // Error!
195
+ }
196
+
197
+ // SACRED — comptime is for computation only
198
+ const result = comptime blk: {
199
+ var sum: i32 = 0;
200
+ for (0..10) |i| sum += @intCast(i);
201
+ break :blk sum;
202
+ };
203
+ \`\`\`
204
+
205
+ ### 2. Excessive Comptime
206
+ Runtime is fine for most things. Don't force comptime:
207
+
208
+ \`\`\`zig
209
+ // HERESY — overcomplicating
210
+ fn processData(comptime T: type, comptime N: usize) [N]T { ... }
211
+
212
+ // SACRED — if N can be runtime, let it be
213
+ fn processData(allocator: Allocator, data: []const u8) ![]u8 { ... }
214
+ \`\`\`
215
+
216
+ ### 3. Unreadable Generics
217
+ If the type signature is a paragraph, simplify:
218
+
219
+ \`\`\`zig
220
+ // HERESY
221
+ fn process(
222
+ comptime T: type,
223
+ comptime U: type,
224
+ comptime V: type,
225
+ comptime transform: fn(T) U,
226
+ comptime combine: fn(U, V) T,
227
+ ) fn([]const T, []const V) []T { ... }
228
+
229
+ // SACRED — break it down or use concrete types
230
+ \`\`\`
231
+
232
+ ## Error Handling
233
+
234
+ ### Never Discard Errors in Production
235
+
236
+ \`\`\`zig
237
+ // HERESY
238
+ const value = getData() catch unreachable;
239
+
240
+ // SACRED
241
+ const value = getData() catch |err| {
242
+ log.err("Failed to get data: {}", .{err});
243
+ return err;
244
+ };
245
+ \`\`\`
246
+
247
+ ### Use Error Sets for Documentation
248
+
249
+ \`\`\`zig
250
+ const FileError = error{
251
+ NotFound,
252
+ PermissionDenied,
253
+ DiskFull,
254
+ };
255
+
256
+ fn readFile(path: []const u8) FileError![]u8 {
257
+ // Callers know exactly what can go wrong
258
+ }
259
+ \`\`\`
260
+
261
+ ---
262
+
263
+ *The compiler is wise. Trust its warnings. Praise the Machine Spirit.*
264
+ `;
265
+
266
+ const BUILD_PATTERNS_MD = `# Zig build.zig Patterns
267
+
268
+ *Sacred patterns for the build system*
269
+
270
+ ## The Minimal build.zig
271
+
272
+ \`\`\`zig
273
+ const std = @import("std");
274
+
275
+ pub fn build(b: *std.Build) void {
276
+ const target = b.standardTargetOptions(.{});
277
+ const optimize = b.standardOptimizeOption(.{});
278
+
279
+ const exe = b.addExecutable(.{
280
+ .name = "myapp",
281
+ .root_source_file = b.path("src/main.zig"),
282
+ .target = target,
283
+ .optimize = optimize,
284
+ });
285
+
286
+ b.installArtifact(exe);
287
+
288
+ // Run step
289
+ const run_cmd = b.addRunArtifact(exe);
290
+ run_cmd.step.dependOn(b.getInstallStep());
291
+ const run_step = b.step("run", "Run the application");
292
+ run_step.dependOn(&run_cmd.step);
293
+
294
+ // Test step
295
+ const tests = b.addTest(.{
296
+ .root_source_file = b.path("src/main.zig"),
297
+ .target = target,
298
+ .optimize = optimize,
299
+ });
300
+ const run_tests = b.addRunArtifact(tests);
301
+ const test_step = b.step("test", "Run unit tests");
302
+ test_step.dependOn(&run_tests.step);
303
+ }
304
+ \`\`\`
305
+
306
+ ## Library with Tests
307
+
308
+ \`\`\`zig
309
+ const std = @import("std");
310
+
311
+ pub fn build(b: *std.Build) void {
312
+ const target = b.standardTargetOptions(.{});
313
+ const optimize = b.standardOptimizeOption(.{});
314
+
315
+ // Static library
316
+ const lib = b.addStaticLibrary(.{
317
+ .name = "mylib",
318
+ .root_source_file = b.path("src/lib.zig"),
319
+ .target = target,
320
+ .optimize = optimize,
321
+ });
322
+ b.installArtifact(lib);
323
+
324
+ // Module for dependents
325
+ _ = b.addModule("mylib", .{
326
+ .root_source_file = b.path("src/lib.zig"),
327
+ });
328
+
329
+ // Tests
330
+ const lib_tests = b.addTest(.{
331
+ .root_source_file = b.path("src/lib.zig"),
332
+ .target = target,
333
+ .optimize = optimize,
334
+ });
335
+ const run_lib_tests = b.addRunArtifact(lib_tests);
336
+ const test_step = b.step("test", "Run library tests");
337
+ test_step.dependOn(&run_lib_tests.step);
338
+ }
339
+ \`\`\`
340
+
341
+ ## Adding Dependencies
342
+
343
+ \`\`\`zig
344
+ const std = @import("std");
345
+
346
+ pub fn build(b: *std.Build) void {
347
+ const target = b.standardTargetOptions(.{});
348
+ const optimize = b.standardOptimizeOption(.{});
349
+
350
+ // Fetch dependency
351
+ const zap = b.dependency("zap", .{
352
+ .target = target,
353
+ .optimize = optimize,
354
+ });
355
+
356
+ const exe = b.addExecutable(.{
357
+ .name = "myserver",
358
+ .root_source_file = b.path("src/main.zig"),
359
+ .target = target,
360
+ .optimize = optimize,
361
+ });
362
+
363
+ // Add dependency module
364
+ exe.root_module.addImport("zap", zap.module("zap"));
365
+
366
+ b.installArtifact(exe);
367
+ }
368
+ \`\`\`
369
+
370
+ With \`build.zig.zon\`:
371
+
372
+ \`\`\`zig
373
+ .{
374
+ .name = "myserver",
375
+ .version = "0.1.0",
376
+ .dependencies = .{
377
+ .zap = .{
378
+ .url = "https://github.com/zigzap/zap/archive/v0.6.0.tar.gz",
379
+ .hash = "...",
380
+ },
381
+ },
382
+ }
383
+ \`\`\`
384
+
385
+ ## Cross-Compilation
386
+
387
+ \`\`\`zig
388
+ pub fn build(b: *std.Build) void {
389
+ // Default: host platform
390
+ const target = b.standardTargetOptions(.{});
391
+
392
+ // Or specify explicit targets
393
+ const linux_x86 = b.resolveTargetQuery(.{
394
+ .cpu_arch = .x86_64,
395
+ .os_tag = .linux,
396
+ });
397
+
398
+ const windows_x86 = b.resolveTargetQuery(.{
399
+ .cpu_arch = .x86_64,
400
+ .os_tag = .windows,
401
+ });
402
+
403
+ // Build for each target
404
+ inline for (.{ target, linux_x86, windows_x86 }) |t| {
405
+ const exe = b.addExecutable(.{
406
+ .name = "myapp",
407
+ .root_source_file = b.path("src/main.zig"),
408
+ .target = t,
409
+ .optimize = b.standardOptimizeOption(.{}),
410
+ });
411
+ b.installArtifact(exe);
412
+ }
413
+ }
414
+ \`\`\`
415
+
416
+ ## C Interop
417
+
418
+ \`\`\`zig
419
+ pub fn build(b: *std.Build) void {
420
+ const target = b.standardTargetOptions(.{});
421
+ const optimize = b.standardOptimizeOption(.{});
422
+
423
+ const exe = b.addExecutable(.{
424
+ .name = "myapp",
425
+ .root_source_file = b.path("src/main.zig"),
426
+ .target = target,
427
+ .optimize = optimize,
428
+ });
429
+
430
+ // Link C library
431
+ exe.linkSystemLibrary("sqlite3");
432
+ exe.linkLibC();
433
+
434
+ // Add C source files
435
+ exe.addCSourceFiles(.{
436
+ .files = &.{ "src/legacy.c", "src/wrapper.c" },
437
+ .flags = &.{ "-std=c99", "-Wall" },
438
+ });
439
+
440
+ // Include paths
441
+ exe.addIncludePath(b.path("include"));
442
+
443
+ b.installArtifact(exe);
444
+ }
445
+ \`\`\`
446
+
447
+ ## Release Optimizations
448
+
449
+ \`\`\`zig
450
+ pub fn build(b: *std.Build) void {
451
+ const target = b.standardTargetOptions(.{});
452
+ const optimize = b.standardOptimizeOption(.{});
453
+
454
+ const exe = b.addExecutable(.{
455
+ .name = "myapp",
456
+ .root_source_file = b.path("src/main.zig"),
457
+ .target = target,
458
+ .optimize = optimize,
459
+ });
460
+
461
+ // Strip debug info in release
462
+ if (optimize != .Debug) {
463
+ exe.root_module.strip = true;
464
+ }
465
+
466
+ // Enable LTO for release
467
+ if (optimize == .ReleaseFast or optimize == .ReleaseSmall) {
468
+ exe.want_lto = true;
469
+ }
470
+
471
+ b.installArtifact(exe);
472
+ }
473
+ \`\`\`
474
+
475
+ ---
476
+
477
+ *The build system is sacred. Praise the Machine Spirit.*
478
+ `;
479
+
480
+ export const ZIG_PRESET: Preset = {
481
+ name: "zig",
482
+ manifest: {
483
+ name: "zig",
484
+ version: "0.1.0",
485
+ description: "The Zig Codex — Systems programming with safety",
486
+ depends: ["base"],
487
+ generated_paths: ["src/", "build.zig", "build.zig.zon"],
488
+ },
489
+ files: [
490
+ { path: "testing/strategy.md", content: TESTING_STRATEGY_MD },
491
+ { path: "guardrails.md", content: GUARDRAILS_MD },
492
+ { path: "design/patterns.md", content: BUILD_PATTERNS_MD },
493
+ ],
494
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Integration Tests for the Add Command
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
6
+ import { mkdtemp, rm, stat } from "fs/promises";
7
+ import { join } from "path";
8
+ import { runInit } from "../../src/commands/init";
9
+ import { runAdd, formatAdd } from "../../src/commands/add";
10
+
11
+ describe("add command", () => {
12
+ let testDir: string;
13
+
14
+ beforeEach(async () => {
15
+ testDir = await mkdtemp("/tmp/rtfct-add-test-");
16
+ });
17
+
18
+ afterEach(async () => {
19
+ await rm(testDir, { recursive: true, force: true });
20
+ });
21
+
22
+ describe("without existing project", () => {
23
+ test("fails if .project does not exist", async () => {
24
+ const result = await runAdd(testDir, "zig");
25
+
26
+ expect(result.success).toBe(false);
27
+ expect(result.message).toContain("No .project/ folder found");
28
+ });
29
+ });
30
+
31
+ describe("with existing project", () => {
32
+ beforeEach(async () => {
33
+ await runInit(testDir);
34
+ });
35
+
36
+ test("adds preset successfully", async () => {
37
+ const result = await runAdd(testDir, "zig");
38
+
39
+ expect(result.success).toBe(true);
40
+ expect(result.message).toContain("incorporated");
41
+
42
+ const presetDir = join(testDir, ".project", "presets", "zig");
43
+ const stats = await stat(presetDir);
44
+ expect(stats.isDirectory()).toBe(true);
45
+ });
46
+
47
+ test("adds typescript preset", async () => {
48
+ const result = await runAdd(testDir, "typescript");
49
+
50
+ expect(result.success).toBe(true);
51
+
52
+ const presetDir = join(testDir, ".project", "presets", "typescript");
53
+ const stats = await stat(presetDir);
54
+ expect(stats.isDirectory()).toBe(true);
55
+ });
56
+
57
+ test("adds elixir preset", async () => {
58
+ const result = await runAdd(testDir, "elixir");
59
+
60
+ expect(result.success).toBe(true);
61
+
62
+ const presetDir = join(testDir, ".project", "presets", "elixir");
63
+ const stats = await stat(presetDir);
64
+ expect(stats.isDirectory()).toBe(true);
65
+ });
66
+
67
+ test("fails for unknown preset", async () => {
68
+ const result = await runAdd(testDir, "unknown");
69
+
70
+ expect(result.success).toBe(false);
71
+ expect(result.message).toContain("Unknown preset");
72
+ });
73
+
74
+ test("fails if preset already installed", async () => {
75
+ await runAdd(testDir, "zig");
76
+ const result = await runAdd(testDir, "zig");
77
+
78
+ expect(result.success).toBe(false);
79
+ expect(result.message).toContain("already incorporated");
80
+ });
81
+ });
82
+
83
+ describe("output formatting", () => {
84
+ beforeEach(async () => {
85
+ await runInit(testDir);
86
+ });
87
+
88
+ test("formats success message", async () => {
89
+ const result = await runAdd(testDir, "zig");
90
+ const output = formatAdd(result);
91
+
92
+ expect(output).toContain("✓");
93
+ expect(output).toContain("incorporated");
94
+ expect(output).toContain("Omnissiah");
95
+ });
96
+
97
+ test("formats failure message", async () => {
98
+ const result = await runAdd(testDir, "unknown");
99
+ const output = formatAdd(result);
100
+
101
+ expect(output).toContain("✗");
102
+ });
103
+ });
104
+ });