byterover-cli 3.0.0 → 3.1.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/dist/agent/core/domain/tools/constants.d.ts +1 -0
- package/dist/agent/core/domain/tools/constants.js +1 -0
- package/dist/agent/core/interfaces/cipher-services.d.ts +8 -0
- package/dist/agent/core/interfaces/i-cipher-agent.d.ts +1 -0
- package/dist/agent/infra/agent/agent-error-codes.d.ts +0 -1
- package/dist/agent/infra/agent/agent-error-codes.js +0 -1
- package/dist/agent/infra/agent/agent-error.d.ts +0 -1
- package/dist/agent/infra/agent/agent-error.js +0 -1
- package/dist/agent/infra/agent/agent-state-manager.d.ts +1 -3
- package/dist/agent/infra/agent/agent-state-manager.js +1 -3
- package/dist/agent/infra/agent/base-agent.d.ts +1 -1
- package/dist/agent/infra/agent/base-agent.js +1 -1
- package/dist/agent/infra/agent/cipher-agent.d.ts +15 -1
- package/dist/agent/infra/agent/cipher-agent.js +188 -3
- package/dist/agent/infra/agent/index.d.ts +1 -1
- package/dist/agent/infra/agent/index.js +1 -1
- package/dist/agent/infra/agent/service-initializer.d.ts +3 -3
- package/dist/agent/infra/agent/service-initializer.js +14 -8
- package/dist/agent/infra/agent/types.d.ts +0 -1
- package/dist/agent/infra/file-system/file-system-service.js +6 -5
- package/dist/agent/infra/folder-pack/folder-pack-service.d.ts +1 -0
- package/dist/agent/infra/folder-pack/folder-pack-service.js +29 -15
- package/dist/agent/infra/llm/providers/openai.js +12 -0
- package/dist/agent/infra/llm/stream-to-text.d.ts +7 -0
- package/dist/agent/infra/llm/stream-to-text.js +14 -0
- package/dist/agent/infra/map/abstract-generator.d.ts +22 -0
- package/dist/agent/infra/map/abstract-generator.js +67 -0
- package/dist/agent/infra/map/abstract-queue.d.ts +67 -0
- package/dist/agent/infra/map/abstract-queue.js +218 -0
- package/dist/agent/infra/memory/memory-deduplicator.d.ts +44 -0
- package/dist/agent/infra/memory/memory-deduplicator.js +88 -0
- package/dist/agent/infra/memory/memory-manager.d.ts +1 -0
- package/dist/agent/infra/memory/memory-manager.js +6 -5
- package/dist/agent/infra/sandbox/curate-service.d.ts +4 -2
- package/dist/agent/infra/sandbox/curate-service.js +6 -7
- package/dist/agent/infra/sandbox/local-sandbox.d.ts +5 -0
- package/dist/agent/infra/sandbox/local-sandbox.js +57 -1
- package/dist/agent/infra/sandbox/tools-sdk.d.ts +3 -1
- package/dist/agent/infra/session/session-compressor.d.ts +43 -0
- package/dist/agent/infra/session/session-compressor.js +296 -0
- package/dist/agent/infra/session/session-manager.d.ts +7 -0
- package/dist/agent/infra/session/session-manager.js +9 -0
- package/dist/agent/infra/tools/implementations/curate-tool.d.ts +3 -2
- package/dist/agent/infra/tools/implementations/curate-tool.js +54 -27
- package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +3 -3
- package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +34 -7
- package/dist/agent/infra/tools/implementations/ingest-resource-tool.d.ts +17 -0
- package/dist/agent/infra/tools/implementations/ingest-resource-tool.js +224 -0
- package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +8 -0
- package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -1
- package/dist/agent/infra/tools/implementations/search-knowledge-service.js +207 -34
- package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
- package/dist/agent/infra/tools/tool-provider.js +1 -0
- package/dist/agent/infra/tools/tool-registry.d.ts +3 -0
- package/dist/agent/infra/tools/tool-registry.js +15 -4
- package/dist/server/constants.d.ts +2 -0
- package/dist/server/constants.js +2 -0
- package/dist/server/core/domain/knowledge/memory-scoring.d.ts +3 -3
- package/dist/server/core/domain/knowledge/memory-scoring.js +5 -5
- package/dist/server/core/domain/knowledge/summary-types.d.ts +4 -0
- package/dist/server/core/domain/transport/schemas.d.ts +10 -10
- package/dist/server/infra/context-tree/derived-artifact.js +5 -1
- package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +2 -1
- package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +43 -7
- package/dist/server/infra/context-tree/file-context-tree-summary-service.js +20 -2
- package/dist/server/infra/executor/curate-executor.js +2 -1
- package/dist/server/infra/executor/folder-pack-executor.js +72 -2
- package/dist/server/infra/executor/query-executor.js +11 -3
- package/dist/server/infra/transport/handlers/status-handler.js +10 -0
- package/dist/server/utils/curate-result-parser.d.ts +4 -4
- package/dist/shared/transport/types/dto.d.ts +7 -0
- package/oclif.manifest.json +1 -1
- package/package.json +10 -4
|
@@ -521,18 +521,18 @@ export declare const TaskExecuteSchema: z.ZodObject<{
|
|
|
521
521
|
taskId: string;
|
|
522
522
|
clientId: string;
|
|
523
523
|
files?: string[] | undefined;
|
|
524
|
-
projectPath?: string | undefined;
|
|
525
|
-
folderPath?: string | undefined;
|
|
526
524
|
clientCwd?: string | undefined;
|
|
525
|
+
folderPath?: string | undefined;
|
|
526
|
+
projectPath?: string | undefined;
|
|
527
527
|
}, {
|
|
528
528
|
type: "curate" | "query" | "curate-folder";
|
|
529
529
|
content: string;
|
|
530
530
|
taskId: string;
|
|
531
531
|
clientId: string;
|
|
532
532
|
files?: string[] | undefined;
|
|
533
|
-
projectPath?: string | undefined;
|
|
534
|
-
folderPath?: string | undefined;
|
|
535
533
|
clientCwd?: string | undefined;
|
|
534
|
+
folderPath?: string | undefined;
|
|
535
|
+
projectPath?: string | undefined;
|
|
536
536
|
}>;
|
|
537
537
|
/**
|
|
538
538
|
* task:cancel - Transport tells Agent to cancel a task
|
|
@@ -689,15 +689,15 @@ export declare const TaskCreatedSchema: z.ZodObject<{
|
|
|
689
689
|
content: string;
|
|
690
690
|
taskId: string;
|
|
691
691
|
files?: string[] | undefined;
|
|
692
|
-
folderPath?: string | undefined;
|
|
693
692
|
clientCwd?: string | undefined;
|
|
693
|
+
folderPath?: string | undefined;
|
|
694
694
|
}, {
|
|
695
695
|
type: "curate" | "query" | "curate-folder";
|
|
696
696
|
content: string;
|
|
697
697
|
taskId: string;
|
|
698
698
|
files?: string[] | undefined;
|
|
699
|
-
folderPath?: string | undefined;
|
|
700
699
|
clientCwd?: string | undefined;
|
|
700
|
+
folderPath?: string | undefined;
|
|
701
701
|
}>;
|
|
702
702
|
/**
|
|
703
703
|
* task:started - Agent begins processing the task
|
|
@@ -965,17 +965,17 @@ export declare const TaskCreateRequestSchema: z.ZodObject<{
|
|
|
965
965
|
content: string;
|
|
966
966
|
taskId: string;
|
|
967
967
|
files?: string[] | undefined;
|
|
968
|
-
projectPath?: string | undefined;
|
|
969
|
-
folderPath?: string | undefined;
|
|
970
968
|
clientCwd?: string | undefined;
|
|
969
|
+
folderPath?: string | undefined;
|
|
970
|
+
projectPath?: string | undefined;
|
|
971
971
|
}, {
|
|
972
972
|
type: "curate" | "query" | "curate-folder";
|
|
973
973
|
content: string;
|
|
974
974
|
taskId: string;
|
|
975
975
|
files?: string[] | undefined;
|
|
976
|
-
projectPath?: string | undefined;
|
|
977
|
-
folderPath?: string | undefined;
|
|
978
976
|
clientCwd?: string | undefined;
|
|
977
|
+
folderPath?: string | undefined;
|
|
978
|
+
projectPath?: string | undefined;
|
|
979
979
|
}>;
|
|
980
980
|
/**
|
|
981
981
|
* Response after task creation
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - isArchiveStub() — searchable stubs (included in BM25 index and fingerprint)
|
|
7
7
|
* - isExcludedFromSync() — union of above (excluded from snapshot/sync/merge/push)
|
|
8
8
|
*/
|
|
9
|
-
import { ARCHIVE_DIR, FULL_ARCHIVE_EXTENSION, MANIFEST_FILE, STUB_EXTENSION, SUMMARY_INDEX_FILE } from '../../constants.js';
|
|
9
|
+
import { ABSTRACT_EXTENSION, ARCHIVE_DIR, FULL_ARCHIVE_EXTENSION, MANIFEST_FILE, OVERVIEW_EXTENSION, STUB_EXTENSION, SUMMARY_INDEX_FILE } from '../../constants.js';
|
|
10
10
|
import { toUnixPath } from './path-utils.js';
|
|
11
11
|
/**
|
|
12
12
|
* Returns true if the given relative path is a derived artifact
|
|
@@ -24,6 +24,10 @@ export function isDerivedArtifact(relativePath) {
|
|
|
24
24
|
return true;
|
|
25
25
|
if (fileName === MANIFEST_FILE)
|
|
26
26
|
return true;
|
|
27
|
+
if (fileName.endsWith(ABSTRACT_EXTENSION))
|
|
28
|
+
return true;
|
|
29
|
+
if (fileName.endsWith(OVERVIEW_EXTENSION))
|
|
30
|
+
return true;
|
|
27
31
|
if (segments.includes(ARCHIVE_DIR) && fileName.endsWith(FULL_ARCHIVE_EXTENSION))
|
|
28
32
|
return true;
|
|
29
33
|
return false;
|
|
@@ -44,7 +44,8 @@ export declare class FileContextTreeManifestService implements IContextTreeManif
|
|
|
44
44
|
private scanForManifest;
|
|
45
45
|
/**
|
|
46
46
|
* Recursively collect stat data for all source files (for fingerprint).
|
|
47
|
-
* Excludes derived artifacts.
|
|
47
|
+
* Excludes derived artifacts except .abstract.md siblings, which are included
|
|
48
|
+
* so abstract generation invalidates the manifest without a second tree walk.
|
|
48
49
|
*/
|
|
49
50
|
private scanSourceStats;
|
|
50
51
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
17
17
|
import { join, relative } from 'node:path';
|
|
18
|
-
import { ARCHIVE_DIR, BRV_DIR, CONTEXT_FILE_EXTENSION, CONTEXT_TREE_DIR, MANIFEST_FILE, STUB_EXTENSION, SUMMARY_INDEX_FILE, } from '../../constants.js';
|
|
18
|
+
import { ABSTRACT_EXTENSION, ARCHIVE_DIR, BRV_DIR, CONTEXT_FILE_EXTENSION, CONTEXT_TREE_DIR, MANIFEST_FILE, STUB_EXTENSION, SUMMARY_INDEX_FILE, } from '../../constants.js';
|
|
19
19
|
import { parseFrontmatterScoring } from '../../core/domain/knowledge/markdown-writer.js';
|
|
20
20
|
import { DEFAULT_LANE_BUDGETS } from '../../core/domain/knowledge/summary-types.js';
|
|
21
21
|
import { estimateTokens } from '../executor/pre-compaction/compaction-escalation.js';
|
|
@@ -98,8 +98,23 @@ export class FileContextTreeManifestService {
|
|
|
98
98
|
/* eslint-disable no-await-in-loop */
|
|
99
99
|
for (const entry of ordered) {
|
|
100
100
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
let content;
|
|
102
|
+
// For context entries, prefer .abstract.md sibling if it exists on disk
|
|
103
|
+
// (dynamic read avoids stale-manifest issues since .abstract.md is a derived artifact)
|
|
104
|
+
if (entry.type === 'context') {
|
|
105
|
+
const abstractRelPath = entry.path.replace(/\.md$/, ABSTRACT_EXTENSION);
|
|
106
|
+
const abstractFullPath = join(contextTreeDir, abstractRelPath);
|
|
107
|
+
try {
|
|
108
|
+
content = await readFile(abstractFullPath, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Abstract not ready yet — fall back to full content
|
|
112
|
+
content = await readFile(join(contextTreeDir, entry.path), 'utf8');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
content = await readFile(join(contextTreeDir, entry.path), 'utf8');
|
|
117
|
+
}
|
|
103
118
|
resolved.push({
|
|
104
119
|
content,
|
|
105
120
|
path: entry.path,
|
|
@@ -188,6 +203,12 @@ export class FileContextTreeManifestService {
|
|
|
188
203
|
catch {
|
|
189
204
|
return;
|
|
190
205
|
}
|
|
206
|
+
// Build a set of abstract sibling paths present in this directory so we can
|
|
207
|
+
// check existence without throwing ENOENT for every context file that has
|
|
208
|
+
// no abstract yet (the common case early in a project's lifetime).
|
|
209
|
+
const abstractsInDir = new Set(entries
|
|
210
|
+
.filter((e) => e.isFile() && e.name.endsWith(ABSTRACT_EXTENSION))
|
|
211
|
+
.map((e) => join(currentDir, e.name)));
|
|
191
212
|
/* eslint-disable no-await-in-loop */
|
|
192
213
|
for (const entry of entries) {
|
|
193
214
|
const entryName = entry.name;
|
|
@@ -221,10 +242,24 @@ export class FileContextTreeManifestService {
|
|
|
221
242
|
try {
|
|
222
243
|
const content = await readFile(fullPath, 'utf8');
|
|
223
244
|
const scoring = parseFrontmatterScoring(content);
|
|
245
|
+
// Use abstract sibling for token budgeting only if it is known to exist
|
|
246
|
+
// (checked via abstractsInDir set, avoiding ENOENT as control flow).
|
|
247
|
+
const abstractRelPath = relativePath.replace(/\.md$/, ABSTRACT_EXTENSION);
|
|
248
|
+
const abstractFullPath = join(contextTreeDir, abstractRelPath);
|
|
249
|
+
let abstractTokens;
|
|
250
|
+
if (abstractsInDir.has(abstractFullPath)) {
|
|
251
|
+
try {
|
|
252
|
+
const abstractContent = await readFile(abstractFullPath, 'utf8');
|
|
253
|
+
abstractTokens = estimateTokens(abstractContent);
|
|
254
|
+
}
|
|
255
|
+
catch { /* unreadable — treat as absent */ }
|
|
256
|
+
}
|
|
224
257
|
contexts.push({
|
|
258
|
+
abstractPath: abstractTokens === undefined ? undefined : abstractRelPath,
|
|
259
|
+
abstractTokens,
|
|
225
260
|
importance: scoring?.importance ?? 50,
|
|
226
261
|
path: relativePath,
|
|
227
|
-
tokens: estimateTokens(content),
|
|
262
|
+
tokens: abstractTokens ?? estimateTokens(content),
|
|
228
263
|
type: 'context',
|
|
229
264
|
});
|
|
230
265
|
}
|
|
@@ -238,7 +273,8 @@ export class FileContextTreeManifestService {
|
|
|
238
273
|
}
|
|
239
274
|
/**
|
|
240
275
|
* Recursively collect stat data for all source files (for fingerprint).
|
|
241
|
-
* Excludes derived artifacts.
|
|
276
|
+
* Excludes derived artifacts except .abstract.md siblings, which are included
|
|
277
|
+
* so abstract generation invalidates the manifest without a second tree walk.
|
|
242
278
|
*/
|
|
243
279
|
async scanSourceStats(currentDir, contextTreeDir, entries) {
|
|
244
280
|
let dirEntries;
|
|
@@ -257,8 +293,8 @@ export class FileContextTreeManifestService {
|
|
|
257
293
|
}
|
|
258
294
|
else if (entry.isFile() && entryName.endsWith(CONTEXT_FILE_EXTENSION)) {
|
|
259
295
|
const relativePath = toUnixPath(relative(contextTreeDir, fullPath));
|
|
260
|
-
|
|
261
|
-
if (isDerivedArtifact(relativePath))
|
|
296
|
+
const isAbstractSibling = entryName.endsWith(ABSTRACT_EXTENSION);
|
|
297
|
+
if (!isAbstractSibling && isDerivedArtifact(relativePath))
|
|
262
298
|
continue;
|
|
263
299
|
try {
|
|
264
300
|
const fileStat = await stat(fullPath);
|
|
@@ -74,7 +74,18 @@ export class FileContextTreeSummaryService {
|
|
|
74
74
|
// Step 5: Three-tier escalation via CipherAgent
|
|
75
75
|
const taskId = `summary_${directoryPath.replaceAll('/', '_') || 'root'}`;
|
|
76
76
|
const childEntries = children.map((c) => ({ content: c.content, name: c.name }));
|
|
77
|
-
|
|
77
|
+
let summaryText;
|
|
78
|
+
try {
|
|
79
|
+
summaryText = await this.generateWithEscalation(agent, taskId, childEntries, level, totalInputTokens);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
const combinedInput = childEntries.map((entry) => `## ${entry.name}\n${entry.content}`).join('\n\n');
|
|
83
|
+
summaryText = buildDeterministicFallbackCompaction({
|
|
84
|
+
inputTokens: totalInputTokens,
|
|
85
|
+
sourceText: combinedInput,
|
|
86
|
+
suffixLabel: 'summary compaction',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
78
89
|
// Step 6: Write _index.md
|
|
79
90
|
const summaryTokens = estimateTokens(summaryText);
|
|
80
91
|
const frontmatter = {
|
|
@@ -289,7 +300,14 @@ export class FileContextTreeSummaryService {
|
|
|
289
300
|
});
|
|
290
301
|
}
|
|
291
302
|
finally {
|
|
292
|
-
|
|
303
|
+
try {
|
|
304
|
+
// Cleanup is best-effort. A generated summary should still be written even
|
|
305
|
+
// if the backing task session cannot be torn down cleanly.
|
|
306
|
+
await agent.deleteTaskSession(sessionId);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Ignore cleanup failures
|
|
310
|
+
}
|
|
293
311
|
}
|
|
294
312
|
}
|
|
295
313
|
/**
|
|
@@ -54,7 +54,7 @@ export class CurateExecutor {
|
|
|
54
54
|
catch {
|
|
55
55
|
// Fail-open: if snapshot fails, skip summary propagation
|
|
56
56
|
}
|
|
57
|
-
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true });
|
|
57
|
+
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true, userFacing: true });
|
|
58
58
|
try {
|
|
59
59
|
// Task-scoped variable names for RLM pattern.
|
|
60
60
|
// Replace hyphens with underscores: UUIDs have hyphens which are invalid in JS identifiers,
|
|
@@ -124,6 +124,7 @@ export class CurateExecutor {
|
|
|
124
124
|
// Fail-open: summary/manifest errors never block curation
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
+
await agent.drainBackgroundWork?.();
|
|
127
128
|
return response;
|
|
128
129
|
}
|
|
129
130
|
finally {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { appendFileSync } from 'node:fs';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { FileContextTreeManifestService } from '../context-tree/file-context-tree-manifest-service.js';
|
|
5
|
+
import { FileContextTreeSnapshotService } from '../context-tree/file-context-tree-snapshot-service.js';
|
|
6
|
+
import { FileContextTreeSummaryService } from '../context-tree/file-context-tree-summary-service.js';
|
|
7
|
+
import { diffStates } from '../context-tree/snapshot-diff.js';
|
|
4
8
|
const LOG_PATH = process.env.BRV_SESSION_LOG;
|
|
5
9
|
function folderPackLog(message) {
|
|
6
10
|
if (!LOG_PATH)
|
|
@@ -41,6 +45,14 @@ export class FolderPackExecutor {
|
|
|
41
45
|
// Resolve folder path
|
|
42
46
|
const basePath = clientCwd ?? process.cwd();
|
|
43
47
|
const absoluteFolderPath = path.isAbsolute(folderPath) ? folderPath : path.resolve(basePath, folderPath);
|
|
48
|
+
const snapshotService = new FileContextTreeSnapshotService({ baseDirectory: basePath });
|
|
49
|
+
let preState;
|
|
50
|
+
try {
|
|
51
|
+
preState = await snapshotService.getCurrentState(basePath);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Fail-open: if snapshot fails, skip summary propagation
|
|
55
|
+
}
|
|
44
56
|
// Pack the folder
|
|
45
57
|
const packResult = await this.folderPackService.pack(absoluteFolderPath, {
|
|
46
58
|
extractDocuments: true,
|
|
@@ -50,7 +62,26 @@ export class FolderPackExecutor {
|
|
|
50
62
|
// Use iterative extraction strategy (inspired by rlm)
|
|
51
63
|
// Stores packed folder in sandbox environment and lets agent iteratively query/extract
|
|
52
64
|
// This avoids token limits entirely - works for folders of any size
|
|
53
|
-
|
|
65
|
+
const response = await this.executeIterative(agent, packResult, content, absoluteFolderPath, taskId, basePath);
|
|
66
|
+
if (preState) {
|
|
67
|
+
try {
|
|
68
|
+
const postState = await snapshotService.getCurrentState(basePath);
|
|
69
|
+
const changedPaths = diffStates(preState, postState);
|
|
70
|
+
if (changedPaths.length > 0) {
|
|
71
|
+
const summaryService = new FileContextTreeSummaryService();
|
|
72
|
+
const results = await summaryService.propagateStaleness(changedPaths, agent, basePath);
|
|
73
|
+
if (results.some((result) => result.actionTaken)) {
|
|
74
|
+
const manifestService = new FileContextTreeManifestService({ baseDirectory: basePath });
|
|
75
|
+
await manifestService.buildManifest(basePath);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Fail-open: summary/manifest errors never block curation
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await agent.drainBackgroundWork?.();
|
|
84
|
+
return response;
|
|
54
85
|
}
|
|
55
86
|
/**
|
|
56
87
|
* Build iterative extraction prompt with file-based access.
|
|
@@ -133,6 +164,15 @@ Use **code_exec with tools.readFile/tools.grep** to extract knowledge:
|
|
|
133
164
|
|
|
134
165
|
**Important**: All tools.* methods are async - always use \`await\`!
|
|
135
166
|
|
|
167
|
+
## Curate Shape Constraints
|
|
168
|
+
|
|
169
|
+
- Prefer one concrete knowledge entry per relevant source file when curating a small leaf folder.
|
|
170
|
+
- For folders with 3 or fewer relevant source files, keep the number of curated leaf entries at or below the number of curated source files by default.
|
|
171
|
+
- Do **NOT** create an extra module/folder "overview" leaf at the bare topic path just because the folder has multiple files.
|
|
172
|
+
- Treat bare topic paths as scopes for \`topicContext\`, not as default destinations for standalone knowledge files.
|
|
173
|
+
- If you need topic-level framing, provide \`topicContext\` on the operation that creates the topic. The system will create/update \`context.md\` and higher-level summaries separately.
|
|
174
|
+
- Only add a standalone overview leaf when the user explicitly asks for it or when there is a distinct cross-file concept that cannot be represented by the per-file entries.
|
|
175
|
+
|
|
136
176
|
## Common Mistakes to Avoid
|
|
137
177
|
|
|
138
178
|
**❌ WRONG - Using require() inside code_exec:**
|
|
@@ -773,7 +813,7 @@ await tools.curate([{
|
|
|
773
813
|
throw new Error(`Failed to write temp file: ${error instanceof Error ? error.message : String(error)}`);
|
|
774
814
|
}
|
|
775
815
|
// Create per-task session for parallel isolation (own sandbox + history + LLM service)
|
|
776
|
-
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true });
|
|
816
|
+
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true, userFacing: true });
|
|
777
817
|
// Step 3: Store full instructions as sandbox variable (lazy prompt loading).
|
|
778
818
|
// This saves ~12-15K tokens by keeping the massive instruction set out of the prompt.
|
|
779
819
|
// The LLM reads instructions on-demand via code_exec.
|
|
@@ -783,15 +823,45 @@ await tools.curate([{
|
|
|
783
823
|
const taskIdSafe = taskId.replaceAll('-', '_');
|
|
784
824
|
const instructionsVar = `__curate_instructions_${taskIdSafe}`;
|
|
785
825
|
agent.setSandboxVariableOnSession(taskSessionId, instructionsVar, fullInstructions);
|
|
826
|
+
const smallFolderFilesVar = `__curate_files_${taskIdSafe}`;
|
|
827
|
+
const shouldExposePackedFiles = packResult.files.length > 0 && packResult.files.length <= 10 && packResult.totalCharacters <= 80_000;
|
|
828
|
+
if (shouldExposePackedFiles) {
|
|
829
|
+
agent.setSandboxVariableOnSession(taskSessionId, smallFolderFilesVar, packResult.files.map((file) => ({
|
|
830
|
+
content: file.content,
|
|
831
|
+
fileType: file.fileType,
|
|
832
|
+
lineCount: file.lineCount,
|
|
833
|
+
path: file.path,
|
|
834
|
+
size: file.size,
|
|
835
|
+
truncated: file.truncated,
|
|
836
|
+
})));
|
|
837
|
+
}
|
|
786
838
|
// Compact prompt with variable reference and essential metadata
|
|
787
839
|
const contextSection = userContext?.trim() ? `\nUser context: ${userContext}\n` : '';
|
|
840
|
+
const sourceFilePaths = packResult.files.map((file) => file.path);
|
|
841
|
+
const sourceFilesSection = sourceFilePaths.length > 0
|
|
842
|
+
? `Relevant source files: ${sourceFilePaths.join(', ')} (these paths are relative to the packed folder root; do not prefix them with parent directories like src/auth/)`
|
|
843
|
+
: undefined;
|
|
844
|
+
const smallFolderLeafQuota = sourceFilePaths.length > 0 && sourceFilePaths.length <= 3
|
|
845
|
+
? `Leaf quota: create no more than ${sourceFilePaths.length} curated leaf knowledge files for this folder unless the user explicitly asks for more.`
|
|
846
|
+
: undefined;
|
|
847
|
+
const smallFolderQuotaWarning = sourceFilePaths.length > 0 && sourceFilePaths.length <= 3
|
|
848
|
+
? `A topic-level overview leaf counts toward that quota and is usually incorrect here; keep folder-level framing in topicContext instead.`
|
|
849
|
+
: undefined;
|
|
788
850
|
const compactPrompt = [
|
|
789
851
|
`# Folder Curation Task`,
|
|
790
852
|
``,
|
|
791
853
|
`Folder: ${folderPath} (${packResult.fileCount} files, ${packResult.totalLines} lines)`,
|
|
792
854
|
`Data file: \`${tmpFilePath}\` (repomix-style XML format)`,
|
|
793
855
|
`Full instructions: variable \`${instructionsVar}\``,
|
|
856
|
+
shouldExposePackedFiles
|
|
857
|
+
? `Relevant files variable: \`${smallFolderFilesVar}\` (array of packed files; for this small folder, prefer using it directly instead of parsing XML with brittle regexes).`
|
|
858
|
+
: undefined,
|
|
794
859
|
contextSection,
|
|
860
|
+
sourceFilesSection,
|
|
861
|
+
`Small-folder rule: for folders with 3 or fewer relevant source files, create at most one leaf knowledge entry per file by default.`,
|
|
862
|
+
smallFolderLeafQuota,
|
|
863
|
+
smallFolderQuotaWarning,
|
|
864
|
+
`Do not create an extra overview leaf at the bare topic path; use topicContext for topic-level framing instead.`,
|
|
795
865
|
`**Start by reading instructions**: Use code_exec to read \`${instructionsVar}.slice(0, 5000)\` for the strategy section, then \`${instructionsVar}.slice(5000, 10000)\` for content rules.`,
|
|
796
866
|
`Use \`tools.readFile()\` and \`tools.grep()\` inside code_exec to process the XML data file.`,
|
|
797
867
|
`Use \`tools.curate()\` to create knowledge topics. Use \`setFinalResult()\` when done.`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { BRV_DIR, CONTEXT_FILE_EXTENSION, CONTEXT_TREE_DIR } from '../../constants.js';
|
|
2
|
+
import { ABSTRACT_EXTENSION, BRV_DIR, CONTEXT_FILE_EXTENSION, CONTEXT_TREE_DIR } from '../../constants.js';
|
|
3
3
|
import { isDerivedArtifact } from '../context-tree/derived-artifact.js';
|
|
4
4
|
import { FileContextTreeManifestService } from '../context-tree/file-context-tree-manifest-service.js';
|
|
5
5
|
import { canRespondDirectly, formatDirectResponse, formatNotFoundResponse, } from './direct-search-responder.js';
|
|
@@ -124,7 +124,7 @@ export class QueryExecutor {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
// Create per-task session for parallel isolation (own sandbox + history + LLM service)
|
|
127
|
-
const taskSessionId = await agent.createTaskSession(taskId, 'query');
|
|
127
|
+
const taskSessionId = await agent.createTaskSession(taskId, 'query', { userFacing: true });
|
|
128
128
|
// Task-scoped variable names for sandbox injection (RLM pattern).
|
|
129
129
|
// Replace hyphens with underscores: UUIDs have hyphens which are invalid in JS identifiers,
|
|
130
130
|
// so the LLM uses underscores when writing code-exec calls — matching curate-executor pattern.
|
|
@@ -273,7 +273,15 @@ ${responseFormat}`;
|
|
|
273
273
|
mtime: f.modified?.getTime() ?? 0,
|
|
274
274
|
path: f.path,
|
|
275
275
|
}));
|
|
276
|
-
|
|
276
|
+
// Include .abstract.md siblings so newly-written abstracts invalidate the cache.
|
|
277
|
+
// They are excluded by isDerivedArtifact() above, so we append them separately.
|
|
278
|
+
const abstractFiles = globResult.files
|
|
279
|
+
.filter((f) => f.path.endsWith(ABSTRACT_EXTENSION))
|
|
280
|
+
.map((f) => ({
|
|
281
|
+
mtime: f.modified?.getTime() ?? 0,
|
|
282
|
+
path: f.path,
|
|
283
|
+
}));
|
|
284
|
+
const fingerprint = QueryResultCache.computeFingerprint([...files, ...abstractFiles]);
|
|
277
285
|
this.cachedFingerprint = {
|
|
278
286
|
expiresAt: Date.now() + QueryExecutor.FINGERPRINT_CACHE_TTL_MS,
|
|
279
287
|
value: fingerprint,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
1
2
|
import { join } from 'node:path';
|
|
2
3
|
import { StatusEvents } from '../../../../shared/transport/events/status-events.js';
|
|
3
4
|
import { BRV_DIR, CONTEXT_TREE_DIR } from '../../../constants.js';
|
|
@@ -65,6 +66,15 @@ export class StatusHandler {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
catch { }
|
|
69
|
+
// Abstract generation queue status (written by agent process via abstract-queue.ts)
|
|
70
|
+
try {
|
|
71
|
+
const queueStatusPath = join(projectPath, BRV_DIR, '_queue_status.json');
|
|
72
|
+
const raw = await readFile(queueStatusPath, 'utf8');
|
|
73
|
+
result.abstractQueue = JSON.parse(raw);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// File doesn't exist yet — no queue running
|
|
77
|
+
}
|
|
68
78
|
// Context tree status
|
|
69
79
|
try {
|
|
70
80
|
const contextTreeExists = await this.contextTreeService.exists(projectPath);
|
|
@@ -98,22 +98,22 @@ export declare const CurateResultSchema: z.ZodObject<{
|
|
|
98
98
|
failed?: number | undefined;
|
|
99
99
|
deleted?: number | undefined;
|
|
100
100
|
updated?: number | undefined;
|
|
101
|
-
added?: number | undefined;
|
|
102
101
|
merged?: number | undefined;
|
|
102
|
+
added?: number | undefined;
|
|
103
103
|
}, {
|
|
104
104
|
failed?: number | undefined;
|
|
105
105
|
deleted?: number | undefined;
|
|
106
106
|
updated?: number | undefined;
|
|
107
|
-
added?: number | undefined;
|
|
108
107
|
merged?: number | undefined;
|
|
108
|
+
added?: number | undefined;
|
|
109
109
|
}>>;
|
|
110
110
|
}, "strip", z.ZodTypeAny, {
|
|
111
111
|
summary?: {
|
|
112
112
|
failed?: number | undefined;
|
|
113
113
|
deleted?: number | undefined;
|
|
114
114
|
updated?: number | undefined;
|
|
115
|
-
added?: number | undefined;
|
|
116
115
|
merged?: number | undefined;
|
|
116
|
+
added?: number | undefined;
|
|
117
117
|
} | undefined;
|
|
118
118
|
applied?: {
|
|
119
119
|
path: string;
|
|
@@ -135,8 +135,8 @@ export declare const CurateResultSchema: z.ZodObject<{
|
|
|
135
135
|
failed?: number | undefined;
|
|
136
136
|
deleted?: number | undefined;
|
|
137
137
|
updated?: number | undefined;
|
|
138
|
-
added?: number | undefined;
|
|
139
138
|
merged?: number | undefined;
|
|
139
|
+
added?: number | undefined;
|
|
140
140
|
} | undefined;
|
|
141
141
|
applied?: {
|
|
142
142
|
path: string;
|
|
@@ -114,6 +114,13 @@ export interface ProjectLocationDTO {
|
|
|
114
114
|
projectPath: string;
|
|
115
115
|
}
|
|
116
116
|
export interface StatusDTO {
|
|
117
|
+
/** Current state of the background abstract generation queue, if active */
|
|
118
|
+
abstractQueue?: {
|
|
119
|
+
failed: number;
|
|
120
|
+
pending: number;
|
|
121
|
+
processed: number;
|
|
122
|
+
processing: boolean;
|
|
123
|
+
};
|
|
117
124
|
authStatus: 'expired' | 'logged_in' | 'not_logged_in' | 'unknown';
|
|
118
125
|
contextTreeChanges?: ContextTreeChanges;
|
|
119
126
|
/** Absolute path to the context tree directory (e.g., '/Users/foo/project/.brv/context-tree') */
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
"name": "byterover-cli",
|
|
3
3
|
"description": "ByteRover's CLI",
|
|
4
4
|
"keywords": [
|
|
5
|
-
"cli",
|
|
6
|
-
"
|
|
5
|
+
"cli",
|
|
6
|
+
"ai",
|
|
7
|
+
"llm",
|
|
8
|
+
"mcp",
|
|
9
|
+
"developer-tools",
|
|
10
|
+
"context-memory",
|
|
11
|
+
"autonomous-agents",
|
|
12
|
+
"coding-assistant",
|
|
7
13
|
"knowledge-management"
|
|
8
14
|
],
|
|
9
|
-
"version": "3.
|
|
15
|
+
"version": "3.1.0",
|
|
10
16
|
"author": "ByteRover",
|
|
11
17
|
"bin": {
|
|
12
18
|
"brv": "./bin/run.js"
|
|
@@ -103,7 +109,7 @@
|
|
|
103
109
|
"expect-type": "^1.2.2",
|
|
104
110
|
"husky": "^9.1.7",
|
|
105
111
|
"lint-staged": "^16.3.1",
|
|
106
|
-
"mocha": "
|
|
112
|
+
"mocha": "10.8.2",
|
|
107
113
|
"nock": "^14.0.10",
|
|
108
114
|
"oclif": "^4",
|
|
109
115
|
"pdfkit": "^0.17.2",
|