AtlatestRepositoryfolio

folio / tree / testtest-task-move-batch.sgl

1;;; folio/task-move-batch — bulk triage in one call.
2;;;
3;;; Triaging 227 inbox items on 2026-08-02 cost 226 individual MCP round-trips,
4;;; hand-batched into groups. That cost is a large part of why the inbox went
5;;; untriaged from April to August: a full triage was something nobody would do
6;;; routinely. This is a workflow fix, not an optimisation.
7;;;
8;;; Partial failures are reported per id rather than swallowed — a batch that
9;;; hid them would be worse than the individual calls it replaces.
11(import (sigil test)
12 (sigil struct)
13 (sigil string)
14 (sigil dict)
15 (sigil fs)
16 (folio store)
17 (folio task)
18 (folio inbox)
19 (folio project)
20 (folio tools))
22(test-group "task-move-batch"
23 (test "moves many ids to one project and reports each"
24 (call-with-temp-directory
25 (lambda (dir)
26 (let* ((store (make-folio-store dir))
27 (file (project-create! store "Target" "Goal" #f))
28 (a (inbox-add! store "Item A" '()))
29 (b (inbox-add! store "Item B" '()))
30 (c (inbox-add! store "Item C" '()))
31 (handler (make-tool-task-move-batch store))
32 (result (handler (dict ids: (list (folio-task-id a)
33 (folio-task-id b)
34 (folio-task-id c))
35 project: "target"))))
36 (assert-true (string-contains? result (folio-task-id a)))
37 (assert-true (string-contains? result (folio-task-id b)))
38 (assert-true (string-contains? result (folio-task-id c)))
39 (assert-equal 0 (length (read-inbox store)))
40 (assert-equal 3 (length (folio-project-tasks (read-project file))))))))
42 (test "fans out across several projects via {id, project} pairs"
43 (call-with-temp-directory
44 (lambda (dir)
45 (let* ((store (make-folio-store dir))
46 (f1 (project-create! store "One" "Goal" #f))
47 (f2 (project-create! store "Two" "Goal" #f))
48 (a (inbox-add! store "Item A" '()))
49 (b (inbox-add! store "Item B" '()))
50 (handler (make-tool-task-move-batch store)))
51 (handler (dict moves: (list (dict id: (folio-task-id a) project: "one")
52 (dict id: (folio-task-id b) project: "two"))))
53 (assert-equal 1 (length (folio-project-tasks (read-project f1))))
54 (assert-equal 1 (length (folio-project-tasks (read-project f2))))
55 (assert-equal 0 (length (read-inbox store)))))))
57 (test "a moves entry with no project sends that task to the inbox"
58 (call-with-temp-directory
59 (lambda (dir)
60 (let* ((store (make-folio-store dir))
61 (f1 (project-create! store "One" "Goal" #f))
62 (src (project-create! store "Source" "Goal" #f))
63 (a (project-add-task! src "Stay" '()))
64 (b (project-add-task! src "Go home" '()))
65 (handler (make-tool-task-move-batch store)))
66 (handler (dict moves: (list (dict id: (folio-task-id a) project: "one")
67 (dict id: (folio-task-id b)))))
68 (assert-equal 1 (length (folio-project-tasks (read-project f1))))
69 (assert-equal 1 (length (read-inbox store)))
70 (assert-equal (folio-task-id b)
71 (folio-task-id (car (read-inbox store))))))))
73 (test "reports the resulting id, which is the id asked for"
74 (call-with-temp-directory
75 (lambda (dir)
76 (let* ((store (make-folio-store dir))
77 (file (project-create! store "Target" "Goal" #f))
78 (a (inbox-add! store "Item A" '()))
79 (handler (make-tool-task-move-batch store))
80 (result (handler (dict ids: (list (folio-task-id a))
81 project: "target")))
82 (landed (car (folio-project-tasks (read-project file)))))
83 (assert-equal (folio-task-id a) (folio-task-id landed))
84 (assert-true (string-contains? result (folio-task-id landed)))))))
86 (test "a partial failure is visible per id and does not stop the others"
87 (call-with-temp-directory
88 (lambda (dir)
89 (let* ((store (make-folio-store dir))
90 (file (project-create! store "Target" "Goal" #f))
91 (a (inbox-add! store "Item A" '()))
92 (handler (make-tool-task-move-batch store))
93 (result (handler (dict ids: (list (folio-task-id a) "t-dead")
94 project: "target"))))
95 (assert-true (string-contains? result "t-dead"))
96 (assert-true (string-contains? result "not found"))
97 ;; the good one still moved
98 (assert-equal 1 (length (folio-project-tasks (read-project file))))))))
100 ;; The header is a claim about what happened and must not contradict the
101 ;; lines below it: "Moved 2 task(s)" above two failures reads as success.
102 (test "the header counts successes, not attempts"
103 (call-with-temp-directory
104 (lambda (dir)
105 (let* ((store (make-folio-store dir))
106 (file (project-create! store "Target" "Goal" #f))
107 (handler (make-tool-task-move-batch store))
108 (result (handler (dict ids: (list "t-dead" "t-beef")
109 project: "target"))))
110 (assert-true (string-contains? result "Moved 0 of 2"))))))
112 (test "an unknown target project fails every id without moving anything"
113 (call-with-temp-directory
114 (lambda (dir)
115 (let* ((store (make-folio-store dir))
116 (a (inbox-add! store "Item A" '()))
117 (handler (make-tool-task-move-batch store))
118 (result (handler (dict ids: (list (folio-task-id a))
119 project: "nonexistent"))))
120 (assert-true (string-contains? result "not found"))
121 ;; put back where it was, not lost
122 (assert-equal 1 (length (read-inbox store)))
123 (assert-equal (folio-task-id a)
124 (folio-task-id (car (read-inbox store))))))))
126 (test "moves to the inbox when no project is given"
127 (call-with-temp-directory
128 (lambda (dir)
129 (let* ((store (make-folio-store dir))
130 (file (project-create! store "Source" "Goal" #f))
131 (a (project-add-task! file "Send back" '()))
132 (handler (make-tool-task-move-batch store)))
133 (handler (dict ids: (list (folio-task-id a))))
134 (assert-equal 1 (length (read-inbox store)))
135 (assert-equal (folio-task-id a)
136 (folio-task-id (car (read-inbox store))))))))
138 (test "an empty id list is not an error"
139 (call-with-temp-directory
140 (lambda (dir)
141 (let* ((store (make-folio-store dir))
142 (handler (make-tool-task-move-batch store))
143 (result (handler (dict ids: '() project: "target"))))
144 (assert-true (string? result))))))
146 (test "a missing ids argument is not an error"
147 (call-with-temp-directory
148 (lambda (dir)
149 (let* ((store (make-folio-store dir))
150 (handler (make-tool-task-move-batch store))
151 (result (handler (dict))))
152 (assert-true (string? result)))))))