@wrongstack/tools 0.303.0 → 0.305.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.
- package/dist/builtin.d.ts +6 -0
- package/dist/builtin.js +715 -378
- package/dist/codebase-index/codebase-index-tool.d.ts +6 -0
- package/dist/codebase-index/index.js +112 -89
- package/dist/codebase-index/indexer.d.ts +6 -0
- package/dist/codebase-index/project-server.js +112 -89
- package/dist/codebase-index/schema.d.ts +11 -0
- package/dist/codebase-index/worker.js +112 -89
- package/dist/index.d.ts +3 -3
- package/dist/index.js +777 -397
- package/dist/kanban-contract-actions.d.ts +7 -0
- package/dist/kanban-task-inputs.d.ts +15 -2
- package/dist/kanban-tool-schema.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +32 -11
- package/dist/kanban.js +366 -245
- package/dist/pack.js +714 -378
- package/dist/plan.js +601 -289
- package/dist/read.js +112 -89
- package/dist/session-kanban.d.ts +86 -1
- package/dist/session-kanban.js +216 -46
- package/dist/task.js +601 -289
- package/dist/todo.js +601 -289
- package/dist/tool-tier.js +714 -378
- package/package.json +3 -3
package/dist/session-kanban.js
CHANGED
|
@@ -9,12 +9,17 @@ import {
|
|
|
9
9
|
mutateTasks
|
|
10
10
|
} from "@wrongstack/core/storage";
|
|
11
11
|
import { deserializeTaskGraph } from "@wrongstack/core/tasking";
|
|
12
|
-
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
12
|
+
import { formatTodosForModel, resolveWstackPaths } from "@wrongstack/core/utils";
|
|
13
13
|
import {
|
|
14
14
|
bridgeKanbanSupervisor,
|
|
15
|
+
compactSessionMirrorBoard,
|
|
15
16
|
createBoard,
|
|
17
|
+
DEFAULT_COLUMNS,
|
|
16
18
|
getBoard,
|
|
19
|
+
getDependencyReadinessIssues,
|
|
20
|
+
getKanbanOrchestrationSnapshot,
|
|
17
21
|
listBoards,
|
|
22
|
+
pruneSessionBoards,
|
|
18
23
|
removeBoard,
|
|
19
24
|
syncBoardFromTaskGraph,
|
|
20
25
|
touchKanbanPresence,
|
|
@@ -22,16 +27,14 @@ import {
|
|
|
22
27
|
} from "@wrongstack/kanban";
|
|
23
28
|
var SESSION_BOARD_TAG = "session-work";
|
|
24
29
|
var MIRROR_DISABLED_ENV = "WRONGSTACK_KANBAN_TASK_MIRROR";
|
|
25
|
-
var SESSION_KANBAN_COLUMNS =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{ id: "review", title: "Preview", order: 2, wipLimit: 0, color: "#7c3aed" },
|
|
29
|
-
{ id: "done", title: "Done", order: 3, wipLimit: 0, color: "#16a34a" }
|
|
30
|
-
];
|
|
30
|
+
var SESSION_KANBAN_COLUMNS = DEFAULT_COLUMNS.map((column) => ({
|
|
31
|
+
...column
|
|
32
|
+
}));
|
|
31
33
|
var boardQueue = /* @__PURE__ */ new Map();
|
|
32
34
|
var boardEnsures = /* @__PURE__ */ new Map();
|
|
33
35
|
var pendingMirrors = /* @__PURE__ */ new Map();
|
|
34
36
|
var activeMirrors = /* @__PURE__ */ new Set();
|
|
37
|
+
var mirrorFailures = /* @__PURE__ */ new Map();
|
|
35
38
|
var bindings = /* @__PURE__ */ new WeakMap();
|
|
36
39
|
var suppressedTodoMirrors = /* @__PURE__ */ new WeakSet();
|
|
37
40
|
var activeSessionBoards = /* @__PURE__ */ new Map();
|
|
@@ -41,6 +44,33 @@ function boardKey(projectRoot, sessionId) {
|
|
|
41
44
|
function mirrorKey(projectRoot, sessionId, sourceSystem) {
|
|
42
45
|
return `${boardKey(projectRoot, sessionId)}\0${sourceSystem}`;
|
|
43
46
|
}
|
|
47
|
+
function completedReconciliationGraph(latest, candidates) {
|
|
48
|
+
const latestNodeIds = new Set(latest.nodes.map((node) => node.id));
|
|
49
|
+
const carriedNodeIds = /* @__PURE__ */ new Set();
|
|
50
|
+
const completedNodes = candidates.flatMap(
|
|
51
|
+
(candidate) => candidate.nodes.filter((node) => {
|
|
52
|
+
if (node.status !== "completed" || latestNodeIds.has(node.id) || carriedNodeIds.has(node.id)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
carriedNodeIds.add(node.id);
|
|
56
|
+
return true;
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
if (completedNodes.length === 0) return void 0;
|
|
60
|
+
const carriedRequirements = completedNodes.flatMap(
|
|
61
|
+
(node) => node.specRequirementId ? [node.specRequirementId] : []
|
|
62
|
+
);
|
|
63
|
+
return {
|
|
64
|
+
...latest,
|
|
65
|
+
nodes: [...latest.nodes, ...completedNodes],
|
|
66
|
+
rootNodes: [.../* @__PURE__ */ new Set([...latest.rootNodes, ...completedNodes.map((node) => node.id)])],
|
|
67
|
+
...latest.requiredRequirementIds ? {
|
|
68
|
+
requiredRequirementIds: [
|
|
69
|
+
.../* @__PURE__ */ new Set([...latest.requiredRequirementIds, ...carriedRequirements])
|
|
70
|
+
]
|
|
71
|
+
} : {}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
44
74
|
function sessionTag(sessionId) {
|
|
45
75
|
return `session:${sessionId}`;
|
|
46
76
|
}
|
|
@@ -192,16 +222,44 @@ async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
|
|
|
192
222
|
sourceSystem,
|
|
193
223
|
tags: [.../* @__PURE__ */ new Set([...board.tags ?? [], ...sessionBoardTags(sessionId)])],
|
|
194
224
|
archiveMissingTasks: true,
|
|
195
|
-
includeCompletedTasks: true
|
|
225
|
+
includeCompletedTasks: true,
|
|
226
|
+
// The scope ledger stays declared and accurate, but it may not veto a
|
|
227
|
+
// projection. A session mirror reflects a tactical list that shrinks by
|
|
228
|
+
// design, and refusing the sync never protected the removed row — it
|
|
229
|
+
// froze the entire board, permanently, because the stored scope then
|
|
230
|
+
// outlived every later snapshot (`session-kanban.mirror-failed`).
|
|
231
|
+
// Nothing is lost by shrinking here: `archiveMissingTasks` keeps the
|
|
232
|
+
// removed card on the board as `archived`, the reconciliation pass
|
|
233
|
+
// first walks vanished completed rows to Done, and the session journal
|
|
234
|
+
// remains the durable record.
|
|
235
|
+
allowRequirementScopeShrink: true
|
|
196
236
|
}
|
|
197
237
|
);
|
|
198
|
-
|
|
238
|
+
if (!result) return null;
|
|
239
|
+
const compacted = await compactSessionMirrorBoard(projectRoot, board.id);
|
|
240
|
+
if (compacted?.removedTaskIds.length) {
|
|
241
|
+
return await getBoard(projectRoot, board.id) ?? result.board;
|
|
242
|
+
}
|
|
243
|
+
return result.board;
|
|
199
244
|
});
|
|
200
245
|
}
|
|
201
246
|
function queueLatestMirror(projectRoot, sessionId, graph, sourceSystem) {
|
|
202
247
|
if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return;
|
|
203
248
|
const key = mirrorKey(projectRoot, sessionId, sourceSystem);
|
|
204
|
-
pendingMirrors.
|
|
249
|
+
const previous = pendingMirrors.get(key);
|
|
250
|
+
const reconciliationGraph = previous ? completedReconciliationGraph(
|
|
251
|
+
graph,
|
|
252
|
+
[previous.reconciliationGraph, previous.graph].filter(
|
|
253
|
+
(candidate) => candidate !== void 0
|
|
254
|
+
)
|
|
255
|
+
) : void 0;
|
|
256
|
+
pendingMirrors.set(key, {
|
|
257
|
+
projectRoot,
|
|
258
|
+
sessionId,
|
|
259
|
+
graph,
|
|
260
|
+
...reconciliationGraph ? { reconciliationGraph } : {},
|
|
261
|
+
sourceSystem
|
|
262
|
+
});
|
|
205
263
|
if (activeMirrors.has(key)) return;
|
|
206
264
|
activeMirrors.add(key);
|
|
207
265
|
void (async () => {
|
|
@@ -211,20 +269,34 @@ function queueLatestMirror(projectRoot, sessionId, graph, sourceSystem) {
|
|
|
211
269
|
if (!pending) break;
|
|
212
270
|
pendingMirrors.delete(key);
|
|
213
271
|
try {
|
|
272
|
+
if (pending.reconciliationGraph) {
|
|
273
|
+
await projectGraph(
|
|
274
|
+
pending.projectRoot,
|
|
275
|
+
pending.sessionId,
|
|
276
|
+
pending.reconciliationGraph,
|
|
277
|
+
pending.sourceSystem
|
|
278
|
+
);
|
|
279
|
+
}
|
|
214
280
|
await projectGraph(
|
|
215
281
|
pending.projectRoot,
|
|
216
282
|
pending.sessionId,
|
|
217
283
|
pending.graph,
|
|
218
284
|
pending.sourceSystem
|
|
219
285
|
);
|
|
286
|
+
mirrorFailures.delete(boardKey(pending.projectRoot, pending.sessionId));
|
|
220
287
|
} catch (error) {
|
|
288
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
289
|
+
mirrorFailures.set(boardKey(pending.projectRoot, pending.sessionId), {
|
|
290
|
+
message,
|
|
291
|
+
sourceSystem: pending.sourceSystem
|
|
292
|
+
});
|
|
221
293
|
console.warn(
|
|
222
294
|
JSON.stringify({
|
|
223
295
|
level: "warn",
|
|
224
296
|
event: "session-kanban.mirror-failed",
|
|
225
297
|
sessionId: pending.sessionId,
|
|
226
298
|
sourceSystem: pending.sourceSystem,
|
|
227
|
-
message
|
|
299
|
+
message,
|
|
228
300
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
229
301
|
})
|
|
230
302
|
);
|
|
@@ -245,6 +317,19 @@ function queueLatestMirror(projectRoot, sessionId, graph, sourceSystem) {
|
|
|
245
317
|
}
|
|
246
318
|
})();
|
|
247
319
|
}
|
|
320
|
+
function takeSessionMirrorFailure(projectRoot, sessionId) {
|
|
321
|
+
if (!projectRoot || !sessionId) return void 0;
|
|
322
|
+
const key = boardKey(projectRoot, sessionId);
|
|
323
|
+
const failure = mirrorFailures.get(key);
|
|
324
|
+
if (!failure) return void 0;
|
|
325
|
+
mirrorFailures.delete(key);
|
|
326
|
+
return `Kanban mirror (${failure.sourceSystem}) failed and the board may be stale: ${failure.message}`;
|
|
327
|
+
}
|
|
328
|
+
function hasInFlightTodoMirror(projectRoot, sessionId) {
|
|
329
|
+
if (!projectRoot || !sessionId) return false;
|
|
330
|
+
const key = mirrorKey(projectRoot, sessionId, "session-todo");
|
|
331
|
+
return pendingMirrors.has(key) || activeMirrors.has(key);
|
|
332
|
+
}
|
|
248
333
|
function todoListToSerializedGraph(todos, sessionId) {
|
|
249
334
|
const graphId = `todo:${sessionId}`;
|
|
250
335
|
const nodes = todos.map((todo, index) => ({
|
|
@@ -406,11 +491,11 @@ function broadcastTodoUpdate(context, todos) {
|
|
|
406
491
|
});
|
|
407
492
|
}
|
|
408
493
|
function notifyTodoUpdate(context, todos) {
|
|
409
|
-
const summary = todos
|
|
494
|
+
const summary = formatTodosForModel(todos);
|
|
410
495
|
const text = `[KANBAN TODO UPDATE]
|
|
411
496
|
Another Kanban agent reassessed the shared board. The canonical todo list is now:
|
|
412
497
|
${summary}
|
|
413
|
-
Reassess your current plan before continuing; do not rely on the initial todo snapshot.`;
|
|
498
|
+
Reassess your current plan before continuing; do not rely on the initial todo snapshot. Preserve each row's <kanban board/task> binding verbatim on your next \`todo\` call \u2014 a row that loses it stops advancing its card.`;
|
|
414
499
|
const state = context.state;
|
|
415
500
|
if (typeof state.appendBlockToLastUserMessage === "function") {
|
|
416
501
|
if (state.appendBlockToLastUserMessage({ type: "text", text })) return;
|
|
@@ -419,6 +504,10 @@ Reassess your current plan before continuing; do not rely on the initial todo sn
|
|
|
419
504
|
state.appendMessage({ role: "user", content: [{ type: "text", text }] });
|
|
420
505
|
}
|
|
421
506
|
}
|
|
507
|
+
function todosNeedingSessionMirror(todos, activeBoardId) {
|
|
508
|
+
if (!activeBoardId) return todos;
|
|
509
|
+
return todos.filter((todo) => todo.kanbanBoardId !== activeBoardId || !todo.kanbanTaskId);
|
|
510
|
+
}
|
|
422
511
|
function mirrorSessionTodosToKanban(projectRoot, todos, sessionId) {
|
|
423
512
|
queueLatestMirror(
|
|
424
513
|
projectRoot,
|
|
@@ -565,16 +654,9 @@ function attachSessionKanbanMirror(context) {
|
|
|
565
654
|
const unsubscribe = context.state.onChange((change) => {
|
|
566
655
|
if (change.kind === "todos_replaced" && !suppressedTodoMirrors.has(context)) {
|
|
567
656
|
const snapshot = change.completedSnapshot ?? change.todos;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
))
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
mirrorSessionTodosToKanban(
|
|
574
|
-
context.projectRoot,
|
|
575
|
-
change.completedSnapshot ?? change.todos,
|
|
576
|
-
sessionId()
|
|
577
|
-
);
|
|
657
|
+
const unbound = todosNeedingSessionMirror(snapshot, activeManagedBoardId());
|
|
658
|
+
if (snapshot.length > 0 && unbound.length === 0) return;
|
|
659
|
+
mirrorSessionTodosToKanban(context.projectRoot, unbound, sessionId());
|
|
578
660
|
return;
|
|
579
661
|
}
|
|
580
662
|
if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path" || change.key === "kanban")) {
|
|
@@ -610,10 +692,42 @@ function attachSessionKanbanMirror(context) {
|
|
|
610
692
|
bindings.set(context, detach);
|
|
611
693
|
return detach;
|
|
612
694
|
}
|
|
695
|
+
async function rebindSessionKanbanTask(context) {
|
|
696
|
+
const sessionId = context.session?.id;
|
|
697
|
+
if (!sessionId || !context.projectRoot) return null;
|
|
698
|
+
if (context.currentKanbanTaskId) return null;
|
|
699
|
+
let best;
|
|
700
|
+
try {
|
|
701
|
+
const snapshot = await getKanbanOrchestrationSnapshot(context.projectRoot);
|
|
702
|
+
const nowMs = Date.now();
|
|
703
|
+
for (const result of snapshot.running) {
|
|
704
|
+
const assignment = result.task.assignment;
|
|
705
|
+
if (assignment?.status !== "running") continue;
|
|
706
|
+
const expiresAt = assignment.leaseExpiresAt ? Date.parse(assignment.leaseExpiresAt) : Number.NaN;
|
|
707
|
+
if (Number.isFinite(expiresAt) && expiresAt <= nowMs) continue;
|
|
708
|
+
const entry = result.board.presence?.find(
|
|
709
|
+
(candidate) => candidate.sessionId === sessionId && candidate.taskId === result.task.id
|
|
710
|
+
);
|
|
711
|
+
if (!entry) continue;
|
|
712
|
+
if (!best || entry.lastSeenAt > best.lastSeenAt) {
|
|
713
|
+
best = { boardId: result.board.id, taskId: result.task.id, lastSeenAt: entry.lastSeenAt };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
if (!best) return null;
|
|
720
|
+
context.setCurrentKanbanTask(best.taskId, best.boardId);
|
|
721
|
+
return { boardId: best.boardId, taskId: best.taskId };
|
|
722
|
+
}
|
|
613
723
|
async function hydrateSessionKanban(context) {
|
|
614
724
|
const id = context.session?.id ?? "";
|
|
615
725
|
if (!id) return null;
|
|
726
|
+
await rebindSessionKanbanTask(context);
|
|
616
727
|
await cleanupEmptySessionKanbanBoards(context.projectRoot, id);
|
|
728
|
+
if (context.projectRoot) {
|
|
729
|
+
fireAndForget("prune-session-boards", pruneSessionBoards(context.projectRoot));
|
|
730
|
+
}
|
|
617
731
|
let board = await ensureSessionKanbanBoard(context.projectRoot, id);
|
|
618
732
|
if (context.todos.length) {
|
|
619
733
|
board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);
|
|
@@ -644,26 +758,68 @@ function todoStatus(task) {
|
|
|
644
758
|
if (status === "in_progress" || status === "review") return "in_progress";
|
|
645
759
|
return "pending";
|
|
646
760
|
}
|
|
647
|
-
function sessionTodoFromTask(task,
|
|
761
|
+
function sessionTodoFromTask(task, board) {
|
|
762
|
+
const blockedBy = board ? blockingTitles(board, task) : [];
|
|
648
763
|
return {
|
|
649
764
|
id: task.origin?.taskId ?? task.id,
|
|
650
765
|
content: task.title,
|
|
651
766
|
status: todoStatus(task),
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
...task.description ? { activeForm: task.description } : {}
|
|
767
|
+
...task.description ? { activeForm: task.description } : {},
|
|
768
|
+
...blockedBy.length ? { blockedBy } : {}
|
|
655
769
|
};
|
|
656
770
|
}
|
|
657
|
-
function managedTodoFromTask(task,
|
|
771
|
+
function managedTodoFromTask(task, board) {
|
|
658
772
|
return {
|
|
659
|
-
...sessionTodoFromTask(task,
|
|
660
|
-
|
|
773
|
+
...sessionTodoFromTask(task, board),
|
|
774
|
+
kanbanBoardId: board.id,
|
|
775
|
+
kanbanTaskId: task.id
|
|
661
776
|
};
|
|
662
777
|
}
|
|
778
|
+
function blockingTitles(board, task) {
|
|
779
|
+
return getDependencyReadinessIssues(board, task).map((issue) => {
|
|
780
|
+
const dependency = board.tasks.find((candidate) => candidate.id === issue.dependencyId);
|
|
781
|
+
if (!dependency) return `${issue.dependencyId} (missing)`;
|
|
782
|
+
return dependency.title;
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
var PRIORITY_ORDER = {
|
|
786
|
+
critical: 0,
|
|
787
|
+
high: 1,
|
|
788
|
+
medium: 2,
|
|
789
|
+
low: 3
|
|
790
|
+
};
|
|
791
|
+
function orderTasksForTodos(board, tasks) {
|
|
792
|
+
const columnOrder = new Map(board.columns.map((column) => [column.id, column.order]));
|
|
793
|
+
const baseline = [...tasks].sort(
|
|
794
|
+
(left, right) => (columnOrder.get(left.columnId) ?? 0) - (columnOrder.get(right.columnId) ?? 0) || (PRIORITY_ORDER[left.priority] ?? 2) - (PRIORITY_ORDER[right.priority] ?? 2) || left.order - right.order || left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
|
|
795
|
+
);
|
|
796
|
+
const included = new Set(baseline.map((task) => task.id));
|
|
797
|
+
const remaining = new Map(baseline.map((task) => [task.id, task]));
|
|
798
|
+
const emitted = [];
|
|
799
|
+
const done = /* @__PURE__ */ new Set();
|
|
800
|
+
while (remaining.size > 0) {
|
|
801
|
+
const ready = baseline.filter(
|
|
802
|
+
(task) => remaining.has(task.id) && (task.dependsOn ?? []).every(
|
|
803
|
+
(dependencyId) => !included.has(dependencyId) || done.has(dependencyId)
|
|
804
|
+
)
|
|
805
|
+
);
|
|
806
|
+
if (ready.length === 0) break;
|
|
807
|
+
for (const task of ready) {
|
|
808
|
+
remaining.delete(task.id);
|
|
809
|
+
done.add(task.id);
|
|
810
|
+
emitted.push(task);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
for (const task of baseline) if (remaining.has(task.id)) emitted.push(task);
|
|
814
|
+
return emitted;
|
|
815
|
+
}
|
|
663
816
|
function sameTodos(left, right) {
|
|
664
817
|
return left.length === right.length && left.every((todo, index) => {
|
|
665
818
|
const candidate = right[index];
|
|
666
|
-
return candidate?.id === todo.id && candidate.content === todo.content && candidate.status === todo.status && candidate.activeForm === todo.activeForm && candidate.promotedFromPlan === todo.promotedFromPlan && candidate.promotedFromTask === todo.promotedFromTask && candidate.kanbanBoardId === todo.kanbanBoardId && candidate.kanbanTaskId === todo.kanbanTaskId
|
|
819
|
+
return candidate?.id === todo.id && candidate.content === todo.content && candidate.status === todo.status && candidate.activeForm === todo.activeForm && candidate.promotedFromPlan === todo.promotedFromPlan && candidate.promotedFromTask === todo.promotedFromTask && candidate.kanbanBoardId === todo.kanbanBoardId && candidate.kanbanTaskId === todo.kanbanTaskId && // Readiness is part of the projection: when a dependency completes,
|
|
820
|
+
// the rows are otherwise identical and the unblocking would never
|
|
821
|
+
// reach the model.
|
|
822
|
+
(candidate.blockedBy ?? []).join("\0") === (todo.blockedBy ?? []).join("\0");
|
|
667
823
|
});
|
|
668
824
|
}
|
|
669
825
|
function applySessionKanbanBoardToTodos(context, board) {
|
|
@@ -671,13 +827,13 @@ function applySessionKanbanBoardToTodos(context, board) {
|
|
|
671
827
|
if (!sessionId || sessionIdFromTags(board.tags) !== sessionId || !isOwnedSessionBoard(board.tags)) {
|
|
672
828
|
return [...context.todos];
|
|
673
829
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
830
|
+
if (hasInFlightTodoMirror(context.projectRoot, sessionId)) return [...context.todos];
|
|
831
|
+
const projectedTodos = orderTasksForTodos(
|
|
832
|
+
board,
|
|
833
|
+
board.tasks.filter(
|
|
834
|
+
(task) => task.status !== "archived" && (!task.origin || task.origin.system === "session-todo" || (task.origin.graphId ?? "").startsWith("todo:"))
|
|
835
|
+
)
|
|
836
|
+
).map((task) => sessionTodoFromTask(task, board));
|
|
681
837
|
const allCompleted = projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === "completed");
|
|
682
838
|
const effectiveTodos = allCompleted ? [] : projectedTodos;
|
|
683
839
|
if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];
|
|
@@ -698,11 +854,12 @@ function applyManagedKanbanBoardToTodos(context, board) {
|
|
|
698
854
|
if (!activeBoardId || board.id !== activeBoardId || board.lifecycle?.mode !== "managed") {
|
|
699
855
|
return [...context.todos];
|
|
700
856
|
}
|
|
701
|
-
const projectedTodos =
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
857
|
+
const projectedTodos = orderTasksForTodos(
|
|
858
|
+
board,
|
|
859
|
+
board.tasks.filter(
|
|
860
|
+
(task) => task.status !== "archived" && task.mergedIntoTaskId === void 0 && (!task.childTaskIds || task.childTaskIds.length === 0)
|
|
861
|
+
)
|
|
862
|
+
).map((task) => managedTodoFromTask(task, board));
|
|
706
863
|
if (sameTodos(context.todos, projectedTodos)) return [...context.todos];
|
|
707
864
|
suppressedTodoMirrors.add(context);
|
|
708
865
|
try {
|
|
@@ -737,7 +894,11 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
737
894
|
const id = context.session?.id ?? "";
|
|
738
895
|
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
739
896
|
const planPath = context.meta["plan.path"];
|
|
740
|
-
if (typeof planPath !== "string" || !planPath)
|
|
897
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
898
|
+
throw new Error(
|
|
899
|
+
"Cannot reflect this Kanban edit back to its plan source: the session has no plan.path configured. The board mutation already succeeded; the plan file is now out of sync."
|
|
900
|
+
);
|
|
901
|
+
}
|
|
741
902
|
const plan = await mutatePlan(planPath, id, (file) => ({
|
|
742
903
|
...file,
|
|
743
904
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -755,7 +916,11 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
755
916
|
}
|
|
756
917
|
if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
|
|
757
918
|
const taskPath = context.meta["task.path"];
|
|
758
|
-
if (typeof taskPath !== "string" || !taskPath)
|
|
919
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
920
|
+
throw new Error(
|
|
921
|
+
"Cannot reflect this Kanban edit back to its task source: the session has no task.path configured. The board mutation already succeeded; the task file is now out of sync."
|
|
922
|
+
);
|
|
923
|
+
}
|
|
759
924
|
const tasks = await mutateTasks(taskPath, id, (file) => ({
|
|
760
925
|
...file,
|
|
761
926
|
tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
|
|
@@ -778,6 +943,7 @@ export {
|
|
|
778
943
|
applySessionKanbanBoardToTodos,
|
|
779
944
|
applySessionKanbanTaskToSource,
|
|
780
945
|
attachSessionKanbanMirror,
|
|
946
|
+
blockingTitles,
|
|
781
947
|
cleanupEmptySessionKanbanBoards,
|
|
782
948
|
cleanupSessionKanbanBoard,
|
|
783
949
|
cleanupSessionKanbanBoardIfEmpty,
|
|
@@ -786,11 +952,15 @@ export {
|
|
|
786
952
|
mirrorSessionPlanToKanban,
|
|
787
953
|
mirrorSessionTasksToKanban,
|
|
788
954
|
mirrorSessionTodosToKanban,
|
|
955
|
+
orderTasksForTodos,
|
|
789
956
|
planFileToSerializedGraph,
|
|
790
957
|
projectSessionPlanToKanban,
|
|
791
958
|
projectSessionTasksToKanban,
|
|
792
959
|
projectSessionTodosToKanban,
|
|
960
|
+
rebindSessionKanbanTask,
|
|
961
|
+
takeSessionMirrorFailure,
|
|
793
962
|
taskFileToSerializedGraph,
|
|
794
|
-
todoListToSerializedGraph
|
|
963
|
+
todoListToSerializedGraph,
|
|
964
|
+
todosNeedingSessionMirror
|
|
795
965
|
};
|
|
796
966
|
//# sourceMappingURL=session-kanban.js.map
|