agent-trajectories 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.
- package/.beads/.local_version +1 -0
- package/.beads/README.md +81 -0
- package/.beads/config.yaml +62 -0
- package/.beads/issues.jsonl +0 -0
- package/.beads/metadata.json +4 -0
- package/.gitattributes +3 -0
- package/IMPLEMENTATION-PROPOSAL.md +1598 -0
- package/PROPOSAL-trajectories.md +1582 -0
- package/README.md +275 -0
- package/biome.json +20 -0
- package/docs/architecture/core.md +477 -0
- package/docs/architecture/memory-integration.md +186 -0
- package/docs/architecture/web-viewer.md +213 -0
- package/package.json +47 -0
- package/src/cli/commands/abandon.ts +32 -0
- package/src/cli/commands/complete.ts +51 -0
- package/src/cli/commands/decision.ts +49 -0
- package/src/cli/commands/export.ts +100 -0
- package/src/cli/commands/index.ts +39 -0
- package/src/cli/commands/list.ts +90 -0
- package/src/cli/commands/show.ts +98 -0
- package/src/cli/commands/start.ts +53 -0
- package/src/cli/commands/status.ts +68 -0
- package/src/cli/index.ts +25 -0
- package/src/cli/runner.ts +67 -0
- package/src/core/id.ts +62 -0
- package/src/core/index.ts +54 -0
- package/src/core/schema.ts +272 -0
- package/src/core/trajectory.ts +283 -0
- package/src/core/types.ts +272 -0
- package/src/export/index.ts +10 -0
- package/src/export/json.ts +26 -0
- package/src/export/markdown.ts +216 -0
- package/src/export/pr-summary.ts +57 -0
- package/src/export/timeline.ts +69 -0
- package/src/index.ts +69 -0
- package/src/storage/file.ts +394 -0
- package/src/storage/index.ts +6 -0
- package/src/storage/interface.ts +92 -0
- package/src/web/generator.ts +347 -0
- package/src/web/index.ts +6 -0
- package/src/web/styles.ts +355 -0
- package/src/workspace/index.ts +6 -0
- package/src/workspace/storage.ts +231 -0
- package/src/workspace/types.ts +88 -0
- package/tests/cli/commands.test.ts +476 -0
- package/tests/core/trajectory.test.ts +426 -0
- package/tests/export/export.test.ts +376 -0
- package/tests/storage/storage.test.ts +410 -0
- package/tests/web/generator.test.ts +197 -0
- package/tests/workspace/storage.test.ts +275 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +14 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test stubs for CLI commands
|
|
8
|
+
*
|
|
9
|
+
* These tests define the expected behavior of the CLI.
|
|
10
|
+
* All tests should FAIL until implementation is complete.
|
|
11
|
+
*
|
|
12
|
+
* Coverage: happy path, edge cases, error scenarios
|
|
13
|
+
* Structure: Arrange-Act-Assert
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
describe("CLI Commands", () => {
|
|
17
|
+
let tempDir: string;
|
|
18
|
+
let originalCwd: string;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
tempDir = await mkdtemp(join(tmpdir(), "trail-cli-test-"));
|
|
22
|
+
originalCwd = process.cwd();
|
|
23
|
+
process.chdir(tempDir);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(async () => {
|
|
27
|
+
process.chdir(originalCwd);
|
|
28
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("trail start", () => {
|
|
32
|
+
it("should create a new trajectory", async () => {
|
|
33
|
+
// Arrange
|
|
34
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
35
|
+
|
|
36
|
+
// Act
|
|
37
|
+
const result = await runCommand(["start", "Implement auth"]);
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(result.success).toBe(true);
|
|
41
|
+
expect(result.output).toContain("Trajectory started");
|
|
42
|
+
expect(result.output).toMatch(/traj_[a-z0-9]+/);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should fail if a trajectory is already active", async () => {
|
|
46
|
+
// Arrange
|
|
47
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
48
|
+
await runCommand(["start", "First task"]);
|
|
49
|
+
|
|
50
|
+
// Act
|
|
51
|
+
const result = await runCommand(["start", "Second task"]);
|
|
52
|
+
|
|
53
|
+
// Assert
|
|
54
|
+
expect(result.success).toBe(false);
|
|
55
|
+
expect(result.error).toContain("already active");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should support --task flag for external reference", async () => {
|
|
59
|
+
// Arrange
|
|
60
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
61
|
+
|
|
62
|
+
// Act
|
|
63
|
+
const result = await runCommand([
|
|
64
|
+
"start",
|
|
65
|
+
"Fix bug",
|
|
66
|
+
"--task",
|
|
67
|
+
"GH#123",
|
|
68
|
+
"--source",
|
|
69
|
+
"github",
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
// Assert
|
|
73
|
+
expect(result.success).toBe(true);
|
|
74
|
+
|
|
75
|
+
// Verify the trajectory has the task reference
|
|
76
|
+
const { FileStorage } = await import("../../src/storage/file.js");
|
|
77
|
+
const storage = new FileStorage(tempDir);
|
|
78
|
+
await storage.initialize();
|
|
79
|
+
const active = await storage.getActive();
|
|
80
|
+
expect(active?.task.source?.id).toBe("GH#123");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("trail status", () => {
|
|
85
|
+
it("should show active trajectory details", async () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
88
|
+
await runCommand(["start", "Test task"]);
|
|
89
|
+
|
|
90
|
+
// Act
|
|
91
|
+
const result = await runCommand(["status"]);
|
|
92
|
+
|
|
93
|
+
// Assert
|
|
94
|
+
expect(result.success).toBe(true);
|
|
95
|
+
expect(result.output).toContain("Test task");
|
|
96
|
+
expect(result.output).toContain("active");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should indicate when no trajectory is active", async () => {
|
|
100
|
+
// Arrange
|
|
101
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
102
|
+
|
|
103
|
+
// Act
|
|
104
|
+
const result = await runCommand(["status"]);
|
|
105
|
+
|
|
106
|
+
// Assert
|
|
107
|
+
expect(result.success).toBe(true);
|
|
108
|
+
expect(result.output).toContain("No active trajectory");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should show chapter and event counts", async () => {
|
|
112
|
+
// Arrange
|
|
113
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
114
|
+
await runCommand(["start", "Test task"]);
|
|
115
|
+
await runCommand(["decision", "Choice A", "--reasoning", "Because"]);
|
|
116
|
+
|
|
117
|
+
// Act
|
|
118
|
+
const result = await runCommand(["status"]);
|
|
119
|
+
|
|
120
|
+
// Assert
|
|
121
|
+
// Start command creates initial chapter, decision adds an event
|
|
122
|
+
expect(result.output).toMatch(/Chapters:\s+1/);
|
|
123
|
+
expect(result.output).toMatch(/Events:\s+1/);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("trail decision", () => {
|
|
128
|
+
it("should record a decision in the active trajectory", async () => {
|
|
129
|
+
// Arrange
|
|
130
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
131
|
+
await runCommand(["start", "Test task"]);
|
|
132
|
+
|
|
133
|
+
// Act
|
|
134
|
+
const result = await runCommand([
|
|
135
|
+
"decision",
|
|
136
|
+
"Chose JWT",
|
|
137
|
+
"--reasoning",
|
|
138
|
+
"Stateless scaling",
|
|
139
|
+
"--alternatives",
|
|
140
|
+
"Sessions,OAuth",
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
// Assert
|
|
144
|
+
expect(result.success).toBe(true);
|
|
145
|
+
expect(result.output).toContain("Decision recorded");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should fail without an active trajectory", async () => {
|
|
149
|
+
// Arrange
|
|
150
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
151
|
+
|
|
152
|
+
// Act
|
|
153
|
+
const result = await runCommand([
|
|
154
|
+
"decision",
|
|
155
|
+
"Choice",
|
|
156
|
+
"--reasoning",
|
|
157
|
+
"Why",
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
// Assert
|
|
161
|
+
expect(result.success).toBe(false);
|
|
162
|
+
expect(result.error).toContain("No active trajectory");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should allow decisions without reasoning (optional)", async () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
168
|
+
await runCommand(["start", "Test task"]);
|
|
169
|
+
|
|
170
|
+
// Act - reasoning is now optional for minor decisions
|
|
171
|
+
const result = await runCommand(["decision", "Minor choice"]);
|
|
172
|
+
|
|
173
|
+
// Assert
|
|
174
|
+
expect(result.success).toBe(true);
|
|
175
|
+
expect(result.output).toContain("Decision recorded");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("trail complete", () => {
|
|
180
|
+
it("should complete the trajectory with retrospective", async () => {
|
|
181
|
+
// Arrange
|
|
182
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
183
|
+
await runCommand(["start", "Test task"]);
|
|
184
|
+
|
|
185
|
+
// Mock the interactive prompt
|
|
186
|
+
vi.mock("@clack/prompts", () => ({
|
|
187
|
+
text: vi.fn().mockResolvedValue("Summary of work"),
|
|
188
|
+
confirm: vi.fn().mockResolvedValue(true),
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
// Act
|
|
192
|
+
const result = await runCommand([
|
|
193
|
+
"complete",
|
|
194
|
+
"--summary",
|
|
195
|
+
"Implemented feature",
|
|
196
|
+
"--confidence",
|
|
197
|
+
"0.85",
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
// Assert
|
|
201
|
+
expect(result.success).toBe(true);
|
|
202
|
+
expect(result.output).toContain("Trajectory completed");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should fail without an active trajectory", async () => {
|
|
206
|
+
// Arrange
|
|
207
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
208
|
+
|
|
209
|
+
// Act
|
|
210
|
+
const result = await runCommand([
|
|
211
|
+
"complete",
|
|
212
|
+
"--summary",
|
|
213
|
+
"Done",
|
|
214
|
+
"--confidence",
|
|
215
|
+
"0.9",
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
// Assert
|
|
219
|
+
expect(result.success).toBe(false);
|
|
220
|
+
expect(result.error).toContain("No active trajectory");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("trail abandon", () => {
|
|
225
|
+
it("should abandon the active trajectory", async () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
228
|
+
await runCommand(["start", "Test task"]);
|
|
229
|
+
|
|
230
|
+
// Act
|
|
231
|
+
const result = await runCommand(["abandon", "--reason", "Requirements changed"]);
|
|
232
|
+
|
|
233
|
+
// Assert
|
|
234
|
+
expect(result.success).toBe(true);
|
|
235
|
+
expect(result.output).toContain("Trajectory abandoned");
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("trail list", () => {
|
|
240
|
+
it("should list trajectories", async () => {
|
|
241
|
+
// Arrange
|
|
242
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
243
|
+
await runCommand(["start", "Task 1"]);
|
|
244
|
+
await runCommand([
|
|
245
|
+
"complete",
|
|
246
|
+
"--summary",
|
|
247
|
+
"Done",
|
|
248
|
+
"--confidence",
|
|
249
|
+
"0.9",
|
|
250
|
+
]);
|
|
251
|
+
await runCommand(["start", "Task 2"]);
|
|
252
|
+
|
|
253
|
+
// Act
|
|
254
|
+
const result = await runCommand(["list"]);
|
|
255
|
+
|
|
256
|
+
// Assert
|
|
257
|
+
expect(result.success).toBe(true);
|
|
258
|
+
expect(result.output).toContain("Task 1");
|
|
259
|
+
expect(result.output).toContain("Task 2");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should filter by status", async () => {
|
|
263
|
+
// Arrange
|
|
264
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
265
|
+
await runCommand(["start", "Task 1"]);
|
|
266
|
+
await runCommand([
|
|
267
|
+
"complete",
|
|
268
|
+
"--summary",
|
|
269
|
+
"Done",
|
|
270
|
+
"--confidence",
|
|
271
|
+
"0.9",
|
|
272
|
+
]);
|
|
273
|
+
await runCommand(["start", "Task 2"]);
|
|
274
|
+
|
|
275
|
+
// Act
|
|
276
|
+
const result = await runCommand(["list", "--status", "completed"]);
|
|
277
|
+
|
|
278
|
+
// Assert
|
|
279
|
+
expect(result.output).toContain("Task 1");
|
|
280
|
+
expect(result.output).not.toContain("Task 2");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should limit results", async () => {
|
|
284
|
+
// Arrange
|
|
285
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
286
|
+
for (let i = 1; i <= 5; i++) {
|
|
287
|
+
await runCommand(["start", `Task ${i}`]);
|
|
288
|
+
await runCommand([
|
|
289
|
+
"complete",
|
|
290
|
+
"--summary",
|
|
291
|
+
"Done",
|
|
292
|
+
"--confidence",
|
|
293
|
+
"0.9",
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Act
|
|
298
|
+
const result = await runCommand(["list", "--limit", "3"]);
|
|
299
|
+
|
|
300
|
+
// Assert
|
|
301
|
+
const lines = result.output.split("\n").filter((l) => l.includes("Task"));
|
|
302
|
+
expect(lines.length).toBeLessThanOrEqual(3);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should search trajectories with --search flag", async () => {
|
|
306
|
+
// Arrange
|
|
307
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
308
|
+
await runCommand(["start", "Implement authentication"]);
|
|
309
|
+
await runCommand([
|
|
310
|
+
"complete",
|
|
311
|
+
"--summary",
|
|
312
|
+
"Added JWT auth",
|
|
313
|
+
"--confidence",
|
|
314
|
+
"0.9",
|
|
315
|
+
]);
|
|
316
|
+
await runCommand(["start", "Fix database bug"]);
|
|
317
|
+
await runCommand([
|
|
318
|
+
"complete",
|
|
319
|
+
"--summary",
|
|
320
|
+
"Fixed query",
|
|
321
|
+
"--confidence",
|
|
322
|
+
"0.9",
|
|
323
|
+
]);
|
|
324
|
+
|
|
325
|
+
// Act
|
|
326
|
+
const result = await runCommand(["list", "--search", "auth"]);
|
|
327
|
+
|
|
328
|
+
// Assert
|
|
329
|
+
expect(result.success).toBe(true);
|
|
330
|
+
expect(result.output).toContain("authentication");
|
|
331
|
+
expect(result.output).not.toContain("database");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe("trail show", () => {
|
|
336
|
+
it("should show trajectory details", async () => {
|
|
337
|
+
// Arrange
|
|
338
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
339
|
+
const startResult = await runCommand(["start", "Test task"]);
|
|
340
|
+
const idMatch = startResult.output.match(/traj_[a-z0-9]+/);
|
|
341
|
+
const id = idMatch?.[0];
|
|
342
|
+
|
|
343
|
+
// Act
|
|
344
|
+
const result = await runCommand(["show", id!]);
|
|
345
|
+
|
|
346
|
+
// Assert
|
|
347
|
+
expect(result.success).toBe(true);
|
|
348
|
+
expect(result.output).toContain("Test task");
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("should show decisions with --decisions flag", async () => {
|
|
352
|
+
// Arrange
|
|
353
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
354
|
+
await runCommand(["start", "Test task"]);
|
|
355
|
+
await runCommand([
|
|
356
|
+
"decision",
|
|
357
|
+
"Chose A",
|
|
358
|
+
"--reasoning",
|
|
359
|
+
"Because",
|
|
360
|
+
"--alternatives",
|
|
361
|
+
"B,C",
|
|
362
|
+
]);
|
|
363
|
+
const status = await runCommand(["status"]);
|
|
364
|
+
const idMatch = status.output.match(/traj_[a-z0-9]+/);
|
|
365
|
+
const id = idMatch?.[0];
|
|
366
|
+
|
|
367
|
+
// Act
|
|
368
|
+
const result = await runCommand(["show", id!, "--decisions"]);
|
|
369
|
+
|
|
370
|
+
// Assert
|
|
371
|
+
expect(result.output).toContain("Chose A");
|
|
372
|
+
expect(result.output).toContain("Because");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("should fail for non-existent trajectory", async () => {
|
|
376
|
+
// Arrange
|
|
377
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
378
|
+
|
|
379
|
+
// Act
|
|
380
|
+
const result = await runCommand(["show", "traj_nonexistent"]);
|
|
381
|
+
|
|
382
|
+
// Assert
|
|
383
|
+
expect(result.success).toBe(false);
|
|
384
|
+
expect(result.error).toContain("not found");
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe("trail export", () => {
|
|
389
|
+
it("should export trajectory as markdown", async () => {
|
|
390
|
+
// Arrange
|
|
391
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
392
|
+
await runCommand(["start", "Test task"]);
|
|
393
|
+
await runCommand([
|
|
394
|
+
"decision",
|
|
395
|
+
"Chose A",
|
|
396
|
+
"--reasoning",
|
|
397
|
+
"Because",
|
|
398
|
+
]);
|
|
399
|
+
await runCommand([
|
|
400
|
+
"complete",
|
|
401
|
+
"--summary",
|
|
402
|
+
"Done",
|
|
403
|
+
"--confidence",
|
|
404
|
+
"0.9",
|
|
405
|
+
]);
|
|
406
|
+
const list = await runCommand(["list"]);
|
|
407
|
+
const idMatch = list.output.match(/traj_[a-z0-9]+/);
|
|
408
|
+
const id = idMatch?.[0];
|
|
409
|
+
|
|
410
|
+
// Act
|
|
411
|
+
const result = await runCommand(["export", id!, "--format", "md"]);
|
|
412
|
+
|
|
413
|
+
// Assert
|
|
414
|
+
expect(result.success).toBe(true);
|
|
415
|
+
expect(result.output).toContain("# Trajectory:");
|
|
416
|
+
expect(result.output).toContain("## Summary");
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("should export trajectory as JSON", async () => {
|
|
420
|
+
// Arrange
|
|
421
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
422
|
+
await runCommand(["start", "Test task"]);
|
|
423
|
+
await runCommand([
|
|
424
|
+
"complete",
|
|
425
|
+
"--summary",
|
|
426
|
+
"Done",
|
|
427
|
+
"--confidence",
|
|
428
|
+
"0.9",
|
|
429
|
+
]);
|
|
430
|
+
const list = await runCommand(["list"]);
|
|
431
|
+
const idMatch = list.output.match(/traj_[a-z0-9]+/);
|
|
432
|
+
const id = idMatch?.[0];
|
|
433
|
+
|
|
434
|
+
// Act
|
|
435
|
+
const result = await runCommand(["export", id!, "--format", "json"]);
|
|
436
|
+
|
|
437
|
+
// Assert
|
|
438
|
+
expect(result.success).toBe(true);
|
|
439
|
+
const parsed = JSON.parse(result.output);
|
|
440
|
+
expect(parsed.id).toBe(id);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("should write to file with --output flag", async () => {
|
|
444
|
+
// Arrange
|
|
445
|
+
const { runCommand } = await import("../../src/cli/runner.js");
|
|
446
|
+
await runCommand(["start", "Test task"]);
|
|
447
|
+
await runCommand([
|
|
448
|
+
"complete",
|
|
449
|
+
"--summary",
|
|
450
|
+
"Done",
|
|
451
|
+
"--confidence",
|
|
452
|
+
"0.9",
|
|
453
|
+
]);
|
|
454
|
+
const list = await runCommand(["list"]);
|
|
455
|
+
const idMatch = list.output.match(/traj_[a-z0-9]+/);
|
|
456
|
+
const id = idMatch?.[0];
|
|
457
|
+
const outputPath = join(tempDir, "export.md");
|
|
458
|
+
|
|
459
|
+
// Act
|
|
460
|
+
const result = await runCommand([
|
|
461
|
+
"export",
|
|
462
|
+
id!,
|
|
463
|
+
"--format",
|
|
464
|
+
"md",
|
|
465
|
+
"--output",
|
|
466
|
+
outputPath,
|
|
467
|
+
]);
|
|
468
|
+
|
|
469
|
+
// Assert
|
|
470
|
+
expect(result.success).toBe(true);
|
|
471
|
+
const { readFileSync } = await import("node:fs");
|
|
472
|
+
const content = readFileSync(outputPath, "utf-8");
|
|
473
|
+
expect(content).toContain("# Trajectory:");
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
});
|