@cesarandreslopez/occ 0.8.12 → 0.9.1

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 (193) hide show
  1. package/README.md +170 -79
  2. package/dist/src/cli-validation.js +3 -11
  3. package/dist/src/cli-validation.js.map +1 -1
  4. package/dist/src/cli.js +23 -11
  5. package/dist/src/cli.js.map +1 -1
  6. package/dist/src/code/build.d.ts +6 -7
  7. package/dist/src/code/build.js +62 -28
  8. package/dist/src/code/build.js.map +1 -1
  9. package/dist/src/code/cache.js +3 -1
  10. package/dist/src/code/cache.js.map +1 -1
  11. package/dist/src/code/chunk.d.ts +2 -0
  12. package/dist/src/code/chunk.js +16 -4
  13. package/dist/src/code/chunk.js.map +1 -1
  14. package/dist/src/code/command.js +147 -37
  15. package/dist/src/code/command.js.map +1 -1
  16. package/dist/src/code/diff.d.ts +264 -0
  17. package/dist/src/code/diff.js +504 -0
  18. package/dist/src/code/diff.js.map +1 -0
  19. package/dist/src/code/discover.js +5 -5
  20. package/dist/src/code/discover.js.map +1 -1
  21. package/dist/src/code/focus.js +5 -5
  22. package/dist/src/code/focus.js.map +1 -1
  23. package/dist/src/code/incremental.js +11 -9
  24. package/dist/src/code/incremental.js.map +1 -1
  25. package/dist/src/code/index-io.d.ts +2 -0
  26. package/dist/src/code/index-io.js +2 -2
  27. package/dist/src/code/index-io.js.map +1 -1
  28. package/dist/src/code/isolated-runner.js.map +1 -1
  29. package/dist/src/code/isolated.js +2 -2
  30. package/dist/src/code/isolated.js.map +1 -1
  31. package/dist/src/code/languages.js +2 -2
  32. package/dist/src/code/languages.js.map +1 -1
  33. package/dist/src/code/map.d.ts +12 -0
  34. package/dist/src/code/map.js +22 -9
  35. package/dist/src/code/map.js.map +1 -1
  36. package/dist/src/code/output.d.ts +3 -0
  37. package/dist/src/code/output.js +39 -22
  38. package/dist/src/code/output.js.map +1 -1
  39. package/dist/src/code/parsers.js +87 -26
  40. package/dist/src/code/parsers.js.map +1 -1
  41. package/dist/src/code/query.js +44 -38
  42. package/dist/src/code/query.js.map +1 -1
  43. package/dist/src/code/rank.js +30 -17
  44. package/dist/src/code/rank.js.map +1 -1
  45. package/dist/src/code/session.js.map +1 -1
  46. package/dist/src/code/slim.d.ts +2 -0
  47. package/dist/src/code/slim.js +23 -8
  48. package/dist/src/code/slim.js.map +1 -1
  49. package/dist/src/code/store.d.ts +6 -8
  50. package/dist/src/code/store.js +57 -41
  51. package/dist/src/code/store.js.map +1 -1
  52. package/dist/src/code/types.js +15 -3
  53. package/dist/src/code/types.js.map +1 -1
  54. package/dist/src/doc/batch.js +6 -2
  55. package/dist/src/doc/batch.js.map +1 -1
  56. package/dist/src/doc/chunk.js +9 -5
  57. package/dist/src/doc/chunk.js.map +1 -1
  58. package/dist/src/doc/command.js +18 -9
  59. package/dist/src/doc/command.js.map +1 -1
  60. package/dist/src/doc/discover.d.ts +1 -1
  61. package/dist/src/doc/discover.js.map +1 -1
  62. package/dist/src/doc/entities.js +90 -10
  63. package/dist/src/doc/entities.js.map +1 -1
  64. package/dist/src/doc/inspect-docx.js +5 -4
  65. package/dist/src/doc/inspect-docx.js.map +1 -1
  66. package/dist/src/doc/inspect-odt.js.map +1 -1
  67. package/dist/src/doc/inspect-pdf.js +1 -1
  68. package/dist/src/doc/inspect-pdf.js.map +1 -1
  69. package/dist/src/doc/inspect.js +4 -4
  70. package/dist/src/doc/inspect.js.map +1 -1
  71. package/dist/src/doc/output.js +11 -8
  72. package/dist/src/doc/output.js.map +1 -1
  73. package/dist/src/doc/references.js +4 -4
  74. package/dist/src/doc/references.js.map +1 -1
  75. package/dist/src/errors.js +2 -4
  76. package/dist/src/errors.js.map +1 -1
  77. package/dist/src/git/api.d.ts +34 -0
  78. package/dist/src/git/api.js +247 -0
  79. package/dist/src/git/api.js.map +1 -0
  80. package/dist/src/git/client.d.ts +48 -0
  81. package/dist/src/git/client.js +336 -0
  82. package/dist/src/git/client.js.map +1 -0
  83. package/dist/src/git/command.d.ts +2 -0
  84. package/dist/src/git/command.js +173 -0
  85. package/dist/src/git/command.js.map +1 -0
  86. package/dist/src/git/index.d.ts +5 -0
  87. package/dist/src/git/index.js +6 -0
  88. package/dist/src/git/index.js.map +1 -0
  89. package/dist/src/git/output.d.ts +8 -0
  90. package/dist/src/git/output.js +38 -0
  91. package/dist/src/git/output.js.map +1 -0
  92. package/dist/src/git/parse.d.ts +19 -0
  93. package/dist/src/git/parse.js +354 -0
  94. package/dist/src/git/parse.js.map +1 -0
  95. package/dist/src/git/types.d.ts +334 -0
  96. package/dist/src/git/types.js +135 -0
  97. package/dist/src/git/types.js.map +1 -0
  98. package/dist/src/index.d.ts +38 -12
  99. package/dist/src/index.js +27 -7
  100. package/dist/src/index.js.map +1 -1
  101. package/dist/src/inspect/shared.js.map +1 -1
  102. package/dist/src/markdown/convert.js +17 -11
  103. package/dist/src/markdown/convert.js.map +1 -1
  104. package/dist/src/output/tabular.js +25 -17
  105. package/dist/src/output/tabular.js.map +1 -1
  106. package/dist/src/output/tree.js +7 -7
  107. package/dist/src/output/tree.js.map +1 -1
  108. package/dist/src/parsers/docx.js +1 -1
  109. package/dist/src/parsers/docx.js.map +1 -1
  110. package/dist/src/parsers/index.js +9 -7
  111. package/dist/src/parsers/index.js.map +1 -1
  112. package/dist/src/parsers/pdf.js +1 -1
  113. package/dist/src/parsers/pdf.js.map +1 -1
  114. package/dist/src/parsers/pptx.js.map +1 -1
  115. package/dist/src/parsers/xlsx.js +5 -3
  116. package/dist/src/parsers/xlsx.js.map +1 -1
  117. package/dist/src/scc.js +9 -5
  118. package/dist/src/scc.js.map +1 -1
  119. package/dist/src/sheet/command.js +11 -7
  120. package/dist/src/sheet/command.js.map +1 -1
  121. package/dist/src/sheet/inspect.js +14 -14
  122. package/dist/src/sheet/inspect.js.map +1 -1
  123. package/dist/src/sheet/output.js +10 -15
  124. package/dist/src/sheet/output.js.map +1 -1
  125. package/dist/src/skills/command.d.ts +2 -0
  126. package/dist/src/skills/command.js +188 -0
  127. package/dist/src/skills/command.js.map +1 -0
  128. package/dist/src/slide/command.js +8 -6
  129. package/dist/src/slide/command.js.map +1 -1
  130. package/dist/src/slide/inspect-odp.js.map +1 -1
  131. package/dist/src/slide/inspect-pptx.js +5 -2
  132. package/dist/src/slide/inspect-pptx.js.map +1 -1
  133. package/dist/src/slide/inspect.js +4 -2
  134. package/dist/src/slide/inspect.js.map +1 -1
  135. package/dist/src/slide/output.js +10 -15
  136. package/dist/src/slide/output.js.map +1 -1
  137. package/dist/src/slide/types.js.map +1 -1
  138. package/dist/src/stats.js +31 -3
  139. package/dist/src/stats.js.map +1 -1
  140. package/dist/src/table/command.js +8 -6
  141. package/dist/src/table/command.js.map +1 -1
  142. package/dist/src/table/inspect-docx.js +1 -1
  143. package/dist/src/table/inspect-docx.js.map +1 -1
  144. package/dist/src/table/inspect-odt.js.map +1 -1
  145. package/dist/src/table/inspect-pptx.js +5 -2
  146. package/dist/src/table/inspect-pptx.js.map +1 -1
  147. package/dist/src/table/inspect-xlsx.js +1 -1
  148. package/dist/src/table/inspect-xlsx.js.map +1 -1
  149. package/dist/src/table/inspect.js +3 -1
  150. package/dist/src/table/inspect.js.map +1 -1
  151. package/dist/src/table/output.js +7 -11
  152. package/dist/src/table/output.js.map +1 -1
  153. package/dist/src/table/types.js.map +1 -1
  154. package/dist/src/tokens.js +1 -1
  155. package/dist/src/tokens.js.map +1 -1
  156. package/dist/src/utils.d.ts +14 -0
  157. package/dist/src/utils.js +32 -11
  158. package/dist/src/utils.js.map +1 -1
  159. package/dist/src/walker.js +2 -4
  160. package/dist/src/walker.js.map +1 -1
  161. package/dist/src/workspace/analyze.js +4 -4
  162. package/dist/src/workspace/analyze.js.map +1 -1
  163. package/dist/src/workspace/bundle-io.d.ts +14 -0
  164. package/dist/src/workspace/bundle-io.js +81 -0
  165. package/dist/src/workspace/bundle-io.js.map +1 -0
  166. package/dist/src/workspace/bundle-runner.d.ts +5 -0
  167. package/dist/src/workspace/bundle-runner.js +50 -0
  168. package/dist/src/workspace/bundle-runner.js.map +1 -0
  169. package/dist/src/workspace/bundle.d.ts +14 -0
  170. package/dist/src/workspace/bundle.js +315 -71
  171. package/dist/src/workspace/bundle.js.map +1 -1
  172. package/dist/src/workspace/command.js +31 -16
  173. package/dist/src/workspace/command.js.map +1 -1
  174. package/dist/src/workspace/describe-output.js +10 -9
  175. package/dist/src/workspace/describe-output.js.map +1 -1
  176. package/dist/src/workspace/describe.js +54 -47
  177. package/dist/src/workspace/describe.js.map +1 -1
  178. package/dist/src/workspace/documents.js +28 -8
  179. package/dist/src/workspace/documents.js.map +1 -1
  180. package/dist/src/workspace/prepare-runner.js.map +1 -1
  181. package/dist/src/workspace/prepare-types.js.map +1 -1
  182. package/dist/src/workspace/prepare.js +38 -19
  183. package/dist/src/workspace/prepare.js.map +1 -1
  184. package/dist/src/workspace/types.js +4 -2
  185. package/dist/src/workspace/types.js.map +1 -1
  186. package/package.json +57 -4
  187. package/scripts/postinstall.js +13 -9
  188. package/skills/occ/SKILL.md +77 -0
  189. package/skills/occ/reference/code.md +69 -0
  190. package/skills/occ/reference/commands.md +66 -0
  191. package/skills/occ/reference/documents.md +65 -0
  192. package/skills/occ/reference/git.md +55 -0
  193. package/skills/occ/reference/workspace.md +65 -0
@@ -1,15 +1,20 @@
1
+ import { fork } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { rm } from 'node:fs/promises';
1
4
  import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
2
6
  import { analyzeWorkspace } from './analyze.js';
3
7
  import { describeWorkspace } from './describe.js';
4
8
  import { inspectWorkspaceDocumentSet } from './documents.js';
9
+ import { readSectionedWorkspaceBundle } from './bundle-io.js';
5
10
  import { buildCodebaseIndexIsolated } from '../code/isolated.js';
6
11
  import { previewCodebaseSize } from '../code/preview.js';
7
12
  import { openCodeIndexStore } from '../code/store.js';
8
- import { slimifyIndex } from '../code/slim.js';
9
- import { chunkFromIndex } from '../code/chunk.js';
10
- import { chunkDocument, chunkDocumentFromMarkdown } from '../doc/chunk.js';
11
- import { abortIfNeeded, mapWithConcurrency, normalizeRelativePath } from '../utils.js';
12
- import { isOccAbortError } from '../errors.js';
13
+ import { slimifyIndex, slimifyIndexCooperative } from '../code/slim.js';
14
+ import { chunkFromIndex, chunkFromIndexCooperative } from '../code/chunk.js';
15
+ import { chunkDocument, chunkDocumentFromMarkdown, } from '../doc/chunk.js';
16
+ import { abortIfNeeded, createYielder, isCooperative, mapWithConcurrency, normalizeRelativePath, sanitizeForkExecArgv, } from '../utils.js';
17
+ import { OccAbortError, isOccAbortError } from '../errors.js';
13
18
  const DEFAULT_EXCLUDE_DIR = ['node_modules', '.git', 'dist', 'vendor', 'build', 'coverage', 'target'];
14
19
  // Step descriptors shared by the sequential and concurrent phase paths.
15
20
  const STEP_DESCRIPTION = { phase: 'description', detail: 'Describe workspace' };
@@ -68,7 +73,9 @@ function buildOutline(rootDir, description, documents, codeIndex) {
68
73
  continue;
69
74
  moduleCounts.set(firstSegment, (moduleCounts.get(firstSegment) ?? 0) + 1);
70
75
  }
71
- for (const [modulePath, files] of [...moduleCounts.entries()].sort((left, right) => right[1] - left[1]).slice(0, 20)) {
76
+ for (const [modulePath, files] of [...moduleCounts.entries()]
77
+ .sort((left, right) => right[1] - left[1])
78
+ .slice(0, 20)) {
72
79
  root.children.push({
73
80
  id: `module:${modulePath}`,
74
81
  kind: 'module',
@@ -89,7 +96,7 @@ function buildOutline(rootDir, description, documents, codeIndex) {
89
96
  tokenEstimate: doc.tokenEstimate,
90
97
  headingCount: doc.headingCount,
91
98
  },
92
- children: doc.topLevelSections.map((section) => ({
99
+ children: doc.topLevelSections.map(section => ({
93
100
  id: `section:${doc.relativePath}:${section}`,
94
101
  kind: 'section',
95
102
  title: section,
@@ -107,18 +114,20 @@ function detectCodeDocumentReferences(codeIndex, documents, limit) {
107
114
  if (!codeIndex || !documents || limit <= 0)
108
115
  return [];
109
116
  const symbolNodes = codeIndex.nodes
110
- .filter((node) => ['class', 'interface', 'type-alias', 'enum', 'function'].includes(node.type))
111
- .filter((node) => node.name.length >= 3)
112
- .sort((left, right) => (right.name.length - left.name.length) || left.name.localeCompare(right.name));
117
+ .filter(node => ['class', 'interface', 'type-alias', 'enum', 'function'].includes(node.type))
118
+ .filter(node => node.name.length >= 3)
119
+ .sort((left, right) => right.name.length - left.name.length || left.name.localeCompare(right.name));
113
120
  const seenNames = new Set();
114
- const symbols = symbolNodes.filter((node) => {
121
+ const symbols = symbolNodes
122
+ .filter(node => {
115
123
  if (seenNames.has(node.name))
116
124
  return false;
117
125
  seenNames.add(node.name);
118
126
  return true;
119
- }).slice(0, 1_000);
127
+ })
128
+ .slice(0, 1_000);
120
129
  // Compile each symbol pattern once instead of per line.
121
- const compiled = symbols.map((symbol) => ({
130
+ const compiled = symbols.map(symbol => ({
122
131
  symbol,
123
132
  pattern: new RegExp(`\\b${escapeRegExp(symbol.name)}\\b`),
124
133
  }));
@@ -128,7 +137,7 @@ function detectCodeDocumentReferences(codeIndex, documents, limit) {
128
137
  let prescreen = null;
129
138
  if (symbols.length > 0) {
130
139
  try {
131
- prescreen = new RegExp(`\\b(?:${symbols.map((symbol) => escapeRegExp(symbol.name)).join('|')})\\b`);
140
+ prescreen = new RegExp(`\\b(?:${symbols.map(symbol => escapeRegExp(symbol.name)).join('|')})\\b`);
132
141
  }
133
142
  catch {
134
143
  prescreen = null; // Fall back to scanning every line with the compiled patterns.
@@ -164,7 +173,77 @@ function detectCodeDocumentReferences(codeIndex, documents, limit) {
164
173
  }
165
174
  return references;
166
175
  }
167
- async function buildDocumentChunks(documents, options, onProgress) {
176
+ async function detectCodeDocumentReferencesCooperative(codeIndex, documents, limit, yielder) {
177
+ if (!codeIndex || !documents || limit <= 0)
178
+ return [];
179
+ const symbolNodes = codeIndex.nodes
180
+ .filter(node => ['class', 'interface', 'type-alias', 'enum', 'function'].includes(node.type))
181
+ .filter(node => node.name.length >= 3)
182
+ .sort((left, right) => right.name.length - left.name.length || left.name.localeCompare(right.name));
183
+ const seenNames = new Set();
184
+ const symbols = symbolNodes
185
+ .filter(node => {
186
+ if (seenNames.has(node.name))
187
+ return false;
188
+ seenNames.add(node.name);
189
+ return true;
190
+ })
191
+ .slice(0, 1_000);
192
+ const compiled = symbols.map(symbol => ({
193
+ symbol,
194
+ pattern: new RegExp(`\\b${escapeRegExp(symbol.name)}\\b`),
195
+ }));
196
+ let prescreen = null;
197
+ if (symbols.length > 0) {
198
+ try {
199
+ prescreen = new RegExp(`\\b(?:${symbols.map(symbol => escapeRegExp(symbol.name)).join('|')})\\b`);
200
+ }
201
+ catch {
202
+ prescreen = null;
203
+ }
204
+ }
205
+ const references = [];
206
+ for (const doc of documents.documents) {
207
+ if (!doc.markdownContent) {
208
+ await yielder.tick();
209
+ continue;
210
+ }
211
+ const lines = doc.markdownContent.split(/\r?\n/);
212
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
213
+ const line = lines[lineIndex];
214
+ if (!line.trim()) {
215
+ if ((lineIndex + 1) % 2_000 === 0)
216
+ await yielder.tick();
217
+ continue;
218
+ }
219
+ if (prescreen && !prescreen.test(line)) {
220
+ if ((lineIndex + 1) % 2_000 === 0)
221
+ await yielder.tick();
222
+ continue;
223
+ }
224
+ for (const { symbol, pattern } of compiled) {
225
+ if (!pattern.test(line))
226
+ continue;
227
+ references.push({
228
+ documentPath: doc.relativePath,
229
+ symbolName: symbol.name,
230
+ symbolType: symbol.type,
231
+ symbolPath: symbol.relativePath ?? normalizeRelativePath(path.relative(codeIndex.repoRoot, symbol.path)),
232
+ symbolLine: symbol.line,
233
+ line: lineIndex + 1,
234
+ context: line.trim().slice(0, 240),
235
+ });
236
+ if (references.length >= limit)
237
+ return references;
238
+ }
239
+ if ((lineIndex + 1) % 2_000 === 0)
240
+ await yielder.tick();
241
+ }
242
+ await yielder.tick();
243
+ }
244
+ return references;
245
+ }
246
+ async function buildDocumentChunks(documents, options, onProgress, yielder) {
168
247
  if (!documents)
169
248
  return [];
170
249
  const maxChunks = options.maxDocumentChunks ?? 500;
@@ -183,19 +262,20 @@ async function buildDocumentChunks(documents, options, onProgress) {
183
262
  return undefined;
184
263
  let docChunks;
185
264
  try {
186
- docChunks = doc.markdownContent != null
187
- // Markdown was already produced by document inspection — reuse it
188
- // instead of re-reading and re-converting the file. Per-document
189
- // values are spread last so caller chunk options cannot clobber them.
190
- ? chunkDocumentFromMarkdown(doc.markdownContent, {
191
- ...options.chunk,
192
- filePath: doc.filePath,
193
- relativePath: doc.relativePath,
194
- })
195
- : await chunkDocument(doc.filePath, {
196
- ...options.chunk,
197
- relativePath: doc.relativePath,
198
- });
265
+ docChunks =
266
+ doc.markdownContent != null
267
+ ? // Markdown was already produced by document inspection — reuse it
268
+ // instead of re-reading and re-converting the file. Per-document
269
+ // values are spread last so caller chunk options cannot clobber them.
270
+ chunkDocumentFromMarkdown(doc.markdownContent, {
271
+ ...options.chunk,
272
+ filePath: doc.filePath,
273
+ relativePath: doc.relativePath,
274
+ })
275
+ : await chunkDocument(doc.filePath, {
276
+ ...options.chunk,
277
+ relativePath: doc.relativePath,
278
+ });
199
279
  }
200
280
  catch {
201
281
  // Document conversion errors are already represented in inspectDocuments.
@@ -210,6 +290,8 @@ async function buildDocumentChunks(documents, options, onProgress) {
210
290
  currentPath: doc.filePath,
211
291
  scope: 'document',
212
292
  });
293
+ if (yielder)
294
+ await yielder.tick();
213
295
  return docChunks;
214
296
  }, options.signal);
215
297
  const chunks = [];
@@ -219,13 +301,13 @@ async function buildDocumentChunks(documents, options, onProgress) {
219
301
  }
220
302
  return chunks.slice(0, maxChunks);
221
303
  }
222
- function buildCodeChunks(codeIndex, options, onProgress) {
304
+ async function buildCodeChunks(codeIndex, options, onProgress, yielder) {
223
305
  if (!codeIndex)
224
306
  return [];
225
- const chunks = chunkFromIndex(codeIndex, options.codeChunk);
226
- const capped = options.maxCodeChunks != null
227
- ? chunks.slice(0, Math.max(0, options.maxCodeChunks))
228
- : chunks;
307
+ const chunks = yielder
308
+ ? await chunkFromIndexCooperative(codeIndex, options.codeChunk, yielder)
309
+ : chunkFromIndex(codeIndex, options.codeChunk);
310
+ const capped = options.maxCodeChunks != null ? chunks.slice(0, Math.max(0, options.maxCodeChunks)) : chunks;
229
311
  onProgress?.({
230
312
  phase: 'code-chunk',
231
313
  total: capped.length,
@@ -253,7 +335,7 @@ function mapCodeChunk(chunk) {
253
335
  }
254
336
  function mapDocumentChunksToCorpus(chunks) {
255
337
  const perFileIndex = new Map();
256
- return chunks.map((chunk) => {
338
+ return chunks.map(chunk => {
257
339
  const chunkIndex = perFileIndex.get(chunk.relativePath) ?? 0;
258
340
  perFileIndex.set(chunk.relativePath, chunkIndex + 1);
259
341
  return {
@@ -274,19 +356,172 @@ function mapDocumentChunksToCorpus(chunks) {
274
356
  };
275
357
  });
276
358
  }
277
- function buildCorpusChunks(codeChunks, documentChunks, options) {
359
+ async function buildCorpusChunks(codeChunks, documentChunks, options, yielder) {
278
360
  if (!options.includeCorpusChunks)
279
361
  return undefined;
280
- const chunks = [
281
- ...(codeChunks ?? []).map(mapCodeChunk),
282
- ...mapDocumentChunksToCorpus(documentChunks ?? []),
283
- ].sort((left, right) => (left.relativePath.localeCompare(right.relativePath)
284
- || left.kind.localeCompare(right.kind)
285
- || left.chunkIndex - right.chunkIndex));
362
+ let chunks;
363
+ if (!yielder) {
364
+ chunks = [...(codeChunks ?? []).map(mapCodeChunk), ...mapDocumentChunksToCorpus(documentChunks ?? [])];
365
+ }
366
+ else {
367
+ chunks = [];
368
+ let mapped = 0;
369
+ for (const chunk of codeChunks ?? []) {
370
+ chunks.push(mapCodeChunk(chunk));
371
+ if (++mapped % 512 === 0)
372
+ await yielder.tick();
373
+ }
374
+ const perFileIndex = new Map();
375
+ for (const chunk of documentChunks ?? []) {
376
+ const chunkIndex = perFileIndex.get(chunk.relativePath) ?? 0;
377
+ perFileIndex.set(chunk.relativePath, chunkIndex + 1);
378
+ chunks.push({
379
+ kind: 'document',
380
+ chunkId: chunk.chunkId,
381
+ filePath: chunk.filePath,
382
+ relativePath: chunk.relativePath,
383
+ title: chunk.title,
384
+ content: chunk.content,
385
+ chunkIndex,
386
+ startLine: chunk.startLine,
387
+ endLine: chunk.endLine,
388
+ format: chunk.format,
389
+ headingPath: chunk.headingPath,
390
+ anchor: chunk.anchor,
391
+ tokenEstimate: chunk.tokenEstimate,
392
+ wordCount: chunk.wordCount,
393
+ });
394
+ if (++mapped % 512 === 0)
395
+ await yielder.tick();
396
+ }
397
+ }
398
+ chunks.sort((left, right) => left.relativePath.localeCompare(right.relativePath) ||
399
+ left.kind.localeCompare(right.kind) ||
400
+ left.chunkIndex - right.chunkIndex);
286
401
  const maxChunks = options.maxCorpusChunks;
287
402
  return maxChunks != null ? chunks.slice(0, Math.max(0, maxChunks)) : chunks;
288
403
  }
404
+ function resolveBundleRunnerPath() {
405
+ const directory = path.dirname(fileURLToPath(import.meta.url));
406
+ const builtRunner = path.join(directory, 'bundle-runner.js');
407
+ if (existsSync(builtRunner))
408
+ return builtRunner;
409
+ return path.join(directory, 'bundle-runner.ts');
410
+ }
411
+ function serializableBundleOptions(options) {
412
+ const { signal: _signal, onProgress: _onProgress, store: _store, cooperative: _cooperative, ...rest } = options;
413
+ return {
414
+ ...rest,
415
+ offThread: false,
416
+ chunk: rest.chunk
417
+ ? {
418
+ maxTokens: rest.chunk.maxTokens,
419
+ overlapTokens: rest.chunk.overlapTokens,
420
+ }
421
+ : undefined,
422
+ codeChunk: rest.codeChunk
423
+ ? {
424
+ maxTokens: rest.codeChunk.maxTokens,
425
+ overlapTokens: rest.codeChunk.overlapTokens,
426
+ files: rest.codeChunk.files,
427
+ }
428
+ : undefined,
429
+ };
430
+ }
431
+ export async function bundleWorkspaceIsolated(rootDir, options = {}, onProgress) {
432
+ if (options.store) {
433
+ throw new Error('offThread bundle requires cacheDir or no store; a caller-provided store instance cannot cross the process boundary');
434
+ }
435
+ abortIfNeeded(options.signal);
436
+ const runnerPath = resolveBundleRunnerPath();
437
+ const progress = onProgress ?? options.onProgress;
438
+ const childOptions = serializableBundleOptions(options);
439
+ return new Promise((resolve, reject) => {
440
+ let settled = false;
441
+ let abortTimer;
442
+ const child = fork(runnerPath, [], {
443
+ stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
444
+ execArgv: sanitizeForkExecArgv(),
445
+ });
446
+ const cleanup = () => {
447
+ if (abortTimer)
448
+ clearTimeout(abortTimer);
449
+ options.signal?.removeEventListener('abort', handleAbort);
450
+ };
451
+ const rejectAbort = () => {
452
+ cleanup();
453
+ reject(new OccAbortError());
454
+ };
455
+ const handleAbort = () => {
456
+ if (settled)
457
+ return;
458
+ child.send({ type: 'abort' });
459
+ abortTimer = setTimeout(() => {
460
+ if (!settled)
461
+ child.kill();
462
+ }, 1000);
463
+ };
464
+ options.signal?.addEventListener('abort', handleAbort, { once: true });
465
+ let stderr = '';
466
+ child.stderr?.on('data', (chunk) => {
467
+ stderr += chunk.toString();
468
+ });
469
+ child.on('message', (msg) => {
470
+ if (msg.type === 'progress' && msg.event) {
471
+ progress?.(msg.event);
472
+ }
473
+ else if (msg.type === 'aborted') {
474
+ settled = true;
475
+ rejectAbort();
476
+ }
477
+ else if (msg.type === 'complete' && msg.resultPath) {
478
+ settled = true;
479
+ void (async () => {
480
+ try {
481
+ const bundle = await readSectionedWorkspaceBundle(msg.resultPath, options.signal);
482
+ await rm(msg.resultPath, { recursive: true, force: true }).catch(() => undefined);
483
+ cleanup();
484
+ resolve(bundle);
485
+ }
486
+ catch (err) {
487
+ cleanup();
488
+ reject(err instanceof Error ? err : new Error(String(err)));
489
+ }
490
+ })();
491
+ }
492
+ else if (msg.type === 'error') {
493
+ settled = true;
494
+ cleanup();
495
+ reject(new Error(msg.message ?? 'Off-thread bundle failed'));
496
+ }
497
+ });
498
+ child.on('error', err => {
499
+ settled = true;
500
+ cleanup();
501
+ reject(err);
502
+ });
503
+ child.on('close', code => {
504
+ if (settled)
505
+ return;
506
+ cleanup();
507
+ if (options.signal?.aborted) {
508
+ rejectAbort();
509
+ return;
510
+ }
511
+ if (code !== 0) {
512
+ reject(new Error(`Bundle runner exited with code ${code}: ${stderr.trim()}`));
513
+ }
514
+ else {
515
+ reject(new Error('Bundle runner exited without sending a result'));
516
+ }
517
+ });
518
+ child.send({ type: 'start', rootDir: path.resolve(rootDir), options: childOptions });
519
+ });
520
+ }
289
521
  export async function bundleWorkspace(rootDir, options = {}) {
522
+ if (options.offThread) {
523
+ return bundleWorkspaceIsolated(rootDir, options, options.onProgress);
524
+ }
290
525
  const startedAt = Date.now();
291
526
  const resolvedRoot = path.resolve(rootDir);
292
527
  const includeDescription = options.includeDescription !== false;
@@ -300,6 +535,8 @@ export async function bundleWorkspace(rootDir, options = {}) {
300
535
  const includeCodeDocumentReferences = options.includeCodeDocumentReferences ?? true;
301
536
  const concurrentPhases = options.concurrentPhases !== false;
302
537
  const onProgress = options.onProgress;
538
+ const cooperative = isCooperative(options.cooperative);
539
+ const yielder = cooperative ? createYielder(options.cooperative, options.signal) : undefined;
303
540
  const errors = [];
304
541
  const shared = commonWorkspaceOptions(options);
305
542
  const steps = [
@@ -313,36 +550,36 @@ export async function bundleWorkspace(rootDir, options = {}) {
313
550
  STEP_OUTLINE,
314
551
  ];
315
552
  let completedSteps = 0;
316
- const scopedCodeProgress = (event) => onProgress?.({ ...event, scope: event.scope ?? 'code' });
553
+ const scopedCodeProgress = event => onProgress?.({ ...event, scope: event.scope ?? 'code' });
317
554
  // Resolves the code index either through a persistent store (cacheDir/store
318
555
  // options — cache hits skip parsing, misses build in-process) or through the
319
556
  // pre-existing isolated subprocess build.
320
557
  const acquireCodeIndex = async () => {
321
558
  const slim = options.slimCodeIndex ?? true;
322
- const contentMode = shouldBuildCodeChunks
323
- ? 'full'
324
- : (options.contentMode ?? (slim ? 'none' : 'excerpt'));
559
+ const contentMode = shouldBuildCodeChunks ? 'full' : (options.contentMode ?? (slim ? 'none' : 'excerpt'));
325
560
  if (options.store || options.cacheDir) {
326
- const store = options.store ?? openCodeIndexStore({
327
- repoRoot: resolvedRoot,
328
- cacheDir: options.cacheDir,
329
- excludeDir: shared.excludeDir,
330
- noGitignore: shared.noGitignore,
331
- ignorePatterns: shared.ignorePatterns,
332
- overlayIgnorePatterns: shared.overlayIgnorePatterns,
333
- contentMode,
334
- maxFiles: options.maxCodeFiles,
335
- maxBytes: options.maxCodeBytes,
336
- onBudgetExceeded: 'truncate',
337
- });
561
+ const store = options.store ??
562
+ openCodeIndexStore({
563
+ repoRoot: resolvedRoot,
564
+ cacheDir: options.cacheDir,
565
+ excludeDir: shared.excludeDir,
566
+ noGitignore: shared.noGitignore,
567
+ ignorePatterns: shared.ignorePatterns,
568
+ overlayIgnorePatterns: shared.overlayIgnorePatterns,
569
+ contentMode,
570
+ maxFiles: options.maxCodeFiles,
571
+ maxBytes: options.maxCodeBytes,
572
+ onBudgetExceeded: 'truncate',
573
+ });
338
574
  const session = await store.getSession({
339
575
  strategy: options.cacheStrategy ?? 'ensure-fresh',
340
576
  signal: options.signal,
341
577
  onProgress: scopedCodeProgress,
578
+ cooperative: options.cooperative,
342
579
  });
343
580
  const index = session.getIndex();
344
581
  return {
345
- codeIndex: slim ? slimifyIndex(index) : index,
582
+ codeIndex: slim ? (yielder ? await slimifyIndexCooperative(index, yielder) : slimifyIndex(index)) : index,
346
583
  chunkSource: index.contentMode === 'full' ? index : undefined,
347
584
  };
348
585
  }
@@ -357,7 +594,11 @@ export async function bundleWorkspace(rootDir, options = {}) {
357
594
  signal: options.signal,
358
595
  }, scopedCodeProgress);
359
596
  return {
360
- codeIndex: slim ? slimifyIndex(index) : index,
597
+ codeIndex: slim
598
+ ? yielder
599
+ ? await slimifyIndexCooperative(index, yielder)
600
+ : slimifyIndex(index)
601
+ : index,
361
602
  chunkSource: index.contentMode === 'full' ? index : undefined,
362
603
  };
363
604
  };
@@ -379,11 +620,11 @@ export async function bundleWorkspace(rootDir, options = {}) {
379
620
  // post-join abortIfNeeded turns the abort into a rejection.
380
621
  let caughtAbort;
381
622
  const phaseErrors = {
382
- 'description': [],
383
- 'analysis': [],
384
- 'documents': [],
623
+ description: [],
624
+ analysis: [],
625
+ documents: [],
385
626
  'code-preview': [],
386
- 'code': [],
627
+ code: [],
387
628
  };
388
629
  const capture = (scope, err) => {
389
630
  if (isOccAbortError(err)) {
@@ -418,7 +659,7 @@ export async function bundleWorkspace(rootDir, options = {}) {
418
659
  ...shared,
419
660
  includeDocuments,
420
661
  includeCode,
421
- }, (event) => onProgress?.({ ...event, scope: event.scope ?? 'workspace' }));
662
+ }, event => onProgress?.({ ...event, scope: event.scope ?? 'workspace' }));
422
663
  }
423
664
  catch (err) {
424
665
  capture('analysis', err);
@@ -439,7 +680,7 @@ export async function bundleWorkspace(rootDir, options = {}) {
439
680
  includeDataFiles: options.includeDataFiles,
440
681
  maxFiles: options.maxDocumentFiles,
441
682
  maxReferenceFiles: options.maxReferenceFiles,
442
- }, (event) => onProgress?.({ ...event, scope: event.scope ?? 'document' }));
683
+ }, event => onProgress?.({ ...event, scope: event.scope ?? 'document' }));
443
684
  }
444
685
  catch (err) {
445
686
  capture('documents', err);
@@ -509,7 +750,7 @@ export async function bundleWorkspace(rootDir, options = {}) {
509
750
  ...shared,
510
751
  includeDocuments,
511
752
  includeCode,
512
- }, (event) => onProgress?.({ ...event, scope: event.scope ?? 'workspace' }));
753
+ }, event => onProgress?.({ ...event, scope: event.scope ?? 'workspace' }));
513
754
  }
514
755
  catch (err) {
515
756
  const error = err;
@@ -526,7 +767,7 @@ export async function bundleWorkspace(rootDir, options = {}) {
526
767
  includeDataFiles: options.includeDataFiles,
527
768
  maxFiles: options.maxDocumentFiles,
528
769
  maxReferenceFiles: options.maxReferenceFiles,
529
- }, (event) => onProgress?.({ ...event, scope: event.scope ?? 'document' }));
770
+ }, event => onProgress?.({ ...event, scope: event.scope ?? 'document' }));
530
771
  }
531
772
  catch (err) {
532
773
  const error = err;
@@ -569,7 +810,7 @@ export async function bundleWorkspace(rootDir, options = {}) {
569
810
  if (!codeChunkSource) {
570
811
  throw new Error('Code chunks require a full-content code index.');
571
812
  }
572
- codeChunks = buildCodeChunks(codeChunkSource, options, onProgress);
813
+ codeChunks = await buildCodeChunks(codeChunkSource, options, onProgress, yielder);
573
814
  }
574
815
  catch (err) {
575
816
  const error = err;
@@ -579,13 +820,16 @@ export async function bundleWorkspace(rootDir, options = {}) {
579
820
  reportStep(onProgress, steps[completedSteps], ++completedSteps, steps.length, startedAt);
580
821
  }
581
822
  const documentChunks = includeDocumentChunks
582
- ? await buildDocumentChunks(documents, options, onProgress)
823
+ ? await buildDocumentChunks(documents, options, onProgress, yielder)
583
824
  : undefined;
584
825
  if (includeDocumentChunks) {
585
826
  reportStep(onProgress, steps[completedSteps], ++completedSteps, steps.length, startedAt);
586
827
  }
587
828
  const outline = buildOutline(resolvedRoot, description, documents, codeIndex);
588
- const codeDocumentReferences = detectCodeDocumentReferences(codeIndex, documents, includeCodeDocumentReferences ? (options.maxCodeDocumentReferences ?? 200) : 0);
829
+ const referenceLimit = includeCodeDocumentReferences ? (options.maxCodeDocumentReferences ?? 200) : 0;
830
+ const codeDocumentReferences = yielder
831
+ ? await detectCodeDocumentReferencesCooperative(codeIndex, documents, referenceLimit, yielder)
832
+ : detectCodeDocumentReferences(codeIndex, documents, referenceLimit);
589
833
  reportStep(onProgress, steps[completedSteps], ++completedSteps, steps.length, startedAt);
590
834
  reportStep(onProgress, { phase: 'bundle', detail: 'Workspace bundle complete' }, steps.length, steps.length, startedAt);
591
835
  return {
@@ -599,7 +843,7 @@ export async function bundleWorkspace(rootDir, options = {}) {
599
843
  codeIndex,
600
844
  documentChunks,
601
845
  codeChunks,
602
- corpusChunks: buildCorpusChunks(codeChunks, documentChunks, options),
846
+ corpusChunks: await buildCorpusChunks(codeChunks, documentChunks, options, yielder),
603
847
  outline,
604
848
  codeDocumentReferences,
605
849
  errors,