bluera-knowledge 0.9.26 → 0.9.31

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 (63) hide show
  1. package/.claude/commands/commit.md +4 -7
  2. package/.claude/hooks/post-edit-check.sh +21 -24
  3. package/.claude/skills/atomic-commits/SKILL.md +6 -0
  4. package/.claude-plugin/plugin.json +1 -1
  5. package/.env.example +4 -0
  6. package/.husky/pre-push +12 -2
  7. package/.versionrc.json +0 -4
  8. package/BUGS-FOUND.md +71 -0
  9. package/CHANGELOG.md +76 -0
  10. package/README.md +55 -20
  11. package/bun.lock +35 -1
  12. package/commands/crawl.md +2 -0
  13. package/dist/{chunk-BICFAWMN.js → chunk-2SJHNRXD.js} +73 -8
  14. package/dist/chunk-2SJHNRXD.js.map +1 -0
  15. package/dist/{chunk-J7J6LXOJ.js → chunk-OGEY66FZ.js} +106 -41
  16. package/dist/chunk-OGEY66FZ.js.map +1 -0
  17. package/dist/{chunk-5QMHZUC4.js → chunk-RWSXP3PQ.js} +482 -106
  18. package/dist/chunk-RWSXP3PQ.js.map +1 -0
  19. package/dist/index.js +73 -28
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp/server.js +2 -2
  22. package/dist/workers/background-worker-cli.js +2 -2
  23. package/eslint.config.js +1 -1
  24. package/package.json +3 -1
  25. package/src/analysis/ast-parser.test.ts +46 -0
  26. package/src/cli/commands/crawl.test.ts +99 -12
  27. package/src/cli/commands/crawl.ts +76 -24
  28. package/src/cli/commands/store.test.ts +68 -1
  29. package/src/cli/commands/store.ts +9 -3
  30. package/src/crawl/article-converter.ts +36 -1
  31. package/src/crawl/bridge.ts +18 -7
  32. package/src/crawl/intelligent-crawler.ts +45 -4
  33. package/src/db/embeddings.test.ts +16 -0
  34. package/src/db/lance.test.ts +31 -0
  35. package/src/db/lance.ts +8 -0
  36. package/src/logging/index.ts +29 -0
  37. package/src/logging/logger.test.ts +75 -0
  38. package/src/logging/logger.ts +147 -0
  39. package/src/logging/payload.test.ts +152 -0
  40. package/src/logging/payload.ts +121 -0
  41. package/src/mcp/handlers/search.handler.test.ts +28 -9
  42. package/src/mcp/handlers/search.handler.ts +69 -29
  43. package/src/mcp/handlers/store.handler.test.ts +1 -0
  44. package/src/mcp/server.ts +44 -16
  45. package/src/services/chunking.service.ts +23 -0
  46. package/src/services/index.service.test.ts +921 -1
  47. package/src/services/index.service.ts +76 -1
  48. package/src/services/index.ts +20 -2
  49. package/src/services/search.service.test.ts +573 -21
  50. package/src/services/search.service.ts +257 -105
  51. package/src/services/services.test.ts +2 -2
  52. package/src/services/snippet.service.ts +28 -3
  53. package/src/services/store.service.test.ts +28 -0
  54. package/src/services/store.service.ts +4 -0
  55. package/src/services/token.service.test.ts +45 -0
  56. package/src/services/token.service.ts +33 -0
  57. package/src/types/result.test.ts +10 -0
  58. package/tests/integration/cli-consistency.test.ts +1 -4
  59. package/vitest.config.ts +4 -0
  60. package/dist/chunk-5QMHZUC4.js.map +0 -1
  61. package/dist/chunk-BICFAWMN.js.map +0 -1
  62. package/dist/chunk-J7J6LXOJ.js.map +0 -1
  63. package/scripts/readme-version-updater.cjs +0 -18
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Large payload handling utilities for logging
3
+ *
4
+ * Handles large content (raw HTML, MCP responses) by:
5
+ * - Truncating to preview in log entries
6
+ * - Optionally dumping full content to separate files at trace level
7
+ */
8
+
9
+ import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { createHash } from 'node:crypto';
12
+ import { getLogDirectory, isLevelEnabled } from './logger.js';
13
+
14
+ /** Maximum characters for log preview */
15
+ const MAX_PREVIEW_LENGTH = 500;
16
+
17
+ /** Minimum size to trigger payload dump (10KB) */
18
+ const PAYLOAD_DUMP_THRESHOLD = 10_000;
19
+
20
+ /** Summary of a large payload for logging */
21
+ export interface PayloadSummary {
22
+ /** Truncated preview of content */
23
+ preview: string;
24
+ /** Size in bytes */
25
+ sizeBytes: number;
26
+ /** Short hash for identification */
27
+ hash: string;
28
+ /** Filename if full content was dumped (trace level only) */
29
+ payloadFile?: string;
30
+ }
31
+
32
+ /** Get the payload dump directory */
33
+ function getPayloadDir(): string {
34
+ const dir = join(getLogDirectory(), 'payload');
35
+ if (!existsSync(dir)) {
36
+ mkdirSync(dir, { recursive: true });
37
+ }
38
+ return dir;
39
+ }
40
+
41
+ /** Generate a safe filename from an identifier */
42
+ function safeFilename(identifier: string): string {
43
+ return identifier
44
+ .replace(/[^a-zA-Z0-9-]/g, '_')
45
+ .substring(0, 50);
46
+ }
47
+
48
+ /**
49
+ * Summarize a large payload for logging
50
+ *
51
+ * Creates a summary with:
52
+ * - Truncated preview (first 500 chars)
53
+ * - Size in bytes
54
+ * - Short MD5 hash for identification
55
+ * - Optional full dump to file at trace level
56
+ *
57
+ * @param content - The full content to summarize
58
+ * @param type - Type identifier (e.g., 'raw-html', 'mcp-response')
59
+ * @param identifier - Unique identifier (e.g., URL, query)
60
+ * @param dumpFull - Whether to dump full content to file (default: trace level check)
61
+ * @returns PayloadSummary for inclusion in log entry
62
+ *
63
+ * @example
64
+ * logger.info({
65
+ * url,
66
+ * ...summarizePayload(html, 'raw-html', url),
67
+ * }, 'Fetched HTML');
68
+ */
69
+ export function summarizePayload(
70
+ content: string,
71
+ type: string,
72
+ identifier: string,
73
+ dumpFull: boolean = isLevelEnabled('trace')
74
+ ): PayloadSummary {
75
+ const sizeBytes = Buffer.byteLength(content, 'utf8');
76
+ const hash = createHash('md5').update(content).digest('hex').substring(0, 12);
77
+ const preview = truncateForLog(content, MAX_PREVIEW_LENGTH);
78
+
79
+ const baseSummary = { preview, sizeBytes, hash };
80
+
81
+ // Dump full payload to file if enabled and above threshold
82
+ if (dumpFull && sizeBytes > PAYLOAD_DUMP_THRESHOLD) {
83
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
84
+ const safeId = safeFilename(identifier);
85
+ const filename = `${timestamp}-${type}-${safeId}-${hash}.json`;
86
+ const filepath = join(getPayloadDir(), filename);
87
+
88
+ writeFileSync(
89
+ filepath,
90
+ JSON.stringify(
91
+ {
92
+ timestamp: new Date().toISOString(),
93
+ type,
94
+ identifier,
95
+ sizeBytes,
96
+ content,
97
+ },
98
+ null,
99
+ 2
100
+ )
101
+ );
102
+
103
+ return { ...baseSummary, payloadFile: filename };
104
+ }
105
+
106
+ return baseSummary;
107
+ }
108
+
109
+ /**
110
+ * Truncate content for logging with ellipsis indicator
111
+ *
112
+ * @param content - Content to truncate
113
+ * @param maxLength - Maximum length (default: 500)
114
+ * @returns Truncated string with '... [truncated]' if needed
115
+ */
116
+ export function truncateForLog(content: string, maxLength: number = MAX_PREVIEW_LENGTH): string {
117
+ if (content.length <= maxLength) {
118
+ return content;
119
+ }
120
+ return content.substring(0, maxLength) + '... [truncated]';
121
+ }
@@ -3,6 +3,20 @@ import { handleSearch, handleGetFullContext, resultCache } from './search.handle
3
3
  import type { HandlerContext } from '../types.js';
4
4
  import type { ServiceContainer } from '../../services/index.js';
5
5
 
6
+ /**
7
+ * Extract JSON from search response that includes a header line.
8
+ * Format: "Search: ... | Results: ... | ~X tokens | Xms\n\n{json}"
9
+ */
10
+ function parseSearchResponse(text: string): { header: string; json: Record<string, unknown> } {
11
+ const parts = text.split('\n\n');
12
+ const header = parts[0] ?? '';
13
+ const jsonStr = parts.slice(1).join('\n\n');
14
+ return {
15
+ header,
16
+ json: JSON.parse(jsonStr || '{}')
17
+ };
18
+ }
19
+
6
20
  describe('Search Handlers', () => {
7
21
  let mockContext: HandlerContext;
8
22
  let mockServices: ServiceContainer;
@@ -70,7 +84,10 @@ describe('Search Handlers', () => {
70
84
  })
71
85
  );
72
86
 
73
- const response = JSON.parse(result.content[0]?.text ?? '{}');
87
+ const { header, json: response } = parseSearchResponse(result.content[0]?.text ?? '');
88
+ expect(header).toContain('Search: "test query"');
89
+ expect(header).toContain('Results: 1');
90
+ expect(header).toContain('tokens');
74
91
  expect(response.results).toHaveLength(1);
75
92
  expect(response.totalResults).toBe(1);
76
93
  });
@@ -129,14 +146,15 @@ describe('Search Handlers', () => {
129
146
  expect(cached?.id).toBe('doc1');
130
147
  });
131
148
 
132
- it('should calculate estimated tokens', async () => {
149
+ it('should show token count in header', async () => {
133
150
  const result = await handleSearch(
134
151
  { query: 'test', detail: 'minimal', limit: 10 },
135
152
  mockContext
136
153
  );
137
154
 
138
- const response = JSON.parse(result.content[0]?.text ?? '{}');
139
- expect(response.estimatedTokens).toBeGreaterThan(0);
155
+ const { header } = parseSearchResponse(result.content[0]?.text ?? '');
156
+ // Header should contain token count (either "~X tokens" or "~X.Xk tokens")
157
+ expect(header).toMatch(/~\d+\.?\d*k? tokens/);
140
158
  });
141
159
 
142
160
  it('should add repoRoot for repo stores', async () => {
@@ -156,7 +174,7 @@ describe('Search Handlers', () => {
156
174
  mockContext
157
175
  );
158
176
 
159
- const response = JSON.parse(result.content[0]?.text ?? '{}');
177
+ const { json: response } = parseSearchResponse(result.content[0]?.text ?? '');
160
178
  expect(response.results[0]?.summary.repoRoot).toBe('/repos/test');
161
179
  });
162
180
 
@@ -166,7 +184,7 @@ describe('Search Handlers', () => {
166
184
  mockContext
167
185
  );
168
186
 
169
- const response = JSON.parse(result.content[0]?.text ?? '{}');
187
+ const { json: response } = parseSearchResponse(result.content[0]?.text ?? '');
170
188
  expect(response.results[0]?.summary.repoRoot).toBeUndefined();
171
189
  });
172
190
 
@@ -176,7 +194,7 @@ describe('Search Handlers', () => {
176
194
  mockContext
177
195
  );
178
196
 
179
- const response = JSON.parse(result.content[0]?.text ?? '{}');
197
+ const { json: response } = parseSearchResponse(result.content[0]?.text ?? '');
180
198
  expect(response.results[0]?.summary.storeName).toBe('Test Store');
181
199
  });
182
200
 
@@ -186,11 +204,12 @@ describe('Search Handlers', () => {
186
204
  mockContext
187
205
  );
188
206
 
189
- const response = JSON.parse(result.content[0]?.text ?? '{}');
207
+ const { header, json: response } = parseSearchResponse(result.content[0]?.text ?? '');
190
208
  expect(response).toHaveProperty('totalResults', 1);
191
- expect(response).toHaveProperty('estimatedTokens');
192
209
  expect(response).toHaveProperty('mode', 'hybrid');
193
210
  expect(response).toHaveProperty('timeMs', 50);
211
+ // Token count is now in header, not in JSON
212
+ expect(header).toContain('tokens');
194
213
  });
195
214
  });
196
215
 
@@ -4,6 +4,10 @@ import { SearchArgsSchema, GetFullContextArgsSchema } from '../schemas/index.js'
4
4
  import type { SearchQuery, DocumentId, StoreId } from '../../types/index.js';
5
5
  import { LRUCache } from '../cache.js';
6
6
  import type { SearchResult } from '../../types/search.js';
7
+ import { createLogger, summarizePayload } from '../../logging/index.js';
8
+ import { estimateTokens, formatTokenCount } from '../../services/token.service.js';
9
+
10
+ const logger = createLogger('mcp-search');
7
11
 
8
12
  // Create result cache for get_full_context
9
13
  // Uses LRU cache to prevent memory leaks (max 1000 items)
@@ -22,6 +26,14 @@ export const handleSearch: ToolHandler<SearchArgs> = async (
22
26
  // Validate arguments with Zod
23
27
  const validated = SearchArgsSchema.parse(args);
24
28
 
29
+ logger.info({
30
+ query: validated.query,
31
+ stores: validated.stores,
32
+ detail: validated.detail,
33
+ limit: validated.limit,
34
+ intent: validated.intent,
35
+ }, 'Search started');
36
+
25
37
  const { services } = context;
26
38
 
27
39
  // Get all stores if none specified, resolve store names to IDs
@@ -63,14 +75,6 @@ export const handleSearch: ToolHandler<SearchArgs> = async (
63
75
  resultCache.set(result.id, result);
64
76
  }
65
77
 
66
- // Calculate estimated tokens
67
- const estimatedTokens = results.results.reduce((sum, r) => {
68
- let tokens = 100; // Base for summary
69
- if (r.context) tokens += 200;
70
- if (r.full) tokens += 800;
71
- return sum + tokens;
72
- }, 0);
73
-
74
78
  // Add repoRoot to results for cloned repos
75
79
  const enhancedResults = await Promise.all(results.results.map(async (r) => {
76
80
  const storeId = r.metadata.storeId;
@@ -89,17 +93,33 @@ export const handleSearch: ToolHandler<SearchArgs> = async (
89
93
  };
90
94
  }));
91
95
 
96
+ const responseJson = JSON.stringify({
97
+ results: enhancedResults,
98
+ totalResults: results.totalResults,
99
+ mode: results.mode,
100
+ timeMs: results.timeMs
101
+ }, null, 2);
102
+
103
+ // Calculate actual token estimate based on response content
104
+ const responseTokens = estimateTokens(responseJson);
105
+
106
+ // Create visible header with token usage
107
+ const header = `Search: "${validated.query}" | Results: ${String(results.totalResults)} | ${formatTokenCount(responseTokens)} tokens | ${String(results.timeMs)}ms\n\n`;
108
+
109
+ // Log the complete MCP response that will be sent to Claude Code
110
+ logger.info({
111
+ query: validated.query,
112
+ totalResults: results.totalResults,
113
+ responseTokens,
114
+ timeMs: results.timeMs,
115
+ ...summarizePayload(responseJson, 'mcp-response', validated.query),
116
+ }, 'Search complete - context sent to Claude Code');
117
+
92
118
  return {
93
119
  content: [
94
120
  {
95
121
  type: 'text',
96
- text: JSON.stringify({
97
- results: enhancedResults,
98
- totalResults: results.totalResults,
99
- estimatedTokens,
100
- mode: results.mode,
101
- timeMs: results.timeMs
102
- }, null, 2)
122
+ text: header + responseJson
103
123
  }
104
124
  ]
105
125
  };
@@ -118,6 +138,8 @@ export const handleGetFullContext: ToolHandler<GetFullContextArgs> = async (
118
138
  // Validate arguments with Zod
119
139
  const validated = GetFullContextArgsSchema.parse(args);
120
140
 
141
+ logger.info({ resultId: validated.resultId }, 'Get full context requested');
142
+
121
143
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
122
144
  const resultId = validated.resultId as DocumentId;
123
145
 
@@ -132,17 +154,26 @@ export const handleGetFullContext: ToolHandler<GetFullContextArgs> = async (
132
154
 
133
155
  // If result already has full context, return it
134
156
  if (cachedResult.full) {
157
+ const responseJson = JSON.stringify({
158
+ id: cachedResult.id,
159
+ score: cachedResult.score,
160
+ summary: cachedResult.summary,
161
+ context: cachedResult.context,
162
+ full: cachedResult.full
163
+ }, null, 2);
164
+
165
+ logger.info({
166
+ resultId,
167
+ cached: true,
168
+ hasFullContext: true,
169
+ ...summarizePayload(responseJson, 'mcp-full-context', resultId),
170
+ }, 'Full context retrieved from cache');
171
+
135
172
  return {
136
173
  content: [
137
174
  {
138
175
  type: 'text',
139
- text: JSON.stringify({
140
- id: cachedResult.id,
141
- score: cachedResult.score,
142
- summary: cachedResult.summary,
143
- context: cachedResult.context,
144
- full: cachedResult.full
145
- }, null, 2)
176
+ text: responseJson
146
177
  }
147
178
  ]
148
179
  };
@@ -192,17 +223,26 @@ export const handleGetFullContext: ToolHandler<GetFullContextArgs> = async (
192
223
  // Update cache with full result
193
224
  resultCache.set(resultId, fullResult);
194
225
 
226
+ const responseJson = JSON.stringify({
227
+ id: fullResult.id,
228
+ score: fullResult.score,
229
+ summary: fullResult.summary,
230
+ context: fullResult.context,
231
+ full: fullResult.full
232
+ }, null, 2);
233
+
234
+ logger.info({
235
+ resultId,
236
+ cached: false,
237
+ hasFullContext: true,
238
+ ...summarizePayload(responseJson, 'mcp-full-context', resultId),
239
+ }, 'Full context retrieved via re-query');
240
+
195
241
  return {
196
242
  content: [
197
243
  {
198
244
  type: 'text',
199
- text: JSON.stringify({
200
- id: fullResult.id,
201
- score: fullResult.score,
202
- summary: fullResult.summary,
203
- context: fullResult.context,
204
- full: fullResult.full
205
- }, null, 2)
245
+ text: responseJson
206
246
  }
207
247
  ]
208
248
  };
@@ -411,5 +411,6 @@ describe('store.handler', () => {
411
411
  const data = JSON.parse(result.content[0].text);
412
412
  expect(data.store.type).toBe('file');
413
413
  });
414
+
414
415
  });
415
416
  });
package/src/mcp/server.ts CHANGED
@@ -9,6 +9,9 @@ import { tools } from './handlers/index.js';
9
9
  import { handleExecute } from './handlers/execute.handler.js';
10
10
  import { ExecuteArgsSchema } from './schemas/index.js';
11
11
  import type { MCPServerOptions } from './types.js';
12
+ import { createLogger } from '../logging/index.js';
13
+
14
+ const logger = createLogger('mcp-server');
12
15
 
13
16
  // eslint-disable-next-line @typescript-eslint/no-deprecated
14
17
  export function createMCPServer(options: MCPServerOptions): Server {
@@ -106,6 +109,9 @@ export function createMCPServer(options: MCPServerOptions): Server {
106
109
  // Handle tool calls
107
110
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
108
111
  const { name, arguments: args } = request.params;
112
+ const startTime = Date.now();
113
+
114
+ logger.info({ tool: name, args: JSON.stringify(args) }, 'Tool invoked');
109
115
 
110
116
  // Create services once (needed by all handlers)
111
117
  const services = await createServices(
@@ -115,34 +121,56 @@ export function createMCPServer(options: MCPServerOptions): Server {
115
121
  );
116
122
  const context = { services, options };
117
123
 
118
- // Handle execute meta-tool
119
- if (name === 'execute') {
120
- const validated = ExecuteArgsSchema.parse(args ?? {});
121
- return handleExecute(validated, context);
122
- }
124
+ try {
125
+ let result;
123
126
 
124
- // Find handler in registry for native tools (search, get_full_context)
125
- const tool = tools.find(t => t.name === name);
126
- if (tool === undefined) {
127
- throw new Error(`Unknown tool: ${name}`);
128
- }
127
+ // Handle execute meta-tool
128
+ if (name === 'execute') {
129
+ const validated = ExecuteArgsSchema.parse(args ?? {});
130
+ result = await handleExecute(validated, context);
131
+ } else {
132
+ // Find handler in registry for native tools (search, get_full_context)
133
+ const tool = tools.find(t => t.name === name);
134
+ if (tool === undefined) {
135
+ throw new Error(`Unknown tool: ${name}`);
136
+ }
129
137
 
130
- // Validate arguments with Zod
131
- const validated = tool.schema.parse(args ?? {});
138
+ // Validate arguments with Zod
139
+ const validated = tool.schema.parse(args ?? {});
132
140
 
133
- // Execute handler with context
134
- return tool.handler(validated, context);
141
+ // Execute handler with context
142
+ result = await tool.handler(validated, context);
143
+ }
144
+
145
+ const durationMs = Date.now() - startTime;
146
+ logger.info({ tool: name, durationMs }, 'Tool completed');
147
+
148
+ return result;
149
+ } catch (error) {
150
+ const durationMs = Date.now() - startTime;
151
+ logger.error({
152
+ tool: name,
153
+ durationMs,
154
+ error: error instanceof Error ? error.message : String(error),
155
+ }, 'Tool execution failed');
156
+ throw error;
157
+ }
135
158
  });
136
159
 
137
160
  return server;
138
161
  }
139
162
 
140
163
  export async function runMCPServer(options: MCPServerOptions): Promise<void> {
164
+ logger.info({
165
+ dataDir: options.dataDir,
166
+ projectRoot: options.projectRoot,
167
+ }, 'MCP server starting');
168
+
141
169
  const server = createMCPServer(options);
142
170
  const transport = new StdioServerTransport();
143
171
  await server.connect(transport);
144
172
 
145
- console.error('Bluera Knowledge MCP server running on stdio');
173
+ logger.info('MCP server connected to stdio transport');
146
174
  }
147
175
 
148
176
  // Run the server only when this file is executed directly (not imported by CLI)
@@ -156,7 +184,7 @@ if (isMCPServerEntry) {
156
184
  config: process.env['CONFIG_PATH'],
157
185
  projectRoot: process.env['PROJECT_ROOT'] ?? process.env['PWD']
158
186
  }).catch((error: unknown) => {
159
- console.error('Failed to start MCP server:', error);
187
+ logger.error({ error: error instanceof Error ? error.message : String(error) }, 'Failed to start MCP server');
160
188
  process.exit(1);
161
189
  });
162
190
  }
@@ -17,6 +17,19 @@ export interface Chunk {
17
17
  docSummary?: string | undefined;
18
18
  }
19
19
 
20
+ /**
21
+ * Preset configurations for different content types.
22
+ * Code uses smaller chunks for precise symbol matching.
23
+ * Web/docs use larger chunks to preserve prose context.
24
+ */
25
+ const CHUNK_PRESETS = {
26
+ code: { chunkSize: 768, chunkOverlap: 100 },
27
+ web: { chunkSize: 1200, chunkOverlap: 200 },
28
+ docs: { chunkSize: 1200, chunkOverlap: 200 },
29
+ } as const;
30
+
31
+ export type ContentType = keyof typeof CHUNK_PRESETS;
32
+
20
33
  export class ChunkingService {
21
34
  private readonly chunkSize: number;
22
35
  private readonly chunkOverlap: number;
@@ -26,6 +39,16 @@ export class ChunkingService {
26
39
  this.chunkOverlap = config.chunkOverlap;
27
40
  }
28
41
 
42
+ /**
43
+ * Create a ChunkingService with preset configuration for a content type.
44
+ * - 'code': Smaller chunks (768/100) for precise code symbol matching
45
+ * - 'web': Larger chunks (1200/200) for web prose content
46
+ * - 'docs': Larger chunks (1200/200) for documentation
47
+ */
48
+ static forContentType(type: ContentType): ChunkingService {
49
+ return new ChunkingService(CHUNK_PRESETS[type]);
50
+ }
51
+
29
52
  /**
30
53
  * Chunk text content. Uses semantic chunking for Markdown and code files,
31
54
  * falling back to sliding window for other content.