@goshenkata/dryscan-core 1.0.16 → 1.1.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.
package/README.md CHANGED
@@ -72,21 +72,20 @@ Place a `dryconfig.json` in your repository root:
72
72
  "threshold": 0.88,
73
73
  "minLines": 5,
74
74
  "minBlockLines": 8,
75
- "embeddingModel": "embeddinggemma",
76
- "embeddingSource": "http://localhost:11434",
75
+ "embeddingSource": "huggingface",
77
76
  "excludedPaths": ["**/test/**", "**/node_modules/**"],
78
77
  "excludedPairs": []
79
78
  }
80
79
  ```
81
80
 
82
81
  **Supported Embedding Providers:**
83
- - **Ollama** (default): Set `embeddingSource` to `"http://localhost:11434"` and `embeddingModel` to `"embeddinggemma"`
84
- - **Google Gemini**: Set `embeddingSource` to `"google"` and `embeddingModel` to `"gemini-embedding-001"` (requires `GOOGLE_API_KEY` env var)
82
+ - **HuggingFace** (default): Set `embeddingSource` to `"huggingface"` (requires `HUGGINGFACEHUB_API_KEY` env var)
83
+ - **Ollama** (local): Set `embeddingSource` to an Ollama URL like `"http://localhost:11434"`
85
84
 
86
85
  ## Requirements
87
86
 
88
87
  - Node.js >= 18.0.0
89
- - Ollama running locally (default) or Google API key for embeddings
88
+ - HuggingFace API key (default) or Ollama running locally for embeddings
90
89
 
91
90
  ## Supported languages**
92
91
 
package/dist/index.d.ts CHANGED
@@ -41,8 +41,7 @@ interface DryConfig {
41
41
  minLines: number;
42
42
  minBlockLines: number;
43
43
  threshold: number;
44
- embeddingModel: string;
45
- embeddingSource?: string;
44
+ embeddingSource: string;
46
45
  contextLength: number;
47
46
  }
48
47
  interface IndexUnit {
package/dist/index.js CHANGED
@@ -62,7 +62,6 @@ var DEFAULT_CONFIG = {
62
62
  minLines: 3,
63
63
  minBlockLines: 5,
64
64
  threshold: 0.88,
65
- embeddingModel: "embeddinggemma",
66
65
  embeddingSource: "http://localhost:11434",
67
66
  contextLength: 2048
68
67
  };
@@ -75,7 +74,6 @@ var partialConfigSchema = {
75
74
  minLines: { type: "number" },
76
75
  minBlockLines: { type: "number" },
77
76
  threshold: { type: "number" },
78
- embeddingModel: { type: "string" },
79
77
  embeddingSource: { type: "string" },
80
78
  contextLength: { type: "number" }
81
79
  }
@@ -88,7 +86,7 @@ var fullConfigSchema = {
88
86
  "minLines",
89
87
  "minBlockLines",
90
88
  "threshold",
91
- "embeddingModel",
89
+ "embeddingSource",
92
90
  "contextLength"
93
91
  ]
94
92
  };
@@ -859,116 +857,18 @@ import fs5 from "fs/promises";
859
857
  // src/services/EmbeddingService.ts
860
858
  import debug2 from "debug";
861
859
  import { OllamaEmbeddings } from "@langchain/ollama";
862
- import { GoogleGenerativeAIEmbeddings } from "@langchain/google-genai";
863
-
864
- // ../node_modules/@google/generative-ai/dist/index.mjs
865
- var SchemaType;
866
- (function(SchemaType2) {
867
- SchemaType2["STRING"] = "string";
868
- SchemaType2["NUMBER"] = "number";
869
- SchemaType2["INTEGER"] = "integer";
870
- SchemaType2["BOOLEAN"] = "boolean";
871
- SchemaType2["ARRAY"] = "array";
872
- SchemaType2["OBJECT"] = "object";
873
- })(SchemaType || (SchemaType = {}));
874
- var ExecutableCodeLanguage;
875
- (function(ExecutableCodeLanguage2) {
876
- ExecutableCodeLanguage2["LANGUAGE_UNSPECIFIED"] = "language_unspecified";
877
- ExecutableCodeLanguage2["PYTHON"] = "python";
878
- })(ExecutableCodeLanguage || (ExecutableCodeLanguage = {}));
879
- var Outcome;
880
- (function(Outcome2) {
881
- Outcome2["OUTCOME_UNSPECIFIED"] = "outcome_unspecified";
882
- Outcome2["OUTCOME_OK"] = "outcome_ok";
883
- Outcome2["OUTCOME_FAILED"] = "outcome_failed";
884
- Outcome2["OUTCOME_DEADLINE_EXCEEDED"] = "outcome_deadline_exceeded";
885
- })(Outcome || (Outcome = {}));
886
- var HarmCategory;
887
- (function(HarmCategory2) {
888
- HarmCategory2["HARM_CATEGORY_UNSPECIFIED"] = "HARM_CATEGORY_UNSPECIFIED";
889
- HarmCategory2["HARM_CATEGORY_HATE_SPEECH"] = "HARM_CATEGORY_HATE_SPEECH";
890
- HarmCategory2["HARM_CATEGORY_SEXUALLY_EXPLICIT"] = "HARM_CATEGORY_SEXUALLY_EXPLICIT";
891
- HarmCategory2["HARM_CATEGORY_HARASSMENT"] = "HARM_CATEGORY_HARASSMENT";
892
- HarmCategory2["HARM_CATEGORY_DANGEROUS_CONTENT"] = "HARM_CATEGORY_DANGEROUS_CONTENT";
893
- HarmCategory2["HARM_CATEGORY_CIVIC_INTEGRITY"] = "HARM_CATEGORY_CIVIC_INTEGRITY";
894
- })(HarmCategory || (HarmCategory = {}));
895
- var HarmBlockThreshold;
896
- (function(HarmBlockThreshold2) {
897
- HarmBlockThreshold2["HARM_BLOCK_THRESHOLD_UNSPECIFIED"] = "HARM_BLOCK_THRESHOLD_UNSPECIFIED";
898
- HarmBlockThreshold2["BLOCK_LOW_AND_ABOVE"] = "BLOCK_LOW_AND_ABOVE";
899
- HarmBlockThreshold2["BLOCK_MEDIUM_AND_ABOVE"] = "BLOCK_MEDIUM_AND_ABOVE";
900
- HarmBlockThreshold2["BLOCK_ONLY_HIGH"] = "BLOCK_ONLY_HIGH";
901
- HarmBlockThreshold2["BLOCK_NONE"] = "BLOCK_NONE";
902
- })(HarmBlockThreshold || (HarmBlockThreshold = {}));
903
- var HarmProbability;
904
- (function(HarmProbability2) {
905
- HarmProbability2["HARM_PROBABILITY_UNSPECIFIED"] = "HARM_PROBABILITY_UNSPECIFIED";
906
- HarmProbability2["NEGLIGIBLE"] = "NEGLIGIBLE";
907
- HarmProbability2["LOW"] = "LOW";
908
- HarmProbability2["MEDIUM"] = "MEDIUM";
909
- HarmProbability2["HIGH"] = "HIGH";
910
- })(HarmProbability || (HarmProbability = {}));
911
- var BlockReason;
912
- (function(BlockReason2) {
913
- BlockReason2["BLOCKED_REASON_UNSPECIFIED"] = "BLOCKED_REASON_UNSPECIFIED";
914
- BlockReason2["SAFETY"] = "SAFETY";
915
- BlockReason2["OTHER"] = "OTHER";
916
- })(BlockReason || (BlockReason = {}));
917
- var FinishReason;
918
- (function(FinishReason2) {
919
- FinishReason2["FINISH_REASON_UNSPECIFIED"] = "FINISH_REASON_UNSPECIFIED";
920
- FinishReason2["STOP"] = "STOP";
921
- FinishReason2["MAX_TOKENS"] = "MAX_TOKENS";
922
- FinishReason2["SAFETY"] = "SAFETY";
923
- FinishReason2["RECITATION"] = "RECITATION";
924
- FinishReason2["LANGUAGE"] = "LANGUAGE";
925
- FinishReason2["BLOCKLIST"] = "BLOCKLIST";
926
- FinishReason2["PROHIBITED_CONTENT"] = "PROHIBITED_CONTENT";
927
- FinishReason2["SPII"] = "SPII";
928
- FinishReason2["MALFORMED_FUNCTION_CALL"] = "MALFORMED_FUNCTION_CALL";
929
- FinishReason2["OTHER"] = "OTHER";
930
- })(FinishReason || (FinishReason = {}));
931
- var TaskType;
932
- (function(TaskType2) {
933
- TaskType2["TASK_TYPE_UNSPECIFIED"] = "TASK_TYPE_UNSPECIFIED";
934
- TaskType2["RETRIEVAL_QUERY"] = "RETRIEVAL_QUERY";
935
- TaskType2["RETRIEVAL_DOCUMENT"] = "RETRIEVAL_DOCUMENT";
936
- TaskType2["SEMANTIC_SIMILARITY"] = "SEMANTIC_SIMILARITY";
937
- TaskType2["CLASSIFICATION"] = "CLASSIFICATION";
938
- TaskType2["CLUSTERING"] = "CLUSTERING";
939
- })(TaskType || (TaskType = {}));
940
- var FunctionCallingMode;
941
- (function(FunctionCallingMode2) {
942
- FunctionCallingMode2["MODE_UNSPECIFIED"] = "MODE_UNSPECIFIED";
943
- FunctionCallingMode2["AUTO"] = "AUTO";
944
- FunctionCallingMode2["ANY"] = "ANY";
945
- FunctionCallingMode2["NONE"] = "NONE";
946
- })(FunctionCallingMode || (FunctionCallingMode = {}));
947
- var DynamicRetrievalMode;
948
- (function(DynamicRetrievalMode2) {
949
- DynamicRetrievalMode2["MODE_UNSPECIFIED"] = "MODE_UNSPECIFIED";
950
- DynamicRetrievalMode2["MODE_DYNAMIC"] = "MODE_DYNAMIC";
951
- })(DynamicRetrievalMode || (DynamicRetrievalMode = {}));
952
- var Task;
953
- (function(Task2) {
954
- Task2["GENERATE_CONTENT"] = "generateContent";
955
- Task2["STREAM_GENERATE_CONTENT"] = "streamGenerateContent";
956
- Task2["COUNT_TOKENS"] = "countTokens";
957
- Task2["EMBED_CONTENT"] = "embedContent";
958
- Task2["BATCH_EMBED_CONTENTS"] = "batchEmbedContents";
959
- })(Task || (Task = {}));
960
- var badFinishReasons = [
961
- FinishReason.RECITATION,
962
- FinishReason.SAFETY,
963
- FinishReason.LANGUAGE
964
- ];
965
-
966
- // src/services/EmbeddingService.ts
860
+ import { HuggingFaceInferenceEmbeddings } from "@langchain/community/embeddings/hf";
967
861
  var log2 = debug2("DryScan:EmbeddingService");
862
+ var OLLAMA_MODEL = "embeddinggemma";
863
+ var HUGGINGFACE_MODEL = "google/embeddinggemma-300m";
968
864
  var EmbeddingService = class {
969
865
  constructor(repoPath) {
970
866
  this.repoPath = repoPath;
971
867
  }
868
+ /**
869
+ * Generates an embedding for the given index unit using the configured provider.
870
+ * Skips embedding if code exceeds the configured context length.
871
+ */
972
872
  async addEmbedding(fn) {
973
873
  const config = await configStore.get(this.repoPath);
974
874
  const maxContext = config?.contextLength ?? 2048;
@@ -981,31 +881,36 @@ var EmbeddingService = class {
981
881
  );
982
882
  return { ...fn, embedding: null };
983
883
  }
984
- const model = config.embeddingModel ?? void 0;
985
884
  const source = config.embeddingSource;
986
885
  if (!source) {
987
886
  const message = `Embedding source is not configured for repository at ${this.repoPath}`;
988
887
  log2(message);
989
888
  throw new Error(message);
990
889
  }
991
- const embeddings = this.buildProvider(source, model);
890
+ const embeddings = this.buildProvider(source);
992
891
  const embedding = await embeddings.embedQuery(fn.code);
993
892
  return { ...fn, embedding };
994
893
  }
995
- buildProvider(source, model) {
996
- if (source === "google") {
997
- return new GoogleGenerativeAIEmbeddings({
998
- model: model ?? "gemini-embedding-001",
999
- taskType: TaskType.SEMANTIC_SIMILARITY
894
+ /**
895
+ * Builds the embedding provider based on the source configuration.
896
+ * - URL (http/https): Uses Ollama with "embeddinggemma" model
897
+ * - "huggingface": Uses HuggingFace Inference API with "embeddinggemma-300m" model
898
+ */
899
+ buildProvider(source) {
900
+ if (source.toLowerCase() === "huggingface") {
901
+ log2("Using HuggingFace Inference with model: %s", HUGGINGFACE_MODEL);
902
+ return new HuggingFaceInferenceEmbeddings({
903
+ model: HUGGINGFACE_MODEL
1000
904
  });
1001
905
  }
1002
906
  if (/^https?:\/\//i.test(source)) {
907
+ log2("Using Ollama at %s with model: %s", source, OLLAMA_MODEL);
1003
908
  return new OllamaEmbeddings({
1004
- model: model ?? "embeddinggemma",
909
+ model: OLLAMA_MODEL,
1005
910
  baseUrl: source
1006
911
  });
1007
912
  }
1008
- const message = `Unsupported embedding source: ${source || "(empty)"}`;
913
+ const message = `Unsupported embedding source: ${source || "(empty)"}. Use "huggingface" or an Ollama URL.`;
1009
914
  log2(message);
1010
915
  throw new Error(message);
1011
916
  }
@@ -1854,36 +1759,4 @@ export {
1854
1759
  DryScan,
1855
1760
  configStore
1856
1761
  };
1857
- /*! Bundled license information:
1858
-
1859
- @google/generative-ai/dist/index.mjs:
1860
- @google/generative-ai/dist/index.mjs:
1861
- @google/generative-ai/dist/index.mjs:
1862
- @google/generative-ai/dist/index.mjs:
1863
- @google/generative-ai/dist/index.mjs:
1864
- @google/generative-ai/dist/index.mjs:
1865
- @google/generative-ai/dist/index.mjs:
1866
- @google/generative-ai/dist/index.mjs:
1867
- @google/generative-ai/dist/index.mjs:
1868
- @google/generative-ai/dist/index.mjs:
1869
- @google/generative-ai/dist/index.mjs:
1870
- @google/generative-ai/dist/index.mjs:
1871
- @google/generative-ai/dist/index.mjs:
1872
- (**
1873
- * @license
1874
- * Copyright 2024 Google LLC
1875
- *
1876
- * Licensed under the Apache License, Version 2.0 (the "License");
1877
- * you may not use this file except in compliance with the License.
1878
- * You may obtain a copy of the License at
1879
- *
1880
- * http://www.apache.org/licenses/LICENSE-2.0
1881
- *
1882
- * Unless required by applicable law or agreed to in writing, software
1883
- * distributed under the License is distributed on an "AS IS" BASIS,
1884
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1885
- * See the License for the specific language governing permissions and
1886
- * limitations under the License.
1887
- *)
1888
- */
1889
1762
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/DryScan.ts","../src/const.ts","../src/IndexUnitExtractor.ts","../src/extractors/java.ts","../src/config/indexConfig.ts","../src/config/configStore.ts","../src/config/dryconfig.ts","../src/Gitignore.ts","../src/db/DryScanDatabase.ts","../src/db/entities/FileEntity.ts","../src/db/entities/IndexUnitEntity.ts","../src/services/RepositoryInitializer.ts","../src/services/EmbeddingService.ts","../../node_modules/@google/generative-ai/dist/index.mjs","../src/services/UpdateService.ts","../src/DryScanUpdater.ts","../src/services/DuplicationCache.ts","../src/services/DuplicateService.ts","../src/services/ExclusionService.ts","../src/services/PairingService.ts"],"sourcesContent":["import upath from \"upath\";\nimport fs from \"fs/promises\";\nimport { DuplicateAnalysisResult, DuplicateReport } from \"./types\";\nimport { DRYSCAN_DIR, INDEX_DB } from \"./const\";\nimport { defaultExtractors, IndexUnitExtractor } from \"./IndexUnitExtractor\";\nimport { DryScanDatabase } from \"./db/DryScanDatabase\";\nimport { RepositoryInitializer, InitOptions as InitServiceOptions } from \"./services/RepositoryInitializer\";\nimport { UpdateService } from \"./services/UpdateService\";\nimport { DuplicateService } from \"./services/DuplicateService\";\nimport { ExclusionService } from \"./services/ExclusionService\";\nimport { DryScanServiceDeps } from \"./services/types\";\nimport { configStore } from \"./config/configStore\";\nimport { DryConfig } from \"./types\";\nimport { PairingService } from \"./services/PairingService\";\n\nexport type InitOptions = InitServiceOptions;\n\n\nexport class DryScan {\n repoPath: string;\n private readonly extractor: IndexUnitExtractor;\n private db: DryScanDatabase;\n private readonly services: {\n initializer: RepositoryInitializer;\n updater: UpdateService;\n duplicate: DuplicateService;\n exclusion: ExclusionService;\n };\n private readonly serviceDeps: DryScanServiceDeps;\n\n constructor(\n repoPath: string,\n extractor?: IndexUnitExtractor,\n db?: DryScanDatabase\n ) {\n this.repoPath = repoPath;\n this.extractor = extractor ?? new IndexUnitExtractor(repoPath, defaultExtractors(repoPath));\n this.db = db ?? new DryScanDatabase();\n\n this.serviceDeps = {\n repoPath: this.repoPath,\n db: this.db,\n extractor: this.extractor,\n pairing: new PairingService(this.extractor),\n };\n\n const exclusion = new ExclusionService(this.serviceDeps);\n this.services = {\n initializer: new RepositoryInitializer(this.serviceDeps, exclusion),\n updater: new UpdateService(this.serviceDeps, exclusion),\n duplicate: new DuplicateService(this.serviceDeps),\n exclusion,\n };\n }\n\n /**\n * Initializes the DryScan repository with a 3-phase analysis:\n * Phase 1: Extract and save all functions\n * Phase 2: Resolve and save internal dependencies\n * Phase 3: Compute and save semantic embeddings\n */\n async init(options?: InitOptions): Promise<void> {\n console.log(`[DryScan] Initializing repository at ${this.repoPath}`);\n console.log(\"[DryScan] Preparing database and cache...\");\n await configStore.init(this.repoPath);\n await this.ensureDatabase();\n if (await this.isInitialized()) {\n console.log(\"[DryScan] Repository already initialized; skipping full init.\");\n return;\n }\n console.log(\"[DryScan] Starting initial scan (may take a moment)...\");\n await this.services.initializer.init(options);\n console.log(\"[DryScan] Initial scan complete.\");\n }\n\n /**\n * Updates the index by detecting changed, new, and deleted files.\n * Only reprocesses units in changed files for efficiency.\n * Delegates to DryScanUpdater module for implementation.\n * \n * Update process:\n * 1. List all current source files in repository\n * 2. For each file, check if it's new, changed, or unchanged (via mtime + checksum)\n * 3. Remove old units from changed/deleted files\n * 4. Extract and save units from new/changed files\n * 5. Recompute internal dependencies for affected units\n * 6. Recompute embeddings for affected units\n * 7. Update file tracking metadata\n */\n async updateIndex(): Promise<void> {\n console.log(`[DryScan] Updating index at ${this.repoPath}...`);\n console.log(\"[DryScan] Checking for file changes...\");\n const start = Date.now();\n await this.ensureDatabase();\n await this.services.updater.updateIndex();\n const duration = Date.now() - start;\n console.log(`[DryScan] Index update complete. Took ${duration}ms.`);\n }\n\n\n /**\n * Runs duplicate detection and returns a normalized report payload ready for persistence or display.\n */\n async buildDuplicateReport(): Promise<DuplicateReport> {\n const config = await this.loadConfig();\n const analysis = await this.findDuplicates(config);\n return {\n version: 1,\n generatedAt: new Date().toISOString(),\n threshold: config.threshold,\n grade: analysis.score.grade,\n score: analysis.score,\n duplicates: analysis.duplicates,\n };\n }\n\n /**\n * Finds duplicate code blocks using cosine similarity on embeddings.\n * Automatically updates the index before searching to ensure results are current.\n * Compares all function pairs and returns groups with similarity above the configured threshold.\n *\n * @returns Analysis result with duplicate groups and duplication score\n */\n private async findDuplicates(config: DryConfig): Promise<DuplicateAnalysisResult> {\n console.log(`[DryScan] Finding duplicates (threshold: ${config.threshold})...`);\n await this.ensureDatabase();\n\n console.log(\"[DryScan] Updating index...\");\n const updateStart = Date.now();\n await this.updateIndex();\n const updateDuration = Date.now() - updateStart;\n console.log(`[DryScan] Index update took ${updateDuration}ms.`);\n\n console.log(\"[DryScan] Detecting duplicates...\");\n const dupStart = Date.now();\n const result = await this.services.duplicate.findDuplicates(config);\n const dupDuration = Date.now() - dupStart;\n console.log(`[DryScan] Duplicate detection took ${dupDuration}ms.`);\n\n return result;\n }\n\n /**\n * Cleans excludedPairs entries that no longer match any indexed units.\n * Runs an update first to ensure the index reflects current code.\n */\n async cleanExclusions(): Promise<{ removed: number; kept: number }> {\n await this.updateIndex();\n return this.services.exclusion.cleanExclusions();\n }\n\n private async ensureDatabase(): Promise<void> {\n if (this.db.isInitialized()) return;\n const dbPath = upath.join(this.repoPath, DRYSCAN_DIR, INDEX_DB);\n await fs.mkdir(upath.dirname(dbPath), { recursive: true });\n await this.db.init(dbPath);\n }\n\n private async loadConfig(): Promise<DryConfig> {\n return configStore.get(this.repoPath);\n }\n\n private async isInitialized(): Promise<boolean> {\n if (!this.db.isInitialized()) return false;\n const unitCount = await this.db.countUnits();\n const initialized = unitCount > 0;\n console.log(`[DryScan] Initialization check: ${unitCount} indexed units`);\n return initialized;\n }\n}\n","export const DRYSCAN_DIR = \".dry\";\nexport const INDEX_DB = \"index.db\";\nexport const REPORTS_DIR = \"reports\";\nexport const FILE_CHECKSUM_ALGO = \"md5\";\nexport const BLOCK_HASH_ALGO = \"sha1\";","import path from \"path\";\nimport type { Stats } from \"fs\";\nimport fs from \"fs/promises\";\nimport upath from \"upath\";\nimport crypto from \"node:crypto\";\nimport debug from \"debug\";\nimport { glob } from \"glob-gitignore\";\nimport { IndexUnit } from \"./types\";\nimport { LanguageExtractor } from \"./extractors/LanguageExtractor\";\nimport { JavaExtractor } from \"./extractors/java\";\nimport { FILE_CHECKSUM_ALGO } from \"./const\";\nimport { configStore } from \"./config/configStore\";\nimport { DryConfig } from \"./types\";\nimport { Gitignore } from \"./Gitignore\"\nimport { Ignore } from \"ignore\";\n\nconst log = debug(\"DryScan:Extractor\");\n\nexport type { LanguageExtractor } from \"./extractors/LanguageExtractor\";\n/**\n * Returns the default set of language extractors supported by DryScan.\n * Extend/override by passing custom extractors into the IndexUnitExtractor constructor.\n */\nexport function defaultExtractors(repoPath: string): LanguageExtractor[] {\n return [new JavaExtractor(repoPath)];\n}\n\n/**\n * Extracts and indexes code units (classes, functions, blocks) for a repository.\n * Owns shared file-system helpers and delegates language-specific parsing to LanguageExtractors.\n */\nexport class IndexUnitExtractor {\n private readonly root: string;\n readonly extractors: LanguageExtractor[];\n private readonly gitignore: Gitignore;\n\n constructor(\n rootPath: string,\n extractors?: LanguageExtractor[]\n ) {\n this.root = rootPath;\n this.extractors = extractors ?? defaultExtractors(rootPath);\n this.gitignore = new Gitignore(this.root);\n log(\"Initialized extractor for %s\", this.root);\n }\n\n /**\n * Lists all supported source files from a path. Honors exclusion globs from config.\n */\n async listSourceFiles(dirPath: string): Promise<string[]> {\n const target = await this.resolveTarget(dirPath);\n const config = await this.loadConfig();\n const ignoreMatcher = await this.gitignore.buildMatcher(config);\n\n if (target.stat.isFile()) {\n return this.filterSingleFile(target.baseRel, ignoreMatcher);\n }\n\n const matches = await this.globSourceFiles(target.baseRel);\n return this.filterSupportedFiles(matches, ignoreMatcher);\n }\n\n /**\n * Computes MD5 checksum of file content to track changes.\n */\n async computeChecksum(filePath: string): Promise<string> {\n const fullPath = path.isAbsolute(filePath)\n ? filePath\n : path.join(this.root, filePath);\n\n const content = await fs.readFile(fullPath, \"utf8\");\n return crypto.createHash(FILE_CHECKSUM_ALGO).update(content).digest(\"hex\");\n }\n\n /**\n * Scans a file or directory and extracts indexable units using the matching LanguageExtractor.\n * The returned units have repo-relative file paths and no embedding attached.\n */\n async scan(targetPath: string): Promise<IndexUnit[]> {\n const fullPath = path.isAbsolute(targetPath)\n ? targetPath\n : path.join(this.root, targetPath);\n\n const stat = await fs.stat(fullPath).catch(() => null);\n if (!stat) {\n throw new Error(`Path not found: ${fullPath}`);\n }\n\n if (stat.isDirectory()) {\n log(\"Scanning directory %s\", fullPath);\n return this.scanDirectory(fullPath);\n }\n\n return this.scanFile(fullPath);\n }\n\n\n /**\n * Scans a directory recursively, extracting units from supported files while honoring exclusions.\n */\n private async scanDirectory(dir: string): Promise<IndexUnit[]> {\n const out: IndexUnit[] = [];\n const relDir = this.relPath(dir);\n const files = await this.listSourceFiles(relDir);\n for (const relFile of files) {\n const absFile = path.join(this.root, relFile);\n const extracted = await this.tryScanSupportedFile(absFile);\n out.push(...extracted);\n }\n return out;\n }\n\n /**\n * Scans a single file and extracts supported units.\n */\n private async scanFile(filePath: string): Promise<IndexUnit[]> {\n return this.tryScanSupportedFile(filePath, true);\n }\n\n /**\n * Extracts units from a supported file.\n * Optionally throws when the file type is unsupported (used when scanning an explicit file).\n */\n private async tryScanSupportedFile(filePath: string, throwOnUnsupported = false): Promise<IndexUnit[]> {\n const extractor = this.extractors.find(ex => ex.supports(filePath));\n if (!extractor) {\n if (throwOnUnsupported) {\n throw new Error(`Unsupported file type: ${filePath}`);\n }\n return [];\n }\n const rel = this.relPath(filePath);\n if (await this.shouldExclude(rel)) {\n log(\"Skipping excluded file %s\", rel);\n return [];\n }\n const source = await fs.readFile(filePath, \"utf8\");\n const units = await extractor.extractFromText(rel, source);\n log(\"Extracted %d units from %s\", units.length, rel);\n return units.map(unit => ({\n ...unit,\n filePath: rel,\n embedding: undefined,\n }));\n }\n\n /**\n * Converts an absolute path to a repo-relative, normalized (POSIX-style) path.\n * This keeps paths stable across platforms and consistent in the index/DB.\n */\n private relPath(absPath: string): string {\n return this.normalizeRelPath(upath.relative(this.root, absPath));\n }\n\n /**\n * Returns true if a repo-relative path matches any configured exclusion glob.\n */\n private async shouldExclude(relPath: string): Promise<boolean> {\n const config = await this.loadConfig();\n const ignoreMatcher = await this.gitignore.buildMatcher(config);\n return ignoreMatcher.ignores(this.normalizeRelPath(relPath));\n }\n\n private async loadConfig(): Promise<DryConfig> {\n return await configStore.get(this.root);\n }\n\n /**\n * Normalizes repo-relative paths and strips leading \"./\" to keep matcher inputs consistent.\n */\n private normalizeRelPath(relPath: string): string {\n const normalized = upath.normalizeTrim(relPath);\n return normalized.startsWith(\"./\") ? normalized.slice(2) : normalized;\n }\n\n private async resolveTarget(dirPath: string): Promise<{ fullPath: string; baseRel: string; stat: Stats; }> {\n const fullPath = path.isAbsolute(dirPath) ? dirPath : path.join(this.root, dirPath);\n const stat = await fs.stat(fullPath).catch(() => null);\n if (!stat) {\n throw new Error(`Path not found: ${fullPath}`);\n }\n const baseRel = this.relPath(fullPath);\n log(\"Listing source files under %s\", fullPath);\n return { fullPath, baseRel, stat };\n }\n\n private async filterSingleFile(baseRel: string, ignoreMatcher: Ignore): Promise<string[]> {\n const relFile = this.normalizeRelPath(baseRel);\n if (ignoreMatcher.ignores(relFile)) return [];\n return this.extractors.some((ex) => ex.supports(relFile)) ? [relFile] : [];\n }\n\n private async globSourceFiles(baseRel: string): Promise<string[]> {\n const pattern = baseRel ? `${baseRel.replace(/\\\\/g, \"/\")}/**/*` : \"**/*\";\n const matches = await glob(pattern, {\n cwd: this.root,\n dot: false,\n nodir: true,\n });\n return matches.map((p: string) => this.normalizeRelPath(p));\n }\n\n private filterSupportedFiles(relPaths: string[], ignoreMatcher: Ignore): string[] {\n return relPaths\n .filter((relPath: string) => !ignoreMatcher.ignores(relPath))\n .filter((relPath: string) => this.extractors.some((ex) => ex.supports(relPath)));\n }\n}\n","import crypto from \"node:crypto\";\nimport Parser from \"tree-sitter\";\nimport Java from \"tree-sitter-java\";\nimport { LanguageExtractor } from \"./LanguageExtractor\";\nimport { IndexUnit, IndexUnitType } from \"../types\";\nimport { indexConfig } from \"../config/indexConfig\";\nimport { DryConfig } from \"../types\";\nimport { configStore } from \"../config/configStore\";\nimport { BLOCK_HASH_ALGO } from \"../const\";\n\nexport class JavaExtractor implements LanguageExtractor {\n readonly id = \"java\";\n readonly exts = [\".java\"];\n\n private parser: Parser;\n private readonly repoPath: string;\n private config?: DryConfig;\n\n constructor(repoPath: string) {\n this.repoPath = repoPath;\n this.parser = new Parser();\n this.parser.setLanguage(Java);\n }\n\n supports(filePath: string): boolean {\n const lower = filePath.toLowerCase();\n return this.exts.some((ext) => lower.endsWith(ext));\n }\n\n async extractFromText(fileRelPath: string, source: string): Promise<IndexUnit[]> {\n if (!source.trim()) return [];\n\n this.config = await configStore.get(this.repoPath);\n\n const tree = this.parser.parse(source);\n const units: IndexUnit[] = [];\n\n const visit = (node: Parser.SyntaxNode, currentClass?: IndexUnit) => {\n if (this.isClassNode(node)) {\n const className = this.getClassName(node, source) || \"<anonymous>\";\n if (this.isDtoClass(node, source, className)) {\n return;\n }\n const startLine = node.startPosition.row;\n const endLine = node.endPosition.row;\n const classLength = endLine - startLine;\n const skipClass = this.shouldSkip(IndexUnitType.CLASS, className, classLength);\n const classId = this.buildId(IndexUnitType.CLASS, className, startLine, endLine);\n const code = this.stripComments(this.stripClassBody(node, source));\n const classUnit: IndexUnit = {\n id: classId,\n name: className,\n filePath: fileRelPath,\n startLine,\n endLine,\n code,\n unitType: IndexUnitType.CLASS,\n children: [],\n };\n if (!skipClass) {\n units.push(classUnit);\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i);\n if (child) visit(child, skipClass ? undefined : classUnit);\n }\n return;\n }\n\n if (this.isFunctionNode(node)) {\n const fnUnit = this.buildFunctionUnit(node, source, fileRelPath, currentClass);\n const fnLength = fnUnit.endLine - fnUnit.startLine;\n const bodyNode = this.getFunctionBody(node);\n const skipFunction = this.shouldSkip(IndexUnitType.FUNCTION, fnUnit.name, fnLength);\n\n if (skipFunction) {\n return;\n }\n\n units.push(fnUnit);\n\n if (bodyNode) {\n const blocks = this.extractBlocks(bodyNode, source, fileRelPath, fnUnit);\n units.push(...blocks);\n }\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i);\n if (child) visit(child, currentClass);\n }\n };\n\n visit(tree.rootNode);\n\n return units;\n }\n\n unitLabel(unit: IndexUnit): string | null {\n if (unit.unitType === IndexUnitType.CLASS) return unit.filePath;\n if (unit.unitType === IndexUnitType.FUNCTION) return this.canonicalFunctionSignature(unit);\n if (unit.unitType === IndexUnitType.BLOCK) return this.normalizedBlockHash(unit);\n return unit.name;\n }\n\n private isClassNode(node: Parser.SyntaxNode): boolean {\n return node.type === \"class_declaration\";\n }\n\n private getClassName(node: Parser.SyntaxNode, source: string): string | null {\n const nameNode = node.childForFieldName?.(\"name\");\n return nameNode ? source.slice(nameNode.startIndex, nameNode.endIndex) : null;\n }\n\n private isFunctionNode(node: Parser.SyntaxNode): boolean {\n return node.type === \"method_declaration\" || node.type === \"constructor_declaration\";\n }\n\n private getFunctionName(node: Parser.SyntaxNode, source: string, parentClass?: IndexUnit): string | null {\n const nameNode = node.childForFieldName?.(\"name\");\n const nameText = nameNode ? source.slice(nameNode.startIndex, nameNode.endIndex) : \"<anonymous>\";\n return parentClass ? `${parentClass.name}.${nameText}` : nameText;\n }\n\n private getFunctionBody(node: Parser.SyntaxNode): Parser.SyntaxNode | null {\n return node.childForFieldName?.(\"body\") ?? null;\n }\n\n private isBlockNode(node: Parser.SyntaxNode): boolean {\n return node.type === \"block\";\n }\n\n private getMethodBodiesForClass(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const bodies: Parser.SyntaxNode[] = [];\n const classBody = node.children.find(child => child.type === \"class_body\");\n if (!classBody) return bodies;\n \n for (let i = 0; i < classBody.namedChildCount; i++) {\n const child = classBody.namedChild(i);\n if (!child) continue;\n if (child.type === \"method_declaration\" || child.type === \"constructor_declaration\") {\n const body = child.childForFieldName?.(\"body\");\n if (body) bodies.push(body);\n }\n }\n return bodies;\n }\n\n private canonicalFunctionSignature(unit: IndexUnit): string {\n const arity = this.extractArity(unit.code);\n return `${unit.name}(arity:${arity})`;\n }\n\n private normalizedBlockHash(unit: IndexUnit): string {\n const normalized = this.normalizeCode(unit.code);\n return crypto.createHash(BLOCK_HASH_ALGO).update(normalized).digest(\"hex\");\n }\n\n private shouldSkip(unitType: IndexUnitType, name: string, lineCount: number): boolean {\n if (!this.config) {\n throw new Error(\"Config not loaded before skip evaluation\");\n }\n const config = this.config;\n const minLines = unitType === IndexUnitType.BLOCK\n ? Math.max(indexConfig.blockMinLines, config.minBlockLines ?? 0)\n : config.minLines;\n const belowMin = minLines > 0 && lineCount < minLines;\n const trivial = unitType === IndexUnitType.FUNCTION && this.isTrivialFunction(name);\n return belowMin || trivial;\n }\n\n private isTrivialFunction(fullName: string): boolean {\n const simpleName = fullName.split(\".\").pop() || fullName;\n const isGetter = /^(get|is)[A-Z]/.test(simpleName);\n const isSetter = /^set[A-Z]/.test(simpleName);\n return isGetter || isSetter;\n }\n\n private isDtoClass(node: Parser.SyntaxNode, source: string, className: string): boolean {\n const classBody = node.children.find((child) => child.type === \"class_body\");\n if (!classBody) return false;\n\n let hasField = false;\n\n for (let i = 0; i < classBody.namedChildCount; i++) {\n const child = classBody.namedChild(i);\n if (!child) continue;\n\n if (child.type === \"field_declaration\") {\n hasField = true;\n continue;\n }\n\n if (child.type.includes(\"annotation\")) {\n continue;\n }\n\n if (child.type === \"method_declaration\" || child.type === \"constructor_declaration\") {\n const simpleName = this.getSimpleFunctionName(child, source);\n const fullName = `${className}.${simpleName}`;\n if (!this.isTrivialFunction(fullName)) {\n return false;\n }\n continue;\n }\n\n return false;\n }\n\n return hasField;\n }\n\n private getSimpleFunctionName(node: Parser.SyntaxNode, source: string): string {\n const nameNode = node.childForFieldName?.(\"name\");\n return nameNode ? source.slice(nameNode.startIndex, nameNode.endIndex) : \"<anonymous>\";\n }\n\n private buildFunctionUnit(\n node: Parser.SyntaxNode,\n source: string,\n file: string,\n parentClass?: IndexUnit\n ): IndexUnit {\n const name = this.getFunctionName(node, source, parentClass) || \"<anonymous>\";\n const startLine = node.startPosition.row;\n const endLine = node.endPosition.row;\n const id = this.buildId(IndexUnitType.FUNCTION, name, startLine, endLine);\n const unit: IndexUnit = {\n id,\n name,\n filePath: file,\n startLine,\n endLine,\n code: this.stripComments(source.slice(node.startIndex, node.endIndex)),\n unitType: IndexUnitType.FUNCTION,\n parentId: parentClass?.id,\n parent: parentClass,\n };\n if (parentClass) {\n parentClass.children = parentClass.children || [];\n parentClass.children.push(unit);\n }\n return unit;\n }\n\n private extractBlocks(\n bodyNode: Parser.SyntaxNode,\n source: string,\n file: string,\n parentFunction: IndexUnit\n ): IndexUnit[] {\n const blocks: IndexUnit[] = [];\n\n const visit = (n: Parser.SyntaxNode) => {\n if (this.isBlockNode(n)) {\n const startLine = n.startPosition.row;\n const endLine = n.endPosition.row;\n const lineCount = endLine - startLine;\n if (this.shouldSkip(IndexUnitType.BLOCK, parentFunction.name, lineCount)) {\n return;\n }\n if (lineCount >= indexConfig.blockMinLines) {\n const id = this.buildId(IndexUnitType.BLOCK, parentFunction.name, startLine, endLine);\n const blockUnit: IndexUnit = {\n id,\n name: parentFunction.name,\n filePath: file,\n startLine,\n endLine,\n code: this.stripComments(source.slice(n.startIndex, n.endIndex)),\n unitType: IndexUnitType.BLOCK,\n parentId: parentFunction.id,\n parent: parentFunction,\n };\n parentFunction.children = parentFunction.children || [];\n parentFunction.children.push(blockUnit);\n blocks.push(blockUnit);\n }\n }\n\n for (let i = 0; i < n.namedChildCount; i++) {\n const child = n.namedChild(i);\n if (child) visit(child);\n }\n };\n\n visit(bodyNode);\n return blocks;\n }\n\n private stripClassBody(node: Parser.SyntaxNode, source: string): string {\n const classStart = node.startIndex;\n let code = source.slice(classStart, node.endIndex);\n\n const methodBodies: Array<{ start: number; end: number }> = [];\n const candidates = this.getMethodBodiesForClass(node);\n\n for (const body of candidates) {\n methodBodies.push({ start: body.startIndex - classStart, end: body.endIndex - classStart });\n }\n\n methodBodies.sort((a, b) => b.start - a.start);\n for (const body of methodBodies) {\n code = code.slice(0, body.start) + \" { }\" + code.slice(body.end);\n }\n\n return code;\n }\n\n private buildId(type: IndexUnitType, name: string, startLine: number, endLine: number): string {\n return `${type}:${name}:${startLine}-${endLine}`;\n }\n\n private extractArity(code: string): number {\n const match = code.match(/^[^{]*?\\(([^)]*)\\)/s);\n if (!match) return 0;\n const params = match[1]\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n return params.length;\n }\n\n private normalizeCode(code: string): string {\n const withoutBlockComments = code.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n const withoutLineComments = withoutBlockComments.replace(/\\/\\/[^\\n\\r]*/g, \"\");\n return withoutLineComments.replace(/\\s+/g, \"\");\n }\n\n private stripComments(code: string): string {\n const withoutBlockComments = code.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (match) => match.replace(/[^\\n\\r]/g, \"\"));\n return withoutBlockComments.replace(/\\/\\/[^\\n\\r]*/g, \"\");\n }\n}\n","export const indexConfig = {\n blockMinLines: 5,\n thresholds: {\n class: 0.88,\n function: 0.88,\n block: 0.88,\n },\n weights: {\n class: { self: 1 },\n function: { self: 0.8, parentClass: 0.2 },\n block: { self: 0.7, parentFunction: 0.2, parentClass: 0.1 },\n },\n};\n","import upath from \"upath\";\nimport { DryConfig } from \"../types\";\nimport { ensureDefaultConfig, resolveDryConfig, saveDryConfig } from \"./dryconfig\";\n\nclass ConfigStore {\n private readonly cache = new Map<string, DryConfig>();\n private readonly loading = new Map<string, Promise<DryConfig>>();\n\n async init(repoPath: string): Promise<DryConfig> {\n const key = this.normalize(repoPath);\n return this.load(key, repoPath);\n }\n\n async get(repoPath: string): Promise<DryConfig> {\n const key = this.normalize(repoPath);\n const cached = this.cache.get(key);\n if (cached) return cached;\n return this.load(key, repoPath);\n }\n\n async refresh(repoPath: string): Promise<DryConfig> {\n const key = this.normalize(repoPath);\n this.cache.delete(key);\n return this.load(key, repoPath);\n }\n\n async save(repoPath: string, config: DryConfig): Promise<void> {\n const key = this.normalize(repoPath);\n await saveDryConfig(repoPath, config);\n this.cache.set(key, config);\n }\n\n private async load(key: string, repoPath: string): Promise<DryConfig> {\n const existing = this.loading.get(key);\n if (existing) return existing;\n\n const promise = ensureDefaultConfig(repoPath).then(() => resolveDryConfig(repoPath)).then((config) => {\n this.cache.set(key, config);\n this.loading.delete(key);\n return config;\n }).catch((err) => {\n this.loading.delete(key);\n throw err;\n });\n\n this.loading.set(key, promise);\n return promise;\n }\n\n private normalize(repoPath: string): string {\n return upath.normalizeTrim(upath.resolve(repoPath));\n }\n}\n\nexport const configStore = new ConfigStore();\n","import fs from \"fs/promises\";\nimport upath from \"upath\";\nimport { Validator, Schema } from \"jsonschema\";\nimport { DryConfig } from \"../types\";\n\n// Baseline config used when no file is present; exported so tests and constructors can seed defaults.\nexport const DEFAULT_CONFIG: DryConfig = {\n excludedPaths: [\n \"**/test/**\",\n ],\n excludedPairs: [],\n minLines: 3,\n minBlockLines: 5,\n threshold: 0.88,\n embeddingModel: \"embeddinggemma\",\n embeddingSource: \"http://localhost:11434\",\n contextLength: 2048,\n};\n\nconst validator = new Validator();\n\nconst partialConfigSchema: Schema = {\n type: \"object\",\n properties: {\n excludedPaths: { type: \"array\", items: { type: \"string\" } },\n excludedPairs: { type: \"array\", items: { type: \"string\" } },\n minLines: { type: \"number\" },\n minBlockLines: { type: \"number\" },\n threshold: { type: \"number\" },\n embeddingModel: { type: \"string\" },\n embeddingSource: { type: \"string\" },\n contextLength: { type: \"number\" },\n },\n};\n\nconst fullConfigSchema: Schema = {\n ...partialConfigSchema,\n required: [\n \"excludedPaths\",\n \"excludedPairs\",\n \"minLines\",\n \"minBlockLines\",\n \"threshold\",\n \"embeddingModel\",\n \"contextLength\",\n ],\n};\n\nfunction validateConfig(raw: unknown, schema: Schema, source: string): any {\n const result = validator.validate(raw, schema);\n if (!result.valid) {\n const details = result.errors.map((e) => e.stack).join(\"; \");\n throw new Error(`${source} config is invalid: ${details}`);\n }\n return raw;\n}\n\nasync function readConfigFile(repoPath: string): Promise<Partial<DryConfig>> {\n const configPath = upath.join(repoPath, \"dryconfig.json\");\n try {\n const content = await fs.readFile(configPath, \"utf8\");\n let parsed: Partial<DryConfig> = {};\n try {\n parsed = JSON.parse(content) as Partial<DryConfig>;\n } catch (parseErr) {\n throw new Error(`Invalid JSON in ${configPath}: ${(parseErr as Error).message}`);\n }\n return parsed;\n } catch (err: any) {\n if (err?.code === \"ENOENT\") {\n return {};\n }\n throw err;\n }\n}\n\n/**\n * Resolves the effective config for a repo using defaults merged with any file config.\n */\nexport async function resolveDryConfig(repoPath: string): Promise<DryConfig> {\n const fileConfigRaw = await readConfigFile(repoPath);\n validateConfig(fileConfigRaw, partialConfigSchema, \"Config file\");\n\n const merged = { ...DEFAULT_CONFIG, ...fileConfigRaw };\n validateConfig(merged, fullConfigSchema, \"Merged\");\n return merged as DryConfig;\n}\n\n// Backwards-compatible helper used by existing callers (file + defaults).\nexport async function loadDryConfig(repoPath: string): Promise<DryConfig> {\n return resolveDryConfig(repoPath);\n}\n\nexport async function saveDryConfig(repoPath: string, config: DryConfig): Promise<void> {\n const configPath = upath.join(repoPath, \"dryconfig.json\");\n validateConfig(config, fullConfigSchema, \"Config to save\");\n await fs.writeFile(configPath, JSON.stringify(config, null, 2), \"utf8\");\n}\n\nexport async function ensureDefaultConfig(repoPath: string): Promise<void> {\n const configPath = upath.join(repoPath, \"dryconfig.json\");\n const repoExists = await fs.stat(repoPath).then((s) => s.isDirectory()).catch((err: any) => {\n if (err?.code === \"ENOENT\") return false;\n throw err;\n });\n\n if (!repoExists) return;\n\n const exists = await fs.stat(configPath).then(() => true).catch((err: any) => {\n if (err?.code === \"ENOENT\") return false;\n throw err;\n });\n\n if (!exists) {\n await saveDryConfig(repoPath, DEFAULT_CONFIG);\n }\n}\n","import path from \"path\";\nimport fs from \"fs/promises\";\nimport upath from \"upath\";\nimport { glob } from \"glob-gitignore\";\nimport ignore, { Ignore } from \"ignore\";\nimport { DryConfig } from \"./types\";\n\n/**\n * Gitignore helper that builds ignore matchers by combining default rules,\n * repo .gitignore files, and config-driven exclusions.\n */\nexport class Gitignore {\n private readonly defaultIgnores = [\".git/**\", \".dry/**\"];\n\n constructor(private readonly root: string) {}\n\n async buildMatcher(config: DryConfig): Promise<Ignore> {\n const rules = await this.resolveRules(config);\n return ignore({ allowRelativePaths: true }).add(rules);\n }\n\n private async resolveRules(config: DryConfig): Promise<string[]> {\n const gitignoreRules = await this.loadGitignoreRules();\n const configRules = config.excludedPaths || [];\n return [...this.defaultIgnores, ...gitignoreRules, ...configRules];\n }\n\n private async loadGitignoreRules(): Promise<string[]> {\n const gitignoreFiles = await glob(\"**/.gitignore\", {\n cwd: this.root,\n dot: true,\n nodir: true,\n ignore: this.defaultIgnores,\n });\n\n const rules: string[] = [];\n\n for (const file of gitignoreFiles) {\n const absPath = path.join(this.root, file);\n const dir = upath.normalizeTrim(upath.dirname(file));\n const content = await fs.readFile(absPath, \"utf8\").catch(() => \"\");\n const lines = content.split(/\\r?\\n/);\n\n for (const raw of lines) {\n const trimmed = raw.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n const negated = trimmed.startsWith(\"!\");\n const body = negated ? trimmed.slice(1) : trimmed;\n\n const scoped = this.scopeRule(body, dir);\n if (!scoped) continue;\n\n rules.push(negated ? `!${scoped}` : scoped);\n }\n }\n\n return rules;\n }\n\n private scopeRule(rule: string, gitignoreDir: string): string | null {\n const cleaned = rule.replace(/^\\//, \"\");\n if (!cleaned) return null;\n\n if (!gitignoreDir || gitignoreDir === \".\") {\n return cleaned;\n }\n\n return upath.normalizeTrim(upath.join(gitignoreDir, cleaned));\n }\n}\n","import \"reflect-metadata\";\nimport fs from \"fs/promises\";\nimport upath from \"upath\";\nimport { DataSource, Repository, In } from \"typeorm\";\nimport { FileEntity } from \"./entities/FileEntity\";\nimport { IndexUnit } from \"../types\";\nimport { IndexUnitEntity } from \"./entities/IndexUnitEntity\";\n\nexport class DryScanDatabase {\n private dataSource?: DataSource;\n private unitRepository?: Repository<IndexUnitEntity>;\n private fileRepository?: Repository<FileEntity>;\n\n isInitialized(): boolean {\n return !!this.dataSource?.isInitialized;\n }\n\n async init(dbPath: string): Promise<void> {\n await fs.mkdir(upath.dirname(dbPath), { recursive: true });\n\n this.dataSource = new DataSource({\n type: \"sqlite\",\n database: dbPath,\n entities: [IndexUnitEntity, FileEntity],\n synchronize: true,\n logging: false,\n });\n\n await this.dataSource.initialize();\n this.unitRepository = this.dataSource.getRepository(IndexUnitEntity);\n this.fileRepository = this.dataSource.getRepository(FileEntity);\n }\n\n async saveUnit(unit: IndexUnit): Promise<void> {\n await this.saveUnits(unit);\n }\n\n async saveUnits(units: IndexUnit | IndexUnit[]): Promise<void> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n const payload = Array.isArray(units) ? units : [units];\n await this.unitRepository.save(payload);\n }\n\n async getUnit(id: string): Promise<IndexUnit | null> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n return this.unitRepository.findOne({ \n where: { id },\n relations: [\"children\", \"parent\"]\n });\n }\n\n async getAllUnits(): Promise<IndexUnit[]> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n return this.unitRepository.find({ relations: [\"children\", \"parent\"] });\n }\n\n async updateUnit(unit: IndexUnit): Promise<void> {\n await this.saveUnits(unit);\n }\n\n async updateUnits(units: IndexUnit | IndexUnit[]): Promise<void> {\n await this.saveUnits(units);\n }\n\n /**\n * Returns total count of indexed units.\n */\n async countUnits(): Promise<number> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n return this.unitRepository.count();\n }\n\n /**\n * Removes index units by their file paths.\n * Used during incremental updates when files change.\n */\n async removeUnitsByFilePaths(filePaths: string[]): Promise<void> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n await this.unitRepository.delete({ filePath: In(filePaths) });\n }\n\n /**\n * Saves file metadata (path, checksum, mtime) to track changes.\n */\n async saveFile(file: FileEntity): Promise<void> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n await this.fileRepository.save(file);\n }\n\n /**\n * Saves multiple file metadata entries.\n */\n async saveFiles(files: FileEntity[]): Promise<void> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n await this.fileRepository.save(files);\n }\n\n /**\n * Gets file metadata by file path.\n */\n async getFile(filePath: string): Promise<FileEntity | null> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n return this.fileRepository.findOne({ where: { filePath } });\n }\n\n /**\n * Gets all tracked files.\n */\n async getAllFiles(): Promise<FileEntity[]> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n return this.fileRepository.find();\n }\n\n /**\n * Removes file metadata entries by file paths.\n * Used when files are deleted from repository.\n */\n async removeFilesByFilePaths(filePaths: string[]): Promise<void> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n await this.fileRepository.delete({ filePath: In(filePaths) });\n }\n\n async close(): Promise<void> {\n if (this.dataSource?.isInitialized) {\n await this.dataSource.destroy();\n }\n }\n}\n","import { Entity, PrimaryColumn, Column } from \"typeorm\";\n\n/**\n * Represents a tracked source file in the repository.\n * Used to detect changes via checksum and mtime for incremental updates.\n */\n@Entity(\"files\")\nexport class FileEntity {\n /**\n * Relative path to the file from repository root.\n * Used as primary key for uniqueness.\n */\n @PrimaryColumn(\"text\")\n filePath!: string;\n\n /**\n * MD5 checksum of file content.\n * Used to detect content changes.\n */\n @Column(\"text\")\n checksum!: string;\n\n /**\n * Last modification time in milliseconds since epoch.\n * Used as fast sanity check before computing checksum.\n */\n @Column(\"integer\")\n mtime!: number;\n}\n","import {\n Column,\n Entity,\n JoinColumn,\n ManyToOne,\n OneToMany,\n PrimaryColumn,\n RelationId,\n} from \"typeorm\";\nimport { IndexUnit, IndexUnitType } from \"../../types\";\n\n@Entity(\"index_units\")\nexport class IndexUnitEntity implements IndexUnit {\n @PrimaryColumn(\"text\")\n id!: string;\n\n @Column(\"text\")\n name!: string;\n\n @Column(\"text\")\n filePath!: string;\n\n @Column(\"integer\")\n startLine!: number;\n\n @Column(\"integer\")\n endLine!: number;\n\n @Column(\"text\")\n code!: string;\n\n @Column(\"text\")\n unitType!: IndexUnitType;\n\n @ManyToOne(() => IndexUnitEntity, (unit) => unit.children, {\n nullable: true,\n onDelete: \"CASCADE\",\n })\n @JoinColumn({ name: \"parent_id\" })\n parent?: IndexUnitEntity | null;\n\n @RelationId((unit: IndexUnitEntity) => unit.parent)\n parentId?: string | null;\n\n @OneToMany(() => IndexUnitEntity, (unit) => unit.parent, { nullable: true })\n children?: IndexUnitEntity[];\n\n @Column(\"simple-array\", { nullable: true })\n embedding?: number[] | null;\n}\n","import path from \"path\";\nimport fs from \"fs/promises\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { ExclusionService } from \"./ExclusionService\";\nimport { IndexUnit } from \"../types\";\nimport { EmbeddingService } from \"./EmbeddingService\";\nimport { FileEntity } from \"../db/entities/FileEntity\";\nimport { IndexUnitExtractor } from \"../IndexUnitExtractor\";\n\nexport interface InitOptions {\n skipEmbeddings?: boolean;\n}\n\nexport class RepositoryInitializer {\n constructor(\n private readonly deps: DryScanServiceDeps,\n private readonly exclusionService: ExclusionService\n ) {}\n\n async init(options?: InitOptions): Promise<void> {\n const extractor = this.deps.extractor;\n\n console.log(\"[DryScan] Phase 1/3: Extracting code units...\");\n await this.initUnits(extractor);\n console.log(\"[DryScan] Phase 2/3: Computing embeddings (may be slow)...\");\n await this.computeEmbeddings(options?.skipEmbeddings === true);\n console.log(\"[DryScan] Phase 3/3: Tracking files...\");\n await this.trackFiles(extractor);\n await this.exclusionService.cleanupExcludedFiles();\n console.log(\"[DryScan] Initialization phases complete.\");\n }\n\n private async initUnits(extractor: IndexUnitExtractor): Promise<void> {\n const units = await extractor.scan(this.deps.repoPath);\n console.log(`[DryScan] Extracted ${units.length} index units.`);\n await this.deps.db.saveUnits(units);\n }\n\n private async computeEmbeddings(skipEmbeddings: boolean): Promise<void> {\n if (skipEmbeddings) {\n console.log(\"[DryScan] Skipping embedding computation by request.\");\n return;\n }\n const allUnits: IndexUnit[] = await this.deps.db.getAllUnits();\n const total = allUnits.length;\n console.log(`[DryScan] Computing embeddings for ${total} units...`);\n\n const updated: IndexUnit[] = [];\n const progressInterval = Math.max(1, Math.ceil(total / 10));\n const embeddingService = new EmbeddingService(this.deps.repoPath);\n\n for (let i = 0; i < total; i++) {\n const unit = allUnits[i];\n try {\n const enriched = await embeddingService.addEmbedding(unit);\n updated.push(enriched);\n } catch (err: any) {\n console.error(\n `[DryScan] Embedding failed for ${unit.filePath} (${unit.name}): ${err?.message || err}`\n );\n throw err;\n }\n\n const completed = i + 1;\n if (completed === total || completed % progressInterval === 0) {\n const pct = Math.floor((completed / total) * 100);\n console.log(`[DryScan] Embeddings ${completed}/${total} (${pct}%)`);\n }\n }\n\n await this.deps.db.updateUnits(updated);\n }\n\n private async trackFiles(extractor: IndexUnitExtractor): Promise<void> {\n const allFunctions = await extractor.listSourceFiles(this.deps.repoPath);\n const fileEntities: FileEntity[] = [];\n\n for (const relPath of allFunctions) {\n const fullPath = path.join(this.deps.repoPath, relPath);\n const stat = await fs.stat(fullPath);\n const checksum = await extractor.computeChecksum(fullPath);\n\n const fileEntity = new FileEntity();\n fileEntity.filePath = relPath;\n fileEntity.checksum = checksum;\n fileEntity.mtime = stat.mtimeMs;\n fileEntities.push(fileEntity);\n }\n\n await this.deps.db.saveFiles(fileEntities);\n console.log(`[DryScan] Tracked ${fileEntities.length} files.`);\n }\n}","import debug from \"debug\";\nimport { OllamaEmbeddings } from \"@langchain/ollama\";\nimport { GoogleGenerativeAIEmbeddings } from \"@langchain/google-genai\";\nimport { TaskType } from \"@google/generative-ai\";\nimport { IndexUnit } from \"../types\";\nimport { configStore } from \"../config/configStore\";\n\nconst log = debug(\"DryScan:EmbeddingService\");\n\nexport class EmbeddingService {\n constructor(private readonly repoPath: string) { }\n\n async addEmbedding(fn: IndexUnit): Promise<IndexUnit> {\n const config = await configStore.get(this.repoPath);\n const maxContext = config?.contextLength ?? 2048;\n if (fn.code.length > maxContext) {\n log(\n \"Skipping embedding for %s (code length %d exceeds context %d)\",\n fn.id,\n fn.code.length,\n maxContext\n );\n return { ...fn, embedding: null };\n }\n\n const model = config.embeddingModel ?? undefined\n const source = config.embeddingSource\n if (!source) {\n const message = `Embedding source is not configured for repository at ${this.repoPath}`;\n log(message);\n throw new Error(message);\n }\n\n const embeddings = this.buildProvider(source, model);\n const embedding = await embeddings.embedQuery(fn.code);\n return { ...fn, embedding };\n }\n\n private buildProvider(source: string, model: string) {\n if (source === \"google\") {\n return new GoogleGenerativeAIEmbeddings({\n model: model ?? \"gemini-embedding-001\",\n taskType: TaskType.SEMANTIC_SIMILARITY,\n });\n }\n\n if (/^https?:\\/\\//i.test(source)) {\n return new OllamaEmbeddings({\n model: model ?? \"embeddinggemma\",\n baseUrl: source,\n });\n }\n\n const message = `Unsupported embedding source: ${source || \"(empty)\"}`;\n log(message);\n throw new Error(message);\n }\n}","/**\n * Contains the list of OpenAPI data types\n * as defined by https://swagger.io/docs/specification/data-models/data-types/\n * @public\n */\nvar SchemaType;\n(function (SchemaType) {\n /** String type. */\n SchemaType[\"STRING\"] = \"string\";\n /** Number type. */\n SchemaType[\"NUMBER\"] = \"number\";\n /** Integer type. */\n SchemaType[\"INTEGER\"] = \"integer\";\n /** Boolean type. */\n SchemaType[\"BOOLEAN\"] = \"boolean\";\n /** Array type. */\n SchemaType[\"ARRAY\"] = \"array\";\n /** Object type. */\n SchemaType[\"OBJECT\"] = \"object\";\n})(SchemaType || (SchemaType = {}));\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * @public\n */\nvar ExecutableCodeLanguage;\n(function (ExecutableCodeLanguage) {\n ExecutableCodeLanguage[\"LANGUAGE_UNSPECIFIED\"] = \"language_unspecified\";\n ExecutableCodeLanguage[\"PYTHON\"] = \"python\";\n})(ExecutableCodeLanguage || (ExecutableCodeLanguage = {}));\n/**\n * Possible outcomes of code execution.\n * @public\n */\nvar Outcome;\n(function (Outcome) {\n /**\n * Unspecified status. This value should not be used.\n */\n Outcome[\"OUTCOME_UNSPECIFIED\"] = \"outcome_unspecified\";\n /**\n * Code execution completed successfully.\n */\n Outcome[\"OUTCOME_OK\"] = \"outcome_ok\";\n /**\n * Code execution finished but with a failure. `stderr` should contain the\n * reason.\n */\n Outcome[\"OUTCOME_FAILED\"] = \"outcome_failed\";\n /**\n * Code execution ran for too long, and was cancelled. There may or may not\n * be a partial output present.\n */\n Outcome[\"OUTCOME_DEADLINE_EXCEEDED\"] = \"outcome_deadline_exceeded\";\n})(Outcome || (Outcome = {}));\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Possible roles.\n * @public\n */\nconst POSSIBLE_ROLES = [\"user\", \"model\", \"function\", \"system\"];\n/**\n * Harm categories that would cause prompts or candidates to be blocked.\n * @public\n */\nvar HarmCategory;\n(function (HarmCategory) {\n HarmCategory[\"HARM_CATEGORY_UNSPECIFIED\"] = \"HARM_CATEGORY_UNSPECIFIED\";\n HarmCategory[\"HARM_CATEGORY_HATE_SPEECH\"] = \"HARM_CATEGORY_HATE_SPEECH\";\n HarmCategory[\"HARM_CATEGORY_SEXUALLY_EXPLICIT\"] = \"HARM_CATEGORY_SEXUALLY_EXPLICIT\";\n HarmCategory[\"HARM_CATEGORY_HARASSMENT\"] = \"HARM_CATEGORY_HARASSMENT\";\n HarmCategory[\"HARM_CATEGORY_DANGEROUS_CONTENT\"] = \"HARM_CATEGORY_DANGEROUS_CONTENT\";\n HarmCategory[\"HARM_CATEGORY_CIVIC_INTEGRITY\"] = \"HARM_CATEGORY_CIVIC_INTEGRITY\";\n})(HarmCategory || (HarmCategory = {}));\n/**\n * Threshold above which a prompt or candidate will be blocked.\n * @public\n */\nvar HarmBlockThreshold;\n(function (HarmBlockThreshold) {\n /** Threshold is unspecified. */\n HarmBlockThreshold[\"HARM_BLOCK_THRESHOLD_UNSPECIFIED\"] = \"HARM_BLOCK_THRESHOLD_UNSPECIFIED\";\n /** Content with NEGLIGIBLE will be allowed. */\n HarmBlockThreshold[\"BLOCK_LOW_AND_ABOVE\"] = \"BLOCK_LOW_AND_ABOVE\";\n /** Content with NEGLIGIBLE and LOW will be allowed. */\n HarmBlockThreshold[\"BLOCK_MEDIUM_AND_ABOVE\"] = \"BLOCK_MEDIUM_AND_ABOVE\";\n /** Content with NEGLIGIBLE, LOW, and MEDIUM will be allowed. */\n HarmBlockThreshold[\"BLOCK_ONLY_HIGH\"] = \"BLOCK_ONLY_HIGH\";\n /** All content will be allowed. */\n HarmBlockThreshold[\"BLOCK_NONE\"] = \"BLOCK_NONE\";\n})(HarmBlockThreshold || (HarmBlockThreshold = {}));\n/**\n * Probability that a prompt or candidate matches a harm category.\n * @public\n */\nvar HarmProbability;\n(function (HarmProbability) {\n /** Probability is unspecified. */\n HarmProbability[\"HARM_PROBABILITY_UNSPECIFIED\"] = \"HARM_PROBABILITY_UNSPECIFIED\";\n /** Content has a negligible chance of being unsafe. */\n HarmProbability[\"NEGLIGIBLE\"] = \"NEGLIGIBLE\";\n /** Content has a low chance of being unsafe. */\n HarmProbability[\"LOW\"] = \"LOW\";\n /** Content has a medium chance of being unsafe. */\n HarmProbability[\"MEDIUM\"] = \"MEDIUM\";\n /** Content has a high chance of being unsafe. */\n HarmProbability[\"HIGH\"] = \"HIGH\";\n})(HarmProbability || (HarmProbability = {}));\n/**\n * Reason that a prompt was blocked.\n * @public\n */\nvar BlockReason;\n(function (BlockReason) {\n // A blocked reason was not specified.\n BlockReason[\"BLOCKED_REASON_UNSPECIFIED\"] = \"BLOCKED_REASON_UNSPECIFIED\";\n // Content was blocked by safety settings.\n BlockReason[\"SAFETY\"] = \"SAFETY\";\n // Content was blocked, but the reason is uncategorized.\n BlockReason[\"OTHER\"] = \"OTHER\";\n})(BlockReason || (BlockReason = {}));\n/**\n * Reason that a candidate finished.\n * @public\n */\nvar FinishReason;\n(function (FinishReason) {\n // Default value. This value is unused.\n FinishReason[\"FINISH_REASON_UNSPECIFIED\"] = \"FINISH_REASON_UNSPECIFIED\";\n // Natural stop point of the model or provided stop sequence.\n FinishReason[\"STOP\"] = \"STOP\";\n // The maximum number of tokens as specified in the request was reached.\n FinishReason[\"MAX_TOKENS\"] = \"MAX_TOKENS\";\n // The candidate content was flagged for safety reasons.\n FinishReason[\"SAFETY\"] = \"SAFETY\";\n // The candidate content was flagged for recitation reasons.\n FinishReason[\"RECITATION\"] = \"RECITATION\";\n // The candidate content was flagged for using an unsupported language.\n FinishReason[\"LANGUAGE\"] = \"LANGUAGE\";\n // Token generation stopped because the content contains forbidden terms.\n FinishReason[\"BLOCKLIST\"] = \"BLOCKLIST\";\n // Token generation stopped for potentially containing prohibited content.\n FinishReason[\"PROHIBITED_CONTENT\"] = \"PROHIBITED_CONTENT\";\n // Token generation stopped because the content potentially contains Sensitive Personally Identifiable Information (SPII).\n FinishReason[\"SPII\"] = \"SPII\";\n // The function call generated by the model is invalid.\n FinishReason[\"MALFORMED_FUNCTION_CALL\"] = \"MALFORMED_FUNCTION_CALL\";\n // Unknown reason.\n FinishReason[\"OTHER\"] = \"OTHER\";\n})(FinishReason || (FinishReason = {}));\n/**\n * Task type for embedding content.\n * @public\n */\nvar TaskType;\n(function (TaskType) {\n TaskType[\"TASK_TYPE_UNSPECIFIED\"] = \"TASK_TYPE_UNSPECIFIED\";\n TaskType[\"RETRIEVAL_QUERY\"] = \"RETRIEVAL_QUERY\";\n TaskType[\"RETRIEVAL_DOCUMENT\"] = \"RETRIEVAL_DOCUMENT\";\n TaskType[\"SEMANTIC_SIMILARITY\"] = \"SEMANTIC_SIMILARITY\";\n TaskType[\"CLASSIFICATION\"] = \"CLASSIFICATION\";\n TaskType[\"CLUSTERING\"] = \"CLUSTERING\";\n})(TaskType || (TaskType = {}));\n/**\n * @public\n */\nvar FunctionCallingMode;\n(function (FunctionCallingMode) {\n // Unspecified function calling mode. This value should not be used.\n FunctionCallingMode[\"MODE_UNSPECIFIED\"] = \"MODE_UNSPECIFIED\";\n // Default model behavior, model decides to predict either a function call\n // or a natural language repspose.\n FunctionCallingMode[\"AUTO\"] = \"AUTO\";\n // Model is constrained to always predicting a function call only.\n // If \"allowed_function_names\" are set, the predicted function call will be\n // limited to any one of \"allowed_function_names\", else the predicted\n // function call will be any one of the provided \"function_declarations\".\n FunctionCallingMode[\"ANY\"] = \"ANY\";\n // Model will not predict any function call. Model behavior is same as when\n // not passing any function declarations.\n FunctionCallingMode[\"NONE\"] = \"NONE\";\n})(FunctionCallingMode || (FunctionCallingMode = {}));\n/**\n * The mode of the predictor to be used in dynamic retrieval.\n * @public\n */\nvar DynamicRetrievalMode;\n(function (DynamicRetrievalMode) {\n // Unspecified function calling mode. This value should not be used.\n DynamicRetrievalMode[\"MODE_UNSPECIFIED\"] = \"MODE_UNSPECIFIED\";\n // Run retrieval only when system decides it is necessary.\n DynamicRetrievalMode[\"MODE_DYNAMIC\"] = \"MODE_DYNAMIC\";\n})(DynamicRetrievalMode || (DynamicRetrievalMode = {}));\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Basic error type for this SDK.\n * @public\n */\nclass GoogleGenerativeAIError extends Error {\n constructor(message) {\n super(`[GoogleGenerativeAI Error]: ${message}`);\n }\n}\n/**\n * Errors in the contents of a response from the model. This includes parsing\n * errors, or responses including a safety block reason.\n * @public\n */\nclass GoogleGenerativeAIResponseError extends GoogleGenerativeAIError {\n constructor(message, response) {\n super(message);\n this.response = response;\n }\n}\n/**\n * Error class covering HTTP errors when calling the server. Includes HTTP\n * status, statusText, and optional details, if provided in the server response.\n * @public\n */\nclass GoogleGenerativeAIFetchError extends GoogleGenerativeAIError {\n constructor(message, status, statusText, errorDetails) {\n super(message);\n this.status = status;\n this.statusText = statusText;\n this.errorDetails = errorDetails;\n }\n}\n/**\n * Errors in the contents of a request originating from user input.\n * @public\n */\nclass GoogleGenerativeAIRequestInputError extends GoogleGenerativeAIError {\n}\n/**\n * Error thrown when a request is aborted, either due to a timeout or\n * intentional cancellation by the user.\n * @public\n */\nclass GoogleGenerativeAIAbortError extends GoogleGenerativeAIError {\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nconst DEFAULT_BASE_URL = \"https://generativelanguage.googleapis.com\";\nconst DEFAULT_API_VERSION = \"v1beta\";\n/**\n * We can't `require` package.json if this runs on web. We will use rollup to\n * swap in the version number here at build time.\n */\nconst PACKAGE_VERSION = \"0.24.1\";\nconst PACKAGE_LOG_HEADER = \"genai-js\";\nvar Task;\n(function (Task) {\n Task[\"GENERATE_CONTENT\"] = \"generateContent\";\n Task[\"STREAM_GENERATE_CONTENT\"] = \"streamGenerateContent\";\n Task[\"COUNT_TOKENS\"] = \"countTokens\";\n Task[\"EMBED_CONTENT\"] = \"embedContent\";\n Task[\"BATCH_EMBED_CONTENTS\"] = \"batchEmbedContents\";\n})(Task || (Task = {}));\nclass RequestUrl {\n constructor(model, task, apiKey, stream, requestOptions) {\n this.model = model;\n this.task = task;\n this.apiKey = apiKey;\n this.stream = stream;\n this.requestOptions = requestOptions;\n }\n toString() {\n var _a, _b;\n const apiVersion = ((_a = this.requestOptions) === null || _a === void 0 ? void 0 : _a.apiVersion) || DEFAULT_API_VERSION;\n const baseUrl = ((_b = this.requestOptions) === null || _b === void 0 ? void 0 : _b.baseUrl) || DEFAULT_BASE_URL;\n let url = `${baseUrl}/${apiVersion}/${this.model}:${this.task}`;\n if (this.stream) {\n url += \"?alt=sse\";\n }\n return url;\n }\n}\n/**\n * Simple, but may become more complex if we add more versions to log.\n */\nfunction getClientHeaders(requestOptions) {\n const clientHeaders = [];\n if (requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.apiClient) {\n clientHeaders.push(requestOptions.apiClient);\n }\n clientHeaders.push(`${PACKAGE_LOG_HEADER}/${PACKAGE_VERSION}`);\n return clientHeaders.join(\" \");\n}\nasync function getHeaders(url) {\n var _a;\n const headers = new Headers();\n headers.append(\"Content-Type\", \"application/json\");\n headers.append(\"x-goog-api-client\", getClientHeaders(url.requestOptions));\n headers.append(\"x-goog-api-key\", url.apiKey);\n let customHeaders = (_a = url.requestOptions) === null || _a === void 0 ? void 0 : _a.customHeaders;\n if (customHeaders) {\n if (!(customHeaders instanceof Headers)) {\n try {\n customHeaders = new Headers(customHeaders);\n }\n catch (e) {\n throw new GoogleGenerativeAIRequestInputError(`unable to convert customHeaders value ${JSON.stringify(customHeaders)} to Headers: ${e.message}`);\n }\n }\n for (const [headerName, headerValue] of customHeaders.entries()) {\n if (headerName === \"x-goog-api-key\") {\n throw new GoogleGenerativeAIRequestInputError(`Cannot set reserved header name ${headerName}`);\n }\n else if (headerName === \"x-goog-api-client\") {\n throw new GoogleGenerativeAIRequestInputError(`Header name ${headerName} can only be set using the apiClient field`);\n }\n headers.append(headerName, headerValue);\n }\n }\n return headers;\n}\nasync function constructModelRequest(model, task, apiKey, stream, body, requestOptions) {\n const url = new RequestUrl(model, task, apiKey, stream, requestOptions);\n return {\n url: url.toString(),\n fetchOptions: Object.assign(Object.assign({}, buildFetchOptions(requestOptions)), { method: \"POST\", headers: await getHeaders(url), body }),\n };\n}\nasync function makeModelRequest(model, task, apiKey, stream, body, requestOptions = {}, \n// Allows this to be stubbed for tests\nfetchFn = fetch) {\n const { url, fetchOptions } = await constructModelRequest(model, task, apiKey, stream, body, requestOptions);\n return makeRequest(url, fetchOptions, fetchFn);\n}\nasync function makeRequest(url, fetchOptions, fetchFn = fetch) {\n let response;\n try {\n response = await fetchFn(url, fetchOptions);\n }\n catch (e) {\n handleResponseError(e, url);\n }\n if (!response.ok) {\n await handleResponseNotOk(response, url);\n }\n return response;\n}\nfunction handleResponseError(e, url) {\n let err = e;\n if (err.name === \"AbortError\") {\n err = new GoogleGenerativeAIAbortError(`Request aborted when fetching ${url.toString()}: ${e.message}`);\n err.stack = e.stack;\n }\n else if (!(e instanceof GoogleGenerativeAIFetchError ||\n e instanceof GoogleGenerativeAIRequestInputError)) {\n err = new GoogleGenerativeAIError(`Error fetching from ${url.toString()}: ${e.message}`);\n err.stack = e.stack;\n }\n throw err;\n}\nasync function handleResponseNotOk(response, url) {\n let message = \"\";\n let errorDetails;\n try {\n const json = await response.json();\n message = json.error.message;\n if (json.error.details) {\n message += ` ${JSON.stringify(json.error.details)}`;\n errorDetails = json.error.details;\n }\n }\n catch (e) {\n // ignored\n }\n throw new GoogleGenerativeAIFetchError(`Error fetching from ${url.toString()}: [${response.status} ${response.statusText}] ${message}`, response.status, response.statusText, errorDetails);\n}\n/**\n * Generates the request options to be passed to the fetch API.\n * @param requestOptions - The user-defined request options.\n * @returns The generated request options.\n */\nfunction buildFetchOptions(requestOptions) {\n const fetchOptions = {};\n if ((requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.signal) !== undefined || (requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.timeout) >= 0) {\n const controller = new AbortController();\n if ((requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.timeout) >= 0) {\n setTimeout(() => controller.abort(), requestOptions.timeout);\n }\n if (requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.signal) {\n requestOptions.signal.addEventListener(\"abort\", () => {\n controller.abort();\n });\n }\n fetchOptions.signal = controller.signal;\n }\n return fetchOptions;\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Adds convenience helper methods to a response object, including stream\n * chunks (as long as each chunk is a complete GenerateContentResponse JSON).\n */\nfunction addHelpers(response) {\n response.text = () => {\n if (response.candidates && response.candidates.length > 0) {\n if (response.candidates.length > 1) {\n console.warn(`This response had ${response.candidates.length} ` +\n `candidates. Returning text from the first candidate only. ` +\n `Access response.candidates directly to use the other candidates.`);\n }\n if (hadBadFinishReason(response.candidates[0])) {\n throw new GoogleGenerativeAIResponseError(`${formatBlockErrorMessage(response)}`, response);\n }\n return getText(response);\n }\n else if (response.promptFeedback) {\n throw new GoogleGenerativeAIResponseError(`Text not available. ${formatBlockErrorMessage(response)}`, response);\n }\n return \"\";\n };\n /**\n * TODO: remove at next major version\n */\n response.functionCall = () => {\n if (response.candidates && response.candidates.length > 0) {\n if (response.candidates.length > 1) {\n console.warn(`This response had ${response.candidates.length} ` +\n `candidates. Returning function calls from the first candidate only. ` +\n `Access response.candidates directly to use the other candidates.`);\n }\n if (hadBadFinishReason(response.candidates[0])) {\n throw new GoogleGenerativeAIResponseError(`${formatBlockErrorMessage(response)}`, response);\n }\n console.warn(`response.functionCall() is deprecated. ` +\n `Use response.functionCalls() instead.`);\n return getFunctionCalls(response)[0];\n }\n else if (response.promptFeedback) {\n throw new GoogleGenerativeAIResponseError(`Function call not available. ${formatBlockErrorMessage(response)}`, response);\n }\n return undefined;\n };\n response.functionCalls = () => {\n if (response.candidates && response.candidates.length > 0) {\n if (response.candidates.length > 1) {\n console.warn(`This response had ${response.candidates.length} ` +\n `candidates. Returning function calls from the first candidate only. ` +\n `Access response.candidates directly to use the other candidates.`);\n }\n if (hadBadFinishReason(response.candidates[0])) {\n throw new GoogleGenerativeAIResponseError(`${formatBlockErrorMessage(response)}`, response);\n }\n return getFunctionCalls(response);\n }\n else if (response.promptFeedback) {\n throw new GoogleGenerativeAIResponseError(`Function call not available. ${formatBlockErrorMessage(response)}`, response);\n }\n return undefined;\n };\n return response;\n}\n/**\n * Returns all text found in all parts of first candidate.\n */\nfunction getText(response) {\n var _a, _b, _c, _d;\n const textStrings = [];\n if ((_b = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0].content) === null || _b === void 0 ? void 0 : _b.parts) {\n for (const part of (_d = (_c = response.candidates) === null || _c === void 0 ? void 0 : _c[0].content) === null || _d === void 0 ? void 0 : _d.parts) {\n if (part.text) {\n textStrings.push(part.text);\n }\n if (part.executableCode) {\n textStrings.push(\"\\n```\" +\n part.executableCode.language +\n \"\\n\" +\n part.executableCode.code +\n \"\\n```\\n\");\n }\n if (part.codeExecutionResult) {\n textStrings.push(\"\\n```\\n\" + part.codeExecutionResult.output + \"\\n```\\n\");\n }\n }\n }\n if (textStrings.length > 0) {\n return textStrings.join(\"\");\n }\n else {\n return \"\";\n }\n}\n/**\n * Returns functionCall of first candidate.\n */\nfunction getFunctionCalls(response) {\n var _a, _b, _c, _d;\n const functionCalls = [];\n if ((_b = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0].content) === null || _b === void 0 ? void 0 : _b.parts) {\n for (const part of (_d = (_c = response.candidates) === null || _c === void 0 ? void 0 : _c[0].content) === null || _d === void 0 ? void 0 : _d.parts) {\n if (part.functionCall) {\n functionCalls.push(part.functionCall);\n }\n }\n }\n if (functionCalls.length > 0) {\n return functionCalls;\n }\n else {\n return undefined;\n }\n}\nconst badFinishReasons = [\n FinishReason.RECITATION,\n FinishReason.SAFETY,\n FinishReason.LANGUAGE,\n];\nfunction hadBadFinishReason(candidate) {\n return (!!candidate.finishReason &&\n badFinishReasons.includes(candidate.finishReason));\n}\nfunction formatBlockErrorMessage(response) {\n var _a, _b, _c;\n let message = \"\";\n if ((!response.candidates || response.candidates.length === 0) &&\n response.promptFeedback) {\n message += \"Response was blocked\";\n if ((_a = response.promptFeedback) === null || _a === void 0 ? void 0 : _a.blockReason) {\n message += ` due to ${response.promptFeedback.blockReason}`;\n }\n if ((_b = response.promptFeedback) === null || _b === void 0 ? void 0 : _b.blockReasonMessage) {\n message += `: ${response.promptFeedback.blockReasonMessage}`;\n }\n }\n else if ((_c = response.candidates) === null || _c === void 0 ? void 0 : _c[0]) {\n const firstCandidate = response.candidates[0];\n if (hadBadFinishReason(firstCandidate)) {\n message += `Candidate was blocked due to ${firstCandidate.finishReason}`;\n if (firstCandidate.finishMessage) {\n message += `: ${firstCandidate.finishMessage}`;\n }\n }\n }\n return message;\n}\n\n/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\n\r\nfunction __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nfunction __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\ntypeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nconst responseLineRE = /^data\\: (.*)(?:\\n\\n|\\r\\r|\\r\\n\\r\\n)/;\n/**\n * Process a response.body stream from the backend and return an\n * iterator that provides one complete GenerateContentResponse at a time\n * and a promise that resolves with a single aggregated\n * GenerateContentResponse.\n *\n * @param response - Response from a fetch call\n */\nfunction processStream(response) {\n const inputStream = response.body.pipeThrough(new TextDecoderStream(\"utf8\", { fatal: true }));\n const responseStream = getResponseStream(inputStream);\n const [stream1, stream2] = responseStream.tee();\n return {\n stream: generateResponseSequence(stream1),\n response: getResponsePromise(stream2),\n };\n}\nasync function getResponsePromise(stream) {\n const allResponses = [];\n const reader = stream.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n return addHelpers(aggregateResponses(allResponses));\n }\n allResponses.push(value);\n }\n}\nfunction generateResponseSequence(stream) {\n return __asyncGenerator(this, arguments, function* generateResponseSequence_1() {\n const reader = stream.getReader();\n while (true) {\n const { value, done } = yield __await(reader.read());\n if (done) {\n break;\n }\n yield yield __await(addHelpers(value));\n }\n });\n}\n/**\n * Reads a raw stream from the fetch response and join incomplete\n * chunks, returning a new stream that provides a single complete\n * GenerateContentResponse in each iteration.\n */\nfunction getResponseStream(inputStream) {\n const reader = inputStream.getReader();\n const stream = new ReadableStream({\n start(controller) {\n let currentText = \"\";\n return pump();\n function pump() {\n return reader\n .read()\n .then(({ value, done }) => {\n if (done) {\n if (currentText.trim()) {\n controller.error(new GoogleGenerativeAIError(\"Failed to parse stream\"));\n return;\n }\n controller.close();\n return;\n }\n currentText += value;\n let match = currentText.match(responseLineRE);\n let parsedResponse;\n while (match) {\n try {\n parsedResponse = JSON.parse(match[1]);\n }\n catch (e) {\n controller.error(new GoogleGenerativeAIError(`Error parsing JSON response: \"${match[1]}\"`));\n return;\n }\n controller.enqueue(parsedResponse);\n currentText = currentText.substring(match[0].length);\n match = currentText.match(responseLineRE);\n }\n return pump();\n })\n .catch((e) => {\n let err = e;\n err.stack = e.stack;\n if (err.name === \"AbortError\") {\n err = new GoogleGenerativeAIAbortError(\"Request aborted when reading from the stream\");\n }\n else {\n err = new GoogleGenerativeAIError(\"Error reading from the stream\");\n }\n throw err;\n });\n }\n },\n });\n return stream;\n}\n/**\n * Aggregates an array of `GenerateContentResponse`s into a single\n * GenerateContentResponse.\n */\nfunction aggregateResponses(responses) {\n const lastResponse = responses[responses.length - 1];\n const aggregatedResponse = {\n promptFeedback: lastResponse === null || lastResponse === void 0 ? void 0 : lastResponse.promptFeedback,\n };\n for (const response of responses) {\n if (response.candidates) {\n let candidateIndex = 0;\n for (const candidate of response.candidates) {\n if (!aggregatedResponse.candidates) {\n aggregatedResponse.candidates = [];\n }\n if (!aggregatedResponse.candidates[candidateIndex]) {\n aggregatedResponse.candidates[candidateIndex] = {\n index: candidateIndex,\n };\n }\n // Keep overwriting, the last one will be final\n aggregatedResponse.candidates[candidateIndex].citationMetadata =\n candidate.citationMetadata;\n aggregatedResponse.candidates[candidateIndex].groundingMetadata =\n candidate.groundingMetadata;\n aggregatedResponse.candidates[candidateIndex].finishReason =\n candidate.finishReason;\n aggregatedResponse.candidates[candidateIndex].finishMessage =\n candidate.finishMessage;\n aggregatedResponse.candidates[candidateIndex].safetyRatings =\n candidate.safetyRatings;\n /**\n * Candidates should always have content and parts, but this handles\n * possible malformed responses.\n */\n if (candidate.content && candidate.content.parts) {\n if (!aggregatedResponse.candidates[candidateIndex].content) {\n aggregatedResponse.candidates[candidateIndex].content = {\n role: candidate.content.role || \"user\",\n parts: [],\n };\n }\n const newPart = {};\n for (const part of candidate.content.parts) {\n if (part.text) {\n newPart.text = part.text;\n }\n if (part.functionCall) {\n newPart.functionCall = part.functionCall;\n }\n if (part.executableCode) {\n newPart.executableCode = part.executableCode;\n }\n if (part.codeExecutionResult) {\n newPart.codeExecutionResult = part.codeExecutionResult;\n }\n if (Object.keys(newPart).length === 0) {\n newPart.text = \"\";\n }\n aggregatedResponse.candidates[candidateIndex].content.parts.push(newPart);\n }\n }\n }\n candidateIndex++;\n }\n if (response.usageMetadata) {\n aggregatedResponse.usageMetadata = response.usageMetadata;\n }\n }\n return aggregatedResponse;\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nasync function generateContentStream(apiKey, model, params, requestOptions) {\n const response = await makeModelRequest(model, Task.STREAM_GENERATE_CONTENT, apiKey, \n /* stream */ true, JSON.stringify(params), requestOptions);\n return processStream(response);\n}\nasync function generateContent(apiKey, model, params, requestOptions) {\n const response = await makeModelRequest(model, Task.GENERATE_CONTENT, apiKey, \n /* stream */ false, JSON.stringify(params), requestOptions);\n const responseJson = await response.json();\n const enhancedResponse = addHelpers(responseJson);\n return {\n response: enhancedResponse,\n };\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nfunction formatSystemInstruction(input) {\n // null or undefined\n if (input == null) {\n return undefined;\n }\n else if (typeof input === \"string\") {\n return { role: \"system\", parts: [{ text: input }] };\n }\n else if (input.text) {\n return { role: \"system\", parts: [input] };\n }\n else if (input.parts) {\n if (!input.role) {\n return { role: \"system\", parts: input.parts };\n }\n else {\n return input;\n }\n }\n}\nfunction formatNewContent(request) {\n let newParts = [];\n if (typeof request === \"string\") {\n newParts = [{ text: request }];\n }\n else {\n for (const partOrString of request) {\n if (typeof partOrString === \"string\") {\n newParts.push({ text: partOrString });\n }\n else {\n newParts.push(partOrString);\n }\n }\n }\n return assignRoleToPartsAndValidateSendMessageRequest(newParts);\n}\n/**\n * When multiple Part types (i.e. FunctionResponsePart and TextPart) are\n * passed in a single Part array, we may need to assign different roles to each\n * part. Currently only FunctionResponsePart requires a role other than 'user'.\n * @private\n * @param parts Array of parts to pass to the model\n * @returns Array of content items\n */\nfunction assignRoleToPartsAndValidateSendMessageRequest(parts) {\n const userContent = { role: \"user\", parts: [] };\n const functionContent = { role: \"function\", parts: [] };\n let hasUserContent = false;\n let hasFunctionContent = false;\n for (const part of parts) {\n if (\"functionResponse\" in part) {\n functionContent.parts.push(part);\n hasFunctionContent = true;\n }\n else {\n userContent.parts.push(part);\n hasUserContent = true;\n }\n }\n if (hasUserContent && hasFunctionContent) {\n throw new GoogleGenerativeAIError(\"Within a single message, FunctionResponse cannot be mixed with other type of part in the request for sending chat message.\");\n }\n if (!hasUserContent && !hasFunctionContent) {\n throw new GoogleGenerativeAIError(\"No content is provided for sending chat message.\");\n }\n if (hasUserContent) {\n return userContent;\n }\n return functionContent;\n}\nfunction formatCountTokensInput(params, modelParams) {\n var _a;\n let formattedGenerateContentRequest = {\n model: modelParams === null || modelParams === void 0 ? void 0 : modelParams.model,\n generationConfig: modelParams === null || modelParams === void 0 ? void 0 : modelParams.generationConfig,\n safetySettings: modelParams === null || modelParams === void 0 ? void 0 : modelParams.safetySettings,\n tools: modelParams === null || modelParams === void 0 ? void 0 : modelParams.tools,\n toolConfig: modelParams === null || modelParams === void 0 ? void 0 : modelParams.toolConfig,\n systemInstruction: modelParams === null || modelParams === void 0 ? void 0 : modelParams.systemInstruction,\n cachedContent: (_a = modelParams === null || modelParams === void 0 ? void 0 : modelParams.cachedContent) === null || _a === void 0 ? void 0 : _a.name,\n contents: [],\n };\n const containsGenerateContentRequest = params.generateContentRequest != null;\n if (params.contents) {\n if (containsGenerateContentRequest) {\n throw new GoogleGenerativeAIRequestInputError(\"CountTokensRequest must have one of contents or generateContentRequest, not both.\");\n }\n formattedGenerateContentRequest.contents = params.contents;\n }\n else if (containsGenerateContentRequest) {\n formattedGenerateContentRequest = Object.assign(Object.assign({}, formattedGenerateContentRequest), params.generateContentRequest);\n }\n else {\n // Array or string\n const content = formatNewContent(params);\n formattedGenerateContentRequest.contents = [content];\n }\n return { generateContentRequest: formattedGenerateContentRequest };\n}\nfunction formatGenerateContentInput(params) {\n let formattedRequest;\n if (params.contents) {\n formattedRequest = params;\n }\n else {\n // Array or string\n const content = formatNewContent(params);\n formattedRequest = { contents: [content] };\n }\n if (params.systemInstruction) {\n formattedRequest.systemInstruction = formatSystemInstruction(params.systemInstruction);\n }\n return formattedRequest;\n}\nfunction formatEmbedContentInput(params) {\n if (typeof params === \"string\" || Array.isArray(params)) {\n const content = formatNewContent(params);\n return { content };\n }\n return params;\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n// https://ai.google.dev/api/rest/v1beta/Content#part\nconst VALID_PART_FIELDS = [\n \"text\",\n \"inlineData\",\n \"functionCall\",\n \"functionResponse\",\n \"executableCode\",\n \"codeExecutionResult\",\n];\nconst VALID_PARTS_PER_ROLE = {\n user: [\"text\", \"inlineData\"],\n function: [\"functionResponse\"],\n model: [\"text\", \"functionCall\", \"executableCode\", \"codeExecutionResult\"],\n // System instructions shouldn't be in history anyway.\n system: [\"text\"],\n};\nfunction validateChatHistory(history) {\n let prevContent = false;\n for (const currContent of history) {\n const { role, parts } = currContent;\n if (!prevContent && role !== \"user\") {\n throw new GoogleGenerativeAIError(`First content should be with role 'user', got ${role}`);\n }\n if (!POSSIBLE_ROLES.includes(role)) {\n throw new GoogleGenerativeAIError(`Each item should include role field. Got ${role} but valid roles are: ${JSON.stringify(POSSIBLE_ROLES)}`);\n }\n if (!Array.isArray(parts)) {\n throw new GoogleGenerativeAIError(\"Content should have 'parts' property with an array of Parts\");\n }\n if (parts.length === 0) {\n throw new GoogleGenerativeAIError(\"Each Content should have at least one part\");\n }\n const countFields = {\n text: 0,\n inlineData: 0,\n functionCall: 0,\n functionResponse: 0,\n fileData: 0,\n executableCode: 0,\n codeExecutionResult: 0,\n };\n for (const part of parts) {\n for (const key of VALID_PART_FIELDS) {\n if (key in part) {\n countFields[key] += 1;\n }\n }\n }\n const validParts = VALID_PARTS_PER_ROLE[role];\n for (const key of VALID_PART_FIELDS) {\n if (!validParts.includes(key) && countFields[key] > 0) {\n throw new GoogleGenerativeAIError(`Content with role '${role}' can't contain '${key}' part`);\n }\n }\n prevContent = true;\n }\n}\n/**\n * Returns true if the response is valid (could be appended to the history), flase otherwise.\n */\nfunction isValidResponse(response) {\n var _a;\n if (response.candidates === undefined || response.candidates.length === 0) {\n return false;\n }\n const content = (_a = response.candidates[0]) === null || _a === void 0 ? void 0 : _a.content;\n if (content === undefined) {\n return false;\n }\n if (content.parts === undefined || content.parts.length === 0) {\n return false;\n }\n for (const part of content.parts) {\n if (part === undefined || Object.keys(part).length === 0) {\n return false;\n }\n if (part.text !== undefined && part.text === \"\") {\n return false;\n }\n }\n return true;\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Do not log a message for this error.\n */\nconst SILENT_ERROR = \"SILENT_ERROR\";\n/**\n * ChatSession class that enables sending chat messages and stores\n * history of sent and received messages so far.\n *\n * @public\n */\nclass ChatSession {\n constructor(apiKey, model, params, _requestOptions = {}) {\n this.model = model;\n this.params = params;\n this._requestOptions = _requestOptions;\n this._history = [];\n this._sendPromise = Promise.resolve();\n this._apiKey = apiKey;\n if (params === null || params === void 0 ? void 0 : params.history) {\n validateChatHistory(params.history);\n this._history = params.history;\n }\n }\n /**\n * Gets the chat history so far. Blocked prompts are not added to history.\n * Blocked candidates are not added to history, nor are the prompts that\n * generated them.\n */\n async getHistory() {\n await this._sendPromise;\n return this._history;\n }\n /**\n * Sends a chat message and receives a non-streaming\n * {@link GenerateContentResult}.\n *\n * Fields set in the optional {@link SingleRequestOptions} parameter will\n * take precedence over the {@link RequestOptions} values provided to\n * {@link GoogleGenerativeAI.getGenerativeModel }.\n */\n async sendMessage(request, requestOptions = {}) {\n var _a, _b, _c, _d, _e, _f;\n await this._sendPromise;\n const newContent = formatNewContent(request);\n const generateContentRequest = {\n safetySettings: (_a = this.params) === null || _a === void 0 ? void 0 : _a.safetySettings,\n generationConfig: (_b = this.params) === null || _b === void 0 ? void 0 : _b.generationConfig,\n tools: (_c = this.params) === null || _c === void 0 ? void 0 : _c.tools,\n toolConfig: (_d = this.params) === null || _d === void 0 ? void 0 : _d.toolConfig,\n systemInstruction: (_e = this.params) === null || _e === void 0 ? void 0 : _e.systemInstruction,\n cachedContent: (_f = this.params) === null || _f === void 0 ? void 0 : _f.cachedContent,\n contents: [...this._history, newContent],\n };\n const chatSessionRequestOptions = Object.assign(Object.assign({}, this._requestOptions), requestOptions);\n let finalResult;\n // Add onto the chain.\n this._sendPromise = this._sendPromise\n .then(() => generateContent(this._apiKey, this.model, generateContentRequest, chatSessionRequestOptions))\n .then((result) => {\n var _a;\n if (isValidResponse(result.response)) {\n this._history.push(newContent);\n const responseContent = Object.assign({ parts: [], \n // Response seems to come back without a role set.\n role: \"model\" }, (_a = result.response.candidates) === null || _a === void 0 ? void 0 : _a[0].content);\n this._history.push(responseContent);\n }\n else {\n const blockErrorMessage = formatBlockErrorMessage(result.response);\n if (blockErrorMessage) {\n console.warn(`sendMessage() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.`);\n }\n }\n finalResult = result;\n })\n .catch((e) => {\n // Resets _sendPromise to avoid subsequent calls failing and throw error.\n this._sendPromise = Promise.resolve();\n throw e;\n });\n await this._sendPromise;\n return finalResult;\n }\n /**\n * Sends a chat message and receives the response as a\n * {@link GenerateContentStreamResult} containing an iterable stream\n * and a response promise.\n *\n * Fields set in the optional {@link SingleRequestOptions} parameter will\n * take precedence over the {@link RequestOptions} values provided to\n * {@link GoogleGenerativeAI.getGenerativeModel }.\n */\n async sendMessageStream(request, requestOptions = {}) {\n var _a, _b, _c, _d, _e, _f;\n await this._sendPromise;\n const newContent = formatNewContent(request);\n const generateContentRequest = {\n safetySettings: (_a = this.params) === null || _a === void 0 ? void 0 : _a.safetySettings,\n generationConfig: (_b = this.params) === null || _b === void 0 ? void 0 : _b.generationConfig,\n tools: (_c = this.params) === null || _c === void 0 ? void 0 : _c.tools,\n toolConfig: (_d = this.params) === null || _d === void 0 ? void 0 : _d.toolConfig,\n systemInstruction: (_e = this.params) === null || _e === void 0 ? void 0 : _e.systemInstruction,\n cachedContent: (_f = this.params) === null || _f === void 0 ? void 0 : _f.cachedContent,\n contents: [...this._history, newContent],\n };\n const chatSessionRequestOptions = Object.assign(Object.assign({}, this._requestOptions), requestOptions);\n const streamPromise = generateContentStream(this._apiKey, this.model, generateContentRequest, chatSessionRequestOptions);\n // Add onto the chain.\n this._sendPromise = this._sendPromise\n .then(() => streamPromise)\n // This must be handled to avoid unhandled rejection, but jump\n // to the final catch block with a label to not log this error.\n .catch((_ignored) => {\n throw new Error(SILENT_ERROR);\n })\n .then((streamResult) => streamResult.response)\n .then((response) => {\n if (isValidResponse(response)) {\n this._history.push(newContent);\n const responseContent = Object.assign({}, response.candidates[0].content);\n // Response seems to come back without a role set.\n if (!responseContent.role) {\n responseContent.role = \"model\";\n }\n this._history.push(responseContent);\n }\n else {\n const blockErrorMessage = formatBlockErrorMessage(response);\n if (blockErrorMessage) {\n console.warn(`sendMessageStream() was unsuccessful. ${blockErrorMessage}. Inspect response object for details.`);\n }\n }\n })\n .catch((e) => {\n // Errors in streamPromise are already catchable by the user as\n // streamPromise is returned.\n // Avoid duplicating the error message in logs.\n if (e.message !== SILENT_ERROR) {\n // Users do not have access to _sendPromise to catch errors\n // downstream from streamPromise, so they should not throw.\n console.error(e);\n }\n });\n return streamPromise;\n }\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nasync function countTokens(apiKey, model, params, singleRequestOptions) {\n const response = await makeModelRequest(model, Task.COUNT_TOKENS, apiKey, false, JSON.stringify(params), singleRequestOptions);\n return response.json();\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nasync function embedContent(apiKey, model, params, requestOptions) {\n const response = await makeModelRequest(model, Task.EMBED_CONTENT, apiKey, false, JSON.stringify(params), requestOptions);\n return response.json();\n}\nasync function batchEmbedContents(apiKey, model, params, requestOptions) {\n const requestsWithModel = params.requests.map((request) => {\n return Object.assign(Object.assign({}, request), { model });\n });\n const response = await makeModelRequest(model, Task.BATCH_EMBED_CONTENTS, apiKey, false, JSON.stringify({ requests: requestsWithModel }), requestOptions);\n return response.json();\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Class for generative model APIs.\n * @public\n */\nclass GenerativeModel {\n constructor(apiKey, modelParams, _requestOptions = {}) {\n this.apiKey = apiKey;\n this._requestOptions = _requestOptions;\n if (modelParams.model.includes(\"/\")) {\n // Models may be named \"models/model-name\" or \"tunedModels/model-name\"\n this.model = modelParams.model;\n }\n else {\n // If path is not included, assume it's a non-tuned model.\n this.model = `models/${modelParams.model}`;\n }\n this.generationConfig = modelParams.generationConfig || {};\n this.safetySettings = modelParams.safetySettings || [];\n this.tools = modelParams.tools;\n this.toolConfig = modelParams.toolConfig;\n this.systemInstruction = formatSystemInstruction(modelParams.systemInstruction);\n this.cachedContent = modelParams.cachedContent;\n }\n /**\n * Makes a single non-streaming call to the model\n * and returns an object containing a single {@link GenerateContentResponse}.\n *\n * Fields set in the optional {@link SingleRequestOptions} parameter will\n * take precedence over the {@link RequestOptions} values provided to\n * {@link GoogleGenerativeAI.getGenerativeModel }.\n */\n async generateContent(request, requestOptions = {}) {\n var _a;\n const formattedParams = formatGenerateContentInput(request);\n const generativeModelRequestOptions = Object.assign(Object.assign({}, this._requestOptions), requestOptions);\n return generateContent(this.apiKey, this.model, Object.assign({ generationConfig: this.generationConfig, safetySettings: this.safetySettings, tools: this.tools, toolConfig: this.toolConfig, systemInstruction: this.systemInstruction, cachedContent: (_a = this.cachedContent) === null || _a === void 0 ? void 0 : _a.name }, formattedParams), generativeModelRequestOptions);\n }\n /**\n * Makes a single streaming call to the model and returns an object\n * containing an iterable stream that iterates over all chunks in the\n * streaming response as well as a promise that returns the final\n * aggregated response.\n *\n * Fields set in the optional {@link SingleRequestOptions} parameter will\n * take precedence over the {@link RequestOptions} values provided to\n * {@link GoogleGenerativeAI.getGenerativeModel }.\n */\n async generateContentStream(request, requestOptions = {}) {\n var _a;\n const formattedParams = formatGenerateContentInput(request);\n const generativeModelRequestOptions = Object.assign(Object.assign({}, this._requestOptions), requestOptions);\n return generateContentStream(this.apiKey, this.model, Object.assign({ generationConfig: this.generationConfig, safetySettings: this.safetySettings, tools: this.tools, toolConfig: this.toolConfig, systemInstruction: this.systemInstruction, cachedContent: (_a = this.cachedContent) === null || _a === void 0 ? void 0 : _a.name }, formattedParams), generativeModelRequestOptions);\n }\n /**\n * Gets a new {@link ChatSession} instance which can be used for\n * multi-turn chats.\n */\n startChat(startChatParams) {\n var _a;\n return new ChatSession(this.apiKey, this.model, Object.assign({ generationConfig: this.generationConfig, safetySettings: this.safetySettings, tools: this.tools, toolConfig: this.toolConfig, systemInstruction: this.systemInstruction, cachedContent: (_a = this.cachedContent) === null || _a === void 0 ? void 0 : _a.name }, startChatParams), this._requestOptions);\n }\n /**\n * Counts the tokens in the provided request.\n *\n * Fields set in the optional {@link SingleRequestOptions} parameter will\n * take precedence over the {@link RequestOptions} values provided to\n * {@link GoogleGenerativeAI.getGenerativeModel }.\n */\n async countTokens(request, requestOptions = {}) {\n const formattedParams = formatCountTokensInput(request, {\n model: this.model,\n generationConfig: this.generationConfig,\n safetySettings: this.safetySettings,\n tools: this.tools,\n toolConfig: this.toolConfig,\n systemInstruction: this.systemInstruction,\n cachedContent: this.cachedContent,\n });\n const generativeModelRequestOptions = Object.assign(Object.assign({}, this._requestOptions), requestOptions);\n return countTokens(this.apiKey, this.model, formattedParams, generativeModelRequestOptions);\n }\n /**\n * Embeds the provided content.\n *\n * Fields set in the optional {@link SingleRequestOptions} parameter will\n * take precedence over the {@link RequestOptions} values provided to\n * {@link GoogleGenerativeAI.getGenerativeModel }.\n */\n async embedContent(request, requestOptions = {}) {\n const formattedParams = formatEmbedContentInput(request);\n const generativeModelRequestOptions = Object.assign(Object.assign({}, this._requestOptions), requestOptions);\n return embedContent(this.apiKey, this.model, formattedParams, generativeModelRequestOptions);\n }\n /**\n * Embeds an array of {@link EmbedContentRequest}s.\n *\n * Fields set in the optional {@link SingleRequestOptions} parameter will\n * take precedence over the {@link RequestOptions} values provided to\n * {@link GoogleGenerativeAI.getGenerativeModel }.\n */\n async batchEmbedContents(batchEmbedContentRequest, requestOptions = {}) {\n const generativeModelRequestOptions = Object.assign(Object.assign({}, this._requestOptions), requestOptions);\n return batchEmbedContents(this.apiKey, this.model, batchEmbedContentRequest, generativeModelRequestOptions);\n }\n}\n\n/**\n * @license\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Top-level class for this SDK\n * @public\n */\nclass GoogleGenerativeAI {\n constructor(apiKey) {\n this.apiKey = apiKey;\n }\n /**\n * Gets a {@link GenerativeModel} instance for the provided model name.\n */\n getGenerativeModel(modelParams, requestOptions) {\n if (!modelParams.model) {\n throw new GoogleGenerativeAIError(`Must provide a model name. ` +\n `Example: genai.getGenerativeModel({ model: 'my-model-name' })`);\n }\n return new GenerativeModel(this.apiKey, modelParams, requestOptions);\n }\n /**\n * Creates a {@link GenerativeModel} instance from provided content cache.\n */\n getGenerativeModelFromCachedContent(cachedContent, modelParams, requestOptions) {\n if (!cachedContent.name) {\n throw new GoogleGenerativeAIRequestInputError(\"Cached content must contain a `name` field.\");\n }\n if (!cachedContent.model) {\n throw new GoogleGenerativeAIRequestInputError(\"Cached content must contain a `model` field.\");\n }\n /**\n * Not checking tools and toolConfig for now as it would require a deep\n * equality comparison and isn't likely to be a common case.\n */\n const disallowedDuplicates = [\"model\", \"systemInstruction\"];\n for (const key of disallowedDuplicates) {\n if ((modelParams === null || modelParams === void 0 ? void 0 : modelParams[key]) &&\n cachedContent[key] &&\n (modelParams === null || modelParams === void 0 ? void 0 : modelParams[key]) !== cachedContent[key]) {\n if (key === \"model\") {\n const modelParamsComp = modelParams.model.startsWith(\"models/\")\n ? modelParams.model.replace(\"models/\", \"\")\n : modelParams.model;\n const cachedContentComp = cachedContent.model.startsWith(\"models/\")\n ? cachedContent.model.replace(\"models/\", \"\")\n : cachedContent.model;\n if (modelParamsComp === cachedContentComp) {\n continue;\n }\n }\n throw new GoogleGenerativeAIRequestInputError(`Different value for \"${key}\" specified in modelParams` +\n ` (${modelParams[key]}) and cachedContent (${cachedContent[key]})`);\n }\n }\n const modelParamsFromCache = Object.assign(Object.assign({}, modelParams), { model: cachedContent.model, tools: cachedContent.tools, toolConfig: cachedContent.toolConfig, systemInstruction: cachedContent.systemInstruction, cachedContent });\n return new GenerativeModel(this.apiKey, modelParamsFromCache, requestOptions);\n }\n}\n\nexport { BlockReason, ChatSession, DynamicRetrievalMode, ExecutableCodeLanguage, FinishReason, FunctionCallingMode, GenerativeModel, GoogleGenerativeAI, GoogleGenerativeAIAbortError, GoogleGenerativeAIError, GoogleGenerativeAIFetchError, GoogleGenerativeAIRequestInputError, GoogleGenerativeAIResponseError, HarmBlockThreshold, HarmCategory, HarmProbability, Outcome, POSSIBLE_ROLES, SchemaType, TaskType };\n//# sourceMappingURL=index.mjs.map\n","import debug from \"debug\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { ExclusionService } from \"./ExclusionService\";\nimport { performIncrementalUpdate } from \"../DryScanUpdater\";\nimport { DuplicationCache } from \"./DuplicationCache\";\n\nconst log = debug(\"DryScan:UpdateService\");\n\nexport class UpdateService {\n constructor(\n private readonly deps: DryScanServiceDeps,\n private readonly exclusionService: ExclusionService\n ) {}\n\n async updateIndex(): Promise<void> {\n const extractor = this.deps.extractor;\n const cache = DuplicationCache.getInstance();\n\n try {\n const changeSet = await performIncrementalUpdate(this.deps.repoPath, extractor, this.deps.db);\n await this.exclusionService.cleanupExcludedFiles();\n await cache.invalidate([...changeSet.changed, ...changeSet.deleted]);\n } catch (err) {\n log(\"Error during index update:\", err);\n throw err;\n }\n }\n}","import path from \"path\";\nimport fs from \"fs/promises\";\nimport debug from \"debug\";\nimport { IndexUnit } from \"./types\";\nimport { IndexUnitExtractor } from \"./IndexUnitExtractor\";\nimport { DryScanDatabase } from \"./db/DryScanDatabase\";\nimport { FileEntity } from \"./db/entities/FileEntity\";\nimport { EmbeddingService } from \"./services/EmbeddingService\";\n\nconst log = debug(\"DryScan:Updater\");\n\n/**\n * DryScan Updater Module\n * \n * This module contains all incremental update logic for DryScan.\n * Separated from DryScan.ts to keep that file focused on core operations.\n * \n * Represents the result of change detection.\n * Categorizes files into added, changed, deleted, and unchanged.\n */\nexport interface FileChangeSet {\n added: string[];\n changed: string[];\n deleted: string[];\n unchanged: string[];\n}\n\n/**\n * Detects which files have been added, changed, or deleted since last scan.\n * Uses mtime as fast check, then checksum for verification.\n * \n * @param repoPath - Root path of the repository\n * @param extractor - Index unit extractor instance for file operations\n * @param db - Database instance for retrieving tracked files\n * @returns Change set with categorized file paths\n */\nexport async function detectFileChanges(\n repoPath: string,\n extractor: IndexUnitExtractor,\n db: DryScanDatabase\n): Promise<FileChangeSet> {\n // Get current files in repository\n const currentFiles = await extractor.listSourceFiles(repoPath);\n const currentFileSet = new Set(currentFiles);\n\n // Get tracked files from database\n const trackedFiles = await db.getAllFiles();\n const trackedFileMap = new Map(trackedFiles.map(f => [f.filePath, f]));\n\n const added: string[] = [];\n const changed: string[] = [];\n const unchanged: string[] = [];\n\n // Check each current file\n for (const filePath of currentFiles) {\n const tracked = trackedFileMap.get(filePath);\n \n if (!tracked) {\n // New file\n added.push(filePath);\n continue;\n }\n\n // Check if file changed using mtime first (fast check)\n const fullPath = path.join(repoPath, filePath);\n const stat = await fs.stat(fullPath);\n \n if (stat.mtimeMs !== tracked.mtime) {\n // Mtime changed, verify with checksum\n const currentChecksum = await extractor.computeChecksum(fullPath);\n if (currentChecksum !== tracked.checksum) {\n changed.push(filePath);\n } else {\n // Mtime changed but content same\n unchanged.push(filePath);\n }\n } else {\n unchanged.push(filePath);\n }\n }\n\n // Find deleted files\n const deleted = trackedFiles\n .map(f => f.filePath)\n .filter(fp => !currentFileSet.has(fp));\n\n return { added, changed, deleted, unchanged };\n}\n\n/**\n * Extracts index units from a list of files.\n * Used during incremental updates.\n * \n * @param filePaths - Array of relative file paths to extract from\n * @param extractor - Index unit extractor instance\n * @returns Array of extracted units\n */\nexport async function extractUnitsFromFiles(\n filePaths: string[],\n extractor: IndexUnitExtractor\n): Promise<IndexUnit[]> {\n const allUnits: IndexUnit[] = [];\n \n for (const relPath of filePaths) {\n const functions = await extractor.scan(relPath);\n allUnits.push(...functions);\n }\n \n return allUnits;\n}\n\n/**\n * Updates file tracking metadata after processing changes.\n * Removes deleted files, updates changed files, adds new files.\n * \n * @param changeSet - Set of file changes to apply\n * @param repoPath - Root path of the repository\n * @param extractor - Index unit extractor for checksum computation\n * @param db - Database instance for file tracking\n */\nexport async function updateFileTracking(\n changeSet: FileChangeSet,\n repoPath: string,\n extractor: IndexUnitExtractor,\n db: DryScanDatabase\n): Promise<void> {\n // Remove deleted files\n if (changeSet.deleted.length > 0) {\n if (typeof (db as any).removeFilesByFilePaths === \"function\") {\n await (db as any).removeFilesByFilePaths(changeSet.deleted);\n } else if (typeof (db as any).removeFiles === \"function\") {\n await (db as any).removeFiles(changeSet.deleted);\n }\n }\n\n // Create file entities for new and changed files\n const filesToTrack = [...changeSet.added, ...changeSet.changed];\n if (filesToTrack.length > 0) {\n const fileEntities: FileEntity[] = [];\n \n for (const relPath of filesToTrack) {\n const fullPath = path.join(repoPath, relPath);\n const stat = await fs.stat(fullPath);\n const checksum = await extractor.computeChecksum(fullPath);\n \n const fileEntity = new FileEntity();\n fileEntity.filePath = relPath;\n fileEntity.checksum = checksum;\n fileEntity.mtime = stat.mtimeMs;\n \n fileEntities.push(fileEntity);\n }\n \n await db.saveFiles(fileEntities);\n }\n}\n\n/**\n * Performs incremental update of the DryScan index.\n * Detects file changes and reprocesses only affected files.\n * \n * @param repoPath - Root path of the repository\n * @param extractor - Index unit extractor instance\n * @param db - Database instance (must be initialized)\n */\nexport async function performIncrementalUpdate(\n repoPath: string,\n extractor: IndexUnitExtractor,\n db: DryScanDatabase,\n): Promise<FileChangeSet> {\n log(\"Starting incremental update\");\n const embeddingService = new EmbeddingService(repoPath);\n \n // Step 1: Detect changes\n const changeSet = await detectFileChanges(repoPath, extractor, db);\n \n if (changeSet.changed.length === 0 && \n changeSet.added.length === 0 && \n changeSet.deleted.length === 0) {\n log(\"No changes detected. Index is up to date.\");\n return changeSet;\n }\n\n log(`Changes detected: ${changeSet.added.length} added, ${changeSet.changed.length} changed, ${changeSet.deleted.length} deleted`);\n\n // Step 2: Remove old data for changed/deleted files\n const filesToRemove = [...changeSet.changed, ...changeSet.deleted];\n if (filesToRemove.length > 0) {\n await db.removeUnitsByFilePaths(filesToRemove);\n log(`Removed units from ${filesToRemove.length} files`);\n }\n\n // Step 3: Extract functions from new/changed files\n const filesToProcess = [...changeSet.added, ...changeSet.changed];\n if (filesToProcess.length > 0) {\n const newUnits = await extractUnitsFromFiles(filesToProcess, extractor);\n await db.saveUnits(newUnits);\n log(`Extracted and saved ${newUnits.length} units from ${filesToProcess.length} files`);\n\n // Step 4: Recompute embeddings for affected units only\n const total = newUnits.length;\n if (total > 0) {\n log(`Recomputing embeddings for ${total} units`);\n const progressInterval = Math.max(1, Math.ceil(total / 10));\n const updatedWithEmbeddings = [] as IndexUnit[];\n\n for (let i = 0; i < total; i++) {\n const unit = newUnits[i];\n try {\n const enriched = await embeddingService.addEmbedding(unit);\n updatedWithEmbeddings.push(enriched);\n } catch (err: any) {\n console.error(\n `[DryScan] embedding failed for ${unit.filePath} (${unit.name}): ${err?.message || err}`\n );\n throw err;\n }\n\n const completed = i + 1;\n if (completed === total || completed % progressInterval === 0) {\n const pct = Math.floor((completed / total) * 100);\n console.log(`[DryScan] Incremental embeddings ${completed}/${total} (${pct}%)`);\n }\n }\n\n await db.updateUnits(updatedWithEmbeddings);\n log(`Recomputed embeddings for ${updatedWithEmbeddings.length} units`);\n }\n }\n\n // Step 5: Update file tracking\n await updateFileTracking(changeSet, repoPath, extractor, db);\n log(\"Incremental update complete\");\n\n return changeSet;\n}\n","import { DuplicateGroup } from \"../types\";\n\n/**\n * In-memory cache for duplicate comparison scores.\n * Stores a global map of comparison keys and a per-file index for fast invalidation.\n */\nexport class DuplicationCache {\n private static instance: DuplicationCache | null = null;\n\n private readonly comparisons = new Map<string, number>();\n private readonly fileIndex = new Map<string, Set<string>>();\n private initialized = false;\n\n static getInstance(): DuplicationCache {\n if (!DuplicationCache.instance) {\n DuplicationCache.instance = new DuplicationCache();\n }\n return DuplicationCache.instance;\n }\n\n /**\n * Updates the cache with fresh duplicate groups. Not awaited by callers to avoid blocking.\n */\n async update(groups: DuplicateGroup[]): Promise<void> {\n if (!groups) return;\n\n for (const group of groups) {\n const key = this.makeKey(group.left.id, group.right.id);\n this.comparisons.set(key, group.similarity);\n this.addKeyForFile(group.left.filePath, key);\n this.addKeyForFile(group.right.filePath, key);\n }\n\n this.initialized = this.initialized || groups.length > 0;\n }\n\n /**\n * Retrieves a cached similarity if present and valid for both file paths.\n * Returns null when the cache has not been initialized or when the pair is missing.\n */\n get(leftId: string, rightId: string, leftFilePath: string, rightFilePath: string): number | null {\n if (!this.initialized) return null;\n\n const key = this.makeKey(leftId, rightId);\n if (!this.fileHasKey(leftFilePath, key) || !this.fileHasKey(rightFilePath, key)) {\n return null;\n }\n\n const value = this.comparisons.get(key);\n return typeof value === \"number\" ? value : null;\n }\n\n /**\n * Invalidates all cached comparisons involving the provided file paths.\n */\n async invalidate(paths: string[]): Promise<void> {\n if (!this.initialized || !paths || paths.length === 0) return;\n\n const unique = new Set(paths);\n for (const filePath of unique) {\n const keys = this.fileIndex.get(filePath);\n if (!keys) continue;\n\n for (const key of keys) {\n this.comparisons.delete(key);\n for (const [otherPath, otherKeys] of this.fileIndex.entries()) {\n if (otherKeys.delete(key) && otherKeys.size === 0) {\n this.fileIndex.delete(otherPath);\n }\n }\n }\n\n this.fileIndex.delete(filePath);\n }\n\n if (this.comparisons.size === 0) {\n this.initialized = false;\n }\n }\n\n /**\n * Clears all cached data. Intended for test setup.\n */\n clear(): void {\n this.comparisons.clear();\n this.fileIndex.clear();\n this.initialized = false;\n }\n\n private addKeyForFile(filePath: string, key: string): void {\n const current = this.fileIndex.get(filePath) ?? new Set<string>();\n current.add(key);\n this.fileIndex.set(filePath, current);\n }\n\n private fileHasKey(filePath: string, key: string): boolean {\n const keys = this.fileIndex.get(filePath);\n return keys ? keys.has(key) : false;\n }\n\n private makeKey(leftId: string, rightId: string): string {\n return [leftId, rightId].sort().join(\"::\");\n }\n}\n","import debug from \"debug\";\nimport shortUuid from \"short-uuid\";\nimport { cosineSimilarity } from \"@langchain/core/utils/math\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { DuplicateAnalysisResult, DuplicateGroup, DuplicationScore, IndexUnit, IndexUnitType } from \"../types\";\nimport { indexConfig } from \"../config/indexConfig\";\nimport { DryConfig } from \"../types\";\nimport { DuplicationCache } from \"./DuplicationCache\";\n\nconst log = debug(\"DryScan:DuplicateService\");\n\nexport class DuplicateService {\n private config?: DryConfig;\n private readonly cache = DuplicationCache.getInstance();\n\n constructor(private readonly deps: DryScanServiceDeps) {}\n\n async findDuplicates(config: DryConfig): Promise<DuplicateAnalysisResult> {\n this.config = config;\n const allUnits = await this.deps.db.getAllUnits();\n if (allUnits.length < 2) {\n const score = this.computeDuplicationScore([], allUnits);\n return { duplicates: [], score };\n }\n\n const thresholds = this.resolveThresholds(config.threshold);\n const duplicates = this.computeDuplicates(allUnits, thresholds);\n const filteredDuplicates = duplicates.filter((group) => !this.isGroupExcluded(group));\n log(\"Found %d duplicate groups\", filteredDuplicates.length);\n\n // Update cache asynchronously; no need to block the main flow.\n this.cache.update(filteredDuplicates).catch((err) => log(\"Cache update failed: %O\", err));\n\n const score = this.computeDuplicationScore(filteredDuplicates, allUnits);\n return { duplicates: filteredDuplicates, score };\n }\n\n private resolveThresholds(functionThreshold?: number): { function: number; block: number; class: number } {\n const defaults = indexConfig.thresholds;\n const clamp = (value: number) => Math.min(1, Math.max(0, value));\n\n const base = functionThreshold ?? defaults.function;\n const blockOffset = defaults.block - defaults.function;\n const classOffset = defaults.class - defaults.function;\n\n const functionThresholdValue = clamp(base);\n return {\n function: functionThresholdValue,\n block: clamp(functionThresholdValue + blockOffset),\n class: clamp(functionThresholdValue + classOffset),\n };\n }\n\n private computeDuplicates(\n units: IndexUnit[],\n thresholds: { function: number; block: number; class: number }\n ): DuplicateGroup[] {\n const duplicates: DuplicateGroup[] = [];\n const byType = new Map<IndexUnitType, IndexUnit[]>();\n\n for (const unit of units) {\n const list = byType.get(unit.unitType) ?? [];\n list.push(unit);\n byType.set(unit.unitType, list);\n }\n\n for (const [type, typedUnits] of byType.entries()) {\n const threshold = this.getThreshold(type, thresholds);\n\n for (let i = 0; i < typedUnits.length; i++) {\n for (let j = i + 1; j < typedUnits.length; j++) {\n const left = typedUnits[i];\n const right = typedUnits[j];\n\n if (this.shouldSkipComparison(left, right)) continue;\n\n const cached = this.cache.get(left.id, right.id, left.filePath, right.filePath);\n let similarity: number | null = null;\n\n if (cached !== null) {\n similarity = cached;\n } else {\n if (!left.embedding || !right.embedding) continue;\n similarity = this.computeWeightedSimilarity(left, right);\n }\n\n if (similarity === null) continue;\n\n if (similarity >= threshold) {\n const exclusionString = this.deps.pairing.pairKeyForUnits(left, right);\n if (!exclusionString) continue;\n\n duplicates.push({\n id: `${left.id}::${right.id}`,\n similarity,\n shortId: shortUuid.generate(),\n exclusionString,\n left: {\n id: left.id,\n name: left.name,\n filePath: left.filePath,\n startLine: left.startLine,\n endLine: left.endLine,\n code: left.code,\n unitType: left.unitType,\n },\n right: {\n id: right.id,\n name: right.name,\n filePath: right.filePath,\n startLine: right.startLine,\n endLine: right.endLine,\n code: right.code,\n unitType: right.unitType,\n },\n });\n }\n }\n }\n }\n\n return duplicates.sort((a, b) => b.similarity - a.similarity);\n }\n\n private isGroupExcluded(group: DuplicateGroup): boolean {\n const config = this.config;\n if (!config || !config.excludedPairs || config.excludedPairs.length === 0) return false;\n const key = this.deps.pairing.pairKeyForUnits(group.left, group.right);\n if (!key) return false;\n const actual = this.deps.pairing.parsePairKey(key);\n if (!actual) return false;\n return config.excludedPairs.some((entry) => {\n const parsed = this.deps.pairing.parsePairKey(entry);\n return parsed ? this.deps.pairing.pairKeyMatches(actual, parsed) : false;\n });\n }\n\n private getThreshold(type: IndexUnitType, thresholds: { function: number; block: number; class: number }): number {\n if (type === IndexUnitType.CLASS) return thresholds.class;\n if (type === IndexUnitType.BLOCK) return thresholds.block;\n return thresholds.function;\n }\n\n private computeWeightedSimilarity(left: IndexUnit, right: IndexUnit): number {\n const selfSimilarity = this.similarityWithFallback(left, right);\n\n if (left.unitType === IndexUnitType.CLASS) {\n return selfSimilarity * indexConfig.weights.class.self;\n }\n\n if (left.unitType === IndexUnitType.FUNCTION) {\n const weights = indexConfig.weights.function;\n const hasParentClass = !!this.findParentOfType(left, IndexUnitType.CLASS) && !!this.findParentOfType(right, IndexUnitType.CLASS);\n const parentClassSimilarity = hasParentClass ? this.parentSimilarity(left, right, IndexUnitType.CLASS) : 0;\n\n // Re-normalize weights when parent context is missing, so standalone units aren't penalized.\n const totalWeight = weights.self + (hasParentClass ? weights.parentClass : 0);\n return ((weights.self * selfSimilarity) + (hasParentClass ? (weights.parentClass * parentClassSimilarity) : 0)) / totalWeight;\n }\n\n const weights = indexConfig.weights.block;\n const hasParentFunction = !!this.findParentOfType(left, IndexUnitType.FUNCTION) && !!this.findParentOfType(right, IndexUnitType.FUNCTION);\n const hasParentClass = !!this.findParentOfType(left, IndexUnitType.CLASS) && !!this.findParentOfType(right, IndexUnitType.CLASS);\n const parentFuncSim = hasParentFunction ? this.parentSimilarity(left, right, IndexUnitType.FUNCTION) : 0;\n const parentClassSim = hasParentClass ? this.parentSimilarity(left, right, IndexUnitType.CLASS) : 0;\n\n // Re-normalize weights when some parent context is missing.\n const totalWeight =\n weights.self +\n (hasParentFunction ? weights.parentFunction : 0) +\n (hasParentClass ? weights.parentClass : 0);\n\n return (\n (weights.self * selfSimilarity) +\n (hasParentFunction ? (weights.parentFunction * parentFuncSim) : 0) +\n (hasParentClass ? (weights.parentClass * parentClassSim) : 0)\n ) / totalWeight;\n }\n\n private parentSimilarity(left: IndexUnit, right: IndexUnit, targetType: IndexUnitType): number {\n const leftParent = this.findParentOfType(left, targetType);\n const rightParent = this.findParentOfType(right, targetType);\n if (!leftParent || !rightParent) return 0;\n return this.similarityWithFallback(leftParent, rightParent);\n }\n\n private similarityWithFallback(left: IndexUnit, right: IndexUnit): number {\n const leftHasEmbedding = this.hasVector(left);\n const rightHasEmbedding = this.hasVector(right);\n\n if (leftHasEmbedding && rightHasEmbedding) {\n return cosineSimilarity([left.embedding as number[]], [right.embedding as number[]])[0][0];\n }\n\n return this.childSimilarity(left, right);\n }\n\n private childSimilarity(left: IndexUnit, right: IndexUnit): number {\n const leftChildren = left.children ?? [];\n const rightChildren = right.children ?? [];\n if (leftChildren.length === 0 || rightChildren.length === 0) return 0;\n\n let best = 0;\n for (const lChild of leftChildren) {\n for (const rChild of rightChildren) {\n if (lChild.unitType !== rChild.unitType) continue;\n const sim = this.similarityWithFallback(lChild, rChild);\n if (sim > best) best = sim;\n }\n }\n return best;\n }\n\n private hasVector(unit: IndexUnit): boolean {\n return Array.isArray(unit.embedding) && unit.embedding.length > 0;\n }\n\n private shouldSkipComparison(left: IndexUnit, right: IndexUnit): boolean {\n if (left.unitType !== IndexUnitType.BLOCK || right.unitType !== IndexUnitType.BLOCK) {\n return false;\n }\n\n if (left.filePath !== right.filePath) {\n return false;\n }\n\n const leftContainsRight = left.startLine <= right.startLine && left.endLine >= right.endLine;\n const rightContainsLeft = right.startLine <= left.startLine && right.endLine >= left.endLine;\n return leftContainsRight || rightContainsLeft;\n }\n\n private findParentOfType(unit: IndexUnit, targetType: IndexUnitType): IndexUnit | null {\n let current: IndexUnit | undefined | null = unit.parent;\n while (current) {\n if (current.unitType === targetType) return current;\n current = current.parent;\n }\n return null;\n }\n\n private computeDuplicationScore(duplicates: DuplicateGroup[], allUnits: IndexUnit[]): DuplicationScore {\n const totalLines = this.calculateTotalLines(allUnits);\n\n if (totalLines === 0 || duplicates.length === 0) {\n return {\n score: 0,\n grade: \"Excellent\",\n totalLines,\n duplicateLines: 0,\n duplicateGroups: 0,\n };\n }\n\n const weightedDuplicateLines = duplicates.reduce((sum, group) => {\n const leftLines = group.left.endLine - group.left.startLine + 1;\n const rightLines = group.right.endLine - group.right.startLine + 1;\n const avgLines = (leftLines + rightLines) / 2;\n return sum + group.similarity * avgLines;\n }, 0);\n\n const score = (weightedDuplicateLines / totalLines) * 100;\n const grade = this.getScoreGrade(score);\n\n return {\n score,\n grade,\n totalLines,\n duplicateLines: Math.round(weightedDuplicateLines),\n duplicateGroups: duplicates.length,\n };\n }\n\n private calculateTotalLines(units: IndexUnit[]): number {\n return units.reduce((sum, unit) => {\n const lines = unit.endLine - unit.startLine + 1;\n return sum + lines;\n }, 0);\n }\n\n private getScoreGrade(score: number): DuplicationScore[\"grade\"] {\n if (score < 5) return \"Excellent\";\n if (score < 15) return \"Good\";\n if (score < 30) return \"Fair\";\n if (score < 50) return \"Poor\";\n return \"Critical\";\n }\n}","import { DryConfig } from \"../types\";\nimport { configStore } from \"../config/configStore\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { IndexUnitType } from \"../types\";\nimport { minimatch } from \"minimatch\";\nimport { ParsedPairKey } from \"./PairingService\";\n\nexport class ExclusionService {\n private config?: DryConfig;\n\n constructor(private readonly deps: DryScanServiceDeps) {}\n\n async cleanupExcludedFiles(): Promise<void> {\n const config = await this.loadConfig();\n if (!config.excludedPaths || config.excludedPaths.length === 0) return;\n\n const units = await this.deps.db.getAllUnits();\n const files = await this.deps.db.getAllFiles();\n\n const unitPathsToRemove = new Set<string>();\n for (const unit of units) {\n if (this.pathExcluded(unit.filePath)) {\n unitPathsToRemove.add(unit.filePath);\n }\n }\n\n const filePathsToRemove = new Set<string>();\n for (const file of files) {\n if (this.pathExcluded(file.filePath)) {\n filePathsToRemove.add(file.filePath);\n }\n }\n\n const paths = [...new Set([...unitPathsToRemove, ...filePathsToRemove])];\n if (paths.length > 0) {\n await this.deps.db.removeUnitsByFilePaths(paths);\n await this.deps.db.removeFilesByFilePaths(paths);\n }\n }\n\n async cleanExclusions(): Promise<{ removed: number; kept: number }> {\n const config = await this.loadConfig();\n const units = await this.deps.db.getAllUnits();\n\n const actualPairsByType = {\n [IndexUnitType.CLASS]: this.buildPairKeys(units, IndexUnitType.CLASS),\n [IndexUnitType.FUNCTION]: this.buildPairKeys(units, IndexUnitType.FUNCTION),\n [IndexUnitType.BLOCK]: this.buildPairKeys(units, IndexUnitType.BLOCK),\n };\n\n const kept: string[] = [];\n const removed: string[] = [];\n\n for (const entry of config.excludedPairs || []) {\n const parsed = this.deps.pairing.parsePairKey(entry);\n if (!parsed) {\n removed.push(entry);\n continue;\n }\n\n const candidates = actualPairsByType[parsed.type];\n const matched = candidates.some((actual) => this.deps.pairing.pairKeyMatches(actual, parsed));\n if (matched) {\n kept.push(entry);\n } else {\n removed.push(entry);\n }\n }\n\n const nextConfig: DryConfig = { ...config, excludedPairs: kept };\n await configStore.save(this.deps.repoPath, nextConfig);\n this.config = nextConfig;\n\n return { removed: removed.length, kept: kept.length };\n }\n\n private pathExcluded(filePath: string): boolean {\n const config = this.config;\n if (!config || !config.excludedPaths || config.excludedPaths.length === 0) return false;\n return config.excludedPaths.some((pattern) => minimatch(filePath, pattern, { dot: true }));\n }\n\n private buildPairKeys(units: any[], type: IndexUnitType): ParsedPairKey[] {\n const typed = units.filter((u) => u.unitType === type);\n const pairs: ParsedPairKey[] = [];\n for (let i = 0; i < typed.length; i++) {\n for (let j = i + 1; j < typed.length; j++) {\n const key = this.deps.pairing.pairKeyForUnits(typed[i], typed[j]);\n const parsed = key ? this.deps.pairing.parsePairKey(key) : null;\n if (parsed) {\n pairs.push(parsed);\n }\n }\n }\n return pairs;\n }\n\n private async loadConfig(): Promise<DryConfig> {\n this.config = await configStore.get(this.deps.repoPath);\n return this.config;\n }\n}","import crypto from \"node:crypto\";\nimport debug from \"debug\";\nimport { minimatch } from \"minimatch\";\nimport { LanguageExtractor } from \"../extractors/LanguageExtractor\";\nimport { IndexUnitExtractor } from \"../IndexUnitExtractor\";\nimport { IndexUnit, IndexUnitType } from \"../types\";\nimport { BLOCK_HASH_ALGO } from \"../const\";\n\nconst log = debug(\"DryScan:pairs\");\n\ntype UnitLike = Pick<IndexUnit, \"unitType\" | \"filePath\" | \"name\" | \"code\">;\n\nexport interface ParsedPairKey {\n type: IndexUnitType;\n left: string;\n right: string;\n key: string;\n}\n\n/**\n * Service for building and parsing pair keys with extractor-aware labeling.\n */\nexport class PairingService {\n constructor(private readonly indexUnitExtractor: IndexUnitExtractor) {}\n\n /**\n * Creates a stable, order-independent key for two units of the same type.\n * Returns null when units differ in type so callers can skip invalid pairs.\n */\n pairKeyForUnits(left: UnitLike, right: UnitLike): string | null {\n if (left.unitType !== right.unitType) {\n log(\"Skipping pair with mismatched types: %s vs %s\", left.unitType, right.unitType);\n return null;\n }\n const type = left.unitType;\n const leftLabel = this.unitLabel(left);\n const rightLabel = this.unitLabel(right);\n const [a, b] = [leftLabel, rightLabel].sort();\n return `${type}|${a}|${b}`;\n }\n\n /**\n * Parses a raw pair key into its components, returning null for malformed values.\n * Sorting is applied so callers can compare pairs without worrying about order.\n */\n parsePairKey(value: string): ParsedPairKey | null {\n const parts = value.split(\"|\");\n if (parts.length !== 3) {\n log(\"Invalid pair key format: %s\", value);\n return null;\n }\n const [typeRaw, leftRaw, rightRaw] = parts;\n const type = this.stringToUnitType(typeRaw);\n if (!type) {\n log(\"Unknown unit type in pair key: %s\", typeRaw);\n return null;\n }\n const [left, right] = [leftRaw, rightRaw].sort();\n return { type, left, right, key: `${type}|${left}|${right}` };\n }\n\n /**\n * Checks whether an actual pair key satisfies a pattern, with glob matching for class paths.\n */\n pairKeyMatches(actual: ParsedPairKey, pattern: ParsedPairKey): boolean {\n if (actual.type !== pattern.type) return false;\n if (actual.type === IndexUnitType.CLASS) {\n // Allow glob matching for class file paths.\n const forward =\n minimatch(actual.left, pattern.left, { dot: true }) &&\n minimatch(actual.right, pattern.right, { dot: true });\n const swapped =\n minimatch(actual.left, pattern.right, { dot: true }) &&\n minimatch(actual.right, pattern.left, { dot: true });\n return forward || swapped;\n }\n\n // Functions and blocks use exact matching on canonical strings.\n return (\n (actual.left === pattern.left && actual.right === pattern.right) ||\n (actual.left === pattern.right && actual.right === pattern.left)\n );\n }\n\n /**\n * Derives a reversible, extractor-aware label for a unit.\n * Extractors may override; fallback uses a fixed format per unit type.\n */\n unitLabel(unit: UnitLike): string {\n const extractor = this.findExtractor(unit.filePath);\n const customLabel = extractor?.unitLabel?.(unit as IndexUnit);\n if (customLabel) return customLabel;\n\n switch (unit.unitType) {\n case IndexUnitType.CLASS:\n return unit.filePath;\n case IndexUnitType.FUNCTION:\n return this.canonicalFunctionSignature(unit);\n case IndexUnitType.BLOCK:\n return this.normalizedBlockHash(unit);\n default:\n return unit.name;\n }\n }\n\n private findExtractor(filePath: string): LanguageExtractor | undefined {\n return this.indexUnitExtractor.extractors.find((ex) => ex.supports(filePath));\n }\n\n private canonicalFunctionSignature(unit: UnitLike): string {\n const arity = this.extractArity(unit.code);\n return `${unit.name}(arity:${arity})`;\n }\n\n /**\n * Normalizes block code (strips comments/whitespace) and hashes it for pair matching.\n */\n private normalizedBlockHash(unit: UnitLike): string {\n const normalized = this.normalizeCode(unit.code);\n return crypto.createHash(BLOCK_HASH_ALGO).update(normalized).digest(\"hex\");\n }\n\n private stringToUnitType(value: string): IndexUnitType | null {\n if (value === IndexUnitType.CLASS) return IndexUnitType.CLASS;\n if (value === IndexUnitType.FUNCTION) return IndexUnitType.FUNCTION;\n if (value === IndexUnitType.BLOCK) return IndexUnitType.BLOCK;\n return null;\n }\n\n private extractArity(code: string): number {\n const match = code.match(/^[^{]*?\\(([^)]*)\\)/s);\n if (!match) return 0;\n const params = match[1]\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n return params.length;\n }\n\n private normalizeCode(code: string): string {\n const withoutBlockComments = code.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n const withoutLineComments = withoutBlockComments.replace(/\\/\\/[^\\n\\r]*/g, \"\");\n return withoutLineComments.replace(/\\s+/g, \"\");\n }\n}\n"],"mappings":"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","names":["upath","fs","path","fs","upath","crypto","glob","upath","upath","fs","upath","path","fs","crypto","upath","glob","fs","upath","Column","Entity","PrimaryColumn","PrimaryColumn","Column","Entity","fs","upath","path","fs","debug","SchemaType","ExecutableCodeLanguage","Outcome","HarmCategory","HarmBlockThreshold","HarmProbability","BlockReason","FinishReason","TaskType","FunctionCallingMode","DynamicRetrievalMode","Task","log","debug","path","fs","debug","path","fs","debug","log","debug","path","fs","log","debug","debug","log","debug","score","weights","hasParentClass","totalWeight","crypto","debug","minimatch","log","debug","minimatch","crypto","upath","fs"]}
1
+ {"version":3,"sources":["../src/DryScan.ts","../src/const.ts","../src/IndexUnitExtractor.ts","../src/extractors/java.ts","../src/config/indexConfig.ts","../src/config/configStore.ts","../src/config/dryconfig.ts","../src/Gitignore.ts","../src/db/DryScanDatabase.ts","../src/db/entities/FileEntity.ts","../src/db/entities/IndexUnitEntity.ts","../src/services/RepositoryInitializer.ts","../src/services/EmbeddingService.ts","../src/services/UpdateService.ts","../src/DryScanUpdater.ts","../src/services/DuplicationCache.ts","../src/services/DuplicateService.ts","../src/services/ExclusionService.ts","../src/services/PairingService.ts"],"sourcesContent":["import upath from \"upath\";\nimport fs from \"fs/promises\";\nimport { DuplicateAnalysisResult, DuplicateReport } from \"./types\";\nimport { DRYSCAN_DIR, INDEX_DB } from \"./const\";\nimport { defaultExtractors, IndexUnitExtractor } from \"./IndexUnitExtractor\";\nimport { DryScanDatabase } from \"./db/DryScanDatabase\";\nimport { RepositoryInitializer, InitOptions as InitServiceOptions } from \"./services/RepositoryInitializer\";\nimport { UpdateService } from \"./services/UpdateService\";\nimport { DuplicateService } from \"./services/DuplicateService\";\nimport { ExclusionService } from \"./services/ExclusionService\";\nimport { DryScanServiceDeps } from \"./services/types\";\nimport { configStore } from \"./config/configStore\";\nimport { DryConfig } from \"./types\";\nimport { PairingService } from \"./services/PairingService\";\n\nexport type InitOptions = InitServiceOptions;\n\n\nexport class DryScan {\n repoPath: string;\n private readonly extractor: IndexUnitExtractor;\n private db: DryScanDatabase;\n private readonly services: {\n initializer: RepositoryInitializer;\n updater: UpdateService;\n duplicate: DuplicateService;\n exclusion: ExclusionService;\n };\n private readonly serviceDeps: DryScanServiceDeps;\n\n constructor(\n repoPath: string,\n extractor?: IndexUnitExtractor,\n db?: DryScanDatabase\n ) {\n this.repoPath = repoPath;\n this.extractor = extractor ?? new IndexUnitExtractor(repoPath, defaultExtractors(repoPath));\n this.db = db ?? new DryScanDatabase();\n\n this.serviceDeps = {\n repoPath: this.repoPath,\n db: this.db,\n extractor: this.extractor,\n pairing: new PairingService(this.extractor),\n };\n\n const exclusion = new ExclusionService(this.serviceDeps);\n this.services = {\n initializer: new RepositoryInitializer(this.serviceDeps, exclusion),\n updater: new UpdateService(this.serviceDeps, exclusion),\n duplicate: new DuplicateService(this.serviceDeps),\n exclusion,\n };\n }\n\n /**\n * Initializes the DryScan repository with a 3-phase analysis:\n * Phase 1: Extract and save all functions\n * Phase 2: Resolve and save internal dependencies\n * Phase 3: Compute and save semantic embeddings\n */\n async init(options?: InitOptions): Promise<void> {\n console.log(`[DryScan] Initializing repository at ${this.repoPath}`);\n console.log(\"[DryScan] Preparing database and cache...\");\n await configStore.init(this.repoPath);\n await this.ensureDatabase();\n if (await this.isInitialized()) {\n console.log(\"[DryScan] Repository already initialized; skipping full init.\");\n return;\n }\n console.log(\"[DryScan] Starting initial scan (may take a moment)...\");\n await this.services.initializer.init(options);\n console.log(\"[DryScan] Initial scan complete.\");\n }\n\n /**\n * Updates the index by detecting changed, new, and deleted files.\n * Only reprocesses units in changed files for efficiency.\n * Delegates to DryScanUpdater module for implementation.\n * \n * Update process:\n * 1. List all current source files in repository\n * 2. For each file, check if it's new, changed, or unchanged (via mtime + checksum)\n * 3. Remove old units from changed/deleted files\n * 4. Extract and save units from new/changed files\n * 5. Recompute internal dependencies for affected units\n * 6. Recompute embeddings for affected units\n * 7. Update file tracking metadata\n */\n async updateIndex(): Promise<void> {\n console.log(`[DryScan] Updating index at ${this.repoPath}...`);\n console.log(\"[DryScan] Checking for file changes...\");\n const start = Date.now();\n await this.ensureDatabase();\n await this.services.updater.updateIndex();\n const duration = Date.now() - start;\n console.log(`[DryScan] Index update complete. Took ${duration}ms.`);\n }\n\n\n /**\n * Runs duplicate detection and returns a normalized report payload ready for persistence or display.\n */\n async buildDuplicateReport(): Promise<DuplicateReport> {\n const config = await this.loadConfig();\n const analysis = await this.findDuplicates(config);\n return {\n version: 1,\n generatedAt: new Date().toISOString(),\n threshold: config.threshold,\n grade: analysis.score.grade,\n score: analysis.score,\n duplicates: analysis.duplicates,\n };\n }\n\n /**\n * Finds duplicate code blocks using cosine similarity on embeddings.\n * Automatically updates the index before searching to ensure results are current.\n * Compares all function pairs and returns groups with similarity above the configured threshold.\n *\n * @returns Analysis result with duplicate groups and duplication score\n */\n private async findDuplicates(config: DryConfig): Promise<DuplicateAnalysisResult> {\n console.log(`[DryScan] Finding duplicates (threshold: ${config.threshold})...`);\n await this.ensureDatabase();\n\n console.log(\"[DryScan] Updating index...\");\n const updateStart = Date.now();\n await this.updateIndex();\n const updateDuration = Date.now() - updateStart;\n console.log(`[DryScan] Index update took ${updateDuration}ms.`);\n\n console.log(\"[DryScan] Detecting duplicates...\");\n const dupStart = Date.now();\n const result = await this.services.duplicate.findDuplicates(config);\n const dupDuration = Date.now() - dupStart;\n console.log(`[DryScan] Duplicate detection took ${dupDuration}ms.`);\n\n return result;\n }\n\n /**\n * Cleans excludedPairs entries that no longer match any indexed units.\n * Runs an update first to ensure the index reflects current code.\n */\n async cleanExclusions(): Promise<{ removed: number; kept: number }> {\n await this.updateIndex();\n return this.services.exclusion.cleanExclusions();\n }\n\n private async ensureDatabase(): Promise<void> {\n if (this.db.isInitialized()) return;\n const dbPath = upath.join(this.repoPath, DRYSCAN_DIR, INDEX_DB);\n await fs.mkdir(upath.dirname(dbPath), { recursive: true });\n await this.db.init(dbPath);\n }\n\n private async loadConfig(): Promise<DryConfig> {\n return configStore.get(this.repoPath);\n }\n\n private async isInitialized(): Promise<boolean> {\n if (!this.db.isInitialized()) return false;\n const unitCount = await this.db.countUnits();\n const initialized = unitCount > 0;\n console.log(`[DryScan] Initialization check: ${unitCount} indexed units`);\n return initialized;\n }\n}\n","export const DRYSCAN_DIR = \".dry\";\nexport const INDEX_DB = \"index.db\";\nexport const REPORTS_DIR = \"reports\";\nexport const FILE_CHECKSUM_ALGO = \"md5\";\nexport const BLOCK_HASH_ALGO = \"sha1\";","import path from \"path\";\nimport type { Stats } from \"fs\";\nimport fs from \"fs/promises\";\nimport upath from \"upath\";\nimport crypto from \"node:crypto\";\nimport debug from \"debug\";\nimport { glob } from \"glob-gitignore\";\nimport { IndexUnit } from \"./types\";\nimport { LanguageExtractor } from \"./extractors/LanguageExtractor\";\nimport { JavaExtractor } from \"./extractors/java\";\nimport { FILE_CHECKSUM_ALGO } from \"./const\";\nimport { configStore } from \"./config/configStore\";\nimport { DryConfig } from \"./types\";\nimport { Gitignore } from \"./Gitignore\"\nimport { Ignore } from \"ignore\";\n\nconst log = debug(\"DryScan:Extractor\");\n\nexport type { LanguageExtractor } from \"./extractors/LanguageExtractor\";\n/**\n * Returns the default set of language extractors supported by DryScan.\n * Extend/override by passing custom extractors into the IndexUnitExtractor constructor.\n */\nexport function defaultExtractors(repoPath: string): LanguageExtractor[] {\n return [new JavaExtractor(repoPath)];\n}\n\n/**\n * Extracts and indexes code units (classes, functions, blocks) for a repository.\n * Owns shared file-system helpers and delegates language-specific parsing to LanguageExtractors.\n */\nexport class IndexUnitExtractor {\n private readonly root: string;\n readonly extractors: LanguageExtractor[];\n private readonly gitignore: Gitignore;\n\n constructor(\n rootPath: string,\n extractors?: LanguageExtractor[]\n ) {\n this.root = rootPath;\n this.extractors = extractors ?? defaultExtractors(rootPath);\n this.gitignore = new Gitignore(this.root);\n log(\"Initialized extractor for %s\", this.root);\n }\n\n /**\n * Lists all supported source files from a path. Honors exclusion globs from config.\n */\n async listSourceFiles(dirPath: string): Promise<string[]> {\n const target = await this.resolveTarget(dirPath);\n const config = await this.loadConfig();\n const ignoreMatcher = await this.gitignore.buildMatcher(config);\n\n if (target.stat.isFile()) {\n return this.filterSingleFile(target.baseRel, ignoreMatcher);\n }\n\n const matches = await this.globSourceFiles(target.baseRel);\n return this.filterSupportedFiles(matches, ignoreMatcher);\n }\n\n /**\n * Computes MD5 checksum of file content to track changes.\n */\n async computeChecksum(filePath: string): Promise<string> {\n const fullPath = path.isAbsolute(filePath)\n ? filePath\n : path.join(this.root, filePath);\n\n const content = await fs.readFile(fullPath, \"utf8\");\n return crypto.createHash(FILE_CHECKSUM_ALGO).update(content).digest(\"hex\");\n }\n\n /**\n * Scans a file or directory and extracts indexable units using the matching LanguageExtractor.\n * The returned units have repo-relative file paths and no embedding attached.\n */\n async scan(targetPath: string): Promise<IndexUnit[]> {\n const fullPath = path.isAbsolute(targetPath)\n ? targetPath\n : path.join(this.root, targetPath);\n\n const stat = await fs.stat(fullPath).catch(() => null);\n if (!stat) {\n throw new Error(`Path not found: ${fullPath}`);\n }\n\n if (stat.isDirectory()) {\n log(\"Scanning directory %s\", fullPath);\n return this.scanDirectory(fullPath);\n }\n\n return this.scanFile(fullPath);\n }\n\n\n /**\n * Scans a directory recursively, extracting units from supported files while honoring exclusions.\n */\n private async scanDirectory(dir: string): Promise<IndexUnit[]> {\n const out: IndexUnit[] = [];\n const relDir = this.relPath(dir);\n const files = await this.listSourceFiles(relDir);\n for (const relFile of files) {\n const absFile = path.join(this.root, relFile);\n const extracted = await this.tryScanSupportedFile(absFile);\n out.push(...extracted);\n }\n return out;\n }\n\n /**\n * Scans a single file and extracts supported units.\n */\n private async scanFile(filePath: string): Promise<IndexUnit[]> {\n return this.tryScanSupportedFile(filePath, true);\n }\n\n /**\n * Extracts units from a supported file.\n * Optionally throws when the file type is unsupported (used when scanning an explicit file).\n */\n private async tryScanSupportedFile(filePath: string, throwOnUnsupported = false): Promise<IndexUnit[]> {\n const extractor = this.extractors.find(ex => ex.supports(filePath));\n if (!extractor) {\n if (throwOnUnsupported) {\n throw new Error(`Unsupported file type: ${filePath}`);\n }\n return [];\n }\n const rel = this.relPath(filePath);\n if (await this.shouldExclude(rel)) {\n log(\"Skipping excluded file %s\", rel);\n return [];\n }\n const source = await fs.readFile(filePath, \"utf8\");\n const units = await extractor.extractFromText(rel, source);\n log(\"Extracted %d units from %s\", units.length, rel);\n return units.map(unit => ({\n ...unit,\n filePath: rel,\n embedding: undefined,\n }));\n }\n\n /**\n * Converts an absolute path to a repo-relative, normalized (POSIX-style) path.\n * This keeps paths stable across platforms and consistent in the index/DB.\n */\n private relPath(absPath: string): string {\n return this.normalizeRelPath(upath.relative(this.root, absPath));\n }\n\n /**\n * Returns true if a repo-relative path matches any configured exclusion glob.\n */\n private async shouldExclude(relPath: string): Promise<boolean> {\n const config = await this.loadConfig();\n const ignoreMatcher = await this.gitignore.buildMatcher(config);\n return ignoreMatcher.ignores(this.normalizeRelPath(relPath));\n }\n\n private async loadConfig(): Promise<DryConfig> {\n return await configStore.get(this.root);\n }\n\n /**\n * Normalizes repo-relative paths and strips leading \"./\" to keep matcher inputs consistent.\n */\n private normalizeRelPath(relPath: string): string {\n const normalized = upath.normalizeTrim(relPath);\n return normalized.startsWith(\"./\") ? normalized.slice(2) : normalized;\n }\n\n private async resolveTarget(dirPath: string): Promise<{ fullPath: string; baseRel: string; stat: Stats; }> {\n const fullPath = path.isAbsolute(dirPath) ? dirPath : path.join(this.root, dirPath);\n const stat = await fs.stat(fullPath).catch(() => null);\n if (!stat) {\n throw new Error(`Path not found: ${fullPath}`);\n }\n const baseRel = this.relPath(fullPath);\n log(\"Listing source files under %s\", fullPath);\n return { fullPath, baseRel, stat };\n }\n\n private async filterSingleFile(baseRel: string, ignoreMatcher: Ignore): Promise<string[]> {\n const relFile = this.normalizeRelPath(baseRel);\n if (ignoreMatcher.ignores(relFile)) return [];\n return this.extractors.some((ex) => ex.supports(relFile)) ? [relFile] : [];\n }\n\n private async globSourceFiles(baseRel: string): Promise<string[]> {\n const pattern = baseRel ? `${baseRel.replace(/\\\\/g, \"/\")}/**/*` : \"**/*\";\n const matches = await glob(pattern, {\n cwd: this.root,\n dot: false,\n nodir: true,\n });\n return matches.map((p: string) => this.normalizeRelPath(p));\n }\n\n private filterSupportedFiles(relPaths: string[], ignoreMatcher: Ignore): string[] {\n return relPaths\n .filter((relPath: string) => !ignoreMatcher.ignores(relPath))\n .filter((relPath: string) => this.extractors.some((ex) => ex.supports(relPath)));\n }\n}\n","import crypto from \"node:crypto\";\nimport Parser from \"tree-sitter\";\nimport Java from \"tree-sitter-java\";\nimport { LanguageExtractor } from \"./LanguageExtractor\";\nimport { IndexUnit, IndexUnitType } from \"../types\";\nimport { indexConfig } from \"../config/indexConfig\";\nimport { DryConfig } from \"../types\";\nimport { configStore } from \"../config/configStore\";\nimport { BLOCK_HASH_ALGO } from \"../const\";\n\nexport class JavaExtractor implements LanguageExtractor {\n readonly id = \"java\";\n readonly exts = [\".java\"];\n\n private parser: Parser;\n private readonly repoPath: string;\n private config?: DryConfig;\n\n constructor(repoPath: string) {\n this.repoPath = repoPath;\n this.parser = new Parser();\n this.parser.setLanguage(Java);\n }\n\n supports(filePath: string): boolean {\n const lower = filePath.toLowerCase();\n return this.exts.some((ext) => lower.endsWith(ext));\n }\n\n async extractFromText(fileRelPath: string, source: string): Promise<IndexUnit[]> {\n if (!source.trim()) return [];\n\n this.config = await configStore.get(this.repoPath);\n\n const tree = this.parser.parse(source);\n const units: IndexUnit[] = [];\n\n const visit = (node: Parser.SyntaxNode, currentClass?: IndexUnit) => {\n if (this.isClassNode(node)) {\n const className = this.getClassName(node, source) || \"<anonymous>\";\n if (this.isDtoClass(node, source, className)) {\n return;\n }\n const startLine = node.startPosition.row;\n const endLine = node.endPosition.row;\n const classLength = endLine - startLine;\n const skipClass = this.shouldSkip(IndexUnitType.CLASS, className, classLength);\n const classId = this.buildId(IndexUnitType.CLASS, className, startLine, endLine);\n const code = this.stripComments(this.stripClassBody(node, source));\n const classUnit: IndexUnit = {\n id: classId,\n name: className,\n filePath: fileRelPath,\n startLine,\n endLine,\n code,\n unitType: IndexUnitType.CLASS,\n children: [],\n };\n if (!skipClass) {\n units.push(classUnit);\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i);\n if (child) visit(child, skipClass ? undefined : classUnit);\n }\n return;\n }\n\n if (this.isFunctionNode(node)) {\n const fnUnit = this.buildFunctionUnit(node, source, fileRelPath, currentClass);\n const fnLength = fnUnit.endLine - fnUnit.startLine;\n const bodyNode = this.getFunctionBody(node);\n const skipFunction = this.shouldSkip(IndexUnitType.FUNCTION, fnUnit.name, fnLength);\n\n if (skipFunction) {\n return;\n }\n\n units.push(fnUnit);\n\n if (bodyNode) {\n const blocks = this.extractBlocks(bodyNode, source, fileRelPath, fnUnit);\n units.push(...blocks);\n }\n }\n\n for (let i = 0; i < node.namedChildCount; i++) {\n const child = node.namedChild(i);\n if (child) visit(child, currentClass);\n }\n };\n\n visit(tree.rootNode);\n\n return units;\n }\n\n unitLabel(unit: IndexUnit): string | null {\n if (unit.unitType === IndexUnitType.CLASS) return unit.filePath;\n if (unit.unitType === IndexUnitType.FUNCTION) return this.canonicalFunctionSignature(unit);\n if (unit.unitType === IndexUnitType.BLOCK) return this.normalizedBlockHash(unit);\n return unit.name;\n }\n\n private isClassNode(node: Parser.SyntaxNode): boolean {\n return node.type === \"class_declaration\";\n }\n\n private getClassName(node: Parser.SyntaxNode, source: string): string | null {\n const nameNode = node.childForFieldName?.(\"name\");\n return nameNode ? source.slice(nameNode.startIndex, nameNode.endIndex) : null;\n }\n\n private isFunctionNode(node: Parser.SyntaxNode): boolean {\n return node.type === \"method_declaration\" || node.type === \"constructor_declaration\";\n }\n\n private getFunctionName(node: Parser.SyntaxNode, source: string, parentClass?: IndexUnit): string | null {\n const nameNode = node.childForFieldName?.(\"name\");\n const nameText = nameNode ? source.slice(nameNode.startIndex, nameNode.endIndex) : \"<anonymous>\";\n return parentClass ? `${parentClass.name}.${nameText}` : nameText;\n }\n\n private getFunctionBody(node: Parser.SyntaxNode): Parser.SyntaxNode | null {\n return node.childForFieldName?.(\"body\") ?? null;\n }\n\n private isBlockNode(node: Parser.SyntaxNode): boolean {\n return node.type === \"block\";\n }\n\n private getMethodBodiesForClass(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const bodies: Parser.SyntaxNode[] = [];\n const classBody = node.children.find(child => child.type === \"class_body\");\n if (!classBody) return bodies;\n \n for (let i = 0; i < classBody.namedChildCount; i++) {\n const child = classBody.namedChild(i);\n if (!child) continue;\n if (child.type === \"method_declaration\" || child.type === \"constructor_declaration\") {\n const body = child.childForFieldName?.(\"body\");\n if (body) bodies.push(body);\n }\n }\n return bodies;\n }\n\n private canonicalFunctionSignature(unit: IndexUnit): string {\n const arity = this.extractArity(unit.code);\n return `${unit.name}(arity:${arity})`;\n }\n\n private normalizedBlockHash(unit: IndexUnit): string {\n const normalized = this.normalizeCode(unit.code);\n return crypto.createHash(BLOCK_HASH_ALGO).update(normalized).digest(\"hex\");\n }\n\n private shouldSkip(unitType: IndexUnitType, name: string, lineCount: number): boolean {\n if (!this.config) {\n throw new Error(\"Config not loaded before skip evaluation\");\n }\n const config = this.config;\n const minLines = unitType === IndexUnitType.BLOCK\n ? Math.max(indexConfig.blockMinLines, config.minBlockLines ?? 0)\n : config.minLines;\n const belowMin = minLines > 0 && lineCount < minLines;\n const trivial = unitType === IndexUnitType.FUNCTION && this.isTrivialFunction(name);\n return belowMin || trivial;\n }\n\n private isTrivialFunction(fullName: string): boolean {\n const simpleName = fullName.split(\".\").pop() || fullName;\n const isGetter = /^(get|is)[A-Z]/.test(simpleName);\n const isSetter = /^set[A-Z]/.test(simpleName);\n return isGetter || isSetter;\n }\n\n private isDtoClass(node: Parser.SyntaxNode, source: string, className: string): boolean {\n const classBody = node.children.find((child) => child.type === \"class_body\");\n if (!classBody) return false;\n\n let hasField = false;\n\n for (let i = 0; i < classBody.namedChildCount; i++) {\n const child = classBody.namedChild(i);\n if (!child) continue;\n\n if (child.type === \"field_declaration\") {\n hasField = true;\n continue;\n }\n\n if (child.type.includes(\"annotation\")) {\n continue;\n }\n\n if (child.type === \"method_declaration\" || child.type === \"constructor_declaration\") {\n const simpleName = this.getSimpleFunctionName(child, source);\n const fullName = `${className}.${simpleName}`;\n if (!this.isTrivialFunction(fullName)) {\n return false;\n }\n continue;\n }\n\n return false;\n }\n\n return hasField;\n }\n\n private getSimpleFunctionName(node: Parser.SyntaxNode, source: string): string {\n const nameNode = node.childForFieldName?.(\"name\");\n return nameNode ? source.slice(nameNode.startIndex, nameNode.endIndex) : \"<anonymous>\";\n }\n\n private buildFunctionUnit(\n node: Parser.SyntaxNode,\n source: string,\n file: string,\n parentClass?: IndexUnit\n ): IndexUnit {\n const name = this.getFunctionName(node, source, parentClass) || \"<anonymous>\";\n const startLine = node.startPosition.row;\n const endLine = node.endPosition.row;\n const id = this.buildId(IndexUnitType.FUNCTION, name, startLine, endLine);\n const unit: IndexUnit = {\n id,\n name,\n filePath: file,\n startLine,\n endLine,\n code: this.stripComments(source.slice(node.startIndex, node.endIndex)),\n unitType: IndexUnitType.FUNCTION,\n parentId: parentClass?.id,\n parent: parentClass,\n };\n if (parentClass) {\n parentClass.children = parentClass.children || [];\n parentClass.children.push(unit);\n }\n return unit;\n }\n\n private extractBlocks(\n bodyNode: Parser.SyntaxNode,\n source: string,\n file: string,\n parentFunction: IndexUnit\n ): IndexUnit[] {\n const blocks: IndexUnit[] = [];\n\n const visit = (n: Parser.SyntaxNode) => {\n if (this.isBlockNode(n)) {\n const startLine = n.startPosition.row;\n const endLine = n.endPosition.row;\n const lineCount = endLine - startLine;\n if (this.shouldSkip(IndexUnitType.BLOCK, parentFunction.name, lineCount)) {\n return;\n }\n if (lineCount >= indexConfig.blockMinLines) {\n const id = this.buildId(IndexUnitType.BLOCK, parentFunction.name, startLine, endLine);\n const blockUnit: IndexUnit = {\n id,\n name: parentFunction.name,\n filePath: file,\n startLine,\n endLine,\n code: this.stripComments(source.slice(n.startIndex, n.endIndex)),\n unitType: IndexUnitType.BLOCK,\n parentId: parentFunction.id,\n parent: parentFunction,\n };\n parentFunction.children = parentFunction.children || [];\n parentFunction.children.push(blockUnit);\n blocks.push(blockUnit);\n }\n }\n\n for (let i = 0; i < n.namedChildCount; i++) {\n const child = n.namedChild(i);\n if (child) visit(child);\n }\n };\n\n visit(bodyNode);\n return blocks;\n }\n\n private stripClassBody(node: Parser.SyntaxNode, source: string): string {\n const classStart = node.startIndex;\n let code = source.slice(classStart, node.endIndex);\n\n const methodBodies: Array<{ start: number; end: number }> = [];\n const candidates = this.getMethodBodiesForClass(node);\n\n for (const body of candidates) {\n methodBodies.push({ start: body.startIndex - classStart, end: body.endIndex - classStart });\n }\n\n methodBodies.sort((a, b) => b.start - a.start);\n for (const body of methodBodies) {\n code = code.slice(0, body.start) + \" { }\" + code.slice(body.end);\n }\n\n return code;\n }\n\n private buildId(type: IndexUnitType, name: string, startLine: number, endLine: number): string {\n return `${type}:${name}:${startLine}-${endLine}`;\n }\n\n private extractArity(code: string): number {\n const match = code.match(/^[^{]*?\\(([^)]*)\\)/s);\n if (!match) return 0;\n const params = match[1]\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n return params.length;\n }\n\n private normalizeCode(code: string): string {\n const withoutBlockComments = code.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n const withoutLineComments = withoutBlockComments.replace(/\\/\\/[^\\n\\r]*/g, \"\");\n return withoutLineComments.replace(/\\s+/g, \"\");\n }\n\n private stripComments(code: string): string {\n const withoutBlockComments = code.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (match) => match.replace(/[^\\n\\r]/g, \"\"));\n return withoutBlockComments.replace(/\\/\\/[^\\n\\r]*/g, \"\");\n }\n}\n","export const indexConfig = {\n blockMinLines: 5,\n thresholds: {\n class: 0.88,\n function: 0.88,\n block: 0.88,\n },\n weights: {\n class: { self: 1 },\n function: { self: 0.8, parentClass: 0.2 },\n block: { self: 0.7, parentFunction: 0.2, parentClass: 0.1 },\n },\n};\n","import upath from \"upath\";\nimport { DryConfig } from \"../types\";\nimport { ensureDefaultConfig, resolveDryConfig, saveDryConfig } from \"./dryconfig\";\n\nclass ConfigStore {\n private readonly cache = new Map<string, DryConfig>();\n private readonly loading = new Map<string, Promise<DryConfig>>();\n\n async init(repoPath: string): Promise<DryConfig> {\n const key = this.normalize(repoPath);\n return this.load(key, repoPath);\n }\n\n async get(repoPath: string): Promise<DryConfig> {\n const key = this.normalize(repoPath);\n const cached = this.cache.get(key);\n if (cached) return cached;\n return this.load(key, repoPath);\n }\n\n async refresh(repoPath: string): Promise<DryConfig> {\n const key = this.normalize(repoPath);\n this.cache.delete(key);\n return this.load(key, repoPath);\n }\n\n async save(repoPath: string, config: DryConfig): Promise<void> {\n const key = this.normalize(repoPath);\n await saveDryConfig(repoPath, config);\n this.cache.set(key, config);\n }\n\n private async load(key: string, repoPath: string): Promise<DryConfig> {\n const existing = this.loading.get(key);\n if (existing) return existing;\n\n const promise = ensureDefaultConfig(repoPath).then(() => resolveDryConfig(repoPath)).then((config) => {\n this.cache.set(key, config);\n this.loading.delete(key);\n return config;\n }).catch((err) => {\n this.loading.delete(key);\n throw err;\n });\n\n this.loading.set(key, promise);\n return promise;\n }\n\n private normalize(repoPath: string): string {\n return upath.normalizeTrim(upath.resolve(repoPath));\n }\n}\n\nexport const configStore = new ConfigStore();\n","import fs from \"fs/promises\";\nimport upath from \"upath\";\nimport { Validator, Schema } from \"jsonschema\";\nimport { DryConfig } from \"../types\";\n\n// Baseline config used when no file is present; exported so tests and constructors can seed defaults.\nexport const DEFAULT_CONFIG: DryConfig = {\n excludedPaths: [\n \"**/test/**\",\n ],\n excludedPairs: [],\n minLines: 3,\n minBlockLines: 5,\n threshold: 0.88,\n embeddingSource: \"http://localhost:11434\",\n contextLength: 2048,\n};\n\nconst validator = new Validator();\n\nconst partialConfigSchema: Schema = {\n type: \"object\",\n properties: {\n excludedPaths: { type: \"array\", items: { type: \"string\" } },\n excludedPairs: { type: \"array\", items: { type: \"string\" } },\n minLines: { type: \"number\" },\n minBlockLines: { type: \"number\" },\n threshold: { type: \"number\" },\n embeddingSource: { type: \"string\" },\n contextLength: { type: \"number\" },\n },\n};\n\nconst fullConfigSchema: Schema = {\n ...partialConfigSchema,\n required: [\n \"excludedPaths\",\n \"excludedPairs\",\n \"minLines\",\n \"minBlockLines\",\n \"threshold\",\n \"embeddingSource\",\n \"contextLength\",\n ],\n};\n\nfunction validateConfig(raw: unknown, schema: Schema, source: string): any {\n const result = validator.validate(raw, schema);\n if (!result.valid) {\n const details = result.errors.map((e) => e.stack).join(\"; \");\n throw new Error(`${source} config is invalid: ${details}`);\n }\n return raw;\n}\n\nasync function readConfigFile(repoPath: string): Promise<Partial<DryConfig>> {\n const configPath = upath.join(repoPath, \"dryconfig.json\");\n try {\n const content = await fs.readFile(configPath, \"utf8\");\n let parsed: Partial<DryConfig> = {};\n try {\n parsed = JSON.parse(content) as Partial<DryConfig>;\n } catch (parseErr) {\n throw new Error(`Invalid JSON in ${configPath}: ${(parseErr as Error).message}`);\n }\n return parsed;\n } catch (err: any) {\n if (err?.code === \"ENOENT\") {\n return {};\n }\n throw err;\n }\n}\n\n/**\n * Resolves the effective config for a repo using defaults merged with any file config.\n */\nexport async function resolveDryConfig(repoPath: string): Promise<DryConfig> {\n const fileConfigRaw = await readConfigFile(repoPath);\n validateConfig(fileConfigRaw, partialConfigSchema, \"Config file\");\n\n const merged = { ...DEFAULT_CONFIG, ...fileConfigRaw };\n validateConfig(merged, fullConfigSchema, \"Merged\");\n return merged as DryConfig;\n}\n\n// Backwards-compatible helper used by existing callers (file + defaults).\nexport async function loadDryConfig(repoPath: string): Promise<DryConfig> {\n return resolveDryConfig(repoPath);\n}\n\nexport async function saveDryConfig(repoPath: string, config: DryConfig): Promise<void> {\n const configPath = upath.join(repoPath, \"dryconfig.json\");\n validateConfig(config, fullConfigSchema, \"Config to save\");\n await fs.writeFile(configPath, JSON.stringify(config, null, 2), \"utf8\");\n}\n\nexport async function ensureDefaultConfig(repoPath: string): Promise<void> {\n const configPath = upath.join(repoPath, \"dryconfig.json\");\n const repoExists = await fs.stat(repoPath).then((s) => s.isDirectory()).catch((err: any) => {\n if (err?.code === \"ENOENT\") return false;\n throw err;\n });\n\n if (!repoExists) return;\n\n const exists = await fs.stat(configPath).then(() => true).catch((err: any) => {\n if (err?.code === \"ENOENT\") return false;\n throw err;\n });\n\n if (!exists) {\n await saveDryConfig(repoPath, DEFAULT_CONFIG);\n }\n}\n","import path from \"path\";\nimport fs from \"fs/promises\";\nimport upath from \"upath\";\nimport { glob } from \"glob-gitignore\";\nimport ignore, { Ignore } from \"ignore\";\nimport { DryConfig } from \"./types\";\n\n/**\n * Gitignore helper that builds ignore matchers by combining default rules,\n * repo .gitignore files, and config-driven exclusions.\n */\nexport class Gitignore {\n private readonly defaultIgnores = [\".git/**\", \".dry/**\"];\n\n constructor(private readonly root: string) {}\n\n async buildMatcher(config: DryConfig): Promise<Ignore> {\n const rules = await this.resolveRules(config);\n return ignore({ allowRelativePaths: true }).add(rules);\n }\n\n private async resolveRules(config: DryConfig): Promise<string[]> {\n const gitignoreRules = await this.loadGitignoreRules();\n const configRules = config.excludedPaths || [];\n return [...this.defaultIgnores, ...gitignoreRules, ...configRules];\n }\n\n private async loadGitignoreRules(): Promise<string[]> {\n const gitignoreFiles = await glob(\"**/.gitignore\", {\n cwd: this.root,\n dot: true,\n nodir: true,\n ignore: this.defaultIgnores,\n });\n\n const rules: string[] = [];\n\n for (const file of gitignoreFiles) {\n const absPath = path.join(this.root, file);\n const dir = upath.normalizeTrim(upath.dirname(file));\n const content = await fs.readFile(absPath, \"utf8\").catch(() => \"\");\n const lines = content.split(/\\r?\\n/);\n\n for (const raw of lines) {\n const trimmed = raw.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n const negated = trimmed.startsWith(\"!\");\n const body = negated ? trimmed.slice(1) : trimmed;\n\n const scoped = this.scopeRule(body, dir);\n if (!scoped) continue;\n\n rules.push(negated ? `!${scoped}` : scoped);\n }\n }\n\n return rules;\n }\n\n private scopeRule(rule: string, gitignoreDir: string): string | null {\n const cleaned = rule.replace(/^\\//, \"\");\n if (!cleaned) return null;\n\n if (!gitignoreDir || gitignoreDir === \".\") {\n return cleaned;\n }\n\n return upath.normalizeTrim(upath.join(gitignoreDir, cleaned));\n }\n}\n","import \"reflect-metadata\";\nimport fs from \"fs/promises\";\nimport upath from \"upath\";\nimport { DataSource, Repository, In } from \"typeorm\";\nimport { FileEntity } from \"./entities/FileEntity\";\nimport { IndexUnit } from \"../types\";\nimport { IndexUnitEntity } from \"./entities/IndexUnitEntity\";\n\nexport class DryScanDatabase {\n private dataSource?: DataSource;\n private unitRepository?: Repository<IndexUnitEntity>;\n private fileRepository?: Repository<FileEntity>;\n\n isInitialized(): boolean {\n return !!this.dataSource?.isInitialized;\n }\n\n async init(dbPath: string): Promise<void> {\n await fs.mkdir(upath.dirname(dbPath), { recursive: true });\n\n this.dataSource = new DataSource({\n type: \"sqlite\",\n database: dbPath,\n entities: [IndexUnitEntity, FileEntity],\n synchronize: true,\n logging: false,\n });\n\n await this.dataSource.initialize();\n this.unitRepository = this.dataSource.getRepository(IndexUnitEntity);\n this.fileRepository = this.dataSource.getRepository(FileEntity);\n }\n\n async saveUnit(unit: IndexUnit): Promise<void> {\n await this.saveUnits(unit);\n }\n\n async saveUnits(units: IndexUnit | IndexUnit[]): Promise<void> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n const payload = Array.isArray(units) ? units : [units];\n await this.unitRepository.save(payload);\n }\n\n async getUnit(id: string): Promise<IndexUnit | null> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n return this.unitRepository.findOne({ \n where: { id },\n relations: [\"children\", \"parent\"]\n });\n }\n\n async getAllUnits(): Promise<IndexUnit[]> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n return this.unitRepository.find({ relations: [\"children\", \"parent\"] });\n }\n\n async updateUnit(unit: IndexUnit): Promise<void> {\n await this.saveUnits(unit);\n }\n\n async updateUnits(units: IndexUnit | IndexUnit[]): Promise<void> {\n await this.saveUnits(units);\n }\n\n /**\n * Returns total count of indexed units.\n */\n async countUnits(): Promise<number> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n return this.unitRepository.count();\n }\n\n /**\n * Removes index units by their file paths.\n * Used during incremental updates when files change.\n */\n async removeUnitsByFilePaths(filePaths: string[]): Promise<void> {\n if (!this.unitRepository) throw new Error(\"Database not initialized\");\n await this.unitRepository.delete({ filePath: In(filePaths) });\n }\n\n /**\n * Saves file metadata (path, checksum, mtime) to track changes.\n */\n async saveFile(file: FileEntity): Promise<void> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n await this.fileRepository.save(file);\n }\n\n /**\n * Saves multiple file metadata entries.\n */\n async saveFiles(files: FileEntity[]): Promise<void> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n await this.fileRepository.save(files);\n }\n\n /**\n * Gets file metadata by file path.\n */\n async getFile(filePath: string): Promise<FileEntity | null> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n return this.fileRepository.findOne({ where: { filePath } });\n }\n\n /**\n * Gets all tracked files.\n */\n async getAllFiles(): Promise<FileEntity[]> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n return this.fileRepository.find();\n }\n\n /**\n * Removes file metadata entries by file paths.\n * Used when files are deleted from repository.\n */\n async removeFilesByFilePaths(filePaths: string[]): Promise<void> {\n if (!this.fileRepository) throw new Error(\"Database not initialized\");\n await this.fileRepository.delete({ filePath: In(filePaths) });\n }\n\n async close(): Promise<void> {\n if (this.dataSource?.isInitialized) {\n await this.dataSource.destroy();\n }\n }\n}\n","import { Entity, PrimaryColumn, Column } from \"typeorm\";\n\n/**\n * Represents a tracked source file in the repository.\n * Used to detect changes via checksum and mtime for incremental updates.\n */\n@Entity(\"files\")\nexport class FileEntity {\n /**\n * Relative path to the file from repository root.\n * Used as primary key for uniqueness.\n */\n @PrimaryColumn(\"text\")\n filePath!: string;\n\n /**\n * MD5 checksum of file content.\n * Used to detect content changes.\n */\n @Column(\"text\")\n checksum!: string;\n\n /**\n * Last modification time in milliseconds since epoch.\n * Used as fast sanity check before computing checksum.\n */\n @Column(\"integer\")\n mtime!: number;\n}\n","import {\n Column,\n Entity,\n JoinColumn,\n ManyToOne,\n OneToMany,\n PrimaryColumn,\n RelationId,\n} from \"typeorm\";\nimport { IndexUnit, IndexUnitType } from \"../../types\";\n\n@Entity(\"index_units\")\nexport class IndexUnitEntity implements IndexUnit {\n @PrimaryColumn(\"text\")\n id!: string;\n\n @Column(\"text\")\n name!: string;\n\n @Column(\"text\")\n filePath!: string;\n\n @Column(\"integer\")\n startLine!: number;\n\n @Column(\"integer\")\n endLine!: number;\n\n @Column(\"text\")\n code!: string;\n\n @Column(\"text\")\n unitType!: IndexUnitType;\n\n @ManyToOne(() => IndexUnitEntity, (unit) => unit.children, {\n nullable: true,\n onDelete: \"CASCADE\",\n })\n @JoinColumn({ name: \"parent_id\" })\n parent?: IndexUnitEntity | null;\n\n @RelationId((unit: IndexUnitEntity) => unit.parent)\n parentId?: string | null;\n\n @OneToMany(() => IndexUnitEntity, (unit) => unit.parent, { nullable: true })\n children?: IndexUnitEntity[];\n\n @Column(\"simple-array\", { nullable: true })\n embedding?: number[] | null;\n}\n","import path from \"path\";\nimport fs from \"fs/promises\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { ExclusionService } from \"./ExclusionService\";\nimport { IndexUnit } from \"../types\";\nimport { EmbeddingService } from \"./EmbeddingService\";\nimport { FileEntity } from \"../db/entities/FileEntity\";\nimport { IndexUnitExtractor } from \"../IndexUnitExtractor\";\n\nexport interface InitOptions {\n skipEmbeddings?: boolean;\n}\n\nexport class RepositoryInitializer {\n constructor(\n private readonly deps: DryScanServiceDeps,\n private readonly exclusionService: ExclusionService\n ) {}\n\n async init(options?: InitOptions): Promise<void> {\n const extractor = this.deps.extractor;\n\n console.log(\"[DryScan] Phase 1/3: Extracting code units...\");\n await this.initUnits(extractor);\n console.log(\"[DryScan] Phase 2/3: Computing embeddings (may be slow)...\");\n await this.computeEmbeddings(options?.skipEmbeddings === true);\n console.log(\"[DryScan] Phase 3/3: Tracking files...\");\n await this.trackFiles(extractor);\n await this.exclusionService.cleanupExcludedFiles();\n console.log(\"[DryScan] Initialization phases complete.\");\n }\n\n private async initUnits(extractor: IndexUnitExtractor): Promise<void> {\n const units = await extractor.scan(this.deps.repoPath);\n console.log(`[DryScan] Extracted ${units.length} index units.`);\n await this.deps.db.saveUnits(units);\n }\n\n private async computeEmbeddings(skipEmbeddings: boolean): Promise<void> {\n if (skipEmbeddings) {\n console.log(\"[DryScan] Skipping embedding computation by request.\");\n return;\n }\n const allUnits: IndexUnit[] = await this.deps.db.getAllUnits();\n const total = allUnits.length;\n console.log(`[DryScan] Computing embeddings for ${total} units...`);\n\n const updated: IndexUnit[] = [];\n const progressInterval = Math.max(1, Math.ceil(total / 10));\n const embeddingService = new EmbeddingService(this.deps.repoPath);\n\n for (let i = 0; i < total; i++) {\n const unit = allUnits[i];\n try {\n const enriched = await embeddingService.addEmbedding(unit);\n updated.push(enriched);\n } catch (err: any) {\n console.error(\n `[DryScan] Embedding failed for ${unit.filePath} (${unit.name}): ${err?.message || err}`\n );\n throw err;\n }\n\n const completed = i + 1;\n if (completed === total || completed % progressInterval === 0) {\n const pct = Math.floor((completed / total) * 100);\n console.log(`[DryScan] Embeddings ${completed}/${total} (${pct}%)`);\n }\n }\n\n await this.deps.db.updateUnits(updated);\n }\n\n private async trackFiles(extractor: IndexUnitExtractor): Promise<void> {\n const allFunctions = await extractor.listSourceFiles(this.deps.repoPath);\n const fileEntities: FileEntity[] = [];\n\n for (const relPath of allFunctions) {\n const fullPath = path.join(this.deps.repoPath, relPath);\n const stat = await fs.stat(fullPath);\n const checksum = await extractor.computeChecksum(fullPath);\n\n const fileEntity = new FileEntity();\n fileEntity.filePath = relPath;\n fileEntity.checksum = checksum;\n fileEntity.mtime = stat.mtimeMs;\n fileEntities.push(fileEntity);\n }\n\n await this.deps.db.saveFiles(fileEntities);\n console.log(`[DryScan] Tracked ${fileEntities.length} files.`);\n }\n}","import debug from \"debug\";\nimport { OllamaEmbeddings } from \"@langchain/ollama\";\nimport { HuggingFaceInferenceEmbeddings } from \"@langchain/community/embeddings/hf\";\nimport { IndexUnit } from \"../types\";\nimport { configStore } from \"../config/configStore\";\n\nconst log = debug(\"DryScan:EmbeddingService\");\n\n// Model names for each provider\nconst OLLAMA_MODEL = \"embeddinggemma\";\nconst HUGGINGFACE_MODEL = \"google/embeddinggemma-300m\";\n\nexport class EmbeddingService {\n constructor(private readonly repoPath: string) { }\n\n /**\n * Generates an embedding for the given index unit using the configured provider.\n * Skips embedding if code exceeds the configured context length.\n */\n async addEmbedding(fn: IndexUnit): Promise<IndexUnit> {\n const config = await configStore.get(this.repoPath);\n const maxContext = config?.contextLength ?? 2048;\n if (fn.code.length > maxContext) {\n log(\n \"Skipping embedding for %s (code length %d exceeds context %d)\",\n fn.id,\n fn.code.length,\n maxContext\n );\n return { ...fn, embedding: null };\n }\n\n const source = config.embeddingSource;\n if (!source) {\n const message = `Embedding source is not configured for repository at ${this.repoPath}`;\n log(message);\n throw new Error(message);\n }\n\n const embeddings = this.buildProvider(source);\n const embedding = await embeddings.embedQuery(fn.code);\n return { ...fn, embedding };\n }\n\n /**\n * Builds the embedding provider based on the source configuration.\n * - URL (http/https): Uses Ollama with \"embeddinggemma\" model\n * - \"huggingface\": Uses HuggingFace Inference API with \"embeddinggemma-300m\" model\n */\n private buildProvider(source: string) {\n // HuggingFace Inference API\n if (source.toLowerCase() === \"huggingface\") {\n log(\"Using HuggingFace Inference with model: %s\", HUGGINGFACE_MODEL);\n return new HuggingFaceInferenceEmbeddings({\n model: HUGGINGFACE_MODEL,\n });\n }\n\n // Ollama (local or remote URL)\n if (/^https?:\\/\\//i.test(source)) {\n log(\"Using Ollama at %s with model: %s\", source, OLLAMA_MODEL);\n return new OllamaEmbeddings({\n model: OLLAMA_MODEL,\n baseUrl: source,\n });\n }\n\n const message = `Unsupported embedding source: ${source || \"(empty)\"}. Use \"huggingface\" or an Ollama URL.`;\n log(message);\n throw new Error(message);\n }\n}","import debug from \"debug\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { ExclusionService } from \"./ExclusionService\";\nimport { performIncrementalUpdate } from \"../DryScanUpdater\";\nimport { DuplicationCache } from \"./DuplicationCache\";\n\nconst log = debug(\"DryScan:UpdateService\");\n\nexport class UpdateService {\n constructor(\n private readonly deps: DryScanServiceDeps,\n private readonly exclusionService: ExclusionService\n ) {}\n\n async updateIndex(): Promise<void> {\n const extractor = this.deps.extractor;\n const cache = DuplicationCache.getInstance();\n\n try {\n const changeSet = await performIncrementalUpdate(this.deps.repoPath, extractor, this.deps.db);\n await this.exclusionService.cleanupExcludedFiles();\n await cache.invalidate([...changeSet.changed, ...changeSet.deleted]);\n } catch (err) {\n log(\"Error during index update:\", err);\n throw err;\n }\n }\n}","import path from \"path\";\nimport fs from \"fs/promises\";\nimport debug from \"debug\";\nimport { IndexUnit } from \"./types\";\nimport { IndexUnitExtractor } from \"./IndexUnitExtractor\";\nimport { DryScanDatabase } from \"./db/DryScanDatabase\";\nimport { FileEntity } from \"./db/entities/FileEntity\";\nimport { EmbeddingService } from \"./services/EmbeddingService\";\n\nconst log = debug(\"DryScan:Updater\");\n\n/**\n * DryScan Updater Module\n * \n * This module contains all incremental update logic for DryScan.\n * Separated from DryScan.ts to keep that file focused on core operations.\n * \n * Represents the result of change detection.\n * Categorizes files into added, changed, deleted, and unchanged.\n */\nexport interface FileChangeSet {\n added: string[];\n changed: string[];\n deleted: string[];\n unchanged: string[];\n}\n\n/**\n * Detects which files have been added, changed, or deleted since last scan.\n * Uses mtime as fast check, then checksum for verification.\n * \n * @param repoPath - Root path of the repository\n * @param extractor - Index unit extractor instance for file operations\n * @param db - Database instance for retrieving tracked files\n * @returns Change set with categorized file paths\n */\nexport async function detectFileChanges(\n repoPath: string,\n extractor: IndexUnitExtractor,\n db: DryScanDatabase\n): Promise<FileChangeSet> {\n // Get current files in repository\n const currentFiles = await extractor.listSourceFiles(repoPath);\n const currentFileSet = new Set(currentFiles);\n\n // Get tracked files from database\n const trackedFiles = await db.getAllFiles();\n const trackedFileMap = new Map(trackedFiles.map(f => [f.filePath, f]));\n\n const added: string[] = [];\n const changed: string[] = [];\n const unchanged: string[] = [];\n\n // Check each current file\n for (const filePath of currentFiles) {\n const tracked = trackedFileMap.get(filePath);\n \n if (!tracked) {\n // New file\n added.push(filePath);\n continue;\n }\n\n // Check if file changed using mtime first (fast check)\n const fullPath = path.join(repoPath, filePath);\n const stat = await fs.stat(fullPath);\n \n if (stat.mtimeMs !== tracked.mtime) {\n // Mtime changed, verify with checksum\n const currentChecksum = await extractor.computeChecksum(fullPath);\n if (currentChecksum !== tracked.checksum) {\n changed.push(filePath);\n } else {\n // Mtime changed but content same\n unchanged.push(filePath);\n }\n } else {\n unchanged.push(filePath);\n }\n }\n\n // Find deleted files\n const deleted = trackedFiles\n .map(f => f.filePath)\n .filter(fp => !currentFileSet.has(fp));\n\n return { added, changed, deleted, unchanged };\n}\n\n/**\n * Extracts index units from a list of files.\n * Used during incremental updates.\n * \n * @param filePaths - Array of relative file paths to extract from\n * @param extractor - Index unit extractor instance\n * @returns Array of extracted units\n */\nexport async function extractUnitsFromFiles(\n filePaths: string[],\n extractor: IndexUnitExtractor\n): Promise<IndexUnit[]> {\n const allUnits: IndexUnit[] = [];\n \n for (const relPath of filePaths) {\n const functions = await extractor.scan(relPath);\n allUnits.push(...functions);\n }\n \n return allUnits;\n}\n\n/**\n * Updates file tracking metadata after processing changes.\n * Removes deleted files, updates changed files, adds new files.\n * \n * @param changeSet - Set of file changes to apply\n * @param repoPath - Root path of the repository\n * @param extractor - Index unit extractor for checksum computation\n * @param db - Database instance for file tracking\n */\nexport async function updateFileTracking(\n changeSet: FileChangeSet,\n repoPath: string,\n extractor: IndexUnitExtractor,\n db: DryScanDatabase\n): Promise<void> {\n // Remove deleted files\n if (changeSet.deleted.length > 0) {\n if (typeof (db as any).removeFilesByFilePaths === \"function\") {\n await (db as any).removeFilesByFilePaths(changeSet.deleted);\n } else if (typeof (db as any).removeFiles === \"function\") {\n await (db as any).removeFiles(changeSet.deleted);\n }\n }\n\n // Create file entities for new and changed files\n const filesToTrack = [...changeSet.added, ...changeSet.changed];\n if (filesToTrack.length > 0) {\n const fileEntities: FileEntity[] = [];\n \n for (const relPath of filesToTrack) {\n const fullPath = path.join(repoPath, relPath);\n const stat = await fs.stat(fullPath);\n const checksum = await extractor.computeChecksum(fullPath);\n \n const fileEntity = new FileEntity();\n fileEntity.filePath = relPath;\n fileEntity.checksum = checksum;\n fileEntity.mtime = stat.mtimeMs;\n \n fileEntities.push(fileEntity);\n }\n \n await db.saveFiles(fileEntities);\n }\n}\n\n/**\n * Performs incremental update of the DryScan index.\n * Detects file changes and reprocesses only affected files.\n * \n * @param repoPath - Root path of the repository\n * @param extractor - Index unit extractor instance\n * @param db - Database instance (must be initialized)\n */\nexport async function performIncrementalUpdate(\n repoPath: string,\n extractor: IndexUnitExtractor,\n db: DryScanDatabase,\n): Promise<FileChangeSet> {\n log(\"Starting incremental update\");\n const embeddingService = new EmbeddingService(repoPath);\n \n // Step 1: Detect changes\n const changeSet = await detectFileChanges(repoPath, extractor, db);\n \n if (changeSet.changed.length === 0 && \n changeSet.added.length === 0 && \n changeSet.deleted.length === 0) {\n log(\"No changes detected. Index is up to date.\");\n return changeSet;\n }\n\n log(`Changes detected: ${changeSet.added.length} added, ${changeSet.changed.length} changed, ${changeSet.deleted.length} deleted`);\n\n // Step 2: Remove old data for changed/deleted files\n const filesToRemove = [...changeSet.changed, ...changeSet.deleted];\n if (filesToRemove.length > 0) {\n await db.removeUnitsByFilePaths(filesToRemove);\n log(`Removed units from ${filesToRemove.length} files`);\n }\n\n // Step 3: Extract functions from new/changed files\n const filesToProcess = [...changeSet.added, ...changeSet.changed];\n if (filesToProcess.length > 0) {\n const newUnits = await extractUnitsFromFiles(filesToProcess, extractor);\n await db.saveUnits(newUnits);\n log(`Extracted and saved ${newUnits.length} units from ${filesToProcess.length} files`);\n\n // Step 4: Recompute embeddings for affected units only\n const total = newUnits.length;\n if (total > 0) {\n log(`Recomputing embeddings for ${total} units`);\n const progressInterval = Math.max(1, Math.ceil(total / 10));\n const updatedWithEmbeddings = [] as IndexUnit[];\n\n for (let i = 0; i < total; i++) {\n const unit = newUnits[i];\n try {\n const enriched = await embeddingService.addEmbedding(unit);\n updatedWithEmbeddings.push(enriched);\n } catch (err: any) {\n console.error(\n `[DryScan] embedding failed for ${unit.filePath} (${unit.name}): ${err?.message || err}`\n );\n throw err;\n }\n\n const completed = i + 1;\n if (completed === total || completed % progressInterval === 0) {\n const pct = Math.floor((completed / total) * 100);\n console.log(`[DryScan] Incremental embeddings ${completed}/${total} (${pct}%)`);\n }\n }\n\n await db.updateUnits(updatedWithEmbeddings);\n log(`Recomputed embeddings for ${updatedWithEmbeddings.length} units`);\n }\n }\n\n // Step 5: Update file tracking\n await updateFileTracking(changeSet, repoPath, extractor, db);\n log(\"Incremental update complete\");\n\n return changeSet;\n}\n","import { DuplicateGroup } from \"../types\";\n\n/**\n * In-memory cache for duplicate comparison scores.\n * Stores a global map of comparison keys and a per-file index for fast invalidation.\n */\nexport class DuplicationCache {\n private static instance: DuplicationCache | null = null;\n\n private readonly comparisons = new Map<string, number>();\n private readonly fileIndex = new Map<string, Set<string>>();\n private initialized = false;\n\n static getInstance(): DuplicationCache {\n if (!DuplicationCache.instance) {\n DuplicationCache.instance = new DuplicationCache();\n }\n return DuplicationCache.instance;\n }\n\n /**\n * Updates the cache with fresh duplicate groups. Not awaited by callers to avoid blocking.\n */\n async update(groups: DuplicateGroup[]): Promise<void> {\n if (!groups) return;\n\n for (const group of groups) {\n const key = this.makeKey(group.left.id, group.right.id);\n this.comparisons.set(key, group.similarity);\n this.addKeyForFile(group.left.filePath, key);\n this.addKeyForFile(group.right.filePath, key);\n }\n\n this.initialized = this.initialized || groups.length > 0;\n }\n\n /**\n * Retrieves a cached similarity if present and valid for both file paths.\n * Returns null when the cache has not been initialized or when the pair is missing.\n */\n get(leftId: string, rightId: string, leftFilePath: string, rightFilePath: string): number | null {\n if (!this.initialized) return null;\n\n const key = this.makeKey(leftId, rightId);\n if (!this.fileHasKey(leftFilePath, key) || !this.fileHasKey(rightFilePath, key)) {\n return null;\n }\n\n const value = this.comparisons.get(key);\n return typeof value === \"number\" ? value : null;\n }\n\n /**\n * Invalidates all cached comparisons involving the provided file paths.\n */\n async invalidate(paths: string[]): Promise<void> {\n if (!this.initialized || !paths || paths.length === 0) return;\n\n const unique = new Set(paths);\n for (const filePath of unique) {\n const keys = this.fileIndex.get(filePath);\n if (!keys) continue;\n\n for (const key of keys) {\n this.comparisons.delete(key);\n for (const [otherPath, otherKeys] of this.fileIndex.entries()) {\n if (otherKeys.delete(key) && otherKeys.size === 0) {\n this.fileIndex.delete(otherPath);\n }\n }\n }\n\n this.fileIndex.delete(filePath);\n }\n\n if (this.comparisons.size === 0) {\n this.initialized = false;\n }\n }\n\n /**\n * Clears all cached data. Intended for test setup.\n */\n clear(): void {\n this.comparisons.clear();\n this.fileIndex.clear();\n this.initialized = false;\n }\n\n private addKeyForFile(filePath: string, key: string): void {\n const current = this.fileIndex.get(filePath) ?? new Set<string>();\n current.add(key);\n this.fileIndex.set(filePath, current);\n }\n\n private fileHasKey(filePath: string, key: string): boolean {\n const keys = this.fileIndex.get(filePath);\n return keys ? keys.has(key) : false;\n }\n\n private makeKey(leftId: string, rightId: string): string {\n return [leftId, rightId].sort().join(\"::\");\n }\n}\n","import debug from \"debug\";\nimport shortUuid from \"short-uuid\";\nimport { cosineSimilarity } from \"@langchain/core/utils/math\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { DuplicateAnalysisResult, DuplicateGroup, DuplicationScore, IndexUnit, IndexUnitType } from \"../types\";\nimport { indexConfig } from \"../config/indexConfig\";\nimport { DryConfig } from \"../types\";\nimport { DuplicationCache } from \"./DuplicationCache\";\n\nconst log = debug(\"DryScan:DuplicateService\");\n\nexport class DuplicateService {\n private config?: DryConfig;\n private readonly cache = DuplicationCache.getInstance();\n\n constructor(private readonly deps: DryScanServiceDeps) {}\n\n async findDuplicates(config: DryConfig): Promise<DuplicateAnalysisResult> {\n this.config = config;\n const allUnits = await this.deps.db.getAllUnits();\n if (allUnits.length < 2) {\n const score = this.computeDuplicationScore([], allUnits);\n return { duplicates: [], score };\n }\n\n const thresholds = this.resolveThresholds(config.threshold);\n const duplicates = this.computeDuplicates(allUnits, thresholds);\n const filteredDuplicates = duplicates.filter((group) => !this.isGroupExcluded(group));\n log(\"Found %d duplicate groups\", filteredDuplicates.length);\n\n // Update cache asynchronously; no need to block the main flow.\n this.cache.update(filteredDuplicates).catch((err) => log(\"Cache update failed: %O\", err));\n\n const score = this.computeDuplicationScore(filteredDuplicates, allUnits);\n return { duplicates: filteredDuplicates, score };\n }\n\n private resolveThresholds(functionThreshold?: number): { function: number; block: number; class: number } {\n const defaults = indexConfig.thresholds;\n const clamp = (value: number) => Math.min(1, Math.max(0, value));\n\n const base = functionThreshold ?? defaults.function;\n const blockOffset = defaults.block - defaults.function;\n const classOffset = defaults.class - defaults.function;\n\n const functionThresholdValue = clamp(base);\n return {\n function: functionThresholdValue,\n block: clamp(functionThresholdValue + blockOffset),\n class: clamp(functionThresholdValue + classOffset),\n };\n }\n\n private computeDuplicates(\n units: IndexUnit[],\n thresholds: { function: number; block: number; class: number }\n ): DuplicateGroup[] {\n const duplicates: DuplicateGroup[] = [];\n const byType = new Map<IndexUnitType, IndexUnit[]>();\n\n for (const unit of units) {\n const list = byType.get(unit.unitType) ?? [];\n list.push(unit);\n byType.set(unit.unitType, list);\n }\n\n for (const [type, typedUnits] of byType.entries()) {\n const threshold = this.getThreshold(type, thresholds);\n\n for (let i = 0; i < typedUnits.length; i++) {\n for (let j = i + 1; j < typedUnits.length; j++) {\n const left = typedUnits[i];\n const right = typedUnits[j];\n\n if (this.shouldSkipComparison(left, right)) continue;\n\n const cached = this.cache.get(left.id, right.id, left.filePath, right.filePath);\n let similarity: number | null = null;\n\n if (cached !== null) {\n similarity = cached;\n } else {\n if (!left.embedding || !right.embedding) continue;\n similarity = this.computeWeightedSimilarity(left, right);\n }\n\n if (similarity === null) continue;\n\n if (similarity >= threshold) {\n const exclusionString = this.deps.pairing.pairKeyForUnits(left, right);\n if (!exclusionString) continue;\n\n duplicates.push({\n id: `${left.id}::${right.id}`,\n similarity,\n shortId: shortUuid.generate(),\n exclusionString,\n left: {\n id: left.id,\n name: left.name,\n filePath: left.filePath,\n startLine: left.startLine,\n endLine: left.endLine,\n code: left.code,\n unitType: left.unitType,\n },\n right: {\n id: right.id,\n name: right.name,\n filePath: right.filePath,\n startLine: right.startLine,\n endLine: right.endLine,\n code: right.code,\n unitType: right.unitType,\n },\n });\n }\n }\n }\n }\n\n return duplicates.sort((a, b) => b.similarity - a.similarity);\n }\n\n private isGroupExcluded(group: DuplicateGroup): boolean {\n const config = this.config;\n if (!config || !config.excludedPairs || config.excludedPairs.length === 0) return false;\n const key = this.deps.pairing.pairKeyForUnits(group.left, group.right);\n if (!key) return false;\n const actual = this.deps.pairing.parsePairKey(key);\n if (!actual) return false;\n return config.excludedPairs.some((entry) => {\n const parsed = this.deps.pairing.parsePairKey(entry);\n return parsed ? this.deps.pairing.pairKeyMatches(actual, parsed) : false;\n });\n }\n\n private getThreshold(type: IndexUnitType, thresholds: { function: number; block: number; class: number }): number {\n if (type === IndexUnitType.CLASS) return thresholds.class;\n if (type === IndexUnitType.BLOCK) return thresholds.block;\n return thresholds.function;\n }\n\n private computeWeightedSimilarity(left: IndexUnit, right: IndexUnit): number {\n const selfSimilarity = this.similarityWithFallback(left, right);\n\n if (left.unitType === IndexUnitType.CLASS) {\n return selfSimilarity * indexConfig.weights.class.self;\n }\n\n if (left.unitType === IndexUnitType.FUNCTION) {\n const weights = indexConfig.weights.function;\n const hasParentClass = !!this.findParentOfType(left, IndexUnitType.CLASS) && !!this.findParentOfType(right, IndexUnitType.CLASS);\n const parentClassSimilarity = hasParentClass ? this.parentSimilarity(left, right, IndexUnitType.CLASS) : 0;\n\n // Re-normalize weights when parent context is missing, so standalone units aren't penalized.\n const totalWeight = weights.self + (hasParentClass ? weights.parentClass : 0);\n return ((weights.self * selfSimilarity) + (hasParentClass ? (weights.parentClass * parentClassSimilarity) : 0)) / totalWeight;\n }\n\n const weights = indexConfig.weights.block;\n const hasParentFunction = !!this.findParentOfType(left, IndexUnitType.FUNCTION) && !!this.findParentOfType(right, IndexUnitType.FUNCTION);\n const hasParentClass = !!this.findParentOfType(left, IndexUnitType.CLASS) && !!this.findParentOfType(right, IndexUnitType.CLASS);\n const parentFuncSim = hasParentFunction ? this.parentSimilarity(left, right, IndexUnitType.FUNCTION) : 0;\n const parentClassSim = hasParentClass ? this.parentSimilarity(left, right, IndexUnitType.CLASS) : 0;\n\n // Re-normalize weights when some parent context is missing.\n const totalWeight =\n weights.self +\n (hasParentFunction ? weights.parentFunction : 0) +\n (hasParentClass ? weights.parentClass : 0);\n\n return (\n (weights.self * selfSimilarity) +\n (hasParentFunction ? (weights.parentFunction * parentFuncSim) : 0) +\n (hasParentClass ? (weights.parentClass * parentClassSim) : 0)\n ) / totalWeight;\n }\n\n private parentSimilarity(left: IndexUnit, right: IndexUnit, targetType: IndexUnitType): number {\n const leftParent = this.findParentOfType(left, targetType);\n const rightParent = this.findParentOfType(right, targetType);\n if (!leftParent || !rightParent) return 0;\n return this.similarityWithFallback(leftParent, rightParent);\n }\n\n private similarityWithFallback(left: IndexUnit, right: IndexUnit): number {\n const leftHasEmbedding = this.hasVector(left);\n const rightHasEmbedding = this.hasVector(right);\n\n if (leftHasEmbedding && rightHasEmbedding) {\n return cosineSimilarity([left.embedding as number[]], [right.embedding as number[]])[0][0];\n }\n\n return this.childSimilarity(left, right);\n }\n\n private childSimilarity(left: IndexUnit, right: IndexUnit): number {\n const leftChildren = left.children ?? [];\n const rightChildren = right.children ?? [];\n if (leftChildren.length === 0 || rightChildren.length === 0) return 0;\n\n let best = 0;\n for (const lChild of leftChildren) {\n for (const rChild of rightChildren) {\n if (lChild.unitType !== rChild.unitType) continue;\n const sim = this.similarityWithFallback(lChild, rChild);\n if (sim > best) best = sim;\n }\n }\n return best;\n }\n\n private hasVector(unit: IndexUnit): boolean {\n return Array.isArray(unit.embedding) && unit.embedding.length > 0;\n }\n\n private shouldSkipComparison(left: IndexUnit, right: IndexUnit): boolean {\n if (left.unitType !== IndexUnitType.BLOCK || right.unitType !== IndexUnitType.BLOCK) {\n return false;\n }\n\n if (left.filePath !== right.filePath) {\n return false;\n }\n\n const leftContainsRight = left.startLine <= right.startLine && left.endLine >= right.endLine;\n const rightContainsLeft = right.startLine <= left.startLine && right.endLine >= left.endLine;\n return leftContainsRight || rightContainsLeft;\n }\n\n private findParentOfType(unit: IndexUnit, targetType: IndexUnitType): IndexUnit | null {\n let current: IndexUnit | undefined | null = unit.parent;\n while (current) {\n if (current.unitType === targetType) return current;\n current = current.parent;\n }\n return null;\n }\n\n private computeDuplicationScore(duplicates: DuplicateGroup[], allUnits: IndexUnit[]): DuplicationScore {\n const totalLines = this.calculateTotalLines(allUnits);\n\n if (totalLines === 0 || duplicates.length === 0) {\n return {\n score: 0,\n grade: \"Excellent\",\n totalLines,\n duplicateLines: 0,\n duplicateGroups: 0,\n };\n }\n\n const weightedDuplicateLines = duplicates.reduce((sum, group) => {\n const leftLines = group.left.endLine - group.left.startLine + 1;\n const rightLines = group.right.endLine - group.right.startLine + 1;\n const avgLines = (leftLines + rightLines) / 2;\n return sum + group.similarity * avgLines;\n }, 0);\n\n const score = (weightedDuplicateLines / totalLines) * 100;\n const grade = this.getScoreGrade(score);\n\n return {\n score,\n grade,\n totalLines,\n duplicateLines: Math.round(weightedDuplicateLines),\n duplicateGroups: duplicates.length,\n };\n }\n\n private calculateTotalLines(units: IndexUnit[]): number {\n return units.reduce((sum, unit) => {\n const lines = unit.endLine - unit.startLine + 1;\n return sum + lines;\n }, 0);\n }\n\n private getScoreGrade(score: number): DuplicationScore[\"grade\"] {\n if (score < 5) return \"Excellent\";\n if (score < 15) return \"Good\";\n if (score < 30) return \"Fair\";\n if (score < 50) return \"Poor\";\n return \"Critical\";\n }\n}","import { DryConfig } from \"../types\";\nimport { configStore } from \"../config/configStore\";\nimport { DryScanServiceDeps } from \"./types\";\nimport { IndexUnitType } from \"../types\";\nimport { minimatch } from \"minimatch\";\nimport { ParsedPairKey } from \"./PairingService\";\n\nexport class ExclusionService {\n private config?: DryConfig;\n\n constructor(private readonly deps: DryScanServiceDeps) {}\n\n async cleanupExcludedFiles(): Promise<void> {\n const config = await this.loadConfig();\n if (!config.excludedPaths || config.excludedPaths.length === 0) return;\n\n const units = await this.deps.db.getAllUnits();\n const files = await this.deps.db.getAllFiles();\n\n const unitPathsToRemove = new Set<string>();\n for (const unit of units) {\n if (this.pathExcluded(unit.filePath)) {\n unitPathsToRemove.add(unit.filePath);\n }\n }\n\n const filePathsToRemove = new Set<string>();\n for (const file of files) {\n if (this.pathExcluded(file.filePath)) {\n filePathsToRemove.add(file.filePath);\n }\n }\n\n const paths = [...new Set([...unitPathsToRemove, ...filePathsToRemove])];\n if (paths.length > 0) {\n await this.deps.db.removeUnitsByFilePaths(paths);\n await this.deps.db.removeFilesByFilePaths(paths);\n }\n }\n\n async cleanExclusions(): Promise<{ removed: number; kept: number }> {\n const config = await this.loadConfig();\n const units = await this.deps.db.getAllUnits();\n\n const actualPairsByType = {\n [IndexUnitType.CLASS]: this.buildPairKeys(units, IndexUnitType.CLASS),\n [IndexUnitType.FUNCTION]: this.buildPairKeys(units, IndexUnitType.FUNCTION),\n [IndexUnitType.BLOCK]: this.buildPairKeys(units, IndexUnitType.BLOCK),\n };\n\n const kept: string[] = [];\n const removed: string[] = [];\n\n for (const entry of config.excludedPairs || []) {\n const parsed = this.deps.pairing.parsePairKey(entry);\n if (!parsed) {\n removed.push(entry);\n continue;\n }\n\n const candidates = actualPairsByType[parsed.type];\n const matched = candidates.some((actual) => this.deps.pairing.pairKeyMatches(actual, parsed));\n if (matched) {\n kept.push(entry);\n } else {\n removed.push(entry);\n }\n }\n\n const nextConfig: DryConfig = { ...config, excludedPairs: kept };\n await configStore.save(this.deps.repoPath, nextConfig);\n this.config = nextConfig;\n\n return { removed: removed.length, kept: kept.length };\n }\n\n private pathExcluded(filePath: string): boolean {\n const config = this.config;\n if (!config || !config.excludedPaths || config.excludedPaths.length === 0) return false;\n return config.excludedPaths.some((pattern) => minimatch(filePath, pattern, { dot: true }));\n }\n\n private buildPairKeys(units: any[], type: IndexUnitType): ParsedPairKey[] {\n const typed = units.filter((u) => u.unitType === type);\n const pairs: ParsedPairKey[] = [];\n for (let i = 0; i < typed.length; i++) {\n for (let j = i + 1; j < typed.length; j++) {\n const key = this.deps.pairing.pairKeyForUnits(typed[i], typed[j]);\n const parsed = key ? this.deps.pairing.parsePairKey(key) : null;\n if (parsed) {\n pairs.push(parsed);\n }\n }\n }\n return pairs;\n }\n\n private async loadConfig(): Promise<DryConfig> {\n this.config = await configStore.get(this.deps.repoPath);\n return this.config;\n }\n}","import crypto from \"node:crypto\";\nimport debug from \"debug\";\nimport { minimatch } from \"minimatch\";\nimport { LanguageExtractor } from \"../extractors/LanguageExtractor\";\nimport { IndexUnitExtractor } from \"../IndexUnitExtractor\";\nimport { IndexUnit, IndexUnitType } from \"../types\";\nimport { BLOCK_HASH_ALGO } from \"../const\";\n\nconst log = debug(\"DryScan:pairs\");\n\ntype UnitLike = Pick<IndexUnit, \"unitType\" | \"filePath\" | \"name\" | \"code\">;\n\nexport interface ParsedPairKey {\n type: IndexUnitType;\n left: string;\n right: string;\n key: string;\n}\n\n/**\n * Service for building and parsing pair keys with extractor-aware labeling.\n */\nexport class PairingService {\n constructor(private readonly indexUnitExtractor: IndexUnitExtractor) {}\n\n /**\n * Creates a stable, order-independent key for two units of the same type.\n * Returns null when units differ in type so callers can skip invalid pairs.\n */\n pairKeyForUnits(left: UnitLike, right: UnitLike): string | null {\n if (left.unitType !== right.unitType) {\n log(\"Skipping pair with mismatched types: %s vs %s\", left.unitType, right.unitType);\n return null;\n }\n const type = left.unitType;\n const leftLabel = this.unitLabel(left);\n const rightLabel = this.unitLabel(right);\n const [a, b] = [leftLabel, rightLabel].sort();\n return `${type}|${a}|${b}`;\n }\n\n /**\n * Parses a raw pair key into its components, returning null for malformed values.\n * Sorting is applied so callers can compare pairs without worrying about order.\n */\n parsePairKey(value: string): ParsedPairKey | null {\n const parts = value.split(\"|\");\n if (parts.length !== 3) {\n log(\"Invalid pair key format: %s\", value);\n return null;\n }\n const [typeRaw, leftRaw, rightRaw] = parts;\n const type = this.stringToUnitType(typeRaw);\n if (!type) {\n log(\"Unknown unit type in pair key: %s\", typeRaw);\n return null;\n }\n const [left, right] = [leftRaw, rightRaw].sort();\n return { type, left, right, key: `${type}|${left}|${right}` };\n }\n\n /**\n * Checks whether an actual pair key satisfies a pattern, with glob matching for class paths.\n */\n pairKeyMatches(actual: ParsedPairKey, pattern: ParsedPairKey): boolean {\n if (actual.type !== pattern.type) return false;\n if (actual.type === IndexUnitType.CLASS) {\n // Allow glob matching for class file paths.\n const forward =\n minimatch(actual.left, pattern.left, { dot: true }) &&\n minimatch(actual.right, pattern.right, { dot: true });\n const swapped =\n minimatch(actual.left, pattern.right, { dot: true }) &&\n minimatch(actual.right, pattern.left, { dot: true });\n return forward || swapped;\n }\n\n // Functions and blocks use exact matching on canonical strings.\n return (\n (actual.left === pattern.left && actual.right === pattern.right) ||\n (actual.left === pattern.right && actual.right === pattern.left)\n );\n }\n\n /**\n * Derives a reversible, extractor-aware label for a unit.\n * Extractors may override; fallback uses a fixed format per unit type.\n */\n unitLabel(unit: UnitLike): string {\n const extractor = this.findExtractor(unit.filePath);\n const customLabel = extractor?.unitLabel?.(unit as IndexUnit);\n if (customLabel) return customLabel;\n\n switch (unit.unitType) {\n case IndexUnitType.CLASS:\n return unit.filePath;\n case IndexUnitType.FUNCTION:\n return this.canonicalFunctionSignature(unit);\n case IndexUnitType.BLOCK:\n return this.normalizedBlockHash(unit);\n default:\n return unit.name;\n }\n }\n\n private findExtractor(filePath: string): LanguageExtractor | undefined {\n return this.indexUnitExtractor.extractors.find((ex) => ex.supports(filePath));\n }\n\n private canonicalFunctionSignature(unit: UnitLike): string {\n const arity = this.extractArity(unit.code);\n return `${unit.name}(arity:${arity})`;\n }\n\n /**\n * Normalizes block code (strips comments/whitespace) and hashes it for pair matching.\n */\n private normalizedBlockHash(unit: UnitLike): string {\n const normalized = this.normalizeCode(unit.code);\n return crypto.createHash(BLOCK_HASH_ALGO).update(normalized).digest(\"hex\");\n }\n\n private stringToUnitType(value: string): IndexUnitType | null {\n if (value === IndexUnitType.CLASS) return IndexUnitType.CLASS;\n if (value === IndexUnitType.FUNCTION) return IndexUnitType.FUNCTION;\n if (value === IndexUnitType.BLOCK) return IndexUnitType.BLOCK;\n return null;\n }\n\n private extractArity(code: string): number {\n const match = code.match(/^[^{]*?\\(([^)]*)\\)/s);\n if (!match) return 0;\n const params = match[1]\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n return params.length;\n }\n\n private normalizeCode(code: string): string {\n const withoutBlockComments = code.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n const withoutLineComments = withoutBlockComments.replace(/\\/\\/[^\\n\\r]*/g, \"\");\n return withoutLineComments.replace(/\\s+/g, \"\");\n }\n}\n"],"mappings":"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","names":["upath","fs","path","fs","upath","crypto","glob","upath","upath","fs","upath","path","fs","crypto","upath","glob","fs","upath","Column","Entity","PrimaryColumn","PrimaryColumn","Column","Entity","fs","upath","path","fs","debug","log","debug","path","fs","debug","path","fs","debug","log","debug","path","fs","log","debug","debug","log","debug","score","weights","hasParentClass","totalWeight","crypto","debug","minimatch","log","debug","minimatch","crypto","upath","fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goshenkata/dryscan-core",
3
- "version": "1.0.16",
3
+ "version": "1.1.1",
4
4
  "description": "Core library for DryScan - semantic code duplication analyzer",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,7 +12,7 @@
12
12
  }
13
13
  },
14
14
  "scripts": {
15
- "build": "tsup src/index.ts --format esm --dts --sourcemap --clean --outDir dist",
15
+ "build": "tsup",
16
16
  "clean": "rm -rf dist",
17
17
  "test": "tsx ../node_modules/mocha/bin/mocha \"test/**/*.test.mjs\"",
18
18
  "coverage": "c8 tsx ../node_modules/mocha/bin/mocha \"test/**/*.test.mjs\""
@@ -45,14 +45,13 @@
45
45
  "typescript": "^5.9.3"
46
46
  },
47
47
  "dependencies": {
48
+ "@langchain/community": "^1.1.1",
48
49
  "@langchain/core": "^1.1.8",
49
- "@langchain/google-genai": "^2.1.3",
50
- "@langchain/ollama": "^1.1.0",
50
+ "@huggingface/inference": "^4.13.5",
51
51
  "debug": "^4.4.3",
52
52
  "glob-gitignore": "^1.0.15",
53
53
  "ignore": "^7.0.5",
54
54
  "jsonschema": "^1.5.0",
55
- "langchain": "^1.2.3",
56
55
  "minimatch": "^10.1.1",
57
56
  "reflect-metadata": "^0.2.2",
58
57
  "short-uuid": "^6.0.3",
@@ -12,7 +12,6 @@ export const DEFAULT_CONFIG: DryConfig = {
12
12
  minLines: 3,
13
13
  minBlockLines: 5,
14
14
  threshold: 0.88,
15
- embeddingModel: "embeddinggemma",
16
15
  embeddingSource: "http://localhost:11434",
17
16
  contextLength: 2048,
18
17
  };
@@ -27,7 +26,6 @@ const partialConfigSchema: Schema = {
27
26
  minLines: { type: "number" },
28
27
  minBlockLines: { type: "number" },
29
28
  threshold: { type: "number" },
30
- embeddingModel: { type: "string" },
31
29
  embeddingSource: { type: "string" },
32
30
  contextLength: { type: "number" },
33
31
  },
@@ -41,7 +39,7 @@ const fullConfigSchema: Schema = {
41
39
  "minLines",
42
40
  "minBlockLines",
43
41
  "threshold",
44
- "embeddingModel",
42
+ "embeddingSource",
45
43
  "contextLength",
46
44
  ],
47
45
  };
@@ -1,15 +1,22 @@
1
1
  import debug from "debug";
2
2
  import { OllamaEmbeddings } from "@langchain/ollama";
3
- import { GoogleGenerativeAIEmbeddings } from "@langchain/google-genai";
4
- import { TaskType } from "@google/generative-ai";
3
+ import { HuggingFaceInferenceEmbeddings } from "@langchain/community/embeddings/hf";
5
4
  import { IndexUnit } from "../types";
6
5
  import { configStore } from "../config/configStore";
7
6
 
8
7
  const log = debug("DryScan:EmbeddingService");
9
8
 
9
+ // Model names for each provider
10
+ const OLLAMA_MODEL = "embeddinggemma";
11
+ const HUGGINGFACE_MODEL = "google/embeddinggemma-300m";
12
+
10
13
  export class EmbeddingService {
11
14
  constructor(private readonly repoPath: string) { }
12
15
 
16
+ /**
17
+ * Generates an embedding for the given index unit using the configured provider.
18
+ * Skips embedding if code exceeds the configured context length.
19
+ */
13
20
  async addEmbedding(fn: IndexUnit): Promise<IndexUnit> {
14
21
  const config = await configStore.get(this.repoPath);
15
22
  const maxContext = config?.contextLength ?? 2048;
@@ -23,35 +30,42 @@ export class EmbeddingService {
23
30
  return { ...fn, embedding: null };
24
31
  }
25
32
 
26
- const model = config.embeddingModel ?? undefined
27
- const source = config.embeddingSource
33
+ const source = config.embeddingSource;
28
34
  if (!source) {
29
35
  const message = `Embedding source is not configured for repository at ${this.repoPath}`;
30
36
  log(message);
31
37
  throw new Error(message);
32
38
  }
33
39
 
34
- const embeddings = this.buildProvider(source, model);
40
+ const embeddings = this.buildProvider(source);
35
41
  const embedding = await embeddings.embedQuery(fn.code);
36
42
  return { ...fn, embedding };
37
43
  }
38
44
 
39
- private buildProvider(source: string, model: string) {
40
- if (source === "google") {
41
- return new GoogleGenerativeAIEmbeddings({
42
- model: model ?? "gemini-embedding-001",
43
- taskType: TaskType.SEMANTIC_SIMILARITY,
45
+ /**
46
+ * Builds the embedding provider based on the source configuration.
47
+ * - URL (http/https): Uses Ollama with "embeddinggemma" model
48
+ * - "huggingface": Uses HuggingFace Inference API with "embeddinggemma-300m" model
49
+ */
50
+ private buildProvider(source: string) {
51
+ // HuggingFace Inference API
52
+ if (source.toLowerCase() === "huggingface") {
53
+ log("Using HuggingFace Inference with model: %s", HUGGINGFACE_MODEL);
54
+ return new HuggingFaceInferenceEmbeddings({
55
+ model: HUGGINGFACE_MODEL,
44
56
  });
45
57
  }
46
58
 
59
+ // Ollama (local or remote URL)
47
60
  if (/^https?:\/\//i.test(source)) {
61
+ log("Using Ollama at %s with model: %s", source, OLLAMA_MODEL);
48
62
  return new OllamaEmbeddings({
49
- model: model ?? "embeddinggemma",
63
+ model: OLLAMA_MODEL,
50
64
  baseUrl: source,
51
65
  });
52
66
  }
53
67
 
54
- const message = `Unsupported embedding source: ${source || "(empty)"}`;
68
+ const message = `Unsupported embedding source: ${source || "(empty)"}. Use "huggingface" or an Ollama URL.`;
55
69
  log(message);
56
70
  throw new Error(message);
57
71
  }
package/src/types.ts CHANGED
@@ -51,8 +51,7 @@ export interface DryConfig {
51
51
  minLines: number;
52
52
  minBlockLines: number;
53
53
  threshold: number;
54
- embeddingModel: string;
55
- embeddingSource?: string;
54
+ embeddingSource: string;
56
55
  contextLength: number;
57
56
  }
58
57
 
package/tsup.config.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ sourcemap: true,
8
+ clean: true,
9
+ outDir: 'dist',
10
+ external: [
11
+ '@langchain/ollama',
12
+ '@langchain/community',
13
+ '@langchain/core',
14
+ ],
15
+ });