@cliangdev/flux-plugin 0.2.0-dev.4f12f3f → 0.2.0-dev.71255d1

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.
@@ -194,8 +194,12 @@ describe("LinearAdapter - Dependency Operations", () => {
194
194
  const mockRelation = {
195
195
  id: "relation_1",
196
196
  type: "blocks",
197
- issue: { id: "issue_epic_1", identifier: "ENG-42" },
198
- relatedIssue: { id: "issue_epic_2", identifier: "ENG-43" },
197
+ // Linear SDK returns promises for lazy-loaded related objects
198
+ issue: Promise.resolve({ id: "issue_epic_1", identifier: "ENG-42" }),
199
+ relatedIssue: Promise.resolve({
200
+ id: "issue_epic_2",
201
+ identifier: "ENG-43",
202
+ }),
199
203
  delete: mockDelete,
200
204
  };
201
205
 
@@ -208,7 +212,7 @@ describe("LinearAdapter - Dependency Operations", () => {
208
212
  id: "issue_epic_2",
209
213
  identifier: "ENG-43",
210
214
  _raw: {
211
- relations: mock(async () => ({
215
+ inverseRelations: mock(async () => ({
212
216
  nodes: [mockRelation],
213
217
  })),
214
218
  },
@@ -235,7 +239,7 @@ describe("LinearAdapter - Dependency Operations", () => {
235
239
  id: "issue_epic_1",
236
240
  identifier: "ENG-42",
237
241
  _raw: {
238
- relations: mock(async () => ({ nodes: [] })),
242
+ inverseRelations: mock(async () => ({ nodes: [] })),
239
243
  },
240
244
  });
241
245
 
@@ -265,7 +269,7 @@ describe("LinearAdapter - Dependency Operations", () => {
265
269
  id: "issue_epic_2",
266
270
  identifier: "ENG-43",
267
271
  _raw: {
268
- relations: mock(async () => ({
272
+ inverseRelations: mock(async () => ({
269
273
  nodes: [], // No relations
270
274
  })),
271
275
  },
@@ -294,19 +298,31 @@ describe("LinearAdapter - Dependency Operations", () => {
294
298
  id: "issue_epic_1",
295
299
  identifier: "ENG-43",
296
300
  _raw: {
297
- relations: mock(async () => ({
301
+ inverseRelations: mock(async () => ({
298
302
  nodes: [
299
303
  {
300
304
  id: "relation_1",
301
305
  type: "blocks",
302
- issue: { id: "issue_epic_2", identifier: "ENG-42" },
303
- relatedIssue: { id: "issue_epic_1", identifier: "ENG-43" },
306
+ issue: Promise.resolve({
307
+ id: "issue_epic_2",
308
+ identifier: "ENG-42",
309
+ }),
310
+ relatedIssue: Promise.resolve({
311
+ id: "issue_epic_1",
312
+ identifier: "ENG-43",
313
+ }),
304
314
  },
305
315
  {
306
316
  id: "relation_2",
307
317
  type: "blocks",
308
- issue: { id: "issue_epic_3", identifier: "ENG-44" },
309
- relatedIssue: { id: "issue_epic_1", identifier: "ENG-43" },
318
+ issue: Promise.resolve({
319
+ id: "issue_epic_3",
320
+ identifier: "ENG-44",
321
+ }),
322
+ relatedIssue: Promise.resolve({
323
+ id: "issue_epic_1",
324
+ identifier: "ENG-43",
325
+ }),
310
326
  },
311
327
  ],
312
328
  })),
@@ -328,7 +344,7 @@ describe("LinearAdapter - Dependency Operations", () => {
328
344
  id: "issue_epic_1",
329
345
  identifier: "ENG-42",
330
346
  _raw: {
331
- relations: mock(async () => ({
347
+ inverseRelations: mock(async () => ({
332
348
  nodes: [],
333
349
  })),
334
350
  },
@@ -349,25 +365,43 @@ describe("LinearAdapter - Dependency Operations", () => {
349
365
  id: "issue_epic_1",
350
366
  identifier: "ENG-43",
351
367
  _raw: {
352
- relations: mock(async () => ({
368
+ inverseRelations: mock(async () => ({
353
369
  nodes: [
354
370
  {
355
371
  id: "relation_1",
356
372
  type: "blocks",
357
- issue: { id: "issue_epic_2", identifier: "ENG-42" },
358
- relatedIssue: { id: "issue_epic_1", identifier: "ENG-43" },
373
+ issue: Promise.resolve({
374
+ id: "issue_epic_2",
375
+ identifier: "ENG-42",
376
+ }),
377
+ relatedIssue: Promise.resolve({
378
+ id: "issue_epic_1",
379
+ identifier: "ENG-43",
380
+ }),
359
381
  },
360
382
  {
361
383
  id: "relation_2",
362
384
  type: "duplicate",
363
- issue: { id: "issue_epic_3", identifier: "ENG-44" },
364
- relatedIssue: { id: "issue_epic_1", identifier: "ENG-43" },
385
+ issue: Promise.resolve({
386
+ id: "issue_epic_3",
387
+ identifier: "ENG-44",
388
+ }),
389
+ relatedIssue: Promise.resolve({
390
+ id: "issue_epic_1",
391
+ identifier: "ENG-43",
392
+ }),
365
393
  },
366
394
  {
367
395
  id: "relation_3",
368
396
  type: "related",
369
- issue: { id: "issue_epic_4", identifier: "ENG-45" },
370
- relatedIssue: { id: "issue_epic_1", identifier: "ENG-43" },
397
+ issue: Promise.resolve({
398
+ id: "issue_epic_4",
399
+ identifier: "ENG-45",
400
+ }),
401
+ relatedIssue: Promise.resolve({
402
+ id: "issue_epic_1",
403
+ identifier: "ENG-43",
404
+ }),
371
405
  },
372
406
  ],
373
407
  })),
@@ -887,16 +887,22 @@ export class LinearAdapter implements BackendAdapter {
887
887
  if (!blockedIssue) throw new Error(`Issue not found: ${ref}`);
888
888
  if (!blockerIssue) throw new Error(`Issue not found: ${dependsOnRef}`);
889
889
 
890
+ // Use inverseRelations to find relations where blockedIssue is the target
890
891
  const relations = await this.client.execute<any>(() =>
891
- blockedIssue._raw.relations(),
892
+ blockedIssue._raw.inverseRelations(),
892
893
  );
893
894
 
894
- const relationToDelete = relations.nodes.find(
895
- (rel: any) =>
896
- rel.type === "blocks" &&
897
- rel.issue.id === blockerIssue.id &&
898
- rel.relatedIssue.id === blockedIssue.id,
899
- );
895
+ // Find the relation to delete - need to await rel.issue since it's lazy-loaded
896
+ let relationToDelete: any = null;
897
+ for (const rel of relations.nodes) {
898
+ if (rel.type === "blocks") {
899
+ const relIssue = await rel.issue;
900
+ if (relIssue?.id === blockerIssue.id) {
901
+ relationToDelete = rel;
902
+ break;
903
+ }
904
+ }
905
+ }
900
906
 
901
907
  if (!relationToDelete) {
902
908
  throw new Error(
@@ -911,18 +917,17 @@ export class LinearAdapter implements BackendAdapter {
911
917
  const issue = await this.fetchIssue(ref);
912
918
  if (!issue) throw new Error(`Issue not found: ${ref}`);
913
919
 
920
+ // Use inverseRelations to get relations where this issue is the target (relatedIssueId)
921
+ // This finds issues that block this one
914
922
  const relations = await this.client.execute<any>(() =>
915
- issue._raw.relations(),
923
+ issue._raw.inverseRelations(),
916
924
  );
917
925
 
918
926
  const blockingRefs: string[] = [];
919
927
  for (const rel of relations.nodes) {
920
- if (
921
- rel.type === "blocks" &&
922
- rel.relatedIssue &&
923
- (rel.relatedIssue as any).id === issue.id
924
- ) {
925
- const blockerIssue = rel.issue as any;
928
+ if (rel.type === "blocks") {
929
+ // Linear SDK returns lazy-loaded promises for related objects
930
+ const blockerIssue = await rel.issue;
926
931
  if (blockerIssue?.identifier) {
927
932
  blockingRefs.push(blockerIssue.identifier);
928
933
  }
@@ -312,6 +312,14 @@ export class LocalAdapter implements BackendAdapter {
312
312
  cascade.epics++;
313
313
  }
314
314
 
315
+ // Delete PRD dependencies
316
+ const deletedDeps = db
317
+ .query(
318
+ "DELETE FROM prd_dependencies WHERE prd_id = ? OR depends_on_prd_id = ?",
319
+ )
320
+ .run(prd.id, prd.id);
321
+ cascade.dependencies += deletedDeps.changes;
322
+
315
323
  remove(db, "prds", prd.id);
316
324
 
317
325
  return { deleted: ref, cascade };
@@ -710,7 +718,15 @@ export class LocalAdapter implements BackendAdapter {
710
718
  throw new Error("Dependencies must be between entities of the same type");
711
719
  }
712
720
 
713
- if (entityType === "E") {
721
+ if (entityType === "P") {
722
+ const prd = findByRef<PrdRow>(db, "prds", ref);
723
+ const dependsOnPrd = findByRef<PrdRow>(db, "prds", dependsOnRef);
724
+ if (!prd || !dependsOnPrd) throw new Error("PRD not found");
725
+
726
+ db.query(
727
+ "INSERT OR IGNORE INTO prd_dependencies (prd_id, depends_on_prd_id) VALUES (?, ?)",
728
+ ).run(prd.id, dependsOnPrd.id);
729
+ } else if (entityType === "E") {
714
730
  const epic = findByRef<EpicRow>(db, "epics", ref);
715
731
  const dependsOnEpic = findByRef<EpicRow>(db, "epics", dependsOnRef);
716
732
  if (!epic || !dependsOnEpic) throw new Error("Epic not found");
@@ -726,8 +742,6 @@ export class LocalAdapter implements BackendAdapter {
726
742
  db.query(
727
743
  "INSERT OR IGNORE INTO task_dependencies (task_id, depends_on_task_id) VALUES (?, ?)",
728
744
  ).run(task.id, dependsOnTask.id);
729
- } else {
730
- throw new Error("Dependencies can only be added to Epics or Tasks");
731
745
  }
732
746
  }
733
747
 
@@ -735,7 +749,15 @@ export class LocalAdapter implements BackendAdapter {
735
749
  const db = getDb();
736
750
  const entityType = getEntityType(ref);
737
751
 
738
- if (entityType === "E") {
752
+ if (entityType === "P") {
753
+ const prd = findByRef<PrdRow>(db, "prds", ref);
754
+ const dependsOnPrd = findByRef<PrdRow>(db, "prds", dependsOnRef);
755
+ if (!prd || !dependsOnPrd) throw new Error("PRD not found");
756
+
757
+ db.query(
758
+ "DELETE FROM prd_dependencies WHERE prd_id = ? AND depends_on_prd_id = ?",
759
+ ).run(prd.id, dependsOnPrd.id);
760
+ } else if (entityType === "E") {
739
761
  const epic = findByRef<EpicRow>(db, "epics", ref);
740
762
  const dependsOnEpic = findByRef<EpicRow>(db, "epics", dependsOnRef);
741
763
  if (!epic || !dependsOnEpic) throw new Error("Epic not found");
@@ -758,7 +780,20 @@ export class LocalAdapter implements BackendAdapter {
758
780
  const db = getDb();
759
781
  const entityType = getEntityType(ref);
760
782
 
761
- if (entityType === "E") {
783
+ if (entityType === "P") {
784
+ const prd = findByRef<PrdRow>(db, "prds", ref);
785
+ if (!prd) return [];
786
+
787
+ const deps = db
788
+ .query(
789
+ `SELECT p.ref FROM prd_dependencies pd
790
+ JOIN prds p ON pd.depends_on_prd_id = p.id
791
+ WHERE pd.prd_id = ?`,
792
+ )
793
+ .all(prd.id) as { ref: string }[];
794
+
795
+ return deps.map((d) => d.ref);
796
+ } else if (entityType === "E") {
762
797
  const epic = findByRef<EpicRow>(db, "epics", ref);
763
798
  if (!epic) return [];
764
799
 
@@ -77,6 +77,15 @@ CREATE TABLE IF NOT EXISTS task_dependencies (
77
77
  FOREIGN KEY (depends_on_task_id) REFERENCES tasks(id)
78
78
  );
79
79
 
80
+ -- PRD Dependencies
81
+ CREATE TABLE IF NOT EXISTS prd_dependencies (
82
+ prd_id TEXT NOT NULL,
83
+ depends_on_prd_id TEXT NOT NULL,
84
+ PRIMARY KEY (prd_id, depends_on_prd_id),
85
+ FOREIGN KEY (prd_id) REFERENCES prds(id),
86
+ FOREIGN KEY (depends_on_prd_id) REFERENCES prds(id)
87
+ );
88
+
80
89
  -- Indexes for common queries
81
90
  CREATE INDEX IF NOT EXISTS idx_prds_project ON prds(project_id);
82
91
  CREATE INDEX IF NOT EXISTS idx_prds_status ON prds(status);
@@ -53,6 +53,7 @@ describe("CRUD MCP Tools", () => {
53
53
  expect(result.ref).toBeDefined();
54
54
  expect(result.status).toBe("DRAFT");
55
55
  expect(result.id).toBeDefined();
56
+ expect(result.dependencies).toEqual([]);
56
57
  });
57
58
 
58
59
  test("creates PRD with optional fields", async () => {
@@ -64,6 +65,17 @@ describe("CRUD MCP Tools", () => {
64
65
  expect(result.description).toBe("A description");
65
66
  expect(result.tag).toBe("mvp");
66
67
  });
68
+
69
+ test("creates PRD with depends_on", async () => {
70
+ const prd1 = (await createPrdTool.handler({ title: "PRD 1" })) as any;
71
+ const prd2 = (await createPrdTool.handler({ title: "PRD 2" })) as any;
72
+ const prd3 = (await createPrdTool.handler({
73
+ title: "PRD 3",
74
+ depends_on: [prd1.ref, prd2.ref],
75
+ })) as any;
76
+
77
+ expect(prd3.dependencies).toEqual([prd1.ref, prd2.ref]);
78
+ });
67
79
  });
68
80
 
69
81
  describe("create_epic", () => {
@@ -77,6 +89,7 @@ describe("CRUD MCP Tools", () => {
77
89
  expect(epic.title).toBe("Test Epic");
78
90
  expect(epic.ref).toBeDefined();
79
91
  expect(epic.status).toBe("PENDING");
92
+ expect(epic.dependencies).toEqual([]);
80
93
  });
81
94
 
82
95
  test("creates epic with acceptance criteria", async () => {
@@ -94,6 +107,25 @@ describe("CRUD MCP Tools", () => {
94
107
  createEpicTool.handler({ prd_ref: "INVALID-P999", title: "Test" }),
95
108
  ).rejects.toThrow("PRD not found");
96
109
  });
110
+
111
+ test("creates epic with depends_on", async () => {
112
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
113
+ const epic1 = (await createEpicTool.handler({
114
+ prd_ref: prd.ref,
115
+ title: "Epic 1",
116
+ })) as any;
117
+ const epic2 = (await createEpicTool.handler({
118
+ prd_ref: prd.ref,
119
+ title: "Epic 2",
120
+ })) as any;
121
+ const epic3 = (await createEpicTool.handler({
122
+ prd_ref: prd.ref,
123
+ title: "Epic 3",
124
+ depends_on: [epic1.ref, epic2.ref],
125
+ })) as any;
126
+
127
+ expect(epic3.dependencies).toEqual([epic1.ref, epic2.ref]);
128
+ });
97
129
  });
98
130
 
99
131
  describe("create_task", () => {
@@ -111,6 +143,7 @@ describe("CRUD MCP Tools", () => {
111
143
  expect(task.title).toBe("Test Task");
112
144
  expect(task.ref).toBeDefined();
113
145
  expect(task.priority).toBe("MEDIUM");
146
+ expect(task.dependencies).toEqual([]);
114
147
  });
115
148
 
116
149
  test("creates task with priority", async () => {
@@ -126,6 +159,29 @@ describe("CRUD MCP Tools", () => {
126
159
  })) as any;
127
160
  expect(task.priority).toBe("HIGH");
128
161
  });
162
+
163
+ test("creates task with depends_on", async () => {
164
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
165
+ const epic = (await createEpicTool.handler({
166
+ prd_ref: prd.ref,
167
+ title: "Test Epic",
168
+ })) as any;
169
+ const task1 = (await createTaskTool.handler({
170
+ epic_ref: epic.ref,
171
+ title: "Task 1",
172
+ })) as any;
173
+ const task2 = (await createTaskTool.handler({
174
+ epic_ref: epic.ref,
175
+ title: "Task 2",
176
+ })) as any;
177
+ const task3 = (await createTaskTool.handler({
178
+ epic_ref: epic.ref,
179
+ title: "Task 3",
180
+ depends_on: [task1.ref, task2.ref],
181
+ })) as any;
182
+
183
+ expect(task3.dependencies).toEqual([task1.ref, task2.ref]);
184
+ });
129
185
  });
130
186
 
131
187
  describe("update_entity", () => {
@@ -183,6 +239,19 @@ describe("CRUD MCP Tools", () => {
183
239
  const result = (await deleteEntityTool.handler({ ref: epic.ref })) as any;
184
240
  expect(result.cascade.tasks).toBe(2);
185
241
  });
242
+
243
+ test("cascade deletes PRD with dependencies", async () => {
244
+ const prd1 = (await createPrdTool.handler({ title: "PRD 1" })) as any;
245
+ const prd2 = (await createPrdTool.handler({ title: "PRD 2" })) as any;
246
+ await addDependencyTool.handler({
247
+ ref: prd2.ref,
248
+ depends_on_ref: prd1.ref,
249
+ });
250
+
251
+ const result = (await deleteEntityTool.handler({ ref: prd1.ref })) as any;
252
+ expect(result.deleted).toBe(prd1.ref);
253
+ expect(result.cascade.dependencies).toBe(1);
254
+ });
186
255
  });
187
256
 
188
257
  describe("dependencies", () => {
@@ -210,6 +279,26 @@ describe("CRUD MCP Tools", () => {
210
279
  expect(removeResult.success).toBe(true);
211
280
  });
212
281
 
282
+ test("adds and removes PRD dependency", async () => {
283
+ const prd1 = (await createPrdTool.handler({ title: "PRD 1" })) as any;
284
+ const prd2 = (await createPrdTool.handler({ title: "PRD 2" })) as any;
285
+
286
+ const addResult = (await addDependencyTool.handler({
287
+ ref: prd2.ref,
288
+ depends_on_ref: prd1.ref,
289
+ })) as any;
290
+ expect(addResult.success).toBe(true);
291
+ expect(addResult.ref).toBe(prd2.ref);
292
+ expect(addResult.depends_on).toBe(prd1.ref);
293
+
294
+ const removeResult = (await removeDependencyTool.handler({
295
+ ref: prd2.ref,
296
+ depends_on_ref: prd1.ref,
297
+ })) as any;
298
+ expect(removeResult.success).toBe(true);
299
+ expect(removeResult.removed_dependency).toBe(prd1.ref);
300
+ });
301
+
213
302
  test("prevents self-dependency", async () => {
214
303
  const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
215
304
  const epic = (await createEpicTool.handler({
@@ -221,6 +310,26 @@ describe("CRUD MCP Tools", () => {
221
310
  addDependencyTool.handler({ ref: epic.ref, depends_on_ref: epic.ref }),
222
311
  ).rejects.toThrow("depend on itself");
223
312
  });
313
+
314
+ test("prevents PRD self-dependency", async () => {
315
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
316
+
317
+ await expect(
318
+ addDependencyTool.handler({ ref: prd.ref, depends_on_ref: prd.ref }),
319
+ ).rejects.toThrow("depend on itself");
320
+ });
321
+
322
+ test("prevents cross-type dependencies between PRD and Epic", async () => {
323
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
324
+ const epic = (await createEpicTool.handler({
325
+ prd_ref: prd.ref,
326
+ title: "Epic",
327
+ })) as any;
328
+
329
+ await expect(
330
+ addDependencyTool.handler({ ref: prd.ref, depends_on_ref: epic.ref }),
331
+ ).rejects.toThrow("same type");
332
+ });
224
333
  });
225
334
 
226
335
  describe("criteria", () => {
@@ -114,6 +114,75 @@ describe("Query MCP Tools", () => {
114
114
  expect(result.dependencies[0]).toBe(epic1.ref);
115
115
  });
116
116
 
117
+ test("returns dependencies by default for PRD without include", async () => {
118
+ const prd1 = (await createPrdTool.handler({ title: "PRD 1" })) as any;
119
+ const prd2 = (await createPrdTool.handler({ title: "PRD 2" })) as any;
120
+ await addDependencyTool.handler({
121
+ ref: prd2.ref,
122
+ depends_on_ref: prd1.ref,
123
+ });
124
+
125
+ const result = (await getEntityTool.handler({
126
+ ref: prd2.ref,
127
+ })) as any;
128
+
129
+ expect(result.dependencies).toBeDefined();
130
+ expect(result.dependencies.length).toBe(1);
131
+ expect(result.dependencies[0]).toBe(prd1.ref);
132
+ });
133
+
134
+ test("returns dependencies by default for Epic without include", async () => {
135
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
136
+ const epic1 = (await createEpicTool.handler({
137
+ prd_ref: prd.ref,
138
+ title: "Epic 1",
139
+ })) as any;
140
+ const epic2 = (await createEpicTool.handler({
141
+ prd_ref: prd.ref,
142
+ title: "Epic 2",
143
+ })) as any;
144
+ await addDependencyTool.handler({
145
+ ref: epic2.ref,
146
+ depends_on_ref: epic1.ref,
147
+ });
148
+
149
+ const result = (await getEntityTool.handler({
150
+ ref: epic2.ref,
151
+ })) as any;
152
+
153
+ expect(result.dependencies).toBeDefined();
154
+ expect(result.dependencies.length).toBe(1);
155
+ expect(result.dependencies[0]).toBe(epic1.ref);
156
+ });
157
+
158
+ test("returns dependencies by default for Task without include", async () => {
159
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
160
+ const epic = (await createEpicTool.handler({
161
+ prd_ref: prd.ref,
162
+ title: "Test Epic",
163
+ })) as any;
164
+ const task1 = (await createTaskTool.handler({
165
+ epic_ref: epic.ref,
166
+ title: "Task 1",
167
+ })) as any;
168
+ const task2 = (await createTaskTool.handler({
169
+ epic_ref: epic.ref,
170
+ title: "Task 2",
171
+ })) as any;
172
+ await addDependencyTool.handler({
173
+ ref: task2.ref,
174
+ depends_on_ref: task1.ref,
175
+ });
176
+
177
+ const result = (await getEntityTool.handler({
178
+ ref: task2.ref,
179
+ })) as any;
180
+
181
+ expect(result.dependencies).toBeDefined();
182
+ expect(result.dependencies.length).toBe(1);
183
+ expect(result.dependencies[0]).toBe(task1.ref);
184
+ });
185
+
117
186
  test("throws error for invalid ref", async () => {
118
187
  await expect(
119
188
  getEntityTool.handler({ ref: "INVALID-P999" }),
@@ -8,6 +8,7 @@ const inputSchema = z.object({
8
8
  title: z.string().min(1, "Title is required"),
9
9
  description: z.string().optional(),
10
10
  acceptance_criteria: z.array(z.string()).optional(),
11
+ depends_on: z.array(z.string()).optional(),
11
12
  });
12
13
 
13
14
  async function handler(input: unknown) {
@@ -23,13 +24,21 @@ async function handler(input: unknown) {
23
24
 
24
25
  const criteriaCount = parsed.acceptance_criteria?.length || 0;
25
26
 
26
- return { ...toMcpEpic(epic), criteria_count: criteriaCount };
27
+ const dependencies: string[] = [];
28
+ if (parsed.depends_on && parsed.depends_on.length > 0) {
29
+ for (const depRef of parsed.depends_on) {
30
+ await adapter.addDependency(epic.ref, depRef);
31
+ dependencies.push(depRef);
32
+ }
33
+ }
34
+
35
+ return { ...toMcpEpic(epic), criteria_count: criteriaCount, dependencies };
27
36
  }
28
37
 
29
38
  export const createEpicTool: ToolDefinition = {
30
39
  name: "create_epic",
31
40
  description:
32
- "Create a new Epic under a PRD. Required: prd_ref (e.g., 'FLUX-P1'), title. Optional: description, acceptance_criteria (string array). Returns {id, ref, title, status, criteria_count}. Status starts as PENDING.",
41
+ "Create a new Epic under a PRD. Required: prd_ref (e.g., 'FLUX-P1'), title. Optional: description, acceptance_criteria (string array), depends_on (array of Epic refs this Epic depends on, e.g., ['FLUX-E1']). Returns {id, ref, title, status, criteria_count, dependencies}. Status starts as PENDING.",
33
42
  inputSchema,
34
43
  handler,
35
44
  };
@@ -7,6 +7,7 @@ const inputSchema = z.object({
7
7
  title: z.string().min(1, "Title is required"),
8
8
  description: z.string().optional(),
9
9
  tag: z.string().optional(),
10
+ depends_on: z.array(z.string()).optional(),
10
11
  });
11
12
 
12
13
  async function handler(input: unknown) {
@@ -19,13 +20,21 @@ async function handler(input: unknown) {
19
20
  tag: parsed.tag,
20
21
  });
21
22
 
22
- return toMcpPrd(prd);
23
+ const dependencies: string[] = [];
24
+ if (parsed.depends_on && parsed.depends_on.length > 0) {
25
+ for (const depRef of parsed.depends_on) {
26
+ await adapter.addDependency(prd.ref, depRef);
27
+ dependencies.push(depRef);
28
+ }
29
+ }
30
+
31
+ return { ...toMcpPrd(prd), dependencies };
23
32
  }
24
33
 
25
34
  export const createPrdTool: ToolDefinition = {
26
35
  name: "create_prd",
27
36
  description:
28
- "Create a new PRD (Product Requirements Document). Required: title. Optional: description, tag. Returns the created PRD with {id, ref, title, description, status, tag}. Status starts as DRAFT.",
37
+ "Create a new PRD (Product Requirements Document). Required: title. Optional: description, tag, depends_on (array of PRD refs this PRD depends on, e.g., ['FLUX-P1', 'FLUX-P2']). Returns the created PRD with {id, ref, title, description, status, tag, dependencies}. Status starts as DRAFT.",
29
38
  inputSchema,
30
39
  handler,
31
40
  };
@@ -10,6 +10,7 @@ const inputSchema = z.object({
10
10
  description: z.string().optional(),
11
11
  priority: z.enum(["LOW", "MEDIUM", "HIGH"]).optional().default("MEDIUM"),
12
12
  acceptance_criteria: z.array(z.string()).optional(),
13
+ depends_on: z.array(z.string()).optional(),
13
14
  });
14
15
 
15
16
  async function handler(input: unknown) {
@@ -26,13 +27,21 @@ async function handler(input: unknown) {
26
27
 
27
28
  const criteriaCount = parsed.acceptance_criteria?.length || 0;
28
29
 
29
- return { ...toMcpTask(task), criteria_count: criteriaCount };
30
+ const dependencies: string[] = [];
31
+ if (parsed.depends_on && parsed.depends_on.length > 0) {
32
+ for (const depRef of parsed.depends_on) {
33
+ await adapter.addDependency(task.ref, depRef);
34
+ dependencies.push(depRef);
35
+ }
36
+ }
37
+
38
+ return { ...toMcpTask(task), criteria_count: criteriaCount, dependencies };
30
39
  }
31
40
 
32
41
  export const createTaskTool: ToolDefinition = {
33
42
  name: "create_task",
34
43
  description:
35
- "Create a new Task under an Epic. Required: epic_ref (e.g., 'FLUX-E1'), title. Optional: description, priority (LOW|MEDIUM|HIGH, default MEDIUM), acceptance_criteria (string array). Returns {id, ref, title, status, priority, criteria_count}.",
44
+ "Create a new Task under an Epic. Required: epic_ref (e.g., 'FLUX-E1'), title. Optional: description, priority (LOW|MEDIUM|HIGH, default MEDIUM), acceptance_criteria (string array), depends_on (array of Task refs this Task depends on, e.g., ['FLUX-T1']). Returns {id, ref, title, status, priority, criteria_count, dependencies}.",
36
45
  inputSchema,
37
46
  handler,
38
47
  };
@@ -41,7 +41,7 @@ async function removeDependencyHandler(input: unknown) {
41
41
  export const addDependencyTool: ToolDefinition = {
42
42
  name: "add_dependency",
43
43
  description:
44
- "Add a dependency between two entities of the same type. Required: ref (depends on depends_on_ref), depends_on_ref. Both must be Epics (FLUX-E*) or both Tasks (FLUX-T*). Validates no circular dependencies. Returns {success, ref, depends_on}.",
44
+ "Add a dependency between two entities of the same type. Required: ref (depends on depends_on_ref), depends_on_ref. Both must be PRDs (FLUX-P*), Epics (FLUX-E*), or Tasks (FLUX-T*). Validates no circular dependencies. Returns {success, ref, depends_on}.",
45
45
  inputSchema: addDependencySchema,
46
46
  handler: addDependencyHandler,
47
47
  };
@@ -49,7 +49,7 @@ export const addDependencyTool: ToolDefinition = {
49
49
  export const removeDependencyTool: ToolDefinition = {
50
50
  name: "remove_dependency",
51
51
  description:
52
- "Remove a dependency between two entities. Required: ref, depends_on_ref. Both must be same type (Epic or Task). Returns {success, ref, removed_dependency}.",
52
+ "Remove a dependency between two entities. Required: ref, depends_on_ref. Both must be same type (PRD, Epic, or Task). Returns {success, ref, removed_dependency}.",
53
53
  inputSchema: removeDependencySchema,
54
54
  handler: removeDependencyHandler,
55
55
  };