byterover-cli 3.2.0 → 3.3.0

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 (38) hide show
  1. package/.env.production +0 -4
  2. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +12 -2
  3. package/dist/oclif/commands/curate/index.d.ts +1 -0
  4. package/dist/oclif/commands/curate/index.js +15 -1
  5. package/dist/oclif/commands/query.d.ts +1 -0
  6. package/dist/oclif/commands/query.js +17 -3
  7. package/dist/oclif/commands/search.d.ts +20 -0
  8. package/dist/oclif/commands/search.js +186 -0
  9. package/dist/oclif/commands/status.js +4 -0
  10. package/dist/oclif/lib/daemon-client.js +0 -1
  11. package/dist/oclif/lib/search-format.d.ts +10 -0
  12. package/dist/oclif/lib/search-format.js +25 -0
  13. package/dist/oclif/lib/task-client.d.ts +6 -0
  14. package/dist/oclif/lib/task-client.js +10 -3
  15. package/dist/server/constants.d.ts +1 -1
  16. package/dist/server/constants.js +2 -0
  17. package/dist/server/core/domain/errors/task-error.d.ts +2 -2
  18. package/dist/server/core/domain/errors/task-error.js +5 -4
  19. package/dist/server/core/domain/transport/schemas.d.ts +10 -10
  20. package/dist/server/core/domain/transport/schemas.js +3 -3
  21. package/dist/server/core/interfaces/executor/i-search-executor.d.ts +34 -0
  22. package/dist/server/core/interfaces/executor/i-search-executor.js +1 -0
  23. package/dist/server/core/interfaces/executor/index.d.ts +1 -0
  24. package/dist/server/core/interfaces/executor/index.js +1 -0
  25. package/dist/server/infra/daemon/agent-process.js +20 -7
  26. package/dist/server/infra/executor/search-executor.d.ts +17 -0
  27. package/dist/server/infra/executor/search-executor.js +30 -0
  28. package/dist/server/infra/transport/handlers/pull-handler.js +3 -3
  29. package/dist/server/infra/transport/handlers/push-handler.js +3 -3
  30. package/dist/server/infra/transport/handlers/status-handler.js +25 -18
  31. package/dist/server/templates/skill/SKILL.md +25 -5
  32. package/dist/shared/transport/search-content.d.ts +28 -0
  33. package/dist/shared/transport/search-content.js +38 -0
  34. package/dist/shared/transport/types/dto.d.ts +1 -1
  35. package/dist/tui/features/status/utils/format-status.js +5 -0
  36. package/dist/tui/utils/error-messages.js +2 -2
  37. package/oclif.manifest.json +425 -341
  38. package/package.json +1 -1
package/.env.production CHANGED
@@ -1,7 +1,3 @@
1
- # ByteRover CLI - Environment Configuration
2
- # Copy this file to .env.development (for dev) or .env.production (for production)
3
- # and fill in the values for your environment.
4
-
5
1
  BRV_API_BASE_URL=https://iam.byterover.dev/api/v1
6
2
  BRV_AUTHORIZATION_URL=https://iam.byterover.dev/api/v1/oidc/authorize
7
3
  BRV_COGIT_API_BASE_URL=https://v3-cgit.byterover.dev/api/v1
@@ -649,6 +649,11 @@ export class SearchKnowledgeService {
649
649
  */
650
650
  async search(query, options) {
651
651
  const limit = options?.limit ?? 10;
652
+ // Normalize scope: strip trailing slashes so "project/" and "project" both work.
653
+ // The symbol tree stores paths without a trailing slash, and getSubtreeDocumentIds
654
+ // does an exact node lookup, so "project/" would otherwise miss the subtree entirely
655
+ // and silently fall back to global search via the block at the end of this method.
656
+ const normalizedScope = options?.scope?.trim().replace(/\/+$/, '') || undefined;
652
657
  const resolvedBaseDirectory = await realpath(this.baseDirectory).catch(() => this.baseDirectory);
653
658
  const contextTreePath = join(resolvedBaseDirectory, BRV_DIR, CONTEXT_TREE_DIR);
654
659
  // Acquire index with parallel-safe locking; flush pending access hits before any rebuild
@@ -667,7 +672,7 @@ export class SearchKnowledgeService {
667
672
  }
668
673
  // Overview mode: return tree structure instead of search results
669
674
  if (options?.overview) {
670
- return this.buildOverviewResult(symbolTree, referenceIndex, options.scope, options.overviewDepth);
675
+ return this.buildOverviewResult(symbolTree, referenceIndex, normalizedScope, options.overviewDepth);
671
676
  }
672
677
  // Symbolic path resolution: try path-based query first
673
678
  if (isPathLikeQuery(query, symbolTree)) {
@@ -678,7 +683,12 @@ export class SearchKnowledgeService {
678
683
  }
679
684
  // Parse query for potential scope prefix (e.g. "auth jwt refresh" → scope=auth, text="jwt refresh")
680
685
  const parsed = parseSymbolicQuery(query, symbolTree);
681
- const effectiveScope = options?.scope ?? parsed.scopePath;
686
+ // Strip trailing slashes from scope so "auth/" resolves to the same
687
+ // symbol-tree node as "auth". The symbol tree stores paths without
688
+ // trailing slashes; a mismatch causes getSubtreeDocumentIds() to
689
+ // return empty → unintended fallback to global search.
690
+ const rawScope = options?.scope?.trim().replace(/\/+$/, '');
691
+ const effectiveScope = (rawScope !== undefined && rawScope !== '' ? rawScope : undefined) ?? parsed.scopePath;
682
692
  const effectiveQuery = parsed.scopePath ? parsed.textQuery : query;
683
693
  // Run text-based MiniSearch (existing pipeline), optionally scoped to a subtree
684
694
  const textResult = this.runTextSearch(effectiveQuery || query, documentMap, index, limit, effectiveScope, pathToDocumentId, symbolTree, referenceIndex, summaryMap, symbolPathDocMap, options);
@@ -11,6 +11,7 @@ export default class Curate extends Command {
11
11
  files: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  folder: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
13
  format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
14
15
  };
15
16
  protected getDaemonClientOptions(): DaemonClientOptions;
16
17
  run(): Promise<void>;
@@ -6,7 +6,7 @@ import { extractCurateOperations } from '../../../server/utils/curate-result-par
6
6
  import { TaskEvents } from '../../../shared/transport/events/index.js';
7
7
  import { formatConnectionError, hasLeakedHandles, providerMissingMessage, withDaemonRetry, } from '../../lib/daemon-client.js';
8
8
  import { writeJsonResponse } from '../../lib/json-response.js';
9
- import { waitForTaskCompletion } from '../../lib/task-client.js';
9
+ import { DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS, MIN_TIMEOUT_SECONDS, waitForTaskCompletion } from '../../lib/task-client.js';
10
10
  export default class Curate extends Command {
11
11
  static args = {
12
12
  context: Args.string({
@@ -38,6 +38,9 @@ Bad examples:
38
38
  '# Folder pack with context',
39
39
  '<%= config.bin %> <%= command.id %> "Analyze authentication module" -d src/auth/',
40
40
  '',
41
+ '# Increase timeout for slow models (in seconds)',
42
+ '<%= config.bin %> <%= command.id %> "context here" --timeout 600',
43
+ '',
41
44
  '# View curate history',
42
45
  '<%= config.bin %> curate view',
43
46
  '<%= config.bin %> curate view --status completed --since 1h',
@@ -62,6 +65,12 @@ Bad examples:
62
65
  description: 'Output format (text or json)',
63
66
  options: ['text', 'json'],
64
67
  }),
68
+ timeout: Flags.integer({
69
+ default: DEFAULT_TIMEOUT_SECONDS,
70
+ description: 'Maximum seconds to wait for task completion',
71
+ max: MAX_TIMEOUT_SECONDS,
72
+ min: MIN_TIMEOUT_SECONDS,
73
+ }),
65
74
  };
66
75
  getDaemonClientOptions() {
67
76
  return {};
@@ -73,6 +82,7 @@ Bad examples:
73
82
  files: rawFlags.files,
74
83
  folder: rawFlags.folder,
75
84
  format: rawFlags.format === 'json' ? 'json' : rawFlags.format === 'text' ? 'text' : undefined,
85
+ timeout: rawFlags.timeout,
76
86
  };
77
87
  const format = flags.format ?? 'text';
78
88
  if (!this.validateInput(args, flags, format))
@@ -235,6 +245,9 @@ Bad examples:
235
245
  ...(worktreeRoot ? { worktreeRoot } : {}),
236
246
  };
237
247
  if (flags.detach) {
248
+ if (flags.timeout !== DEFAULT_TIMEOUT_SECONDS && format !== 'json') {
249
+ this.log('Note: --timeout has no effect with --detach');
250
+ }
238
251
  const ack = await client.requestWithAck(TaskEvents.CREATE, taskPayload);
239
252
  const { logId } = ack;
240
253
  if (format === 'json') {
@@ -299,6 +312,7 @@ Bad examples:
299
312
  }
300
313
  },
301
314
  taskId,
315
+ timeoutMs: (flags.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000,
302
316
  }, (msg) => this.log(msg));
303
317
  await client.requestWithAck(TaskEvents.CREATE, taskPayload);
304
318
  await completionPromise;
@@ -8,6 +8,7 @@ export default class Query extends Command {
8
8
  static examples: string[];
9
9
  static flags: {
10
10
  format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
12
  };
12
13
  static strict: boolean;
13
14
  protected getDaemonClientOptions(): DaemonClientOptions;
@@ -4,7 +4,7 @@ import { TransportStateEventNames } from '../../server/core/domain/transport/sch
4
4
  import { TaskEvents } from '../../shared/transport/events/index.js';
5
5
  import { formatConnectionError, hasLeakedHandles, providerMissingMessage, withDaemonRetry, } from '../lib/daemon-client.js';
6
6
  import { writeJsonResponse } from '../lib/json-response.js';
7
- import { waitForTaskCompletion } from '../lib/task-client.js';
7
+ import { DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS, MIN_TIMEOUT_SECONDS, waitForTaskCompletion } from '../lib/task-client.js';
8
8
  export default class Query extends Command {
9
9
  static args = {
10
10
  query: Args.string({
@@ -34,6 +34,12 @@ Bad:
34
34
  description: 'Output format (text or json)',
35
35
  options: ['text', 'json'],
36
36
  }),
37
+ timeout: Flags.integer({
38
+ default: DEFAULT_TIMEOUT_SECONDS,
39
+ description: 'Maximum seconds to wait for task completion',
40
+ max: MAX_TIMEOUT_SECONDS,
41
+ min: MIN_TIMEOUT_SECONDS,
42
+ }),
37
43
  };
38
44
  static strict = false;
39
45
  getDaemonClientOptions() {
@@ -56,7 +62,14 @@ Bad:
56
62
  if (active.providerKeyMissing) {
57
63
  throw new Error(providerMissingMessage(active.activeProvider, active.authMethod));
58
64
  }
59
- await this.submitTask({ client, format, projectRoot, query: args.query, worktreeRoot });
65
+ await this.submitTask({
66
+ client,
67
+ format,
68
+ projectRoot,
69
+ query: args.query,
70
+ timeoutMs: (flags.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000,
71
+ worktreeRoot,
72
+ });
60
73
  }, {
61
74
  ...this.getDaemonClientOptions(),
62
75
  onRetry: format === 'text'
@@ -82,7 +95,7 @@ Bad:
82
95
  }
83
96
  }
84
97
  async submitTask(props) {
85
- const { client, format, projectRoot, query, worktreeRoot } = props;
98
+ const { client, format, projectRoot, query, timeoutMs, worktreeRoot } = props;
86
99
  const taskId = randomUUID();
87
100
  const taskPayload = {
88
101
  clientCwd: process.cwd(),
@@ -156,6 +169,7 @@ Bad:
156
169
  }
157
170
  },
158
171
  taskId,
172
+ timeoutMs,
159
173
  }, (msg) => this.log(msg));
160
174
  await client.requestWithAck(TaskEvents.CREATE, taskPayload);
161
175
  await completionPromise;
@@ -0,0 +1,20 @@
1
+ import { Command } from '@oclif/core';
2
+ import { type DaemonClientOptions } from '../lib/daemon-client.js';
3
+ export default class Search extends Command {
4
+ static args: {
5
+ query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static examples: string[];
9
+ static flags: {
10
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ scope: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ static strict: boolean;
15
+ protected getDaemonClientOptions(): DaemonClientOptions;
16
+ run(): Promise<void>;
17
+ private reportError;
18
+ private submitTask;
19
+ private validateInput;
20
+ }
@@ -0,0 +1,186 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { TaskEvents } from '../../shared/transport/events/index.js';
4
+ import { encodeSearchContent } from '../../shared/transport/search-content.js';
5
+ import { formatConnectionError, hasLeakedHandles, withDaemonRetry, } from '../lib/daemon-client.js';
6
+ import { writeJsonResponse } from '../lib/json-response.js';
7
+ import { formatSearchTextOutput } from '../lib/search-format.js';
8
+ import { waitForTaskCompletion } from '../lib/task-client.js';
9
+ export default class Search extends Command {
10
+ static args = {
11
+ query: Args.string({
12
+ description: 'Search query to find relevant knowledge in the context tree',
13
+ required: true,
14
+ }),
15
+ };
16
+ static description = `Search the context tree for relevant knowledge
17
+
18
+ Returns ranked results with paths, scores, and excerpts.
19
+ Pure BM25 retrieval — no LLM, no token cost.
20
+
21
+ Use this for structured results with file paths.
22
+ Use "brv query" when you need a synthesized answer.`;
23
+ static examples = [
24
+ '# Search for knowledge about authentication',
25
+ '<%= config.bin %> <%= command.id %> "authentication"',
26
+ '',
27
+ '# Limit results and scope to a domain',
28
+ '<%= config.bin %> <%= command.id %> "JWT tokens" --limit 5 --scope auth/',
29
+ '',
30
+ '# JSON output (for automation)',
31
+ '<%= config.bin %> <%= command.id %> "auth" --format json',
32
+ ];
33
+ static flags = {
34
+ format: Flags.string({
35
+ default: 'text',
36
+ description: 'Output format (text or json)',
37
+ options: ['text', 'json'],
38
+ }),
39
+ limit: Flags.integer({
40
+ default: 10,
41
+ description: 'Maximum number of results (1-50)',
42
+ max: 50,
43
+ min: 1,
44
+ }),
45
+ scope: Flags.string({
46
+ description: 'Path prefix to scope results (e.g. "auth/" for auth domain only)',
47
+ }),
48
+ };
49
+ static strict = false;
50
+ getDaemonClientOptions() {
51
+ return {};
52
+ }
53
+ async run() {
54
+ const { args, flags } = await this.parse(Search);
55
+ const format = flags.format === 'json' ? 'json' : 'text';
56
+ if (!this.validateInput(args.query, format))
57
+ return;
58
+ try {
59
+ await withDaemonRetry(async (client, projectRoot, worktreeRoot) => {
60
+ // No provider validation — search is pure BM25, no LLM needed.
61
+ await this.submitTask({
62
+ client,
63
+ format,
64
+ limit: flags.limit,
65
+ projectRoot,
66
+ query: args.query,
67
+ scope: flags.scope,
68
+ worktreeRoot,
69
+ });
70
+ }, {
71
+ ...this.getDaemonClientOptions(),
72
+ onRetry: format === 'text'
73
+ ? (attempt, maxRetries) => this.log(`\nConnection lost. Restarting daemon... (attempt ${attempt}/${maxRetries})`)
74
+ : undefined,
75
+ });
76
+ }
77
+ catch (error) {
78
+ this.reportError(error, format);
79
+ }
80
+ }
81
+ reportError(error, format) {
82
+ const errorMessage = error instanceof Error ? error.message : 'Search failed';
83
+ if (format === 'json') {
84
+ writeJsonResponse({ command: 'search', data: { error: errorMessage, status: 'error' }, success: false });
85
+ }
86
+ else {
87
+ this.log(formatConnectionError(error));
88
+ }
89
+ if (hasLeakedHandles(error)) {
90
+ // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
91
+ process.exit(1);
92
+ }
93
+ }
94
+ async submitTask(props) {
95
+ const { client, format, projectRoot, query, worktreeRoot } = props;
96
+ const taskId = randomUUID();
97
+ const contentPayload = encodeSearchContent({ limit: props.limit, query, scope: props.scope });
98
+ const taskPayload = {
99
+ clientCwd: process.cwd(),
100
+ content: contentPayload,
101
+ ...(projectRoot ? { projectPath: projectRoot } : {}),
102
+ taskId,
103
+ type: 'search',
104
+ ...(worktreeRoot ? { worktreeRoot } : {}),
105
+ };
106
+ const completionPromise = waitForTaskCompletion({
107
+ client,
108
+ command: 'search',
109
+ format,
110
+ onCompleted: ({ result }) => {
111
+ if (!result) {
112
+ if (format === 'json') {
113
+ writeJsonResponse({
114
+ command: 'search',
115
+ data: { results: [], status: 'completed', totalFound: 0 },
116
+ success: true,
117
+ });
118
+ }
119
+ else {
120
+ this.log('\nNo results.\n');
121
+ }
122
+ return;
123
+ }
124
+ try {
125
+ const searchResult = JSON.parse(result);
126
+ if (format === 'json') {
127
+ writeJsonResponse({
128
+ command: 'search',
129
+ data: {
130
+ ...searchResult,
131
+ status: 'completed',
132
+ },
133
+ success: true,
134
+ });
135
+ }
136
+ else {
137
+ for (const line of formatSearchTextOutput(searchResult)) {
138
+ this.log(line);
139
+ }
140
+ }
141
+ }
142
+ catch {
143
+ // Fallback: result isn't valid JSON — display as-is
144
+ if (format === 'json') {
145
+ writeJsonResponse({
146
+ command: 'search',
147
+ data: { error: 'Invalid search result format', raw: result, status: 'error' },
148
+ success: false,
149
+ });
150
+ }
151
+ else {
152
+ this.log(`\n${result}\n`);
153
+ }
154
+ }
155
+ },
156
+ onError({ error }) {
157
+ if (format === 'json') {
158
+ writeJsonResponse({
159
+ command: 'search',
160
+ data: { event: 'error', message: error.message, status: 'error' },
161
+ success: false,
162
+ });
163
+ }
164
+ },
165
+ taskId,
166
+ }, (msg) => this.log(msg));
167
+ await client.requestWithAck(TaskEvents.CREATE, taskPayload);
168
+ await completionPromise;
169
+ }
170
+ validateInput(query, format) {
171
+ if (query.trim())
172
+ return true;
173
+ if (format === 'json') {
174
+ writeJsonResponse({
175
+ command: 'search',
176
+ data: { message: 'Search query is required.', status: 'error' },
177
+ success: false,
178
+ });
179
+ }
180
+ else {
181
+ this.log('Search query is required.');
182
+ this.log('Usage: brv search "your query here"');
183
+ }
184
+ return false;
185
+ }
186
+ }
@@ -139,6 +139,10 @@ export default class Status extends Command {
139
139
  this.log('Context Tree: No changes');
140
140
  break;
141
141
  }
142
+ case 'no_vc': {
143
+ this.log('Context Tree: Managed by Byterover version control (use brv vc commands)');
144
+ break;
145
+ }
142
146
  case 'not_initialized': {
143
147
  this.log('Context Tree: Not initialized');
144
148
  break;
@@ -18,7 +18,6 @@ const USER_FRIENDLY_MESSAGES = {
18
18
  [TaskErrorCode.OAUTH_TOKEN_EXPIRED]: 'OAuth token has expired. Run "brv providers connect <provider> --oauth" to reconnect.',
19
19
  [TaskErrorCode.PROJECT_NOT_INIT]: 'Project not initialized. Run "brv restart" to reinitialize.',
20
20
  [TaskErrorCode.PROVIDER_NOT_CONFIGURED]: 'No provider connected. Run "brv providers connect byterover" to use the free built-in provider, or connect another provider.',
21
- [TaskErrorCode.SPACE_NOT_CONFIGURED]: 'No space configured. Run "brv space list" to see available spaces, then "brv space switch --team <team> --name <space>" to select one.',
22
21
  [TaskErrorCode.SPACE_NOT_FOUND]: 'Space not found. Check your configuration.',
23
22
  [TaskErrorCode.VC_GIT_INITIALIZED]: 'ByteRover version control is active. Use brv vc commands instead of legacy sync commands.',
24
23
  [VcErrorCode.AUTH_FAILED]: 'Authentication failed. Run brv login.',
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Text formatting for the `brv search` CLI command.
3
+ * Pure function — no oclif, transport, or daemon dependencies.
4
+ */
5
+ import type { SearchKnowledgeResult } from '../../agent/infra/sandbox/tools-sdk.js';
6
+ /**
7
+ * Format search results for terminal display.
8
+ * Returns an array of lines (without trailing newlines).
9
+ */
10
+ export declare function formatSearchTextOutput(searchResult: SearchKnowledgeResult): string[];
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Text formatting for the `brv search` CLI command.
3
+ * Pure function — no oclif, transport, or daemon dependencies.
4
+ */
5
+ /**
6
+ * Format search results for terminal display.
7
+ * Returns an array of lines (without trailing newlines).
8
+ */
9
+ export function formatSearchTextOutput(searchResult) {
10
+ const lines = [];
11
+ if (searchResult.totalFound === 0) {
12
+ lines.push('', 'No results found.', '');
13
+ return lines;
14
+ }
15
+ const displayed = searchResult.results.length;
16
+ const total = searchResult.totalFound;
17
+ const countLabel = displayed < total ? `Showing ${displayed} of ${total} results` : `Found ${total} result${total === 1 ? '' : 's'}`;
18
+ lines.push('', `${countLabel}:`, '');
19
+ for (const [i, result] of searchResult.results.entries()) {
20
+ const scoreStr = (result.score * 100).toFixed(0);
21
+ const excerpt = result.excerpt && result.excerpt.length > 120 ? `${result.excerpt.slice(0, 117)}...` : result.excerpt;
22
+ lines.push(` ${i + 1}. ${result.title} [${scoreStr}%]`, ` Path: ${result.path}`, ...(excerpt ? [` ${excerpt}`] : []), ...(result.backlinkCount ? [` Backlinks: ${result.backlinkCount}`] : []), '');
23
+ }
24
+ return lines;
25
+ }
@@ -58,6 +58,12 @@ export interface WaitForTaskOptions {
58
58
  /** Timeout in ms (default: 5 minutes) */
59
59
  timeoutMs?: number;
60
60
  }
61
+ /** Default timeout for task completion (seconds) — shared across curate/query commands. */
62
+ export declare const DEFAULT_TIMEOUT_SECONDS = 300;
63
+ /** Minimum --timeout value accepted from the CLI (seconds). */
64
+ export declare const MIN_TIMEOUT_SECONDS = 10;
65
+ /** Maximum --timeout value accepted from the CLI (seconds). */
66
+ export declare const MAX_TIMEOUT_SECONDS = 3600;
61
67
  /**
62
68
  * Format tool call for CLI display (simplified version of TUI formatToolDisplay).
63
69
  */
@@ -4,8 +4,14 @@ import { ReviewEvents } from '../../shared/transport/events/review-events.js';
4
4
  import { writeJsonResponse } from './json-response.js';
5
5
  /** Grace period before treating 'reconnecting' as daemon death (ms) */
6
6
  const DISCONNECT_GRACE_MS = 10_000;
7
+ /** Default timeout for task completion (seconds) — shared across curate/query commands. */
8
+ export const DEFAULT_TIMEOUT_SECONDS = 300;
9
+ /** Minimum --timeout value accepted from the CLI (seconds). */
10
+ export const MIN_TIMEOUT_SECONDS = 10;
11
+ /** Maximum --timeout value accepted from the CLI (seconds). */
12
+ export const MAX_TIMEOUT_SECONDS = 3600;
7
13
  /** Default timeout for task completion (ms) */
8
- const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
14
+ const DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1000;
9
15
  /**
10
16
  * Format tool call for CLI display (simplified version of TUI formatToolDisplay).
11
17
  */
@@ -70,13 +76,14 @@ export function waitForTaskCompletion(options, log) {
70
76
  if (!completed) {
71
77
  completed = true;
72
78
  cleanup();
79
+ const timeoutMessage = `Task timed out after ${timeoutMs / 1000}s`;
73
80
  if (isText) {
74
- reject(new Error('Task timed out after 5 minutes'));
81
+ reject(new Error(timeoutMessage));
75
82
  }
76
83
  else {
77
84
  writeJsonResponse({
78
85
  command,
79
- data: { event: 'error', message: 'Task timed out after 5 minutes', status: 'error' },
86
+ data: { event: 'error', message: timeoutMessage, status: 'error' },
80
87
  success: false,
81
88
  });
82
89
  resolve();
@@ -67,4 +67,4 @@ export declare const MANIFEST_FILE = "_manifest.json";
67
67
  export declare const ARCHIVE_IMPORTANCE_THRESHOLD = 35;
68
68
  export declare const DEFAULT_GHOST_CUE_MAX_TOKENS = 220;
69
69
  /** .gitignore content for the context tree — ignore derived artifacts only */
70
- export declare const CONTEXT_TREE_GITIGNORE = "# Derived artifacts \u2014 do not track\n.gitignore\n.snapshot.json\n_manifest.json\n_index.md\n";
70
+ export declare const CONTEXT_TREE_GITIGNORE = "# Derived artifacts \u2014 do not track\n.gitignore\n.snapshot.json\n_manifest.json\n_index.md\n*.abstract.md\n*.overview.md\n";
@@ -94,4 +94,6 @@ export const CONTEXT_TREE_GITIGNORE = `# Derived artifacts — do not track
94
94
  .snapshot.json
95
95
  _manifest.json
96
96
  _index.md
97
+ *.abstract.md
98
+ *.overview.md
97
99
  `;
@@ -7,6 +7,7 @@ export declare const TaskErrorCode: {
7
7
  readonly AGENT_NOT_AVAILABLE: "ERR_AGENT_NOT_AVAILABLE";
8
8
  readonly AGENT_NOT_INITIALIZED: "ERR_AGENT_NOT_INITIALIZED";
9
9
  readonly CONTEXT_TREE_NOT_INITIALIZED: "ERR_CONTEXT_TREE_NOT_INIT";
10
+ readonly LEGACY_SYNC_UNAVAILABLE: "ERR_LEGACY_SYNC_UNAVAILABLE";
10
11
  readonly LLM_ERROR: "ERR_LLM_ERROR";
11
12
  readonly LLM_RATE_LIMIT: "ERR_LLM_RATE_LIMIT";
12
13
  readonly LOCAL_CHANGES_EXIST: "ERR_LOCAL_CHANGES_EXIST";
@@ -15,7 +16,6 @@ export declare const TaskErrorCode: {
15
16
  readonly OAUTH_TOKEN_EXPIRED: "ERR_OAUTH_TOKEN_EXPIRED";
16
17
  readonly PROJECT_NOT_INIT: "ERR_PROJECT_NOT_INIT";
17
18
  readonly PROVIDER_NOT_CONFIGURED: "ERR_PROVIDER_NOT_CONFIGURED";
18
- readonly SPACE_NOT_CONFIGURED: "ERR_SPACE_NOT_CONFIGURED";
19
19
  readonly SPACE_NOT_FOUND: "ERR_SPACE_NOT_FOUND";
20
20
  readonly TASK_CANCELLED: "ERR_TASK_CANCELLED";
21
21
  readonly TASK_EXECUTION: "ERR_TASK_EXECUTION";
@@ -73,7 +73,7 @@ export declare class FileValidationError extends Error {
73
73
  export declare class LocalChangesExistError extends TaskError {
74
74
  constructor(message?: string);
75
75
  }
76
- export declare class SpaceNotConfiguredError extends TaskError {
76
+ export declare class LegacySyncUnavailableError extends TaskError {
77
77
  constructor();
78
78
  }
79
79
  export declare class GitVcInitializedError extends TaskError {
@@ -9,6 +9,8 @@ export const TaskErrorCode = {
9
9
  AGENT_NOT_INITIALIZED: 'ERR_AGENT_NOT_INITIALIZED',
10
10
  // Context tree errors
11
11
  CONTEXT_TREE_NOT_INITIALIZED: 'ERR_CONTEXT_TREE_NOT_INIT',
12
+ // Legacy sync (brv push/pull) no longer available because project has no team+space configured
13
+ LEGACY_SYNC_UNAVAILABLE: 'ERR_LEGACY_SYNC_UNAVAILABLE',
12
14
  // LLM errors
13
15
  LLM_ERROR: 'ERR_LLM_ERROR',
14
16
  LLM_RATE_LIMIT: 'ERR_LLM_RATE_LIMIT',
@@ -21,7 +23,6 @@ export const TaskErrorCode = {
21
23
  // Execution errors
22
24
  PROJECT_NOT_INIT: 'ERR_PROJECT_NOT_INIT',
23
25
  PROVIDER_NOT_CONFIGURED: 'ERR_PROVIDER_NOT_CONFIGURED',
24
- SPACE_NOT_CONFIGURED: 'ERR_SPACE_NOT_CONFIGURED',
25
26
  SPACE_NOT_FOUND: 'ERR_SPACE_NOT_FOUND',
26
27
  TASK_CANCELLED: 'ERR_TASK_CANCELLED',
27
28
  TASK_EXECUTION: 'ERR_TASK_EXECUTION',
@@ -153,10 +154,10 @@ export class LocalChangesExistError extends TaskError {
153
154
  this.name = 'LocalChangesExistError';
154
155
  }
155
156
  }
156
- export class SpaceNotConfiguredError extends TaskError {
157
+ export class LegacySyncUnavailableError extends TaskError {
157
158
  constructor() {
158
- super('No space configured. Run "brv space list" to see available spaces, then "brv space switch --team <team> --name <space>" to select one.', TaskErrorCode.SPACE_NOT_CONFIGURED);
159
- this.name = 'SpaceNotConfiguredError';
159
+ super('Command brv push and brv pull are deprecated. Run `brv vc init` to start using Byterover version control.', TaskErrorCode.LEGACY_SYNC_UNAVAILABLE);
160
+ this.name = 'LegacySyncUnavailableError';
160
161
  }
161
162
  }
162
163
  export class GitVcInitializedError extends TaskError {
@@ -514,11 +514,11 @@ export declare const TaskExecuteSchema: z.ZodObject<{
514
514
  /** Unique task identifier */
515
515
  taskId: z.ZodString;
516
516
  /** Task type */
517
- type: z.ZodEnum<["curate", "curate-folder", "query"]>;
517
+ type: z.ZodEnum<["curate", "curate-folder", "query", "search"]>;
518
518
  /** Workspace root for scoped query/curate */
519
519
  worktreeRoot: z.ZodOptional<z.ZodString>;
520
520
  }, "strip", z.ZodTypeAny, {
521
- type: "curate" | "query" | "curate-folder";
521
+ type: "curate" | "query" | "search" | "curate-folder";
522
522
  content: string;
523
523
  taskId: string;
524
524
  clientId: string;
@@ -528,7 +528,7 @@ export declare const TaskExecuteSchema: z.ZodObject<{
528
528
  projectPath?: string | undefined;
529
529
  worktreeRoot?: string | undefined;
530
530
  }, {
531
- type: "curate" | "query" | "curate-folder";
531
+ type: "curate" | "query" | "search" | "curate-folder";
532
532
  content: string;
533
533
  taskId: string;
534
534
  clientId: string;
@@ -687,16 +687,16 @@ export declare const TaskCreatedSchema: z.ZodObject<{
687
687
  /** Unique task identifier */
688
688
  taskId: z.ZodString;
689
689
  /** Task type */
690
- type: z.ZodEnum<["curate", "curate-folder", "query"]>;
690
+ type: z.ZodEnum<["curate", "curate-folder", "query", "search"]>;
691
691
  }, "strip", z.ZodTypeAny, {
692
- type: "curate" | "query" | "curate-folder";
692
+ type: "curate" | "query" | "search" | "curate-folder";
693
693
  content: string;
694
694
  taskId: string;
695
695
  files?: string[] | undefined;
696
696
  clientCwd?: string | undefined;
697
697
  folderPath?: string | undefined;
698
698
  }, {
699
- type: "curate" | "query" | "curate-folder";
699
+ type: "curate" | "query" | "search" | "curate-folder";
700
700
  content: string;
701
701
  taskId: string;
702
702
  files?: string[] | undefined;
@@ -945,7 +945,7 @@ export type TaskStartedEvent = z.infer<typeof TaskStartedEventSchema>;
945
945
  export type TaskCompletedEvent = z.infer<typeof TaskCompletedEventSchema>;
946
946
  export type TaskErrorData = z.infer<typeof TaskErrorDataSchema>;
947
947
  export type TaskErrorEvent = z.infer<typeof TaskErrorEventSchema>;
948
- export declare const TaskTypeSchema: z.ZodEnum<["curate", "curate-folder", "query"]>;
948
+ export declare const TaskTypeSchema: z.ZodEnum<["curate", "curate-folder", "query", "search"]>;
949
949
  /**
950
950
  * Request to create a new task
951
951
  */
@@ -963,11 +963,11 @@ export declare const TaskCreateRequestSchema: z.ZodObject<{
963
963
  /** Task ID - generated by Client UseCase (UUID v4) */
964
964
  taskId: z.ZodString;
965
965
  /** Task type */
966
- type: z.ZodEnum<["curate", "curate-folder", "query"]>;
966
+ type: z.ZodEnum<["curate", "curate-folder", "query", "search"]>;
967
967
  /** Workspace root for scoped query/curate (stable linked root or projectRoot if unlinked) */
968
968
  worktreeRoot: z.ZodOptional<z.ZodString>;
969
969
  }, "strip", z.ZodTypeAny, {
970
- type: "curate" | "query" | "curate-folder";
970
+ type: "curate" | "query" | "search" | "curate-folder";
971
971
  content: string;
972
972
  taskId: string;
973
973
  files?: string[] | undefined;
@@ -976,7 +976,7 @@ export declare const TaskCreateRequestSchema: z.ZodObject<{
976
976
  projectPath?: string | undefined;
977
977
  worktreeRoot?: string | undefined;
978
978
  }, {
979
- type: "curate" | "query" | "curate-folder";
979
+ type: "curate" | "query" | "search" | "curate-folder";
980
980
  content: string;
981
981
  taskId: string;
982
982
  files?: string[] | undefined;