clawvault 2.4.3 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/bin/clawvault.js +4 -0
  2. package/bin/command-registration.test.js +13 -1
  3. package/bin/help-contract.test.js +2 -0
  4. package/bin/register-config-commands.js +153 -0
  5. package/bin/register-config-route-commands.test.js +114 -0
  6. package/bin/register-resilience-commands.js +37 -2
  7. package/bin/register-resilience-commands.test.js +81 -0
  8. package/bin/register-route-commands.js +114 -0
  9. package/bin/register-task-commands.js +42 -2
  10. package/bin/test-helpers/cli-command-fixtures.js +10 -0
  11. package/dist/{chunk-XDCFXFGH.js → chunk-22WE3J4F.js} +1 -1
  12. package/dist/chunk-3PJIGGWV.js +49 -0
  13. package/dist/{chunk-GBIDDDSL.js → chunk-4JJL47IJ.js} +1 -1
  14. package/dist/{chunk-FDJIZKCW.js → chunk-6B3JWM7J.js} +12 -48
  15. package/dist/{chunk-SNEMCQP7.js → chunk-B3SMJZIZ.js} +1 -1
  16. package/dist/{chunk-MZZJLQNQ.js → chunk-F55HGNU4.js} +6 -0
  17. package/dist/{chunk-BMOQI62Q.js → chunk-HNMFXFYP.js} +5 -3
  18. package/dist/chunk-OIWVQYQF.js +284 -0
  19. package/dist/{chunk-FEFPBHH4.js → chunk-RXEIQ3KQ.js} +452 -22
  20. package/dist/{chunk-DHJPXGC7.js → chunk-U2ONVV7N.js} +1 -1
  21. package/dist/chunk-UMMCYTJV.js +105 -0
  22. package/dist/{chunk-IFTEGE4D.js → chunk-YIRWDQKA.js} +4 -2
  23. package/dist/commands/blocked.d.ts +1 -0
  24. package/dist/commands/blocked.js +5 -1
  25. package/dist/commands/checkpoint.js +1 -1
  26. package/dist/commands/context.js +4 -3
  27. package/dist/commands/doctor.js +3 -2
  28. package/dist/commands/observe.js +3 -2
  29. package/dist/commands/rebuild.js +3 -2
  30. package/dist/commands/recover.d.ts +13 -1
  31. package/dist/commands/recover.js +10 -2
  32. package/dist/commands/replay.js +3 -2
  33. package/dist/commands/setup.js +3 -2
  34. package/dist/commands/sleep.js +5 -4
  35. package/dist/commands/status.js +3 -2
  36. package/dist/commands/task.d.ts +20 -6
  37. package/dist/commands/task.js +71 -4
  38. package/dist/commands/wake.js +5 -4
  39. package/dist/index.d.ts +76 -1
  40. package/dist/index.js +52 -14
  41. package/package.json +1 -1
  42. package/dist/chunk-IWYZAXKJ.js +0 -146
package/bin/clawvault.js CHANGED
@@ -16,6 +16,8 @@ import { registerResilienceCommands } from './register-resilience-commands.js';
16
16
  import { registerSessionLifecycleCommands } from './register-session-lifecycle-commands.js';
17
17
  import { registerTemplateCommands } from './register-template-commands.js';
18
18
  import { registerVaultOperationsCommands } from './register-vault-operations-commands.js';
19
+ import { registerConfigCommands } from './register-config-commands.js';
20
+ import { registerRouteCommands } from './register-route-commands.js';
19
21
 
20
22
  import { registerTaskCommands } from './register-task-commands.js';
21
23
 
@@ -93,6 +95,8 @@ registerTaskCommands(program, {
93
95
  });
94
96
 
95
97
  registerTailscaleCommands(program, { chalk });
98
+ registerConfigCommands(program, { chalk, resolveVaultPath });
99
+ registerRouteCommands(program, { chalk, resolveVaultPath });
96
100
 
97
101
  // Parse and run
98
102
  program.parse();
@@ -9,6 +9,8 @@ import { registerResilienceCommands } from './register-resilience-commands.js';
9
9
  import { registerSessionLifecycleCommands } from './register-session-lifecycle-commands.js';
10
10
  import { registerTemplateCommands } from './register-template-commands.js';
11
11
  import { registerVaultOperationsCommands } from './register-vault-operations-commands.js';
12
+ import { registerConfigCommands } from './register-config-commands.js';
13
+ import { registerRouteCommands } from './register-route-commands.js';
12
14
  import {
13
15
  chalkStub,
14
16
  createGetVaultStub,
@@ -107,6 +109,14 @@ describe('CLI command registration modules', () => {
107
109
  runQmd: async () => {}
108
110
  });
109
111
  registerTemplateCommands(program, { chalk: chalkStub });
112
+ registerConfigCommands(program, {
113
+ chalk: chalkStub,
114
+ resolveVaultPath: stubResolveVaultPath
115
+ });
116
+ registerRouteCommands(program, {
117
+ chalk: chalkStub,
118
+ resolveVaultPath: stubResolveVaultPath
119
+ });
110
120
 
111
121
  const names = listCommandNames(program);
112
122
  expect(names).toEqual(expect.arrayContaining([
@@ -129,7 +139,9 @@ describe('CLI command registration modules', () => {
129
139
  'sleep',
130
140
  'handoff',
131
141
  'recap',
132
- 'template'
142
+ 'template',
143
+ 'config',
144
+ 'route'
133
145
  ]));
134
146
 
135
147
  const templateCommand = program.commands.find((command) => command.name() === 'template');
@@ -12,6 +12,8 @@ describe('CLI help contract', () => {
12
12
  expect(help).toContain('replay');
13
13
  expect(help).toContain('repair-session');
14
14
  expect(help).toContain('template');
15
+ expect(help).toContain('config');
16
+ expect(help).toContain('route');
15
17
  });
16
18
 
17
19
  it('documents context auto profile and compat strict options', () => {
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Runtime config command registrations backed by .clawvault.json.
3
+ */
4
+
5
+ function stringifyValue(value) {
6
+ if (Array.isArray(value)) {
7
+ if (value.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean')) {
8
+ return value.join(', ');
9
+ }
10
+ return JSON.stringify(value);
11
+ }
12
+ if (value && typeof value === 'object') {
13
+ return JSON.stringify(value);
14
+ }
15
+ if (value === null || value === undefined) {
16
+ return '(unset)';
17
+ }
18
+ return String(value);
19
+ }
20
+
21
+ function flattenConfig(value, prefix = '') {
22
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
23
+ return [{ key: prefix || '(root)', value: stringifyValue(value) }];
24
+ }
25
+
26
+ const rows = [];
27
+ const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
28
+ for (const key of keys) {
29
+ const next = prefix ? `${prefix}.${key}` : key;
30
+ const entry = value[key];
31
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
32
+ rows.push(...flattenConfig(entry, next));
33
+ continue;
34
+ }
35
+ rows.push({ key: next, value: stringifyValue(entry) });
36
+ }
37
+ return rows;
38
+ }
39
+
40
+ function printTable(rows) {
41
+ if (rows.length === 0) {
42
+ console.log('No config values found.');
43
+ return;
44
+ }
45
+ const keyWidth = Math.max(
46
+ 'KEY'.length,
47
+ ...rows.map((row) => row.key.length)
48
+ );
49
+ const valueWidth = Math.max(
50
+ 'VALUE'.length,
51
+ ...rows.map((row) => row.value.length)
52
+ );
53
+ const header = `${'KEY'.padEnd(keyWidth)} ${'VALUE'.padEnd(valueWidth)}`;
54
+ const divider = `${'-'.repeat(keyWidth)} ${'-'.repeat(valueWidth)}`;
55
+ console.log(header);
56
+ console.log(divider);
57
+ for (const row of rows) {
58
+ console.log(`${row.key.padEnd(keyWidth)} ${row.value}`);
59
+ }
60
+ }
61
+
62
+ function normalizeConfigKey(key) {
63
+ return String(key || '').trim();
64
+ }
65
+
66
+ export function registerConfigCommands(program, { chalk, resolveVaultPath }) {
67
+ const config = program
68
+ .command('config')
69
+ .description('Read and modify runtime config');
70
+
71
+ config
72
+ .command('get <key>')
73
+ .description('Read a runtime config value (dot-notation supported)')
74
+ .option('-v, --vault <path>', 'Vault path')
75
+ .action(async (key, options) => {
76
+ try {
77
+ const { getConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
78
+ const normalizedKey = normalizeConfigKey(key);
79
+ if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
80
+ throw new Error(`Unsupported config key: ${normalizedKey}`);
81
+ }
82
+
83
+ const value = getConfigValue(resolveVaultPath(options.vault), normalizedKey);
84
+ if (Array.isArray(value)) {
85
+ console.log(value.join(','));
86
+ return;
87
+ }
88
+ if (value && typeof value === 'object') {
89
+ console.log(JSON.stringify(value, null, 2));
90
+ return;
91
+ }
92
+ console.log(value === undefined || value === null ? '' : String(value));
93
+ } catch (err) {
94
+ console.error(chalk.red(`Error: ${err.message}`));
95
+ process.exit(1);
96
+ }
97
+ });
98
+
99
+ config
100
+ .command('set <key> <value>')
101
+ .description('Set a runtime config value')
102
+ .option('-v, --vault <path>', 'Vault path')
103
+ .action(async (key, value, options) => {
104
+ try {
105
+ const { setConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
106
+ const normalizedKey = normalizeConfigKey(key);
107
+ if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
108
+ throw new Error(`Unsupported config key: ${normalizedKey}`);
109
+ }
110
+ const result = setConfigValue(resolveVaultPath(options.vault), normalizedKey, value);
111
+ console.log(chalk.green(`✓ Updated ${normalizedKey}`));
112
+ console.log(chalk.dim(` Value: ${stringifyValue(result.value)}`));
113
+ } catch (err) {
114
+ console.error(chalk.red(`Error: ${err.message}`));
115
+ process.exit(1);
116
+ }
117
+ });
118
+
119
+ config
120
+ .command('list')
121
+ .description('List all config values')
122
+ .option('-v, --vault <path>', 'Vault path')
123
+ .action(async (options) => {
124
+ try {
125
+ const { listConfig } = await import('../dist/index.js');
126
+ const values = listConfig(resolveVaultPath(options.vault));
127
+ const rows = flattenConfig(values);
128
+ printTable(rows);
129
+ } catch (err) {
130
+ console.error(chalk.red(`Error: ${err.message}`));
131
+ process.exit(1);
132
+ }
133
+ });
134
+
135
+ config
136
+ .command('reset')
137
+ .description('Reset runtime config values to defaults')
138
+ .option('--confirm', 'Confirm reset')
139
+ .option('-v, --vault <path>', 'Vault path')
140
+ .action(async (options) => {
141
+ try {
142
+ if (!options.confirm) {
143
+ throw new Error('Refusing to reset config without --confirm.');
144
+ }
145
+ const { resetConfig } = await import('../dist/index.js');
146
+ resetConfig(resolveVaultPath(options.vault));
147
+ console.log(chalk.green('✓ Config reset to defaults'));
148
+ } catch (err) {
149
+ console.error(chalk.red(`Error: ${err.message}`));
150
+ process.exit(1);
151
+ }
152
+ });
153
+ }
@@ -0,0 +1,114 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import { registerConfigCommands } from './register-config-commands.js';
4
+ import { registerRouteCommands } from './register-route-commands.js';
5
+ import { chalkStub } from './test-helpers/cli-command-fixtures.js';
6
+
7
+ const {
8
+ getConfigValueMock,
9
+ setConfigValueMock,
10
+ listConfigMock,
11
+ resetConfigMock,
12
+ addRouteRuleMock,
13
+ listRouteRulesMock,
14
+ removeRouteRuleMock,
15
+ testRouteRuleMock
16
+ } = vi.hoisted(() => ({
17
+ getConfigValueMock: vi.fn(),
18
+ setConfigValueMock: vi.fn(),
19
+ listConfigMock: vi.fn(),
20
+ resetConfigMock: vi.fn(),
21
+ addRouteRuleMock: vi.fn(),
22
+ listRouteRulesMock: vi.fn(),
23
+ removeRouteRuleMock: vi.fn(),
24
+ testRouteRuleMock: vi.fn()
25
+ }));
26
+
27
+ vi.mock('../dist/index.js', () => ({
28
+ SUPPORTED_CONFIG_KEYS: [
29
+ 'name',
30
+ 'categories',
31
+ 'theme',
32
+ 'observe.model',
33
+ 'observe.provider',
34
+ 'context.maxResults',
35
+ 'context.defaultProfile',
36
+ 'graph.maxHops'
37
+ ],
38
+ getConfigValue: getConfigValueMock,
39
+ setConfigValue: setConfigValueMock,
40
+ listConfig: listConfigMock,
41
+ resetConfig: resetConfigMock,
42
+ addRouteRule: addRouteRuleMock,
43
+ listRouteRules: listRouteRulesMock,
44
+ removeRouteRule: removeRouteRuleMock,
45
+ testRouteRule: testRouteRuleMock
46
+ }));
47
+
48
+ function buildProgram() {
49
+ const program = new Command();
50
+ const resolveVaultPath = (value) => value ?? '/vault';
51
+ registerConfigCommands(program, { chalk: chalkStub, resolveVaultPath });
52
+ registerRouteCommands(program, { chalk: chalkStub, resolveVaultPath });
53
+ return program;
54
+ }
55
+
56
+ async function runCommand(args) {
57
+ const program = buildProgram();
58
+ await program.parseAsync(args, { from: 'user' });
59
+ }
60
+
61
+ beforeEach(() => {
62
+ vi.clearAllMocks();
63
+ getConfigValueMock.mockReturnValue('demo-vault');
64
+ setConfigValueMock.mockReturnValue({ value: 'demo-vault' });
65
+ listConfigMock.mockReturnValue({ name: 'demo-vault', context: { maxResults: 5 } });
66
+ resetConfigMock.mockReturnValue(undefined);
67
+ addRouteRuleMock.mockReturnValue({ pattern: 'Pedro', target: 'people/pedro', priority: 1 });
68
+ listRouteRulesMock.mockReturnValue([{ pattern: 'Pedro', target: 'people/pedro', priority: 1 }]);
69
+ removeRouteRuleMock.mockReturnValue(true);
70
+ testRouteRuleMock.mockReturnValue({ pattern: 'Pedro', target: 'people/pedro', priority: 1 });
71
+ });
72
+
73
+ describe('config and route command registrations', () => {
74
+ it('executes config get/set/list subcommands', async () => {
75
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
76
+ try {
77
+ await runCommand(['config', 'get', 'name']);
78
+ expect(getConfigValueMock).toHaveBeenCalledWith('/vault', 'name');
79
+
80
+ await runCommand(['config', 'set', 'categories', 'people,projects']);
81
+ expect(setConfigValueMock).toHaveBeenCalledWith('/vault', 'categories', 'people,projects');
82
+
83
+ await runCommand(['config', 'list']);
84
+ expect(listConfigMock).toHaveBeenCalledWith('/vault');
85
+
86
+ const output = logSpy.mock.calls.map((call) => call.join(' ')).join('\n');
87
+ expect(output).toContain('context.maxResults');
88
+ } finally {
89
+ logSpy.mockRestore();
90
+ }
91
+ });
92
+
93
+ it('executes route add/remove/test subcommands', async () => {
94
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
95
+ try {
96
+ await runCommand(['route', 'add', 'Pedro', 'people/pedro']);
97
+ expect(addRouteRuleMock).toHaveBeenCalledWith('/vault', 'Pedro', 'people/pedro');
98
+
99
+ await runCommand(['route', 'remove', 'Pedro']);
100
+ expect(removeRouteRuleMock).toHaveBeenCalledWith('/vault', 'Pedro');
101
+
102
+ await runCommand(['route', 'test', 'Talked to Pedro']);
103
+ expect(testRouteRuleMock).toHaveBeenCalledWith('/vault', 'Talked to Pedro');
104
+
105
+ await runCommand(['route', 'list']);
106
+ expect(listRouteRulesMock).toHaveBeenCalledWith('/vault');
107
+
108
+ const output = logSpy.mock.calls.map((call) => call.join(' ')).join('\n');
109
+ expect(output).toContain('Route matched');
110
+ } finally {
111
+ logSpy.mockRestore();
112
+ }
113
+ });
114
+ });
@@ -45,13 +45,48 @@ export function registerResilienceCommands(program, { chalk, resolveVaultPath })
45
45
  .command('recover')
46
46
  .description('Check for context death and recover state')
47
47
  .option('--clear', 'Clear the dirty death flag after recovery')
48
+ .option('--check', 'Check dirty death flag without clearing it')
49
+ .option('--list', 'List saved checkpoints (newest first)')
48
50
  .option('--verbose', 'Show full checkpoint and handoff content')
49
51
  .option('-v, --vault <path>', 'Vault path')
50
52
  .option('--json', 'Output as JSON')
51
53
  .action(async (options) => {
52
54
  try {
53
- const { recover, formatRecoveryInfo } = await import('../dist/commands/recover.js');
54
- const info = await recover(resolveVaultPath(options.vault), {
55
+ if (options.check && options.list) {
56
+ throw new Error('--check and --list cannot be used together.');
57
+ }
58
+
59
+ const {
60
+ recover,
61
+ formatRecoveryInfo,
62
+ checkRecoveryStatus,
63
+ formatRecoveryCheckStatus,
64
+ listCheckpoints,
65
+ formatCheckpointList
66
+ } = await import('../dist/commands/recover.js');
67
+ const vaultPath = resolveVaultPath(options.vault);
68
+
69
+ if (options.check) {
70
+ const status = await checkRecoveryStatus(vaultPath);
71
+ if (options.json) {
72
+ console.log(JSON.stringify(status, null, 2));
73
+ } else {
74
+ console.log(formatRecoveryCheckStatus(status));
75
+ }
76
+ return;
77
+ }
78
+
79
+ if (options.list) {
80
+ const checkpoints = listCheckpoints(vaultPath);
81
+ if (options.json) {
82
+ console.log(JSON.stringify(checkpoints, null, 2));
83
+ } else {
84
+ console.log(formatCheckpointList(checkpoints));
85
+ }
86
+ return;
87
+ }
88
+
89
+ const info = await recover(vaultPath, {
55
90
  clearFlag: options.clear,
56
91
  verbose: options.verbose
57
92
  });
@@ -0,0 +1,81 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import { registerResilienceCommands } from './register-resilience-commands.js';
4
+ import { chalkStub } from './test-helpers/cli-command-fixtures.js';
5
+
6
+ const {
7
+ recoverMock,
8
+ formatRecoveryInfoMock,
9
+ checkRecoveryStatusMock,
10
+ formatRecoveryCheckStatusMock,
11
+ listCheckpointsMock,
12
+ formatCheckpointListMock
13
+ } = vi.hoisted(() => ({
14
+ recoverMock: vi.fn(),
15
+ formatRecoveryInfoMock: vi.fn(),
16
+ checkRecoveryStatusMock: vi.fn(),
17
+ formatRecoveryCheckStatusMock: vi.fn(),
18
+ listCheckpointsMock: vi.fn(),
19
+ formatCheckpointListMock: vi.fn()
20
+ }));
21
+
22
+ vi.mock('../dist/commands/recover.js', () => ({
23
+ recover: recoverMock,
24
+ formatRecoveryInfo: formatRecoveryInfoMock,
25
+ checkRecoveryStatus: checkRecoveryStatusMock,
26
+ formatRecoveryCheckStatus: formatRecoveryCheckStatusMock,
27
+ listCheckpoints: listCheckpointsMock,
28
+ formatCheckpointList: formatCheckpointListMock
29
+ }));
30
+
31
+ function buildProgram() {
32
+ const program = new Command();
33
+ registerResilienceCommands(program, {
34
+ chalk: chalkStub,
35
+ resolveVaultPath: (value) => value ?? '/vault'
36
+ });
37
+ return program;
38
+ }
39
+
40
+ async function runCommand(args) {
41
+ const program = buildProgram();
42
+ await program.parseAsync(args, { from: 'user' });
43
+ }
44
+
45
+ beforeEach(() => {
46
+ vi.clearAllMocks();
47
+ recoverMock.mockResolvedValue({ died: false });
48
+ formatRecoveryInfoMock.mockReturnValue('recover');
49
+ checkRecoveryStatusMock.mockResolvedValue({ died: false, deathTime: null, checkpoint: null });
50
+ formatRecoveryCheckStatusMock.mockReturnValue('check');
51
+ listCheckpointsMock.mockReturnValue([]);
52
+ formatCheckpointListMock.mockReturnValue('list');
53
+ });
54
+
55
+ describe('register-resilience-commands', () => {
56
+ it('routes recover --check through check helpers', async () => {
57
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
58
+ try {
59
+ await runCommand(['recover', '--check']);
60
+ expect(checkRecoveryStatusMock).toHaveBeenCalledWith('/vault');
61
+ expect(formatRecoveryCheckStatusMock).toHaveBeenCalled();
62
+ expect(recoverMock).not.toHaveBeenCalled();
63
+ expect(logSpy).toHaveBeenCalledWith('check');
64
+ } finally {
65
+ logSpy.mockRestore();
66
+ }
67
+ });
68
+
69
+ it('routes recover --list through checkpoint listing helpers', async () => {
70
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
71
+ try {
72
+ await runCommand(['recover', '--list']);
73
+ expect(listCheckpointsMock).toHaveBeenCalledWith('/vault');
74
+ expect(formatCheckpointListMock).toHaveBeenCalled();
75
+ expect(recoverMock).not.toHaveBeenCalled();
76
+ expect(logSpy).toHaveBeenCalledWith('list');
77
+ } finally {
78
+ logSpy.mockRestore();
79
+ }
80
+ });
81
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Route command registrations for custom observation entity routing.
3
+ */
4
+
5
+ function printRoutesTable(routes) {
6
+ if (routes.length === 0) {
7
+ console.log('No custom routes configured.');
8
+ return;
9
+ }
10
+
11
+ const priorityWidth = Math.max(
12
+ 'PRIORITY'.length,
13
+ ...routes.map((route) => String(route.priority).length)
14
+ );
15
+ const patternWidth = Math.max(
16
+ 'PATTERN'.length,
17
+ ...routes.map((route) => route.pattern.length)
18
+ );
19
+ const targetWidth = Math.max(
20
+ 'TARGET'.length,
21
+ ...routes.map((route) => route.target.length)
22
+ );
23
+
24
+ const header = `${'PRIORITY'.padEnd(priorityWidth)} ${'PATTERN'.padEnd(patternWidth)} ${'TARGET'.padEnd(targetWidth)}`;
25
+ const divider = `${'-'.repeat(priorityWidth)} ${'-'.repeat(patternWidth)} ${'-'.repeat(targetWidth)}`;
26
+
27
+ console.log(header);
28
+ console.log(divider);
29
+ for (const route of routes) {
30
+ console.log(
31
+ `${String(route.priority).padEnd(priorityWidth)} ${route.pattern.padEnd(patternWidth)} ${route.target}`
32
+ );
33
+ }
34
+ }
35
+
36
+ export function registerRouteCommands(program, { chalk, resolveVaultPath }) {
37
+ const route = program
38
+ .command('route')
39
+ .description('Manage custom observation routing rules');
40
+
41
+ route
42
+ .command('list')
43
+ .description('List custom routing rules')
44
+ .option('-v, --vault <path>', 'Vault path')
45
+ .action(async (options) => {
46
+ try {
47
+ const { listRouteRules } = await import('../dist/index.js');
48
+ const rules = listRouteRules(resolveVaultPath(options.vault));
49
+ printRoutesTable(rules);
50
+ } catch (err) {
51
+ console.error(chalk.red(`Error: ${err.message}`));
52
+ process.exit(1);
53
+ }
54
+ });
55
+
56
+ route
57
+ .command('add <pattern> <target>')
58
+ .description('Add a custom routing rule')
59
+ .option('-v, --vault <path>', 'Vault path')
60
+ .action(async (pattern, target, options) => {
61
+ try {
62
+ const { addRouteRule } = await import('../dist/index.js');
63
+ const rule = addRouteRule(resolveVaultPath(options.vault), pattern, target);
64
+ console.log(chalk.green('✓ Route added'));
65
+ console.log(chalk.dim(` Pattern: ${rule.pattern}`));
66
+ console.log(chalk.dim(` Target: ${rule.target}`));
67
+ console.log(chalk.dim(` Priority: ${rule.priority}`));
68
+ } catch (err) {
69
+ console.error(chalk.red(`Error: ${err.message}`));
70
+ process.exit(1);
71
+ }
72
+ });
73
+
74
+ route
75
+ .command('remove <pattern>')
76
+ .description('Remove a custom routing rule by pattern')
77
+ .option('-v, --vault <path>', 'Vault path')
78
+ .action(async (pattern, options) => {
79
+ try {
80
+ const { removeRouteRule } = await import('../dist/index.js');
81
+ const removed = removeRouteRule(resolveVaultPath(options.vault), pattern);
82
+ if (!removed) {
83
+ console.log(chalk.yellow(`No route found for pattern: ${pattern}`));
84
+ return;
85
+ }
86
+ console.log(chalk.green(`✓ Removed route: ${pattern}`));
87
+ } catch (err) {
88
+ console.error(chalk.red(`Error: ${err.message}`));
89
+ process.exit(1);
90
+ }
91
+ });
92
+
93
+ route
94
+ .command('test <text>')
95
+ .description('Test custom routes against text')
96
+ .option('-v, --vault <path>', 'Vault path')
97
+ .action(async (text, options) => {
98
+ try {
99
+ const { testRouteRule } = await import('../dist/index.js');
100
+ const match = testRouteRule(resolveVaultPath(options.vault), text);
101
+ if (!match) {
102
+ console.log('No route matched.');
103
+ return;
104
+ }
105
+ console.log(chalk.green('✓ Route matched'));
106
+ console.log(chalk.dim(` Pattern: ${match.pattern}`));
107
+ console.log(chalk.dim(` Target: ${match.target}`));
108
+ console.log(chalk.dim(` Priority: ${match.priority}`));
109
+ } catch (err) {
110
+ console.error(chalk.red(`Error: ${err.message}`));
111
+ process.exit(1);
112
+ }
113
+ });
114
+ }
@@ -80,6 +80,8 @@ export function registerTaskCommands(
80
80
  .option('--priority <priority>', 'New priority')
81
81
  .option('--blocked-by <blocker>', 'What is blocking this task')
82
82
  .option('--due <date>', 'New due date')
83
+ .option('--confidence <value>', 'Transition confidence (0-1)', parseFloat)
84
+ .option('--reason <reason>', 'Reason for status change')
83
85
  .action(async (slug, options) => {
84
86
  try {
85
87
  const vaultPath = resolveVaultPath(options.vault);
@@ -92,7 +94,9 @@ export function registerTaskCommands(
92
94
  project: options.project,
93
95
  priority: options.priority,
94
96
  blockedBy: options.blockedBy,
95
- due: options.due
97
+ due: options.due,
98
+ confidence: options.confidence,
99
+ reason: options.reason
96
100
  }
97
101
  });
98
102
  } catch (err) {
@@ -106,11 +110,45 @@ export function registerTaskCommands(
106
110
  .command('done <slug>')
107
111
  .description('Mark a task as done')
108
112
  .option('-v, --vault <path>', 'Vault path')
113
+ .option('--confidence <value>', 'Transition confidence (0-1)', parseFloat)
114
+ .option('--reason <reason>', 'Reason for completion')
109
115
  .action(async (slug, options) => {
110
116
  try {
111
117
  const vaultPath = resolveVaultPath(options.vault);
112
118
  const { taskCommand } = await import('../dist/commands/task.js');
113
- await taskCommand(vaultPath, 'done', { slug });
119
+ await taskCommand(vaultPath, 'done', {
120
+ slug,
121
+ options: {
122
+ confidence: options.confidence,
123
+ reason: options.reason
124
+ }
125
+ });
126
+ } catch (err) {
127
+ console.error(chalk.red(`Error: ${err.message}`));
128
+ process.exit(1);
129
+ }
130
+ });
131
+
132
+ // task transitions
133
+ taskCmd
134
+ .command('transitions [task_id]')
135
+ .description('Show transition history')
136
+ .option('-v, --vault <path>', 'Vault path')
137
+ .option('--agent <id>', 'Filter by agent')
138
+ .option('--failed', 'Show only regression transitions')
139
+ .option('--json', 'Output as JSON')
140
+ .action(async (taskId, options) => {
141
+ try {
142
+ const vaultPath = resolveVaultPath(options.vault);
143
+ const { taskCommand } = await import('../dist/commands/task.js');
144
+ await taskCommand(vaultPath, 'transitions', {
145
+ slug: taskId,
146
+ options: {
147
+ agent: options.agent,
148
+ failed: options.failed,
149
+ json: options.json
150
+ }
151
+ });
114
152
  } catch (err) {
115
153
  console.error(chalk.red(`Error: ${err.message}`));
116
154
  process.exit(1);
@@ -221,6 +259,7 @@ export function registerTaskCommands(
221
259
  .description('View blocked tasks')
222
260
  .option('-v, --vault <path>', 'Vault path')
223
261
  .option('--project <project>', 'Filter by project')
262
+ .option('--escalated', 'Show only escalated tasks (3+ blocked transitions)')
224
263
  .option('--json', 'Output as JSON')
225
264
  .action(async (options) => {
226
265
  try {
@@ -228,6 +267,7 @@ export function registerTaskCommands(
228
267
  const { blockedCommand } = await import('../dist/commands/blocked.js');
229
268
  await blockedCommand(vaultPath, {
230
269
  project: options.project,
270
+ escalated: options.escalated,
231
271
  json: options.json
232
272
  });
233
273
  } catch (err) {
@@ -8,6 +8,8 @@ import { registerResilienceCommands } from '../register-resilience-commands.js';
8
8
  import { registerSessionLifecycleCommands } from '../register-session-lifecycle-commands.js';
9
9
  import { registerTemplateCommands } from '../register-template-commands.js';
10
10
  import { registerVaultOperationsCommands } from '../register-vault-operations-commands.js';
11
+ import { registerConfigCommands } from '../register-config-commands.js';
12
+ import { registerRouteCommands } from '../register-route-commands.js';
11
13
 
12
14
  export const chalkStub = {
13
15
  cyan: (value) => value,
@@ -89,6 +91,14 @@ export function registerAllCommandModules(program = new Command()) {
89
91
  runQmd: async () => {}
90
92
  });
91
93
  registerTemplateCommands(program, { chalk: chalkStub });
94
+ registerConfigCommands(program, {
95
+ chalk: chalkStub,
96
+ resolveVaultPath: stubResolveVaultPath
97
+ });
98
+ registerRouteCommands(program, {
99
+ chalk: chalkStub,
100
+ resolveVaultPath: stubResolveVaultPath
101
+ });
92
102
 
93
103
  return program;
94
104
  }