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 +5 -0
- package/bin/register-kanban-commands.js +56 -0
- package/bin/register-kanban-commands.test.js +83 -0
- package/bin/register-task-commands.js +58 -7
- package/bin/register-task-commands.test.js +46 -0
- package/bin/test-helpers/cli-command-fixtures.js +10 -0
- package/dist/{chunk-DEFBIVQ3.js → chunk-6QLRSPLZ.js} +170 -7
- package/dist/{chunk-B3SMJZIZ.js → chunk-FD2ZA65C.js} +1 -1
- package/dist/{chunk-4JJL47IJ.js → chunk-FKQJB6XC.js} +1 -1
- package/dist/{chunk-RXEIQ3KQ.js → chunk-H6WQUUNK.js} +1 -1
- package/dist/{chunk-U2ONVV7N.js → chunk-JTO7NZLS.js} +1 -1
- package/dist/chunk-LLN5SPGL.js +399 -0
- package/dist/commands/backlog.js +1 -1
- package/dist/commands/blocked.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/kanban.d.ts +63 -0
- package/dist/commands/kanban.js +21 -0
- package/dist/commands/observe.js +3 -3
- package/dist/commands/rebuild.js +3 -3
- package/dist/commands/replay.js +3 -3
- package/dist/commands/setup.js +1 -1
- package/dist/commands/sleep.js +2 -2
- package/dist/commands/status.js +3 -3
- package/dist/commands/task.d.ts +18 -6
- package/dist/commands/task.js +69 -12
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -6
- package/dist/lib/task-utils.d.ts +41 -10
- package/dist/lib/task-utils.js +5 -1
- package/package.json +2 -2
- /package/dist/{chunk-YIRWDQKA.js → chunk-P2ZH6AN5.js} +0 -0
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)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|