@jamesaphoenix/tx 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.
@@ -0,0 +1,140 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { SqliteClient } from "../db.js";
3
+ import { DatabaseError } from "../errors.js";
4
+ import { rowToTask } from "../schema.js";
5
+ export class TaskRepository extends Context.Tag("TaskRepository")() {
6
+ }
7
+ export const TaskRepositoryLive = Layer.effect(TaskRepository, Effect.gen(function* () {
8
+ const db = yield* SqliteClient;
9
+ return {
10
+ findById: (id) => Effect.try({
11
+ try: () => {
12
+ const row = db.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
13
+ return row ? rowToTask(row) : null;
14
+ },
15
+ catch: (cause) => new DatabaseError({ cause })
16
+ }),
17
+ findByIds: (ids) => Effect.try({
18
+ try: () => {
19
+ if (ids.length === 0)
20
+ return [];
21
+ const placeholders = ids.map(() => "?").join(",");
22
+ const rows = db.prepare(`SELECT * FROM tasks WHERE id IN (${placeholders})`).all(...ids);
23
+ return rows.map(rowToTask);
24
+ },
25
+ catch: (cause) => new DatabaseError({ cause })
26
+ }),
27
+ findAll: (filter) => Effect.try({
28
+ try: () => {
29
+ const conditions = [];
30
+ const params = [];
31
+ if (filter?.status) {
32
+ if (Array.isArray(filter.status)) {
33
+ const placeholders = filter.status.map(() => "?").join(",");
34
+ conditions.push(`status IN (${placeholders})`);
35
+ params.push(...filter.status);
36
+ }
37
+ else {
38
+ conditions.push("status = ?");
39
+ params.push(filter.status);
40
+ }
41
+ }
42
+ if (filter?.parentId !== undefined) {
43
+ if (filter.parentId === null) {
44
+ conditions.push("parent_id IS NULL");
45
+ }
46
+ else {
47
+ conditions.push("parent_id = ?");
48
+ params.push(filter.parentId);
49
+ }
50
+ }
51
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
52
+ const limit = filter?.limit ? `LIMIT ${filter.limit}` : "";
53
+ const sql = `SELECT * FROM tasks ${where} ORDER BY score DESC, created_at ASC ${limit}`;
54
+ const rows = db.prepare(sql).all(...params);
55
+ return rows.map(rowToTask);
56
+ },
57
+ catch: (cause) => new DatabaseError({ cause })
58
+ }),
59
+ findByParent: (parentId) => Effect.try({
60
+ try: () => {
61
+ const rows = parentId === null
62
+ ? db.prepare("SELECT * FROM tasks WHERE parent_id IS NULL ORDER BY score DESC").all()
63
+ : db.prepare("SELECT * FROM tasks WHERE parent_id = ? ORDER BY score DESC").all(parentId);
64
+ return rows.map(rowToTask);
65
+ },
66
+ catch: (cause) => new DatabaseError({ cause })
67
+ }),
68
+ getChildIds: (id) => Effect.try({
69
+ try: () => {
70
+ const rows = db.prepare("SELECT id FROM tasks WHERE parent_id = ?").all(id);
71
+ return rows.map(r => r.id);
72
+ },
73
+ catch: (cause) => new DatabaseError({ cause })
74
+ }),
75
+ getChildIdsForMany: (ids) => Effect.try({
76
+ try: () => {
77
+ const result = new Map();
78
+ if (ids.length === 0)
79
+ return result;
80
+ const placeholders = ids.map(() => "?").join(",");
81
+ const rows = db.prepare(`SELECT id, parent_id FROM tasks WHERE parent_id IN (${placeholders})`).all(...ids);
82
+ // Initialize all requested IDs with empty arrays
83
+ for (const id of ids) {
84
+ result.set(id, []);
85
+ }
86
+ // Group by parent_id
87
+ for (const row of rows) {
88
+ const existing = result.get(row.parent_id) ?? [];
89
+ result.set(row.parent_id, [...existing, row.id]);
90
+ }
91
+ return result;
92
+ },
93
+ catch: (cause) => new DatabaseError({ cause })
94
+ }),
95
+ insert: (task) => Effect.try({
96
+ try: () => {
97
+ db.prepare(`INSERT INTO tasks (id, title, description, status, parent_id, score, created_at, updated_at, completed_at, metadata)
98
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(task.id, task.title, task.description, task.status, task.parentId, task.score, task.createdAt.toISOString(), task.updatedAt.toISOString(), task.completedAt?.toISOString() ?? null, JSON.stringify(task.metadata));
99
+ },
100
+ catch: (cause) => new DatabaseError({ cause })
101
+ }),
102
+ update: (task) => Effect.try({
103
+ try: () => {
104
+ db.prepare(`UPDATE tasks SET
105
+ title = ?, description = ?, status = ?, parent_id = ?,
106
+ score = ?, updated_at = ?, completed_at = ?, metadata = ?
107
+ WHERE id = ?`).run(task.title, task.description, task.status, task.parentId, task.score, task.updatedAt.toISOString(), task.completedAt?.toISOString() ?? null, JSON.stringify(task.metadata), task.id);
108
+ },
109
+ catch: (cause) => new DatabaseError({ cause })
110
+ }),
111
+ remove: (id) => Effect.try({
112
+ try: () => {
113
+ db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
114
+ },
115
+ catch: (cause) => new DatabaseError({ cause })
116
+ }),
117
+ count: (filter) => Effect.try({
118
+ try: () => {
119
+ const conditions = [];
120
+ const params = [];
121
+ if (filter?.status) {
122
+ if (Array.isArray(filter.status)) {
123
+ const placeholders = filter.status.map(() => "?").join(",");
124
+ conditions.push(`status IN (${placeholders})`);
125
+ params.push(...filter.status);
126
+ }
127
+ else {
128
+ conditions.push("status = ?");
129
+ params.push(filter.status);
130
+ }
131
+ }
132
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
133
+ const result = db.prepare(`SELECT COUNT(*) as cnt FROM tasks ${where}`).get(...params);
134
+ return result.cnt;
135
+ },
136
+ catch: (cause) => new DatabaseError({ cause })
137
+ })
138
+ };
139
+ }));
140
+ //# sourceMappingURL=task-repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-repo.js","sourceRoot":"","sources":["../../src/repo/task-repo.ts"],"names":[],"mappings":"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"}
package/dist/schema.js ADDED
@@ -0,0 +1,37 @@
1
+ // Plain TypeScript types for the bootstrap.
2
+ // Can be migrated to Effect Schema.Class later for runtime validation.
3
+ export const TASK_STATUSES = [
4
+ "backlog", "ready", "planning", "active",
5
+ "blocked", "review", "human_needs_to_review", "done"
6
+ ];
7
+ // Status transition validation
8
+ const VALID_TRANSITIONS = {
9
+ backlog: ["ready", "planning", "active", "blocked", "done"],
10
+ ready: ["planning", "active", "blocked", "done"],
11
+ planning: ["ready", "active", "blocked", "done"],
12
+ active: ["blocked", "review", "done"],
13
+ blocked: ["backlog", "ready", "planning", "active"],
14
+ review: ["active", "human_needs_to_review", "done"],
15
+ human_needs_to_review: ["active", "review", "done"],
16
+ done: ["backlog"]
17
+ };
18
+ export const isValidTransition = (from, to) => VALID_TRANSITIONS[from]?.includes(to) ?? false;
19
+ export const isValidStatus = (s) => TASK_STATUSES.includes(s);
20
+ export const rowToTask = (row) => ({
21
+ id: row.id,
22
+ title: row.title,
23
+ description: row.description,
24
+ status: row.status,
25
+ parentId: row.parent_id,
26
+ score: row.score,
27
+ createdAt: new Date(row.created_at),
28
+ updatedAt: new Date(row.updated_at),
29
+ completedAt: row.completed_at ? new Date(row.completed_at) : null,
30
+ metadata: JSON.parse(row.metadata || "{}")
31
+ });
32
+ export const rowToDependency = (row) => ({
33
+ blockerId: row.blocker_id,
34
+ blockedId: row.blocked_id,
35
+ createdAt: new Date(row.created_at)
36
+ });
37
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,uEAAuE;AAEvE,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ;IACxC,SAAS,EAAE,QAAQ,EAAE,uBAAuB,EAAE,MAAM;CAC5C,CAAA;AA4DV,+BAA+B;AAC/B,MAAM,iBAAiB,GAAqC;IAC1D,OAAO,EAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC;IAC1E,KAAK,EAAmB,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC;IACjE,QAAQ,EAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC;IAC9D,MAAM,EAAkB,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;IACrD,OAAO,EAAiB,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC;IAClE,MAAM,EAAkB,CAAC,QAAQ,EAAE,uBAAuB,EAAE,MAAM,CAAC;IACnE,qBAAqB,EAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;IACpD,IAAI,EAAoB,CAAC,SAAS,CAAC;CACpC,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAgB,EAAE,EAAc,EAAW,EAAE,CAC7E,iBAAiB,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,IAAI,KAAK,CAAA;AAEhD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAS,EAAmB,EAAE,CAC1D,aAAa,CAAC,QAAQ,CAAC,CAAe,CAAC,CAAA;AAgBzC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAY,EAAQ,EAAE,CAAC,CAAC;IAChD,EAAE,EAAE,GAAG,CAAC,EAAY;IACpB,KAAK,EAAE,GAAG,CAAC,KAAK;IAChB,WAAW,EAAE,GAAG,CAAC,WAAW;IAC5B,MAAM,EAAE,GAAG,CAAC,MAAoB;IAChC,QAAQ,EAAE,GAAG,CAAC,SAA0B;IACxC,KAAK,EAAE,GAAG,CAAC,KAAK;IAChB,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;IACnC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;IACnC,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;IACjE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC;CAC3C,CAAC,CAAA;AASF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAkB,EAAkB,EAAE,CAAC,CAAC;IACtE,SAAS,EAAE,GAAG,CAAC,UAAoB;IACnC,SAAS,EAAE,GAAG,CAAC,UAAoB;IACnC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;CACpC,CAAC,CAAA"}
@@ -0,0 +1,55 @@
1
+ // Effect Schema definitions for JSONL sync operations.
2
+ // See DD-009 for specification details.
3
+ import { Schema } from "effect";
4
+ import { TASK_STATUSES } from "../schema.js";
5
+ // Schema version - v=1 for all sync operations
6
+ export const SyncVersion = Schema.Literal(1);
7
+ // TaskId schema - matches tx-[a-z0-9]{6,8} pattern
8
+ export const TaskIdSchema = Schema.String.pipe(Schema.pattern(/^tx-[a-z0-9]{6,8}$/));
9
+ // TaskStatus schema - matches the status lifecycle
10
+ export const TaskStatusSchema = Schema.Literal(...TASK_STATUSES);
11
+ // ISO 8601 timestamp pattern (basic validation)
12
+ const IsoTimestamp = Schema.String.pipe(Schema.pattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/));
13
+ // Task data embedded in upsert operations
14
+ export const TaskDataSchema = Schema.Struct({
15
+ title: Schema.String,
16
+ description: Schema.String,
17
+ status: TaskStatusSchema,
18
+ score: Schema.Number.pipe(Schema.int()),
19
+ parentId: Schema.NullOr(TaskIdSchema),
20
+ metadata: Schema.Record({ key: Schema.String, value: Schema.Unknown })
21
+ });
22
+ // Task upsert operation
23
+ export const TaskUpsertOp = Schema.Struct({
24
+ v: SyncVersion,
25
+ op: Schema.Literal("upsert"),
26
+ ts: IsoTimestamp,
27
+ id: TaskIdSchema,
28
+ data: TaskDataSchema
29
+ });
30
+ // Task delete operation (tombstone)
31
+ export const TaskDeleteOp = Schema.Struct({
32
+ v: SyncVersion,
33
+ op: Schema.Literal("delete"),
34
+ ts: IsoTimestamp,
35
+ id: TaskIdSchema
36
+ });
37
+ // Dependency add operation
38
+ export const DepAddOp = Schema.Struct({
39
+ v: SyncVersion,
40
+ op: Schema.Literal("dep_add"),
41
+ ts: IsoTimestamp,
42
+ blockerId: TaskIdSchema,
43
+ blockedId: TaskIdSchema
44
+ });
45
+ // Dependency remove operation
46
+ export const DepRemoveOp = Schema.Struct({
47
+ v: SyncVersion,
48
+ op: Schema.Literal("dep_remove"),
49
+ ts: IsoTimestamp,
50
+ blockerId: TaskIdSchema,
51
+ blockedId: TaskIdSchema
52
+ });
53
+ // Union of all sync operations
54
+ export const SyncOperation = Schema.Union(TaskUpsertOp, TaskDeleteOp, DepAddOp, DepRemoveOp);
55
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/schemas/sync.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,wCAAwC;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,+CAA+C;AAC/C,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;AAE5C,mDAAmD;AACnD,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAC5C,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,CACrC,CAAA;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,CAAA;AAEhE,gDAAgD;AAChD,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CACrC,MAAM,CAAC,OAAO,CAAC,sCAAsC,CAAC,CACvD,CAAA;AAED,0CAA0C;AAC1C,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM;IAC1B,MAAM,EAAE,gBAAgB;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;CACvE,CAAC,CAAA;AAEF,wBAAwB;AACxB,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,CAAC,EAAE,WAAW;IACd,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC5B,EAAE,EAAE,YAAY;IAChB,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,cAAc;CACrB,CAAC,CAAA;AAGF,oCAAoC;AACpC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,CAAC,EAAE,WAAW;IACd,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC5B,EAAE,EAAE,YAAY;IAChB,EAAE,EAAE,YAAY;CACjB,CAAC,CAAA;AAGF,2BAA2B;AAC3B,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IACpC,CAAC,EAAE,WAAW;IACd,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;IAC7B,EAAE,EAAE,YAAY;IAChB,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE,YAAY;CACxB,CAAC,CAAA;AAGF,8BAA8B;AAC9B,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,CAAC,EAAE,WAAW;IACd,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC;IAChC,EAAE,EAAE,YAAY;IAChB,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE,YAAY;CACxB,CAAC,CAAA;AAGF,+BAA+B;AAC/B,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CACvC,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,WAAW,CACZ,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { DependencyRepository } from "../repo/dep-repo.js";
3
+ import { TaskRepository } from "../repo/task-repo.js";
4
+ import { ValidationError, CircularDependencyError, TaskNotFoundError } from "../errors.js";
5
+ export class DependencyService extends Context.Tag("DependencyService")() {
6
+ }
7
+ export const DependencyServiceLive = Layer.effect(DependencyService, Effect.gen(function* () {
8
+ const depRepo = yield* DependencyRepository;
9
+ const taskRepo = yield* TaskRepository;
10
+ return {
11
+ addBlocker: (taskId, blockerId) => Effect.gen(function* () {
12
+ if (taskId === blockerId) {
13
+ return yield* Effect.fail(new ValidationError({ reason: "A task cannot block itself" }));
14
+ }
15
+ const task = yield* taskRepo.findById(taskId);
16
+ if (!task) {
17
+ return yield* Effect.fail(new TaskNotFoundError({ id: taskId }));
18
+ }
19
+ const blocker = yield* taskRepo.findById(blockerId);
20
+ if (!blocker) {
21
+ return yield* Effect.fail(new TaskNotFoundError({ id: blockerId }));
22
+ }
23
+ // Cycle detection: check if there's already a path from taskId to blockerId
24
+ // (i.e., blockerId is transitively blocked by taskId)
25
+ const wouldCycle = yield* depRepo.hasPath(blockerId, taskId);
26
+ if (wouldCycle) {
27
+ return yield* Effect.fail(new CircularDependencyError({ taskId, blockerId }));
28
+ }
29
+ yield* depRepo.insert(blockerId, taskId);
30
+ }),
31
+ removeBlocker: (taskId, blockerId) => depRepo.remove(blockerId, taskId)
32
+ };
33
+ }));
34
+ //# sourceMappingURL=dep-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dep-service.js","sourceRoot":"","sources":["../../src/services/dep-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,iBAAiB,EAAiB,MAAM,cAAc,CAAA;AAGzG,MAAM,OAAO,iBAAkB,SAAQ,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAMpE;CAAG;AAEN,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC,MAAM,CAC/C,iBAAiB,EACjB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,oBAAoB,CAAA;IAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,cAAc,CAAA;IAEtC,OAAO;QACL,UAAU,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,CAChC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAC,CAAC,CAAA;YAC1F,CAAC;YAED,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;YAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;YAClE,CAAC;YAED,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;YACnD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;YACrE,CAAC;YAED,4EAA4E;YAC5E,sDAAsD;YACtD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;YAC5D,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,uBAAuB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;YAC/E,CAAC;YAED,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAC1C,CAAC,CAAC;QAEJ,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,CACnC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;KACpC,CAAA;AACH,CAAC,CAAC,CACH,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { TaskRepository } from "../repo/task-repo.js";
3
+ import { TaskNotFoundError } from "../errors.js";
4
+ export class HierarchyService extends Context.Tag("HierarchyService")() {
5
+ }
6
+ export const HierarchyServiceLive = Layer.effect(HierarchyService, Effect.gen(function* () {
7
+ const taskRepo = yield* TaskRepository;
8
+ const buildTree = (task) => Effect.gen(function* () {
9
+ const childTasks = yield* taskRepo.findByParent(task.id);
10
+ const childTrees = [];
11
+ for (const child of childTasks) {
12
+ childTrees.push(yield* buildTree(child));
13
+ }
14
+ return { task, children: childTrees };
15
+ });
16
+ return {
17
+ getChildren: (id) => Effect.gen(function* () {
18
+ const task = yield* taskRepo.findById(id);
19
+ if (!task) {
20
+ return yield* Effect.fail(new TaskNotFoundError({ id }));
21
+ }
22
+ return yield* taskRepo.findByParent(id);
23
+ }),
24
+ getAncestors: (id) => Effect.gen(function* () {
25
+ const task = yield* taskRepo.findById(id);
26
+ if (!task) {
27
+ return yield* Effect.fail(new TaskNotFoundError({ id }));
28
+ }
29
+ const ancestors = [];
30
+ let currentId = task.parentId;
31
+ while (currentId !== null) {
32
+ const parent = yield* taskRepo.findById(currentId);
33
+ if (!parent)
34
+ break;
35
+ ancestors.push(parent);
36
+ currentId = parent.parentId;
37
+ }
38
+ return ancestors;
39
+ }),
40
+ getTree: (id) => Effect.gen(function* () {
41
+ const task = yield* taskRepo.findById(id);
42
+ if (!task) {
43
+ return yield* Effect.fail(new TaskNotFoundError({ id }));
44
+ }
45
+ return yield* buildTree(task);
46
+ }),
47
+ getDepth: (id) => Effect.gen(function* () {
48
+ const task = yield* taskRepo.findById(id);
49
+ if (!task) {
50
+ return yield* Effect.fail(new TaskNotFoundError({ id }));
51
+ }
52
+ let depth = 0;
53
+ let currentId = task.parentId;
54
+ while (currentId !== null) {
55
+ const parent = yield* taskRepo.findById(currentId);
56
+ if (!parent)
57
+ break;
58
+ depth++;
59
+ currentId = parent.parentId;
60
+ }
61
+ return depth;
62
+ }),
63
+ getRoots: () => taskRepo.findByParent(null)
64
+ };
65
+ }));
66
+ //# sourceMappingURL=hierarchy-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchy-service.js","sourceRoot":"","sources":["../../src/services/hierarchy-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { TaskRepository } from "../repo/task-repo.js";
3
+ import { DependencyRepository } from "../repo/dep-repo.js";
4
+ export class ReadyService extends Context.Tag("ReadyService")() {
5
+ }
6
+ export const ReadyServiceLive = Layer.effect(ReadyService, Effect.gen(function* () {
7
+ const taskRepo = yield* TaskRepository;
8
+ const depRepo = yield* DependencyRepository;
9
+ return {
10
+ getReady: (limit = 100) => Effect.gen(function* () {
11
+ const candidates = yield* taskRepo.findAll({
12
+ status: ["backlog", "ready", "planning"]
13
+ });
14
+ const ready = [];
15
+ for (const task of candidates) {
16
+ const blockerIds = yield* depRepo.getBlockerIds(task.id);
17
+ const blockingIds = yield* depRepo.getBlockingIds(task.id);
18
+ const childIds = yield* taskRepo.getChildIds(task.id);
19
+ if (blockerIds.length === 0) {
20
+ ready.push({
21
+ ...task,
22
+ blockedBy: [],
23
+ blocks: blockingIds,
24
+ children: childIds,
25
+ isReady: true
26
+ });
27
+ continue;
28
+ }
29
+ const blockers = yield* taskRepo.findByIds(blockerIds);
30
+ const allDone = blockers.every(b => b.status === "done");
31
+ if (allDone) {
32
+ ready.push({
33
+ ...task,
34
+ blockedBy: blockerIds,
35
+ blocks: blockingIds,
36
+ children: childIds,
37
+ isReady: true
38
+ });
39
+ }
40
+ }
41
+ ready.sort((a, b) => b.score - a.score);
42
+ return ready.slice(0, limit);
43
+ }),
44
+ isReady: (id) => Effect.gen(function* () {
45
+ const task = yield* taskRepo.findById(id);
46
+ if (!task)
47
+ return false;
48
+ if (!["backlog", "ready", "planning"].includes(task.status))
49
+ return false;
50
+ const blockerIds = yield* depRepo.getBlockerIds(id);
51
+ if (blockerIds.length === 0)
52
+ return true;
53
+ const blockers = yield* taskRepo.findByIds(blockerIds);
54
+ return blockers.every(b => b.status === "done");
55
+ }),
56
+ getBlockers: (id) => Effect.gen(function* () {
57
+ const blockerIds = yield* depRepo.getBlockerIds(id);
58
+ if (blockerIds.length === 0)
59
+ return [];
60
+ return yield* taskRepo.findByIds(blockerIds);
61
+ }),
62
+ getBlocking: (id) => Effect.gen(function* () {
63
+ const blockingIds = yield* depRepo.getBlockingIds(id);
64
+ if (blockingIds.length === 0)
65
+ return [];
66
+ return yield* taskRepo.findByIds(blockingIds);
67
+ })
68
+ };
69
+ }));
70
+ //# sourceMappingURL=ready-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-service.js","sourceRoot":"","sources":["../../src/services/ready-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { TaskRepository } from "../repo/task-repo.js";
3
+ import { DependencyRepository } from "../repo/dep-repo.js";
4
+ import { TaskNotFoundError } from "../errors.js";
5
+ import { HierarchyService } from "./hierarchy-service.js";
6
+ export class ScoreService extends Context.Tag("ScoreService")() {
7
+ }
8
+ export const ScoreServiceLive = Layer.effect(ScoreService, Effect.gen(function* () {
9
+ const taskRepo = yield* TaskRepository;
10
+ const depRepo = yield* DependencyRepository;
11
+ const hierarchySvc = yield* HierarchyService;
12
+ const computeBreakdown = (task, blockingCount, depth) => {
13
+ // Base score from DB
14
+ const baseScore = task.score;
15
+ // Blocking bonus: +25 per task this task blocks
16
+ const blockingBonus = blockingCount * 25;
17
+ // Age bonus: old tasks shouldn't rot
18
+ const ageMs = Date.now() - task.createdAt.getTime();
19
+ const ageHours = ageMs / (1000 * 60 * 60);
20
+ let ageBonus = 0;
21
+ if (ageHours > 48) {
22
+ ageBonus = 100;
23
+ }
24
+ else if (ageHours > 24) {
25
+ ageBonus = 50;
26
+ }
27
+ // Depth penalty: prefer root tasks over deep subtasks
28
+ const depthPenalty = depth * 10;
29
+ // Blocked status penalty: blocked tasks should not be prioritized
30
+ const blockedPenalty = task.status === "blocked" ? 1000 : 0;
31
+ // Final calculation
32
+ const finalScore = baseScore + blockingBonus + ageBonus - depthPenalty - blockedPenalty;
33
+ return {
34
+ baseScore,
35
+ blockingBonus,
36
+ blockingCount,
37
+ ageBonus,
38
+ ageHours: Math.floor(ageHours),
39
+ depthPenalty,
40
+ depth,
41
+ blockedPenalty,
42
+ finalScore
43
+ };
44
+ };
45
+ const getTaskContext = (task) => Effect.gen(function* () {
46
+ // Get how many tasks this task blocks
47
+ const blockingIds = yield* depRepo.getBlockingIds(task.id);
48
+ const blockingCount = blockingIds.length;
49
+ // Get depth in hierarchy
50
+ const depth = yield* hierarchySvc.getDepth(task.id).pipe(Effect.catchTag("TaskNotFoundError", () => Effect.succeed(0)));
51
+ return { blockingCount, depth };
52
+ });
53
+ return {
54
+ calculate: (task) => Effect.gen(function* () {
55
+ const ctx = yield* getTaskContext(task);
56
+ const breakdown = computeBreakdown(task, ctx.blockingCount, ctx.depth);
57
+ return breakdown.finalScore;
58
+ }),
59
+ calculateById: (id) => Effect.gen(function* () {
60
+ const task = yield* taskRepo.findById(id);
61
+ if (!task) {
62
+ return yield* Effect.fail(new TaskNotFoundError({ id }));
63
+ }
64
+ const ctx = yield* getTaskContext(task);
65
+ const breakdown = computeBreakdown(task, ctx.blockingCount, ctx.depth);
66
+ return breakdown.finalScore;
67
+ }),
68
+ getBreakdown: (task) => Effect.gen(function* () {
69
+ const ctx = yield* getTaskContext(task);
70
+ return computeBreakdown(task, ctx.blockingCount, ctx.depth);
71
+ }),
72
+ getBreakdownById: (id) => Effect.gen(function* () {
73
+ const task = yield* taskRepo.findById(id);
74
+ if (!task) {
75
+ return yield* Effect.fail(new TaskNotFoundError({ id }));
76
+ }
77
+ const ctx = yield* getTaskContext(task);
78
+ return computeBreakdown(task, ctx.blockingCount, ctx.depth);
79
+ })
80
+ };
81
+ }));
82
+ //# sourceMappingURL=score-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score-service.js","sourceRoot":"","sources":["../../src/services/score-service.ts"],"names":[],"mappings":"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"}