@cwim/kanban 1.1.20 → 1.2.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.
@@ -1,13 +1,13 @@
1
- import { promises as fs, existsSync, mkdirSync } from 'fs';
2
- import * as path from 'path';
3
- import * as os from 'os';
4
- import { execSync } from 'child_process';
5
- const KANBAN_DIR = path.join(os.homedir(), '.kanban');
6
- const SESSIONS_DIR = path.join(KANBAN_DIR, 'sessions');
7
- const ACTIVE_SESSION_FILE = path.join(KANBAN_DIR, 'active-session.json');
8
- const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
9
- const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
10
- const OPENCODE_DATA_DIR = path.join(os.homedir(), '.local', 'share', 'opencode');
1
+ import { promises as fs, existsSync, mkdirSync } from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ import { execSync } from "child_process";
5
+ const KANBAN_DIR = path.join(os.homedir(), ".kanban");
6
+ const SESSIONS_DIR = path.join(KANBAN_DIR, "sessions");
7
+ const ACTIVE_SESSION_FILE = path.join(KANBAN_DIR, "active-session.json");
8
+ const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), ".claude", "projects");
9
+ const OPENCODE_CONFIG_DIR = path.join(os.homedir(), ".config", "opencode");
10
+ const OPENCODE_DATA_DIR = path.join(os.homedir(), ".local", "share", "opencode");
11
11
  function getDefaultData() {
12
12
  return {
13
13
  version: 1,
@@ -24,13 +24,13 @@ function getSessionDir(sessionName) {
24
24
  return path.join(SESSIONS_DIR, sessionName);
25
25
  }
26
26
  function getSessionDataFile(sessionName) {
27
- return path.join(getSessionDir(sessionName), 'tasks.json');
27
+ return path.join(getSessionDir(sessionName), "tasks.json");
28
28
  }
29
29
  export async function getActiveSession() {
30
30
  try {
31
31
  if (!existsSync(ACTIVE_SESSION_FILE))
32
32
  return undefined;
33
- const raw = await fs.readFile(ACTIVE_SESSION_FILE, 'utf-8');
33
+ const raw = await fs.readFile(ACTIVE_SESSION_FILE, "utf-8");
34
34
  const active = JSON.parse(raw);
35
35
  return active.name;
36
36
  }
@@ -44,21 +44,23 @@ export async function setActiveSession(sessionName) {
44
44
  name: sessionName,
45
45
  setAt: new Date().toISOString(),
46
46
  };
47
- await fs.writeFile(ACTIVE_SESSION_FILE, JSON.stringify(active, null, 2), 'utf-8');
47
+ await fs.writeFile(ACTIVE_SESSION_FILE, JSON.stringify(active, null, 2), "utf-8");
48
48
  }
49
49
  // Session detection
50
50
  async function detectClaudeSessions() {
51
51
  try {
52
52
  if (!existsSync(CLAUDE_PROJECTS_DIR))
53
53
  return [];
54
- const entries = await fs.readdir(CLAUDE_PROJECTS_DIR, { withFileTypes: true });
54
+ const entries = await fs.readdir(CLAUDE_PROJECTS_DIR, {
55
+ withFileTypes: true,
56
+ });
55
57
  return entries
56
58
  .filter((e) => e.isDirectory())
57
59
  .map((e) => ({
58
60
  name: e.name,
59
61
  path: path.join(CLAUDE_PROJECTS_DIR, e.name),
60
62
  detectedAt: new Date().toISOString(),
61
- source: 'claude',
63
+ source: "claude",
62
64
  }));
63
65
  }
64
66
  catch {
@@ -66,10 +68,23 @@ async function detectClaudeSessions() {
66
68
  }
67
69
  }
68
70
  const OPENCODE_CONFIG_EXCLUDES = new Set([
69
- 'agents', 'commands', 'modes', 'plugins', 'skills', 'tools', 'themes', 'node_modules'
71
+ "agents",
72
+ "command",
73
+ "commands",
74
+ "modes",
75
+ "plugins",
76
+ "skills",
77
+ "tools",
78
+ "themes",
79
+ "node_modules",
70
80
  ]);
71
81
  const OPENCODE_DATA_EXCLUDES = new Set([
72
- 'bin', 'log', 'repos', 'snapshot', 'storage', 'tool-output'
82
+ "bin",
83
+ "log",
84
+ "repos",
85
+ "snapshot",
86
+ "storage",
87
+ "tool-output",
73
88
  ]);
74
89
  const ALL_INTERNAL_NAMES = new Set([
75
90
  ...OPENCODE_CONFIG_EXCLUDES,
@@ -85,7 +100,7 @@ async function detectOpencodeSessions() {
85
100
  return detectOpencodeSessionsFromDirectories();
86
101
  }
87
102
  async function queryOpencodeDatabase() {
88
- const dbPath = path.join(OPENCODE_DATA_DIR, 'opencode.db');
103
+ const dbPath = path.join(OPENCODE_DATA_DIR, "opencode.db");
89
104
  if (!existsSync(dbPath)) {
90
105
  return [];
91
106
  }
@@ -95,14 +110,19 @@ async function queryOpencodeDatabase() {
95
110
  const query = `SELECT id, worktree, name, time_updated FROM project WHERE worktree IS NOT NULL AND worktree != '/' ORDER BY time_updated DESC;`;
96
111
  // Write query to temp file to avoid shell escaping issues with paths
97
112
  const tempQueryFile = path.join(os.tmpdir(), `opencode-query-${Date.now()}.sql`);
98
- await fs.writeFile(tempQueryFile, query, 'utf-8');
113
+ await fs.writeFile(tempQueryFile, query, "utf-8");
99
114
  let result;
100
115
  try {
101
116
  result = execSync(`sqlite3 "${dbPath}" < "${tempQueryFile}"`, {
102
- encoding: 'utf8',
117
+ encoding: "utf8",
103
118
  timeout: 10000,
119
+ stdio: ["pipe", "pipe", "pipe"],
104
120
  });
105
121
  }
122
+ catch {
123
+ // sqlite3 not installed or other error - return empty to trigger fallback
124
+ return [];
125
+ }
106
126
  finally {
107
127
  // Clean up temp file
108
128
  try {
@@ -114,14 +134,14 @@ async function queryOpencodeDatabase() {
114
134
  }
115
135
  const sessions = [];
116
136
  const seenPaths = new Set();
117
- for (const line of result.trim().split('\n')) {
137
+ for (const line of result.trim().split("\n")) {
118
138
  if (!line.trim())
119
139
  continue;
120
- const parts = line.split('|');
140
+ const parts = line.split("|");
121
141
  if (parts.length < 4)
122
142
  continue;
123
143
  const [, worktree, name, timeUpdated] = parts;
124
- if (!worktree || worktree === '/')
144
+ if (!worktree || worktree === "/")
125
145
  continue;
126
146
  // Normalize path for deduplication
127
147
  const normalizedPath = path.normalize(worktree);
@@ -134,7 +154,7 @@ async function queryOpencodeDatabase() {
134
154
  name: sessionName,
135
155
  path: normalizedPath,
136
156
  detectedAt: new Date(parseInt(timeUpdated)).toISOString(),
137
- source: 'opencode',
157
+ source: "opencode",
138
158
  });
139
159
  }
140
160
  return sessions;
@@ -149,14 +169,16 @@ async function detectOpencodeSessionsFromDirectories() {
149
169
  // Check ~/.config/opencode/ for project directories
150
170
  try {
151
171
  if (existsSync(OPENCODE_CONFIG_DIR)) {
152
- const entries = await fs.readdir(OPENCODE_CONFIG_DIR, { withFileTypes: true });
172
+ const entries = await fs.readdir(OPENCODE_CONFIG_DIR, {
173
+ withFileTypes: true,
174
+ });
153
175
  for (const entry of entries) {
154
176
  if (entry.isDirectory() && !OPENCODE_CONFIG_EXCLUDES.has(entry.name)) {
155
177
  sessions.push({
156
178
  name: entry.name,
157
179
  path: path.join(OPENCODE_CONFIG_DIR, entry.name),
158
180
  detectedAt: new Date().toISOString(),
159
- source: 'opencode',
181
+ source: "opencode",
160
182
  });
161
183
  }
162
184
  }
@@ -168,17 +190,19 @@ async function detectOpencodeSessionsFromDirectories() {
168
190
  // Check ~/.local/share/opencode/ for session data
169
191
  try {
170
192
  if (existsSync(OPENCODE_DATA_DIR)) {
171
- const entries = await fs.readdir(OPENCODE_DATA_DIR, { withFileTypes: true });
193
+ const entries = await fs.readdir(OPENCODE_DATA_DIR, {
194
+ withFileTypes: true,
195
+ });
172
196
  for (const entry of entries) {
173
197
  if (entry.isDirectory() && !OPENCODE_DATA_EXCLUDES.has(entry.name)) {
174
198
  const sessionName = entry.name;
175
199
  // Only add if not already found
176
- if (!sessions.some(s => s.name === sessionName)) {
200
+ if (!sessions.some((s) => s.name === sessionName)) {
177
201
  sessions.push({
178
202
  name: sessionName,
179
203
  path: path.join(OPENCODE_DATA_DIR, entry.name),
180
204
  detectedAt: new Date().toISOString(),
181
- source: 'opencode',
205
+ source: "opencode",
182
206
  });
183
207
  }
184
208
  }
@@ -190,22 +214,58 @@ async function detectOpencodeSessionsFromDirectories() {
190
214
  }
191
215
  return sessions;
192
216
  }
217
+ function detectGitRepo() {
218
+ try {
219
+ const cwd = process.cwd();
220
+ const gitRoot = execSync("git rev-parse --show-toplevel", {
221
+ cwd,
222
+ encoding: "utf8",
223
+ timeout: 5000,
224
+ stdio: ["pipe", "pipe", "ignore"],
225
+ }).trim();
226
+ if (!gitRoot)
227
+ return undefined;
228
+ const normalizedPath = path.normalize(gitRoot);
229
+ const name = path.basename(normalizedPath);
230
+ return { name, path: normalizedPath };
231
+ }
232
+ catch {
233
+ return undefined;
234
+ }
235
+ }
236
+ async function detectGitRepoSessions() {
237
+ const repo = detectGitRepo();
238
+ if (!repo)
239
+ return [];
240
+ return [
241
+ {
242
+ name: repo.name,
243
+ path: repo.path,
244
+ detectedAt: new Date().toISOString(),
245
+ source: "git",
246
+ },
247
+ ];
248
+ }
193
249
  export async function detectSessions() {
194
250
  try {
195
- // Detect sessions from both Claude and opencode
196
- const [claudeSessions, opencodeSessions] = await Promise.all([
251
+ // Detect sessions from all sources: Git (CWD), Claude, and OpenCode
252
+ const [gitSessions, claudeSessions, opencodeSessions] = await Promise.all([
253
+ detectGitRepoSessions(),
197
254
  detectClaudeSessions(),
198
255
  detectOpencodeSessions(),
199
256
  ]);
200
257
  // Merge and deduplicate by name
258
+ // Priority: Git > Claude > OpenCode
201
259
  const sessionMap = new Map();
260
+ for (const session of opencodeSessions) {
261
+ sessionMap.set(session.name, session);
262
+ }
202
263
  for (const session of claudeSessions) {
203
264
  sessionMap.set(session.name, session);
204
265
  }
205
- for (const session of opencodeSessions) {
206
- if (!sessionMap.has(session.name)) {
207
- sessionMap.set(session.name, session);
208
- }
266
+ // Git sessions take highest priority - they reflect the actual working directory
267
+ for (const session of gitSessions) {
268
+ sessionMap.set(session.name, session);
209
269
  }
210
270
  const sessions = Array.from(sessionMap.values());
211
271
  if (sessions.length === 0)
@@ -234,6 +294,12 @@ export async function detectSessions() {
234
294
  }
235
295
  export async function detectLatestSession() {
236
296
  const sessions = await detectSessions();
297
+ // If we're in a git repo, prefer the git-detected session
298
+ // even if it's not the most recently modified
299
+ const gitSession = sessions.find((s) => s.source === "git");
300
+ if (gitSession) {
301
+ return gitSession;
302
+ }
237
303
  return sessions[0];
238
304
  }
239
305
  export async function listAllSessions() {
@@ -247,13 +313,13 @@ export async function listAllSessions() {
247
313
  for (const entry of entries) {
248
314
  if (entry.isDirectory() &&
249
315
  !detectedNames.has(entry.name) &&
250
- entry.name !== 'independent' &&
316
+ entry.name !== "independent" &&
251
317
  !ALL_INTERNAL_NAMES.has(entry.name)) {
252
318
  storedSessions.push({
253
319
  name: entry.name,
254
320
  path: getSessionDir(entry.name),
255
321
  detectedAt: new Date().toISOString(),
256
- source: 'manual',
322
+ source: "manual",
257
323
  });
258
324
  }
259
325
  }
@@ -263,10 +329,10 @@ export async function listAllSessions() {
263
329
  ...detected,
264
330
  ...storedSessions,
265
331
  {
266
- name: 'independent',
267
- path: getSessionDir('independent'),
332
+ name: "independent",
333
+ path: getSessionDir("independent"),
268
334
  detectedAt: new Date().toISOString(),
269
- source: 'independent',
335
+ source: "independent",
270
336
  },
271
337
  ];
272
338
  return allSessions;
@@ -283,15 +349,15 @@ export async function getCurrentSessionName() {
283
349
  return latest.name;
284
350
  }
285
351
  // Fallback to independent
286
- await setActiveSession('independent');
287
- return 'independent';
352
+ await setActiveSession("independent");
353
+ return "independent";
288
354
  }
289
355
  // Per-session data operations
290
356
  async function readSessionData(sessionName) {
291
357
  const dataFile = getSessionDataFile(sessionName);
292
358
  ensureDir(getSessionDir(sessionName));
293
359
  try {
294
- const raw = await fs.readFile(dataFile, 'utf-8');
360
+ const raw = await fs.readFile(dataFile, "utf-8");
295
361
  return JSON.parse(raw);
296
362
  }
297
363
  catch {
@@ -304,13 +370,13 @@ async function writeSessionData(sessionName, data) {
304
370
  const dataFile = getSessionDataFile(sessionName);
305
371
  ensureDir(getSessionDir(sessionName));
306
372
  data.updatedAt = new Date().toISOString();
307
- const tempFile = dataFile + '.tmp';
308
- await fs.writeFile(tempFile, JSON.stringify(data, null, 2), 'utf-8');
373
+ const tempFile = dataFile + ".tmp";
374
+ await fs.writeFile(tempFile, JSON.stringify(data, null, 2), "utf-8");
309
375
  await fs.rename(tempFile, dataFile);
310
376
  }
311
377
  // Task CRUD (session-aware)
312
378
  export async function listTasks(sessionName, filter) {
313
- const targetSession = sessionName ?? await getCurrentSessionName();
379
+ const targetSession = sessionName ?? (await getCurrentSessionName());
314
380
  const data = await readSessionData(targetSession);
315
381
  let tasks = data.tasks;
316
382
  if (filter?.status) {
@@ -327,20 +393,20 @@ export async function listTasks(sessionName, filter) {
327
393
  return tasks;
328
394
  }
329
395
  export async function getTask(id, sessionName) {
330
- const targetSession = sessionName ?? await getCurrentSessionName();
396
+ const targetSession = sessionName ?? (await getCurrentSessionName());
331
397
  const data = await readSessionData(targetSession);
332
398
  return data.tasks.find((t) => t.id === id);
333
399
  }
334
400
  export async function createTask(input, sessionName) {
335
- const targetSession = sessionName ?? await getCurrentSessionName();
401
+ const targetSession = sessionName ?? (await getCurrentSessionName());
336
402
  const data = await readSessionData(targetSession);
337
403
  const task = {
338
404
  id: generateId(),
339
405
  title: input.title,
340
406
  description: input.description,
341
- status: input.status ?? 'todo',
407
+ status: input.status ?? "todo",
342
408
  tags: input.tags ?? [],
343
- source: input.source ?? 'manual',
409
+ source: input.source ?? "manual",
344
410
  createdAt: new Date().toISOString(),
345
411
  updatedAt: new Date().toISOString(),
346
412
  };
@@ -349,7 +415,7 @@ export async function createTask(input, sessionName) {
349
415
  return task;
350
416
  }
351
417
  export async function updateTask(id, input, sessionName) {
352
- const targetSession = sessionName ?? await getCurrentSessionName();
418
+ const targetSession = sessionName ?? (await getCurrentSessionName());
353
419
  const data = await readSessionData(targetSession);
354
420
  const idx = data.tasks.findIndex((t) => t.id === id);
355
421
  if (idx === -1)
@@ -371,7 +437,7 @@ export async function moveTask(id, status, sessionName) {
371
437
  return updateTask(id, { status }, sessionName);
372
438
  }
373
439
  export async function deleteTask(id, sessionName) {
374
- const targetSession = sessionName ?? await getCurrentSessionName();
440
+ const targetSession = sessionName ?? (await getCurrentSessionName());
375
441
  const data = await readSessionData(targetSession);
376
442
  const initialLen = data.tasks.length;
377
443
  data.tasks = data.tasks.filter((t) => t.id !== id);
@@ -381,7 +447,7 @@ export async function deleteTask(id, sessionName) {
381
447
  return true;
382
448
  }
383
449
  export async function appendNote(id, note, sessionName) {
384
- const targetSession = sessionName ?? await getCurrentSessionName();
450
+ const targetSession = sessionName ?? (await getCurrentSessionName());
385
451
  const data = await readSessionData(targetSession);
386
452
  const idx = data.tasks.findIndex((t) => t.id === id);
387
453
  if (idx === -1)
@@ -397,7 +463,7 @@ export async function appendNote(id, note, sessionName) {
397
463
  return task;
398
464
  }
399
465
  export async function getAllData(sessionName) {
400
- const targetSession = sessionName ?? await getCurrentSessionName();
466
+ const targetSession = sessionName ?? (await getCurrentSessionName());
401
467
  const data = await readSessionData(targetSession);
402
468
  // Attach session info
403
469
  const sessions = await listAllSessions();
@@ -408,7 +474,128 @@ export async function getAllData(sessionName) {
408
474
  return data;
409
475
  }
410
476
  const STOP_WORDS = new Set([
411
- 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'its', 'may', 'new', 'now', 'old', 'see', 'two', 'who', 'boy', 'did', 'she', 'use', 'her', 'way', 'many', 'oil', 'sit', 'set', 'run', 'eat', 'far', 'sea', 'eye', 'ago', 'off', 'too', 'any', 'say', 'man', 'try', 'ask', 'end', 'why', 'let', 'put', 'say', 'she', 'try', 'way', 'own', 'say', 'too', 'old', 'tell', 'very', 'when', 'much', 'would', 'there', 'their', 'what', 'said', 'each', 'which', 'will', 'about', 'could', 'other', 'after', 'first', 'never', 'these', 'think', 'where', 'being', 'every', 'great', 'might', 'shall', 'still', 'those', 'while', 'this', 'that', 'with', 'have', 'from', 'they', 'know', 'want', 'been', 'good', 'much', 'some', 'time', 'very', 'when', 'come', 'here', 'just', 'like', 'long', 'make', 'many', 'over', 'such', 'take', 'than', 'them', 'well', 'were'
477
+ "the",
478
+ "and",
479
+ "for",
480
+ "are",
481
+ "but",
482
+ "not",
483
+ "you",
484
+ "all",
485
+ "can",
486
+ "had",
487
+ "her",
488
+ "was",
489
+ "one",
490
+ "our",
491
+ "out",
492
+ "day",
493
+ "get",
494
+ "has",
495
+ "him",
496
+ "his",
497
+ "how",
498
+ "its",
499
+ "may",
500
+ "new",
501
+ "now",
502
+ "old",
503
+ "see",
504
+ "two",
505
+ "who",
506
+ "boy",
507
+ "did",
508
+ "she",
509
+ "use",
510
+ "her",
511
+ "way",
512
+ "many",
513
+ "oil",
514
+ "sit",
515
+ "set",
516
+ "run",
517
+ "eat",
518
+ "far",
519
+ "sea",
520
+ "eye",
521
+ "ago",
522
+ "off",
523
+ "too",
524
+ "any",
525
+ "say",
526
+ "man",
527
+ "try",
528
+ "ask",
529
+ "end",
530
+ "why",
531
+ "let",
532
+ "put",
533
+ "say",
534
+ "she",
535
+ "try",
536
+ "way",
537
+ "own",
538
+ "say",
539
+ "too",
540
+ "old",
541
+ "tell",
542
+ "very",
543
+ "when",
544
+ "much",
545
+ "would",
546
+ "there",
547
+ "their",
548
+ "what",
549
+ "said",
550
+ "each",
551
+ "which",
552
+ "will",
553
+ "about",
554
+ "could",
555
+ "other",
556
+ "after",
557
+ "first",
558
+ "never",
559
+ "these",
560
+ "think",
561
+ "where",
562
+ "being",
563
+ "every",
564
+ "great",
565
+ "might",
566
+ "shall",
567
+ "still",
568
+ "those",
569
+ "while",
570
+ "this",
571
+ "that",
572
+ "with",
573
+ "have",
574
+ "from",
575
+ "they",
576
+ "know",
577
+ "want",
578
+ "been",
579
+ "good",
580
+ "much",
581
+ "some",
582
+ "time",
583
+ "very",
584
+ "when",
585
+ "come",
586
+ "here",
587
+ "just",
588
+ "like",
589
+ "long",
590
+ "make",
591
+ "many",
592
+ "over",
593
+ "such",
594
+ "take",
595
+ "than",
596
+ "them",
597
+ "well",
598
+ "were",
412
599
  ]);
413
600
  function extractKeywords(text) {
414
601
  return text
@@ -420,9 +607,9 @@ function extractKeywords(text) {
420
607
  function getTaskFields(task) {
421
608
  return {
422
609
  title: task.title.toLowerCase(),
423
- tags: task.tags.join(' ').toLowerCase(),
424
- description: (task.description || '').toLowerCase(),
425
- notes: (task.notes || []).join(' ').toLowerCase(),
610
+ tags: task.tags.join(" ").toLowerCase(),
611
+ description: (task.description || "").toLowerCase(),
612
+ notes: (task.notes || []).join(" ").toLowerCase(),
426
613
  };
427
614
  }
428
615
  function scoreTask(task, queryWords) {
@@ -435,7 +622,7 @@ function scoreTask(task, queryWords) {
435
622
  if (fields.title.includes(word)) {
436
623
  score += 5;
437
624
  signals++;
438
- reasons.push({ field: 'title', matched: word });
625
+ reasons.push({ field: "title", matched: word });
439
626
  break; // Only count title once per word
440
627
  }
441
628
  }
@@ -444,7 +631,7 @@ function scoreTask(task, queryWords) {
444
631
  if (fields.tags.includes(word)) {
445
632
  score += 4;
446
633
  signals++;
447
- reasons.push({ field: 'tag', matched: word });
634
+ reasons.push({ field: "tag", matched: word });
448
635
  break;
449
636
  }
450
637
  }
@@ -453,7 +640,7 @@ function scoreTask(task, queryWords) {
453
640
  if (fields.description.includes(word)) {
454
641
  score += 2;
455
642
  signals++;
456
- reasons.push({ field: 'description', matched: word });
643
+ reasons.push({ field: "description", matched: word });
457
644
  break;
458
645
  }
459
646
  }
@@ -462,7 +649,7 @@ function scoreTask(task, queryWords) {
462
649
  if (fields.notes.includes(word)) {
463
650
  score += 1;
464
651
  signals++;
465
- reasons.push({ field: 'notes', matched: word });
652
+ reasons.push({ field: "notes", matched: word });
466
653
  break;
467
654
  }
468
655
  }
@@ -476,25 +663,25 @@ function scoreTask(task, queryWords) {
476
663
  score += 1;
477
664
  }
478
665
  // Status boost
479
- if (task.status === 'in-progress') {
666
+ if (task.status === "in-progress") {
480
667
  score += 2;
481
668
  signals++;
482
669
  }
483
- else if (task.status === 'blocked') {
670
+ else if (task.status === "blocked") {
484
671
  score += 1;
485
672
  signals++;
486
673
  }
487
674
  return { task, score, signals, reasons };
488
675
  }
489
676
  export async function recallTasks(context, limit = 5, sessionName) {
490
- const targetSession = sessionName ?? await getCurrentSessionName();
677
+ const targetSession = sessionName ?? (await getCurrentSessionName());
491
678
  const data = await readSessionData(targetSession);
492
679
  if (data.tasks.length === 0) {
493
680
  return {
494
681
  relevant: [],
495
682
  reasons: new Map(),
496
683
  summary: { active: 0, done: 0, blocked: 0, total: 0 },
497
- quality: 'poor',
684
+ quality: "poor",
498
685
  };
499
686
  }
500
687
  const queryWords = extractKeywords(context);
@@ -507,19 +694,19 @@ export async function recallTasks(context, limit = 5, sessionName) {
507
694
  let quality;
508
695
  let actualLimit;
509
696
  if (maxScore >= 10) {
510
- quality = 'excellent';
697
+ quality = "excellent";
511
698
  actualLimit = Math.min(limit, 2);
512
699
  }
513
700
  else if (maxScore >= 7) {
514
- quality = 'good';
701
+ quality = "good";
515
702
  actualLimit = Math.min(limit, 3);
516
703
  }
517
704
  else if (maxScore >= 5) {
518
- quality = 'fair';
705
+ quality = "fair";
519
706
  actualLimit = 1;
520
707
  }
521
708
  else {
522
- quality = 'poor';
709
+ quality = "poor";
523
710
  actualLimit = 0;
524
711
  }
525
712
  // Filter by minimum threshold and signal count
@@ -530,9 +717,9 @@ export async function recallTasks(context, limit = 5, sessionName) {
530
717
  const reasons = new Map(filtered.map((s) => [s.task.id, s.reasons]));
531
718
  // Calculate summary
532
719
  const summary = {
533
- active: data.tasks.filter((t) => t.status === 'in-progress').length,
534
- done: data.tasks.filter((t) => t.status === 'done').length,
535
- blocked: data.tasks.filter((t) => t.status === 'blocked').length,
720
+ active: data.tasks.filter((t) => t.status === "in-progress").length,
721
+ done: data.tasks.filter((t) => t.status === "done").length,
722
+ blocked: data.tasks.filter((t) => t.status === "blocked").length,
536
723
  total: data.tasks.length,
537
724
  };
538
725
  return { relevant, reasons, summary, quality };