@tashks/core 0.1.4 → 0.2.3

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.
@@ -6,8 +6,8 @@ import * as Effect from "effect/Effect";
6
6
  import * as Layer from "effect/Layer";
7
7
  import * as Schema from "effect/Schema";
8
8
  import YAML from "yaml";
9
- import { Task as TaskSchema, TaskCreateInput as TaskCreateInputSchema, TaskPatch as TaskPatchSchema, WorkLogCreateInput as WorkLogCreateInputSchema, WorkLogEntry as WorkLogEntrySchema, WorkLogPatch as WorkLogPatchSchema, } from "./schema.js";
10
- import { byUpdatedDescThenTitle, isDueBefore, isStalerThan, isUnblocked, } from "./query.js";
9
+ import { Task as TaskSchema, TaskCreateInput as TaskCreateInputSchema, TaskPatch as TaskPatchSchema, WorkLogCreateInput as WorkLogCreateInputSchema, WorkLogEntry as WorkLogEntrySchema, WorkLogPatch as WorkLogPatchSchema, Project as ProjectSchema, ProjectCreateInput as ProjectCreateInputSchema, ProjectPatch as ProjectPatchSchema, } from "./schema.js";
10
+ import { byUpdatedDescThenTitle, isDueBefore, isStalerThan, isUnblocked, listContexts as listContextsFromTasks, } from "./query.js";
11
11
  import { generateTaskId } from "./id.js";
12
12
  import { runCreateHooks, runModifyHooks, runNonMutatingHooks, } from "./hooks.js";
13
13
  import { buildCompletionRecurrenceTask, buildNextClockRecurrenceTask, isClockRecurrenceDue, } from "./recurrence.js";
@@ -22,11 +22,17 @@ const decodeWorkLogCreateInput = Schema.decodeUnknownSync(WorkLogCreateInputSche
22
22
  const decodeWorkLogEntry = Schema.decodeUnknownSync(WorkLogEntrySchema);
23
23
  const decodeWorkLogEntryEither = Schema.decodeUnknownEither(WorkLogEntrySchema);
24
24
  const decodeWorkLogPatch = Schema.decodeUnknownSync(WorkLogPatchSchema);
25
+ const decodeProject = Schema.decodeUnknownSync(ProjectSchema);
26
+ const decodeProjectEither = Schema.decodeUnknownEither(ProjectSchema);
27
+ const decodeProjectCreateInput = Schema.decodeUnknownSync(ProjectCreateInputSchema);
28
+ const decodeProjectPatch = Schema.decodeUnknownSync(ProjectPatchSchema);
25
29
  const toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
26
30
  const taskFilePath = (dataDir, id) => join(dataDir, "tasks", `${id}.yaml`);
27
31
  const legacyTaskFilePath = (dataDir, id) => join(dataDir, "tasks", `${id}.yml`);
28
32
  const workLogFilePath = (dataDir, id) => join(dataDir, "work-log", `${id}.yaml`);
29
33
  const legacyWorkLogFilePath = (dataDir, id) => join(dataDir, "work-log", `${id}.yml`);
34
+ const projectFilePath = (dataDir, id) => join(dataDir, "projects", `${id}.yaml`);
35
+ const legacyProjectFilePath = (dataDir, id) => join(dataDir, "projects", `${id}.yml`);
30
36
  const dailyHighlightFilePath = (dataDir) => join(dataDir, "daily-highlight.yaml");
31
37
  const ensureTasksDir = (dataDir) => Effect.tryPromise({
32
38
  try: () => mkdir(join(dataDir, "tasks"), { recursive: true }),
@@ -36,6 +42,10 @@ const ensureWorkLogDir = (dataDir) => Effect.tryPromise({
36
42
  try: () => mkdir(join(dataDir, "work-log"), { recursive: true }),
37
43
  catch: (error) => `TaskRepository failed to create work-log directory: ${toErrorMessage(error)}`,
38
44
  });
45
+ const ensureProjectsDir = (dataDir) => Effect.tryPromise({
46
+ try: () => mkdir(join(dataDir, "projects"), { recursive: true }),
47
+ catch: (error) => `TaskRepository failed to create projects directory: ${toErrorMessage(error)}`,
48
+ });
39
49
  const writeDailyHighlightToDisk = (dataDir, id) => Effect.tryPromise({
40
50
  try: async () => {
41
51
  await mkdir(dataDir, { recursive: true });
@@ -214,6 +224,75 @@ const generateNextClockRecurrence = (dataDir, existing, generatedAt) => Effect.g
214
224
  : null;
215
225
  return { nextTask: result.nextTask, replacedId };
216
226
  });
227
+ const readProjectByIdFromDisk = (dataDir, id) => Effect.tryPromise({
228
+ try: async () => {
229
+ const candidatePaths = [
230
+ projectFilePath(dataDir, id),
231
+ legacyProjectFilePath(dataDir, id),
232
+ ];
233
+ for (const path of candidatePaths) {
234
+ const source = await readFile(path, "utf8").catch((error) => {
235
+ if (error !== null &&
236
+ typeof error === "object" &&
237
+ "code" in error &&
238
+ error.code === "ENOENT") {
239
+ return null;
240
+ }
241
+ throw error;
242
+ });
243
+ if (source === null) {
244
+ continue;
245
+ }
246
+ const parsed = YAML.parse(source);
247
+ const project = parseProjectRecord(parsed);
248
+ if (project === null) {
249
+ throw new Error(`Invalid project record in ${path}`);
250
+ }
251
+ return { path, project };
252
+ }
253
+ throw new Error(`Project not found: ${id}`);
254
+ },
255
+ catch: (error) => `TaskRepository failed to read project ${id}: ${toErrorMessage(error)}`,
256
+ });
257
+ const readProjectsFromDisk = (dataDir) => Effect.tryPromise({
258
+ try: async () => {
259
+ const projectsDir = join(dataDir, "projects");
260
+ const entries = await readdir(projectsDir, { withFileTypes: true }).catch((error) => {
261
+ if (error !== null &&
262
+ typeof error === "object" &&
263
+ "code" in error &&
264
+ error.code === "ENOENT") {
265
+ return [];
266
+ }
267
+ throw error;
268
+ });
269
+ const projectFiles = entries
270
+ .filter((entry) => entry.isFile() &&
271
+ (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")))
272
+ .map((entry) => entry.name);
273
+ const projects = [];
274
+ for (const fileName of projectFiles) {
275
+ const filePath = join(projectsDir, fileName);
276
+ const source = await readFile(filePath, "utf8");
277
+ const parsed = YAML.parse(source);
278
+ const project = parseProjectRecord(parsed);
279
+ if (project === null) {
280
+ throw new Error(`Invalid project record in ${filePath}`);
281
+ }
282
+ projects.push(project);
283
+ }
284
+ return projects;
285
+ },
286
+ catch: (error) => `TaskRepository.listProjects failed to read project files: ${toErrorMessage(error)}`,
287
+ });
288
+ const writeProjectToDisk = (path, project) => Effect.tryPromise({
289
+ try: () => writeFile(path, YAML.stringify(project), "utf8"),
290
+ catch: (error) => `TaskRepository failed to write project ${project.id}: ${toErrorMessage(error)}`,
291
+ });
292
+ const deleteProjectFromDisk = (path, id) => Effect.tryPromise({
293
+ try: () => rm(path),
294
+ catch: (error) => `TaskRepository failed to delete project ${id}: ${toErrorMessage(error)}`,
295
+ });
217
296
  const byStartedAtDescThenId = (a, b) => {
218
297
  const byStartedAtDesc = b.started_at.localeCompare(a.started_at);
219
298
  if (byStartedAtDesc !== 0) {
@@ -223,6 +302,9 @@ const byStartedAtDescThenId = (a, b) => {
223
302
  };
224
303
  export const applyListTaskFilters = (tasks, filters = {}) => {
225
304
  const dueBeforePredicate = filters.due_before !== undefined ? isDueBefore(filters.due_before) : null;
305
+ const stalePredicate = filters.stale_days !== undefined
306
+ ? isStalerThan(filters.stale_days, todayIso())
307
+ : null;
226
308
  return tasks
227
309
  .filter((task) => {
228
310
  if (filters.status !== undefined && task.status !== filters.status) {
@@ -231,7 +313,7 @@ export const applyListTaskFilters = (tasks, filters = {}) => {
231
313
  if (filters.area !== undefined && task.area !== filters.area) {
232
314
  return false;
233
315
  }
234
- if (filters.project !== undefined && task.project !== filters.project) {
316
+ if (filters.project !== undefined && !task.projects.includes(filters.project)) {
235
317
  return false;
236
318
  }
237
319
  if (filters.tags !== undefined &&
@@ -254,10 +336,48 @@ export const applyListTaskFilters = (tasks, filters = {}) => {
254
336
  if (filters.unblocked_only === true && !isUnblocked(task, tasks)) {
255
337
  return false;
256
338
  }
339
+ if (filters.duration_min !== undefined &&
340
+ (task.estimated_minutes === null || task.estimated_minutes < filters.duration_min)) {
341
+ return false;
342
+ }
343
+ if (filters.duration_max !== undefined &&
344
+ (task.estimated_minutes === null || task.estimated_minutes > filters.duration_max)) {
345
+ return false;
346
+ }
347
+ if (filters.context !== undefined &&
348
+ task.context !== filters.context) {
349
+ return false;
350
+ }
351
+ if (filters.include_templates !== true &&
352
+ task.is_template === true) {
353
+ return false;
354
+ }
355
+ if (stalePredicate !== null && !stalePredicate(task)) {
356
+ return false;
357
+ }
257
358
  return true;
258
359
  })
259
360
  .sort(byUpdatedDescThenTitle);
260
361
  };
362
+ export const applyListProjectFilters = (projects, filters = {}) => {
363
+ return projects
364
+ .filter((project) => {
365
+ if (filters.status !== undefined && project.status !== filters.status) {
366
+ return false;
367
+ }
368
+ if (filters.area !== undefined && project.area !== filters.area) {
369
+ return false;
370
+ }
371
+ return true;
372
+ })
373
+ .sort((a, b) => {
374
+ const byUpdatedDesc = b.updated.localeCompare(a.updated);
375
+ if (byUpdatedDesc !== 0) {
376
+ return byUpdatedDesc;
377
+ }
378
+ return a.title.localeCompare(b.title);
379
+ });
380
+ };
261
381
  export class TaskRepository extends Context.Tag("TaskRepository")() {
262
382
  }
263
383
  const defaultDataDir = () => {
@@ -266,6 +386,50 @@ const defaultDataDir = () => {
266
386
  ? `${home}/.local/share/tashks`
267
387
  : ".local/share/tashks";
268
388
  };
389
+ export const buildInstanceFromTemplate = (template, overrides) => {
390
+ const now = new Date().toISOString().slice(0, 10);
391
+ return decodeTask({
392
+ id: generateTaskId(overrides?.title ?? template.title),
393
+ title: overrides?.title ?? template.title,
394
+ status: overrides?.status ?? "backlog",
395
+ area: template.area,
396
+ projects: overrides?.projects
397
+ ? [...overrides.projects]
398
+ : [...template.projects],
399
+ tags: [...template.tags],
400
+ created: now,
401
+ updated: now,
402
+ urgency: template.urgency,
403
+ energy: template.energy,
404
+ due: overrides?.due ?? null,
405
+ context: template.context,
406
+ subtasks: template.subtasks.map((s) => ({ text: s.text, done: false })),
407
+ blocked_by: [],
408
+ estimated_minutes: template.estimated_minutes,
409
+ actual_minutes: null,
410
+ completed_at: null,
411
+ last_surfaced: null,
412
+ defer_until: overrides?.defer_until ?? null,
413
+ nudge_count: 0,
414
+ recurrence: null,
415
+ recurrence_trigger: "clock",
416
+ recurrence_strategy: "replace",
417
+ recurrence_last_generated: null,
418
+ related: [...template.related],
419
+ is_template: false,
420
+ from_template: template.id,
421
+ });
422
+ };
423
+ const validateNoTemplateRefs = (relatedIds, allTasks) => {
424
+ const templateIds = allTasks
425
+ .filter((t) => t.is_template)
426
+ .map((t) => t.id);
427
+ const badRefs = relatedIds.filter((id) => templateIds.includes(id));
428
+ if (badRefs.length > 0) {
429
+ return Effect.fail(`Cannot reference template(s) in related: ${badRefs.join(", ")}`);
430
+ }
431
+ return Effect.void;
432
+ };
269
433
  const makeTaskRepositoryLive = (options = {}) => {
270
434
  const dataDir = options.dataDir ?? defaultDataDir();
271
435
  const hookRuntimeOptions = {
@@ -279,6 +443,10 @@ const makeTaskRepositoryLive = (options = {}) => {
279
443
  createTask: (input) => Effect.gen(function* () {
280
444
  yield* ensureTasksDir(dataDir);
281
445
  const created = createTaskFromInput(input);
446
+ if (created.related.length > 0) {
447
+ const allTasks = yield* readTasksFromDisk(dataDir);
448
+ yield* validateNoTemplateRefs(created.related, allTasks);
449
+ }
282
450
  const taskFromHooks = yield* runCreateHooks(created, hookRuntimeOptions);
283
451
  yield* writeTaskToDisk(taskFilePath(dataDir, taskFromHooks.id), taskFromHooks);
284
452
  return taskFromHooks;
@@ -286,6 +454,11 @@ const makeTaskRepositoryLive = (options = {}) => {
286
454
  updateTask: (id, patch) => Effect.gen(function* () {
287
455
  const existing = yield* readTaskByIdFromDisk(dataDir, id);
288
456
  const updated = applyTaskPatch(existing.task, patch);
457
+ if (patch.related !== undefined &&
458
+ updated.related.length > 0) {
459
+ const allTasks = yield* readTasksFromDisk(dataDir);
460
+ yield* validateNoTemplateRefs(updated.related, allTasks);
461
+ }
289
462
  const taskFromHooks = yield* runModifyHooks(existing.task, updated, hookRuntimeOptions);
290
463
  yield* writeTaskToDisk(existing.path, taskFromHooks);
291
464
  return taskFromHooks;
@@ -319,7 +492,8 @@ const makeTaskRepositoryLive = (options = {}) => {
319
492
  const recurringTasks = tasks.filter((task) => task.recurrence !== null &&
320
493
  task.recurrence_trigger === "clock" &&
321
494
  task.status !== "done" &&
322
- task.status !== "dropped");
495
+ task.status !== "dropped" &&
496
+ !task.is_template);
323
497
  const created = [];
324
498
  const replaced = [];
325
499
  for (const task of recurringTasks) {
@@ -347,6 +521,31 @@ const makeTaskRepositoryLive = (options = {}) => {
347
521
  yield* writeDailyHighlightToDisk(dataDir, id);
348
522
  return existing.task;
349
523
  }),
524
+ getDailyHighlight: () => Effect.gen(function* () {
525
+ const source = yield* Effect.tryPromise({
526
+ try: () => readFile(dailyHighlightFilePath(dataDir), "utf8").catch((error) => {
527
+ if (error !== null &&
528
+ typeof error === "object" &&
529
+ "code" in error &&
530
+ error.code === "ENOENT") {
531
+ return null;
532
+ }
533
+ throw error;
534
+ }),
535
+ catch: (error) => `TaskRepository failed to read daily highlight: ${toErrorMessage(error)}`,
536
+ });
537
+ if (source === null || source.trim().length === 0) {
538
+ return null;
539
+ }
540
+ const parsed = YAML.parse(source);
541
+ if (parsed === null ||
542
+ typeof parsed !== "object" ||
543
+ typeof parsed.id !== "string") {
544
+ return null;
545
+ }
546
+ const result = yield* Effect.catchAll(Effect.map(readTaskByIdFromDisk(dataDir, parsed.id), (r) => r.task), () => Effect.succeed(null));
547
+ return result;
548
+ }),
350
549
  listStale: (days) => Effect.map(readTasksFromDisk(dataDir), (tasks) => {
351
550
  const stalePredicate = isStalerThan(days, todayIso());
352
551
  return tasks
@@ -392,12 +591,77 @@ const makeTaskRepositoryLive = (options = {}) => {
392
591
  yield* writeWorkLogEntryToDisk(workLogFilePath(dataDir, entry.id), entry);
393
592
  return entry;
394
593
  }),
594
+ listProjects: (filters) => Effect.map(readProjectsFromDisk(dataDir), (projects) => applyListProjectFilters(projects, filters)),
595
+ getProject: (id) => Effect.map(readProjectByIdFromDisk(dataDir, id), (result) => result.project),
596
+ createProject: (input) => Effect.gen(function* () {
597
+ yield* ensureProjectsDir(dataDir);
598
+ const created = createProjectFromInput(input);
599
+ yield* writeProjectToDisk(projectFilePath(dataDir, created.id), created);
600
+ return created;
601
+ }),
602
+ updateProject: (id, patch) => Effect.gen(function* () {
603
+ const existing = yield* readProjectByIdFromDisk(dataDir, id);
604
+ const updated = applyProjectPatch(existing.project, patch);
605
+ yield* writeProjectToDisk(existing.path, updated);
606
+ return updated;
607
+ }),
608
+ deleteProject: (id) => Effect.gen(function* () {
609
+ const existing = yield* readProjectByIdFromDisk(dataDir, id);
610
+ yield* deleteProjectFromDisk(existing.path, id);
611
+ return { deleted: true };
612
+ }),
613
+ importProject: (project) => Effect.gen(function* () {
614
+ yield* ensureProjectsDir(dataDir);
615
+ yield* writeProjectToDisk(projectFilePath(dataDir, project.id), project);
616
+ return project;
617
+ }),
618
+ listContexts: () => Effect.map(readTasksFromDisk(dataDir), (tasks) => listContextsFromTasks(tasks)),
619
+ getRelated: (id) => Effect.gen(function* () {
620
+ const existing = yield* readTaskByIdFromDisk(dataDir, id);
621
+ const allTasks = yield* readTasksFromDisk(dataDir);
622
+ const targetRelated = new Set(existing.task.related);
623
+ return allTasks.filter((t) => t.id !== id &&
624
+ (targetRelated.has(t.id) || t.related.includes(id)));
625
+ }),
626
+ instantiateTemplate: (templateId, overrides) => Effect.gen(function* () {
627
+ const existing = yield* readTaskByIdFromDisk(dataDir, templateId);
628
+ const template = existing.task;
629
+ if (!template.is_template) {
630
+ return yield* Effect.fail(`Task ${templateId} is not a template`);
631
+ }
632
+ const instance = buildInstanceFromTemplate(template, overrides);
633
+ const taskFromHooks = yield* runCreateHooks(instance, hookRuntimeOptions);
634
+ yield* ensureTasksDir(dataDir);
635
+ yield* writeTaskToDisk(taskFilePath(dataDir, taskFromHooks.id), taskFromHooks);
636
+ return taskFromHooks;
637
+ }),
395
638
  };
396
639
  };
397
640
  export const TaskRepositoryLive = (options = {}) => Layer.succeed(TaskRepository, makeTaskRepositoryLive(options));
398
641
  export const todayIso = () => new Date().toISOString().slice(0, 10);
642
+ const migrateTaskRecord = (record) => {
643
+ if (record === null || typeof record !== "object") {
644
+ return record;
645
+ }
646
+ let rec = record;
647
+ if ("project" in rec && !("projects" in rec)) {
648
+ const { project, ...rest } = rec;
649
+ rec = {
650
+ ...rest,
651
+ projects: typeof project === "string" ? [project] : [],
652
+ };
653
+ }
654
+ if (!("related" in rec))
655
+ rec.related = [];
656
+ if (!("is_template" in rec))
657
+ rec.is_template = false;
658
+ if (!("from_template" in rec))
659
+ rec.from_template = null;
660
+ return rec;
661
+ };
399
662
  export const parseTaskRecord = (record) => {
400
- const result = decodeTaskEither(record);
663
+ const migrated = migrateTaskRecord(record);
664
+ const result = decodeTaskEither(migrated);
401
665
  return Either.isRight(result) ? result.right : null;
402
666
  };
403
667
  export const parseWorkLogRecord = (record) => {
@@ -414,9 +678,10 @@ export const createTaskFromInput = (input) => {
414
678
  export const applyTaskPatch = (task, patch) => {
415
679
  const normalizedTask = decodeTask(task);
416
680
  const normalizedPatch = decodeTaskPatch(patch);
681
+ const { from_template: _stripped, ...safePatch } = normalizedPatch;
417
682
  return decodeTask({
418
683
  ...normalizedTask,
419
- ...normalizedPatch,
684
+ ...safePatch,
420
685
  updated: todayIso(),
421
686
  });
422
687
  };
@@ -428,3 +693,44 @@ export const applyWorkLogPatch = (entry, patch) => {
428
693
  ...normalizedPatch,
429
694
  });
430
695
  };
696
+ export const parseProjectRecord = (record) => {
697
+ const result = decodeProjectEither(record);
698
+ return Either.isRight(result) ? result.right : null;
699
+ };
700
+ export const createProjectFromInput = (input) => {
701
+ const normalizedInput = decodeProjectCreateInput(input);
702
+ return decodeProject({
703
+ ...normalizedInput,
704
+ id: generateTaskId(normalizedInput.title),
705
+ });
706
+ };
707
+ export const promoteSubtask = (repository, taskId, subtaskIndex) => Effect.gen(function* () {
708
+ const parent = yield* repository.getTask(taskId);
709
+ if (parent.is_template) {
710
+ return yield* Effect.fail("Cannot promote subtasks on a template. Instantiate the template first.");
711
+ }
712
+ if (subtaskIndex < 0 || subtaskIndex >= parent.subtasks.length) {
713
+ return yield* Effect.fail(`Subtask index ${subtaskIndex} is out of range (0..${parent.subtasks.length - 1})`);
714
+ }
715
+ const subtask = parent.subtasks[subtaskIndex];
716
+ const newTask = yield* repository.createTask({
717
+ title: subtask.text,
718
+ projects: [...parent.projects],
719
+ area: parent.area,
720
+ tags: [...parent.tags],
721
+ status: subtask.done ? "done" : "backlog",
722
+ blocked_by: [parent.id],
723
+ });
724
+ const updatedSubtasks = parent.subtasks.filter((_, i) => i !== subtaskIndex);
725
+ yield* repository.updateTask(taskId, { subtasks: updatedSubtasks });
726
+ return newTask;
727
+ });
728
+ export const applyProjectPatch = (project, patch) => {
729
+ const normalizedProject = decodeProject(project);
730
+ const normalizedPatch = decodeProjectPatch(patch);
731
+ return decodeProject({
732
+ ...normalizedProject,
733
+ ...normalizedPatch,
734
+ updated: todayIso(),
735
+ });
736
+ };
@@ -1,32 +1,32 @@
1
1
  import * as Schema from "effect/Schema";
2
- export declare const TaskStatus: Schema.Literal<["active", "backlog", "blocked", "done", "dropped", "on-hold"]>;
2
+ export declare const TaskStatus: typeof Schema.String;
3
3
  export type TaskStatus = Schema.Schema.Type<typeof TaskStatus>;
4
- export declare const TaskArea: Schema.Literal<["health", "infrastructure", "work", "personal", "blog", "code", "home", "side-projects"]>;
4
+ export declare const TaskArea: typeof Schema.String;
5
5
  export type TaskArea = Schema.Schema.Type<typeof TaskArea>;
6
- export declare const TaskUrgency: Schema.Literal<["low", "medium", "high", "urgent", "critical"]>;
6
+ export declare const TaskUrgency: typeof Schema.String;
7
7
  export type TaskUrgency = Schema.Schema.Type<typeof TaskUrgency>;
8
- export declare const TaskEnergy: Schema.Literal<["low", "medium", "high"]>;
8
+ export declare const TaskEnergy: typeof Schema.String;
9
9
  export type TaskEnergy = Schema.Schema.Type<typeof TaskEnergy>;
10
10
  export declare const Subtask: Schema.Struct<{
11
11
  text: typeof Schema.String;
12
12
  done: typeof Schema.Boolean;
13
13
  }>;
14
14
  export type Subtask = Schema.Schema.Type<typeof Subtask>;
15
- export declare const TaskRecurrenceTrigger: Schema.Literal<["clock", "completion"]>;
15
+ export declare const TaskRecurrenceTrigger: typeof Schema.String;
16
16
  export type TaskRecurrenceTrigger = Schema.Schema.Type<typeof TaskRecurrenceTrigger>;
17
- export declare const TaskRecurrenceStrategy: Schema.Literal<["replace", "accumulate"]>;
17
+ export declare const TaskRecurrenceStrategy: typeof Schema.String;
18
18
  export type TaskRecurrenceStrategy = Schema.Schema.Type<typeof TaskRecurrenceStrategy>;
19
19
  export declare const Task: Schema.Struct<{
20
20
  id: typeof Schema.String;
21
21
  title: typeof Schema.String;
22
- status: Schema.Literal<["active", "backlog", "blocked", "done", "dropped", "on-hold"]>;
23
- area: Schema.Literal<["health", "infrastructure", "work", "personal", "blog", "code", "home", "side-projects"]>;
24
- project: Schema.NullOr<typeof Schema.String>;
22
+ status: typeof Schema.String;
23
+ area: typeof Schema.String;
24
+ projects: Schema.Array$<typeof Schema.String>;
25
25
  tags: Schema.Array$<typeof Schema.String>;
26
26
  created: typeof Schema.String;
27
27
  updated: typeof Schema.String;
28
- urgency: Schema.Literal<["low", "medium", "high", "urgent", "critical"]>;
29
- energy: Schema.Literal<["low", "medium", "high"]>;
28
+ urgency: typeof Schema.String;
29
+ energy: typeof Schema.String;
30
30
  due: Schema.NullOr<typeof Schema.String>;
31
31
  context: typeof Schema.String;
32
32
  subtasks: Schema.Array$<Schema.Struct<{
@@ -41,21 +41,24 @@ export declare const Task: Schema.Struct<{
41
41
  defer_until: Schema.NullOr<typeof Schema.String>;
42
42
  nudge_count: typeof Schema.Number;
43
43
  recurrence: Schema.NullOr<typeof Schema.String>;
44
- recurrence_trigger: Schema.Literal<["clock", "completion"]>;
45
- recurrence_strategy: Schema.Literal<["replace", "accumulate"]>;
44
+ recurrence_trigger: typeof Schema.String;
45
+ recurrence_strategy: typeof Schema.String;
46
46
  recurrence_last_generated: Schema.NullOr<typeof Schema.String>;
47
+ related: Schema.Array$<typeof Schema.String>;
48
+ is_template: typeof Schema.Boolean;
49
+ from_template: Schema.NullOr<typeof Schema.String>;
47
50
  }>;
48
51
  export type Task = Schema.Schema.Type<typeof Task>;
49
52
  export declare const TaskCreateInput: Schema.Struct<{
50
53
  title: typeof Schema.String;
51
- status: Schema.optionalWith<Schema.Literal<["active", "backlog", "blocked", "done", "dropped", "on-hold"]>, {
52
- default: () => "active";
54
+ status: Schema.optionalWith<typeof Schema.String, {
55
+ default: () => string;
53
56
  }>;
54
- area: Schema.optionalWith<Schema.Literal<["health", "infrastructure", "work", "personal", "blog", "code", "home", "side-projects"]>, {
55
- default: () => "personal";
57
+ area: Schema.optionalWith<typeof Schema.String, {
58
+ default: () => string;
56
59
  }>;
57
- project: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
58
- default: () => null;
60
+ projects: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
61
+ default: () => never[];
59
62
  }>;
60
63
  tags: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
61
64
  default: () => never[];
@@ -66,11 +69,11 @@ export declare const TaskCreateInput: Schema.Struct<{
66
69
  updated: Schema.optionalWith<typeof Schema.String, {
67
70
  default: () => string;
68
71
  }>;
69
- urgency: Schema.optionalWith<Schema.Literal<["low", "medium", "high", "urgent", "critical"]>, {
70
- default: () => "medium";
72
+ urgency: Schema.optionalWith<typeof Schema.String, {
73
+ default: () => string;
71
74
  }>;
72
- energy: Schema.optionalWith<Schema.Literal<["low", "medium", "high"]>, {
73
- default: () => "medium";
75
+ energy: Schema.optionalWith<typeof Schema.String, {
76
+ default: () => string;
74
77
  }>;
75
78
  due: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
76
79
  default: () => null;
@@ -108,15 +111,24 @@ export declare const TaskCreateInput: Schema.Struct<{
108
111
  recurrence: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
109
112
  default: () => null;
110
113
  }>;
111
- recurrence_trigger: Schema.optionalWith<Schema.Literal<["clock", "completion"]>, {
112
- default: () => "clock";
114
+ recurrence_trigger: Schema.optionalWith<typeof Schema.String, {
115
+ default: () => string;
113
116
  }>;
114
- recurrence_strategy: Schema.optionalWith<Schema.Literal<["replace", "accumulate"]>, {
115
- default: () => "replace";
117
+ recurrence_strategy: Schema.optionalWith<typeof Schema.String, {
118
+ default: () => string;
116
119
  }>;
117
120
  recurrence_last_generated: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
118
121
  default: () => null;
119
122
  }>;
123
+ related: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
124
+ default: () => never[];
125
+ }>;
126
+ is_template: Schema.optionalWith<typeof Schema.Boolean, {
127
+ default: () => false;
128
+ }>;
129
+ from_template: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
130
+ default: () => null;
131
+ }>;
120
132
  }>;
121
133
  export type TaskCreateInput = Schema.Schema.Encoded<typeof TaskCreateInput>;
122
134
  export declare const TaskPatch: Schema.Struct<{
@@ -126,13 +138,13 @@ export declare const TaskPatch: Schema.Struct<{
126
138
  title: Schema.optionalWith<typeof Schema.String, {
127
139
  exact: true;
128
140
  }>;
129
- status: Schema.optionalWith<Schema.Literal<["active", "backlog", "blocked", "done", "dropped", "on-hold"]>, {
141
+ status: Schema.optionalWith<typeof Schema.String, {
130
142
  exact: true;
131
143
  }>;
132
- area: Schema.optionalWith<Schema.Literal<["health", "infrastructure", "work", "personal", "blog", "code", "home", "side-projects"]>, {
144
+ area: Schema.optionalWith<typeof Schema.String, {
133
145
  exact: true;
134
146
  }>;
135
- project: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
147
+ projects: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
136
148
  exact: true;
137
149
  }>;
138
150
  tags: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
@@ -144,10 +156,10 @@ export declare const TaskPatch: Schema.Struct<{
144
156
  updated: Schema.optionalWith<typeof Schema.String, {
145
157
  exact: true;
146
158
  }>;
147
- urgency: Schema.optionalWith<Schema.Literal<["low", "medium", "high", "urgent", "critical"]>, {
159
+ urgency: Schema.optionalWith<typeof Schema.String, {
148
160
  exact: true;
149
161
  }>;
150
- energy: Schema.optionalWith<Schema.Literal<["low", "medium", "high"]>, {
162
+ energy: Schema.optionalWith<typeof Schema.String, {
151
163
  exact: true;
152
164
  }>;
153
165
  due: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
@@ -186,17 +198,79 @@ export declare const TaskPatch: Schema.Struct<{
186
198
  recurrence: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
187
199
  exact: true;
188
200
  }>;
189
- recurrence_trigger: Schema.optionalWith<Schema.Literal<["clock", "completion"]>, {
201
+ recurrence_trigger: Schema.optionalWith<typeof Schema.String, {
190
202
  exact: true;
191
203
  }>;
192
- recurrence_strategy: Schema.optionalWith<Schema.Literal<["replace", "accumulate"]>, {
204
+ recurrence_strategy: Schema.optionalWith<typeof Schema.String, {
193
205
  exact: true;
194
206
  }>;
195
207
  recurrence_last_generated: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
196
208
  exact: true;
197
209
  }>;
210
+ related: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
211
+ exact: true;
212
+ }>;
213
+ is_template: Schema.optionalWith<typeof Schema.Boolean, {
214
+ exact: true;
215
+ }>;
216
+ from_template: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
217
+ exact: true;
218
+ }>;
198
219
  }>;
199
220
  export type TaskPatch = Schema.Schema.Encoded<typeof TaskPatch>;
221
+ export declare const ProjectStatus: typeof Schema.String;
222
+ export type ProjectStatus = Schema.Schema.Type<typeof ProjectStatus>;
223
+ export declare const Project: Schema.Struct<{
224
+ id: typeof Schema.String;
225
+ title: typeof Schema.String;
226
+ status: typeof Schema.String;
227
+ area: typeof Schema.String;
228
+ description: typeof Schema.String;
229
+ tags: Schema.Array$<typeof Schema.String>;
230
+ created: typeof Schema.String;
231
+ updated: typeof Schema.String;
232
+ }>;
233
+ export type Project = Schema.Schema.Type<typeof Project>;
234
+ export declare const ProjectCreateInput: Schema.Struct<{
235
+ title: typeof Schema.String;
236
+ status: Schema.optionalWith<typeof Schema.String, {
237
+ default: () => string;
238
+ }>;
239
+ area: Schema.optionalWith<typeof Schema.String, {
240
+ default: () => string;
241
+ }>;
242
+ description: Schema.optionalWith<typeof Schema.String, {
243
+ default: () => string;
244
+ }>;
245
+ tags: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
246
+ default: () => string[];
247
+ }>;
248
+ created: Schema.optionalWith<typeof Schema.String, {
249
+ default: () => string;
250
+ }>;
251
+ updated: Schema.optionalWith<typeof Schema.String, {
252
+ default: () => string;
253
+ }>;
254
+ }>;
255
+ export type ProjectCreateInput = Schema.Schema.Encoded<typeof ProjectCreateInput>;
256
+ export declare const ProjectPatch: Schema.Struct<{
257
+ title: Schema.optionalWith<typeof Schema.String, {
258
+ exact: true;
259
+ }>;
260
+ status: Schema.optionalWith<typeof Schema.String, {
261
+ exact: true;
262
+ }>;
263
+ area: Schema.optionalWith<typeof Schema.String, {
264
+ exact: true;
265
+ }>;
266
+ description: Schema.optionalWith<typeof Schema.String, {
267
+ exact: true;
268
+ }>;
269
+ tags: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
270
+ exact: true;
271
+ }>;
272
+ }>;
273
+ export type ProjectPatch = Schema.Schema.Encoded<typeof ProjectPatch>;
200
274
  export declare const WorkLogEntry: Schema.Struct<{
201
275
  id: typeof Schema.String;
202
276
  task_id: typeof Schema.String;