@akira-tl/forgerelay 0.8.1 → 0.8.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.
@@ -265,6 +265,7 @@ try {
265
265
  "hooks.lifecycle",
266
266
  "capability-guides.read",
267
267
  "code.intelligence",
268
+ "workspace.tasks",
268
269
  "batch.execute",
269
270
  ...(process.platform === "linux" ? ["artifact.native-download"] : []),
270
271
  "ui.mcp-app",
@@ -276,12 +277,55 @@ try {
276
277
  "hooks.check",
277
278
  "review.changes",
278
279
  "code.intelligence",
280
+ "workspace.tasks",
279
281
  "batch.execute",
280
282
  ...(process.platform === "linux" ? ["artifact.download"] : []),
281
283
  ]);
282
284
  assert.equal(capabilityCatalog[0].available, true);
283
285
  assert.equal(capabilityCatalog[0].guide.name, "lifecycle-hooks");
284
286
 
287
+ const checkoutTaskStatePath = join(stateDir, "workspaces", workspaceId, "tasks.json");
288
+ assert.ok(existsSync(checkoutTaskStatePath));
289
+ const checkoutTaskList = callTool(oauth.accessToken, sessionId, 130, "capability", {
290
+ workspaceId,
291
+ name: "workspace.tasks",
292
+ action: "run",
293
+ arguments: { operation: "list.create", name: "7677 release tasks" },
294
+ });
295
+ assert.equal(checkoutTaskList.isError, undefined);
296
+ const checkoutListId = checkoutTaskList.structuredContent.result.lists[0].id;
297
+ const checkoutTask = callTool(oauth.accessToken, sessionId, 131, "capability", {
298
+ workspaceId,
299
+ name: "workspace.tasks",
300
+ action: "run",
301
+ arguments: {
302
+ operation: "task.create",
303
+ listId: checkoutListId,
304
+ subject: "Verify v0.8.3 Task persistence",
305
+ content: "created through real MCP",
306
+ status: "in_progress",
307
+ },
308
+ });
309
+ assert.equal(checkoutTask.isError, undefined);
310
+ const checkoutTaskId = checkoutTask.structuredContent.result.lists[0].tasks[0].id;
311
+ const taskFingerprintBeforeExternal = checkoutTask.structuredContent.result.fingerprint;
312
+ const externalTaskState = JSON.parse(readFileSync(checkoutTaskStatePath, "utf8"));
313
+ externalTaskState.lists[0].tasks[0].content = "reloaded external task edit";
314
+ writeFileSync(checkoutTaskStatePath, `${JSON.stringify(externalTaskState, null, 2)}\n`);
315
+ const reloadedCheckoutTasks = callTool(oauth.accessToken, sessionId, 132, "capability", {
316
+ workspaceId,
317
+ name: "workspace.tasks",
318
+ action: "run",
319
+ arguments: { operation: "get" },
320
+ });
321
+ assert.equal(reloadedCheckoutTasks.isError, undefined);
322
+ assert.equal(reloadedCheckoutTasks.structuredContent.result.lists[0].tasks[0].id, checkoutTaskId);
323
+ assert.equal(
324
+ reloadedCheckoutTasks.structuredContent.result.lists[0].tasks[0].content,
325
+ "reloaded external task edit",
326
+ );
327
+ assert.notEqual(reloadedCheckoutTasks.structuredContent.result.fingerprint, taskFingerprintBeforeExternal);
328
+
285
329
  const repeatedOpen = callTool(oauth.accessToken, sessionId, 84, "open_workspace", {
286
330
  path: checkoutWorkspace,
287
331
  newWorkspace: true,
@@ -312,6 +356,13 @@ try {
312
356
  assert.equal(closedWorkspace.isError, undefined);
313
357
  assert.equal(closedWorkspace.structuredContent.workspaceId, workspaceId);
314
358
  assert.equal(closedWorkspace.structuredContent.action, "close");
359
+ const closedCheckoutTasks = callTool(oauth.accessToken, sessionId, 133, "capability", {
360
+ workspaceId,
361
+ name: "workspace.tasks",
362
+ action: "run",
363
+ arguments: { operation: "get" },
364
+ });
365
+ assert.equal(closedCheckoutTasks.isError, true);
315
366
 
316
367
  const closedInventory = callTool(oauth.accessToken, sessionId, 87, "open_workspace", {
317
368
  action: "list",
@@ -336,12 +387,34 @@ try {
336
387
  resumedOriginal.structuredContent.contextFingerprint,
337
388
  opened.structuredContent.contextFingerprint,
338
389
  );
390
+ const resumedCheckoutTasks = callTool(oauth.accessToken, sessionId, 134, "capability", {
391
+ workspaceId,
392
+ name: "workspace.tasks",
393
+ action: "run",
394
+ arguments: { operation: "get" },
395
+ });
396
+ assert.equal(resumedCheckoutTasks.isError, undefined);
397
+ assert.equal(resumedCheckoutTasks.structuredContent.result.lists[0].tasks[0].id, checkoutTaskId);
398
+ assert.equal(
399
+ resumedCheckoutTasks.structuredContent.result.lists[0].tasks[0].content,
400
+ "reloaded external task edit",
401
+ );
402
+ assert.equal(resumedOriginal.structuredContent.contextFingerprint, opened.structuredContent.contextFingerprint);
339
403
 
340
404
  const deleteOpened = callTool(oauth.accessToken, sessionId, 91, "open_workspace", {
341
405
  path: lifecycleDeleteWorkspace,
342
406
  context: "none",
343
407
  }, { "openai/session": "acceptance-workspace-delete" });
344
408
  const deleteWorkspaceId = deleteOpened.structuredContent.workspaceId;
409
+ const deleteTaskStatePath = join(stateDir, "workspaces", deleteWorkspaceId, "tasks.json");
410
+ const deleteTaskList = callTool(oauth.accessToken, sessionId, 135, "capability", {
411
+ workspaceId: deleteWorkspaceId,
412
+ name: "workspace.tasks",
413
+ action: "run",
414
+ arguments: { operation: "list.create", name: "delete with workspace" },
415
+ });
416
+ assert.equal(deleteTaskList.isError, undefined);
417
+ assert.ok(existsSync(deleteTaskStatePath));
345
418
  const deleteClosed = callTool(oauth.accessToken, sessionId, 92, "close_workspace", {
346
419
  workspaceId: deleteWorkspaceId,
347
420
  });
@@ -353,12 +426,17 @@ try {
353
426
  assert.equal(deletedWorkspace.isError, undefined);
354
427
  assert.equal(deletedWorkspace.structuredContent.workspaceId, deleteWorkspaceId);
355
428
  assert.equal(deletedWorkspace.structuredContent.action, "delete");
429
+ assert.equal(existsSync(deleteTaskStatePath), false);
356
430
  assert.equal(readFileSync(join(lifecycleDeleteWorkspace, "keep.txt"), "utf8"), "keep checkout files\n");
357
431
  const deletedInventory = callTool(oauth.accessToken, sessionId, 94, "open_workspace", {
358
432
  action: "list",
359
433
  workspaceId: deleteWorkspaceId,
360
434
  });
361
435
  assert.equal(deletedInventory.structuredContent.workspaces.length, 0);
436
+ pass(
437
+ "workspace tasks",
438
+ `${workspaceId} create -> external reload -> close/reopen; explicit delete removed ${deleteWorkspaceId} Task state`,
439
+ );
362
440
 
363
441
  pass(
364
442
  "workspace lifecycle + inventory",
@@ -440,6 +518,7 @@ try {
440
518
  "host-integration",
441
519
  "shell-processes",
442
520
  "code-intelligence",
521
+ "workspace-tasks",
443
522
  "batch-execution",
444
523
  ]);
445
524
  const hooksGuide = callTool(oauth.accessToken, sessionId, 78, "read", {
@@ -644,9 +723,183 @@ try {
644
723
  readFileSync(join(gitProject, "feature.txt"), "utf8").replace(/\r\n/g, "\n"),
645
724
  "debug worktree acceptance\n",
646
725
  );
726
+ const closedWorktreeInventory = callTool(oauth.accessToken, sessionId, 110, "open_workspace", {
727
+ action: "list",
728
+ workspaceId: worktreeWorkspaceId,
729
+ });
730
+ assert.equal(closedWorktreeInventory.structuredContent.workspaces.length, 1);
731
+ assert.equal(closedWorktreeInventory.structuredContent.workspaces[0].state, "closed");
732
+
733
+ const reopenedWorktree = callTool(oauth.accessToken, sessionId, 111, "open_workspace", {
734
+ workspaceId: worktreeWorkspaceId,
735
+ context: "none",
736
+ });
737
+ assert.equal(reopenedWorktree.structuredContent.workspaceId, worktreeWorkspaceId);
738
+ const reopenedWorktreePath = reopenedWorktree.structuredContent.worktree.path;
739
+ assert.notEqual(reopenedWorktreePath, managedWorktreePath);
740
+ assert.ok(existsSync(reopenedWorktreePath));
741
+
742
+ callTool(oauth.accessToken, sessionId, 112, "write", {
743
+ workspaceId: worktreeWorkspaceId,
744
+ path: "delete-feature.txt",
745
+ content: "debug worktree delete acceptance\n",
746
+ });
747
+ const deletedWorktree = callTool(oauth.accessToken, sessionId, 113, "close_workspace", {
748
+ workspaceId: worktreeWorkspaceId,
749
+ action: "delete",
750
+ commitMessage: "test(debug): verify 7677 worktree delete lifecycle",
751
+ });
752
+ assert.equal(deletedWorktree.structuredContent.action, "delete");
753
+ assert.equal(existsSync(reopenedWorktreePath), false);
754
+ assert.equal(
755
+ readFileSync(join(gitProject, "delete-feature.txt"), "utf8").replace(/\r\n/g, "\n"),
756
+ "debug worktree delete acceptance\n",
757
+ );
758
+ const deletedWorktreeInventory = callTool(oauth.accessToken, sessionId, 114, "open_workspace", {
759
+ action: "list",
760
+ workspaceId: worktreeWorkspaceId,
761
+ });
762
+ assert.equal(deletedWorktreeInventory.structuredContent.workspaces.length, 0);
763
+ pass(
764
+ "managed worktree lifecycle",
765
+ `${worktreeWorkspaceId} close -> closed inventory -> same-id reopen with fresh backing -> safe delete`,
766
+ );
767
+
768
+ callTool(oauth.accessToken, sessionId, 115, "write", {
769
+ workspaceId,
770
+ path: "composite-sentinel.txt",
771
+ content: "debug composite member acceptance\n",
772
+ });
773
+ const compositeOpened = callTool(oauth.accessToken, sessionId, 116, "open_workspace", {
774
+ kind: "composite",
775
+ name: "debug-lifecycle-composite",
776
+ context: "none",
777
+ });
778
+ const compositeWorkspaceId = compositeOpened.structuredContent.workspaceId;
779
+ assert.deepEqual(
780
+ compositeOpened.structuredContent.capabilityCatalog.map((entry) => entry.name),
781
+ ["workspace.tasks"],
782
+ );
783
+ const compositeTaskStatePath = join(stateDir, "workspaces", compositeWorkspaceId, "tasks.json");
784
+ assert.ok(existsSync(compositeTaskStatePath));
785
+ const compositeTaskList = callTool(oauth.accessToken, sessionId, 136, "capability", {
786
+ workspaceId: compositeWorkspaceId,
787
+ name: "workspace.tasks",
788
+ action: "run",
789
+ arguments: { operation: "list.create", name: "Composite release tasks" },
790
+ });
791
+ assert.equal(compositeTaskList.isError, undefined);
792
+ const compositeTaskListId = compositeTaskList.structuredContent.result.lists[0].id;
793
+ const compositeTask = callTool(oauth.accessToken, sessionId, 137, "capability", {
794
+ workspaceId: compositeWorkspaceId,
795
+ name: "workspace.tasks",
796
+ action: "run",
797
+ arguments: {
798
+ operation: "task.create",
799
+ listId: compositeTaskListId,
800
+ subject: "Preserve Composite Task state",
801
+ content: "Composite-owned state",
802
+ },
803
+ });
804
+ assert.equal(compositeTask.isError, undefined);
805
+ const compositeTaskId = compositeTask.structuredContent.result.lists[0].tasks[0].id;
806
+ callTool(oauth.accessToken, sessionId, 117, "open_workspace", {
807
+ action: "member",
808
+ workspaceId: compositeWorkspaceId,
809
+ memberAction: "add",
810
+ member: {
811
+ name: "code",
812
+ purpose: "Debug lifecycle member",
813
+ workspaceId,
814
+ },
815
+ });
816
+ const memberScopedCompositeTasks = callTool(oauth.accessToken, sessionId, 138, "capability", {
817
+ workspaceId: compositeWorkspaceId,
818
+ member: "code",
819
+ name: "workspace.tasks",
820
+ action: "run",
821
+ arguments: { operation: "get" },
822
+ });
823
+ assert.equal(memberScopedCompositeTasks.isError, true);
824
+ const closedComposite = callTool(oauth.accessToken, sessionId, 118, "close_workspace", {
825
+ workspaceId: compositeWorkspaceId,
826
+ });
827
+ assert.equal(closedComposite.structuredContent.action, "close");
828
+ assert.equal(closedComposite.structuredContent.status, "closed");
829
+ assert.equal(closedComposite.structuredContent.dissolved, false);
830
+ const closedCompositeInventory = callTool(oauth.accessToken, sessionId, 119, "open_workspace", {
831
+ action: "list",
832
+ kind: "composite",
833
+ workspaceId: compositeWorkspaceId,
834
+ status: "closed",
835
+ });
836
+ assert.equal(closedCompositeInventory.structuredContent.compositeWorkspaces.length, 1);
837
+ assert.equal(closedCompositeInventory.structuredContent.compositeWorkspaces[0].state, "closed");
838
+ const closedCompositeRead = callTool(oauth.accessToken, sessionId, 120, "read", {
839
+ workspaceId: compositeWorkspaceId,
840
+ member: "code",
841
+ path: "composite-sentinel.txt",
842
+ });
843
+ assert.equal(closedCompositeRead.isError, true);
844
+ const closedCompositeTasks = callTool(oauth.accessToken, sessionId, 139, "capability", {
845
+ workspaceId: compositeWorkspaceId,
846
+ name: "workspace.tasks",
847
+ action: "run",
848
+ arguments: { operation: "get" },
849
+ });
850
+ assert.equal(closedCompositeTasks.isError, true);
851
+
852
+ const reopenedComposite = callTool(oauth.accessToken, sessionId, 121, "open_workspace", {
853
+ workspaceId: compositeWorkspaceId,
854
+ context: "none",
855
+ });
856
+ assert.equal(reopenedComposite.structuredContent.workspaceId, compositeWorkspaceId);
857
+ assert.equal(reopenedComposite.structuredContent.status, "active");
858
+ assert.equal(reopenedComposite.structuredContent.members[0].workspaceId, workspaceId);
859
+ const reopenedCompositeTasks = callTool(oauth.accessToken, sessionId, 140, "capability", {
860
+ workspaceId: compositeWorkspaceId,
861
+ name: "workspace.tasks",
862
+ action: "run",
863
+ arguments: { operation: "get" },
864
+ });
865
+ assert.equal(reopenedCompositeTasks.isError, undefined);
866
+ assert.equal(reopenedCompositeTasks.structuredContent.result.lists[0].tasks[0].id, compositeTaskId);
867
+ assert.equal(
868
+ reopenedCompositeTasks.structuredContent.result.lists[0].tasks[0].content,
869
+ "Composite-owned state",
870
+ );
871
+ const reopenedCompositeRead = callTool(oauth.accessToken, sessionId, 122, "read", {
872
+ workspaceId: compositeWorkspaceId,
873
+ member: "code",
874
+ path: "composite-sentinel.txt",
875
+ });
876
+ assert.match(reopenedCompositeRead.structuredContent.result, /debug composite member acceptance/);
877
+
878
+ const deletedComposite = callTool(oauth.accessToken, sessionId, 123, "close_workspace", {
879
+ workspaceId: compositeWorkspaceId,
880
+ action: "delete",
881
+ });
882
+ assert.equal(deletedComposite.structuredContent.action, "delete");
883
+ assert.equal(deletedComposite.structuredContent.dissolved, true);
884
+ assert.equal(existsSync(compositeTaskStatePath), false);
885
+ const memberAfterCompositeDelete = callTool(oauth.accessToken, sessionId, 124, "read", {
886
+ workspaceId,
887
+ path: "composite-sentinel.txt",
888
+ });
889
+ assert.match(memberAfterCompositeDelete.structuredContent.result, /debug composite member acceptance/);
890
+ const deletedCompositeInventory = callTool(oauth.accessToken, sessionId, 125, "open_workspace", {
891
+ action: "list",
892
+ kind: "composite",
893
+ workspaceId: compositeWorkspaceId,
894
+ });
895
+ assert.equal(deletedCompositeInventory.structuredContent.compositeWorkspaces.length, 0);
896
+ pass(
897
+ "Composite workspace tasks",
898
+ `${compositeWorkspaceId} self-owned Task state -> close/reopen -> delete cleanup; member-scoped Task access rejected`,
899
+ );
647
900
  pass(
648
- "managed worktree workspace close",
649
- `${closed.structuredContent.branch} -> ${closed.structuredContent.targetBranch}`,
901
+ "Composite lifecycle",
902
+ `${compositeWorkspaceId} close -> closed/non-routable -> same-id reopen -> delete; member Workspace preserved`,
650
903
  );
651
904
 
652
905
  exerciseReleaseTagHooks(oauth.accessToken, sessionId);