rea-agents 0.2.1 → 0.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 (122) hide show
  1. package/README.md +124 -20
  2. package/bridge/hopper_bridge.py +141 -13
  3. package/dist/application/AnalysisProvider.js +12 -1
  4. package/dist/application/ArtifactExtraction.js +166 -0
  5. package/dist/application/ArtifactGraphConstruction.js +257 -0
  6. package/dist/application/ArtifactInventory.js +253 -0
  7. package/dist/application/BinarySession.js +164 -14
  8. package/dist/application/CompositeProvider.js +73 -0
  9. package/dist/application/DirectAnalysis.js +27 -3
  10. package/dist/application/Doctor.js +35 -6
  11. package/dist/application/EnhancedTools.js +8 -7
  12. package/dist/application/EvidenceBundleCommands.js +29 -0
  13. package/dist/application/EvidenceBundleFiles.js +127 -0
  14. package/dist/application/EvidenceLedger.js +225 -18
  15. package/dist/application/FilesystemSnapshot.js +124 -0
  16. package/dist/application/LinuxHopper.js +186 -0
  17. package/dist/application/LoopbackReplay.js +195 -37
  18. package/dist/application/ProcessHarness.js +200 -191
  19. package/dist/application/ProcessNormalization.js +44 -0
  20. package/dist/application/ProcessOwnership.js +84 -0
  21. package/dist/application/ProcessSampling.js +284 -0
  22. package/dist/application/RealHopperAssertions.js +105 -0
  23. package/dist/application/ReferenceSourceImport.js +182 -0
  24. package/dist/application/ReferenceSourceImportEntries.js +122 -0
  25. package/dist/application/ReferenceSourceImportPolicy.js +73 -0
  26. package/dist/application/ReferenceSourceImportTypes.js +18 -0
  27. package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
  28. package/dist/application/Setup.js +192 -41
  29. package/dist/application/Uninstall.js +130 -0
  30. package/dist/application/runtime.js +8 -1
  31. package/dist/artifacts/ArtifactPaths.js +51 -0
  32. package/dist/artifacts/ArtifactProvider.js +130 -0
  33. package/dist/artifacts/ArtifactReader.js +9 -0
  34. package/dist/artifacts/AsarArtifactReader.js +62 -0
  35. package/dist/artifacts/DirectoryArtifactReader.js +107 -0
  36. package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
  37. package/dist/artifacts/SafeOutputTree.js +199 -0
  38. package/dist/artifacts/StreamBytes.js +10 -0
  39. package/dist/artifacts/ZipArtifactReader.js +109 -0
  40. package/dist/cli.js +153 -6
  41. package/dist/config.js +35 -1
  42. package/dist/contracts/artifactComparisonExample.js +95 -0
  43. package/dist/contracts/artifactToolContracts.js +84 -0
  44. package/dist/contracts/enhancedInputs.js +4 -0
  45. package/dist/contracts/functionComparisonExample.js +57 -0
  46. package/dist/contracts/investigationExamples.js +118 -0
  47. package/dist/contracts/nativeToolContracts.js +52 -0
  48. package/dist/contracts/processCaptureExample.js +25 -0
  49. package/dist/contracts/toolContractExamples.js +69 -0
  50. package/dist/contracts/toolContracts.js +99 -36
  51. package/dist/contracts/toolOutputSchemas.js +154 -82
  52. package/dist/contracts/unknownContractExamples.js +33 -0
  53. package/dist/domain/artifactComparison.js +273 -0
  54. package/dist/domain/artifactGraph.js +194 -0
  55. package/dist/domain/artifactInventoryEvidence.js +150 -0
  56. package/dist/domain/binaryTarget.js +55 -0
  57. package/dist/domain/bundleComparison.js +266 -0
  58. package/dist/domain/callPath.js +346 -0
  59. package/dist/domain/changedBehavior.js +294 -0
  60. package/dist/domain/errors.js +192 -4
  61. package/dist/domain/evidence.js +41 -10
  62. package/dist/domain/evidenceBundle.js +187 -6
  63. package/dist/domain/functionComparison.js +201 -0
  64. package/dist/domain/functionComparisonNormalization.js +112 -0
  65. package/dist/domain/functionComparisonResults.js +54 -0
  66. package/dist/domain/functionComparisonSchemas.js +82 -0
  67. package/dist/domain/functionDossierEvidence.js +171 -0
  68. package/dist/domain/hopperValues.js +14 -7
  69. package/dist/domain/nativeInspection.js +142 -0
  70. package/dist/domain/processCapture.js +152 -54
  71. package/dist/domain/processComparison.js +106 -0
  72. package/dist/domain/reconstructionUnknowns.js +90 -0
  73. package/dist/domain/reconstructionVerification.js +285 -0
  74. package/dist/domain/reconstructionVerificationSchemas.js +126 -0
  75. package/dist/domain/referenceSourceClassification.js +496 -0
  76. package/dist/domain/referenceSourceGraph.js +376 -0
  77. package/dist/domain/referenceSourceImportParsing.js +235 -0
  78. package/dist/domain/residualUnknown.js +239 -0
  79. package/dist/domain/staticRuntimeCorrelation.js +375 -0
  80. package/dist/hopper/BridgeLauncher.js +40 -3
  81. package/dist/hopper/HopperClient.js +14 -5
  82. package/dist/hopper/HopperProvider.js +57 -22
  83. package/dist/identity.js +1 -0
  84. package/dist/main.js +5 -1
  85. package/dist/native/CommandRunner.js +156 -0
  86. package/dist/native/NativeMacOSProvider.js +306 -0
  87. package/dist/native/NativeMachoInspection.js +135 -0
  88. package/dist/native/parsers/codesign.js +55 -0
  89. package/dist/native/parsers/demangle.js +26 -0
  90. package/dist/native/parsers/dyldInfo.js +25 -0
  91. package/dist/native/parsers/lipo.js +67 -0
  92. package/dist/native/parsers/otool.js +193 -0
  93. package/dist/native/parsers/plist.js +23 -0
  94. package/dist/reference/ReferenceSourceReader.js +73 -0
  95. package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
  96. package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
  97. package/dist/reference/ReferenceSourceReaderFile.js +119 -0
  98. package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
  99. package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
  100. package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
  101. package/dist/server/createServer.js +31 -5
  102. package/dist/server/recordDerivedEvidence.js +10 -0
  103. package/dist/server/registerArtifactComparisonTool.js +62 -0
  104. package/dist/server/registerArtifactTools.js +6 -0
  105. package/dist/server/registerBundleComparisonTool.js +47 -0
  106. package/dist/server/registerEnhancedTools.js +64 -12
  107. package/dist/server/registerEvidenceTools.js +36 -0
  108. package/dist/server/registerFunctionComparisonTool.js +68 -0
  109. package/dist/server/registerInvestigationTools.js +224 -0
  110. package/dist/server/registerNativeTools.js +6 -0
  111. package/dist/server/registerOfficialTools.js +47 -14
  112. package/dist/server/registerProcessComparisonTool.js +106 -0
  113. package/dist/server/registerSessionTools.js +179 -70
  114. package/dist/server/sessionEvidence.js +28 -0
  115. package/dist/server/sessionToolPolicies.js +64 -0
  116. package/dist/server/toolRegistrationOptions.js +7 -0
  117. package/dist/server/toolResult.js +8 -5
  118. package/install.sh +198 -0
  119. package/package.json +19 -2
  120. package/scripts/rea.mjs +5 -1
  121. package/skills/rea-analysis/SKILL.md +77 -2
  122. /package/dist/{application/HopperToolPort.js → domain/referenceSourcePolicy.js} +0 -0
@@ -0,0 +1,496 @@
1
+ import { posix } from "node:path";
2
+ const SOURCE_CLASSIFICATIONS = [
3
+ "source",
4
+ "test",
5
+ "config",
6
+ "manifest",
7
+ "generated",
8
+ "vendor",
9
+ "documentation",
10
+ "unknown",
11
+ ];
12
+ const CODE_EXTENSIONS = new Set([
13
+ "js",
14
+ "mjs",
15
+ "cjs",
16
+ "jsx",
17
+ "ts",
18
+ "tsx",
19
+ "mts",
20
+ "cts",
21
+ "py",
22
+ "rs",
23
+ "go",
24
+ "java",
25
+ "c",
26
+ "h",
27
+ "cpp",
28
+ "hpp",
29
+ "cc",
30
+ "cxx",
31
+ "swift",
32
+ "m",
33
+ "mm",
34
+ "sh",
35
+ ]);
36
+ const LANGUAGE_BY_EXTENSION = new Map([
37
+ ["js", "JavaScript"],
38
+ ["mjs", "JavaScript"],
39
+ ["cjs", "JavaScript"],
40
+ ["jsx", "JSX"],
41
+ ["ts", "TypeScript"],
42
+ ["tsx", "TSX"],
43
+ ["mts", "TypeScript"],
44
+ ["cts", "TypeScript"],
45
+ ["py", "Python"],
46
+ ["rs", "Rust"],
47
+ ["go", "Go"],
48
+ ["java", "Java"],
49
+ ["c", "C"],
50
+ ["h", "C"],
51
+ ["cpp", "C++"],
52
+ ["hpp", "C++"],
53
+ ["cc", "C++"],
54
+ ["cxx", "C++"],
55
+ ["swift", "Swift"],
56
+ ["m", "Objective-C"],
57
+ ["mm", "Objective-C++"],
58
+ ["sh", "Shell"],
59
+ ["json", "JSON"],
60
+ ["jsonc", "JSON"],
61
+ ["yaml", "YAML"],
62
+ ["yml", "YAML"],
63
+ ["toml", "TOML"],
64
+ ["ini", "INI"],
65
+ ["cfg", "INI"],
66
+ ["conf", "INI"],
67
+ ["md", "Markdown"],
68
+ ["markdown", "Markdown"],
69
+ ["txt", "Text"],
70
+ ["html", "HTML"],
71
+ ["htm", "HTML"],
72
+ ["css", "CSS"],
73
+ ["scss", "SCSS"],
74
+ ["sass", "Sass"],
75
+ ["less", "Less"],
76
+ ["xml", "XML"],
77
+ ["svg", "SVG"],
78
+ ]);
79
+ const MANIFEST_FILENAMES = new Set([
80
+ "package.json",
81
+ "package-lock.json",
82
+ "yarn.lock",
83
+ "pnpm-lock.yaml",
84
+ "npm-shrinkwrap.json",
85
+ "composer.json",
86
+ "composer.lock",
87
+ "gemfile",
88
+ "gemfile.lock",
89
+ "cargo.toml",
90
+ "cargo.lock",
91
+ "go.mod",
92
+ "go.sum",
93
+ "pom.xml",
94
+ "build.gradle",
95
+ "build.gradle.kts",
96
+ "settings.gradle",
97
+ "requirements.txt",
98
+ "setup.py",
99
+ "pyproject.toml",
100
+ "pipfile",
101
+ "pipfile.lock",
102
+ "setup.cfg",
103
+ "manifest.in",
104
+ "CMakeLists.txt",
105
+ "meson.build",
106
+ "configure",
107
+ "configure.ac",
108
+ "makefile",
109
+ "makefile.am",
110
+ "dockerfile",
111
+ "docker-compose.yml",
112
+ "docker-compose.yaml",
113
+ "kubernetes.yml",
114
+ "kubernetes.yaml",
115
+ "kustomization.yml",
116
+ "kustomization.yaml",
117
+ ".releaserc",
118
+ ".releaserc.json",
119
+ ".releaserc.yml",
120
+ ".releaserc.yaml",
121
+ "release-please-config.json",
122
+ "release-please-manifest.json",
123
+ ]);
124
+ const CONFIG_FILENAMES = new Set([
125
+ ".gitignore",
126
+ ".gitattributes",
127
+ ".gitmodules",
128
+ ".editorconfig",
129
+ ".npmrc",
130
+ ".nvmrc",
131
+ ".yarnrc",
132
+ ".yarnrc.yml",
133
+ "eslint.config.js",
134
+ "eslint.config.mjs",
135
+ "eslint.config.ts",
136
+ ".prettierignore",
137
+ ".prettierrc",
138
+ ".prettierrc.json",
139
+ ".oxlintrc.json",
140
+ "tsconfig.json",
141
+ "tsconfig.build.json",
142
+ "jsconfig.json",
143
+ "vitest.config.ts",
144
+ "wrangler.json",
145
+ "wrangler.jsonc",
146
+ "dependabot.yml",
147
+ ".github",
148
+ ".husky",
149
+ ]);
150
+ const DOCUMENTATION_EXTENSIONS = new Set([
151
+ "md",
152
+ "markdown",
153
+ "mdx",
154
+ "rst",
155
+ "txt",
156
+ "adoc",
157
+ "asciidoc",
158
+ ]);
159
+ const DOCUMENTATION_FILENAMES = new Set([
160
+ "readme",
161
+ "readme.md",
162
+ "readme.markdown",
163
+ "readme.txt",
164
+ "changelog",
165
+ "changelog.md",
166
+ "changes.md",
167
+ "history.md",
168
+ "news.md",
169
+ "license",
170
+ "license.md",
171
+ "license.txt",
172
+ "copying",
173
+ "copying.md",
174
+ "authors",
175
+ "authors.md",
176
+ "contributing",
177
+ "contributing.md",
178
+ "code_of_conduct",
179
+ "code_of_conduct.md",
180
+ "security",
181
+ "security.md",
182
+ "support",
183
+ "support.md",
184
+ "faq",
185
+ "faq.md",
186
+ ]);
187
+ const GENERATED_DIRECTORY_NAMES = new Set([
188
+ "dist",
189
+ "build",
190
+ "out",
191
+ "target",
192
+ "coverage",
193
+ "htmlcov",
194
+ "site",
195
+ "public",
196
+ ".next",
197
+ ".nuxt",
198
+ ".svelte-kit",
199
+ ".astro",
200
+ "storybook-static",
201
+ "docs/api",
202
+ ".codex",
203
+ ".devin",
204
+ ".factory",
205
+ "__pycache__",
206
+ ".pytest_cache",
207
+ ".mypy_cache",
208
+ ".ruff_cache",
209
+ ".tox",
210
+ ".nyc_output",
211
+ ]);
212
+ const VENDOR_DIRECTORY_NAMES = new Set([
213
+ "node_modules",
214
+ "vendor",
215
+ "bower_components",
216
+ "third_party",
217
+ "third-party",
218
+ "thirdparty",
219
+ "libs",
220
+ "external",
221
+ ]);
222
+ const SOURCE_DIRECTORY_NAMES = new Set([
223
+ "src",
224
+ "source",
225
+ "sources",
226
+ "lib",
227
+ "libs",
228
+ "packages",
229
+ "pkg",
230
+ "app",
231
+ "apps",
232
+ "bin",
233
+ "scripts",
234
+ "bridge",
235
+ "components",
236
+ "domain",
237
+ "application",
238
+ "server",
239
+ "contracts",
240
+ "hopper",
241
+ "tests",
242
+ "test",
243
+ "__tests__",
244
+ "spec",
245
+ "specs",
246
+ "testing",
247
+ "fixtures",
248
+ ]);
249
+ const TEST_DIRECTORY_NAMES = new Set([
250
+ "tests",
251
+ "test",
252
+ "__tests__",
253
+ "spec",
254
+ "specs",
255
+ "testing",
256
+ ]);
257
+ const DOCUMENTATION_DIRECTORY_NAMES = new Set([
258
+ "docs",
259
+ "doc",
260
+ "documentation",
261
+ "guides",
262
+ "examples",
263
+ "tutorials",
264
+ "wiki",
265
+ "man",
266
+ ]);
267
+ const SECRET_BASENAMES = new Set([
268
+ "id_rsa",
269
+ "id_ed25519",
270
+ "pgpass",
271
+ ".netrc",
272
+ ".npmrc",
273
+ ".pypirc",
274
+ ".envrc",
275
+ ".env.local",
276
+ ".env.production",
277
+ ".env.development",
278
+ ".env.test",
279
+ ".env.staging",
280
+ ".env.example",
281
+ "auth.json",
282
+ "auth.yaml",
283
+ "auth.yml",
284
+ "token.json",
285
+ "tokens.json",
286
+ "credentials.json",
287
+ "secrets.json",
288
+ "google-services.json",
289
+ "service-account.json",
290
+ ".htpasswd",
291
+ ]);
292
+ const SECRET_BASENAME_PREFIXES = [
293
+ ".env",
294
+ ".env.",
295
+ "private",
296
+ "secret",
297
+ "secrets",
298
+ "credential",
299
+ "credentials",
300
+ "token",
301
+ "tokens",
302
+ "password",
303
+ "passwords",
304
+ "auth",
305
+ "apikey",
306
+ "api-key",
307
+ "api_key",
308
+ "service-account",
309
+ "kubeconfig",
310
+ ];
311
+ const SECRET_BASENAME_SUFFIXES = [
312
+ ".pem",
313
+ ".key",
314
+ ".p12",
315
+ ".gpg",
316
+ ".pgp",
317
+ ".keystore",
318
+ ];
319
+ const SECRET_PATH_SEGMENTS = new Set([
320
+ ".ssh",
321
+ ".aws",
322
+ ".gnupg",
323
+ ".pki",
324
+ ".docker",
325
+ ".secrets",
326
+ "secrets",
327
+ "secret",
328
+ "private",
329
+ "credentials",
330
+ "tokens",
331
+ "passwords",
332
+ "passphrase",
333
+ "kubernetes",
334
+ "k8s",
335
+ ]);
336
+ const filenameIsTest = (filename) => {
337
+ const lower = filename.toLowerCase();
338
+ if (lower.includes(".test.") || lower.includes(".spec."))
339
+ return true;
340
+ if (lower.startsWith("test_") || lower.startsWith("spec_"))
341
+ return true;
342
+ if (lower.endsWith("_test") || lower.endsWith("_spec"))
343
+ return true;
344
+ return false;
345
+ };
346
+ const filenameIsGenerated = (filename) => {
347
+ const lower = filename.toLowerCase();
348
+ if (lower.endsWith(".min.js") || lower.endsWith(".min.css"))
349
+ return true;
350
+ if (lower.endsWith(".map"))
351
+ return true;
352
+ if (lower.endsWith(".lock"))
353
+ return true;
354
+ if (lower.endsWith(".d.ts") || lower.endsWith(".d.ts.map"))
355
+ return true;
356
+ if (lower.endsWith(".d.mts") || lower.endsWith(".d.mts.map"))
357
+ return true;
358
+ if (lower.endsWith(".d.cts") || lower.endsWith(".d.cts.map"))
359
+ return true;
360
+ return false;
361
+ };
362
+ const basenameParts = (filename) => {
363
+ const lastDot = filename.lastIndexOf(".");
364
+ if (lastDot <= 0)
365
+ return { base: filename, ext: "" };
366
+ return { base: filename.slice(0, lastDot), ext: filename.slice(lastDot + 1) };
367
+ };
368
+ const basenameMatch = (filename, set) => {
369
+ const lower = filename.toLowerCase();
370
+ return set.has(lower);
371
+ };
372
+ const isPathUnderAny = (path, names) => {
373
+ for (const segment of path.split("/")) {
374
+ if (segment === "")
375
+ continue;
376
+ if (names.has(segment.toLowerCase()))
377
+ return true;
378
+ }
379
+ return false;
380
+ };
381
+ const secretPrefixMatches = (filename) => {
382
+ const lower = filename.toLowerCase();
383
+ for (const prefix of SECRET_BASENAME_PREFIXES) {
384
+ const prefixLower = prefix.toLowerCase();
385
+ if (!lower.startsWith(prefixLower))
386
+ continue;
387
+ const nextIndex = prefixLower.length;
388
+ if (nextIndex < lower.length) {
389
+ const next = lower[nextIndex];
390
+ if (next !== "." && next !== "_" && next !== "-")
391
+ continue;
392
+ }
393
+ const extension = basenameParts(filename).ext.toLowerCase();
394
+ if (CODE_EXTENSIONS.has(extension))
395
+ continue;
396
+ return true;
397
+ }
398
+ return false;
399
+ };
400
+ /** Return true when a path or any parent segment matches common secret patterns. */
401
+ export const isSecretLikePath = (path) => {
402
+ const parts = path.split("/").filter((segment) => segment !== "");
403
+ for (const segment of parts) {
404
+ const lower = segment.toLowerCase();
405
+ if (SECRET_PATH_SEGMENTS.has(lower))
406
+ return true;
407
+ }
408
+ const filename = parts[parts.length - 1];
409
+ if (filename === undefined)
410
+ return false;
411
+ const lower = filename.toLowerCase();
412
+ if (SECRET_BASENAMES.has(lower))
413
+ return true;
414
+ if (secretPrefixMatches(filename))
415
+ return true;
416
+ for (const suffix of SECRET_BASENAME_SUFFIXES) {
417
+ if (lower.endsWith(suffix.toLowerCase()))
418
+ return true;
419
+ }
420
+ return false;
421
+ };
422
+ /** Detect a source language from a POSIX path. */
423
+ export const detectReferenceSourceLanguage = (path) => {
424
+ const filename = posix.basename(path);
425
+ if (filename === "Dockerfile" || filename.startsWith("Dockerfile."))
426
+ return "Dockerfile";
427
+ if (filename.toLowerCase().startsWith("dockerfile"))
428
+ return "Dockerfile";
429
+ const lower = filename.toLowerCase();
430
+ if (lower.endsWith(".d.ts"))
431
+ return "TypeScript";
432
+ if (lower.endsWith(".d.mts"))
433
+ return "TypeScript";
434
+ if (lower.endsWith(".d.cts"))
435
+ return "TypeScript";
436
+ const parts = basenameParts(filename);
437
+ const extension = parts.ext.toLowerCase();
438
+ return LANGUAGE_BY_EXTENSION.get(extension) ?? null;
439
+ };
440
+ /** Classify a reference source path deterministically. */
441
+ export const classifyReferenceSourcePath = (path) => {
442
+ const classifications = new Set();
443
+ const parts = path.split("/").filter((segment) => segment !== "");
444
+ const filename = parts[parts.length - 1] ?? "";
445
+ const lower = filename.toLowerCase();
446
+ if (parts.length === 0) {
447
+ classifications.add("source");
448
+ return [...classifications].sort();
449
+ }
450
+ addDirectoryClassifications(path, classifications);
451
+ addFilenameClassifications(filename, classifications);
452
+ if (lower.endsWith(".json") ||
453
+ lower.endsWith(".yaml") ||
454
+ lower.endsWith(".yml")) {
455
+ if (!classifications.has("manifest") && !classifications.has("config"))
456
+ classifications.add("config");
457
+ }
458
+ if (isPathUnderAny(path, SOURCE_DIRECTORY_NAMES)) {
459
+ if (!classifications.has("vendor") && !classifications.has("generated"))
460
+ classifications.add("source");
461
+ }
462
+ if (classifications.size === 0) {
463
+ if (lower === "license" || lower === "copying")
464
+ classifications.add("documentation");
465
+ else
466
+ classifications.add("unknown");
467
+ }
468
+ return [...classifications].sort();
469
+ };
470
+ const addDirectoryClassifications = (path, classifications) => {
471
+ if (isPathUnderAny(path, TEST_DIRECTORY_NAMES))
472
+ classifications.add("test");
473
+ if (isPathUnderAny(path, DOCUMENTATION_DIRECTORY_NAMES))
474
+ classifications.add("documentation");
475
+ if (isPathUnderAny(path, VENDOR_DIRECTORY_NAMES))
476
+ classifications.add("vendor");
477
+ if (isPathUnderAny(path, GENERATED_DIRECTORY_NAMES))
478
+ classifications.add("generated");
479
+ };
480
+ const addFilenameClassifications = (filename, classifications) => {
481
+ const extension = basenameParts(filename).ext.toLowerCase();
482
+ if (filenameIsTest(filename))
483
+ classifications.add("test");
484
+ if (filenameIsGenerated(filename))
485
+ classifications.add("generated");
486
+ if (basenameMatch(filename, MANIFEST_FILENAMES))
487
+ classifications.add("manifest");
488
+ if (basenameMatch(filename, CONFIG_FILENAMES))
489
+ classifications.add("config");
490
+ if (DOCUMENTATION_EXTENSIONS.has(extension))
491
+ classifications.add("documentation");
492
+ if (DOCUMENTATION_FILENAMES.has(filename.toLowerCase()))
493
+ classifications.add("documentation");
494
+ if (CODE_EXTENSIONS.has(extension))
495
+ classifications.add("source");
496
+ };