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,278 @@
1
+ /* eslint-disable camelcase */
2
+ /**
3
+ * File-based implementation of IContextTreeManifestService.
4
+ *
5
+ * Builds and reads the context manifest (_manifest.json) which allocates
6
+ * context tree entries into three lanes (summaries, contexts, stubs)
7
+ * with token budgets for efficient query context injection.
8
+ *
9
+ * Freshness check uses source_fingerprint (hash of sorted path:mtime:size)
10
+ * to detect additions, modifications, and deletions. Stat-only, no file reads.
11
+ *
12
+ * Tradeoff: rare false-fresh cases where content changes but mtime+size
13
+ * are preserved. Acceptable because writeFile() updates mtime and the next
14
+ * curate run will rebuild the manifest anyway.
15
+ */
16
+ import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
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';
19
+ import { parseFrontmatterScoring } from '../../core/domain/knowledge/markdown-writer.js';
20
+ import { DEFAULT_LANE_BUDGETS } from '../../core/domain/knowledge/summary-types.js';
21
+ import { estimateTokens } from '../executor/pre-compaction/compaction-escalation.js';
22
+ import { isArchiveStub, isDerivedArtifact } from './derived-artifact.js';
23
+ import { computeContentHash } from './hash-utils.js';
24
+ import { toUnixPath } from './path-utils.js';
25
+ import { parseSummaryFrontmatter } from './summary-frontmatter.js';
26
+ export class FileContextTreeManifestService {
27
+ config;
28
+ constructor(config = {}) {
29
+ this.config = config;
30
+ }
31
+ async buildManifest(directory, laneBudgets) {
32
+ const baseDir = directory ?? this.config.baseDirectory ?? process.cwd();
33
+ const contextTreeDir = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR);
34
+ const budgets = laneBudgets ?? DEFAULT_LANE_BUDGETS;
35
+ // Scan all entries
36
+ const summaries = [];
37
+ const contexts = [];
38
+ const stubs = [];
39
+ await this.scanForManifest(contextTreeDir, contextTreeDir, summaries, contexts, stubs);
40
+ // Lane allocation with prioritized fill
41
+ const activeSummaries = this.allocateLane(summaries.sort((a, b) => (b.order ?? 0) - (a.order ?? 0)), budgets.summaries);
42
+ const activeContexts = this.allocateLane(contexts.sort((a, b) => (b.importance ?? 50) - (a.importance ?? 50)), budgets.contexts);
43
+ const activeStubs = this.allocateLane(stubs, budgets.stubs);
44
+ const activeContext = [...activeSummaries, ...activeContexts, ...activeStubs];
45
+ const totalTokens = activeContext.reduce((sum, e) => sum + e.tokens, 0);
46
+ // Compute source fingerprint (stat-only, no file reads)
47
+ const sourceFingerprint = await this.computeSourceFingerprint(contextTreeDir);
48
+ const manifest = {
49
+ active_context: activeContext,
50
+ generated_at: new Date().toISOString(),
51
+ lane_tokens: {
52
+ contexts: activeContexts.reduce((sum, e) => sum + e.tokens, 0),
53
+ stubs: activeStubs.reduce((sum, e) => sum + e.tokens, 0),
54
+ summaries: activeSummaries.reduce((sum, e) => sum + e.tokens, 0),
55
+ },
56
+ source_fingerprint: sourceFingerprint,
57
+ total_tokens: totalTokens,
58
+ version: 1,
59
+ };
60
+ // Write _manifest.json
61
+ const manifestPath = join(contextTreeDir, MANIFEST_FILE);
62
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
63
+ return manifest;
64
+ }
65
+ async readManifest(directory) {
66
+ const baseDir = directory ?? this.config.baseDirectory ?? process.cwd();
67
+ const manifestPath = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR, MANIFEST_FILE);
68
+ try {
69
+ const content = await readFile(manifestPath, 'utf8');
70
+ return JSON.parse(content);
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ async readManifestIfFresh(directory) {
77
+ const manifest = await this.readManifest(directory);
78
+ if (!manifest)
79
+ return null;
80
+ const baseDir = directory ?? this.config.baseDirectory ?? process.cwd();
81
+ const contextTreeDir = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR);
82
+ // Compare stored fingerprint against current state
83
+ const currentFingerprint = await this.computeSourceFingerprint(contextTreeDir);
84
+ if (currentFingerprint !== manifest.source_fingerprint)
85
+ return null;
86
+ return manifest;
87
+ }
88
+ async resolveForInjection(manifest, _query, directory) {
89
+ const baseDir = directory ?? this.config.baseDirectory ?? process.cwd();
90
+ const contextTreeDir = join(baseDir, BRV_DIR, CONTEXT_TREE_DIR);
91
+ const resolved = [];
92
+ // Order: summaries (broadest first) → contexts → stubs
93
+ const ordered = [
94
+ ...manifest.active_context.filter((e) => e.type === 'summary'),
95
+ ...manifest.active_context.filter((e) => e.type === 'context'),
96
+ ...manifest.active_context.filter((e) => e.type === 'stub'),
97
+ ];
98
+ /* eslint-disable no-await-in-loop */
99
+ for (const entry of ordered) {
100
+ try {
101
+ const fullPath = join(contextTreeDir, entry.path);
102
+ const content = await readFile(fullPath, 'utf8');
103
+ resolved.push({
104
+ content,
105
+ path: entry.path,
106
+ tokens: entry.tokens,
107
+ type: entry.type,
108
+ });
109
+ }
110
+ catch {
111
+ // Skip unreadable files
112
+ }
113
+ }
114
+ /* eslint-enable no-await-in-loop */
115
+ return resolved;
116
+ }
117
+ /**
118
+ * Allocate entries into a lane respecting the token budget.
119
+ * Entries are already sorted by priority (caller responsibility).
120
+ */
121
+ allocateLane(entries, budget) {
122
+ const allocated = [];
123
+ let remaining = budget;
124
+ for (const entry of entries) {
125
+ if (entry.tokens <= remaining) {
126
+ allocated.push(entry);
127
+ remaining -= entry.tokens;
128
+ }
129
+ }
130
+ return allocated;
131
+ }
132
+ /**
133
+ * Compute source fingerprint from stat data (path:mtime:size).
134
+ * Stat-only — no file reads required.
135
+ */
136
+ async computeSourceFingerprint(contextTreeDir) {
137
+ const entries = [];
138
+ await this.scanSourceStats(contextTreeDir, contextTreeDir, entries);
139
+ const input = entries
140
+ .sort((a, b) => a.path.localeCompare(b.path))
141
+ .map((e) => `${e.path}:${e.mtime}:${e.size}`)
142
+ .join('\n');
143
+ return computeContentHash(input);
144
+ }
145
+ /**
146
+ * Recursively scan _archived/ directory for .stub.md files.
147
+ */
148
+ async scanArchivedStubs(currentDir, contextTreeDir, stubs) {
149
+ let entries;
150
+ try {
151
+ entries = await readdir(currentDir, { withFileTypes: true });
152
+ }
153
+ catch {
154
+ return;
155
+ }
156
+ /* eslint-disable no-await-in-loop */
157
+ for (const entry of entries) {
158
+ const entryName = entry.name;
159
+ const fullPath = join(currentDir, entryName);
160
+ if (entry.isDirectory()) {
161
+ await this.scanArchivedStubs(fullPath, contextTreeDir, stubs);
162
+ }
163
+ else if (entry.isFile() && entryName.endsWith(STUB_EXTENSION)) {
164
+ const relativePath = toUnixPath(relative(contextTreeDir, fullPath));
165
+ try {
166
+ const content = await readFile(fullPath, 'utf8');
167
+ stubs.push({
168
+ path: relativePath,
169
+ tokens: estimateTokens(content),
170
+ type: 'stub',
171
+ });
172
+ }
173
+ catch {
174
+ // Skip unreadable stubs
175
+ }
176
+ }
177
+ }
178
+ /* eslint-enable no-await-in-loop */
179
+ }
180
+ /**
181
+ * Recursively scan context tree, collecting entries for manifest building.
182
+ */
183
+ async scanForManifest(currentDir, contextTreeDir, summaries, contexts, stubs) {
184
+ let entries;
185
+ try {
186
+ entries = await readdir(currentDir, { withFileTypes: true });
187
+ }
188
+ catch {
189
+ return;
190
+ }
191
+ /* eslint-disable no-await-in-loop */
192
+ for (const entry of entries) {
193
+ const entryName = entry.name;
194
+ const fullPath = join(currentDir, entryName);
195
+ if (entry.isDirectory()) {
196
+ // Scan _archived/ for .stub.md files only; recurse otherwise
197
+ await (entryName === ARCHIVE_DIR
198
+ ? this.scanArchivedStubs(fullPath, contextTreeDir, stubs)
199
+ : this.scanForManifest(fullPath, contextTreeDir, summaries, contexts, stubs));
200
+ }
201
+ else if (entry.isFile() && entryName.endsWith(CONTEXT_FILE_EXTENSION)) {
202
+ const relativePath = toUnixPath(relative(contextTreeDir, fullPath));
203
+ if (entryName === SUMMARY_INDEX_FILE) {
204
+ // Summary entry — read frontmatter for condensation_order and token_count
205
+ try {
206
+ const content = await readFile(fullPath, 'utf8');
207
+ const fm = parseSummaryFrontmatter(content);
208
+ summaries.push({
209
+ order: fm?.condensation_order,
210
+ path: relativePath,
211
+ tokens: fm?.token_count ?? estimateTokens(content),
212
+ type: 'summary',
213
+ });
214
+ }
215
+ catch {
216
+ // Skip unreadable summaries
217
+ }
218
+ }
219
+ else if (!isDerivedArtifact(relativePath) && !isArchiveStub(relativePath)) {
220
+ // Regular context entry — extract importance from frontmatter
221
+ try {
222
+ const content = await readFile(fullPath, 'utf8');
223
+ const scoring = parseFrontmatterScoring(content);
224
+ contexts.push({
225
+ importance: scoring?.importance ?? 50,
226
+ path: relativePath,
227
+ tokens: estimateTokens(content),
228
+ type: 'context',
229
+ });
230
+ }
231
+ catch {
232
+ // Skip unreadable files
233
+ }
234
+ }
235
+ }
236
+ }
237
+ /* eslint-enable no-await-in-loop */
238
+ }
239
+ /**
240
+ * Recursively collect stat data for all source files (for fingerprint).
241
+ * Excludes derived artifacts.
242
+ */
243
+ async scanSourceStats(currentDir, contextTreeDir, entries) {
244
+ let dirEntries;
245
+ try {
246
+ dirEntries = await readdir(currentDir, { withFileTypes: true });
247
+ }
248
+ catch {
249
+ return;
250
+ }
251
+ /* eslint-disable no-await-in-loop */
252
+ for (const entry of dirEntries) {
253
+ const entryName = entry.name;
254
+ const fullPath = join(currentDir, entryName);
255
+ if (entry.isDirectory()) {
256
+ await this.scanSourceStats(fullPath, contextTreeDir, entries);
257
+ }
258
+ else if (entry.isFile() && entryName.endsWith(CONTEXT_FILE_EXTENSION)) {
259
+ const relativePath = toUnixPath(relative(contextTreeDir, fullPath));
260
+ // Include all non-derived files (contexts + stubs) in fingerprint
261
+ if (isDerivedArtifact(relativePath))
262
+ continue;
263
+ try {
264
+ const fileStat = await stat(fullPath);
265
+ entries.push({
266
+ mtime: fileStat.mtimeMs,
267
+ path: relativePath,
268
+ size: fileStat.size,
269
+ });
270
+ }
271
+ catch {
272
+ // Skip unreadable files
273
+ }
274
+ }
275
+ }
276
+ /* eslint-enable no-await-in-loop */
277
+ }
278
+ }
@@ -1,6 +1,7 @@
1
1
  import { copyFile, mkdir, opendir, readFile, rename, rm, stat, unlink, writeFile } from 'node:fs/promises';
2
2
  import { dirname, extname, join } from 'node:path';
3
3
  import { BRV_DIR, CONTEXT_TREE_BACKUP_DIR, CONTEXT_TREE_CONFLICT_DIR, CONTEXT_TREE_DIR } from '../../constants.js';
4
+ import { isExcludedFromSync } from './derived-artifact.js';
4
5
  import { computeContentHash } from './hash-utils.js';
5
6
  import { toUnixPath } from './path-utils.js';
6
7
  /**
@@ -148,6 +149,9 @@ export class FileContextTreeMerger {
148
149
  const remoteFileStates = new Map();
149
150
  /* eslint-disable no-await-in-loop */
150
151
  for (const [normalPath, file] of remoteFilesMap) {
152
+ // Skip derived artifacts (_index.md, _archived/*, _manifest.json)
153
+ if (isExcludedFromSync(normalPath))
154
+ continue;
151
155
  const targetPath = join(contextTreeDir, normalPath);
152
156
  const remoteHash = computeContentHash(file.decodedContent);
153
157
  const snapshotHash = snapshotState.get(normalPath)?.hash;
@@ -1,7 +1,8 @@
1
1
  import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join, relative } from 'node:path';
3
- import { BRV_DIR, CONTEXT_FILE_EXTENSION, CONTEXT_TREE_DIR, README_FILE, SNAPSHOT_FILE } from '../../constants.js';
3
+ import { ARCHIVE_DIR, BRV_DIR, CONTEXT_FILE_EXTENSION, CONTEXT_TREE_DIR, README_FILE, SNAPSHOT_FILE } from '../../constants.js';
4
4
  import { ContextTreeSnapshot, } from '../../core/domain/entities/context-tree-snapshot.js';
5
+ import { isExcludedFromSync } from './derived-artifact.js';
5
6
  import { computeContentHash } from './hash-utils.js';
6
7
  import { toUnixPath } from './path-utils.js';
7
8
  /**
@@ -117,15 +118,22 @@ export class FileContextTreeSnapshotService {
117
118
  continue;
118
119
  }
119
120
  if (entry.isDirectory()) {
121
+ // Skip _archived/ directory (contains derived artifacts)
122
+ if (entry.name === ARCHIVE_DIR)
123
+ continue;
120
124
  tasks.push(this.scanDirectory(fullPath, rootDir, files));
121
125
  }
122
126
  else if (entry.isFile() && entry.name.endsWith(CONTEXT_FILE_EXTENSION)) {
123
127
  // Only ignore README.md at root level, track it in subdirectories
124
128
  const isRoot = currentDir === rootDir;
125
129
  const isReadmeAtRoot = entry.name === README_FILE && isRoot;
126
- if (!isReadmeAtRoot) {
127
- tasks.push(this.processFile(fullPath, rootDir, files));
128
- }
130
+ if (isReadmeAtRoot)
131
+ continue;
132
+ // Skip derived artifacts (_index.md, _manifest.json, .stub.md, .full.md)
133
+ const relativePath = toUnixPath(relative(rootDir, fullPath));
134
+ if (isExcludedFromSync(relativePath))
135
+ continue;
136
+ tasks.push(this.processFile(fullPath, rootDir, files));
129
137
  }
130
138
  }
131
139
  await Promise.all(tasks);
@@ -0,0 +1,44 @@
1
+ /**
2
+ * File-based implementation of IContextTreeSummaryService.
3
+ *
4
+ * Manages hierarchical summary nodes (_index.md) in the context tree.
5
+ * Uses three-tier escalation (normal → aggressive → deterministic fallback)
6
+ * following the same pattern as PreCompactionService.
7
+ *
8
+ * Fail-open: any error returns { actionTaken: false } — never blocks curation.
9
+ */
10
+ import type { ICipherAgent } from '../../../agent/core/interfaces/i-cipher-agent.js';
11
+ import type { StalenessCheckResult, SummaryGenerationResult } from '../../core/domain/knowledge/summary-types.js';
12
+ import type { IContextTreeSummaryService } from '../../core/interfaces/context-tree/i-context-tree-summary-service.js';
13
+ export declare class FileContextTreeSummaryService implements IContextTreeSummaryService {
14
+ checkStaleness(directoryPath: string, directory?: string): Promise<StalenessCheckResult>;
15
+ generateSummary(directoryPath: string, agent: ICipherAgent, directory?: string): Promise<SummaryGenerationResult>;
16
+ hasSummary(directoryPath: string, directory?: string): Promise<boolean>;
17
+ propagateStaleness(changedPaths: string[], agent: ICipherAgent, directory?: string): Promise<SummaryGenerationResult[]>;
18
+ /**
19
+ * Collect inputs for a summary (the summary input set invariant):
20
+ * - Leaf .md files in the directory (excluding _index.md, _archived/)
21
+ * - Child directory _index.md files (summaries of subdirectories)
22
+ */
23
+ private collectInputs;
24
+ /**
25
+ * Determine condensation order from directory depth relative to context tree root.
26
+ * Root = d3, domain = d2, topic = d1, subtopic = d0
27
+ */
28
+ private depthToCondensationOrder;
29
+ /**
30
+ * Execute a single summary generation pass via the agent.
31
+ */
32
+ private executeSummaryPass;
33
+ /**
34
+ * Three-tier escalation for summary generation.
35
+ * Follows the same pattern as PreCompactionService.compact().
36
+ */
37
+ private generateWithEscalation;
38
+ /**
39
+ * Check if any descendant of dirPath was stopped due to LLM/IO error.
40
+ * Since we process bottom-up (deepest first), a stopped child means
41
+ * its parent should also stop to prevent regenerating from stale state.
42
+ */
43
+ private hasStoppedDescendant;
44
+ }