@trail-pm/cli 0.1.6 → 0.2.2

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/dist/lib.js ADDED
@@ -0,0 +1,669 @@
1
+ // src/core/paths.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ var MAX_TRAIL_ROOT_WALK = 20;
5
+ function findTrailRoot(startDir) {
6
+ let dir = path.resolve(startDir);
7
+ for (let i = 0; i < MAX_TRAIL_ROOT_WALK; i++) {
8
+ const configPath = path.join(dir, ".trail", "config.json");
9
+ if (fs.existsSync(configPath)) {
10
+ return dir;
11
+ }
12
+ const parent = path.dirname(dir);
13
+ if (parent === dir) {
14
+ break;
15
+ }
16
+ dir = parent;
17
+ }
18
+ return null;
19
+ }
20
+ function trailPaths(root) {
21
+ const trailDir = path.join(root, ".trail");
22
+ return {
23
+ root,
24
+ trailDir,
25
+ tasksDir: path.join(trailDir, "tasks"),
26
+ configPath: path.join(trailDir, "config.json"),
27
+ snapshotPath: path.join(trailDir, "snapshot.json"),
28
+ gitignorePath: path.join(trailDir, ".gitignore")
29
+ };
30
+ }
31
+
32
+ // src/core/task-store.ts
33
+ import fs2 from "fs";
34
+ import path2 from "path";
35
+
36
+ // src/schemas/task.ts
37
+ import { z } from "zod";
38
+ var TaskStatusSchema = z.enum([
39
+ "draft",
40
+ "todo",
41
+ "in_progress",
42
+ "in_review",
43
+ "done",
44
+ "cancelled"
45
+ ]);
46
+ var isoDateStringSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected ISO date string (YYYY-MM-DD)");
47
+ var TaskGitHubSchema = z.object({
48
+ issue_number: z.number().int(),
49
+ synced_at: z.string().datetime({ offset: true }),
50
+ url: z.string().url()
51
+ }).nullable();
52
+ var TaskAiSchema = z.object({
53
+ summary: z.string().optional(),
54
+ acceptance_criteria: z.array(z.string()).optional(),
55
+ implementation_context: z.array(z.string()).optional(),
56
+ test_strategy: z.array(z.string()).optional(),
57
+ constraints: z.array(z.string()).optional()
58
+ }).strict().optional();
59
+ var TaskSchema = z.object({
60
+ id: z.string(),
61
+ title: z.string(),
62
+ description: z.string().optional(),
63
+ status: TaskStatusSchema,
64
+ priority: z.enum(["p0", "p1", "p2", "p3"]).optional(),
65
+ type: z.enum(["feature", "bug", "chore", "epic"]),
66
+ assignee: z.string().optional(),
67
+ milestone: z.string().optional(),
68
+ branch: z.string().optional(),
69
+ labels: z.array(z.string()).default([]),
70
+ parent: z.string().nullable().optional(),
71
+ depends_on: z.array(z.string()).default([]),
72
+ blocks: z.array(z.string()).default([]),
73
+ due_date: isoDateStringSchema.optional(),
74
+ start_date: isoDateStringSchema.optional(),
75
+ estimate: z.enum(["xs", "sm", "md", "lg", "xl"]).optional(),
76
+ github: TaskGitHubSchema.optional(),
77
+ refs: z.array(
78
+ z.object({
79
+ type: z.string(),
80
+ path: z.string()
81
+ }).strict()
82
+ ).default([]),
83
+ ai: TaskAiSchema,
84
+ created_at: z.string().datetime({ offset: true }),
85
+ updated_at: z.string().datetime({ offset: true })
86
+ }).strict();
87
+
88
+ // src/core/task-store.ts
89
+ function toValidationError(zodError, context) {
90
+ const issues = zodError.issues.map(
91
+ (i) => `${i.path.length ? i.path.join(".") : "(root)"}: ${i.message}`
92
+ );
93
+ return {
94
+ code: "VALIDATION_FAILED",
95
+ message: context ? `Task validation failed (${context})` : "Task validation failed",
96
+ details: zodError.message,
97
+ issues
98
+ };
99
+ }
100
+ function throwValidationFailed(err) {
101
+ const e = new Error(`[VALIDATION_FAILED] ${err.message}`);
102
+ e.name = "TrailError";
103
+ e.trailError = err;
104
+ throw e;
105
+ }
106
+ function listTaskFiles(tasksDir) {
107
+ const names = fs2.readdirSync(tasksDir);
108
+ return names.filter(
109
+ (n) => n.endsWith(".json") && n !== "snapshot.json"
110
+ ).sort((a, b) => a.localeCompare(b));
111
+ }
112
+ function readTaskFile(filePath) {
113
+ let raw;
114
+ try {
115
+ raw = JSON.parse(fs2.readFileSync(filePath, "utf8"));
116
+ } catch (e) {
117
+ if (e instanceof SyntaxError) {
118
+ throwValidationFailed({
119
+ code: "VALIDATION_FAILED",
120
+ message: "Invalid JSON in task file",
121
+ details: filePath
122
+ });
123
+ }
124
+ throw e;
125
+ }
126
+ const parsed = TaskSchema.safeParse(raw);
127
+ if (!parsed.success) {
128
+ throwValidationFailed(toValidationError(parsed.error, filePath));
129
+ }
130
+ return parsed.data;
131
+ }
132
+ function writeTaskFile(filePath, task) {
133
+ const parsed = TaskSchema.safeParse(task);
134
+ if (!parsed.success) {
135
+ throwValidationFailed(toValidationError(parsed.error, filePath));
136
+ }
137
+ const dir = path2.dirname(filePath);
138
+ fs2.mkdirSync(dir, { recursive: true });
139
+ const body = `${JSON.stringify(parsed.data, null, 2)}
140
+ `;
141
+ fs2.writeFileSync(filePath, body, "utf8");
142
+ }
143
+ function loadAllTasks(tasksDir) {
144
+ if (!fs2.existsSync(tasksDir)) {
145
+ return [];
146
+ }
147
+ const files = listTaskFiles(tasksDir);
148
+ return files.map((name) => readTaskFile(path2.join(tasksDir, name)));
149
+ }
150
+ function findTaskFileById(tasksDir, id) {
151
+ if (!fs2.existsSync(tasksDir)) {
152
+ return null;
153
+ }
154
+ const direct = path2.join(tasksDir, `${id}.json`);
155
+ if (fs2.existsSync(direct)) {
156
+ const task = readTaskFile(direct);
157
+ if (task.id === id) {
158
+ return { filePath: direct, task };
159
+ }
160
+ }
161
+ for (const name of listTaskFiles(tasksDir)) {
162
+ const filePath = path2.join(tasksDir, name);
163
+ const task = readTaskFile(filePath);
164
+ if (task.id === id) {
165
+ return { filePath, task };
166
+ }
167
+ }
168
+ return null;
169
+ }
170
+
171
+ // src/core/compile-snapshot.ts
172
+ import fs3 from "fs";
173
+ import path3 from "path";
174
+
175
+ // src/schemas/snapshot.ts
176
+ import { z as z2 } from "zod";
177
+ var SnapshotSchema = z2.object({
178
+ generated_at: z2.string().datetime({ offset: true }),
179
+ tasks: z2.array(TaskSchema),
180
+ warnings: z2.array(
181
+ z2.object({
182
+ code: z2.string(),
183
+ message: z2.string(),
184
+ taskId: z2.string().optional()
185
+ }).strict()
186
+ )
187
+ }).strict();
188
+
189
+ // src/core/compile-snapshot.ts
190
+ var UNKNOWN_DEPENDENCY = "UNKNOWN_DEPENDENCY";
191
+ var DEPENDENCY_CYCLE = "DEPENDENCY_CYCLE";
192
+ function detectCycles(taskIds, deps) {
193
+ const cycles = [];
194
+ const visited = /* @__PURE__ */ new Set();
195
+ const visiting = /* @__PURE__ */ new Set();
196
+ const stack = [];
197
+ function recordCycle(fromIndex) {
198
+ cycles.push(stack.slice(fromIndex));
199
+ }
200
+ function dfs(u) {
201
+ visiting.add(u);
202
+ stack.push(u);
203
+ for (const v of deps.get(u) ?? []) {
204
+ if (!taskIds.has(v)) {
205
+ continue;
206
+ }
207
+ if (visiting.has(v)) {
208
+ const i = stack.indexOf(v);
209
+ if (i !== -1) {
210
+ recordCycle(i);
211
+ }
212
+ continue;
213
+ }
214
+ if (!visited.has(v)) {
215
+ dfs(v);
216
+ }
217
+ }
218
+ stack.pop();
219
+ visiting.delete(u);
220
+ visited.add(u);
221
+ }
222
+ for (const id of taskIds) {
223
+ if (!visited.has(id)) {
224
+ dfs(id);
225
+ }
226
+ }
227
+ return cycles;
228
+ }
229
+ function formatCycle(nodes) {
230
+ return nodes.join(" \u2192 ");
231
+ }
232
+ function compileSnapshot(tasks, now) {
233
+ const generatedAt = (now ?? /* @__PURE__ */ new Date()).toISOString();
234
+ const taskList = [...tasks];
235
+ const taskIds = new Set(taskList.map((t) => t.id));
236
+ const deps = /* @__PURE__ */ new Map();
237
+ for (const t of taskList) {
238
+ deps.set(t.id, t.depends_on ?? []);
239
+ }
240
+ const warnings = [];
241
+ for (const t of taskList) {
242
+ for (const depId of t.depends_on ?? []) {
243
+ if (!taskIds.has(depId)) {
244
+ warnings.push({
245
+ code: UNKNOWN_DEPENDENCY,
246
+ message: `Task "${t.id}" depends on unknown task id "${depId}"`,
247
+ taskId: t.id
248
+ });
249
+ }
250
+ }
251
+ }
252
+ for (const cycle of detectCycles(taskIds, deps)) {
253
+ warnings.push({
254
+ code: DEPENDENCY_CYCLE,
255
+ message: `Dependency cycle: ${formatCycle(cycle)}`,
256
+ taskId: cycle[0]
257
+ });
258
+ }
259
+ return {
260
+ generated_at: generatedAt,
261
+ tasks: taskList,
262
+ warnings
263
+ };
264
+ }
265
+ function writeSnapshot(snapshotPath, snapshot) {
266
+ const parsed = SnapshotSchema.parse(snapshot);
267
+ const dir = path3.dirname(snapshotPath);
268
+ fs3.mkdirSync(dir, { recursive: true });
269
+ const body = `${JSON.stringify(parsed, null, 2)}
270
+ `;
271
+ fs3.writeFileSync(snapshotPath, body, "utf8");
272
+ }
273
+
274
+ // src/core/rebuild-snapshot.ts
275
+ function rebuildSnapshot(paths, now = /* @__PURE__ */ new Date()) {
276
+ const tasks = loadAllTasks(paths.tasksDir);
277
+ const snapshot = compileSnapshot(tasks, now);
278
+ writeSnapshot(paths.snapshotPath, snapshot);
279
+ }
280
+
281
+ // src/core/draft-id.ts
282
+ import { randomBytes } from "crypto";
283
+ function generateDraftId() {
284
+ return `draft-${randomBytes(4).toString("hex")}`;
285
+ }
286
+
287
+ // src/schemas/config.ts
288
+ import { z as z3 } from "zod";
289
+ var TrailConfigSchema = z3.object({
290
+ github: z3.object({
291
+ owner: z3.string(),
292
+ repo: z3.string()
293
+ }).strict(),
294
+ sync: z3.object({
295
+ preset: z3.enum(["collaborative", "solo", "offline"]),
296
+ auto_sync_on_command: z3.boolean(),
297
+ ui_poll_interval_seconds: z3.number(),
298
+ ui_idle_backoff: z3.boolean()
299
+ }).strict(),
300
+ /** Last successful full sync; useful for future `trail status` and similar. */
301
+ last_full_sync_at: z3.string().datetime({ offset: true }).optional()
302
+ }).strict();
303
+
304
+ // src/core/auth.ts
305
+ import * as childProcess from "child_process";
306
+ var AUTH_HINT = "Set GITHUB_TOKEN or install gh and run gh auth login";
307
+ function authRequired(message) {
308
+ return {
309
+ ok: false,
310
+ error: {
311
+ code: "AUTH_REQUIRED",
312
+ message,
313
+ hint: AUTH_HINT
314
+ }
315
+ };
316
+ }
317
+ function resolveGitHubToken(env) {
318
+ const e = env ?? process.env;
319
+ const fromEnv = e.GITHUB_TOKEN;
320
+ if (typeof fromEnv === "string" && fromEnv.trim() !== "") {
321
+ return { ok: true, token: fromEnv.trim() };
322
+ }
323
+ try {
324
+ const out = childProcess.execFileSync("gh", ["auth", "token"], {
325
+ encoding: "utf-8"
326
+ });
327
+ const token = out.trim();
328
+ if (token === "") {
329
+ return authRequired("GitHub CLI returned an empty token");
330
+ }
331
+ return { ok: true, token };
332
+ } catch {
333
+ return authRequired(
334
+ "No GitHub token found; could not read from environment or gh CLI"
335
+ );
336
+ }
337
+ }
338
+
339
+ // src/core/github-client.ts
340
+ var USER_AGENT = "trail-cli/0.0.1";
341
+ function normalizeBaseUrl(baseUrl) {
342
+ return baseUrl.replace(/\/$/, "");
343
+ }
344
+ var GitHubClient = class {
345
+ token;
346
+ baseUrl;
347
+ constructor(token, baseUrl = "https://api.github.com") {
348
+ this.token = token;
349
+ this.baseUrl = normalizeBaseUrl(baseUrl);
350
+ }
351
+ async request(method, path6, body) {
352
+ const url = `${this.baseUrl}${path6.startsWith("/") ? path6 : `/${path6}`}`;
353
+ const headers = new Headers({
354
+ Authorization: `Bearer ${this.token}`,
355
+ Accept: "application/vnd.github+json",
356
+ "X-GitHub-Api-Version": "2022-11-28",
357
+ "User-Agent": USER_AGENT
358
+ });
359
+ if (body !== void 0) {
360
+ headers.set("Content-Type", "application/json");
361
+ }
362
+ return fetch(url, {
363
+ method,
364
+ headers,
365
+ body: body !== void 0 ? JSON.stringify(body) : void 0
366
+ });
367
+ }
368
+ async parseJson(response) {
369
+ const text = await response.text();
370
+ if (!response.ok) {
371
+ const snippet = text.slice(0, 200);
372
+ throw new Error(`GitHub API ${response.status}: ${snippet}`);
373
+ }
374
+ return JSON.parse(text);
375
+ }
376
+ async listIssues(owner, repo, params) {
377
+ const search = new URLSearchParams({
378
+ state: params.state,
379
+ per_page: String(params.per_page),
380
+ page: String(params.page)
381
+ });
382
+ const path6 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues?${search}`;
383
+ const response = await this.request("GET", path6);
384
+ return this.parseJson(response);
385
+ }
386
+ async getIssue(owner, repo, issueNumber) {
387
+ const path6 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`;
388
+ const response = await this.request("GET", path6);
389
+ return this.parseJson(response);
390
+ }
391
+ async updateIssue(owner, repo, issueNumber, patch) {
392
+ const path6 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`;
393
+ const response = await this.request("PATCH", path6, patch);
394
+ return this.parseJson(response);
395
+ }
396
+ async createIssueComment(owner, repo, issueNumber, body) {
397
+ const path6 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments`;
398
+ const response = await this.request("POST", path6, { body });
399
+ return this.parseJson(response);
400
+ }
401
+ /** Create a new issue. Returns the created issue (same shape as list/get). */
402
+ async createIssue(owner, repo, input) {
403
+ const path6 = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues`;
404
+ const response = await this.request("POST", path6, {
405
+ title: input.title,
406
+ body: input.body ?? "",
407
+ labels: input.labels ?? []
408
+ });
409
+ return this.parseJson(response);
410
+ }
411
+ };
412
+
413
+ // src/core/github-mapper.ts
414
+ function statusFromIssue(issue, existing) {
415
+ if (issue.state === "closed") {
416
+ return "done";
417
+ }
418
+ const prev = existing?.status;
419
+ if (prev === "in_progress" || prev === "in_review") {
420
+ return prev;
421
+ }
422
+ return "todo";
423
+ }
424
+ function parseIssueTimestamp(issue, fallback) {
425
+ if (issue.updated_at.trim() === "") {
426
+ return fallback.toISOString();
427
+ }
428
+ return issue.updated_at;
429
+ }
430
+ function issueToTask(issue, existing, now) {
431
+ const updatedAt = parseIssueTimestamp(issue, now);
432
+ const createdAt = existing?.created_at ?? (issue.updated_at.trim() !== "" ? issue.updated_at : now.toISOString());
433
+ const raw = {
434
+ id: String(issue.number),
435
+ title: issue.title,
436
+ description: issue.body ?? "",
437
+ status: statusFromIssue(issue, existing),
438
+ type: existing?.type ?? "feature",
439
+ labels: issue.labels.map((l) => l.name),
440
+ assignee: issue.assignee?.login,
441
+ milestone: issue.milestone?.title,
442
+ depends_on: existing?.depends_on ?? [],
443
+ blocks: existing?.blocks ?? [],
444
+ refs: existing?.refs ?? [],
445
+ ai: existing?.ai,
446
+ branch: existing?.branch,
447
+ estimate: existing?.estimate,
448
+ priority: existing?.priority,
449
+ parent: existing?.parent,
450
+ due_date: existing?.due_date,
451
+ start_date: existing?.start_date,
452
+ github: {
453
+ issue_number: issue.number,
454
+ synced_at: now.toISOString(),
455
+ url: issue.html_url
456
+ },
457
+ created_at: createdAt,
458
+ updated_at: updatedAt
459
+ };
460
+ return TaskSchema.parse(raw);
461
+ }
462
+ function taskToIssueUpdate(task) {
463
+ const labels = [...task.labels];
464
+ if (task.priority) {
465
+ const priorityLabel = `priority:${task.priority}`;
466
+ if (!labels.includes(priorityLabel)) {
467
+ labels.push(priorityLabel);
468
+ }
469
+ }
470
+ const state = task.status === "done" || task.status === "cancelled" ? "closed" : "open";
471
+ return {
472
+ title: task.title,
473
+ body: task.description ?? "",
474
+ state,
475
+ labels
476
+ };
477
+ }
478
+
479
+ // src/core/sync.ts
480
+ import fs4 from "fs";
481
+ import path4 from "path";
482
+
483
+ // src/core/relevant-tasks.ts
484
+ var NON_TERMINAL = [
485
+ "draft",
486
+ "todo",
487
+ "in_progress",
488
+ "in_review"
489
+ ];
490
+ function isNonTerminalStatus(status) {
491
+ return NON_TERMINAL.includes(status);
492
+ }
493
+ function isActiveForRelevance(task) {
494
+ return isNonTerminalStatus(task.status);
495
+ }
496
+ function computeRelevantTaskIds(tasks) {
497
+ const byId = /* @__PURE__ */ new Map();
498
+ for (const t of tasks) {
499
+ byId.set(t.id, t);
500
+ }
501
+ const relevant = /* @__PURE__ */ new Set();
502
+ const queue = [];
503
+ for (const t of tasks) {
504
+ if (isActiveForRelevance(t)) {
505
+ if (!relevant.has(t.id)) {
506
+ relevant.add(t.id);
507
+ queue.push(t.id);
508
+ }
509
+ }
510
+ }
511
+ function enqueue(id) {
512
+ if (relevant.has(id)) return;
513
+ relevant.add(id);
514
+ queue.push(id);
515
+ }
516
+ while (queue.length > 0) {
517
+ const id = queue.pop();
518
+ const task = byId.get(id);
519
+ if (!task) continue;
520
+ for (const d of task.depends_on) {
521
+ if (byId.has(d)) enqueue(d);
522
+ }
523
+ for (const b of task.blocks) {
524
+ if (byId.has(b)) enqueue(b);
525
+ }
526
+ if (task.parent != null && task.parent !== "" && byId.has(task.parent)) {
527
+ enqueue(task.parent);
528
+ }
529
+ }
530
+ return relevant;
531
+ }
532
+
533
+ // src/core/sync.ts
534
+ var ISSUES_PER_PAGE = 100;
535
+ async function pullSync(options) {
536
+ const { client, owner, repo, tasksDir } = options;
537
+ const now = options.now ?? /* @__PURE__ */ new Date();
538
+ const onProgress = options.onProgress;
539
+ let page = 1;
540
+ let issuesSoFar = 0;
541
+ for (; ; ) {
542
+ const issues = await client.listIssues(owner, repo, {
543
+ state: "all",
544
+ per_page: ISSUES_PER_PAGE,
545
+ page
546
+ });
547
+ if (issues.length === 0) {
548
+ break;
549
+ }
550
+ for (const issue of issues) {
551
+ const filePath = path4.join(tasksDir, `${issue.number}.json`);
552
+ let existing = null;
553
+ if (fs4.existsSync(filePath)) {
554
+ existing = readTaskFile(filePath);
555
+ }
556
+ const task = issueToTask(issue, existing, now);
557
+ writeTaskFile(filePath, task);
558
+ }
559
+ issuesSoFar += issues.length;
560
+ onProgress?.({
561
+ phase: "pull",
562
+ page,
563
+ issuesInPage: issues.length,
564
+ issuesSoFar
565
+ });
566
+ if (issues.length < ISSUES_PER_PAGE) {
567
+ break;
568
+ }
569
+ page += 1;
570
+ }
571
+ }
572
+ function isLinkedTask(task) {
573
+ return task.github != null && typeof task.github === "object";
574
+ }
575
+ async function pushSync(options) {
576
+ const { client, owner, repo, tasks } = options;
577
+ const only = options.onlyTaskIds;
578
+ const onProgress = options.onProgress;
579
+ const toPush = tasks.filter((task) => {
580
+ if (task.status === "draft") {
581
+ return false;
582
+ }
583
+ if (!isLinkedTask(task)) {
584
+ return false;
585
+ }
586
+ if (only !== void 0 && !only.has(task.id)) {
587
+ return false;
588
+ }
589
+ return true;
590
+ });
591
+ let index = 0;
592
+ for (const task of toPush) {
593
+ if (!isLinkedTask(task)) {
594
+ continue;
595
+ }
596
+ index += 1;
597
+ onProgress?.({
598
+ phase: "push",
599
+ index,
600
+ total: toPush.length,
601
+ taskId: task.id
602
+ });
603
+ const patch = taskToIssueUpdate(task);
604
+ await client.updateIssue(owner, repo, task.github.issue_number, patch);
605
+ }
606
+ }
607
+ async function fullSync(options) {
608
+ const { client, owner, repo, tasksDir, snapshotPath, now } = options;
609
+ await pullSync({ client, owner, repo, tasksDir, now, onProgress: options.onProgress });
610
+ const tasks = loadAllTasks(tasksDir);
611
+ const relevant = computeRelevantTaskIds(tasks);
612
+ await pushSync({
613
+ client,
614
+ owner,
615
+ repo,
616
+ tasks,
617
+ onlyTaskIds: relevant,
618
+ onProgress: options.onProgress
619
+ });
620
+ const snapshot = compileSnapshot(tasks, now);
621
+ writeSnapshot(snapshotPath, snapshot);
622
+ }
623
+
624
+ // src/core/config-update.ts
625
+ import fs5 from "fs";
626
+ function writeLastFullSyncAt(paths, iso) {
627
+ const raw = fs5.readFileSync(paths.configPath, "utf-8");
628
+ const config = TrailConfigSchema.parse(JSON.parse(raw));
629
+ const next = { ...config, last_full_sync_at: iso };
630
+ fs5.writeFileSync(paths.configPath, `${JSON.stringify(next, null, 2)}
631
+ `, "utf-8");
632
+ }
633
+
634
+ // src/core/link-draft-issue.ts
635
+ import fs6 from "fs";
636
+ import path5 from "path";
637
+ async function linkDraftToNewGitHubIssue(options) {
638
+ const { client, owner, repo, draft, draftFilePath, tasksDir, now } = options;
639
+ const issue = await client.createIssue(owner, repo, {
640
+ title: draft.title,
641
+ body: draft.description ?? "",
642
+ labels: draft.labels
643
+ });
644
+ const promoted = issueToTask(issue, draft, now);
645
+ fs6.unlinkSync(draftFilePath);
646
+ const newPath = path5.join(tasksDir, `${issue.number}.json`);
647
+ writeTaskFile(newPath, promoted);
648
+ return promoted;
649
+ }
650
+ export {
651
+ GitHubClient,
652
+ TaskSchema,
653
+ TrailConfigSchema,
654
+ findTaskFileById,
655
+ findTrailRoot,
656
+ fullSync,
657
+ generateDraftId,
658
+ issueToTask,
659
+ linkDraftToNewGitHubIssue,
660
+ loadAllTasks,
661
+ pullSync,
662
+ rebuildSnapshot,
663
+ resolveGitHubToken,
664
+ taskToIssueUpdate,
665
+ trailPaths,
666
+ writeLastFullSyncAt,
667
+ writeTaskFile
668
+ };
669
+ //# sourceMappingURL=lib.js.map