@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.
- package/CHANGELOG.md +13 -0
- package/README.md +4 -2
- package/capabilities/workspace-tasks/GUIDE.md +29 -0
- package/dist/capabilities.js +6 -0
- package/dist/capability-registry.js +82 -21
- package/dist/composite-activity.js +1 -1
- package/dist/composite-workspaces.js +31 -10
- package/dist/git-worktrees.js +22 -0
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/server.js +290 -60
- package/dist/subagents/sessions/capability.js +3 -0
- package/dist/workspace-store.js +25 -0
- package/dist/workspace-tasks.js +351 -0
- package/dist/workspaces.js +95 -13
- package/docs/chatgpt-coding-workflow.md +17 -2
- package/docs/configuration.md +21 -6
- package/package.json +2 -2
- package/scripts/debug/accept.mjs +255 -2
package/scripts/debug/accept.mjs
CHANGED
|
@@ -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
|
-
"
|
|
649
|
-
`${
|
|
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);
|