byterover-cli 3.0.1 → 3.2.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 +4 -0
- package/README.md +17 -0
- 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-schemas.d.ts +8 -0
- package/dist/agent/infra/agent/agent-schemas.js +1 -0
- 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 +20 -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/sandbox-service.js +1 -0
- package/dist/agent/infra/sandbox/tools-sdk.d.ts +13 -1
- package/dist/agent/infra/sandbox/tools-sdk.js +9 -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 +392 -106
- package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
- package/dist/agent/infra/tools/implementations/write-file-tool.d.ts +2 -1
- package/dist/agent/infra/tools/implementations/write-file-tool.js +16 -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 +16 -5
- package/dist/agent/infra/tools/write-guard.d.ts +11 -0
- package/dist/agent/infra/tools/write-guard.js +48 -0
- package/dist/agent/resources/prompts/system-prompt.yml +9 -0
- package/dist/agent/resources/tools/expand_knowledge.txt +4 -0
- package/dist/agent/resources/tools/search_knowledge.txt +11 -1
- package/dist/oclif/commands/curate/index.js +4 -3
- package/dist/oclif/commands/curate/view.js +2 -2
- package/dist/oclif/commands/main.js +13 -0
- package/dist/oclif/commands/query.js +4 -3
- package/dist/oclif/commands/source/add.d.ts +12 -0
- package/dist/oclif/commands/source/add.js +42 -0
- package/dist/oclif/commands/source/index.d.ts +6 -0
- package/dist/oclif/commands/source/index.js +8 -0
- package/dist/oclif/commands/source/list.d.ts +6 -0
- package/dist/oclif/commands/source/list.js +32 -0
- package/dist/oclif/commands/source/remove.d.ts +9 -0
- package/dist/oclif/commands/source/remove.js +33 -0
- package/dist/oclif/commands/status.d.ts +5 -1
- package/dist/oclif/commands/status.js +41 -6
- package/dist/oclif/commands/worktree/add.d.ts +12 -0
- package/dist/oclif/commands/worktree/add.js +44 -0
- package/dist/oclif/commands/worktree/index.d.ts +6 -0
- package/dist/oclif/commands/worktree/index.js +8 -0
- package/dist/oclif/commands/worktree/list.d.ts +6 -0
- package/dist/oclif/commands/worktree/list.js +28 -0
- package/dist/oclif/commands/worktree/remove.d.ts +9 -0
- package/dist/oclif/commands/worktree/remove.js +35 -0
- package/dist/oclif/hooks/init/validate-brv-config.js +4 -0
- package/dist/oclif/lib/daemon-client.d.ts +4 -2
- package/dist/oclif/lib/daemon-client.js +19 -3
- package/dist/server/constants.d.ts +8 -0
- package/dist/server/constants.js +10 -0
- package/dist/server/core/domain/client/client-info.d.ts +7 -0
- package/dist/server/core/domain/client/client-info.js +11 -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/project/worktrees-schema.d.ts +29 -0
- package/dist/server/core/domain/project/worktrees-schema.js +17 -0
- package/dist/server/core/domain/source/source-operations.d.ts +31 -0
- package/dist/server/core/domain/source/source-operations.js +201 -0
- package/dist/server/core/domain/source/source-schema.d.ts +94 -0
- package/dist/server/core/domain/source/source-schema.js +121 -0
- package/dist/server/core/domain/transport/schemas.d.ts +18 -10
- package/dist/server/core/domain/transport/schemas.js +4 -0
- package/dist/server/core/domain/transport/task-info.d.ts +2 -0
- package/dist/server/core/interfaces/client/i-client-manager.d.ts +13 -0
- package/dist/server/core/interfaces/executor/i-curate-executor.d.ts +4 -0
- package/dist/server/core/interfaces/executor/i-folder-pack-executor.d.ts +7 -3
- package/dist/server/core/interfaces/executor/i-query-executor.d.ts +2 -0
- package/dist/server/infra/client/client-manager.d.ts +1 -0
- package/dist/server/infra/client/client-manager.js +16 -0
- 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/daemon/agent-process.js +15 -5
- package/dist/server/infra/executor/curate-executor.js +6 -3
- package/dist/server/infra/executor/direct-search-responder.js +5 -1
- package/dist/server/infra/executor/folder-pack-executor.js +88 -7
- package/dist/server/infra/executor/query-executor.d.ts +23 -0
- package/dist/server/infra/executor/query-executor.js +125 -23
- package/dist/server/infra/mcp/mcp-mode-detector.d.ts +7 -5
- package/dist/server/infra/mcp/mcp-mode-detector.js +11 -18
- package/dist/server/infra/mcp/mcp-server.d.ts +1 -0
- package/dist/server/infra/mcp/mcp-server.js +11 -6
- package/dist/server/infra/mcp/tools/brv-curate-tool.d.ts +2 -1
- package/dist/server/infra/mcp/tools/brv-curate-tool.js +9 -16
- package/dist/server/infra/mcp/tools/brv-query-tool.d.ts +2 -1
- package/dist/server/infra/mcp/tools/brv-query-tool.js +9 -16
- package/dist/server/infra/mcp/tools/mcp-project-context.d.ts +11 -0
- package/dist/server/infra/mcp/tools/mcp-project-context.js +54 -0
- package/dist/server/infra/process/connection-coordinator.js +11 -0
- package/dist/server/infra/process/feature-handlers.js +4 -1
- package/dist/server/infra/process/task-router.d.ts +1 -0
- package/dist/server/infra/process/task-router.js +60 -5
- package/dist/server/infra/project/resolve-project.d.ts +106 -0
- package/dist/server/infra/project/resolve-project.js +473 -0
- package/dist/server/infra/transport/handlers/index.d.ts +4 -0
- package/dist/server/infra/transport/handlers/index.js +2 -0
- package/dist/server/infra/transport/handlers/source-handler.d.ts +12 -0
- package/dist/server/infra/transport/handlers/source-handler.js +37 -0
- package/dist/server/infra/transport/handlers/status-handler.js +65 -13
- package/dist/server/infra/transport/handlers/worktree-handler.d.ts +12 -0
- package/dist/server/infra/transport/handlers/worktree-handler.js +67 -0
- package/dist/server/infra/transport/transport-connector.d.ts +10 -4
- package/dist/server/infra/transport/transport-connector.js +2 -2
- package/dist/server/utils/curate-result-parser.d.ts +4 -4
- package/dist/server/utils/path-utils.d.ts +5 -0
- package/dist/server/utils/path-utils.js +11 -1
- package/dist/shared/transport/events/client-events.d.ts +3 -0
- package/dist/shared/transport/events/client-events.js +3 -0
- package/dist/shared/transport/events/index.d.ts +13 -0
- package/dist/shared/transport/events/index.js +9 -0
- package/dist/shared/transport/events/source-events.d.ts +30 -0
- package/dist/shared/transport/events/source-events.js +5 -0
- package/dist/shared/transport/events/status-events.d.ts +5 -0
- package/dist/shared/transport/events/task-events.d.ts +4 -1
- package/dist/shared/transport/events/worktree-events.d.ts +31 -0
- package/dist/shared/transport/events/worktree-events.js +5 -0
- package/dist/shared/transport/types/dto.d.ts +26 -0
- package/dist/tui/features/commands/definitions/index.js +6 -0
- package/dist/tui/features/commands/definitions/source-add.d.ts +2 -0
- package/dist/tui/features/commands/definitions/source-add.js +48 -0
- package/dist/tui/features/commands/definitions/source-list.d.ts +2 -0
- package/dist/tui/features/commands/definitions/source-list.js +47 -0
- package/dist/tui/features/commands/definitions/source-remove.d.ts +2 -0
- package/dist/tui/features/commands/definitions/source-remove.js +38 -0
- package/dist/tui/features/commands/definitions/source.d.ts +2 -0
- package/dist/tui/features/commands/definitions/source.js +8 -0
- package/dist/tui/features/commands/definitions/worktree-add.d.ts +2 -0
- package/dist/tui/features/commands/definitions/worktree-add.js +35 -0
- package/dist/tui/features/commands/definitions/worktree-list.d.ts +2 -0
- package/dist/tui/features/commands/definitions/worktree-list.js +36 -0
- package/dist/tui/features/commands/definitions/worktree-remove.d.ts +2 -0
- package/dist/tui/features/commands/definitions/worktree-remove.js +33 -0
- package/dist/tui/features/commands/definitions/worktree.d.ts +2 -0
- package/dist/tui/features/commands/definitions/worktree.js +8 -0
- package/dist/tui/features/curate/api/create-curate-task.js +3 -1
- package/dist/tui/features/query/api/create-query-task.js +3 -1
- package/dist/tui/features/source/api/source-api.d.ts +4 -0
- package/dist/tui/features/source/api/source-api.js +22 -0
- package/dist/tui/features/status/api/get-status.js +2 -1
- package/dist/tui/features/status/utils/format-status.js +23 -1
- package/dist/tui/features/transport/components/transport-initializer.js +36 -1
- package/dist/tui/features/worktree/api/worktree-api.d.ts +4 -0
- package/dist/tui/features/worktree/api/worktree-api.js +22 -0
- package/dist/tui/repl-startup.d.ts +2 -0
- package/dist/tui/repl-startup.js +5 -3
- package/dist/tui/stores/transport-store.d.ts +6 -0
- package/dist/tui/stores/transport-store.js +6 -0
- package/oclif.manifest.json +261 -1
- package/package.json +10 -4
|
@@ -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
|
/**
|
|
@@ -31,6 +31,7 @@ import { AuthEvents } from '../../../shared/transport/events/auth-events.js';
|
|
|
31
31
|
import { getCurrentConfig } from '../../config/environment.js';
|
|
32
32
|
import { DEFAULT_LLM_MODEL, PROJECT } from '../../constants.js';
|
|
33
33
|
import { serializeTaskError, TaskError, TaskErrorCode } from '../../core/domain/errors/task-error.js';
|
|
34
|
+
import { loadSources } from '../../core/domain/source/source-schema.js';
|
|
34
35
|
import { TransportAgentEventNames, TransportDaemonEventNames, TransportStateEventNames, TransportTaskEventNames, } from '../../core/domain/transport/schemas.js';
|
|
35
36
|
import { CurateExecutor } from '../executor/curate-executor.js';
|
|
36
37
|
import { FolderPackExecutor } from '../executor/folder-pack-executor.js';
|
|
@@ -189,10 +190,14 @@ async function start() {
|
|
|
189
190
|
cachedProviderHeaders = providerResult.providerHeaders ? JSON.stringify(providerResult.providerHeaders) : undefined;
|
|
190
191
|
agentLog(`Provider: ${activeProvider}, Model: ${activeModel ?? 'default'}`);
|
|
191
192
|
// 5. Create CipherAgent with lazy providers + transport client
|
|
193
|
+
// Load knowledge sources early so shared context tree roots can be shared with both
|
|
194
|
+
// the agent's FileSystemService (via config) and the executor's FileSystemService
|
|
195
|
+
const sourcesData = loadSources(projectPath);
|
|
196
|
+
const sharedAllowedPaths = (sourcesData?.origins ?? []).map((o) => o.contextTreeRoot);
|
|
192
197
|
const envConfig = getCurrentConfig();
|
|
193
198
|
const agentConfig = {
|
|
194
199
|
apiBaseUrl: envConfig.llmApiBaseUrl,
|
|
195
|
-
fileSystem: { workingDirectory: projectPath },
|
|
200
|
+
fileSystem: { allowedPaths: ['.', ...sharedAllowedPaths], workingDirectory: projectPath },
|
|
196
201
|
llm: {
|
|
197
202
|
maxIterations: 10,
|
|
198
203
|
maxTokens: 4096,
|
|
@@ -279,7 +284,10 @@ async function start() {
|
|
|
279
284
|
}
|
|
280
285
|
});
|
|
281
286
|
// 6. Create FileSystemService + SearchKnowledgeService for smart query routing
|
|
282
|
-
const fileSystemService = new FileSystemService({
|
|
287
|
+
const fileSystemService = new FileSystemService({
|
|
288
|
+
allowedPaths: ['.', ...sharedAllowedPaths],
|
|
289
|
+
workingDirectory: projectPath,
|
|
290
|
+
});
|
|
283
291
|
await fileSystemService.initialize();
|
|
284
292
|
const searchService = createSearchKnowledgeService(fileSystemService, { baseDirectory: projectPath });
|
|
285
293
|
// 7. Create executors and listen for task:execute from pool
|
|
@@ -305,7 +313,7 @@ async function start() {
|
|
|
305
313
|
agentLog('Ready — listening for tasks');
|
|
306
314
|
}
|
|
307
315
|
async function executeTask(task, curateExecutor, folderPackExecutor, queryExecutor) {
|
|
308
|
-
const { clientCwd, clientId, content, files, folderPath, taskId, type } = task;
|
|
316
|
+
const { clientCwd, clientId, content, files, folderPath, taskId, type, worktreeRoot } = task;
|
|
309
317
|
if (!transport || !agent)
|
|
310
318
|
return;
|
|
311
319
|
const freshProviderConfig = await transport.requestWithAck(TransportStateEventNames.GET_PROVIDER_CONFIG);
|
|
@@ -372,7 +380,7 @@ async function executeTask(task, curateExecutor, folderPackExecutor, queryExecut
|
|
|
372
380
|
let result;
|
|
373
381
|
switch (type) {
|
|
374
382
|
case 'curate': {
|
|
375
|
-
result = await curateExecutor.executeWithAgent(agent, { clientCwd, content, files, taskId });
|
|
383
|
+
result = await curateExecutor.executeWithAgent(agent, { clientCwd, content, files, projectRoot: projectPath, taskId, worktreeRoot });
|
|
376
384
|
break;
|
|
377
385
|
}
|
|
378
386
|
case 'curate-folder': {
|
|
@@ -380,12 +388,14 @@ async function executeTask(task, curateExecutor, folderPackExecutor, queryExecut
|
|
|
380
388
|
clientCwd,
|
|
381
389
|
content,
|
|
382
390
|
folderPath: folderPath,
|
|
391
|
+
projectRoot: projectPath,
|
|
383
392
|
taskId,
|
|
393
|
+
worktreeRoot,
|
|
384
394
|
});
|
|
385
395
|
break;
|
|
386
396
|
}
|
|
387
397
|
case 'query': {
|
|
388
|
-
result = await queryExecutor.executeWithAgent(agent, { query: content, taskId });
|
|
398
|
+
result = await queryExecutor.executeWithAgent(agent, { query: content, taskId, worktreeRoot });
|
|
389
399
|
break;
|
|
390
400
|
}
|
|
391
401
|
}
|
|
@@ -36,7 +36,7 @@ export class CurateExecutor {
|
|
|
36
36
|
this.fileContentReader = fileContentReader ?? createFileContentReader();
|
|
37
37
|
}
|
|
38
38
|
async executeWithAgent(agent, options) {
|
|
39
|
-
const { clientCwd, content, files, taskId } = options;
|
|
39
|
+
const { clientCwd, content, files, projectRoot, taskId } = options;
|
|
40
40
|
// --- Phase 1: Preprocessing (no sessions created yet — safe to throw) ---
|
|
41
41
|
const fileReferenceInstructions = await this.processFileReferences(files ?? [], clientCwd);
|
|
42
42
|
const fullContext = fileReferenceInstructions ? `${content}\n${fileReferenceInstructions}` : content;
|
|
@@ -45,7 +45,9 @@ export class CurateExecutor {
|
|
|
45
45
|
const effectiveContext = compactionResult.context;
|
|
46
46
|
// --- Phase 3: Curation (session created AFTER preprocessing + compaction) ---
|
|
47
47
|
// Capture pre-curation state for snapshot diff (summary propagation)
|
|
48
|
-
|
|
48
|
+
// Post-processing (snapshot, summary, manifest) operates on projectRoot where .brv/ lives.
|
|
49
|
+
// worktreeRoot is a linked subdir — .brv/ does not exist there in linked setups.
|
|
50
|
+
const baseDir = projectRoot ?? clientCwd ?? process.cwd();
|
|
49
51
|
const snapshotService = new FileContextTreeSnapshotService({ baseDirectory: baseDir });
|
|
50
52
|
let preState;
|
|
51
53
|
try {
|
|
@@ -54,7 +56,7 @@ export class CurateExecutor {
|
|
|
54
56
|
catch {
|
|
55
57
|
// Fail-open: if snapshot fails, skip summary propagation
|
|
56
58
|
}
|
|
57
|
-
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true });
|
|
59
|
+
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true, userFacing: true });
|
|
58
60
|
try {
|
|
59
61
|
// Task-scoped variable names for RLM pattern.
|
|
60
62
|
// Replace hyphens with underscores: UUIDs have hyphens which are invalid in JS identifiers,
|
|
@@ -124,6 +126,7 @@ export class CurateExecutor {
|
|
|
124
126
|
// Fail-open: summary/manifest errors never block curation
|
|
125
127
|
}
|
|
126
128
|
}
|
|
129
|
+
await agent.drainBackgroundWork?.();
|
|
127
130
|
return response;
|
|
128
131
|
}
|
|
129
132
|
finally {
|
|
@@ -58,7 +58,11 @@ export function formatDirectResponse(query, results) {
|
|
|
58
58
|
return `### ${r.title}\n\n${truncatedContent}`;
|
|
59
59
|
})
|
|
60
60
|
.join('\n\n---\n\n');
|
|
61
|
-
const sources = topResults.map((r) =>
|
|
61
|
+
const sources = topResults.map((r) => {
|
|
62
|
+
// Paths starting with [ are already namespaced (linked results)
|
|
63
|
+
const displayPath = r.path.startsWith('[') ? r.path : `.brv/context-tree/${r.path}`;
|
|
64
|
+
return `- \`${displayPath}\``;
|
|
65
|
+
}).join('\n');
|
|
62
66
|
return `**Summary**: ${summary}
|
|
63
67
|
|
|
64
68
|
**Details**:
|
|
@@ -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)
|
|
@@ -34,13 +38,32 @@ export class FolderPackExecutor {
|
|
|
34
38
|
this.folderPackService = folderPackService;
|
|
35
39
|
}
|
|
36
40
|
async executeWithAgent(agent, options) {
|
|
37
|
-
const { clientCwd, content, folderPath, taskId } = options;
|
|
41
|
+
const { clientCwd, content, folderPath, projectRoot, taskId, worktreeRoot } = options;
|
|
42
|
+
// Resolve folder path:
|
|
43
|
+
// - Absent folderPath → default to worktreeRoot (implicit workspace default)
|
|
44
|
+
// - Relative folderPath → resolve from clientCwd (shell semantics)
|
|
45
|
+
// - Absolute folderPath → use as-is
|
|
46
|
+
let absoluteFolderPath;
|
|
38
47
|
if (!folderPath) {
|
|
39
|
-
|
|
48
|
+
absoluteFolderPath = worktreeRoot ?? clientCwd ?? process.cwd();
|
|
49
|
+
}
|
|
50
|
+
else if (path.isAbsolute(folderPath)) {
|
|
51
|
+
absoluteFolderPath = folderPath;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const shellCwd = clientCwd ?? process.cwd();
|
|
55
|
+
absoluteFolderPath = path.resolve(shellCwd, folderPath);
|
|
56
|
+
}
|
|
57
|
+
// Temp file location: use projectRoot where .brv/ lives (accessible to sandbox)
|
|
58
|
+
const tempFileDir = projectRoot ?? clientCwd ?? process.cwd();
|
|
59
|
+
const snapshotService = new FileContextTreeSnapshotService({ baseDirectory: tempFileDir });
|
|
60
|
+
let preState;
|
|
61
|
+
try {
|
|
62
|
+
preState = await snapshotService.getCurrentState(tempFileDir);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Fail-open: if snapshot fails, skip summary propagation
|
|
40
66
|
}
|
|
41
|
-
// Resolve folder path
|
|
42
|
-
const basePath = clientCwd ?? process.cwd();
|
|
43
|
-
const absoluteFolderPath = path.isAbsolute(folderPath) ? folderPath : path.resolve(basePath, folderPath);
|
|
44
67
|
// Pack the folder
|
|
45
68
|
const packResult = await this.folderPackService.pack(absoluteFolderPath, {
|
|
46
69
|
extractDocuments: true,
|
|
@@ -50,7 +73,26 @@ export class FolderPackExecutor {
|
|
|
50
73
|
// Use iterative extraction strategy (inspired by rlm)
|
|
51
74
|
// Stores packed folder in sandbox environment and lets agent iteratively query/extract
|
|
52
75
|
// This avoids token limits entirely - works for folders of any size
|
|
53
|
-
|
|
76
|
+
const response = await this.executeIterative(agent, packResult, content, absoluteFolderPath, taskId, tempFileDir);
|
|
77
|
+
if (preState) {
|
|
78
|
+
try {
|
|
79
|
+
const postState = await snapshotService.getCurrentState(tempFileDir);
|
|
80
|
+
const changedPaths = diffStates(preState, postState);
|
|
81
|
+
if (changedPaths.length > 0) {
|
|
82
|
+
const summaryService = new FileContextTreeSummaryService();
|
|
83
|
+
const results = await summaryService.propagateStaleness(changedPaths, agent, tempFileDir);
|
|
84
|
+
if (results.some((result) => result.actionTaken)) {
|
|
85
|
+
const manifestService = new FileContextTreeManifestService({ baseDirectory: tempFileDir });
|
|
86
|
+
await manifestService.buildManifest(tempFileDir);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Fail-open: summary/manifest errors never block curation
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
await agent.drainBackgroundWork?.();
|
|
95
|
+
return response;
|
|
54
96
|
}
|
|
55
97
|
/**
|
|
56
98
|
* Build iterative extraction prompt with file-based access.
|
|
@@ -133,6 +175,15 @@ Use **code_exec with tools.readFile/tools.grep** to extract knowledge:
|
|
|
133
175
|
|
|
134
176
|
**Important**: All tools.* methods are async - always use \`await\`!
|
|
135
177
|
|
|
178
|
+
## Curate Shape Constraints
|
|
179
|
+
|
|
180
|
+
- Prefer one concrete knowledge entry per relevant source file when curating a small leaf folder.
|
|
181
|
+
- 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.
|
|
182
|
+
- Do **NOT** create an extra module/folder "overview" leaf at the bare topic path just because the folder has multiple files.
|
|
183
|
+
- Treat bare topic paths as scopes for \`topicContext\`, not as default destinations for standalone knowledge files.
|
|
184
|
+
- 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.
|
|
185
|
+
- 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.
|
|
186
|
+
|
|
136
187
|
## Common Mistakes to Avoid
|
|
137
188
|
|
|
138
189
|
**❌ WRONG - Using require() inside code_exec:**
|
|
@@ -773,7 +824,7 @@ await tools.curate([{
|
|
|
773
824
|
throw new Error(`Failed to write temp file: ${error instanceof Error ? error.message : String(error)}`);
|
|
774
825
|
}
|
|
775
826
|
// Create per-task session for parallel isolation (own sandbox + history + LLM service)
|
|
776
|
-
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true });
|
|
827
|
+
const taskSessionId = await agent.createTaskSession(taskId, 'curate', { mapRootEligible: true, userFacing: true });
|
|
777
828
|
// Step 3: Store full instructions as sandbox variable (lazy prompt loading).
|
|
778
829
|
// This saves ~12-15K tokens by keeping the massive instruction set out of the prompt.
|
|
779
830
|
// The LLM reads instructions on-demand via code_exec.
|
|
@@ -783,15 +834,45 @@ await tools.curate([{
|
|
|
783
834
|
const taskIdSafe = taskId.replaceAll('-', '_');
|
|
784
835
|
const instructionsVar = `__curate_instructions_${taskIdSafe}`;
|
|
785
836
|
agent.setSandboxVariableOnSession(taskSessionId, instructionsVar, fullInstructions);
|
|
837
|
+
const smallFolderFilesVar = `__curate_files_${taskIdSafe}`;
|
|
838
|
+
const shouldExposePackedFiles = packResult.files.length > 0 && packResult.files.length <= 10 && packResult.totalCharacters <= 80_000;
|
|
839
|
+
if (shouldExposePackedFiles) {
|
|
840
|
+
agent.setSandboxVariableOnSession(taskSessionId, smallFolderFilesVar, packResult.files.map((file) => ({
|
|
841
|
+
content: file.content,
|
|
842
|
+
fileType: file.fileType,
|
|
843
|
+
lineCount: file.lineCount,
|
|
844
|
+
path: file.path,
|
|
845
|
+
size: file.size,
|
|
846
|
+
truncated: file.truncated,
|
|
847
|
+
})));
|
|
848
|
+
}
|
|
786
849
|
// Compact prompt with variable reference and essential metadata
|
|
787
850
|
const contextSection = userContext?.trim() ? `\nUser context: ${userContext}\n` : '';
|
|
851
|
+
const sourceFilePaths = packResult.files.map((file) => file.path);
|
|
852
|
+
const sourceFilesSection = sourceFilePaths.length > 0
|
|
853
|
+
? `Relevant source files: ${sourceFilePaths.join(', ')} (these paths are relative to the packed folder root; do not prefix them with parent directories like src/auth/)`
|
|
854
|
+
: undefined;
|
|
855
|
+
const smallFolderLeafQuota = sourceFilePaths.length > 0 && sourceFilePaths.length <= 3
|
|
856
|
+
? `Leaf quota: create no more than ${sourceFilePaths.length} curated leaf knowledge files for this folder unless the user explicitly asks for more.`
|
|
857
|
+
: undefined;
|
|
858
|
+
const smallFolderQuotaWarning = sourceFilePaths.length > 0 && sourceFilePaths.length <= 3
|
|
859
|
+
? `A topic-level overview leaf counts toward that quota and is usually incorrect here; keep folder-level framing in topicContext instead.`
|
|
860
|
+
: undefined;
|
|
788
861
|
const compactPrompt = [
|
|
789
862
|
`# Folder Curation Task`,
|
|
790
863
|
``,
|
|
791
864
|
`Folder: ${folderPath} (${packResult.fileCount} files, ${packResult.totalLines} lines)`,
|
|
792
865
|
`Data file: \`${tmpFilePath}\` (repomix-style XML format)`,
|
|
793
866
|
`Full instructions: variable \`${instructionsVar}\``,
|
|
867
|
+
shouldExposePackedFiles
|
|
868
|
+
? `Relevant files variable: \`${smallFolderFilesVar}\` (array of packed files; for this small folder, prefer using it directly instead of parsing XML with brittle regexes).`
|
|
869
|
+
: undefined,
|
|
794
870
|
contextSection,
|
|
871
|
+
sourceFilesSection,
|
|
872
|
+
`Small-folder rule: for folders with 3 or fewer relevant source files, create at most one leaf knowledge entry per file by default.`,
|
|
873
|
+
smallFolderLeafQuota,
|
|
874
|
+
smallFolderQuotaWarning,
|
|
875
|
+
`Do not create an extra overview leaf at the bare topic path; use topicContext for topic-level framing instead.`,
|
|
795
876
|
`**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
877
|
`Use \`tools.readFile()\` and \`tools.grep()\` inside code_exec to process the XML data file.`,
|
|
797
878
|
`Use \`tools.curate()\` to create knowledge topics. Use \`setFinalResult()\` when done.`,
|
|
@@ -64,8 +64,31 @@ export declare class QueryExecutor implements IQueryExecutor {
|
|
|
64
64
|
* Compute a context tree fingerprint cheaply using file mtimes.
|
|
65
65
|
* Used for cache invalidation — if any file in the context tree changes,
|
|
66
66
|
* the fingerprint changes and cached results are invalidated.
|
|
67
|
+
*
|
|
68
|
+
* Includes worktreeRoot in the hash so different workspaces produce
|
|
69
|
+
* different fingerprints, preventing cross-workspace cache bleed.
|
|
67
70
|
*/
|
|
68
71
|
private computeContextTreeFingerprint;
|
|
72
|
+
/**
|
|
73
|
+
* Lightweight hash of currently valid shared source keys.
|
|
74
|
+
* Used by the fingerprint cache fast path to detect when a source target
|
|
75
|
+
* becomes broken (directory deleted) within the TTL window.
|
|
76
|
+
* Cost: one readFileSync + existsSync per source — sub-millisecond for typical setups.
|
|
77
|
+
*/
|
|
78
|
+
private computeSourceValidityHash;
|
|
79
|
+
/**
|
|
80
|
+
* Derive a workspace scope for search from the worktreeRoot.
|
|
81
|
+
* Returns the relative path from projectRoot to worktreeRoot,
|
|
82
|
+
* or undefined if they are the same (no scoping needed).
|
|
83
|
+
*
|
|
84
|
+
* KNOWN LIMITATION: Workspace scoping only works if the curated context
|
|
85
|
+
* tree has a subtree matching the workspace relative path (e.g., 'packages/api').
|
|
86
|
+
* Since the context tree is organized semantically by the LLM (topic-based),
|
|
87
|
+
* not by directory structure, scope filtering typically has 0 matches and
|
|
88
|
+
* falls through to unscoped search. A proper fix requires tagging curated
|
|
89
|
+
* files with source workspace metadata during curation.
|
|
90
|
+
*/
|
|
91
|
+
private deriveWorkspaceScope;
|
|
69
92
|
/**
|
|
70
93
|
* Extract key entities from a query for supplementary searches.
|
|
71
94
|
* Simple heuristic: split query, filter stopwords, keep significant terms.
|