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.
- package/.env.production +0 -4
- package/dist/agent/infra/tools/implementations/search-knowledge-service.js +12 -2
- package/dist/oclif/commands/curate/index.d.ts +1 -0
- package/dist/oclif/commands/curate/index.js +15 -1
- package/dist/oclif/commands/query.d.ts +1 -0
- package/dist/oclif/commands/query.js +17 -3
- package/dist/oclif/commands/search.d.ts +20 -0
- package/dist/oclif/commands/search.js +186 -0
- package/dist/oclif/commands/status.js +4 -0
- package/dist/oclif/lib/daemon-client.js +0 -1
- package/dist/oclif/lib/search-format.d.ts +10 -0
- package/dist/oclif/lib/search-format.js +25 -0
- package/dist/oclif/lib/task-client.d.ts +6 -0
- package/dist/oclif/lib/task-client.js +10 -3
- package/dist/server/constants.d.ts +1 -1
- package/dist/server/constants.js +2 -0
- package/dist/server/core/domain/errors/task-error.d.ts +2 -2
- package/dist/server/core/domain/errors/task-error.js +5 -4
- package/dist/server/core/domain/transport/schemas.d.ts +10 -10
- package/dist/server/core/domain/transport/schemas.js +3 -3
- package/dist/server/core/interfaces/executor/i-search-executor.d.ts +34 -0
- package/dist/server/core/interfaces/executor/i-search-executor.js +1 -0
- package/dist/server/core/interfaces/executor/index.d.ts +1 -0
- package/dist/server/core/interfaces/executor/index.js +1 -0
- package/dist/server/infra/daemon/agent-process.js +20 -7
- package/dist/server/infra/executor/search-executor.d.ts +17 -0
- package/dist/server/infra/executor/search-executor.js +30 -0
- package/dist/server/infra/transport/handlers/pull-handler.js +3 -3
- package/dist/server/infra/transport/handlers/push-handler.js +3 -3
- package/dist/server/infra/transport/handlers/status-handler.js +25 -18
- package/dist/server/templates/skill/SKILL.md +25 -5
- package/dist/shared/transport/search-content.d.ts +28 -0
- package/dist/shared/transport/search-content.js +38 -0
- package/dist/shared/transport/types/dto.d.ts +1 -1
- package/dist/tui/features/status/utils/format-status.js +5 -0
- package/dist/tui/utils/error-messages.js +2 -2
- package/oclif.manifest.json +425 -341
- 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,
|
|
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
|
-
|
|
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({
|
|
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 =
|
|
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(
|
|
81
|
+
reject(new Error(timeoutMessage));
|
|
75
82
|
}
|
|
76
83
|
else {
|
|
77
84
|
writeJsonResponse({
|
|
78
85
|
command,
|
|
79
|
-
data: { event: 'error', message:
|
|
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";
|
package/dist/server/constants.js
CHANGED
|
@@ -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
|
|
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
|
|
157
|
+
export class LegacySyncUnavailableError extends TaskError {
|
|
157
158
|
constructor() {
|
|
158
|
-
super('
|
|
159
|
-
this.name = '
|
|
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;
|