@bastani/atomic 0.6.3-0 → 0.6.4-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 (49) hide show
  1. package/.agents/skills/ast-grep/SKILL.md +323 -0
  2. package/.agents/skills/ast-grep/references/rule_reference.md +297 -0
  3. package/.agents/skills/ripgrep/SKILL.md +382 -0
  4. package/.mcp.json +5 -6
  5. package/dist/commands/cli/claude-inflight-hook.d.ts +100 -0
  6. package/dist/commands/cli/claude-inflight-hook.d.ts.map +1 -0
  7. package/dist/commands/cli/claude-stop-hook.d.ts +2 -0
  8. package/dist/commands/cli/claude-stop-hook.d.ts.map +1 -1
  9. package/dist/lib/spawn.d.ts +1 -1
  10. package/dist/lib/spawn.d.ts.map +1 -1
  11. package/dist/sdk/providers/claude.d.ts +36 -0
  12. package/dist/sdk/providers/claude.d.ts.map +1 -1
  13. package/dist/sdk/providers/copilot.d.ts +17 -1
  14. package/dist/sdk/providers/copilot.d.ts.map +1 -1
  15. package/dist/sdk/runtime/executor.d.ts.map +1 -1
  16. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +49 -34
  17. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +1 -1
  18. package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +18 -16
  19. package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +1 -1
  20. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/batching.d.ts +43 -0
  21. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/batching.d.ts.map +1 -0
  22. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +30 -0
  23. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts.map +1 -1
  24. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +2 -1
  25. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts.map +1 -1
  26. package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +18 -16
  27. package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -1
  28. package/dist/services/config/additional-instructions.d.ts +67 -0
  29. package/dist/services/config/additional-instructions.d.ts.map +1 -0
  30. package/package.json +3 -1
  31. package/src/cli.ts +18 -1
  32. package/src/commands/cli/chat/index.ts +52 -2
  33. package/src/commands/cli/claude-inflight-hook.test.ts +598 -0
  34. package/src/commands/cli/claude-inflight-hook.ts +359 -0
  35. package/src/commands/cli/claude-stop-hook.ts +40 -4
  36. package/src/commands/cli/init/index.ts +9 -0
  37. package/src/lib/spawn.ts +6 -2
  38. package/src/sdk/providers/claude.ts +131 -0
  39. package/src/sdk/providers/copilot.ts +30 -1
  40. package/src/sdk/runtime/executor.ts +43 -2
  41. package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +318 -158
  42. package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +253 -129
  43. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/batching.ts +65 -0
  44. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/ignore-by-default.d.ts +8 -0
  45. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +203 -12
  46. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +248 -78
  47. package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +258 -146
  48. package/src/services/config/additional-instructions.ts +273 -0
  49. package/src/services/system/auto-sync.ts +10 -1
@@ -1,34 +1,36 @@
1
1
  /**
2
2
  * deep-research-codebase / copilot
3
3
  *
4
- * Copilot replica of the Claude deep-research-codebase workflow. Specialist
5
- * sub-agents are dispatched as separate headless `ctx.stage()` calls — each
6
- * binds the SDK's session to a single named agent via `sessionOpts: { agent }`,
7
- * which is the SDK-native way to spawn a sub-agent on Copilot.
4
+ * Copilot replica of the Claude deep-research-codebase workflow with the
5
+ * same **batched** Task-tool fan-out. Specialist sub-agents run inside batch
6
+ * sessions: each batch is a single `ctx.stage()` whose orchestrator turn
7
+ * dispatches up to MAX_TASKS_PER_BATCH (≈10) specialists in parallel via
8
+ * Copilot's `agent` tool (alias `Task`, see Copilot subagents docs).
9
+ * Research-history specialists remain as their own sequential sub-pipeline.
8
10
  *
9
- * Copilot-specific concerns baked in (see references/failure-modes.md):
11
+ * See claude/index.ts for the full design rationale and topology diagram.
10
12
  *
11
- * F5 every `ctx.stage()` is a FRESH session. Each specialist receives
12
- * everything it needs (research question, scope, scout overview, and —
13
- * for layer-2 specialists — verbatim locator output) in its first prompt.
13
+ * Copilot-specific concerns baked in (see references/failure-modes.md):
14
14
  *
15
15
  * • F1 — Copilot's last assistant turn is often empty when the agent ends
16
- * on a tool call. We use `getAssistantText()` (canonical concatenation
17
- * of every top-level non-empty assistant turn, ignoring sub-agent
18
- * `parentToolCallId` traffic) instead of `.at(-1).data.content`.
16
+ * on a tool call. Batch sessions don't read `getAssistantText` for the
17
+ * orchestrator output (sub-agents write to disk; the orchestrator's text
18
+ * reply is just a short tally), so this is no longer load-bearing for
19
+ * Stage 2 — but the history pipeline still depends on it.
19
20
  *
20
- * • F6 — every prompt explicitly requires trailing prose AFTER any tool
21
- * call so `getAssistantText()` and downstream `transcript()` reads are
22
- * never empty.
21
+ * • F5 — every `ctx.stage()` is a FRESH session. Batch session prompts
22
+ * embed everything the orchestrator needs (per-task subagent_type,
23
+ * output path, and verbatim specialist prompt) in the first turn.
23
24
  *
24
- * • F9`s.save()` receives `SessionEvent[]` from `s.session.getMessages()`.
25
+ * • F6orchestrator prompt requires a single-line tally as the trailing
26
+ * turn so transcripts are never empty.
25
27
  *
26
- * See claude/index.ts for the full design rationale and topology diagram.
28
+ * F9 — `s.save()` receives `SessionEvent[]` from `s.session.getMessages()`.
27
29
  */
28
30
 
29
31
  import { defineWorkflow } from "../../../index.ts";
30
32
  import type { SessionEvent } from "@github/copilot-sdk";
31
- import { mkdir } from "node:fs/promises";
33
+ import { mkdir, readFile } from "node:fs/promises";
32
34
  import path from "node:path";
33
35
 
34
36
  import {
@@ -43,6 +45,7 @@ import {
43
45
  import {
44
46
  buildAggregatorPrompt,
45
47
  buildAnalyzerPrompt,
48
+ buildBatchOrchestratorPrompt,
46
49
  buildHistoryAnalyzerPrompt,
47
50
  buildHistoryLocatorPrompt,
48
51
  buildLocatorPrompt,
@@ -50,13 +53,23 @@ import {
50
53
  buildPatternFinderPrompt,
51
54
  buildScoutPrompt,
52
55
  slugifyPrompt,
56
+ wrapPromptForTaskDispatch,
53
57
  } from "../helpers/prompts.ts";
54
58
  import { writeExplorerScratchFile } from "../helpers/scratch.ts";
59
+ import {
60
+ chunkBatches,
61
+ MAX_TASKS_PER_BATCH,
62
+ SUBAGENT_TYPE,
63
+ type Layer1Task,
64
+ type Layer2Task,
65
+ } from "../helpers/batching.ts";
55
66
 
56
67
  /**
57
68
  * Concatenate every top-level assistant turn's non-empty content. The final
58
69
  * `assistant.message` of a Copilot turn is often empty when the agent ends
59
70
  * on a tool call (F1), and sub-agent traffic is signalled by `parentToolCallId`.
71
+ * Used for the history pipeline only — batch sessions don't need this since
72
+ * sub-agents write to disk.
60
73
  */
61
74
  function getAssistantText(messages: SessionEvent[]): string {
62
75
  return messages
@@ -69,6 +82,31 @@ function getAssistantText(messages: SessionEvent[]): string {
69
82
  .join("\n\n");
70
83
  }
71
84
 
85
+ /** Read a file as UTF-8, returning empty string if missing or unreadable. */
86
+ async function safeReadFile(absPath: string): Promise<string> {
87
+ try {
88
+ return await readFile(absPath, "utf8");
89
+ } catch {
90
+ return "";
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Log Promise.allSettled rejection reasons to stderr so an all-failed wave
96
+ * leaves a debugging trail instead of silently producing an empty report.
97
+ */
98
+ function logBatchRejections(
99
+ label: string,
100
+ results: PromiseSettledResult<unknown>[],
101
+ ): void {
102
+ for (let i = 0; i < results.length; i++) {
103
+ const r = results[i];
104
+ if (r?.status === "rejected") {
105
+ console.error(`[deep-research-codebase] ${label} batch ${i + 1} failed:`, r.reason);
106
+ }
107
+ }
108
+ }
109
+
72
110
  export default defineWorkflow({
73
111
  name: "deep-research-codebase",
74
112
  description:
@@ -105,7 +143,9 @@ export default defineWorkflow({
105
143
  if (data.units.length === 0) {
106
144
  throw new Error(
107
145
  `deep-research-codebase: scout found no source files under ${root}. ` +
108
- `Run from inside a code repository or check the CODE_EXTENSIONS list.`,
146
+ `Run from inside a code repository, or verify your files use a ` +
147
+ `recognized programming-language extension (sourced from GitHub ` +
148
+ `Linguist + sql/graphql/proto).`,
109
149
  );
110
150
  }
111
151
 
@@ -196,127 +236,211 @@ export default defineWorkflow({
196
236
 
197
237
  const scoutOverview = (await ctx.transcript(scout)).content;
198
238
 
199
- // ── Stage 2: per-partition specialist fan-out ─────────────────────────
200
- const explorerHandles = await Promise.all(
201
- partitions.map(async (partition, idx) => {
202
- const i = idx + 1;
203
- const scratchPath = path.join(scratchDir, `explorer-${i}.md`);
204
-
205
- // Layer 1: locator + pattern-finder run independently.
206
- const [locator, patternFinder] = await Promise.all([
207
- ctx.stage(
208
- {
209
- name: `locator-${i}`,
210
- headless: true,
211
- description: `codebase-locator over partition ${i}`,
212
- },
213
- {},
214
- { agent: "codebase-locator" },
215
- async (s) => {
216
- await s.session.send({
217
- prompt: buildLocatorPrompt({
218
- question: prompt,
219
- partition,
220
- scoutOverview,
221
- index: i,
222
- total: explorerCount,
223
- }),
224
- });
225
- const messages = await s.session.getMessages();
226
- s.save(messages);
227
- return getAssistantText(messages);
228
- },
229
- ),
230
- ctx.stage(
231
- {
232
- name: `pattern-finder-${i}`,
233
- headless: true,
234
- description: `codebase-pattern-finder over partition ${i}`,
235
- },
236
- {},
237
- { agent: "codebase-pattern-finder" },
238
- async (s) => {
239
- await s.session.send({
240
- prompt: buildPatternFinderPrompt({
241
- question: prompt,
242
- partition,
243
- scoutOverview,
244
- index: i,
245
- total: explorerCount,
246
- }),
239
+ // ── Stage 2: batched specialist fan-out ───────────────────────────────
240
+ //
241
+ // Same two-wave batched design as claude/index.ts. Each batch session
242
+ // pins the dispatcher to the `orchestrator` agent (.github/agents/
243
+ // orchestrator.md) its system prompt is purpose-built to delegate
244
+ // everything via the `agent`/Task tool, so the dispatcher cannot wander
245
+ // off and start doing the specialists' work itself.
246
+
247
+ // Per-partition output paths, computed once and reused across both wave
248
+ // task-list construction and synthesis.
249
+ const partitionPaths = partitions.map((_, idx) => {
250
+ const i = idx + 1;
251
+ return {
252
+ locator: path.join(scratchDir, `locator-${i}.md`),
253
+ patternFinder: path.join(scratchDir, `pattern-finder-${i}.md`),
254
+ analyzer: path.join(scratchDir, `analyzer-${i}.md`),
255
+ online: path.join(scratchDir, `online-${i}.md`),
256
+ explorer: path.join(scratchDir, `explorer-${i}.md`),
257
+ };
258
+ });
259
+
260
+ const wave1Tasks: Layer1Task[] = partitions.flatMap((partition, idx) => {
261
+ const i = idx + 1;
262
+ const paths = partitionPaths[idx]!;
263
+ return [
264
+ {
265
+ kind: "locator" as const,
266
+ partitionIndex: i,
267
+ partition,
268
+ outputPath: paths.locator,
269
+ },
270
+ {
271
+ kind: "pattern-finder" as const,
272
+ partitionIndex: i,
273
+ partition,
274
+ outputPath: paths.patternFinder,
275
+ },
276
+ ];
277
+ });
278
+
279
+ const wave1Batches = chunkBatches(wave1Tasks, MAX_TASKS_PER_BATCH);
280
+
281
+ const wave1Results = await Promise.allSettled(
282
+ wave1Batches.map((batch, batchIdx) => {
283
+ const batchNumber = batchIdx + 1;
284
+ return ctx.stage(
285
+ {
286
+ name: `wave1-batch-${batchNumber}`,
287
+ headless: true,
288
+ description: `Layer 1 dispatch (${batch.length} tasks)`,
289
+ },
290
+ {},
291
+ { agent: "orchestrator" },
292
+ async (s) => {
293
+ const taskSpecs = batch.map((t) => {
294
+ const builder =
295
+ t.kind === "locator" ? buildLocatorPrompt : buildPatternFinderPrompt;
296
+ const specialistPrompt = builder({
297
+ question: prompt,
298
+ partition: t.partition,
299
+ scoutOverview,
300
+ index: t.partitionIndex,
301
+ total: explorerCount,
247
302
  });
248
- const messages = await s.session.getMessages();
249
- s.save(messages);
250
- return getAssistantText(messages);
251
- },
252
- ),
253
- ]);
254
-
255
- const locatorOutput = locator.result;
256
- const patternsOutput = patternFinder.result;
257
-
258
- // Layer 2: analyzer + online-researcher consume locator output.
259
- const [analyzer, onlineResearcher] = await Promise.all([
260
- ctx.stage(
261
- {
262
- name: `analyzer-${i}`,
263
- headless: true,
264
- description: `codebase-analyzer over partition ${i}`,
265
- },
266
- {},
267
- { agent: "codebase-analyzer" },
268
- async (s) => {
269
- await s.session.send({
270
- prompt: buildAnalyzerPrompt({
271
- question: prompt,
272
- partition,
273
- locatorOutput,
274
- scoutOverview,
275
- index: i,
276
- total: explorerCount,
303
+ return {
304
+ subagentType: SUBAGENT_TYPE[t.kind],
305
+ outputPath: t.outputPath,
306
+ prompt: wrapPromptForTaskDispatch({
307
+ specialistPrompt,
308
+ outputPath: t.outputPath,
309
+ agentLabel: t.kind.toUpperCase().replaceAll("-", "_"),
277
310
  }),
278
- });
279
- const messages = await s.session.getMessages();
280
- s.save(messages);
281
- return getAssistantText(messages);
282
- },
283
- ),
284
- ctx.stage(
285
- {
286
- name: `online-researcher-${i}`,
287
- headless: true,
288
- description: `codebase-online-researcher over partition ${i}`,
289
- },
290
- {},
291
- { agent: "codebase-online-researcher" },
292
- async (s) => {
293
- await s.session.send({
294
- prompt: buildOnlineResearcherPrompt({
295
- question: prompt,
296
- partition,
297
- locatorOutput,
298
- index: i,
299
- total: explorerCount,
311
+ };
312
+ });
313
+
314
+ await s.session.send({
315
+ prompt: buildBatchOrchestratorPrompt({
316
+ wave: 1,
317
+ batchIndex: batchNumber,
318
+ totalBatches: wave1Batches.length,
319
+ tasks: taskSpecs,
320
+ }),
321
+ });
322
+ s.save(await s.session.getMessages());
323
+ },
324
+ );
325
+ }),
326
+ );
327
+ logBatchRejections("wave1", wave1Results);
328
+
329
+ const locatorOutputs: Map<number, string> = new Map();
330
+ await Promise.all(
331
+ partitions.map(async (_p, idx) => {
332
+ const i = idx + 1;
333
+ locatorOutputs.set(i, await safeReadFile(partitionPaths[idx]!.locator));
334
+ }),
335
+ );
336
+
337
+ const wave2Tasks: Layer2Task[] = partitions.flatMap((partition, idx) => {
338
+ const i = idx + 1;
339
+ const paths = partitionPaths[idx]!;
340
+ const locatorOutput = locatorOutputs.get(i) ?? "";
341
+ return [
342
+ {
343
+ kind: "analyzer" as const,
344
+ partitionIndex: i,
345
+ partition,
346
+ outputPath: paths.analyzer,
347
+ locatorOutput,
348
+ },
349
+ {
350
+ kind: "online-researcher" as const,
351
+ partitionIndex: i,
352
+ partition,
353
+ outputPath: paths.online,
354
+ locatorOutput,
355
+ },
356
+ ];
357
+ });
358
+
359
+ const wave2Batches = chunkBatches(wave2Tasks, MAX_TASKS_PER_BATCH);
360
+
361
+ const wave2Results = await Promise.allSettled(
362
+ wave2Batches.map((batch, batchIdx) => {
363
+ const batchNumber = batchIdx + 1;
364
+ return ctx.stage(
365
+ {
366
+ name: `wave2-batch-${batchNumber}`,
367
+ headless: true,
368
+ description: `Layer 2 dispatch (${batch.length} tasks)`,
369
+ },
370
+ {},
371
+ { agent: "orchestrator" },
372
+ async (s) => {
373
+ const taskSpecs = batch.map((t) => {
374
+ const specialistPrompt =
375
+ t.kind === "analyzer"
376
+ ? buildAnalyzerPrompt({
377
+ question: prompt,
378
+ partition: t.partition,
379
+ locatorOutput: t.locatorOutput,
380
+ scoutOverview,
381
+ index: t.partitionIndex,
382
+ total: explorerCount,
383
+ })
384
+ : buildOnlineResearcherPrompt({
385
+ question: prompt,
386
+ partition: t.partition,
387
+ locatorOutput: t.locatorOutput,
388
+ index: t.partitionIndex,
389
+ total: explorerCount,
390
+ });
391
+ return {
392
+ subagentType: SUBAGENT_TYPE[t.kind],
393
+ outputPath: t.outputPath,
394
+ prompt: wrapPromptForTaskDispatch({
395
+ specialistPrompt,
396
+ outputPath: t.outputPath,
397
+ agentLabel: t.kind.toUpperCase().replaceAll("-", "_"),
300
398
  }),
301
- });
302
- const messages = await s.session.getMessages();
303
- s.save(messages);
304
- return getAssistantText(messages);
305
- },
306
- ),
307
- ]);
308
-
309
- await writeExplorerScratchFile(scratchPath, {
399
+ };
400
+ });
401
+
402
+ await s.session.send({
403
+ prompt: buildBatchOrchestratorPrompt({
404
+ wave: 2,
405
+ batchIndex: batchNumber,
406
+ totalBatches: wave2Batches.length,
407
+ tasks: taskSpecs,
408
+ }),
409
+ });
410
+ s.save(await s.session.getMessages());
411
+ },
412
+ );
413
+ }),
414
+ );
415
+ logBatchRejections("wave2", wave2Results);
416
+
417
+ // Synthesis: read all four specialist files per partition and write the
418
+ // consolidated explorer scratch file. Missing files fall back to "" so
419
+ // partial batch failures degrade gracefully.
420
+ const explorerHandles = await Promise.all(
421
+ partitions.map(async (partition, idx) => {
422
+ const i = idx + 1;
423
+ const paths = partitionPaths[idx]!;
424
+
425
+ const [locatorOutput, patternsOutput, analyzerOutput, onlineOutput] =
426
+ await Promise.all([
427
+ Promise.resolve(locatorOutputs.get(i) ?? ""),
428
+ safeReadFile(paths.patternFinder),
429
+ safeReadFile(paths.analyzer),
430
+ safeReadFile(paths.online),
431
+ ]);
432
+
433
+ await writeExplorerScratchFile(paths.explorer, {
310
434
  index: i,
311
435
  total: explorerCount,
312
436
  partition,
313
437
  locatorOutput,
314
438
  patternsOutput,
315
- analyzerOutput: analyzer.result,
316
- onlineOutput: onlineResearcher.result,
439
+ analyzerOutput,
440
+ onlineOutput,
317
441
  });
318
442
 
319
- return { index: i, scratchPath, partition };
443
+ return { index: i, scratchPath: paths.explorer, partition };
320
444
  }),
321
445
  );
322
446
 
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Batching primitives for the deep-research-codebase workflow.
3
+ *
4
+ * The workflow caps SDK-level fan-out by grouping specialist invocations into
5
+ * "batch sessions" — one `ctx.stage()` per batch. Inside each batch session,
6
+ * the default Claude Code agent dispatches up to MAX_TASKS_PER_BATCH
7
+ * sub-agents in parallel via the Task tool. This keeps the parallel SDK
8
+ * subprocess count proportional to (specialists / 10) rather than to
9
+ * specialists itself, which scales linearly with codebase size.
10
+ *
11
+ * The per-message Task fan-out cap is empirical: there's no documented hard
12
+ * limit, but ~10 parallel sub-agents per single message is the reliable
13
+ * ceiling before rate limits, context contention, and degraded coordination
14
+ * kick in. Lower this if you see batch sessions stalling or returning
15
+ * partial completions; raise it only after measuring.
16
+ */
17
+
18
+ import type { PartitionUnit } from "./scout.ts";
19
+
20
+ /** Maximum Task-tool sub-agent dispatches per single batch session. */
21
+ export const MAX_TASKS_PER_BATCH = 10;
22
+
23
+ /** Specialist kinds that share Layer 1 (no inter-task dependencies). */
24
+ export type Layer1Kind = "locator" | "pattern-finder";
25
+
26
+ /** Specialist kinds that share Layer 2 (depend on Layer 1 locator output). */
27
+ export type Layer2Kind = "analyzer" | "online-researcher";
28
+
29
+ /** Maps a specialist kind to the Claude agent name it dispatches as. */
30
+ export const SUBAGENT_TYPE: Record<Layer1Kind | Layer2Kind, string> = {
31
+ locator: "codebase-locator",
32
+ "pattern-finder": "codebase-pattern-finder",
33
+ analyzer: "codebase-analyzer",
34
+ "online-researcher": "codebase-online-researcher",
35
+ };
36
+
37
+ export type Layer1Task = {
38
+ kind: Layer1Kind;
39
+ partitionIndex: number;
40
+ partition: PartitionUnit[];
41
+ /** Absolute path the sub-agent must write its verbatim findings to. */
42
+ outputPath: string;
43
+ };
44
+
45
+ export type Layer2Task = {
46
+ kind: Layer2Kind;
47
+ partitionIndex: number;
48
+ partition: PartitionUnit[];
49
+ outputPath: string;
50
+ /** Verbatim locator text for this partition, embedded into the prompt. */
51
+ locatorOutput: string;
52
+ };
53
+
54
+ /** Split a flat task list into fixed-size chunks (last chunk may be smaller). */
55
+ export function chunkBatches<T>(
56
+ items: T[],
57
+ size: number = MAX_TASKS_PER_BATCH,
58
+ ): T[][] {
59
+ if (size <= 0) throw new Error("chunkBatches: size must be > 0");
60
+ const out: T[][] = [];
61
+ for (let i = 0; i < items.length; i += size) {
62
+ out.push(items.slice(i, i + size));
63
+ }
64
+ return out;
65
+ }
@@ -0,0 +1,8 @@
1
+ // `ignore-by-default` ships no .d.ts and `@types/ignore-by-default` is an
2
+ // empty placeholder package. Declare the surface we use here so the import
3
+ // in scout.ts type-checks. The runtime API is a single CJS export:
4
+ // `module.exports.directories(): string[]`.
5
+ declare module "ignore-by-default" {
6
+ const ibd: { directories(): string[] };
7
+ export default ibd;
8
+ }