clawvault 2.4.5 → 2.4.6

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/bin/clawvault.js CHANGED
@@ -18,6 +18,7 @@ import { registerTemplateCommands } from './register-template-commands.js';
18
18
  import { registerVaultOperationsCommands } from './register-vault-operations-commands.js';
19
19
  import { registerConfigCommands } from './register-config-commands.js';
20
20
  import { registerRouteCommands } from './register-route-commands.js';
21
+ import { registerKanbanCommands } from './register-kanban-commands.js';
21
22
 
22
23
  import { registerTaskCommands } from './register-task-commands.js';
23
24
 
@@ -93,6 +94,10 @@ registerTaskCommands(program, {
93
94
  chalk,
94
95
  resolveVaultPath
95
96
  });
97
+ registerKanbanCommands(program, {
98
+ chalk,
99
+ resolveVaultPath
100
+ });
96
101
 
97
102
  registerTailscaleCommands(program, { chalk });
98
103
  registerConfigCommands(program, { chalk, resolveVaultPath });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Kanban command registrations for ClawVault.
3
+ */
4
+
5
+ export function registerKanbanCommands(
6
+ program,
7
+ { chalk, resolveVaultPath }
8
+ ) {
9
+ const kanbanCmd = program
10
+ .command('kanban')
11
+ .description('Sync Obsidian Kanban boards with task frontmatter');
12
+
13
+ kanbanCmd
14
+ .command('sync')
15
+ .description('Generate and sync an Obsidian Kanban board from tasks')
16
+ .option('-v, --vault <path>', 'Vault path')
17
+ .option('--output <path>', 'Board markdown path (default: Board.md)')
18
+ .option('--group-by <field>', 'Grouping field (status, priority, project, owner)')
19
+ .option('--filter-project <project>', 'Only include tasks from a project')
20
+ .option('--filter-owner <owner>', 'Only include tasks for an owner')
21
+ .option('--include-done', 'Include done tasks')
22
+ .action(async (options) => {
23
+ try {
24
+ const vaultPath = resolveVaultPath(options.vault);
25
+ const { kanbanCommand } = await import('../dist/commands/kanban.js');
26
+ await kanbanCommand(vaultPath, 'sync', {
27
+ output: options.output,
28
+ groupBy: options.groupBy,
29
+ filterProject: options.filterProject,
30
+ filterOwner: options.filterOwner,
31
+ includeDone: options.includeDone
32
+ });
33
+ } catch (err) {
34
+ console.error(chalk.red(`Error: ${err.message}`));
35
+ process.exit(1);
36
+ }
37
+ });
38
+
39
+ kanbanCmd
40
+ .command('import')
41
+ .description('Import lane state from an Obsidian Kanban board into tasks')
42
+ .option('-v, --vault <path>', 'Vault path')
43
+ .option('--output <path>', 'Board markdown path (default: Board.md)')
44
+ .action(async (options) => {
45
+ try {
46
+ const vaultPath = resolveVaultPath(options.vault);
47
+ const { kanbanCommand } = await import('../dist/commands/kanban.js');
48
+ await kanbanCommand(vaultPath, 'import', {
49
+ output: options.output
50
+ });
51
+ } catch (err) {
52
+ console.error(chalk.red(`Error: ${err.message}`));
53
+ process.exit(1);
54
+ }
55
+ });
56
+ }
@@ -0,0 +1,83 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import { registerKanbanCommands } from './register-kanban-commands.js';
4
+ import { chalkStub } from './test-helpers/cli-command-fixtures.js';
5
+
6
+ const { kanbanCommandMock } = vi.hoisted(() => ({
7
+ kanbanCommandMock: vi.fn()
8
+ }));
9
+
10
+ vi.mock('../dist/commands/kanban.js', () => ({
11
+ kanbanCommand: kanbanCommandMock
12
+ }));
13
+
14
+ function buildProgram() {
15
+ const program = new Command();
16
+ registerKanbanCommands(program, {
17
+ chalk: chalkStub,
18
+ resolveVaultPath: (value) => value ?? '/vault'
19
+ });
20
+ return program;
21
+ }
22
+
23
+ async function runCommand(args) {
24
+ const program = buildProgram();
25
+ await program.parseAsync(args, { from: 'user' });
26
+ }
27
+
28
+ describe('register-kanban-commands', () => {
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ it('registers sync and import subcommands with expected options', () => {
34
+ const program = buildProgram();
35
+ const kanbanCommand = program.commands.find((command) => command.name() === 'kanban');
36
+ expect(kanbanCommand).toBeDefined();
37
+
38
+ const syncCommand = kanbanCommand?.commands.find((command) => command.name() === 'sync');
39
+ expect(syncCommand).toBeDefined();
40
+ const syncFlags = syncCommand?.options.map((option) => option.flags) ?? [];
41
+ expect(syncFlags).toEqual(expect.arrayContaining([
42
+ '--output <path>',
43
+ '--group-by <field>',
44
+ '--filter-project <project>',
45
+ '--filter-owner <owner>',
46
+ '--include-done'
47
+ ]));
48
+
49
+ const importCommand = kanbanCommand?.commands.find((command) => command.name() === 'import');
50
+ expect(importCommand).toBeDefined();
51
+ const importFlags = importCommand?.options.map((option) => option.flags) ?? [];
52
+ expect(importFlags).toEqual(expect.arrayContaining(['--output <path>']));
53
+ });
54
+
55
+ it('dispatches sync and import actions to the kanban command handler', async () => {
56
+ await runCommand([
57
+ 'kanban',
58
+ 'sync',
59
+ '--group-by',
60
+ 'priority',
61
+ '--output',
62
+ 'Board.md',
63
+ '--filter-project',
64
+ 'apollo',
65
+ '--filter-owner',
66
+ 'alice',
67
+ '--include-done'
68
+ ]);
69
+
70
+ expect(kanbanCommandMock).toHaveBeenCalledWith('/vault', 'sync', {
71
+ output: 'Board.md',
72
+ groupBy: 'priority',
73
+ filterProject: 'apollo',
74
+ filterOwner: 'alice',
75
+ includeDone: true
76
+ });
77
+
78
+ await runCommand(['kanban', 'import', '--output', 'Board.md']);
79
+ expect(kanbanCommandMock).toHaveBeenCalledWith('/vault', 'import', {
80
+ output: 'Board.md'
81
+ });
82
+ });
83
+ });
@@ -3,6 +3,20 @@
3
3
  * Registers task, backlog, blocked, and canvas commands
4
4
  */
5
5
 
6
+ function parseCsvList(value) {
7
+ if (!value) return undefined;
8
+ const items = String(value)
9
+ .split(',')
10
+ .map((item) => item.trim())
11
+ .filter(Boolean);
12
+ return items.length > 0 ? items : undefined;
13
+ }
14
+
15
+ function clearableValue(value, shouldClear) {
16
+ if (shouldClear) return null;
17
+ return value;
18
+ }
19
+
6
20
  export function registerTaskCommands(
7
21
  program,
8
22
  { chalk, resolveVaultPath }
@@ -21,6 +35,11 @@ export function registerTaskCommands(
21
35
  .option('--project <project>', 'Project name')
22
36
  .option('--priority <priority>', 'Priority (critical, high, medium, low)')
23
37
  .option('--due <date>', 'Due date (YYYY-MM-DD)')
38
+ .option('--tags <tags>', 'Comma-separated tags')
39
+ .option('--description <description>', 'One-line task summary')
40
+ .option('--estimate <estimate>', 'Estimate (for example: 2h, 1d, 1w)')
41
+ .option('--parent <slug>', 'Parent task slug')
42
+ .option('--depends-on <slugs>', 'Comma-separated dependency slugs')
24
43
  .action(async (title, options) => {
25
44
  try {
26
45
  const vaultPath = resolveVaultPath(options.vault);
@@ -31,7 +50,12 @@ export function registerTaskCommands(
31
50
  owner: options.owner,
32
51
  project: options.project,
33
52
  priority: options.priority,
34
- due: options.due
53
+ due: options.due,
54
+ tags: parseCsvList(options.tags),
55
+ description: options.description,
56
+ estimate: options.estimate,
57
+ parent: options.parent,
58
+ dependsOn: parseCsvList(options.dependsOn)
35
59
  }
36
60
  });
37
61
  } catch (err) {
@@ -49,6 +73,9 @@ export function registerTaskCommands(
49
73
  .option('--project <project>', 'Filter by project')
50
74
  .option('--status <status>', 'Filter by status (open, in-progress, blocked, done)')
51
75
  .option('--priority <priority>', 'Filter by priority')
76
+ .option('--due', 'Show only tasks with due dates (sorted by due date)')
77
+ .option('--tag <tag>', 'Filter by tag')
78
+ .option('--overdue', 'Show overdue tasks that are not done')
52
79
  .option('--json', 'Output as JSON')
53
80
  .action(async (options) => {
54
81
  try {
@@ -60,6 +87,9 @@ export function registerTaskCommands(
60
87
  project: options.project,
61
88
  status: options.status,
62
89
  priority: options.priority,
90
+ due: options.due,
91
+ tag: options.tag,
92
+ overdue: options.overdue,
63
93
  json: options.json
64
94
  }
65
95
  });
@@ -76,12 +106,28 @@ export function registerTaskCommands(
76
106
  .option('-v, --vault <path>', 'Vault path')
77
107
  .option('--status <status>', 'New status')
78
108
  .option('--owner <owner>', 'New owner')
109
+ .option('--clear-owner', 'Clear owner')
79
110
  .option('--project <project>', 'New project')
111
+ .option('--clear-project', 'Clear project')
80
112
  .option('--priority <priority>', 'New priority')
113
+ .option('--clear-priority', 'Clear priority')
81
114
  .option('--blocked-by <blocker>', 'What is blocking this task')
115
+ .option('--clear-blocked-by', 'Clear blocked-by field')
82
116
  .option('--due <date>', 'New due date')
117
+ .option('--clear-due', 'Clear due date')
118
+ .option('--tags <tags>', 'Comma-separated tags')
119
+ .option('--clear-tags', 'Clear tags')
120
+ .option('--description <description>', 'One-line task summary')
121
+ .option('--clear-description', 'Clear description')
122
+ .option('--estimate <estimate>', 'Estimate (for example: 2h, 1d, 1w)')
123
+ .option('--clear-estimate', 'Clear estimate')
124
+ .option('--parent <slug>', 'Parent task slug')
125
+ .option('--clear-parent', 'Clear parent task')
126
+ .option('--depends-on <slugs>', 'Comma-separated dependency slugs')
127
+ .option('--clear-depends-on', 'Clear dependencies')
83
128
  .option('--confidence <value>', 'Transition confidence (0-1)', parseFloat)
84
129
  .option('--reason <reason>', 'Reason for status change')
130
+ .option('--clear-reason', 'Clear reason')
85
131
  .action(async (slug, options) => {
86
132
  try {
87
133
  const vaultPath = resolveVaultPath(options.vault);
@@ -90,13 +136,18 @@ export function registerTaskCommands(
90
136
  slug,
91
137
  options: {
92
138
  status: options.status,
93
- owner: options.owner,
94
- project: options.project,
95
- priority: options.priority,
96
- blockedBy: options.blockedBy,
97
- due: options.due,
139
+ owner: clearableValue(options.owner, options.clearOwner),
140
+ project: clearableValue(options.project, options.clearProject),
141
+ priority: clearableValue(options.priority, options.clearPriority),
142
+ blockedBy: clearableValue(options.blockedBy, options.clearBlockedBy),
143
+ due: clearableValue(options.due, options.clearDue),
144
+ tags: options.clearTags ? null : parseCsvList(options.tags),
145
+ description: clearableValue(options.description, options.clearDescription),
146
+ estimate: clearableValue(options.estimate, options.clearEstimate),
147
+ parent: clearableValue(options.parent, options.clearParent),
148
+ dependsOn: options.clearDependsOn ? null : parseCsvList(options.dependsOn),
98
149
  confidence: options.confidence,
99
- reason: options.reason
150
+ reason: clearableValue(options.reason, options.clearReason)
100
151
  }
101
152
  });
102
153
  } catch (err) {
@@ -4,6 +4,52 @@ import { registerTaskCommands } from './register-task-commands.js';
4
4
  import { chalkStub, stubResolveVaultPath } from './test-helpers/cli-command-fixtures.js';
5
5
 
6
6
  describe('register-task-commands', () => {
7
+ it('adds enriched task add/list/update options', () => {
8
+ const program = new Command();
9
+ registerTaskCommands(program, {
10
+ chalk: chalkStub,
11
+ resolveVaultPath: stubResolveVaultPath
12
+ });
13
+
14
+ const taskCommand = program.commands.find((command) => command.name() === 'task');
15
+ expect(taskCommand).toBeDefined();
16
+
17
+ const addCommand = taskCommand?.commands.find((command) => command.name() === 'add');
18
+ const addFlags = addCommand?.options.map((option) => option.flags) ?? [];
19
+ expect(addFlags).toEqual(expect.arrayContaining([
20
+ '--due <date>',
21
+ '--tags <tags>',
22
+ '--description <description>',
23
+ '--estimate <estimate>',
24
+ '--parent <slug>',
25
+ '--depends-on <slugs>'
26
+ ]));
27
+
28
+ const listCommand = taskCommand?.commands.find((command) => command.name() === 'list');
29
+ const listFlags = listCommand?.options.map((option) => option.flags) ?? [];
30
+ expect(listFlags).toEqual(expect.arrayContaining([
31
+ '--due',
32
+ '--tag <tag>',
33
+ '--overdue'
34
+ ]));
35
+
36
+ const updateCommand = taskCommand?.commands.find((command) => command.name() === 'update');
37
+ const updateFlags = updateCommand?.options.map((option) => option.flags) ?? [];
38
+ expect(updateFlags).toEqual(expect.arrayContaining([
39
+ '--tags <tags>',
40
+ '--description <description>',
41
+ '--estimate <estimate>',
42
+ '--parent <slug>',
43
+ '--depends-on <slugs>',
44
+ '--clear-due',
45
+ '--clear-tags',
46
+ '--clear-description',
47
+ '--clear-estimate',
48
+ '--clear-parent',
49
+ '--clear-depends-on'
50
+ ]));
51
+ });
52
+
7
53
  it('adds canvas template and listing flags', () => {
8
54
  const program = new Command();
9
55
  registerTaskCommands(program, {
@@ -10,6 +10,8 @@ import { registerTemplateCommands } from '../register-template-commands.js';
10
10
  import { registerVaultOperationsCommands } from '../register-vault-operations-commands.js';
11
11
  import { registerConfigCommands } from '../register-config-commands.js';
12
12
  import { registerRouteCommands } from '../register-route-commands.js';
13
+ import { registerTaskCommands } from '../register-task-commands.js';
14
+ import { registerKanbanCommands } from '../register-kanban-commands.js';
13
15
 
14
16
  export const chalkStub = {
15
17
  cyan: (value) => value,
@@ -99,6 +101,14 @@ export function registerAllCommandModules(program = new Command()) {
99
101
  chalk: chalkStub,
100
102
  resolveVaultPath: stubResolveVaultPath
101
103
  });
104
+ registerTaskCommands(program, {
105
+ chalk: chalkStub,
106
+ resolveVaultPath: stubResolveVaultPath
107
+ });
108
+ registerKanbanCommands(program, {
109
+ chalk: chalkStub,
110
+ resolveVaultPath: stubResolveVaultPath
111
+ });
102
112
 
103
113
  return program;
104
114
  }
@@ -33,6 +33,16 @@ function extractTitle(content) {
33
33
  const match = content.match(/^#\s+(.+)$/m);
34
34
  return match ? match[1].trim() : "";
35
35
  }
36
+ function parseDueDate(value) {
37
+ if (!value) return null;
38
+ const timestamp = Date.parse(value);
39
+ if (Number.isNaN(timestamp)) return null;
40
+ return timestamp;
41
+ }
42
+ function startOfToday() {
43
+ const now = /* @__PURE__ */ new Date();
44
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
45
+ }
36
46
  function readTask(vaultPath, slug) {
37
47
  const taskPath = getTaskPath(vaultPath, slug);
38
48
  if (!fs.existsSync(taskPath)) {
@@ -80,6 +90,7 @@ function listTasks(vaultPath, filters) {
80
90
  }
81
91
  const tasks = [];
82
92
  const entries = fs.readdirSync(tasksDir, { withFileTypes: true });
93
+ const today = startOfToday();
83
94
  for (const entry of entries) {
84
95
  if (!entry.isFile() || !entry.name.endsWith(".md")) {
85
96
  continue;
@@ -92,6 +103,16 @@ function listTasks(vaultPath, filters) {
92
103
  if (filters.owner && task.frontmatter.owner !== filters.owner) continue;
93
104
  if (filters.project && task.frontmatter.project !== filters.project) continue;
94
105
  if (filters.priority && task.frontmatter.priority !== filters.priority) continue;
106
+ if (filters.due && !task.frontmatter.due) continue;
107
+ if (filters.tag) {
108
+ const tags = task.frontmatter.tags || [];
109
+ const hasTag = tags.some((tag) => tag.toLowerCase() === filters.tag?.toLowerCase());
110
+ if (!hasTag) continue;
111
+ }
112
+ if (filters.overdue) {
113
+ const dueTime = parseDueDate(task.frontmatter.due);
114
+ if (task.frontmatter.status === "done" || dueTime === null || dueTime >= today) continue;
115
+ }
95
116
  }
96
117
  tasks.push(task);
97
118
  }
@@ -101,6 +122,18 @@ function listTasks(vaultPath, filters) {
101
122
  medium: 2,
102
123
  low: 3
103
124
  };
125
+ if (filters?.due || filters?.overdue) {
126
+ return tasks.sort((a, b) => {
127
+ const aDue = parseDueDate(a.frontmatter.due);
128
+ const bDue = parseDueDate(b.frontmatter.due);
129
+ if (aDue !== null && bDue !== null && aDue !== bDue) {
130
+ return aDue - bDue;
131
+ }
132
+ if (aDue !== null && bDue === null) return -1;
133
+ if (aDue === null && bDue !== null) return 1;
134
+ return new Date(b.frontmatter.created).getTime() - new Date(a.frontmatter.created).getTime();
135
+ });
136
+ }
104
137
  return tasks.sort((a, b) => {
105
138
  const aPriority = priorityOrder[a.frontmatter.priority || "low"];
106
139
  const bPriority = priorityOrder[b.frontmatter.priority || "low"];
@@ -153,6 +186,10 @@ function createTask(vaultPath, title, options = {}) {
153
186
  if (options.priority) frontmatter.priority = options.priority;
154
187
  if (options.due) frontmatter.due = options.due;
155
188
  if (options.tags && options.tags.length > 0) frontmatter.tags = options.tags;
189
+ if (options.description) frontmatter.description = options.description;
190
+ if (options.estimate) frontmatter.estimate = options.estimate;
191
+ if (options.parent) frontmatter.parent = options.parent;
192
+ if (options.depends_on && options.depends_on.length > 0) frontmatter.depends_on = options.depends_on;
156
193
  let content = `# ${title}
157
194
  `;
158
195
  const links = [];
@@ -188,14 +225,129 @@ function updateTask(vaultPath, slug, updates) {
188
225
  ...task.frontmatter,
189
226
  updated: now
190
227
  };
191
- if (updates.status !== void 0) newFrontmatter.status = updates.status;
192
- if (updates.owner !== void 0) newFrontmatter.owner = updates.owner;
193
- if (updates.project !== void 0) newFrontmatter.project = updates.project;
194
- if (updates.priority !== void 0) newFrontmatter.priority = updates.priority;
195
- if (updates.due !== void 0) newFrontmatter.due = updates.due;
196
- if (updates.tags !== void 0) newFrontmatter.tags = updates.tags;
228
+ if (updates.status !== void 0) {
229
+ newFrontmatter.status = updates.status;
230
+ if (updates.status === "done" && !newFrontmatter.completed) {
231
+ newFrontmatter.completed = now;
232
+ }
233
+ if (updates.status !== "done") {
234
+ delete newFrontmatter.completed;
235
+ }
236
+ }
237
+ if (updates.source !== void 0) {
238
+ if (updates.source === null || updates.source.trim() === "") {
239
+ delete newFrontmatter.source;
240
+ } else {
241
+ newFrontmatter.source = updates.source;
242
+ }
243
+ }
244
+ if (updates.owner !== void 0) {
245
+ if (updates.owner === null || updates.owner.trim() === "") {
246
+ delete newFrontmatter.owner;
247
+ } else {
248
+ newFrontmatter.owner = updates.owner;
249
+ }
250
+ }
251
+ if (updates.project !== void 0) {
252
+ if (updates.project === null || updates.project.trim() === "") {
253
+ delete newFrontmatter.project;
254
+ } else {
255
+ newFrontmatter.project = updates.project;
256
+ }
257
+ }
258
+ if (updates.priority !== void 0) {
259
+ if (updates.priority === null) {
260
+ delete newFrontmatter.priority;
261
+ } else {
262
+ newFrontmatter.priority = updates.priority;
263
+ }
264
+ }
265
+ if (updates.due !== void 0) {
266
+ if (updates.due === null || updates.due.trim() === "") {
267
+ delete newFrontmatter.due;
268
+ } else {
269
+ newFrontmatter.due = updates.due;
270
+ }
271
+ }
272
+ if (updates.tags !== void 0) {
273
+ if (updates.tags === null) {
274
+ delete newFrontmatter.tags;
275
+ } else {
276
+ const normalizedTags = updates.tags.map((tag) => tag.trim()).filter(Boolean);
277
+ if (normalizedTags.length === 0) {
278
+ delete newFrontmatter.tags;
279
+ } else {
280
+ newFrontmatter.tags = normalizedTags;
281
+ }
282
+ }
283
+ }
284
+ if (updates.completed !== void 0) {
285
+ if (updates.completed === null || updates.completed.trim() === "") {
286
+ delete newFrontmatter.completed;
287
+ } else {
288
+ newFrontmatter.completed = updates.completed;
289
+ }
290
+ }
291
+ if (updates.escalation !== void 0) {
292
+ if (updates.escalation === null) {
293
+ delete newFrontmatter.escalation;
294
+ } else {
295
+ newFrontmatter.escalation = updates.escalation;
296
+ }
297
+ }
298
+ if (updates.confidence !== void 0) {
299
+ if (updates.confidence === null) {
300
+ delete newFrontmatter.confidence;
301
+ } else {
302
+ newFrontmatter.confidence = updates.confidence;
303
+ }
304
+ }
305
+ if (updates.reason !== void 0) {
306
+ if (updates.reason === null || updates.reason.trim() === "") {
307
+ delete newFrontmatter.reason;
308
+ } else {
309
+ newFrontmatter.reason = updates.reason;
310
+ }
311
+ }
312
+ if (updates.description !== void 0) {
313
+ if (updates.description === null || updates.description.trim() === "") {
314
+ delete newFrontmatter.description;
315
+ } else {
316
+ newFrontmatter.description = updates.description;
317
+ }
318
+ }
319
+ if (updates.estimate !== void 0) {
320
+ if (updates.estimate === null || updates.estimate.trim() === "") {
321
+ delete newFrontmatter.estimate;
322
+ } else {
323
+ newFrontmatter.estimate = updates.estimate;
324
+ }
325
+ }
326
+ if (updates.parent !== void 0) {
327
+ if (updates.parent === null || updates.parent.trim() === "") {
328
+ delete newFrontmatter.parent;
329
+ } else {
330
+ newFrontmatter.parent = updates.parent;
331
+ }
332
+ }
333
+ if (updates.depends_on !== void 0) {
334
+ if (updates.depends_on === null) {
335
+ delete newFrontmatter.depends_on;
336
+ } else {
337
+ const normalizedDeps = updates.depends_on.map((dep) => dep.trim()).filter(Boolean);
338
+ if (normalizedDeps.length === 0) {
339
+ delete newFrontmatter.depends_on;
340
+ } else {
341
+ newFrontmatter.depends_on = normalizedDeps;
342
+ }
343
+ }
344
+ }
197
345
  if (updates.blocked_by !== void 0) {
198
- newFrontmatter.blocked_by = updates.blocked_by;
346
+ if (updates.blocked_by === null || updates.blocked_by.trim() === "") {
347
+ delete newFrontmatter.blocked_by;
348
+ } else {
349
+ newFrontmatter.blocked_by = updates.blocked_by;
350
+ }
199
351
  } else if (updates.status && updates.status !== "blocked") {
200
352
  delete newFrontmatter.blocked_by;
201
353
  }
@@ -310,6 +462,15 @@ function getActiveTasks(vaultPath, filters) {
310
462
  const allTasks = listTasks(vaultPath, filters);
311
463
  return allTasks.filter((t) => t.frontmatter.status === "open" || t.frontmatter.status === "in-progress");
312
464
  }
465
+ function listSubtasks(vaultPath, parentSlug) {
466
+ return listTasks(vaultPath).filter((task) => task.frontmatter.parent === parentSlug);
467
+ }
468
+ function listDependentTasks(vaultPath, dependencySlug) {
469
+ return listTasks(vaultPath).filter((task) => {
470
+ const dependencies = task.frontmatter.depends_on || [];
471
+ return dependencies.includes(dependencySlug);
472
+ });
473
+ }
313
474
  function getRecentlyCompletedTasks(vaultPath, limit = 10) {
314
475
  const allTasks = listTasks(vaultPath, { status: "done" });
315
476
  return allTasks.filter((t) => t.frontmatter.completed).sort((a, b) => {
@@ -367,6 +528,8 @@ export {
367
528
  promoteBacklogItem,
368
529
  getBlockedTasks,
369
530
  getActiveTasks,
531
+ listSubtasks,
532
+ listDependentTasks,
370
533
  getRecentlyCompletedTasks,
371
534
  getStatusIcon,
372
535
  getStatusDisplay
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-P5EPF6MB.js";
7
7
  import {
8
8
  Observer
9
- } from "./chunk-RXEIQ3KQ.js";
9
+ } from "./chunk-H6WQUUNK.js";
10
10
  import {
11
11
  resolveVaultPath
12
12
  } from "./chunk-MXSSG3QU.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-AHGUJG76.js";
4
4
  import {
5
5
  Observer
6
- } from "./chunk-RXEIQ3KQ.js";
6
+ } from "./chunk-H6WQUUNK.js";
7
7
  import {
8
8
  resolveVaultPath
9
9
  } from "./chunk-MXSSG3QU.js";
@@ -4,7 +4,7 @@ import {
4
4
  listTasks,
5
5
  updateBacklogItem,
6
6
  updateTask
7
- } from "./chunk-DEFBIVQ3.js";
7
+ } from "./chunk-6QLRSPLZ.js";
8
8
  import {
9
9
  DEFAULT_CATEGORIES
10
10
  } from "./chunk-3PJIGGWV.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Observer
3
- } from "./chunk-RXEIQ3KQ.js";
3
+ } from "./chunk-H6WQUUNK.js";
4
4
  import {
5
5
  resolveVaultPath
6
6
  } from "./chunk-MXSSG3QU.js";