@wrongstack/tools 0.303.0 → 0.305.1
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 +717 -381
- package/dist/codebase-index/codebase-index-tool.d.ts +6 -0
- package/dist/codebase-index/index.d.ts +1 -0
- package/dist/codebase-index/index.js +114 -90
- package/dist/codebase-index/indexer.d.ts +6 -0
- package/dist/codebase-index/project-server-endpoint.d.ts +1 -2
- package/dist/codebase-index/project-server.js +131 -109
- 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 +793 -402
- 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 +716 -381
- package/dist/plan.js +601 -289
- package/dist/read.js +112 -90
- package/dist/session-kanban.d.ts +111 -1
- package/dist/session-kanban.js +232 -46
- package/dist/task.js +601 -289
- package/dist/todo.js +601 -289
- package/dist/tool-tier.js +716 -381
- package/package.json +4 -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,57 @@ 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
|
+
}
|
|
723
|
+
var degradationReason;
|
|
724
|
+
function sessionKanbanDegradation() {
|
|
725
|
+
return degradationReason;
|
|
726
|
+
}
|
|
613
727
|
async function hydrateSessionKanban(context) {
|
|
614
728
|
const id = context.session?.id ?? "";
|
|
615
729
|
if (!id) return null;
|
|
730
|
+
try {
|
|
731
|
+
const board = await hydrateSessionKanbanBoard(context, id);
|
|
732
|
+
degradationReason = void 0;
|
|
733
|
+
return board;
|
|
734
|
+
} catch (error) {
|
|
735
|
+
degradationReason = error instanceof Error ? error.message : String(error);
|
|
736
|
+
fireAndForget("hydrate", Promise.reject(error));
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async function hydrateSessionKanbanBoard(context, id) {
|
|
741
|
+
await rebindSessionKanbanTask(context);
|
|
616
742
|
await cleanupEmptySessionKanbanBoards(context.projectRoot, id);
|
|
743
|
+
if (context.projectRoot) {
|
|
744
|
+
fireAndForget("prune-session-boards", pruneSessionBoards(context.projectRoot));
|
|
745
|
+
}
|
|
617
746
|
let board = await ensureSessionKanbanBoard(context.projectRoot, id);
|
|
618
747
|
if (context.todos.length) {
|
|
619
748
|
board = await projectSessionTodosToKanban(context.projectRoot, context.todos, id);
|
|
@@ -644,26 +773,68 @@ function todoStatus(task) {
|
|
|
644
773
|
if (status === "in_progress" || status === "review") return "in_progress";
|
|
645
774
|
return "pending";
|
|
646
775
|
}
|
|
647
|
-
function sessionTodoFromTask(task,
|
|
776
|
+
function sessionTodoFromTask(task, board) {
|
|
777
|
+
const blockedBy = board ? blockingTitles(board, task) : [];
|
|
648
778
|
return {
|
|
649
779
|
id: task.origin?.taskId ?? task.id,
|
|
650
780
|
content: task.title,
|
|
651
781
|
status: todoStatus(task),
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
...task.description ? { activeForm: task.description } : {}
|
|
782
|
+
...task.description ? { activeForm: task.description } : {},
|
|
783
|
+
...blockedBy.length ? { blockedBy } : {}
|
|
655
784
|
};
|
|
656
785
|
}
|
|
657
|
-
function managedTodoFromTask(task,
|
|
786
|
+
function managedTodoFromTask(task, board) {
|
|
658
787
|
return {
|
|
659
|
-
...sessionTodoFromTask(task,
|
|
660
|
-
|
|
788
|
+
...sessionTodoFromTask(task, board),
|
|
789
|
+
kanbanBoardId: board.id,
|
|
790
|
+
kanbanTaskId: task.id
|
|
661
791
|
};
|
|
662
792
|
}
|
|
793
|
+
function blockingTitles(board, task) {
|
|
794
|
+
return getDependencyReadinessIssues(board, task).map((issue) => {
|
|
795
|
+
const dependency = board.tasks.find((candidate) => candidate.id === issue.dependencyId);
|
|
796
|
+
if (!dependency) return `${issue.dependencyId} (missing)`;
|
|
797
|
+
return dependency.title;
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
var PRIORITY_ORDER = {
|
|
801
|
+
critical: 0,
|
|
802
|
+
high: 1,
|
|
803
|
+
medium: 2,
|
|
804
|
+
low: 3
|
|
805
|
+
};
|
|
806
|
+
function orderTasksForTodos(board, tasks) {
|
|
807
|
+
const columnOrder = new Map(board.columns.map((column) => [column.id, column.order]));
|
|
808
|
+
const baseline = [...tasks].sort(
|
|
809
|
+
(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)
|
|
810
|
+
);
|
|
811
|
+
const included = new Set(baseline.map((task) => task.id));
|
|
812
|
+
const remaining = new Map(baseline.map((task) => [task.id, task]));
|
|
813
|
+
const emitted = [];
|
|
814
|
+
const done = /* @__PURE__ */ new Set();
|
|
815
|
+
while (remaining.size > 0) {
|
|
816
|
+
const ready = baseline.filter(
|
|
817
|
+
(task) => remaining.has(task.id) && (task.dependsOn ?? []).every(
|
|
818
|
+
(dependencyId) => !included.has(dependencyId) || done.has(dependencyId)
|
|
819
|
+
)
|
|
820
|
+
);
|
|
821
|
+
if (ready.length === 0) break;
|
|
822
|
+
for (const task of ready) {
|
|
823
|
+
remaining.delete(task.id);
|
|
824
|
+
done.add(task.id);
|
|
825
|
+
emitted.push(task);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
for (const task of baseline) if (remaining.has(task.id)) emitted.push(task);
|
|
829
|
+
return emitted;
|
|
830
|
+
}
|
|
663
831
|
function sameTodos(left, right) {
|
|
664
832
|
return left.length === right.length && left.every((todo, index) => {
|
|
665
833
|
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
|
|
834
|
+
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,
|
|
835
|
+
// the rows are otherwise identical and the unblocking would never
|
|
836
|
+
// reach the model.
|
|
837
|
+
(candidate.blockedBy ?? []).join("\0") === (todo.blockedBy ?? []).join("\0");
|
|
667
838
|
});
|
|
668
839
|
}
|
|
669
840
|
function applySessionKanbanBoardToTodos(context, board) {
|
|
@@ -671,13 +842,13 @@ function applySessionKanbanBoardToTodos(context, board) {
|
|
|
671
842
|
if (!sessionId || sessionIdFromTags(board.tags) !== sessionId || !isOwnedSessionBoard(board.tags)) {
|
|
672
843
|
return [...context.todos];
|
|
673
844
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
845
|
+
if (hasInFlightTodoMirror(context.projectRoot, sessionId)) return [...context.todos];
|
|
846
|
+
const projectedTodos = orderTasksForTodos(
|
|
847
|
+
board,
|
|
848
|
+
board.tasks.filter(
|
|
849
|
+
(task) => task.status !== "archived" && (!task.origin || task.origin.system === "session-todo" || (task.origin.graphId ?? "").startsWith("todo:"))
|
|
850
|
+
)
|
|
851
|
+
).map((task) => sessionTodoFromTask(task, board));
|
|
681
852
|
const allCompleted = projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === "completed");
|
|
682
853
|
const effectiveTodos = allCompleted ? [] : projectedTodos;
|
|
683
854
|
if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];
|
|
@@ -698,11 +869,12 @@ function applyManagedKanbanBoardToTodos(context, board) {
|
|
|
698
869
|
if (!activeBoardId || board.id !== activeBoardId || board.lifecycle?.mode !== "managed") {
|
|
699
870
|
return [...context.todos];
|
|
700
871
|
}
|
|
701
|
-
const projectedTodos =
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
872
|
+
const projectedTodos = orderTasksForTodos(
|
|
873
|
+
board,
|
|
874
|
+
board.tasks.filter(
|
|
875
|
+
(task) => task.status !== "archived" && task.mergedIntoTaskId === void 0 && (!task.childTaskIds || task.childTaskIds.length === 0)
|
|
876
|
+
)
|
|
877
|
+
).map((task) => managedTodoFromTask(task, board));
|
|
706
878
|
if (sameTodos(context.todos, projectedTodos)) return [...context.todos];
|
|
707
879
|
suppressedTodoMirrors.add(context);
|
|
708
880
|
try {
|
|
@@ -737,7 +909,11 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
737
909
|
const id = context.session?.id ?? "";
|
|
738
910
|
if (task.origin?.system === "session-plan" || graphId.startsWith("plan:")) {
|
|
739
911
|
const planPath = context.meta["plan.path"];
|
|
740
|
-
if (typeof planPath !== "string" || !planPath)
|
|
912
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
913
|
+
throw new Error(
|
|
914
|
+
"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."
|
|
915
|
+
);
|
|
916
|
+
}
|
|
741
917
|
const plan = await mutatePlan(planPath, id, (file) => ({
|
|
742
918
|
...file,
|
|
743
919
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -755,7 +931,11 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
755
931
|
}
|
|
756
932
|
if (task.origin?.system === "session-task" || task.origin?.system === "session" || graphId.startsWith("session:")) {
|
|
757
933
|
const taskPath = context.meta["task.path"];
|
|
758
|
-
if (typeof taskPath !== "string" || !taskPath)
|
|
934
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
935
|
+
throw new Error(
|
|
936
|
+
"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."
|
|
937
|
+
);
|
|
938
|
+
}
|
|
759
939
|
const tasks = await mutateTasks(taskPath, id, (file) => ({
|
|
760
940
|
...file,
|
|
761
941
|
tasks: options.remove ? file.tasks.filter((item) => item.id !== originId) : file.tasks.map(
|
|
@@ -778,6 +958,7 @@ export {
|
|
|
778
958
|
applySessionKanbanBoardToTodos,
|
|
779
959
|
applySessionKanbanTaskToSource,
|
|
780
960
|
attachSessionKanbanMirror,
|
|
961
|
+
blockingTitles,
|
|
781
962
|
cleanupEmptySessionKanbanBoards,
|
|
782
963
|
cleanupSessionKanbanBoard,
|
|
783
964
|
cleanupSessionKanbanBoardIfEmpty,
|
|
@@ -786,11 +967,16 @@ export {
|
|
|
786
967
|
mirrorSessionPlanToKanban,
|
|
787
968
|
mirrorSessionTasksToKanban,
|
|
788
969
|
mirrorSessionTodosToKanban,
|
|
970
|
+
orderTasksForTodos,
|
|
789
971
|
planFileToSerializedGraph,
|
|
790
972
|
projectSessionPlanToKanban,
|
|
791
973
|
projectSessionTasksToKanban,
|
|
792
974
|
projectSessionTodosToKanban,
|
|
975
|
+
rebindSessionKanbanTask,
|
|
976
|
+
sessionKanbanDegradation,
|
|
977
|
+
takeSessionMirrorFailure,
|
|
793
978
|
taskFileToSerializedGraph,
|
|
794
|
-
todoListToSerializedGraph
|
|
979
|
+
todoListToSerializedGraph,
|
|
980
|
+
todosNeedingSessionMirror
|
|
795
981
|
};
|
|
796
982
|
//# sourceMappingURL=session-kanban.js.map
|