graph-indexer 1.0.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config.mjs ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * @file config.mjs
3
+ * @description Single source of truth for runtime configuration. Resolves, in
4
+ * order of precedence, CLI flags > environment variables >
5
+ * `.graph-indexer.json` > built-in defaults, into one frozen object
6
+ * consumed by the indexer, watch daemon and MCP server.
7
+ * @author MaquinaTech <https://github.com/MaquinaTech>
8
+ * @copyright (c) 2026 MaquinaTech. All rights reserved.
9
+ * @license MIT
10
+ */
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+
14
+ export const DEFAULTS = Object.freeze({
15
+ storage: 'memory', // 'memory' (default, zero-dependency) | 'sqlite'
16
+ embedModel: 'nomic-embed-text',
17
+ ollamaHost: 'http://localhost:11434',
18
+ enrichment: Object.freeze({
19
+ enabled: false,
20
+ model: 'qwen2.5-coder:1.5b', // small, code-aware; configurable, opt-in
21
+ coreRatio: 1.0, // 1.0 = all production files (tests/examples always excluded);
22
+ // <1 bounds enrichment to the most-central share by PageRank
23
+ maxChunks: 500, // cap on NEW LLM calls per index run (cache accumulates across runs)
24
+ concurrency: 12, // parallel Ollama requests during enrichment
25
+ }),
26
+ rerank: Object.freeze({
27
+ enabled: false, // opt-in: one LLM call (~1–2 s) per natural-language query
28
+ model: 'qwen2.5-coder:7b', // judge quality matters: 7B measured +50% semantic rank-1, 1.5B ~nil
29
+ topM: 8, // fused results shown to the judge (8 measured better than 10)
30
+ }),
31
+ });
32
+
33
+ /** Reads `.graph-indexer.json` from a directory, tolerating absence/corruption. */
34
+ export function loadConfigFile(root) {
35
+ const configPath = path.join(root, '.graph-indexer.json');
36
+ try {
37
+ if (fs.existsSync(configPath)) return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
38
+ } catch { /* malformed config is ignored — defaults apply */ }
39
+ return {};
40
+ }
41
+
42
+ /** Returns the value following `--flag` in argv, or undefined. */
43
+ function flagValue(argv, flag) {
44
+ const i = argv.indexOf(flag);
45
+ return i >= 0 && i + 1 < argv.length ? argv[i + 1] : undefined;
46
+ }
47
+
48
+ /**
49
+ * Resolve the effective configuration.
50
+ *
51
+ * @param {object} [opts]
52
+ * @param {string[]} [opts.argv] Defaults to process.argv.slice(2).
53
+ * @param {object} [opts.env] Defaults to process.env.
54
+ * @param {string} [opts.cwd] Defaults to process.cwd().
55
+ * @returns {Readonly<object>}
56
+ */
57
+ export function resolveConfig({ argv = process.argv.slice(2), env = process.env, cwd = process.cwd() } = {}) {
58
+ // Project root: --repo wins, then MCP_PROJECT_ROOT, then cwd. The config file,
59
+ // index artifacts and language selection are all anchored to this directory.
60
+ const repoArg = flagValue(argv, '--repo');
61
+ const projectRoot = path.resolve(repoArg || env.MCP_PROJECT_ROOT || cwd);
62
+
63
+ const file = loadConfigFile(projectRoot);
64
+ const fileEnrich = file.enrichment || {};
65
+
66
+ // Storage: --use-sqlite flag or "storage" key. The in-memory engine remains
67
+ // the default so the zero-dependency baseline is never disturbed implicitly.
68
+ const storage = argv.includes('--use-sqlite') ? 'sqlite'
69
+ : (file.storage === 'sqlite' ? 'sqlite' : DEFAULTS.storage);
70
+
71
+ const enrichmentEnabled = argv.includes('--llm-enrichment')
72
+ || argv.includes('--enrich')
73
+ || Boolean(fileEnrich.enabled);
74
+
75
+ const ollamaHost = env.OLLAMA_HOST || file.ollamaHost || DEFAULTS.ollamaHost;
76
+ const embeddingsEnabled = env.INDEXER_EMBEDDINGS !== 'off';
77
+
78
+ return Object.freeze({
79
+ projectRoot,
80
+ storage,
81
+ // Index artifact paths — all derive from the same stem next to the project.
82
+ indexPath: path.join(projectRoot, 'code-index.json'),
83
+ embeddingPath: path.join(projectRoot, 'code-index.embeddings.bin'),
84
+ sqlitePath: path.join(projectRoot, 'code-index.db'),
85
+ enrichmentCachePath: path.join(projectRoot, 'code-index.enrichment.json'),
86
+
87
+ languages: Array.isArray(file.languages) ? file.languages : null, // null = all
88
+
89
+ ollamaHost,
90
+ embeddingsEnabled,
91
+ embedModel: file.embedModel || DEFAULTS.embedModel,
92
+
93
+ enrichment: Object.freeze({
94
+ enabled: enrichmentEnabled,
95
+ model: flagValue(argv, '--enrich-model') || fileEnrich.model || DEFAULTS.enrichment.model,
96
+ coreRatio: Number(fileEnrich.coreRatio) > 0 ? Number(fileEnrich.coreRatio) : DEFAULTS.enrichment.coreRatio,
97
+ maxChunks: Number(flagValue(argv, '--enrich-max')) > 0
98
+ ? Number(flagValue(argv, '--enrich-max'))
99
+ : (Number.isInteger(fileEnrich.maxChunks) ? fileEnrich.maxChunks : DEFAULTS.enrichment.maxChunks),
100
+ concurrency: Number(flagValue(argv, '--enrich-concurrency')) > 0
101
+ ? Number(flagValue(argv, '--enrich-concurrency'))
102
+ : (Number(fileEnrich.concurrency) > 0 ? Number(fileEnrich.concurrency) : DEFAULTS.enrichment.concurrency),
103
+ }),
104
+
105
+ rerank: Object.freeze({
106
+ enabled: Boolean((file.rerank || {}).enabled),
107
+ model: (file.rerank || {}).model || DEFAULTS.rerank.model,
108
+ topM: Number.isInteger((file.rerank || {}).topM) ? (file.rerank || {}).topM : DEFAULTS.rerank.topM,
109
+ }),
110
+ });
111
+ }
112
+
113
+ // Memoised singleton for import-time consumers (e.g. parser-utils language loading).
114
+ let _cached = null;
115
+ export function getConfig() {
116
+ if (!_cached) _cached = resolveConfig();
117
+ return _cached;
118
+ }