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,197 @@
1
+ /**
2
+ * Integration Tests for the Init Command
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
6
+ import { mkdtemp, rm, stat, readFile, readdir } from "fs/promises";
7
+ import { join } from "path";
8
+ import { runInit, formatInit } from "../../src/commands/init";
9
+
10
+ describe("init command", () => {
11
+ let testDir: string;
12
+
13
+ beforeEach(async () => {
14
+ testDir = await mkdtemp("/tmp/rtfct-init-test-");
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await rm(testDir, { recursive: true, force: true });
19
+ });
20
+
21
+ describe("basic initialization", () => {
22
+ test("creates .project directory", async () => {
23
+ const result = await runInit(testDir);
24
+
25
+ expect(result.success).toBe(true);
26
+ const projectDir = join(testDir, ".project");
27
+ const stats = await stat(projectDir);
28
+ expect(stats.isDirectory()).toBe(true);
29
+ });
30
+
31
+ test("creates protocol.md", async () => {
32
+ await runInit(testDir);
33
+
34
+ const filePath = join(testDir, ".project", "protocol.md");
35
+ const stats = await stat(filePath);
36
+ expect(stats.isFile()).toBe(true);
37
+ });
38
+
39
+ test("creates theology.md", async () => {
40
+ await runInit(testDir);
41
+
42
+ const filePath = join(testDir, ".project", "theology.md");
43
+ const stats = await stat(filePath);
44
+ expect(stats.isFile()).toBe(true);
45
+ });
46
+
47
+ test("creates kickstart.md", async () => {
48
+ await runInit(testDir);
49
+
50
+ const filePath = join(testDir, ".project", "kickstart.md");
51
+ const stats = await stat(filePath);
52
+ expect(stats.isFile()).toBe(true);
53
+ });
54
+
55
+ test("creates guardrails.md", async () => {
56
+ await runInit(testDir);
57
+
58
+ const filePath = join(testDir, ".project", "guardrails.md");
59
+ const stats = await stat(filePath);
60
+ expect(stats.isFile()).toBe(true);
61
+ });
62
+
63
+ test("creates kanban directory with files", async () => {
64
+ await runInit(testDir);
65
+
66
+ const kanbanDir = join(testDir, ".project", "kanban");
67
+ const stats = await stat(kanbanDir);
68
+ expect(stats.isDirectory()).toBe(true);
69
+
70
+ const files = await readdir(kanbanDir);
71
+ expect(files).toContain("backlog.md");
72
+ expect(files).toContain("in-progress.md");
73
+ expect(files).toContain("done.md");
74
+ });
75
+
76
+ test("creates all subdirectories", async () => {
77
+ await runInit(testDir);
78
+
79
+ const dirs = [
80
+ "specs",
81
+ "design",
82
+ "adrs",
83
+ "kanban",
84
+ "testing",
85
+ "references",
86
+ "presets",
87
+ ];
88
+
89
+ for (const dir of dirs) {
90
+ const dirPath = join(testDir, ".project", dir);
91
+ const stats = await stat(dirPath);
92
+ expect(stats.isDirectory()).toBe(true);
93
+ }
94
+ });
95
+ });
96
+
97
+ describe("existing project handling", () => {
98
+ test("fails if .project already exists", async () => {
99
+ // First init
100
+ await runInit(testDir);
101
+
102
+ // Second init should fail
103
+ const result = await runInit(testDir);
104
+ expect(result.success).toBe(false);
105
+ expect(result.message).toContain("already exist");
106
+ });
107
+
108
+ test("succeeds with --force flag", async () => {
109
+ // First init
110
+ await runInit(testDir);
111
+
112
+ // Second init with force
113
+ const result = await runInit(testDir, { force: true });
114
+ expect(result.success).toBe(true);
115
+ });
116
+ });
117
+
118
+ describe("with presets", () => {
119
+ test("installs single preset", async () => {
120
+ const result = await runInit(testDir, { presets: ["zig"] });
121
+
122
+ expect(result.success).toBe(true);
123
+
124
+ const presetDir = join(testDir, ".project", "presets", "zig");
125
+ const stats = await stat(presetDir);
126
+ expect(stats.isDirectory()).toBe(true);
127
+ });
128
+
129
+ test("installs multiple presets", async () => {
130
+ const result = await runInit(testDir, {
131
+ presets: ["zig", "typescript"],
132
+ });
133
+
134
+ expect(result.success).toBe(true);
135
+
136
+ const zigDir = join(testDir, ".project", "presets", "zig");
137
+ const tsDir = join(testDir, ".project", "presets", "typescript");
138
+
139
+ const zigStats = await stat(zigDir);
140
+ const tsStats = await stat(tsDir);
141
+
142
+ expect(zigStats.isDirectory()).toBe(true);
143
+ expect(tsStats.isDirectory()).toBe(true);
144
+ });
145
+
146
+ test("reports errors for unknown presets", async () => {
147
+ const result = await runInit(testDir, { presets: ["unknown"] });
148
+
149
+ expect(result.success).toBe(true); // Still succeeds overall
150
+ expect(result.presetErrors).toBeDefined();
151
+ expect(result.presetErrors!.length).toBeGreaterThan(0);
152
+ });
153
+
154
+ test("installs valid presets even if some fail", async () => {
155
+ const result = await runInit(testDir, {
156
+ presets: ["zig", "unknown"],
157
+ });
158
+
159
+ expect(result.success).toBe(true);
160
+
161
+ // Zig should be installed
162
+ const zigDir = join(testDir, ".project", "presets", "zig");
163
+ const zigStats = await stat(zigDir);
164
+ expect(zigStats.isDirectory()).toBe(true);
165
+
166
+ // But we should have an error for unknown
167
+ expect(result.presetErrors!.length).toBe(1);
168
+ });
169
+ });
170
+
171
+ describe("output formatting", () => {
172
+ test("formats success message", async () => {
173
+ const result = await runInit(testDir);
174
+ const output = formatInit(result);
175
+
176
+ expect(output).toContain("✓");
177
+ expect(output).toContain("consecrated");
178
+ expect(output).toContain("Omnissiah");
179
+ });
180
+
181
+ test("formats failure message", async () => {
182
+ await runInit(testDir);
183
+ const result = await runInit(testDir); // Second init fails
184
+ const output = formatInit(result);
185
+
186
+ expect(output).toContain("✗");
187
+ expect(output).toContain("already exist");
188
+ });
189
+
190
+ test("formats preset warnings", async () => {
191
+ const result = await runInit(testDir, { presets: ["unknown"] });
192
+ const output = formatInit(result);
193
+
194
+ expect(output).toContain("warning");
195
+ });
196
+ });
197
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Integration Tests for the Praise Command
3
+ */
4
+
5
+ import { describe, test, expect } from "bun:test";
6
+ import { runPraise } from "../../src/commands/praise";
7
+
8
+ describe("praise command", () => {
9
+ test("outputs the litany", () => {
10
+ const output = runPraise();
11
+
12
+ expect(output).toContain("The flesh is weak, but the protocol is strong.");
13
+ expect(output).toContain("The code is temporary, but the spec endures.");
14
+ expect(output).toContain(
15
+ "The tests do not lie, and the agent does not tire."
16
+ );
17
+ expect(output).toContain(
18
+ "From specification, code. From code, verification. From verification, truth."
19
+ );
20
+ expect(output).toContain("The Omnissiah provides.");
21
+ expect(output).toContain("Praise the Machine Spirit.");
22
+ });
23
+
24
+ test("outputs exactly the expected litany", () => {
25
+ const output = runPraise();
26
+
27
+ const expected = `The flesh is weak, but the protocol is strong.
28
+ The code is temporary, but the spec endures.
29
+ The tests do not lie, and the agent does not tire.
30
+ From specification, code. From code, verification. From verification, truth.
31
+ The Omnissiah provides.
32
+ Praise the Machine Spirit.`;
33
+
34
+ expect(output).toBe(expected);
35
+ });
36
+ });
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Integration Tests for the Regenerate Command
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
6
+ import { mkdtemp, rm, stat, mkdir, writeFile } from "fs/promises";
7
+ import { join } from "path";
8
+ import { runInit } from "../../src/commands/init";
9
+ import {
10
+ runRegenerate,
11
+ formatRegenerate,
12
+ } from "../../src/commands/regenerate";
13
+
14
+ describe("regenerate command", () => {
15
+ let testDir: string;
16
+
17
+ beforeEach(async () => {
18
+ testDir = await mkdtemp("/tmp/rtfct-regen-test-");
19
+ });
20
+
21
+ afterEach(async () => {
22
+ await rm(testDir, { recursive: true, force: true });
23
+ });
24
+
25
+ describe("without existing project", () => {
26
+ test("fails if .project does not exist", async () => {
27
+ const result = await runRegenerate(testDir);
28
+
29
+ expect(result.success).toBe(false);
30
+ expect(result.message).toContain("No .project/ folder found");
31
+ });
32
+ });
33
+
34
+ describe("confirmation behavior", () => {
35
+ beforeEach(async () => {
36
+ await runInit(testDir);
37
+ });
38
+
39
+ test("requires confirmation without --yes", async () => {
40
+ const result = await runRegenerate(testDir);
41
+
42
+ expect(result.success).toBe(true);
43
+ expect(result.requiresConfirmation).toBe(true);
44
+ expect(result.pathsToDelete).toBeDefined();
45
+ });
46
+
47
+ test("proceeds with --yes flag", async () => {
48
+ const result = await runRegenerate(testDir, { yes: true });
49
+
50
+ expect(result.success).toBe(true);
51
+ expect(result.requiresConfirmation).toBeUndefined();
52
+ });
53
+ });
54
+
55
+ describe("path deletion", () => {
56
+ beforeEach(async () => {
57
+ await runInit(testDir);
58
+ });
59
+
60
+ test("deletes default paths when no presets", async () => {
61
+ // Create src/ and tests/
62
+ await mkdir(join(testDir, "src"));
63
+ await writeFile(join(testDir, "src", "index.ts"), "// code");
64
+ await mkdir(join(testDir, "tests"));
65
+ await writeFile(join(testDir, "tests", "test.ts"), "// test");
66
+
67
+ const result = await runRegenerate(testDir, { yes: true });
68
+
69
+ expect(result.success).toBe(true);
70
+ expect(result.deletedPaths).toContain("src/");
71
+ expect(result.deletedPaths).toContain("tests/");
72
+
73
+ // Verify paths are deleted
74
+ try {
75
+ await stat(join(testDir, "src"));
76
+ expect(true).toBe(false); // Should not reach here
77
+ } catch {
78
+ // Expected - directory should not exist
79
+ }
80
+ });
81
+
82
+ test("deletes paths from preset manifests", async () => {
83
+ await runInit(testDir, { presets: ["zig"], force: true });
84
+
85
+ // Create files that match the zig preset's generated_paths
86
+ await mkdir(join(testDir, "src"));
87
+ await writeFile(join(testDir, "src", "main.zig"), "// zig code");
88
+ await writeFile(join(testDir, "build.zig"), "// build");
89
+
90
+ const result = await runRegenerate(testDir, { yes: true });
91
+
92
+ expect(result.success).toBe(true);
93
+ expect(result.deletedPaths).toContain("src/");
94
+ expect(result.deletedPaths).toContain("build.zig");
95
+ });
96
+
97
+ test("handles non-existent paths gracefully", async () => {
98
+ // Don't create src/ or tests/
99
+ const result = await runRegenerate(testDir, { yes: true });
100
+
101
+ expect(result.success).toBe(true);
102
+ // Paths might be empty since nothing existed to delete
103
+ });
104
+
105
+ test("preserves .project directory", async () => {
106
+ await mkdir(join(testDir, "src"));
107
+
108
+ await runRegenerate(testDir, { yes: true });
109
+
110
+ // .project should still exist
111
+ const projectStats = await stat(join(testDir, ".project"));
112
+ expect(projectStats.isDirectory()).toBe(true);
113
+
114
+ // And its files should still exist
115
+ const protocolStats = await stat(join(testDir, ".project", "protocol.md"));
116
+ expect(protocolStats.isFile()).toBe(true);
117
+ });
118
+ });
119
+
120
+ describe("output formatting", () => {
121
+ beforeEach(async () => {
122
+ await runInit(testDir);
123
+ });
124
+
125
+ test("formats confirmation request", async () => {
126
+ const result = await runRegenerate(testDir);
127
+ const output = formatRegenerate(result);
128
+
129
+ expect(output).toContain("Purification");
130
+ expect(output).toContain("Run with --yes");
131
+ });
132
+
133
+ test("formats success output", async () => {
134
+ await mkdir(join(testDir, "src"));
135
+
136
+ const result = await runRegenerate(testDir, { yes: true });
137
+ const output = formatRegenerate(result);
138
+
139
+ expect(output).toContain("✓");
140
+ expect(output).toContain("purified");
141
+ expect(output).toContain("Omnissiah");
142
+ });
143
+
144
+ test("formats failure output", async () => {
145
+ await rm(testDir, { recursive: true, force: true });
146
+ testDir = await mkdtemp("/tmp/rtfct-regen-test-");
147
+
148
+ const result = await runRegenerate(testDir);
149
+ const output = formatRegenerate(result);
150
+
151
+ expect(output).toContain("✗");
152
+ });
153
+ });
154
+ });
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Integration Tests for the Status Command
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
6
+ import { mkdtemp, rm, writeFile, mkdir } from "fs/promises";
7
+ import { join } from "path";
8
+ import { runInit } from "../../src/commands/init";
9
+ import { runStatus, formatStatus } from "../../src/commands/status";
10
+
11
+ describe("status command", () => {
12
+ let testDir: string;
13
+
14
+ beforeEach(async () => {
15
+ testDir = await mkdtemp("/tmp/rtfct-status-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 runStatus(testDir);
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("returns success", async () => {
37
+ const result = await runStatus(testDir);
38
+ expect(result.success).toBe(true);
39
+ });
40
+
41
+ test("returns project name", async () => {
42
+ const result = await runStatus(testDir);
43
+ expect(result.data).toBeDefined();
44
+ expect(result.data!.projectName).toBeDefined();
45
+ });
46
+
47
+ test("counts tasks from default kanban files", async () => {
48
+ const result = await runStatus(testDir);
49
+
50
+ // Default template has 1 task in backlog
51
+ expect(result.data!.backlogCount).toBe(1);
52
+ expect(result.data!.inProgressCount).toBe(0);
53
+ expect(result.data!.doneCount).toBe(0);
54
+ });
55
+
56
+ test("returns last activity date", async () => {
57
+ const result = await runStatus(testDir);
58
+ expect(result.data!.lastActivity).not.toBeNull();
59
+ });
60
+ });
61
+
62
+ describe("with custom kanban content", () => {
63
+ beforeEach(async () => {
64
+ await runInit(testDir);
65
+ });
66
+
67
+ test("counts multiple backlog tasks", async () => {
68
+ const backlogContent = `# Backlog
69
+
70
+ ## [TASK-001] First Task
71
+
72
+ ## [TASK-002] Second Task
73
+
74
+ ## [TASK-003] Third Task
75
+ `;
76
+ await writeFile(
77
+ join(testDir, ".project", "kanban", "backlog.md"),
78
+ backlogContent
79
+ );
80
+
81
+ const result = await runStatus(testDir);
82
+ expect(result.data!.backlogCount).toBe(3);
83
+ });
84
+
85
+ test("extracts current task from in-progress", async () => {
86
+ const inProgressContent = `# In Progress
87
+
88
+ ## [TASK-004] Implement user auth
89
+
90
+ Working on it.
91
+ `;
92
+ await writeFile(
93
+ join(testDir, ".project", "kanban", "in-progress.md"),
94
+ inProgressContent
95
+ );
96
+
97
+ const result = await runStatus(testDir);
98
+ expect(result.data!.inProgressCount).toBe(1);
99
+ expect(result.data!.currentTask).not.toBeNull();
100
+ expect(result.data!.currentTask!.id).toBe("TASK-004");
101
+ expect(result.data!.currentTask!.title).toBe("Implement user auth");
102
+ });
103
+
104
+ test("counts completed tasks", async () => {
105
+ const doneContent = `# Done
106
+
107
+ ## [TASK-001] First Completed
108
+
109
+ ## [TASK-002] Second Completed
110
+ `;
111
+ await writeFile(
112
+ join(testDir, ".project", "kanban", "done.md"),
113
+ doneContent
114
+ );
115
+
116
+ const result = await runStatus(testDir);
117
+ expect(result.data!.doneCount).toBe(2);
118
+ });
119
+ });
120
+
121
+ describe("output formatting", () => {
122
+ beforeEach(async () => {
123
+ await runInit(testDir);
124
+ });
125
+
126
+ test("formats success output", async () => {
127
+ const result = await runStatus(testDir);
128
+ const output = formatStatus(result);
129
+
130
+ expect(output).toContain("rtfct:");
131
+ expect(output).toContain("Litany of Tasks");
132
+ expect(output).toContain("Backlog:");
133
+ expect(output).toContain("In Progress:");
134
+ expect(output).toContain("Completed:");
135
+ expect(output).toContain("Omnissiah");
136
+ });
137
+
138
+ test("formats current task", async () => {
139
+ const inProgressContent = `# In Progress
140
+
141
+ ## [TASK-007] The Holy Task
142
+ `;
143
+ await writeFile(
144
+ join(testDir, ".project", "kanban", "in-progress.md"),
145
+ inProgressContent
146
+ );
147
+
148
+ const result = await runStatus(testDir);
149
+ const output = formatStatus(result);
150
+
151
+ expect(output).toContain("[TASK-007]");
152
+ expect(output).toContain("The Holy Task");
153
+ });
154
+
155
+ test("formats failure output", async () => {
156
+ await rm(testDir, { recursive: true, force: true });
157
+ testDir = await mkdtemp("/tmp/rtfct-status-test-");
158
+
159
+ const result = await runStatus(testDir);
160
+ const output = formatStatus(result);
161
+
162
+ expect(output).toContain("✗");
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Unit Tests for the Argument Parser
3
+ */
4
+
5
+ import { describe, test, expect } from "bun:test";
6
+ import { parseArgs } from "../../src/args";
7
+
8
+ describe("parseArgs", () => {
9
+ describe("commands", () => {
10
+ test("parses init command", () => {
11
+ const result = parseArgs(["init"]);
12
+ expect(result.command).toBe("init");
13
+ expect(result.error).toBeUndefined();
14
+ });
15
+
16
+ test("parses add command", () => {
17
+ const result = parseArgs(["add"]);
18
+ expect(result.command).toBe("add");
19
+ expect(result.error).toBeUndefined();
20
+ });
21
+
22
+ test("parses status command", () => {
23
+ const result = parseArgs(["status"]);
24
+ expect(result.command).toBe("status");
25
+ expect(result.error).toBeUndefined();
26
+ });
27
+
28
+ test("parses regenerate command", () => {
29
+ const result = parseArgs(["regenerate"]);
30
+ expect(result.command).toBe("regenerate");
31
+ expect(result.error).toBeUndefined();
32
+ });
33
+
34
+ test("parses praise command", () => {
35
+ const result = parseArgs(["praise"]);
36
+ expect(result.command).toBe("praise");
37
+ expect(result.error).toBeUndefined();
38
+ });
39
+
40
+ test("returns error for unknown command", () => {
41
+ const result = parseArgs(["unknown"]);
42
+ expect(result.error).toBe("Unknown command: unknown");
43
+ });
44
+
45
+ test("no command returns undefined", () => {
46
+ const result = parseArgs([]);
47
+ expect(result.command).toBeUndefined();
48
+ expect(result.error).toBeUndefined();
49
+ });
50
+ });
51
+
52
+ describe("flags", () => {
53
+ test("parses --help flag", () => {
54
+ const result = parseArgs(["--help"]);
55
+ expect(result.flags.help).toBe(true);
56
+ });
57
+
58
+ test("parses -h flag", () => {
59
+ const result = parseArgs(["-h"]);
60
+ expect(result.flags.help).toBe(true);
61
+ });
62
+
63
+ test("parses --version flag", () => {
64
+ const result = parseArgs(["--version"]);
65
+ expect(result.flags.version).toBe(true);
66
+ });
67
+
68
+ test("parses -v flag", () => {
69
+ const result = parseArgs(["-v"]);
70
+ expect(result.flags.version).toBe(true);
71
+ });
72
+
73
+ test("parses --force flag", () => {
74
+ const result = parseArgs(["init", "--force"]);
75
+ expect(result.flags.force).toBe(true);
76
+ });
77
+
78
+ test("parses -f flag", () => {
79
+ const result = parseArgs(["init", "-f"]);
80
+ expect(result.flags.force).toBe(true);
81
+ });
82
+
83
+ test("parses --yes flag", () => {
84
+ const result = parseArgs(["regenerate", "--yes"]);
85
+ expect(result.flags.yes).toBe(true);
86
+ });
87
+
88
+ test("parses -y flag", () => {
89
+ const result = parseArgs(["regenerate", "-y"]);
90
+ expect(result.flags.yes).toBe(true);
91
+ });
92
+
93
+ test("parses --with flag with single preset", () => {
94
+ const result = parseArgs(["init", "--with", "zig"]);
95
+ expect(result.flags.with).toEqual(["zig"]);
96
+ });
97
+
98
+ test("parses --with flag with multiple presets", () => {
99
+ const result = parseArgs(["init", "--with", "zig,typescript"]);
100
+ expect(result.flags.with).toEqual(["zig", "typescript"]);
101
+ });
102
+
103
+ test("returns error for unknown flag", () => {
104
+ const result = parseArgs(["--unknown"]);
105
+ expect(result.error).toBe("Unknown flag: --unknown");
106
+ });
107
+
108
+ test("returns error for unknown short flag", () => {
109
+ const result = parseArgs(["-x"]);
110
+ expect(result.error).toBe("Unknown flag: -x");
111
+ });
112
+ });
113
+
114
+ describe("arguments", () => {
115
+ test("collects arguments after command", () => {
116
+ const result = parseArgs(["add", "zig"]);
117
+ expect(result.command).toBe("add");
118
+ expect(result.args).toEqual(["zig"]);
119
+ });
120
+
121
+ test("collects multiple arguments", () => {
122
+ const result = parseArgs(["add", "zig", "typescript"]);
123
+ expect(result.args).toEqual(["zig", "typescript"]);
124
+ });
125
+ });
126
+
127
+ describe("combined", () => {
128
+ test("parses command with flags and arguments", () => {
129
+ const result = parseArgs(["init", "--force", "--with", "zig"]);
130
+ expect(result.command).toBe("init");
131
+ expect(result.flags.force).toBe(true);
132
+ expect(result.flags.with).toEqual(["zig"]);
133
+ });
134
+
135
+ test("flags default to false", () => {
136
+ const result = parseArgs(["init"]);
137
+ expect(result.flags.help).toBe(false);
138
+ expect(result.flags.version).toBe(false);
139
+ expect(result.flags.force).toBe(false);
140
+ expect(result.flags.yes).toBe(false);
141
+ expect(result.flags.with).toEqual([]);
142
+ });
143
+ });
144
+ });