clawvault 2.4.5 → 2.4.7
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 +10 -0
- package/bin/command-registration.test.js +1 -1
- package/bin/help-contract.test.js +1 -0
- package/bin/register-config-route-commands.test.js +8 -1
- package/bin/register-core-commands.js +3 -3
- package/bin/register-kanban-commands.js +56 -0
- package/bin/register-kanban-commands.test.js +83 -0
- package/bin/register-project-commands.js +209 -0
- package/bin/register-project-commands.test.js +201 -0
- package/bin/register-query-commands.js +40 -0
- package/bin/register-task-commands.js +60 -25
- package/bin/register-task-commands.test.js +49 -4
- package/bin/test-helpers/cli-command-fixtures.js +15 -0
- package/dist/{chunk-3PJIGGWV.js → chunk-2CDEETQN.js} +1 -0
- package/dist/{chunk-B3SMJZIZ.js → chunk-2RK2AG32.js} +5 -5
- package/dist/chunk-5GZFTAL7.js +340 -0
- package/dist/{chunk-YIRWDQKA.js → chunk-6RQPD7X6.js} +3 -4
- package/dist/{chunk-HNMFXFYP.js → chunk-7OHQFMJK.js} +2 -1
- package/dist/{chunk-4JJL47IJ.js → chunk-C3PF7WBA.js} +2 -2
- package/dist/{chunk-JXY6T5R7.js → chunk-FW465EEA.js} +1 -1
- package/dist/{chunk-BI6SGGZP.js → chunk-G3OQJ2NQ.js} +1 -1
- package/dist/chunk-GSD4ALSI.js +724 -0
- package/dist/chunk-IOALNTAN.js +757 -0
- package/dist/chunk-ITPEXLHA.js +528 -0
- package/dist/chunk-J5EMBUPK.js +399 -0
- package/dist/chunk-K3CDT7IH.js +122 -0
- package/dist/{chunk-AHGUJG76.js → chunk-KCCHROBR.js} +13 -69
- package/dist/{chunk-U2ONVV7N.js → chunk-LMCC5OC7.js} +2 -2
- package/dist/{chunk-QALB2V3E.js → chunk-MQUJNOHK.js} +1 -1
- package/dist/{chunk-RXEIQ3KQ.js → chunk-TMZMN7OS.js} +334 -457
- package/dist/{chunk-HVTTYDCJ.js → chunk-VR5NE7PZ.js} +1 -1
- package/dist/{chunk-22WE3J4F.js → chunk-WIICLBNF.js} +35 -4
- package/dist/chunk-YCVDVI5B.js +273 -0
- package/dist/{chunk-NAMFB7ZA.js → chunk-Z2XBWN7A.js} +0 -2
- package/dist/commands/archive.js +3 -3
- package/dist/commands/backlog.js +1 -1
- package/dist/commands/blocked.js +1 -1
- package/dist/commands/canvas.d.ts +1 -14
- package/dist/commands/canvas.js +123 -1543
- package/dist/commands/context.js +5 -6
- package/dist/commands/doctor.js +5 -5
- package/dist/commands/inject.d.ts +2 -0
- package/dist/commands/inject.js +14 -0
- package/dist/commands/kanban.d.ts +63 -0
- package/dist/commands/kanban.js +21 -0
- package/dist/commands/migrate-observations.js +2 -2
- package/dist/commands/observe.js +8 -6
- package/dist/commands/project.d.ts +85 -0
- package/dist/commands/project.js +411 -0
- package/dist/commands/rebuild.js +7 -5
- package/dist/commands/reflect.js +5 -4
- package/dist/commands/replay.js +10 -7
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.d.ts +1 -1
- package/dist/commands/sleep.js +11 -8
- package/dist/commands/status.js +5 -5
- package/dist/commands/task.d.ts +20 -8
- package/dist/commands/task.js +11 -244
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +4 -4
- package/dist/index.d.ts +76 -106
- package/dist/index.js +99 -34
- package/dist/inject-x65KXWPk.d.ts +137 -0
- package/dist/lib/project-utils.d.ts +97 -0
- package/dist/lib/project-utils.js +19 -0
- package/dist/lib/task-utils.d.ts +48 -12
- package/dist/lib/task-utils.js +5 -1
- package/dist/{types-DMU3SuAV.d.ts → types-jjuYN2Xn.d.ts} +1 -1
- package/package.json +2 -2
- package/dist/chunk-DEFBIVQ3.js +0 -373
- package/dist/chunk-L3DJ36BZ.js +0 -40
- package/dist/chunk-UMMCYTJV.js +0 -105
package/bin/clawvault.js
CHANGED
|
@@ -18,6 +18,8 @@ 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';
|
|
22
|
+
import { registerProjectCommands } from './register-project-commands.js';
|
|
21
23
|
|
|
22
24
|
import { registerTaskCommands } from './register-task-commands.js';
|
|
23
25
|
|
|
@@ -93,6 +95,14 @@ registerTaskCommands(program, {
|
|
|
93
95
|
chalk,
|
|
94
96
|
resolveVaultPath
|
|
95
97
|
});
|
|
98
|
+
registerKanbanCommands(program, {
|
|
99
|
+
chalk,
|
|
100
|
+
resolveVaultPath
|
|
101
|
+
});
|
|
102
|
+
registerProjectCommands(program, {
|
|
103
|
+
chalk,
|
|
104
|
+
resolveVaultPath
|
|
105
|
+
});
|
|
96
106
|
|
|
97
107
|
registerTailscaleCommands(program, { chalk });
|
|
98
108
|
registerConfigCommands(program, { chalk, resolveVaultPath });
|
|
@@ -49,7 +49,7 @@ describe('CLI command registration modules', () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const names = listCommandNames(program);
|
|
52
|
-
expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'observe', 'reflect', 'session-recap']));
|
|
52
|
+
expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'inject', 'observe', 'reflect', 'session-recap']));
|
|
53
53
|
|
|
54
54
|
const contextCommand = program.commands.find((command) => command.name() === 'context');
|
|
55
55
|
const profileOption = contextCommand?.options.find((option) => option.flags.includes('--profile <profile>'));
|
|
@@ -6,6 +6,7 @@ describe('CLI help contract', () => {
|
|
|
6
6
|
const help = registerAllCommandModules().helpInformation();
|
|
7
7
|
expect(help).toContain('init');
|
|
8
8
|
expect(help).toContain('context');
|
|
9
|
+
expect(help).toContain('inject');
|
|
9
10
|
expect(help).toContain('compat');
|
|
10
11
|
expect(help).toContain('graph');
|
|
11
12
|
expect(help).toContain('reflect');
|
|
@@ -31,9 +31,16 @@ vi.mock('../dist/index.js', () => ({
|
|
|
31
31
|
'theme',
|
|
32
32
|
'observe.model',
|
|
33
33
|
'observe.provider',
|
|
34
|
+
'observer.compression.provider',
|
|
35
|
+
'observer.compression.model',
|
|
36
|
+
'observer.compression.baseUrl',
|
|
37
|
+
'observer.compression.apiKey',
|
|
34
38
|
'context.maxResults',
|
|
35
39
|
'context.defaultProfile',
|
|
36
|
-
'graph.maxHops'
|
|
40
|
+
'graph.maxHops',
|
|
41
|
+
'inject.maxResults',
|
|
42
|
+
'inject.useLlm',
|
|
43
|
+
'inject.scope'
|
|
37
44
|
],
|
|
38
45
|
getConfigValue: getConfigValueMock,
|
|
39
46
|
setConfigValue: setConfigValueMock,
|
|
@@ -17,7 +17,7 @@ export function registerCoreCommands(
|
|
|
17
17
|
.option('--no-tasks', 'Skip tasks/ and backlog/ directories')
|
|
18
18
|
.option('--no-graph', 'Skip initial graph build')
|
|
19
19
|
.option('--categories <list>', 'Comma-separated list of custom categories to create')
|
|
20
|
-
.option('--canvas
|
|
20
|
+
.option('--canvas', 'Generate a vault status canvas dashboard on init')
|
|
21
21
|
.option('--theme <style>', 'Graph color theme to apply (neural, minimal, none)', 'none')
|
|
22
22
|
.option('--minimal', 'Create minimal vault (memory categories only, no tasks/bases/graph)')
|
|
23
23
|
.action(async (vaultPath, options) => {
|
|
@@ -106,7 +106,7 @@ export function registerCoreCommands(
|
|
|
106
106
|
await setupCommand({
|
|
107
107
|
graphColors: false,
|
|
108
108
|
bases: false,
|
|
109
|
-
canvas:
|
|
109
|
+
canvas: true,
|
|
110
110
|
theme: 'none',
|
|
111
111
|
vault: resolvedPath
|
|
112
112
|
});
|
|
@@ -138,7 +138,7 @@ export function registerCoreCommands(
|
|
|
138
138
|
.option('--no-graph-colors', 'Skip graph color configuration')
|
|
139
139
|
.option('--bases', 'Generate Obsidian Bases views for task management')
|
|
140
140
|
.option('--no-bases', 'Skip Bases file generation')
|
|
141
|
-
.option('--canvas
|
|
141
|
+
.option('--canvas', 'Generate vault status canvas dashboard')
|
|
142
142
|
.option('--no-canvas', 'Skip canvas generation')
|
|
143
143
|
.option('--theme <style>', 'Graph color theme (neural, minimal, none)', 'neural')
|
|
144
144
|
.option('--force', 'Overwrite existing configuration files')
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project command registrations for ClawVault
|
|
3
|
+
* Registers project add/update/archive/list/show/tasks/board commands
|
|
4
|
+
*/
|
|
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
|
+
export function registerProjectCommands(
|
|
16
|
+
program,
|
|
17
|
+
{ chalk, resolveVaultPath }
|
|
18
|
+
) {
|
|
19
|
+
const projectCmd = program
|
|
20
|
+
.command('project')
|
|
21
|
+
.description('Project management');
|
|
22
|
+
|
|
23
|
+
projectCmd
|
|
24
|
+
.command('add <title>')
|
|
25
|
+
.description('Add a new project')
|
|
26
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
27
|
+
.option('--owner <owner>', 'Project owner')
|
|
28
|
+
.option('--status <status>', 'Project status (active, paused, completed, archived)')
|
|
29
|
+
.option('--team <team>', 'Comma-separated team members')
|
|
30
|
+
.option('--client <client>', 'Client name')
|
|
31
|
+
.option('--tags <tags>', 'Comma-separated tags')
|
|
32
|
+
.option('--description <description>', 'One-line project summary')
|
|
33
|
+
.option('--deadline <date>', 'Deadline (YYYY-MM-DD)')
|
|
34
|
+
.option('--repo <url>', 'Repository URL')
|
|
35
|
+
.option('--url <url>', 'Production URL')
|
|
36
|
+
.action(async (title, options) => {
|
|
37
|
+
try {
|
|
38
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
39
|
+
const { projectCommand } = await import('../dist/commands/project.js');
|
|
40
|
+
await projectCommand(vaultPath, 'add', {
|
|
41
|
+
title,
|
|
42
|
+
options: {
|
|
43
|
+
owner: options.owner,
|
|
44
|
+
status: options.status,
|
|
45
|
+
team: parseCsvList(options.team),
|
|
46
|
+
client: options.client,
|
|
47
|
+
tags: parseCsvList(options.tags),
|
|
48
|
+
description: options.description,
|
|
49
|
+
deadline: options.deadline,
|
|
50
|
+
repo: options.repo,
|
|
51
|
+
url: options.url
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
projectCmd
|
|
61
|
+
.command('update <slug>')
|
|
62
|
+
.description('Update a project')
|
|
63
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
64
|
+
.option('--status <status>', 'Project status (active, paused, completed, archived)')
|
|
65
|
+
.option('--owner <owner>', 'Project owner')
|
|
66
|
+
.option('--team <team>', 'Comma-separated team members')
|
|
67
|
+
.option('--client <client>', 'Client name')
|
|
68
|
+
.option('--tags <tags>', 'Comma-separated tags')
|
|
69
|
+
.option('--description <description>', 'One-line project summary')
|
|
70
|
+
.option('--deadline <date>', 'Deadline (YYYY-MM-DD)')
|
|
71
|
+
.option('--repo <url>', 'Repository URL')
|
|
72
|
+
.option('--url <url>', 'Production URL')
|
|
73
|
+
.action(async (slug, options) => {
|
|
74
|
+
try {
|
|
75
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
76
|
+
const { projectCommand } = await import('../dist/commands/project.js');
|
|
77
|
+
await projectCommand(vaultPath, 'update', {
|
|
78
|
+
slug,
|
|
79
|
+
options: {
|
|
80
|
+
status: options.status,
|
|
81
|
+
owner: options.owner,
|
|
82
|
+
team: parseCsvList(options.team),
|
|
83
|
+
client: options.client,
|
|
84
|
+
tags: parseCsvList(options.tags),
|
|
85
|
+
description: options.description,
|
|
86
|
+
deadline: options.deadline,
|
|
87
|
+
repo: options.repo,
|
|
88
|
+
url: options.url
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
projectCmd
|
|
98
|
+
.command('archive <slug>')
|
|
99
|
+
.description('Archive a project')
|
|
100
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
101
|
+
.option('--reason <reason>', 'Reason for archiving')
|
|
102
|
+
.action(async (slug, options) => {
|
|
103
|
+
try {
|
|
104
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
105
|
+
const { projectCommand } = await import('../dist/commands/project.js');
|
|
106
|
+
await projectCommand(vaultPath, 'archive', {
|
|
107
|
+
slug,
|
|
108
|
+
options: {
|
|
109
|
+
reason: options.reason
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
projectCmd
|
|
119
|
+
.command('list')
|
|
120
|
+
.description('List projects')
|
|
121
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
122
|
+
.option('--status <status>', 'Filter by status')
|
|
123
|
+
.option('--owner <owner>', 'Filter by owner')
|
|
124
|
+
.option('--client <client>', 'Filter by client')
|
|
125
|
+
.option('--tag <tag>', 'Filter by tag')
|
|
126
|
+
.option('--json', 'Output as JSON')
|
|
127
|
+
.action(async (options) => {
|
|
128
|
+
try {
|
|
129
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
130
|
+
const { projectCommand } = await import('../dist/commands/project.js');
|
|
131
|
+
await projectCommand(vaultPath, 'list', {
|
|
132
|
+
options: {
|
|
133
|
+
status: options.status,
|
|
134
|
+
owner: options.owner,
|
|
135
|
+
client: options.client,
|
|
136
|
+
tag: options.tag,
|
|
137
|
+
json: options.json
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
projectCmd
|
|
147
|
+
.command('show <slug>')
|
|
148
|
+
.description('Show project details')
|
|
149
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
150
|
+
.option('--json', 'Output as JSON')
|
|
151
|
+
.action(async (slug, options) => {
|
|
152
|
+
try {
|
|
153
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
154
|
+
const { projectCommand } = await import('../dist/commands/project.js');
|
|
155
|
+
await projectCommand(vaultPath, 'show', {
|
|
156
|
+
slug,
|
|
157
|
+
options: {
|
|
158
|
+
json: options.json
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
projectCmd
|
|
168
|
+
.command('tasks <slug>')
|
|
169
|
+
.description('List tasks for a project')
|
|
170
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
171
|
+
.option('--json', 'Output as JSON')
|
|
172
|
+
.action(async (slug, options) => {
|
|
173
|
+
try {
|
|
174
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
175
|
+
const { projectCommand } = await import('../dist/commands/project.js');
|
|
176
|
+
await projectCommand(vaultPath, 'tasks', {
|
|
177
|
+
slug,
|
|
178
|
+
options: {
|
|
179
|
+
json: options.json
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
projectCmd
|
|
189
|
+
.command('board')
|
|
190
|
+
.description('Generate project kanban board')
|
|
191
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
192
|
+
.option('--output <path>', 'Board markdown path (default: Projects-Board.md)')
|
|
193
|
+
.option('--group-by <field>', 'Grouping field (status, owner, client)')
|
|
194
|
+
.action(async (options) => {
|
|
195
|
+
try {
|
|
196
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
197
|
+
const { projectCommand } = await import('../dist/commands/project.js');
|
|
198
|
+
await projectCommand(vaultPath, 'board', {
|
|
199
|
+
options: {
|
|
200
|
+
output: options.output,
|
|
201
|
+
groupBy: options.groupBy
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
} catch (err) {
|
|
205
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerProjectCommands } from './register-project-commands.js';
|
|
4
|
+
import { chalkStub } from './test-helpers/cli-command-fixtures.js';
|
|
5
|
+
|
|
6
|
+
const { projectCommandMock } = vi.hoisted(() => ({
|
|
7
|
+
projectCommandMock: vi.fn()
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('../dist/commands/project.js', () => ({
|
|
11
|
+
projectCommand: projectCommandMock
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
function buildProgram() {
|
|
15
|
+
const program = new Command();
|
|
16
|
+
registerProjectCommands(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-project-commands', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('registers add/update/archive/list/show/tasks/board subcommands', () => {
|
|
34
|
+
const program = buildProgram();
|
|
35
|
+
const projectCommand = program.commands.find((command) => command.name() === 'project');
|
|
36
|
+
expect(projectCommand).toBeDefined();
|
|
37
|
+
|
|
38
|
+
const subcommandNames = projectCommand?.commands.map((command) => command.name()) ?? [];
|
|
39
|
+
expect(subcommandNames).toEqual(expect.arrayContaining([
|
|
40
|
+
'add',
|
|
41
|
+
'update',
|
|
42
|
+
'archive',
|
|
43
|
+
'list',
|
|
44
|
+
'show',
|
|
45
|
+
'tasks',
|
|
46
|
+
'board'
|
|
47
|
+
]));
|
|
48
|
+
|
|
49
|
+
const addCommand = projectCommand?.commands.find((command) => command.name() === 'add');
|
|
50
|
+
const addFlags = addCommand?.options.map((option) => option.flags) ?? [];
|
|
51
|
+
expect(addFlags).toEqual(expect.arrayContaining([
|
|
52
|
+
'--owner <owner>',
|
|
53
|
+
'--status <status>',
|
|
54
|
+
'--team <team>',
|
|
55
|
+
'--client <client>',
|
|
56
|
+
'--tags <tags>',
|
|
57
|
+
'--description <description>',
|
|
58
|
+
'--deadline <date>',
|
|
59
|
+
'--repo <url>',
|
|
60
|
+
'--url <url>'
|
|
61
|
+
]));
|
|
62
|
+
|
|
63
|
+
const listCommand = projectCommand?.commands.find((command) => command.name() === 'list');
|
|
64
|
+
const listFlags = listCommand?.options.map((option) => option.flags) ?? [];
|
|
65
|
+
expect(listFlags).toEqual(expect.arrayContaining([
|
|
66
|
+
'--status <status>',
|
|
67
|
+
'--owner <owner>',
|
|
68
|
+
'--client <client>',
|
|
69
|
+
'--tag <tag>',
|
|
70
|
+
'--json'
|
|
71
|
+
]));
|
|
72
|
+
|
|
73
|
+
const boardCommand = projectCommand?.commands.find((command) => command.name() === 'board');
|
|
74
|
+
const boardFlags = boardCommand?.options.map((option) => option.flags) ?? [];
|
|
75
|
+
expect(boardFlags).toEqual(expect.arrayContaining([
|
|
76
|
+
'--output <path>',
|
|
77
|
+
'--group-by <field>'
|
|
78
|
+
]));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('dispatches project subcommands to project command handler', async () => {
|
|
82
|
+
await runCommand([
|
|
83
|
+
'project',
|
|
84
|
+
'add',
|
|
85
|
+
'Apollo Launch',
|
|
86
|
+
'--owner',
|
|
87
|
+
'alice',
|
|
88
|
+
'--status',
|
|
89
|
+
'active',
|
|
90
|
+
'--team',
|
|
91
|
+
'alice,bob',
|
|
92
|
+
'--client',
|
|
93
|
+
'Acme',
|
|
94
|
+
'--tags',
|
|
95
|
+
'platform,release',
|
|
96
|
+
'--description',
|
|
97
|
+
'Launch project',
|
|
98
|
+
'--deadline',
|
|
99
|
+
'2026-03-01',
|
|
100
|
+
'--repo',
|
|
101
|
+
'https://github.com/acme/apollo',
|
|
102
|
+
'--url',
|
|
103
|
+
'https://apollo.acme.dev'
|
|
104
|
+
]);
|
|
105
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'add', {
|
|
106
|
+
title: 'Apollo Launch',
|
|
107
|
+
options: {
|
|
108
|
+
owner: 'alice',
|
|
109
|
+
status: 'active',
|
|
110
|
+
team: ['alice', 'bob'],
|
|
111
|
+
client: 'Acme',
|
|
112
|
+
tags: ['platform', 'release'],
|
|
113
|
+
description: 'Launch project',
|
|
114
|
+
deadline: '2026-03-01',
|
|
115
|
+
repo: 'https://github.com/acme/apollo',
|
|
116
|
+
url: 'https://apollo.acme.dev'
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await runCommand([
|
|
121
|
+
'project',
|
|
122
|
+
'update',
|
|
123
|
+
'apollo-launch',
|
|
124
|
+
'--status',
|
|
125
|
+
'paused',
|
|
126
|
+
'--owner',
|
|
127
|
+
'carol',
|
|
128
|
+
'--team',
|
|
129
|
+
'carol,dave',
|
|
130
|
+
'--client',
|
|
131
|
+
'Acme',
|
|
132
|
+
'--tags',
|
|
133
|
+
'ops',
|
|
134
|
+
'--description',
|
|
135
|
+
'Paused pending review',
|
|
136
|
+
'--deadline',
|
|
137
|
+
'2026-03-10',
|
|
138
|
+
'--repo',
|
|
139
|
+
'https://github.com/acme/apollo-v2',
|
|
140
|
+
'--url',
|
|
141
|
+
'https://staging.acme.dev'
|
|
142
|
+
]);
|
|
143
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'update', {
|
|
144
|
+
slug: 'apollo-launch',
|
|
145
|
+
options: {
|
|
146
|
+
status: 'paused',
|
|
147
|
+
owner: 'carol',
|
|
148
|
+
team: ['carol', 'dave'],
|
|
149
|
+
client: 'Acme',
|
|
150
|
+
tags: ['ops'],
|
|
151
|
+
description: 'Paused pending review',
|
|
152
|
+
deadline: '2026-03-10',
|
|
153
|
+
repo: 'https://github.com/acme/apollo-v2',
|
|
154
|
+
url: 'https://staging.acme.dev'
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await runCommand(['project', 'archive', 'apollo-launch', '--reason', 'Client offboarded']);
|
|
159
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'archive', {
|
|
160
|
+
slug: 'apollo-launch',
|
|
161
|
+
options: {
|
|
162
|
+
reason: 'Client offboarded'
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await runCommand(['project', 'list', '--status', 'active', '--owner', 'alice', '--client', 'Acme', '--tag', 'platform', '--json']);
|
|
167
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'list', {
|
|
168
|
+
options: {
|
|
169
|
+
status: 'active',
|
|
170
|
+
owner: 'alice',
|
|
171
|
+
client: 'Acme',
|
|
172
|
+
tag: 'platform',
|
|
173
|
+
json: true
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await runCommand(['project', 'show', 'apollo-launch']);
|
|
178
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'show', {
|
|
179
|
+
slug: 'apollo-launch',
|
|
180
|
+
options: {
|
|
181
|
+
json: undefined
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await runCommand(['project', 'tasks', 'apollo-launch', '--json']);
|
|
186
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'tasks', {
|
|
187
|
+
slug: 'apollo-launch',
|
|
188
|
+
options: {
|
|
189
|
+
json: true
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await runCommand(['project', 'board', '--output', 'Projects-Board.md', '--group-by', 'client']);
|
|
194
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'board', {
|
|
195
|
+
options: {
|
|
196
|
+
output: 'Projects-Board.md',
|
|
197
|
+
groupBy: 'client'
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -168,6 +168,46 @@ export function registerQueryCommands(
|
|
|
168
168
|
}
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
// === INJECT ===
|
|
172
|
+
program
|
|
173
|
+
.command('inject <message>')
|
|
174
|
+
.description('Inject relevant rules, decisions, and preferences for prompt context')
|
|
175
|
+
.option('-n, --max-results <n>', 'Maximum injected items')
|
|
176
|
+
.option('--scope <scope>', 'Comma-separated scope filter override')
|
|
177
|
+
.option('--enable-llm', 'Enable optional LLM fuzzy intent matching')
|
|
178
|
+
.option('--disable-llm', 'Disable optional LLM fuzzy intent matching')
|
|
179
|
+
.option('--format <format>', 'Output format (markdown|json)', 'markdown')
|
|
180
|
+
.option('--model <model>', 'Override LLM model when fuzzy matching is enabled')
|
|
181
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
182
|
+
.action(async (message, options) => {
|
|
183
|
+
try {
|
|
184
|
+
const parsedMaxResults = options.maxResults
|
|
185
|
+
? Number.parseInt(options.maxResults, 10)
|
|
186
|
+
: undefined;
|
|
187
|
+
if (options.maxResults && (!Number.isFinite(parsedMaxResults) || parsedMaxResults <= 0)) {
|
|
188
|
+
throw new Error(`Invalid --max-results value: ${options.maxResults}`);
|
|
189
|
+
}
|
|
190
|
+
const useLlm = options.enableLlm
|
|
191
|
+
? true
|
|
192
|
+
: options.disableLlm
|
|
193
|
+
? false
|
|
194
|
+
: undefined;
|
|
195
|
+
|
|
196
|
+
const { injectCommand } = await import('../dist/commands/inject.js');
|
|
197
|
+
await injectCommand(message, {
|
|
198
|
+
vaultPath: resolveVaultPath(options.vault),
|
|
199
|
+
maxResults: parsedMaxResults,
|
|
200
|
+
useLlm,
|
|
201
|
+
scope: options.scope,
|
|
202
|
+
format: options.format === 'json' ? 'json' : 'markdown',
|
|
203
|
+
model: options.model
|
|
204
|
+
});
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
171
211
|
// === OBSERVE ===
|
|
172
212
|
program
|
|
173
213
|
.command('observe')
|