@wrongstack/tools 0.302.0 → 0.303.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.
Files changed (40) hide show
  1. package/dist/builtin.js +2799 -609
  2. package/dist/codebase-index/binary-frame.d.ts +43 -0
  3. package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +1 -0
  4. package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +1 -0
  5. package/dist/codebase-index/content-hash.d.ts +66 -0
  6. package/dist/codebase-index/index.js +1569 -148
  7. package/dist/codebase-index/parser-worker-pool.d.ts +63 -0
  8. package/dist/codebase-index/parser-worker-script.d.ts +42 -0
  9. package/dist/codebase-index/project-server-protocol.d.ts +2 -0
  10. package/dist/codebase-index/project-server.js +1455 -99
  11. package/dist/codebase-index/schema.d.ts +7 -0
  12. package/dist/codebase-index/tree-sitter/queries.d.ts +48 -0
  13. package/dist/codebase-index/tree-sitter/util.d.ts +31 -0
  14. package/dist/codebase-index/tree-sitter/visitor.d.ts +47 -0
  15. package/dist/codebase-index/tree-sitter-parser.d.ts +58 -0
  16. package/dist/codebase-index/vector-search.d.ts +62 -0
  17. package/dist/codebase-index/worker-protocol.d.ts +2 -0
  18. package/dist/codebase-index/worker.js +1424 -68
  19. package/dist/codebase-index/writer-bulk-insert.d.ts +5 -0
  20. package/dist/codebase-index/writer-graph-reader.d.ts +39 -0
  21. package/dist/codebase-index/writer-schema.d.ts +9 -2
  22. package/dist/codebase-index/writer.d.ts +36 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +2785 -634
  25. package/dist/kanban-task-inputs.d.ts +1 -0
  26. package/dist/kanban-tool-schema.d.ts +2 -2
  27. package/dist/kanban-tool-types.d.ts +18 -2
  28. package/dist/kanban.js +392 -126
  29. package/dist/pack.js +2799 -609
  30. package/dist/plan.d.ts +4 -1
  31. package/dist/plan.js +2380 -9
  32. package/dist/read.js +1531 -98
  33. package/dist/session-kanban.d.ts +8 -0
  34. package/dist/session-kanban.js +111 -17
  35. package/dist/task.d.ts +5 -4
  36. package/dist/task.js +2418 -43
  37. package/dist/todo.d.ts +10 -1
  38. package/dist/todo.js +2152 -20
  39. package/dist/tool-tier.js +2799 -609
  40. package/package.json +8 -4
@@ -7,6 +7,12 @@ import { type KanbanBoard, type KanbanColumn, type KanbanTask } from '@wrongstac
7
7
  export declare const SESSION_KANBAN_COLUMNS: KanbanColumn[];
8
8
  /** Create (or migrate) the single Kanban board owned by a session. */
9
9
  export declare function ensureSessionKanbanBoard(projectRoot: string | undefined, sessionId: string): Promise<KanbanBoard | null>;
10
+ /**
11
+ * Remove an inactive session's observational mirror, including its cards.
12
+ * The durable source remains the session todo/task/plan files; retaining the
13
+ * mirror after detach only creates historical duplicate work in project views.
14
+ */
15
+ export declare function cleanupSessionKanbanBoard(projectRoot: string | undefined, sessionId: string): Promise<string[]>;
10
16
  /** Remove a particular inactive session's system-owned board when it has no cards. */
11
17
  export declare function cleanupSessionKanbanBoardIfEmpty(projectRoot: string | undefined, sessionId: string): Promise<string[]>;
12
18
  /** Prune stale empty session boards while preserving manual and live boards. */
@@ -43,6 +49,8 @@ export interface SessionKanbanSourceUpdate {
43
49
  * todo list that happened to exist when the run started.
44
50
  */
45
51
  export declare function applySessionKanbanBoardToTodos(context: Context, board: KanbanBoard): TodoItem[];
52
+ /** Project a durable managed board into the session's compact todo surface. */
53
+ export declare function applyManagedKanbanBoardToTodos(context: Context, board: KanbanBoard): TodoItem[];
46
54
  /** Reflect a TUI/WebUI Kanban card edit back to its originating work list. */
47
55
  export declare function applySessionKanbanTaskToSource(context: Context, task: KanbanTask, options?: {
48
56
  remove?: boolean | undefined;
@@ -135,6 +135,26 @@ async function removeEmptySessionBoard(projectRoot, boardId, sessionId) {
135
135
  return await removeBoard(projectRoot, board.id) ? board.id : null;
136
136
  });
137
137
  }
138
+ async function removeOwnedSessionBoard(projectRoot, boardId, sessionId) {
139
+ return enqueueBoardWork(projectRoot, sessionId, async () => {
140
+ if (isSessionBoardActive(projectRoot, sessionId)) return null;
141
+ const board = await getBoard(projectRoot, boardId);
142
+ if (!board || !isOwnedSessionBoard(board.tags)) return null;
143
+ if (sessionIdFromTags(board.tags) !== sessionId) return null;
144
+ return await removeBoard(projectRoot, board.id) ? board.id : null;
145
+ });
146
+ }
147
+ async function cleanupSessionKanbanBoard(projectRoot, sessionId) {
148
+ if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return [];
149
+ if (isSessionBoardActive(projectRoot, sessionId)) return [];
150
+ const candidates = (await listBoards(projectRoot)).filter(
151
+ (board) => isOwnedSessionBoard(board.tags) && sessionIdFromTags(board.tags) === sessionId
152
+ );
153
+ const removed = await Promise.all(
154
+ candidates.map((board) => removeOwnedSessionBoard(projectRoot, board.id, sessionId))
155
+ );
156
+ return removed.filter((boardId) => Boolean(boardId));
157
+ }
138
158
  async function cleanupSessionKanbanBoardIfEmpty(projectRoot, sessionId) {
139
159
  if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return [];
140
160
  if (isSessionBoardActive(projectRoot, sessionId)) return [];
@@ -197,7 +217,17 @@ function queueLatestMirror(projectRoot, sessionId, graph, sourceSystem) {
197
217
  pending.graph,
198
218
  pending.sourceSystem
199
219
  );
200
- } catch {
220
+ } catch (error) {
221
+ console.warn(
222
+ JSON.stringify({
223
+ level: "warn",
224
+ event: "session-kanban.mirror-failed",
225
+ sessionId: pending.sessionId,
226
+ sourceSystem: pending.sourceSystem,
227
+ message: error instanceof Error ? error.message : String(error),
228
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
229
+ })
230
+ );
201
231
  }
202
232
  }
203
233
  } finally {
@@ -216,6 +246,7 @@ function queueLatestMirror(projectRoot, sessionId, graph, sourceSystem) {
216
246
  })();
217
247
  }
218
248
  function todoListToSerializedGraph(todos, sessionId) {
249
+ const graphId = `todo:${sessionId}`;
219
250
  const nodes = todos.map((todo, index) => ({
220
251
  id: todo.id,
221
252
  title: todo.content,
@@ -223,12 +254,14 @@ function todoListToSerializedGraph(todos, sessionId) {
223
254
  type: "chore",
224
255
  priority: "medium",
225
256
  status: todo.status,
257
+ specRequirementId: `${graphId}:${todo.id}`,
226
258
  createdAt: index,
227
259
  updatedAt: index
228
260
  }));
229
261
  return {
230
- id: `todo:${sessionId}`,
231
- specId: `todo:${sessionId}`,
262
+ id: graphId,
263
+ specId: graphId,
264
+ requiredRequirementIds: nodes.map((node) => node.specRequirementId),
232
265
  title: "Session todos",
233
266
  nodes,
234
267
  edges: [],
@@ -238,6 +271,7 @@ function todoListToSerializedGraph(todos, sessionId) {
238
271
  };
239
272
  }
240
273
  function taskFileToSerializedGraph(tasks, sessionId) {
274
+ const graphId = `session:${sessionId}`;
241
275
  const ids = new Set(tasks.map((task) => task.id));
242
276
  const nodes = tasks.map((task, index) => ({
243
277
  id: task.id,
@@ -246,6 +280,7 @@ function taskFileToSerializedGraph(tasks, sessionId) {
246
280
  type: task.type,
247
281
  priority: task.priority,
248
282
  status: task.status,
283
+ specRequirementId: `${graphId}:${task.id}`,
249
284
  ...task.assignee ? { assignee: task.assignee } : {},
250
285
  ...task.estimateHours !== void 0 ? { estimateHours: task.estimateHours } : {},
251
286
  createdAt: index,
@@ -263,8 +298,9 @@ function taskFileToSerializedGraph(tasks, sessionId) {
263
298
  const rootNodes = nodes.filter((node) => !hasIncoming.has(node.id)).map((node) => node.id);
264
299
  return {
265
300
  // Keep the historical graph id so existing mirrored task cards are reused.
266
- id: `session:${sessionId}`,
267
- specId: `session:${sessionId}`,
301
+ id: graphId,
302
+ specId: graphId,
303
+ requiredRequirementIds: nodes.map((node) => node.specRequirementId),
268
304
  title: "Session tasks",
269
305
  nodes,
270
306
  edges,
@@ -279,6 +315,7 @@ var PLAN_STATUS_TO_TASK = {
279
315
  done: "completed"
280
316
  };
281
317
  function planFileToSerializedGraph(items, sessionId) {
318
+ const graphId = `plan:${sessionId}`;
282
319
  const nodes = items.map((item, index) => ({
283
320
  id: item.id,
284
321
  title: item.title,
@@ -286,12 +323,14 @@ function planFileToSerializedGraph(items, sessionId) {
286
323
  type: "chore",
287
324
  priority: "medium",
288
325
  status: PLAN_STATUS_TO_TASK[item.status],
326
+ specRequirementId: `${graphId}:${item.id}`,
289
327
  createdAt: index,
290
328
  updatedAt: index
291
329
  }));
292
330
  return {
293
- id: `plan:${sessionId}`,
294
- specId: `plan:${sessionId}`,
331
+ id: graphId,
332
+ specId: graphId,
333
+ requiredRequirementIds: nodes.map((node) => node.specRequirementId),
295
334
  title: "Session plan",
296
335
  nodes,
297
336
  edges: [],
@@ -417,7 +456,7 @@ function attachSessionKanbanMirror(context) {
417
456
  releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
418
457
  fireAndForget(
419
458
  "cleanup-board",
420
- cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId)
459
+ cleanupSessionKanbanBoard(attachedProjectRoot, registeredSessionId)
421
460
  );
422
461
  }
423
462
  registeredSessionId = currentSessionId;
@@ -434,6 +473,11 @@ function attachSessionKanbanMirror(context) {
434
473
  let boardTimer = null;
435
474
  let presenceTimer = null;
436
475
  const sessionId = () => context.session?.id ?? "";
476
+ const activeManagedBoardId = () => {
477
+ const metaKanban = context.meta["kanban"];
478
+ const metaBoardId = metaKanban && typeof metaKanban === "object" ? metaKanban["boardId"] : void 0;
479
+ return context.currentKanbanBoardId ?? (typeof metaBoardId === "string" && metaBoardId ? metaBoardId : "");
480
+ };
437
481
  const refreshFiles = async () => {
438
482
  const id = sessionId();
439
483
  if (!id) return;
@@ -451,12 +495,19 @@ function attachSessionKanbanMirror(context) {
451
495
  const refreshBoard = async () => {
452
496
  if (!watchedBoardId) return;
453
497
  const board = await getBoard(context.projectRoot, watchedBoardId);
454
- if (board) applySessionKanbanBoardToTodos(context, board);
498
+ if (!board) return;
499
+ if (board.lifecycle?.mode === "managed") applyManagedKanbanBoardToTodos(context, board);
500
+ else applySessionKanbanBoardToTodos(context, board);
455
501
  };
456
502
  const configureBoardWatcher = async () => {
457
503
  const id = sessionId();
458
- const board = id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;
459
- if (!board || board.id === watchedBoardId) return;
504
+ const managedBoardId = activeManagedBoardId();
505
+ const board = managedBoardId ? await getBoard(context.projectRoot, managedBoardId) : id ? await ensureSessionKanbanBoard(context.projectRoot, id) : null;
506
+ if (!board) return;
507
+ if (board.id === watchedBoardId) {
508
+ await refreshBoard();
509
+ return;
510
+ }
460
511
  unsubscribeBoardEvents?.();
461
512
  unsubscribeBoardEvents = null;
462
513
  watchedBoardId = board.id;
@@ -480,6 +531,7 @@ function attachSessionKanbanMirror(context) {
480
531
  if (presenceTimer) clearInterval(presenceTimer);
481
532
  presenceTimer = setInterval(() => fireAndForget("touch-presence", touchPresence()), 6e4);
482
533
  presenceTimer.unref?.();
534
+ if (board.lifecycle?.mode === "managed") applyManagedKanbanBoardToTodos(context, board);
483
535
  } catch {
484
536
  unsubscribeBoardEvents = null;
485
537
  watchedBoardId = "";
@@ -512,6 +564,12 @@ function attachSessionKanbanMirror(context) {
512
564
  };
513
565
  const unsubscribe = context.state.onChange((change) => {
514
566
  if (change.kind === "todos_replaced" && !suppressedTodoMirrors.has(context)) {
567
+ const snapshot = change.completedSnapshot ?? change.todos;
568
+ if (snapshot.length > 0 && snapshot.every(
569
+ (todo) => todo.kanbanBoardId === activeManagedBoardId() && Boolean(todo.kanbanTaskId)
570
+ )) {
571
+ return;
572
+ }
515
573
  mirrorSessionTodosToKanban(
516
574
  context.projectRoot,
517
575
  change.completedSnapshot ?? change.todos,
@@ -519,11 +577,14 @@ function attachSessionKanbanMirror(context) {
519
577
  );
520
578
  return;
521
579
  }
522
- if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path")) {
580
+ if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path" || change.key === "kanban")) {
523
581
  syncActiveSessionRegistration();
524
582
  configureWatcher();
525
- fireAndForget("ensure-board", ensureSessionKanbanBoard(context.projectRoot, sessionId()));
583
+ if (!activeManagedBoardId()) {
584
+ fireAndForget("ensure-board", ensureSessionKanbanBoard(context.projectRoot, sessionId()));
585
+ }
526
586
  fireAndForget("configure-watcher", configureBoardWatcher());
587
+ fireAndForget("refresh-board", refreshBoard());
527
588
  fireAndForget("refresh-files", refreshFiles());
528
589
  }
529
590
  });
@@ -541,7 +602,7 @@ function attachSessionKanbanMirror(context) {
541
602
  releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
542
603
  fireAndForget(
543
604
  "cleanup-board",
544
- cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId)
605
+ cleanupSessionKanbanBoard(attachedProjectRoot, registeredSessionId)
545
606
  );
546
607
  registeredSessionId = "";
547
608
  }
@@ -583,18 +644,26 @@ function todoStatus(task) {
583
644
  if (status === "in_progress" || status === "review") return "in_progress";
584
645
  return "pending";
585
646
  }
586
- function sessionTodoFromTask(task) {
647
+ function sessionTodoFromTask(task, boardId) {
587
648
  return {
588
649
  id: task.origin?.taskId ?? task.id,
589
650
  content: task.title,
590
651
  status: todoStatus(task),
652
+ kanbanBoardId: boardId,
653
+ kanbanTaskId: task.id,
591
654
  ...task.description ? { activeForm: task.description } : {}
592
655
  };
593
656
  }
657
+ function managedTodoFromTask(task, boardId) {
658
+ return {
659
+ ...sessionTodoFromTask(task, boardId),
660
+ status: task.status === "completed" ? "completed" : task.status === "in_progress" ? "in_progress" : "pending"
661
+ };
662
+ }
594
663
  function sameTodos(left, right) {
595
664
  return left.length === right.length && left.every((todo, index) => {
596
665
  const candidate = right[index];
597
- 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;
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;
598
667
  });
599
668
  }
600
669
  function applySessionKanbanBoardToTodos(context, board) {
@@ -608,7 +677,7 @@ function applySessionKanbanBoardToTodos(context, board) {
608
677
  const leftColumn = board.columns.find((column) => column.id === left.columnId)?.order ?? 0;
609
678
  const rightColumn = board.columns.find((column) => column.id === right.columnId)?.order ?? 0;
610
679
  return leftColumn - rightColumn || left.order - right.order || left.createdAt.localeCompare(right.createdAt);
611
- }).map(sessionTodoFromTask);
680
+ }).map((task) => sessionTodoFromTask(task, board.id));
612
681
  const allCompleted = projectedTodos.length > 0 && projectedTodos.every((todo) => todo.status === "completed");
613
682
  const effectiveTodos = allCompleted ? [] : projectedTodos;
614
683
  if (sameTodos(context.todos, effectiveTodos)) return [...context.todos];
@@ -622,6 +691,29 @@ function applySessionKanbanBoardToTodos(context, board) {
622
691
  broadcastTodoUpdate(context, context.todos);
623
692
  return [...context.todos];
624
693
  }
694
+ function applyManagedKanbanBoardToTodos(context, board) {
695
+ const metaKanban = context.meta["kanban"];
696
+ const metaBoardId = metaKanban && typeof metaKanban === "object" ? metaKanban["boardId"] : void 0;
697
+ const activeBoardId = context.currentKanbanBoardId ?? (typeof metaBoardId === "string" ? metaBoardId : void 0);
698
+ if (!activeBoardId || board.id !== activeBoardId || board.lifecycle?.mode !== "managed") {
699
+ return [...context.todos];
700
+ }
701
+ const projectedTodos = board.tasks.filter(
702
+ (task) => task.status !== "archived" && task.mergedIntoTaskId === void 0 && (!task.childTaskIds || task.childTaskIds.length === 0)
703
+ ).sort(
704
+ (left, right) => left.createdAt.localeCompare(right.createdAt) || left.order - right.order
705
+ ).map((task) => managedTodoFromTask(task, board.id));
706
+ if (sameTodos(context.todos, projectedTodos)) return [...context.todos];
707
+ suppressedTodoMirrors.add(context);
708
+ try {
709
+ context.state.replaceTodos(projectedTodos);
710
+ } finally {
711
+ suppressedTodoMirrors.delete(context);
712
+ }
713
+ notifyTodoUpdate(context, context.todos);
714
+ broadcastTodoUpdate(context, context.todos);
715
+ return [...context.todos];
716
+ }
625
717
  async function applySessionKanbanTaskToSource(context, task, options = {}) {
626
718
  const originId = task.origin?.taskId;
627
719
  const graphId = task.origin?.graphId ?? "";
@@ -682,10 +774,12 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
682
774
  }
683
775
  export {
684
776
  SESSION_KANBAN_COLUMNS,
777
+ applyManagedKanbanBoardToTodos,
685
778
  applySessionKanbanBoardToTodos,
686
779
  applySessionKanbanTaskToSource,
687
780
  attachSessionKanbanMirror,
688
781
  cleanupEmptySessionKanbanBoards,
782
+ cleanupSessionKanbanBoard,
689
783
  cleanupSessionKanbanBoardIfEmpty,
690
784
  ensureSessionKanbanBoard,
691
785
  hydrateSessionKanban,
package/dist/task.d.ts CHANGED
@@ -1,13 +1,14 @@
1
- import type { TaskStatus } from '@wrongstack/core/types';
1
+ import type { TaskStatus, Tool } from '@wrongstack/core/types';
2
2
  import { type TaskItem } from '@wrongstack/core/utils';
3
- import type { Tool } from '@wrongstack/core/types';
3
+ type TaskReplacementItem = Omit<TaskItem, 'createdAt' | 'updatedAt'> & Partial<Pick<TaskItem, 'createdAt' | 'updatedAt'>>;
4
+ type TaskAdditionItem = Omit<TaskItem, 'id' | 'createdAt' | 'updatedAt' | 'status'> & Partial<Pick<TaskItem, 'status'>>;
4
5
  interface TaskInput {
5
6
  /** Replace: set new task list. Add: append a task. Status: update task status. Promote: convert a task to todo items. */
6
7
  action: 'replace' | 'add' | 'status' | 'show' | 'promote' | 'planify';
7
8
  /** Full task list for action=replace. */
8
- tasks?: TaskItem[] | undefined;
9
+ tasks?: TaskReplacementItem[] | undefined;
9
10
  /** Single task for action=add. id, createdAt, updatedAt are auto-generated. */
10
- task?: Omit<TaskItem, 'id' | 'createdAt' | 'updatedAt'> | undefined;
11
+ task?: TaskAdditionItem | undefined;
11
12
  /** Task id for action=status or target for action=promote. */
12
13
  id?: string | undefined;
13
14
  /** New status for action=status. */