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.
Files changed (196) hide show
  1. package/.env.production +4 -0
  2. package/README.md +17 -0
  3. package/dist/agent/core/domain/tools/constants.d.ts +1 -0
  4. package/dist/agent/core/domain/tools/constants.js +1 -0
  5. package/dist/agent/core/interfaces/cipher-services.d.ts +8 -0
  6. package/dist/agent/core/interfaces/i-cipher-agent.d.ts +1 -0
  7. package/dist/agent/infra/agent/agent-error-codes.d.ts +0 -1
  8. package/dist/agent/infra/agent/agent-error-codes.js +0 -1
  9. package/dist/agent/infra/agent/agent-error.d.ts +0 -1
  10. package/dist/agent/infra/agent/agent-error.js +0 -1
  11. package/dist/agent/infra/agent/agent-schemas.d.ts +8 -0
  12. package/dist/agent/infra/agent/agent-schemas.js +1 -0
  13. package/dist/agent/infra/agent/agent-state-manager.d.ts +1 -3
  14. package/dist/agent/infra/agent/agent-state-manager.js +1 -3
  15. package/dist/agent/infra/agent/base-agent.d.ts +1 -1
  16. package/dist/agent/infra/agent/base-agent.js +1 -1
  17. package/dist/agent/infra/agent/cipher-agent.d.ts +15 -1
  18. package/dist/agent/infra/agent/cipher-agent.js +188 -3
  19. package/dist/agent/infra/agent/index.d.ts +1 -1
  20. package/dist/agent/infra/agent/index.js +1 -1
  21. package/dist/agent/infra/agent/service-initializer.d.ts +3 -3
  22. package/dist/agent/infra/agent/service-initializer.js +14 -8
  23. package/dist/agent/infra/agent/types.d.ts +0 -1
  24. package/dist/agent/infra/file-system/file-system-service.js +6 -5
  25. package/dist/agent/infra/folder-pack/folder-pack-service.d.ts +1 -0
  26. package/dist/agent/infra/folder-pack/folder-pack-service.js +29 -15
  27. package/dist/agent/infra/llm/providers/openai.js +12 -0
  28. package/dist/agent/infra/llm/stream-to-text.d.ts +7 -0
  29. package/dist/agent/infra/llm/stream-to-text.js +14 -0
  30. package/dist/agent/infra/map/abstract-generator.d.ts +22 -0
  31. package/dist/agent/infra/map/abstract-generator.js +67 -0
  32. package/dist/agent/infra/map/abstract-queue.d.ts +67 -0
  33. package/dist/agent/infra/map/abstract-queue.js +218 -0
  34. package/dist/agent/infra/memory/memory-deduplicator.d.ts +44 -0
  35. package/dist/agent/infra/memory/memory-deduplicator.js +88 -0
  36. package/dist/agent/infra/memory/memory-manager.d.ts +1 -0
  37. package/dist/agent/infra/memory/memory-manager.js +6 -5
  38. package/dist/agent/infra/sandbox/curate-service.d.ts +4 -2
  39. package/dist/agent/infra/sandbox/curate-service.js +20 -7
  40. package/dist/agent/infra/sandbox/local-sandbox.d.ts +5 -0
  41. package/dist/agent/infra/sandbox/local-sandbox.js +57 -1
  42. package/dist/agent/infra/sandbox/sandbox-service.js +1 -0
  43. package/dist/agent/infra/sandbox/tools-sdk.d.ts +13 -1
  44. package/dist/agent/infra/sandbox/tools-sdk.js +9 -1
  45. package/dist/agent/infra/session/session-compressor.d.ts +43 -0
  46. package/dist/agent/infra/session/session-compressor.js +296 -0
  47. package/dist/agent/infra/session/session-manager.d.ts +7 -0
  48. package/dist/agent/infra/session/session-manager.js +9 -0
  49. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +3 -2
  50. package/dist/agent/infra/tools/implementations/curate-tool.js +54 -27
  51. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +3 -3
  52. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +34 -7
  53. package/dist/agent/infra/tools/implementations/ingest-resource-tool.d.ts +17 -0
  54. package/dist/agent/infra/tools/implementations/ingest-resource-tool.js +224 -0
  55. package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +8 -0
  56. package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -1
  57. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +392 -106
  58. package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
  59. package/dist/agent/infra/tools/implementations/write-file-tool.d.ts +2 -1
  60. package/dist/agent/infra/tools/implementations/write-file-tool.js +16 -2
  61. package/dist/agent/infra/tools/tool-provider.js +1 -0
  62. package/dist/agent/infra/tools/tool-registry.d.ts +3 -0
  63. package/dist/agent/infra/tools/tool-registry.js +16 -5
  64. package/dist/agent/infra/tools/write-guard.d.ts +11 -0
  65. package/dist/agent/infra/tools/write-guard.js +48 -0
  66. package/dist/agent/resources/prompts/system-prompt.yml +9 -0
  67. package/dist/agent/resources/tools/expand_knowledge.txt +4 -0
  68. package/dist/agent/resources/tools/search_knowledge.txt +11 -1
  69. package/dist/oclif/commands/curate/index.js +4 -3
  70. package/dist/oclif/commands/curate/view.js +2 -2
  71. package/dist/oclif/commands/main.js +13 -0
  72. package/dist/oclif/commands/query.js +4 -3
  73. package/dist/oclif/commands/source/add.d.ts +12 -0
  74. package/dist/oclif/commands/source/add.js +42 -0
  75. package/dist/oclif/commands/source/index.d.ts +6 -0
  76. package/dist/oclif/commands/source/index.js +8 -0
  77. package/dist/oclif/commands/source/list.d.ts +6 -0
  78. package/dist/oclif/commands/source/list.js +32 -0
  79. package/dist/oclif/commands/source/remove.d.ts +9 -0
  80. package/dist/oclif/commands/source/remove.js +33 -0
  81. package/dist/oclif/commands/status.d.ts +5 -1
  82. package/dist/oclif/commands/status.js +41 -6
  83. package/dist/oclif/commands/worktree/add.d.ts +12 -0
  84. package/dist/oclif/commands/worktree/add.js +44 -0
  85. package/dist/oclif/commands/worktree/index.d.ts +6 -0
  86. package/dist/oclif/commands/worktree/index.js +8 -0
  87. package/dist/oclif/commands/worktree/list.d.ts +6 -0
  88. package/dist/oclif/commands/worktree/list.js +28 -0
  89. package/dist/oclif/commands/worktree/remove.d.ts +9 -0
  90. package/dist/oclif/commands/worktree/remove.js +35 -0
  91. package/dist/oclif/hooks/init/validate-brv-config.js +4 -0
  92. package/dist/oclif/lib/daemon-client.d.ts +4 -2
  93. package/dist/oclif/lib/daemon-client.js +19 -3
  94. package/dist/server/constants.d.ts +8 -0
  95. package/dist/server/constants.js +10 -0
  96. package/dist/server/core/domain/client/client-info.d.ts +7 -0
  97. package/dist/server/core/domain/client/client-info.js +11 -0
  98. package/dist/server/core/domain/knowledge/memory-scoring.d.ts +3 -3
  99. package/dist/server/core/domain/knowledge/memory-scoring.js +5 -5
  100. package/dist/server/core/domain/knowledge/summary-types.d.ts +4 -0
  101. package/dist/server/core/domain/project/worktrees-schema.d.ts +29 -0
  102. package/dist/server/core/domain/project/worktrees-schema.js +17 -0
  103. package/dist/server/core/domain/source/source-operations.d.ts +31 -0
  104. package/dist/server/core/domain/source/source-operations.js +201 -0
  105. package/dist/server/core/domain/source/source-schema.d.ts +94 -0
  106. package/dist/server/core/domain/source/source-schema.js +121 -0
  107. package/dist/server/core/domain/transport/schemas.d.ts +18 -10
  108. package/dist/server/core/domain/transport/schemas.js +4 -0
  109. package/dist/server/core/domain/transport/task-info.d.ts +2 -0
  110. package/dist/server/core/interfaces/client/i-client-manager.d.ts +13 -0
  111. package/dist/server/core/interfaces/executor/i-curate-executor.d.ts +4 -0
  112. package/dist/server/core/interfaces/executor/i-folder-pack-executor.d.ts +7 -3
  113. package/dist/server/core/interfaces/executor/i-query-executor.d.ts +2 -0
  114. package/dist/server/infra/client/client-manager.d.ts +1 -0
  115. package/dist/server/infra/client/client-manager.js +16 -0
  116. package/dist/server/infra/context-tree/derived-artifact.js +5 -1
  117. package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +2 -1
  118. package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +43 -7
  119. package/dist/server/infra/context-tree/file-context-tree-summary-service.js +20 -2
  120. package/dist/server/infra/daemon/agent-process.js +15 -5
  121. package/dist/server/infra/executor/curate-executor.js +6 -3
  122. package/dist/server/infra/executor/direct-search-responder.js +5 -1
  123. package/dist/server/infra/executor/folder-pack-executor.js +88 -7
  124. package/dist/server/infra/executor/query-executor.d.ts +23 -0
  125. package/dist/server/infra/executor/query-executor.js +125 -23
  126. package/dist/server/infra/mcp/mcp-mode-detector.d.ts +7 -5
  127. package/dist/server/infra/mcp/mcp-mode-detector.js +11 -18
  128. package/dist/server/infra/mcp/mcp-server.d.ts +1 -0
  129. package/dist/server/infra/mcp/mcp-server.js +11 -6
  130. package/dist/server/infra/mcp/tools/brv-curate-tool.d.ts +2 -1
  131. package/dist/server/infra/mcp/tools/brv-curate-tool.js +9 -16
  132. package/dist/server/infra/mcp/tools/brv-query-tool.d.ts +2 -1
  133. package/dist/server/infra/mcp/tools/brv-query-tool.js +9 -16
  134. package/dist/server/infra/mcp/tools/mcp-project-context.d.ts +11 -0
  135. package/dist/server/infra/mcp/tools/mcp-project-context.js +54 -0
  136. package/dist/server/infra/process/connection-coordinator.js +11 -0
  137. package/dist/server/infra/process/feature-handlers.js +4 -1
  138. package/dist/server/infra/process/task-router.d.ts +1 -0
  139. package/dist/server/infra/process/task-router.js +60 -5
  140. package/dist/server/infra/project/resolve-project.d.ts +106 -0
  141. package/dist/server/infra/project/resolve-project.js +473 -0
  142. package/dist/server/infra/transport/handlers/index.d.ts +4 -0
  143. package/dist/server/infra/transport/handlers/index.js +2 -0
  144. package/dist/server/infra/transport/handlers/source-handler.d.ts +12 -0
  145. package/dist/server/infra/transport/handlers/source-handler.js +37 -0
  146. package/dist/server/infra/transport/handlers/status-handler.js +65 -13
  147. package/dist/server/infra/transport/handlers/worktree-handler.d.ts +12 -0
  148. package/dist/server/infra/transport/handlers/worktree-handler.js +67 -0
  149. package/dist/server/infra/transport/transport-connector.d.ts +10 -4
  150. package/dist/server/infra/transport/transport-connector.js +2 -2
  151. package/dist/server/utils/curate-result-parser.d.ts +4 -4
  152. package/dist/server/utils/path-utils.d.ts +5 -0
  153. package/dist/server/utils/path-utils.js +11 -1
  154. package/dist/shared/transport/events/client-events.d.ts +3 -0
  155. package/dist/shared/transport/events/client-events.js +3 -0
  156. package/dist/shared/transport/events/index.d.ts +13 -0
  157. package/dist/shared/transport/events/index.js +9 -0
  158. package/dist/shared/transport/events/source-events.d.ts +30 -0
  159. package/dist/shared/transport/events/source-events.js +5 -0
  160. package/dist/shared/transport/events/status-events.d.ts +5 -0
  161. package/dist/shared/transport/events/task-events.d.ts +4 -1
  162. package/dist/shared/transport/events/worktree-events.d.ts +31 -0
  163. package/dist/shared/transport/events/worktree-events.js +5 -0
  164. package/dist/shared/transport/types/dto.d.ts +26 -0
  165. package/dist/tui/features/commands/definitions/index.js +6 -0
  166. package/dist/tui/features/commands/definitions/source-add.d.ts +2 -0
  167. package/dist/tui/features/commands/definitions/source-add.js +48 -0
  168. package/dist/tui/features/commands/definitions/source-list.d.ts +2 -0
  169. package/dist/tui/features/commands/definitions/source-list.js +47 -0
  170. package/dist/tui/features/commands/definitions/source-remove.d.ts +2 -0
  171. package/dist/tui/features/commands/definitions/source-remove.js +38 -0
  172. package/dist/tui/features/commands/definitions/source.d.ts +2 -0
  173. package/dist/tui/features/commands/definitions/source.js +8 -0
  174. package/dist/tui/features/commands/definitions/worktree-add.d.ts +2 -0
  175. package/dist/tui/features/commands/definitions/worktree-add.js +35 -0
  176. package/dist/tui/features/commands/definitions/worktree-list.d.ts +2 -0
  177. package/dist/tui/features/commands/definitions/worktree-list.js +36 -0
  178. package/dist/tui/features/commands/definitions/worktree-remove.d.ts +2 -0
  179. package/dist/tui/features/commands/definitions/worktree-remove.js +33 -0
  180. package/dist/tui/features/commands/definitions/worktree.d.ts +2 -0
  181. package/dist/tui/features/commands/definitions/worktree.js +8 -0
  182. package/dist/tui/features/curate/api/create-curate-task.js +3 -1
  183. package/dist/tui/features/query/api/create-query-task.js +3 -1
  184. package/dist/tui/features/source/api/source-api.d.ts +4 -0
  185. package/dist/tui/features/source/api/source-api.js +22 -0
  186. package/dist/tui/features/status/api/get-status.js +2 -1
  187. package/dist/tui/features/status/utils/format-status.js +23 -1
  188. package/dist/tui/features/transport/components/transport-initializer.js +36 -1
  189. package/dist/tui/features/worktree/api/worktree-api.d.ts +4 -0
  190. package/dist/tui/features/worktree/api/worktree-api.js +22 -0
  191. package/dist/tui/repl-startup.d.ts +2 -0
  192. package/dist/tui/repl-startup.js +5 -3
  193. package/dist/tui/stores/transport-store.d.ts +6 -0
  194. package/dist/tui/stores/transport-store.js +6 -0
  195. package/oclif.manifest.json +261 -1
  196. package/package.json +10 -4
@@ -4,7 +4,7 @@
4
4
  * This module is responsible for initializing and wiring together all core agent services.
5
5
  * It provides a single entry point for constructing the service graph.
6
6
  *
7
- * Following DextoAgent pattern:
7
+ * Following pattern:
8
8
  * - Config file is source of truth (ValidatedAgentConfig)
9
9
  * - Centralized function (not factory class) for service creation
10
10
  * - Explicit dependency order with numbered steps
@@ -26,6 +26,7 @@ import { createGeneratorForProvider } from '../llm/providers/index.js';
26
26
  import { DEFAULT_RETRY_POLICY } from '../llm/retry/retry-policy.js';
27
27
  import { GeminiTokenizer } from '../llm/tokenizers/gemini-tokenizer.js';
28
28
  import { EventBasedLogger } from '../logger/event-based-logger.js';
29
+ import { AbstractGenerationQueue } from '../map/abstract-queue.js';
29
30
  import { MemoryManager } from '../memory/memory-manager.js';
30
31
  import { ProcessService } from '../process/process-service.js';
31
32
  import { SandboxService } from '../sandbox/sandbox-service.js';
@@ -45,7 +46,7 @@ import { ToolProvider } from '../tools/tool-provider.js';
45
46
  * Creates shared services for CipherAgent.
46
47
  * These services are singletons shared across all sessions.
47
48
  *
48
- * Initialization order follows DextoAgent pattern (explicit numbered steps):
49
+ * Initialization order (explicit numbered steps):
49
50
  * 1. Logger (uses provided event bus)
50
51
  * 2. File system service (no dependencies)
51
52
  * 3. Process service (no dependencies)
@@ -60,11 +61,11 @@ import { ToolProvider } from '../tools/tool-provider.js';
60
61
  * 12. Return all services
61
62
  *
62
63
  * @param config - Validated agent configuration (Zod-validated)
63
- * @param agentEventBus - Pre-created event bus from agent constructor (DextoAgent pattern)
64
+ * @param agentEventBus - Pre-created event bus from agent constructor
64
65
  * @returns Initialized shared services
65
66
  */
66
67
  export async function createCipherAgentServices(config, agentEventBus) {
67
- // 1. Logger (uses provided event bus - DextoAgent pattern)
68
+ // 1. Logger (uses provided event bus )
68
69
  const logger = new EventBasedLogger(agentEventBus, 'CipherAgent');
69
70
  // 2. File system service (no dependencies)
70
71
  const fileSystemService = new FileSystemService(config.fileSystem);
@@ -132,10 +133,13 @@ export async function createCipherAgentServices(config, agentEventBus) {
132
133
  // Priority 16 — right after context tree structure, before memories
133
134
  const mapSelectionContributor = new MapSelectionContributor('mapSelection', 16);
134
135
  systemPromptManager.registerContributor(mapSelectionContributor);
135
- // 7. Tool provider (depends on FileSystemService, ProcessService, MemoryManager, SystemPromptManager)
136
+ // 7. Abstract generation queue (generator injected later via rebindCurateTools)
137
+ const abstractQueue = new AbstractGenerationQueue(workingDirectory);
138
+ // 8. Tool provider (depends on FileSystemService, ProcessService, MemoryManager, SystemPromptManager)
136
139
  const verbose = config.llm.verbose ?? false;
137
140
  const descriptionLoader = new ToolDescriptionLoader();
138
141
  const toolProvider = new ToolProvider({
142
+ abstractQueue,
139
143
  environmentContext,
140
144
  fileSystemService,
141
145
  getToolProvider: () => toolProvider,
@@ -144,14 +148,14 @@ export async function createCipherAgentServices(config, agentEventBus) {
144
148
  sandboxService,
145
149
  }, systemPromptManager, descriptionLoader);
146
150
  await toolProvider.initialize();
147
- // 8. Policy engine with default rules for autonomous execution
151
+ // 9. Policy engine with default rules for autonomous execution
148
152
  const policyEngine = new PolicyEngine({ defaultDecision: 'ALLOW' });
149
153
  policyEngine.addRules(DEFAULT_POLICY_RULES);
150
- // 9. Tool scheduler (orchestrates policy check → execution)
154
+ // 10. Tool scheduler (orchestrates policy check → execution)
151
155
  const toolScheduler = new CoreToolScheduler(toolProvider, policyEngine, undefined, {
152
156
  verbose,
153
157
  });
154
- // 10. Tool manager (with scheduler for policy-based execution)
158
+ // 11. Tool manager (with scheduler for policy-based execution)
155
159
  const toolManager = new ToolManager(toolProvider, toolScheduler);
156
160
  await toolManager.initialize();
157
161
  // 11. History storage - granular file-based storage
@@ -177,6 +181,7 @@ export async function createCipherAgentServices(config, agentEventBus) {
177
181
  workingDirectory,
178
182
  });
179
183
  return {
184
+ abstractQueue,
180
185
  agentEventBus,
181
186
  blobStorage,
182
187
  compactionService,
@@ -191,6 +196,7 @@ export async function createCipherAgentServices(config, agentEventBus) {
191
196
  toolManager,
192
197
  toolProvider,
193
198
  toolScheduler,
199
+ workingDirectory,
194
200
  };
195
201
  }
196
202
  /**
@@ -19,7 +19,6 @@ export interface AgentExecutionContext {
19
19
  /**
20
20
  * Agent event subscriber interface.
21
21
  * Objects implementing this can be registered for event subscription.
22
- * Follows DextoAgent's AgentEventSubscriber pattern.
23
22
  */
24
23
  export interface AgentEventSubscriber {
25
24
  /**
@@ -208,17 +208,18 @@ export class FileSystemService {
208
208
  this.ensureInitialized();
209
209
  const rawCwd = options.cwd ?? this.config.workingDirectory;
210
210
  const cwd = path.isAbsolute(rawCwd) ? rawCwd : path.resolve(this.config.workingDirectory, rawCwd);
211
+ const normalizedCwd = await fs.realpath(cwd).catch(() => cwd);
211
212
  const maxResults = options.maxResults ?? 1000;
212
213
  const includeMetadata = options.includeMetadata ?? true;
213
214
  const caseSensitive = options.caseSensitive ?? true;
214
215
  const respectGitignore = options.respectGitignore ?? true;
215
216
  try {
216
217
  // Handle special characters - escape pattern if it matches an existing file
217
- const escapedPattern = await escapeIfExactMatch(pattern, cwd);
218
+ const escapedPattern = await escapeIfExactMatch(pattern, normalizedCwd);
218
219
  // Execute glob with case sensitivity option
219
220
  const files = await glob(escapedPattern, {
220
221
  absolute: true,
221
- cwd,
222
+ cwd: normalizedCwd,
222
223
  follow: false, // Don't follow symlinks
223
224
  nocase: !caseSensitive, // Case insensitive if caseSensitive is false
224
225
  nodir: true, // Only files
@@ -226,7 +227,7 @@ export class FileSystemService {
226
227
  // Initialize gitignore filter if requested
227
228
  let gitignoreFilter = null;
228
229
  if (respectGitignore) {
229
- gitignoreFilter = await createGitignoreFilter(cwd);
230
+ gitignoreFilter = await createGitignoreFilter(normalizedCwd);
230
231
  }
231
232
  // Validate paths and apply gitignore filtering
232
233
  const validPaths = [];
@@ -240,7 +241,7 @@ export class FileSystemService {
240
241
  }
241
242
  // Apply gitignore filter if enabled
242
243
  if (gitignoreFilter) {
243
- const relativePath = path.relative(cwd, validation.normalizedPath);
244
+ const relativePath = path.relative(normalizedCwd, validation.normalizedPath);
244
245
  if (gitignoreFilter.isIgnored(relativePath)) {
245
246
  ignoredCount++;
246
247
  continue;
@@ -250,7 +251,7 @@ export class FileSystemService {
250
251
  }
251
252
  const totalFound = validPaths.length;
252
253
  // Collect metadata for all valid paths
253
- const filesWithMetadata = await collectFileMetadata(validPaths, cwd);
254
+ const filesWithMetadata = await collectFileMetadata(validPaths, normalizedCwd);
254
255
  // Sort files: recent files first (within 24h), then alphabetical
255
256
  const sortedFiles = sortFilesByRecency(filesWithMetadata);
256
257
  // Apply maxResults limit after sorting
@@ -54,4 +54,5 @@ export declare class FolderPackService implements IFolderPackService {
54
54
  * Parse an Office document using the document parser.
55
55
  */
56
56
  private parseOfficeDocument;
57
+ private toRelativePackPath;
57
58
  }
@@ -109,9 +109,10 @@ export class FolderPackService {
109
109
  const startTime = Date.now();
110
110
  const mergedConfig = this.mergeConfig(config);
111
111
  // Resolve to absolute path
112
- const absolutePath = path.isAbsolute(folderPath)
112
+ const rawAbsolutePath = path.isAbsolute(folderPath)
113
113
  ? folderPath
114
114
  : path.resolve(process.cwd(), folderPath);
115
+ const absolutePath = await fs.realpath(rawAbsolutePath).catch(() => rawAbsolutePath);
115
116
  // Phase 1: Search for files
116
117
  onProgress?.({ current: 0, message: 'Searching for files...', phase: 'searching' });
117
118
  const ignorePatterns = [...getDefaultIgnorePatterns(), ...mergedConfig.ignore];
@@ -130,8 +131,18 @@ export class FolderPackService {
130
131
  }
131
132
  throw error;
132
133
  }
133
- // Filter files based on ignore patterns
134
- const filteredFiles = globResult.files.filter((file) => !this.matchesIgnorePattern(file.path, ignorePatterns));
134
+ const discoveredFiles = globResult.files
135
+ .map((file) => {
136
+ const absoluteFilePath = path.isAbsolute(file.path) ? file.path : path.resolve(absolutePath, file.path);
137
+ return {
138
+ absolutePath: absoluteFilePath,
139
+ relativePath: this.toRelativePackPath(absolutePath, absoluteFilePath),
140
+ size: file.size,
141
+ };
142
+ })
143
+ .filter((file) => file.relativePath.length > 0 && !file.relativePath.startsWith('../'));
144
+ // Filter files based on ignore patterns using paths relative to the packed root.
145
+ const filteredFiles = discoveredFiles.filter((file) => !this.matchesIgnorePattern(file.relativePath, ignorePatterns));
135
146
  onProgress?.({
136
147
  current: filteredFiles.length,
137
148
  message: `Found ${filteredFiles.length} files`,
@@ -152,7 +163,7 @@ export class FolderPackService {
152
163
  // Report progress (note: may be out of order due to parallelism)
153
164
  onProgress?.({
154
165
  current: index + 1,
155
- message: `Reading ${fileInfo.path}`,
166
+ message: `Reading ${fileInfo.relativePath}`,
156
167
  phase: 'collecting',
157
168
  total: totalFiles,
158
169
  });
@@ -162,19 +173,19 @@ export class FolderPackService {
162
173
  return {
163
174
  skipped: {
164
175
  message: `File size ${fileInfo.size} exceeds limit ${mergedConfig.maxFileSize}`,
165
- path: fileInfo.path,
176
+ path: fileInfo.relativePath,
166
177
  reason: 'size-limit',
167
178
  },
168
179
  type: 'skipped',
169
180
  };
170
181
  }
171
182
  // Check if this is an Office document that should be parsed
172
- if (mergedConfig.extractDocuments && this.documentParser && isOfficeFile(fileInfo.path)) {
173
- return this.parseOfficeDocument(fileInfo.path, fileInfo.size);
183
+ if (mergedConfig.extractDocuments && this.documentParser && isOfficeFile(fileInfo.absolutePath)) {
184
+ return this.parseOfficeDocument(fileInfo.absolutePath, fileInfo.relativePath, fileInfo.size);
174
185
  }
175
186
  // Read file content using FileSystemService
176
187
  // This handles binary detection, PDF extraction, encoding, etc.
177
- const fileContent = await this.fileSystemService.readFile(fileInfo.path, {
188
+ const fileContent = await this.fileSystemService.readFile(fileInfo.absolutePath, {
178
189
  limit: mergedConfig.maxLinesPerFile,
179
190
  });
180
191
  // Check if it's a PDF with extracted text
@@ -182,9 +193,9 @@ export class FolderPackService {
182
193
  return {
183
194
  file: {
184
195
  content: fileContent.content,
185
- fileType: isPdf ? 'pdf' : this.detectFileType(fileInfo.path),
196
+ fileType: isPdf ? 'pdf' : this.detectFileType(fileInfo.relativePath),
186
197
  lineCount: fileContent.lines,
187
- path: fileInfo.path,
198
+ path: fileInfo.relativePath,
188
199
  size: fileInfo.size,
189
200
  truncated: fileContent.truncated,
190
201
  },
@@ -197,7 +208,7 @@ export class FolderPackService {
197
208
  return {
198
209
  skipped: {
199
210
  message: error instanceof Error ? error.message : String(error),
200
- path: fileInfo.path,
211
+ path: fileInfo.relativePath,
201
212
  reason: skipReason,
202
213
  },
203
214
  type: 'skipped',
@@ -329,12 +340,12 @@ export class FolderPackService {
329
340
  /**
330
341
  * Parse an Office document using the document parser.
331
342
  */
332
- async parseOfficeDocument(filePath, size) {
343
+ async parseOfficeDocument(filePath, outputPath, size) {
333
344
  if (!this.documentParser) {
334
345
  return {
335
346
  skipped: {
336
347
  message: 'Document parser not available',
337
- path: filePath,
348
+ path: outputPath,
338
349
  reason: 'read-error',
339
350
  },
340
351
  type: 'skipped',
@@ -351,7 +362,7 @@ export class FolderPackService {
351
362
  content: result.content,
352
363
  fileType: 'document',
353
364
  lineCount: lines.length,
354
- path: filePath,
365
+ path: outputPath,
355
366
  size,
356
367
  truncated: false,
357
368
  },
@@ -362,11 +373,14 @@ export class FolderPackService {
362
373
  return {
363
374
  skipped: {
364
375
  message: error instanceof Error ? error.message : String(error),
365
- path: filePath,
376
+ path: outputPath,
366
377
  reason: 'read-error',
367
378
  },
368
379
  type: 'skipped',
369
380
  };
370
381
  }
371
382
  }
383
+ toRelativePackPath(rootPath, filePath) {
384
+ return path.relative(rootPath, filePath).replaceAll('\\', '/');
385
+ }
372
386
  }
@@ -31,6 +31,18 @@ export function createChatGptOAuthFetch() {
31
31
  catch {
32
32
  return globalThis.fetch(input, init);
33
33
  }
34
+ // The AI SDK sends systemPrompt as input[0] with role "system" or "developer".
35
+ // The ChatGPT OAuth Responses endpoint expects it in top-level `instructions`.
36
+ if (!body.instructions && Array.isArray(body.input) && body.input.length > 0) {
37
+ const first = body.input[0];
38
+ if (typeof first === 'object' && first !== null) {
39
+ const record = first;
40
+ if ((record.role === 'system' || record.role === 'developer') && typeof record.content === 'string') {
41
+ body.instructions = record.content;
42
+ body.input.splice(0, 1);
43
+ }
44
+ }
45
+ }
34
46
  if (!body.instructions) {
35
47
  body.instructions = '';
36
48
  }
@@ -0,0 +1,7 @@
1
+ import type { GenerateContentRequest, IContentGenerator } from '../../core/interfaces/i-content-generator.js';
2
+ /**
3
+ * Consume generateContentStream() and return accumulated text.
4
+ * Used instead of generateContent() because the ChatGPT OAuth Codex
5
+ * endpoint requires stream: true in all requests.
6
+ */
7
+ export declare function streamToText(generator: IContentGenerator, request: GenerateContentRequest): Promise<string>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Consume generateContentStream() and return accumulated text.
3
+ * Used instead of generateContent() because the ChatGPT OAuth Codex
4
+ * endpoint requires stream: true in all requests.
5
+ */
6
+ export async function streamToText(generator, request) {
7
+ const chunks = [];
8
+ for await (const chunk of generator.generateContentStream(request)) {
9
+ if (chunk.content) {
10
+ chunks.push(chunk.content);
11
+ }
12
+ }
13
+ return chunks.join('');
14
+ }
@@ -0,0 +1,22 @@
1
+ import type { IContentGenerator } from '../../core/interfaces/i-content-generator.js';
2
+ /**
3
+ * Result from abstract generation.
4
+ */
5
+ export interface AbstractGenerateResult {
6
+ /** L0: one-line summary (~80 tokens) */
7
+ abstractContent: string;
8
+ /** L1: key points + structure (~1500 tokens) */
9
+ overviewContent: string;
10
+ }
11
+ /**
12
+ * Generate L0 abstract and L1 overview for a knowledge file.
13
+ *
14
+ * Makes two parallel LLM calls at temperature=0:
15
+ * 1. L0 .abstract.md — one-line summary (~80 tokens)
16
+ * 2. L1 .overview.md — key points + structure (~1500 tokens)
17
+ *
18
+ * @param fullContent - Full markdown content of the knowledge file
19
+ * @param generator - LLM content generator
20
+ * @returns Abstract and overview content strings
21
+ */
22
+ export declare function generateFileAbstracts(fullContent: string, generator: IContentGenerator): Promise<AbstractGenerateResult>;
@@ -0,0 +1,67 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { streamToText } from '../llm/stream-to-text.js';
3
+ const ABSTRACT_SYSTEM_PROMPT = `You are a technical documentation assistant.
4
+ Your job is to produce precise, factual summaries of knowledge documents.
5
+ Output only the requested content — no preamble, no commentary.`;
6
+ const OVERVIEW_SYSTEM_PROMPT = `You are a technical documentation assistant.
7
+ Your job is to produce structured overviews of knowledge documents.
8
+ Preserve factual accuracy, surface important entities and decisions, and format the result in concise markdown.`;
9
+ function buildAbstractPrompt(content) {
10
+ return `Produce a ONE-LINE summary (max 80 tokens) of the following knowledge document.
11
+ The line must be a complete sentence that captures the core topic and key insight.
12
+ Output only the single line — nothing else.
13
+
14
+ <document>
15
+ ${content}
16
+ </document>`;
17
+ }
18
+ function buildOverviewPrompt(content) {
19
+ return `Produce a structured overview of the following knowledge document.
20
+ Include:
21
+ - Key points (3-7 bullet points)
22
+ - Structure / sections summary
23
+ - Any notable entities, patterns, or decisions mentioned
24
+
25
+ Keep it under 1500 tokens. Use markdown formatting.
26
+ Output only the overview — no preamble.
27
+
28
+ <document>
29
+ ${content}
30
+ </document>`;
31
+ }
32
+ /** Truncate content before embedding in LLM prompts to avoid exceeding model context windows during bulk ingest. */
33
+ const MAX_ABSTRACT_CONTENT_CHARS = 20_000;
34
+ /**
35
+ * Generate L0 abstract and L1 overview for a knowledge file.
36
+ *
37
+ * Makes two parallel LLM calls at temperature=0:
38
+ * 1. L0 .abstract.md — one-line summary (~80 tokens)
39
+ * 2. L1 .overview.md — key points + structure (~1500 tokens)
40
+ *
41
+ * @param fullContent - Full markdown content of the knowledge file
42
+ * @param generator - LLM content generator
43
+ * @returns Abstract and overview content strings
44
+ */
45
+ export async function generateFileAbstracts(fullContent, generator) {
46
+ const truncated = fullContent.slice(0, MAX_ABSTRACT_CONTENT_CHARS);
47
+ const [abstractText, overviewText] = await Promise.all([
48
+ streamToText(generator, {
49
+ config: { maxTokens: 150, temperature: 0 },
50
+ contents: [{ content: buildAbstractPrompt(truncated), role: 'user' }],
51
+ model: 'default',
52
+ systemPrompt: ABSTRACT_SYSTEM_PROMPT,
53
+ taskId: randomUUID(),
54
+ }),
55
+ streamToText(generator, {
56
+ config: { maxTokens: 2000, temperature: 0 },
57
+ contents: [{ content: buildOverviewPrompt(truncated), role: 'user' }],
58
+ model: 'default',
59
+ systemPrompt: OVERVIEW_SYSTEM_PROMPT,
60
+ taskId: randomUUID(),
61
+ }),
62
+ ]);
63
+ return {
64
+ abstractContent: abstractText.trim(),
65
+ overviewContent: overviewText.trim(),
66
+ };
67
+ }
@@ -0,0 +1,67 @@
1
+ import type { IContentGenerator } from '../../core/interfaces/i-content-generator.js';
2
+ /**
3
+ * Observable status of the abstract generation queue.
4
+ */
5
+ export interface AbstractQueueStatus {
6
+ failed: number;
7
+ pending: number;
8
+ processed: number;
9
+ processing: boolean;
10
+ }
11
+ /**
12
+ * Background queue for generating L0/L1 abstract files (.abstract.md, .overview.md).
13
+ *
14
+ * - Generator is injected lazily via setGenerator() (mirrors rebindMapTools pattern)
15
+ * - Items arriving before setGenerator() are buffered and processed once generator is set
16
+ * - Writes status to <projectRoot>/.brv/_queue_status.json after each state transition
17
+ * - Retries up to maxAttempts with exponential backoff (500ms base)
18
+ * - drain() waits for all pending/processing items to complete (for graceful shutdown)
19
+ */
20
+ export declare class AbstractGenerationQueue {
21
+ private readonly projectRoot;
22
+ private readonly maxAttempts;
23
+ private drainResolvers;
24
+ private failed;
25
+ private generator;
26
+ private onBeforeProcess?;
27
+ private pending;
28
+ private processed;
29
+ private processing;
30
+ /** Number of items currently in retry backoff (removed from pending but not yet re-enqueued). */
31
+ private retrying;
32
+ private statusDirCreated;
33
+ private statusWriteFailed;
34
+ private statusWritePromise;
35
+ constructor(projectRoot: string, maxAttempts?: number);
36
+ /**
37
+ * Wait for all pending items to finish processing (graceful shutdown).
38
+ * Includes items currently in retry backoff so drain() does not resolve prematurely.
39
+ */
40
+ drain(): Promise<void>;
41
+ /**
42
+ * Add a file to the abstract generation queue.
43
+ */
44
+ enqueue(item: {
45
+ contextPath: string;
46
+ fullContent: string;
47
+ }): void;
48
+ /**
49
+ * Return current queue status snapshot.
50
+ */
51
+ getStatus(): AbstractQueueStatus;
52
+ /**
53
+ * Set a callback that runs before each item is processed.
54
+ * Used to refresh OAuth tokens before LLM calls.
55
+ */
56
+ setBeforeProcess(fn: () => Promise<void>): void;
57
+ /**
58
+ * Inject the LLM generator. Triggers processing of any buffered items.
59
+ */
60
+ setGenerator(generator: IContentGenerator): void;
61
+ private isIdle;
62
+ private processNext;
63
+ private queueStatusWrite;
64
+ private resolveDrainersIfIdle;
65
+ private scheduleNext;
66
+ private writeStatusFile;
67
+ }