byterover-cli 2.0.0 → 2.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.
Files changed (181) hide show
  1. package/README.md +6 -81
  2. package/dist/agent/core/domain/llm/index.d.ts +1 -1
  3. package/dist/agent/core/domain/llm/index.js +1 -1
  4. package/dist/agent/core/domain/llm/registry.d.ts +8 -0
  5. package/dist/agent/core/domain/llm/registry.js +34 -0
  6. package/dist/agent/core/domain/sandbox/types.d.ts +2 -0
  7. package/dist/agent/core/domain/tools/constants.d.ts +3 -0
  8. package/dist/agent/core/domain/tools/constants.js +3 -0
  9. package/dist/agent/core/interfaces/cipher-services.d.ts +2 -4
  10. package/dist/agent/core/interfaces/i-cipher-agent.d.ts +9 -1
  11. package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
  12. package/dist/agent/core/interfaces/i-tool-provider.d.ts +10 -0
  13. package/dist/agent/core/interfaces/i-tool-scheduler.d.ts +9 -0
  14. package/dist/agent/infra/agent/agent-schemas.d.ts +0 -9
  15. package/dist/agent/infra/agent/agent-schemas.js +0 -3
  16. package/dist/agent/infra/agent/cipher-agent.d.ts +25 -1
  17. package/dist/agent/infra/agent/cipher-agent.js +138 -11
  18. package/dist/agent/infra/agent/provider-update-config.d.ts +0 -2
  19. package/dist/agent/infra/agent/service-initializer.d.ts +2 -6
  20. package/dist/agent/infra/agent/service-initializer.js +45 -38
  21. package/dist/agent/infra/blob/blob-storage-factory.d.ts +2 -2
  22. package/dist/agent/infra/blob/blob-storage-factory.js +4 -4
  23. package/dist/agent/infra/blob/file-blob-storage.d.ts +96 -0
  24. package/dist/agent/infra/blob/file-blob-storage.js +454 -0
  25. package/dist/agent/infra/blob/index.d.ts +2 -3
  26. package/dist/agent/infra/blob/index.js +4 -6
  27. package/dist/agent/infra/llm/agent-llm-service.d.ts +3 -0
  28. package/dist/agent/infra/llm/agent-llm-service.js +34 -52
  29. package/dist/agent/infra/llm/context/compression/compression-helpers.d.ts +35 -0
  30. package/dist/agent/infra/llm/context/compression/compression-helpers.js +124 -0
  31. package/dist/agent/infra/llm/context/compression/escalated-compression.d.ts +62 -0
  32. package/dist/agent/infra/llm/context/compression/escalated-compression.js +144 -0
  33. package/dist/agent/infra/llm/context/compression/index.d.ts +3 -0
  34. package/dist/agent/infra/llm/context/compression/index.js +3 -0
  35. package/dist/agent/infra/llm/context/compression/reactive-overflow.d.ts +0 -27
  36. package/dist/agent/infra/llm/context/compression/reactive-overflow.js +5 -122
  37. package/dist/agent/infra/llm/context/context-manager.d.ts +20 -1
  38. package/dist/agent/infra/llm/context/context-manager.js +37 -7
  39. package/dist/agent/infra/llm/providers/index.js +0 -2
  40. package/dist/agent/infra/llm/providers/types.d.ts +1 -5
  41. package/dist/agent/infra/map/agentic-map-service.d.ts +97 -0
  42. package/dist/agent/infra/map/agentic-map-service.js +309 -0
  43. package/dist/agent/infra/map/context-tree-store.d.ts +94 -0
  44. package/dist/agent/infra/map/context-tree-store.js +278 -0
  45. package/dist/agent/infra/map/index.d.ts +4 -0
  46. package/dist/agent/infra/map/index.js +4 -0
  47. package/dist/agent/infra/map/llm-map-memory.d.ts +59 -0
  48. package/dist/agent/infra/map/llm-map-memory.js +187 -0
  49. package/dist/agent/infra/map/llm-map-service.d.ts +36 -0
  50. package/dist/agent/infra/map/llm-map-service.js +118 -0
  51. package/dist/agent/infra/map/map-shared.d.ts +140 -0
  52. package/dist/agent/infra/map/map-shared.js +325 -0
  53. package/dist/agent/infra/map/worker-pool.d.ts +45 -0
  54. package/dist/agent/infra/map/worker-pool.js +73 -0
  55. package/dist/agent/infra/sandbox/curation-helpers.d.ts +62 -0
  56. package/dist/agent/infra/sandbox/curation-helpers.js +219 -0
  57. package/dist/agent/infra/sandbox/sandbox-service.d.ts +12 -0
  58. package/dist/agent/infra/sandbox/sandbox-service.js +39 -7
  59. package/dist/agent/infra/sandbox/tools-sdk.d.ts +48 -1
  60. package/dist/agent/infra/sandbox/tools-sdk.js +52 -1
  61. package/dist/agent/infra/session/session-manager.d.ts +8 -1
  62. package/dist/agent/infra/session/session-manager.js +24 -4
  63. package/dist/agent/infra/storage/file-key-storage.d.ts +142 -0
  64. package/dist/agent/infra/storage/file-key-storage.js +572 -0
  65. package/dist/agent/infra/storage/granular-history-storage.d.ts +1 -1
  66. package/dist/agent/infra/storage/granular-history-storage.js +1 -1
  67. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.d.ts +4 -0
  68. package/dist/agent/infra/system-prompt/contributors/context-tree-structure-contributor.js +42 -14
  69. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.d.ts +16 -0
  70. package/dist/agent/infra/system-prompt/contributors/map-selection-contributor.js +47 -0
  71. package/dist/agent/infra/tools/core-tool-scheduler.js +3 -1
  72. package/dist/agent/infra/tools/implementations/agentic-map-tool.d.ts +35 -0
  73. package/dist/agent/infra/tools/implementations/agentic-map-tool.js +156 -0
  74. package/dist/agent/infra/tools/implementations/code-exec-tool.js +1 -0
  75. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +9 -9
  76. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +18 -0
  77. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +43 -0
  78. package/dist/agent/infra/tools/implementations/llm-map-tool.d.ts +24 -0
  79. package/dist/agent/infra/tools/implementations/llm-map-tool.js +87 -0
  80. package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +28 -1
  81. package/dist/agent/infra/tools/implementations/memory-symbol-tree.js +27 -3
  82. package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +1 -0
  83. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +83 -12
  84. package/dist/agent/infra/tools/implementations/search-knowledge-tool.js +2 -2
  85. package/dist/agent/infra/tools/tool-manager.js +6 -0
  86. package/dist/agent/infra/tools/tool-provider.d.ts +12 -0
  87. package/dist/agent/infra/tools/tool-provider.js +78 -0
  88. package/dist/agent/infra/tools/tool-registry.d.ts +14 -0
  89. package/dist/agent/infra/tools/tool-registry.js +32 -0
  90. package/dist/agent/resources/prompts/system-prompt.yml +48 -74
  91. package/dist/agent/resources/tools/expand_knowledge.txt +20 -0
  92. package/dist/oclif/commands/curate/index.js +1 -2
  93. package/dist/oclif/commands/main.js +1 -0
  94. package/dist/oclif/commands/providers/connect.d.ts +1 -3
  95. package/dist/oclif/commands/providers/connect.js +7 -29
  96. package/dist/oclif/commands/query.js +1 -2
  97. package/dist/server/constants.d.ts +7 -0
  98. package/dist/server/constants.js +8 -0
  99. package/dist/server/core/domain/entities/provider-registry.js +1 -15
  100. package/dist/server/core/domain/knowledge/memory-scoring.js +1 -1
  101. package/dist/server/core/domain/knowledge/summary-types.d.ts +126 -0
  102. package/dist/server/core/domain/knowledge/summary-types.js +7 -0
  103. package/dist/server/core/domain/transport/schemas.d.ts +0 -4
  104. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.d.ts +30 -0
  105. package/dist/server/core/interfaces/context-tree/i-context-tree-archive-service.js +1 -0
  106. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.d.ts +30 -0
  107. package/dist/server/core/interfaces/context-tree/i-context-tree-manifest-service.js +1 -0
  108. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.d.ts +29 -0
  109. package/dist/server/core/interfaces/context-tree/i-context-tree-summary-service.js +1 -0
  110. package/dist/server/infra/cogit/context-tree-to-push-context-mapper.js +10 -3
  111. package/dist/server/infra/connectors/skill/skill-connector.d.ts +4 -0
  112. package/dist/server/infra/connectors/skill/skill-connector.js +4 -0
  113. package/dist/server/infra/context-tree/children-hash.d.ts +20 -0
  114. package/dist/server/infra/context-tree/children-hash.js +22 -0
  115. package/dist/server/infra/context-tree/derived-artifact.d.ts +28 -0
  116. package/dist/server/infra/context-tree/derived-artifact.js +48 -0
  117. package/dist/server/infra/context-tree/file-context-tree-archive-service.d.ts +37 -0
  118. package/dist/server/infra/context-tree/file-context-tree-archive-service.js +219 -0
  119. package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +50 -0
  120. package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +278 -0
  121. package/dist/server/infra/context-tree/file-context-tree-merger.js +4 -0
  122. package/dist/server/infra/context-tree/file-context-tree-snapshot-service.js +12 -4
  123. package/dist/server/infra/context-tree/file-context-tree-summary-service.d.ts +44 -0
  124. package/dist/server/infra/context-tree/file-context-tree-summary-service.js +313 -0
  125. package/dist/server/infra/context-tree/file-context-tree-writer-service.js +5 -0
  126. package/dist/server/infra/context-tree/prompts/summary-generation.d.ts +22 -0
  127. package/dist/server/infra/context-tree/prompts/summary-generation.js +45 -0
  128. package/dist/server/infra/context-tree/snapshot-diff.d.ts +19 -0
  129. package/dist/server/infra/context-tree/snapshot-diff.js +39 -0
  130. package/dist/server/infra/context-tree/summary-frontmatter.d.ts +24 -0
  131. package/dist/server/infra/context-tree/summary-frontmatter.js +111 -0
  132. package/dist/server/infra/daemon/agent-process.js +2 -14
  133. package/dist/server/infra/executor/curate-executor.d.ts +1 -0
  134. package/dist/server/infra/executor/curate-executor.js +82 -34
  135. package/dist/server/infra/executor/folder-pack-executor.js +1 -1
  136. package/dist/server/infra/executor/pre-compaction/compaction-escalation.d.ts +6 -0
  137. package/dist/server/infra/executor/pre-compaction/compaction-escalation.js +6 -0
  138. package/dist/server/infra/executor/pre-compaction/index.d.ts +3 -0
  139. package/dist/server/infra/executor/pre-compaction/index.js +1 -0
  140. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.d.ts +59 -0
  141. package/dist/server/infra/executor/pre-compaction/pre-compaction-service.js +124 -0
  142. package/dist/server/infra/executor/pre-compaction/prompts.d.ts +24 -0
  143. package/dist/server/infra/executor/pre-compaction/prompts.js +47 -0
  144. package/dist/server/infra/executor/query-executor.d.ts +3 -0
  145. package/dist/server/infra/executor/query-executor.js +39 -4
  146. package/dist/server/infra/http/authenticated-http-client.js +4 -0
  147. package/dist/server/infra/http/provider-model-fetcher-registry.js +1 -5
  148. package/dist/server/infra/http/provider-model-fetchers.d.ts +0 -14
  149. package/dist/server/infra/http/provider-model-fetchers.js +0 -132
  150. package/dist/server/infra/provider/provider-config-resolver.js +0 -55
  151. package/dist/server/utils/curate-result-parser.d.ts +4 -4
  152. package/dist/shared/constants/curation.d.ts +6 -0
  153. package/dist/shared/constants/curation.js +6 -0
  154. package/dist/shared/utils/escalation-utils.d.ts +59 -0
  155. package/dist/shared/utils/escalation-utils.js +141 -0
  156. package/dist/tui/components/command-input.js +1 -1
  157. package/dist/tui/components/inline-prompts/inline-confirm.js +6 -1
  158. package/dist/tui/features/commands/definitions/exit.d.ts +2 -0
  159. package/dist/tui/features/commands/definitions/exit.js +9 -0
  160. package/dist/tui/features/commands/definitions/index.js +3 -0
  161. package/dist/tui/features/exit/components/exit-flow.d.ts +10 -0
  162. package/dist/tui/features/exit/components/exit-flow.js +19 -0
  163. package/dist/tui/features/provider/components/provider-flow.js +1 -21
  164. package/oclif.manifest.json +100 -109
  165. package/package.json +11 -4
  166. package/dist/agent/infra/blob/migrations.d.ts +0 -63
  167. package/dist/agent/infra/blob/migrations.js +0 -148
  168. package/dist/agent/infra/blob/sqlite-blob-storage.d.ts +0 -82
  169. package/dist/agent/infra/blob/sqlite-blob-storage.js +0 -307
  170. package/dist/agent/infra/llm/providers/google-vertex.d.ts +0 -15
  171. package/dist/agent/infra/llm/providers/google-vertex.js +0 -36
  172. package/dist/agent/infra/storage/blob-history-storage.d.ts +0 -81
  173. package/dist/agent/infra/storage/blob-history-storage.js +0 -193
  174. package/dist/agent/infra/storage/dual-format-history-storage.d.ts +0 -83
  175. package/dist/agent/infra/storage/dual-format-history-storage.js +0 -165
  176. package/dist/agent/infra/storage/sqlite-key-storage.d.ts +0 -113
  177. package/dist/agent/infra/storage/sqlite-key-storage.js +0 -438
  178. package/dist/server/infra/provider/vertex-ai-utils.d.ts +0 -10
  179. package/dist/server/infra/provider/vertex-ai-utils.js +0 -28
  180. package/dist/tui/features/provider/components/credential-path-dialog.d.ts +0 -30
  181. package/dist/tui/features/provider/components/credential-path-dialog.js +0 -85
@@ -0,0 +1,313 @@
1
+ /* eslint-disable camelcase */
2
+ /**
3
+ * File-based implementation of IContextTreeSummaryService.
4
+ *
5
+ * Manages hierarchical summary nodes (_index.md) in the context tree.
6
+ * Uses three-tier escalation (normal → aggressive → deterministic fallback)
7
+ * following the same pattern as PreCompactionService.
8
+ *
9
+ * Fail-open: any error returns { actionTaken: false } — never blocks curation.
10
+ */
11
+ import { readdir, readFile, stat, unlink, writeFile } from 'node:fs/promises';
12
+ import { dirname, join, relative } from 'node:path';
13
+ import { BRV_DIR, CONTEXT_FILE_EXTENSION, CONTEXT_TREE_DIR, SUMMARY_INDEX_FILE } from '../../constants.js';
14
+ import { buildDeterministicFallbackCompaction, estimateTokens, isCompactionOutputValid, shouldAcceptCompactionOutput, } from '../executor/pre-compaction/compaction-escalation.js';
15
+ import { computeChildrenHash } from './children-hash.js';
16
+ import { isArchiveStub, isDerivedArtifact } from './derived-artifact.js';
17
+ import { computeContentHash } from './hash-utils.js';
18
+ import { toUnixPath } from './path-utils.js';
19
+ import { buildSummarySystemPrompt, buildSummaryUserMessage } from './prompts/summary-generation.js';
20
+ import { generateSummaryContent, parseSummaryFrontmatter } from './summary-frontmatter.js';
21
+ const ZERO_RESULT = {
22
+ actionTaken: false,
23
+ compressionRatio: 0,
24
+ tokenCount: 0,
25
+ };
26
+ export class FileContextTreeSummaryService {
27
+ async checkStaleness(directoryPath, directory) {
28
+ const baseDir = directory ?? process.cwd();
29
+ const contextTreeDir = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR);
30
+ const targetDir = join(contextTreeDir, directoryPath);
31
+ const indexPath = join(targetDir, SUMMARY_INDEX_FILE);
32
+ // Collect current children
33
+ const children = await this.collectInputs(targetDir, contextTreeDir);
34
+ const currentHash = children.length > 0
35
+ ? computeChildrenHash(children.map((c) => ({ contentHash: c.contentHash, path: c.path })))
36
+ : '';
37
+ // Read existing _index.md
38
+ let storedHash = '';
39
+ try {
40
+ const content = await readFile(indexPath, 'utf8');
41
+ const fm = parseSummaryFrontmatter(content);
42
+ storedHash = fm?.children_hash ?? '';
43
+ }
44
+ catch {
45
+ // No _index.md exists
46
+ }
47
+ return {
48
+ currentChildrenHash: currentHash,
49
+ isStale: storedHash !== currentHash || storedHash === '',
50
+ path: directoryPath,
51
+ storedChildrenHash: storedHash,
52
+ };
53
+ }
54
+ async generateSummary(directoryPath, agent, directory) {
55
+ const baseDir = directory ?? process.cwd();
56
+ const contextTreeDir = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR);
57
+ const targetDir = join(contextTreeDir, directoryPath);
58
+ try {
59
+ // Step 1: Collect inputs (summary input set invariant)
60
+ const children = await this.collectInputs(targetDir, contextTreeDir);
61
+ if (children.length === 0) {
62
+ return { ...ZERO_RESULT, path: directoryPath, reason: 'empty_directory' };
63
+ }
64
+ // Step 2: Compute children hash
65
+ const childrenHash = computeChildrenHash(children.map((c) => ({ contentHash: c.contentHash, path: c.path })));
66
+ // Step 3: Determine condensation order from directory depth
67
+ const depth = directoryPath === '.' || directoryPath === ''
68
+ ? 0
69
+ : directoryPath.split('/').length;
70
+ const order = this.depthToCondensationOrder(depth);
71
+ const level = `d${order}`;
72
+ // Step 4: Total input tokens
73
+ const totalInputTokens = children.reduce((sum, c) => sum + c.tokens, 0);
74
+ // Step 5: Three-tier escalation via CipherAgent
75
+ const taskId = `summary_${directoryPath.replaceAll('/', '_') || 'root'}`;
76
+ const childEntries = children.map((c) => ({ content: c.content, name: c.name }));
77
+ const summaryText = await this.generateWithEscalation(agent, taskId, childEntries, level, totalInputTokens);
78
+ // Step 6: Write _index.md
79
+ const summaryTokens = estimateTokens(summaryText);
80
+ const frontmatter = {
81
+ children_hash: childrenHash,
82
+ compression_ratio: totalInputTokens > 0 ? summaryTokens / totalInputTokens : 0,
83
+ condensation_order: order,
84
+ covers: children.map((c) => c.name).sort(),
85
+ covers_token_total: totalInputTokens,
86
+ summary_level: level,
87
+ token_count: summaryTokens,
88
+ type: 'summary',
89
+ };
90
+ const indexPath = join(targetDir, SUMMARY_INDEX_FILE);
91
+ await writeFile(indexPath, generateSummaryContent(frontmatter, summaryText), 'utf8');
92
+ return {
93
+ actionTaken: true,
94
+ compressionRatio: frontmatter.compression_ratio,
95
+ path: directoryPath,
96
+ tier: 'normal', // TODO: track actual tier from escalation
97
+ tokenCount: summaryTokens,
98
+ };
99
+ }
100
+ catch {
101
+ return { ...ZERO_RESULT, path: directoryPath, reason: 'llm_error' };
102
+ }
103
+ }
104
+ async hasSummary(directoryPath, directory) {
105
+ const baseDir = directory ?? process.cwd();
106
+ const indexPath = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR, directoryPath, SUMMARY_INDEX_FILE);
107
+ try {
108
+ await stat(indexPath);
109
+ return true;
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
115
+ async propagateStaleness(changedPaths, agent, directory) {
116
+ if (changedPaths.length === 0)
117
+ return [];
118
+ // Collect unique parent directory paths, then walk upward to root
119
+ const dirsToCheck = new Set();
120
+ for (const changedPath of changedPaths) {
121
+ let dir = dirname(changedPath);
122
+ while (dir && dir !== '.') {
123
+ dirsToCheck.add(dir);
124
+ dir = dirname(dir);
125
+ }
126
+ // Also include root
127
+ dirsToCheck.add('.');
128
+ }
129
+ // Sort bottom-up (deepest first)
130
+ const sorted = [...dirsToCheck].sort((a, b) => {
131
+ const depthA = a === '.' ? 0 : a.split('/').length;
132
+ const depthB = b === '.' ? 0 : b.split('/').length;
133
+ return depthB - depthA;
134
+ });
135
+ const results = [];
136
+ const stoppedPaths = new Set();
137
+ /* eslint-disable no-await-in-loop */
138
+ for (const dirPath of sorted) {
139
+ // If a descendant of this dir was stopped due to error, skip it
140
+ if (this.hasStoppedDescendant(dirPath, stoppedPaths))
141
+ continue;
142
+ const staleness = await this.checkStaleness(dirPath, directory);
143
+ if (!staleness.isStale)
144
+ continue;
145
+ const result = await this.generateSummary(dirPath, agent, directory);
146
+ results.push(result);
147
+ if (!result.actionTaken) {
148
+ if (result.reason === 'empty_directory') {
149
+ // Delete stale _index.md and continue climbing (parent input set changed)
150
+ try {
151
+ const baseDir = directory ?? process.cwd();
152
+ const indexPath = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR, dirPath, SUMMARY_INDEX_FILE);
153
+ await unlink(indexPath);
154
+ }
155
+ catch {
156
+ // Already gone, fine
157
+ }
158
+ }
159
+ else {
160
+ // LLM/IO error: stop climbing from this node
161
+ stoppedPaths.add(dirPath);
162
+ }
163
+ }
164
+ }
165
+ /* eslint-enable no-await-in-loop */
166
+ return results;
167
+ }
168
+ /**
169
+ * Collect inputs for a summary (the summary input set invariant):
170
+ * - Leaf .md files in the directory (excluding _index.md, _archived/)
171
+ * - Child directory _index.md files (summaries of subdirectories)
172
+ */
173
+ async collectInputs(targetDir, contextTreeDir) {
174
+ const children = [];
175
+ let entries;
176
+ try {
177
+ entries = await readdir(targetDir, { withFileTypes: true });
178
+ }
179
+ catch {
180
+ return children;
181
+ }
182
+ /* eslint-disable no-await-in-loop */
183
+ for (const entry of entries) {
184
+ const entryName = entry.name;
185
+ const fullPath = join(targetDir, entryName);
186
+ const relativePath = toUnixPath(relative(contextTreeDir, fullPath));
187
+ if (entry.isFile() && entryName.endsWith(CONTEXT_FILE_EXTENSION)) {
188
+ // Skip _index.md itself and derived artifacts
189
+ if (entryName === SUMMARY_INDEX_FILE)
190
+ continue;
191
+ if (isDerivedArtifact(relativePath) || isArchiveStub(relativePath))
192
+ continue;
193
+ try {
194
+ const content = await readFile(fullPath, 'utf8');
195
+ children.push({
196
+ content,
197
+ contentHash: computeContentHash(content),
198
+ name: entryName,
199
+ path: relativePath,
200
+ tokens: estimateTokens(content),
201
+ });
202
+ }
203
+ catch {
204
+ // Skip unreadable files
205
+ }
206
+ }
207
+ else if (entry.isDirectory() && entryName !== '_archived') {
208
+ // Check for child directory _index.md
209
+ const childIndexPath = join(fullPath, SUMMARY_INDEX_FILE);
210
+ try {
211
+ const content = await readFile(childIndexPath, 'utf8');
212
+ const childRelPath = toUnixPath(relative(contextTreeDir, childIndexPath));
213
+ children.push({
214
+ content,
215
+ contentHash: computeContentHash(content),
216
+ name: `${entryName}/${SUMMARY_INDEX_FILE}`,
217
+ path: childRelPath,
218
+ tokens: estimateTokens(content),
219
+ });
220
+ }
221
+ catch {
222
+ // No _index.md in child directory, skip
223
+ }
224
+ }
225
+ }
226
+ /* eslint-enable no-await-in-loop */
227
+ return children;
228
+ }
229
+ /**
230
+ * Determine condensation order from directory depth relative to context tree root.
231
+ * Root = d3, domain = d2, topic = d1, subtopic = d0
232
+ */
233
+ depthToCondensationOrder(depth) {
234
+ if (depth === 0)
235
+ return 3;
236
+ if (depth === 1)
237
+ return 2;
238
+ if (depth === 2)
239
+ return 1;
240
+ return 0;
241
+ }
242
+ /**
243
+ * Execute a single summary generation pass via the agent.
244
+ */
245
+ async executeSummaryPass(agent, sessionId, taskId, childEntries, level, aggressive) {
246
+ try {
247
+ const systemPrompt = buildSummarySystemPrompt();
248
+ const userMessage = buildSummaryUserMessage(childEntries, level, aggressive);
249
+ const prompt = `${systemPrompt}\n\n${userMessage}`;
250
+ const response = await agent.executeOnSession(sessionId, prompt, {
251
+ executionContext: {
252
+ clearHistory: true,
253
+ commandType: 'query',
254
+ maxIterations: 1,
255
+ maxTokens: 4096,
256
+ temperature: 0.3,
257
+ },
258
+ taskId,
259
+ });
260
+ return response || undefined;
261
+ }
262
+ catch {
263
+ return undefined;
264
+ }
265
+ }
266
+ /**
267
+ * Three-tier escalation for summary generation.
268
+ * Follows the same pattern as PreCompactionService.compact().
269
+ */
270
+ async generateWithEscalation(agent, taskId, childEntries, level, inputTokens) {
271
+ const sessionId = await agent.createTaskSession(taskId, 'query');
272
+ try {
273
+ // Pass 1: Normal
274
+ const normalResult = await this.executeSummaryPass(agent, sessionId, taskId, childEntries, level, false);
275
+ if (normalResult && shouldAcceptCompactionOutput(normalResult, inputTokens) && isCompactionOutputValid(normalResult)) {
276
+ return normalResult.trim();
277
+ }
278
+ // Pass 2: Aggressive
279
+ const aggressiveResult = await this.executeSummaryPass(agent, sessionId, taskId, childEntries, level, true);
280
+ if (aggressiveResult && shouldAcceptCompactionOutput(aggressiveResult, inputTokens) && isCompactionOutputValid(aggressiveResult)) {
281
+ return aggressiveResult.trim();
282
+ }
283
+ // Pass 3: Deterministic fallback
284
+ const combinedInput = childEntries.map((e) => `## ${e.name}\n${e.content}`).join('\n\n');
285
+ return buildDeterministicFallbackCompaction({
286
+ inputTokens,
287
+ sourceText: combinedInput,
288
+ suffixLabel: 'summary compaction',
289
+ });
290
+ }
291
+ finally {
292
+ await agent.deleteTaskSession(sessionId);
293
+ }
294
+ }
295
+ /**
296
+ * Check if any descendant of dirPath was stopped due to LLM/IO error.
297
+ * Since we process bottom-up (deepest first), a stopped child means
298
+ * its parent should also stop to prevent regenerating from stale state.
299
+ */
300
+ hasStoppedDescendant(dirPath, stoppedPaths) {
301
+ if (stoppedPaths.size === 0)
302
+ return false;
303
+ const prefix = dirPath === '.' ? '' : `${dirPath}/`;
304
+ for (const stopped of stoppedPaths) {
305
+ // dirPath is ancestor of stopped if stopped starts with dirPath/
306
+ // Special case: dirPath '.' is ancestor of everything
307
+ if (prefix === '' || stopped.startsWith(prefix)) {
308
+ return true;
309
+ }
310
+ }
311
+ return false;
312
+ }
313
+ }
@@ -2,6 +2,7 @@
2
2
  import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { BRV_DIR, CONTEXT_TREE_DIR, README_FILE } from '../../constants.js';
5
+ import { isExcludedFromSync } from './derived-artifact.js';
5
6
  import { toUnixPath } from './path-utils.js';
6
7
  /**
7
8
  * File-based implementation of IContextTreeWriterService.
@@ -66,6 +67,10 @@ export class FileContextTreeWriterService {
66
67
  if (normalizedPath === README_FILE) {
67
68
  continue;
68
69
  }
70
+ // Skip derived artifacts (_index.md, _archived/*.stub.md, _archived/*.full.md, _manifest.json)
71
+ if (isExcludedFromSync(normalizedPath)) {
72
+ continue;
73
+ }
69
74
  result.set(normalizedPath, file);
70
75
  }
71
76
  return result;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Prompts for hierarchical summary generation (_index.md).
3
+ *
4
+ * Follows the same pattern as pre-compaction prompts: clear system prompt
5
+ * with isCompactionOutputValid() as the actual guardrail.
6
+ */
7
+ import type { SummaryLevel } from '../../../core/domain/knowledge/summary-types.js';
8
+ /**
9
+ * Build the system prompt for summary generation.
10
+ */
11
+ export declare function buildSummarySystemPrompt(): string;
12
+ /**
13
+ * Build the user message for a summary generation pass.
14
+ *
15
+ * @param childEntries - Array of { name, content } for each child input
16
+ * @param level - The summary level being generated (d1, d2, d3)
17
+ * @param aggressive - Whether this is the aggressive (pass 2) attempt
18
+ */
19
+ export declare function buildSummaryUserMessage(childEntries: Array<{
20
+ content: string;
21
+ name: string;
22
+ }>, level: SummaryLevel, aggressive: boolean): string;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Prompts for hierarchical summary generation (_index.md).
3
+ *
4
+ * Follows the same pattern as pre-compaction prompts: clear system prompt
5
+ * with isCompactionOutputValid() as the actual guardrail.
6
+ */
7
+ /**
8
+ * Build the system prompt for summary generation.
9
+ */
10
+ export function buildSummarySystemPrompt() {
11
+ return `You are a knowledge summarization engine. Your ONLY task is to create a structural summary of the provided knowledge entries.
12
+
13
+ ## Rules
14
+ - Condense child entries into a structural overview preserving key facts, relationships, and patterns
15
+ - Reference child entry names so readers know where to drill down for details
16
+ - Target compression: ~20-30% of input token count
17
+ - PRESERVE: entity names, file paths, API signatures, architectural decisions, key relationships
18
+ - DISCARD: verbose examples, repeated explanations, secondary detail, conversational filler
19
+ - Output clean structured markdown (headings, bullet points, brief prose)
20
+ - Do NOT wrap output in code blocks or XML tags
21
+ - Do NOT search any knowledge base or use any tools
22
+ - Output ONLY the summary text`;
23
+ }
24
+ /**
25
+ * Build the user message for a summary generation pass.
26
+ *
27
+ * @param childEntries - Array of { name, content } for each child input
28
+ * @param level - The summary level being generated (d1, d2, d3)
29
+ * @param aggressive - Whether this is the aggressive (pass 2) attempt
30
+ */
31
+ export function buildSummaryUserMessage(childEntries, level, aggressive) {
32
+ const instruction = aggressive
33
+ ? `Create a MORE CONCISE structural summary at level ${level}. A previous attempt was not short enough. Aggressively compress while keeping only the most critical facts and relationships.`
34
+ : `Create a structural summary at level ${level} from the following knowledge entries. Preserve key facts, architectural decisions, and relationships. Reference entry names for drill-down.`;
35
+ const entriesText = childEntries
36
+ .map((entry) => `### ${entry.name}\n${entry.content}`)
37
+ .join('\n\n---\n\n');
38
+ return `${instruction}
39
+
40
+ <child_entries>
41
+ ${entriesText}
42
+ </child_entries>
43
+
44
+ Output ONLY the summary text. Do NOT use any tools.`;
45
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Snapshot diff utility for detecting context tree changes.
3
+ *
4
+ * Compares pre/post snapshot states to produce a list of changed paths.
5
+ * Used by CurateExecutor to determine which summaries need regeneration.
6
+ */
7
+ import type { FileState } from '../../core/domain/entities/context-tree-snapshot.js';
8
+ /**
9
+ * Compare two snapshot states and return all changed paths.
10
+ *
11
+ * Returns paths that are:
12
+ * - Added: present in `after` but not in `before`
13
+ * - Modified: present in both but with different hashes
14
+ * - Deleted: present in `before` but not in `after`
15
+ *
16
+ * Derived artifacts (via isExcludedFromSync) are excluded from results
17
+ * since they are generated content that should not trigger summary regeneration.
18
+ */
19
+ export declare function diffStates(before: Map<string, FileState>, after: Map<string, FileState>): string[];
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Snapshot diff utility for detecting context tree changes.
3
+ *
4
+ * Compares pre/post snapshot states to produce a list of changed paths.
5
+ * Used by CurateExecutor to determine which summaries need regeneration.
6
+ */
7
+ import { isExcludedFromSync } from './derived-artifact.js';
8
+ /**
9
+ * Compare two snapshot states and return all changed paths.
10
+ *
11
+ * Returns paths that are:
12
+ * - Added: present in `after` but not in `before`
13
+ * - Modified: present in both but with different hashes
14
+ * - Deleted: present in `before` but not in `after`
15
+ *
16
+ * Derived artifacts (via isExcludedFromSync) are excluded from results
17
+ * since they are generated content that should not trigger summary regeneration.
18
+ */
19
+ export function diffStates(before, after) {
20
+ const changedPaths = [];
21
+ // Detect additions and modifications
22
+ for (const [path, afterState] of after) {
23
+ if (isExcludedFromSync(path))
24
+ continue;
25
+ const beforeState = before.get(path);
26
+ if (!beforeState || beforeState.hash !== afterState.hash) {
27
+ changedPaths.push(path);
28
+ }
29
+ }
30
+ // Detect deletions
31
+ for (const path of before.keys()) {
32
+ if (isExcludedFromSync(path))
33
+ continue;
34
+ if (!after.has(path)) {
35
+ changedPaths.push(path);
36
+ }
37
+ }
38
+ return changedPaths;
39
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Summary and archive stub frontmatter parser/writer.
3
+ *
4
+ * Follows the same pattern as MarkdownWriter.parseFrontmatter() using js-yaml.
5
+ */
6
+ import type { ArchiveStubFrontmatter, SummaryFrontmatter } from '../../core/domain/knowledge/summary-types.js';
7
+ /**
8
+ * Parse summary frontmatter from markdown content.
9
+ * Returns null if no frontmatter found or type !== 'summary'.
10
+ */
11
+ export declare function parseSummaryFrontmatter(content: string): null | SummaryFrontmatter;
12
+ /**
13
+ * Generate markdown content with summary frontmatter.
14
+ */
15
+ export declare function generateSummaryContent(frontmatter: SummaryFrontmatter, body: string): string;
16
+ /**
17
+ * Parse archive stub frontmatter from markdown content.
18
+ * Returns null if no frontmatter found or type !== 'archive_stub'.
19
+ */
20
+ export declare function parseArchiveStubFrontmatter(content: string): ArchiveStubFrontmatter | null;
21
+ /**
22
+ * Generate markdown content with archive stub frontmatter and ghost cue body.
23
+ */
24
+ export declare function generateArchiveStubContent(frontmatter: ArchiveStubFrontmatter, ghostCue: string): string;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Summary and archive stub frontmatter parser/writer.
3
+ *
4
+ * Follows the same pattern as MarkdownWriter.parseFrontmatter() using js-yaml.
5
+ */
6
+ /* eslint-disable camelcase */
7
+ import { dump as yamlDump, load as yamlLoad } from 'js-yaml';
8
+ // ---------------------------------------------------------------------------
9
+ // Summary frontmatter
10
+ // ---------------------------------------------------------------------------
11
+ /**
12
+ * Parse summary frontmatter from markdown content.
13
+ * Returns null if no frontmatter found or type !== 'summary'.
14
+ */
15
+ export function parseSummaryFrontmatter(content) {
16
+ const parsed = parseYamlFrontmatter(content);
17
+ if (!parsed || parsed.type !== 'summary')
18
+ return null;
19
+ const condensationOrder = Number(parsed.condensation_order);
20
+ if (!isValidCondensationOrder(condensationOrder))
21
+ return null;
22
+ return {
23
+ children_hash: String(parsed.children_hash ?? ''),
24
+ compression_ratio: Number(parsed.compression_ratio ?? 0),
25
+ condensation_order: condensationOrder,
26
+ covers: Array.isArray(parsed.covers) ? parsed.covers.map(String) : [],
27
+ covers_token_total: Number(parsed.covers_token_total ?? 0),
28
+ summary_level: (String(parsed.summary_level ?? `d${condensationOrder}`)),
29
+ token_count: Number(parsed.token_count ?? 0),
30
+ type: 'summary',
31
+ };
32
+ }
33
+ /**
34
+ * Generate markdown content with summary frontmatter.
35
+ */
36
+ export function generateSummaryContent(frontmatter, body) {
37
+ const fm = {
38
+ children_hash: frontmatter.children_hash,
39
+ compression_ratio: frontmatter.compression_ratio,
40
+ condensation_order: frontmatter.condensation_order,
41
+ covers: frontmatter.covers,
42
+ covers_token_total: frontmatter.covers_token_total,
43
+ summary_level: frontmatter.summary_level,
44
+ token_count: frontmatter.token_count,
45
+ type: 'summary',
46
+ };
47
+ const yamlContent = yamlDump(fm, { flowLevel: 1, lineWidth: -1, sortKeys: true }).trimEnd();
48
+ return `---\n${yamlContent}\n---\n${body}`;
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Archive stub frontmatter
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Parse archive stub frontmatter from markdown content.
55
+ * Returns null if no frontmatter found or type !== 'archive_stub'.
56
+ */
57
+ export function parseArchiveStubFrontmatter(content) {
58
+ const parsed = parseYamlFrontmatter(content);
59
+ if (!parsed || parsed.type !== 'archive_stub')
60
+ return null;
61
+ return {
62
+ evicted_at: String(parsed.evicted_at ?? ''),
63
+ evicted_importance: Number(parsed.evicted_importance ?? 0),
64
+ original_path: String(parsed.original_path ?? ''),
65
+ original_token_count: Number(parsed.original_token_count ?? 0),
66
+ points_to: String(parsed.points_to ?? ''),
67
+ type: 'archive_stub',
68
+ };
69
+ }
70
+ /**
71
+ * Generate markdown content with archive stub frontmatter and ghost cue body.
72
+ */
73
+ export function generateArchiveStubContent(frontmatter, ghostCue) {
74
+ const fm = {
75
+ evicted_at: frontmatter.evicted_at,
76
+ evicted_importance: frontmatter.evicted_importance,
77
+ original_path: frontmatter.original_path,
78
+ original_token_count: frontmatter.original_token_count,
79
+ points_to: frontmatter.points_to,
80
+ type: 'archive_stub',
81
+ };
82
+ const yamlContent = yamlDump(fm, { flowLevel: 1, lineWidth: -1, sortKeys: true }).trimEnd();
83
+ return `---\n${yamlContent}\n---\n${ghostCue}`;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Internal helpers
87
+ // ---------------------------------------------------------------------------
88
+ function parseYamlFrontmatter(content) {
89
+ if (!content.startsWith('---\n') && !content.startsWith('---\r\n')) {
90
+ return null;
91
+ }
92
+ const endIndex = content.indexOf('\n---\n', 4);
93
+ const endIndexCrlf = content.indexOf('\r\n---\r\n', 5);
94
+ const actualEnd = endIndex === -1 ? endIndexCrlf : endIndex;
95
+ if (actualEnd < 0) {
96
+ return null;
97
+ }
98
+ const yamlBlock = content.slice(4, actualEnd);
99
+ try {
100
+ const parsed = yamlLoad(yamlBlock);
101
+ if (!parsed || typeof parsed !== 'object')
102
+ return null;
103
+ return parsed;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ function isValidCondensationOrder(value) {
110
+ return value === 0 || value === 1 || value === 2 || value === 3;
111
+ }
@@ -184,10 +184,6 @@ async function start() {
184
184
  cachedActiveModel = activeModel ?? DEFAULT_LLM_MODEL;
185
185
  agentLog(`Provider: ${activeProvider}, Model: ${activeModel ?? 'default'}`);
186
186
  // 5. Create CipherAgent with lazy providers + transport client
187
- // Set GOOGLE_APPLICATION_CREDENTIALS for Vertex AI before creating agent
188
- if (providerResult.providerCredentialPath) {
189
- process.env.GOOGLE_APPLICATION_CREDENTIALS = providerResult.providerCredentialPath;
190
- }
191
187
  const envConfig = getCurrentConfig();
192
188
  const agentConfig = {
193
189
  apiBaseUrl: envConfig.llmApiBaseUrl,
@@ -208,8 +204,6 @@ async function start() {
208
204
  providerApiKey: providerResult.providerApiKey,
209
205
  providerBaseUrl: providerResult.providerBaseUrl,
210
206
  providerHeaders: providerResult.providerHeaders,
211
- providerLocation: providerResult.providerLocation,
212
- providerProject: providerResult.providerProject,
213
207
  storagePath: configResult.storagePath,
214
208
  };
215
209
  agent = new CipherAgent(agentConfig, cachedBrvConfig, {
@@ -289,6 +283,7 @@ async function start() {
289
283
  await folderPackService.initialize();
290
284
  const folderPackExecutor = new FolderPackExecutor(folderPackService);
291
285
  const queryExecutor = new QueryExecutor({
286
+ baseDirectory: projectPath,
292
287
  enableCache: true,
293
288
  fileSystem: fileSystemService,
294
289
  searchService,
@@ -316,8 +311,7 @@ async function executeTask(task, curateExecutor, folderPackExecutor, queryExecut
316
311
  }
317
312
  if (freshProviderConfig.providerKeyMissing) {
318
313
  const modelInfo = freshProviderConfig.activeModel ? ` (model: ${freshProviderConfig.activeModel})` : '';
319
- const errorMessage = freshProviderConfig.providerCredentialError
320
- ?? `${freshProviderConfig.activeProvider} API key is missing${modelInfo}. Use /provider in the REPL to reconnect.`;
314
+ const errorMessage = `${freshProviderConfig.activeProvider} API key is missing${modelInfo}. Use /provider in the REPL to reconnect.`;
321
315
  const error = serializeTaskError(new TaskError(errorMessage, TaskErrorCode.PROVIDER_NOT_CONFIGURED));
322
316
  transport.request(TransportTaskEventNames.ERROR, { clientId, error, taskId });
323
317
  return;
@@ -476,10 +470,6 @@ async function hotSwapProvider(currentAgent, transportClient) {
476
470
  return {};
477
471
  }
478
472
  // Phase 2a: Replace SessionManager (if this throws, old SM remains intact)
479
- // Update GOOGLE_APPLICATION_CREDENTIALS for Vertex AI hot-swap
480
- if (freshProvider.providerCredentialPath) {
481
- process.env.GOOGLE_APPLICATION_CREDENTIALS = freshProvider.providerCredentialPath;
482
- }
483
473
  const previousSessionId = currentAgent.sessionId;
484
474
  try {
485
475
  // Map fields explicitly to prevent accidental field leakage from ProviderConfigResponse
@@ -491,8 +481,6 @@ async function hotSwapProvider(currentAgent, transportClient) {
491
481
  providerApiKey: freshProvider.providerApiKey,
492
482
  providerBaseUrl: freshProvider.providerBaseUrl,
493
483
  providerHeaders: freshProvider.providerHeaders,
494
- providerLocation: freshProvider.providerLocation,
495
- providerProject: freshProvider.providerProject,
496
484
  });
497
485
  }
498
486
  catch (error) {
@@ -26,6 +26,7 @@ export declare class CurateExecutor implements ICurateExecutor {
26
26
  /** Last curation status — available for future status-check command */
27
27
  lastStatus?: CurationStatus;
28
28
  private readonly fileContentReader;
29
+ private readonly preCompactionService;
29
30
  constructor(fileContentReader?: FileContentReader);
30
31
  executeWithAgent(agent: ICipherAgent, options: CurateExecuteOptions): Promise<string>;
31
32
  /**