@untangle-code/config 0.0.0-stage → 0.1.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/LICENSE +661 -0
- package/NOTICE +90 -0
- package/README.md +262 -2
- package/THIRD_PARTY_LICENSES.md +29 -0
- package/dist/chunker-budget.d.ts +58 -0
- package/dist/chunker-budget.d.ts.map +1 -0
- package/dist/chunker-budget.js +11 -0
- package/dist/chunker-budget.js.map +1 -0
- package/dist/defaults.d.ts +50 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +179 -0
- package/dist/defaults.js.map +1 -0
- package/dist/discovery.d.ts +40 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +76 -0
- package/dist/discovery.js.map +1 -0
- package/dist/env.d.ts +37 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +100 -0
- package/dist/env.js.map +1 -0
- package/dist/hash.d.ts +37 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +52 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/load.d.ts +61 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +264 -0
- package/dist/load.js.map +1 -0
- package/dist/merge.d.ts +32 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +65 -0
- package/dist/merge.js.map +1 -0
- package/dist/migrations.d.ts +26 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +40 -0
- package/dist/migrations.js.map +1 -0
- package/dist/parse.d.ts +7 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +14 -0
- package/dist/parse.js.map +1 -0
- package/dist/paths.d.ts +14 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +37 -0
- package/dist/paths.js.map +1 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +19 -0
- package/dist/schema.js.map +1 -0
- package/dist/secrets.d.ts +33 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +69 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +323 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/user-config.d.ts +13 -0
- package/dist/user-config.d.ts.map +1 -0
- package/dist/user-config.js +34 -0
- package/dist/user-config.js.map +1 -0
- package/dist/validate.d.ts +36 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +337 -0
- package/dist/validate.js.map +1 -0
- package/package.json +65 -3
- package/schemas/config-v1.json +375 -0
- package/src/chunker-budget.ts +63 -0
- package/src/defaults.ts +207 -0
- package/src/discovery.ts +95 -0
- package/src/env.ts +141 -0
- package/src/hash.ts +71 -0
- package/src/index.ts +51 -0
- package/src/load.ts +346 -0
- package/src/merge.ts +69 -0
- package/src/migrations.ts +64 -0
- package/src/parse.ts +18 -0
- package/src/paths.ts +42 -0
- package/src/schema.ts +23 -0
- package/src/secrets.ts +78 -0
- package/src/types.ts +366 -0
- package/src/user-config.ts +48 -0
- package/src/validate.ts +430 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration types (spec.md §4.2–§4.8, §5.7; product.md §11; portfolio.md
|
|
3
|
+
* §14.4; architecture.md §6.3, §23.2).
|
|
4
|
+
*
|
|
5
|
+
* These are the *raw*, *partial*-friendly shapes a `.untangle.json`,
|
|
6
|
+
* environment layer or CLI argument object may contribute. The fully merged,
|
|
7
|
+
* defaulted and validated shape produced by {@link "./load.js"} carries the
|
|
8
|
+
* same interfaces but with all optional fields the product defaults fill in
|
|
9
|
+
* actually present — see `EffectiveUntangleConfigV1` at the bottom of this
|
|
10
|
+
* file.
|
|
11
|
+
*/
|
|
12
|
+
import type { ContextArtifactConfig, RepositoryConfig } from "@untangle-code/domain";
|
|
13
|
+
|
|
14
|
+
// Re-exported so consumers of @untangle-code/config never need to reach into
|
|
15
|
+
// @untangle-code/domain themselves for these two config-shaped types (domain
|
|
16
|
+
// README, decision 5: RepositoryConfig lives in domain by WP-01 order;
|
|
17
|
+
// ContextArtifactConfig likewise — this package re-exports rather than
|
|
18
|
+
// duplicating either).
|
|
19
|
+
export type { ContextArtifactConfig, ContextArtifactType } from "@untangle-code/domain";
|
|
20
|
+
export type { RepositoryConfig } from "@untangle-code/domain";
|
|
21
|
+
|
|
22
|
+
/** spec.md §4.4. */
|
|
23
|
+
export interface QdrantConfig {
|
|
24
|
+
mode: "managed" | "external";
|
|
25
|
+
url?: string;
|
|
26
|
+
/** Name of the environment variable holding the API key — never the key itself (architecture.md §23.2). */
|
|
27
|
+
apiKeyEnv?: string;
|
|
28
|
+
collectionPrefix?: string;
|
|
29
|
+
requestTimeoutMs?: number;
|
|
30
|
+
maxRetries?: number;
|
|
31
|
+
upsertMaxPoints?: number;
|
|
32
|
+
upsertBudgetBytes?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Search backend selection. Relative local paths resolve against the repository root. */
|
|
36
|
+
export interface StorageConfig {
|
|
37
|
+
backend: "lancedb" | "qdrant";
|
|
38
|
+
/** Embedded LanceDB directory; defaults to `.untangle/index/lancedb`. */
|
|
39
|
+
path?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** spec.md §4.5. */
|
|
43
|
+
export interface EmbeddingConfig {
|
|
44
|
+
provider: "ollama";
|
|
45
|
+
mode: "auto" | "managed" | "external";
|
|
46
|
+
url?: string;
|
|
47
|
+
/** Name of the environment variable holding the API key — never the key itself (architecture.md §23.2). */
|
|
48
|
+
apiKeyEnv?: string;
|
|
49
|
+
model: string;
|
|
50
|
+
dimensions: number;
|
|
51
|
+
contextLength: number;
|
|
52
|
+
queryPrefix: string;
|
|
53
|
+
documentPrefix: string;
|
|
54
|
+
includePath: boolean;
|
|
55
|
+
batchSize: number;
|
|
56
|
+
maxConnections: number;
|
|
57
|
+
requestTimeoutMs: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** spec.md §4.6. */
|
|
61
|
+
export interface IndexConfig {
|
|
62
|
+
watch: boolean;
|
|
63
|
+
reconcileIntervalMinutes: number;
|
|
64
|
+
maxFileSizeMb: number;
|
|
65
|
+
maxGraphFileSizeMb: number;
|
|
66
|
+
scanConcurrency: number;
|
|
67
|
+
embeddingConcurrency: number;
|
|
68
|
+
filesPerCheckpoint: number;
|
|
69
|
+
targetChunkTokens: number;
|
|
70
|
+
maxChunkTokens: number;
|
|
71
|
+
fallbackOverlapTokens: number;
|
|
72
|
+
includeDotFiles: boolean;
|
|
73
|
+
indexExtensionless: boolean;
|
|
74
|
+
extraExtensions: string[];
|
|
75
|
+
extensionLanguageMap: Record<string, string>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Search defaults (Untangle decision — spec.md §13 defines the request/result
|
|
80
|
+
* shapes `SearchRequestV1`/`SearchResultV1` in domain, but names no
|
|
81
|
+
* `SearchConfig` fields; this shape documents which of those request knobs
|
|
82
|
+
* get a project-wide default and adds the boost weights spec.md §13.3 lists
|
|
83
|
+
* without assigning default magnitudes).
|
|
84
|
+
*/
|
|
85
|
+
export interface SearchConfig {
|
|
86
|
+
/** Default `limit` when a search request omits it. */
|
|
87
|
+
defaultLimit: number;
|
|
88
|
+
/** Default `candidateLimit` (pre-RRF/pre-filter pool size) when omitted. */
|
|
89
|
+
defaultCandidateLimit: number;
|
|
90
|
+
/** Hard ceiling a caller-supplied `candidateLimit` is clamped to. */
|
|
91
|
+
maxCandidateLimit: number;
|
|
92
|
+
/** Default `diversify` (MMR, architecture.md §18.5) when a request omits it. */
|
|
93
|
+
diversifyByDefault: boolean;
|
|
94
|
+
/** Default `minScore` when a request omits it. */
|
|
95
|
+
minScoreDefault?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Calibrated absence decisions, keyed by the exact embedding profile id.
|
|
98
|
+
*
|
|
99
|
+
* These values are deliberately optional and have no product default: raw
|
|
100
|
+
* cosine scores are model-specific, and their useful separation also
|
|
101
|
+
* depends on the indexed corpus. A policy therefore only applies when its
|
|
102
|
+
* key, workspace id, and physical collection all match the active target.
|
|
103
|
+
*/
|
|
104
|
+
relevancePolicies?: Record<string, SearchRelevancePolicyBindingConfigV1>;
|
|
105
|
+
/** Relative weights of the additive score components (spec.md §13.3). */
|
|
106
|
+
boostWeights: {
|
|
107
|
+
exactSymbol: number;
|
|
108
|
+
exactPath: number;
|
|
109
|
+
repoPriority: number;
|
|
110
|
+
graphProximity: number;
|
|
111
|
+
recency: number;
|
|
112
|
+
duplicatePenalty: number;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Corpus binding for a calibrated search relevance policy (WP-231). */
|
|
117
|
+
export interface SearchRelevancePolicyBindingConfigV1 {
|
|
118
|
+
/** Exact logical workspace used while calibrating the thresholds. */
|
|
119
|
+
workspaceId: string;
|
|
120
|
+
/** Exact physical vector collection used while calibrating the thresholds. */
|
|
121
|
+
collection: string;
|
|
122
|
+
policy: SearchRelevancePolicyConfigV1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Config representation of the search engine's calibrated relevance policy (WP-231). */
|
|
126
|
+
export interface SearchRelevancePolicyConfigV1 {
|
|
127
|
+
/** Human- and machine-readable provenance of the measurements behind the thresholds. */
|
|
128
|
+
calibrationId: string;
|
|
129
|
+
/** Absolute minimum cosine score of the strongest dense candidate, in [-1, 1]. */
|
|
130
|
+
minimumDenseScore: number;
|
|
131
|
+
/** Minimum difference between rank 1 and `denseReferenceRank`, in [0, 2]. */
|
|
132
|
+
minimumDenseSpread: number;
|
|
133
|
+
/** One-based dense rank used for the relative comparison; at least 2. */
|
|
134
|
+
denseReferenceRank: number;
|
|
135
|
+
/** Minimum IDF-weighted fraction of query evidence found together in one candidate. */
|
|
136
|
+
minimumCandidateIdfCoverage: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Graph traversal limits (Untangle decision — architecture.md §17.4 names
|
|
141
|
+
* `maxDepth`/`maxNodes`/`maxEdges`/`maxWallTimeMs` as per-query traversal
|
|
142
|
+
* limits without a config shape; this makes them project-configurable
|
|
143
|
+
* defaults). `includeDynamicImports` reflects the `dynamic_import` edge kind
|
|
144
|
+
* from spec.md §14.2 being switchable.
|
|
145
|
+
*/
|
|
146
|
+
export interface GraphConfig {
|
|
147
|
+
enabled: boolean;
|
|
148
|
+
maxTraversalDepth: number;
|
|
149
|
+
maxTraversalNodes: number;
|
|
150
|
+
maxTraversalEdges: number;
|
|
151
|
+
maxTraversalWallTimeMs: number;
|
|
152
|
+
includeDynamicImports: boolean;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Complexity engine switches (Untangle decision — spec.md §16 defines the
|
|
157
|
+
* metrics and algorithm IDs exhaustively but no `ComplexityConfig` shape;
|
|
158
|
+
* product.md §11's example config uses `enabled` and `cognitiveAlgorithm`,
|
|
159
|
+
* which anchor this shape). `cyclomaticVariant` selects which of classic
|
|
160
|
+
* (§16.2) and extended (§16.3) get computed; both are cheap to compute
|
|
161
|
+
* together so "both" is the default.
|
|
162
|
+
*/
|
|
163
|
+
export interface ComplexityConfig {
|
|
164
|
+
enabled: boolean;
|
|
165
|
+
/** Overridable for future algorithm versions; defaults to `COGNITIVE_ALGORITHM_ID` from domain. */
|
|
166
|
+
cognitiveAlgorithm: string;
|
|
167
|
+
cyclomaticVariant: "classic" | "extended" | "both";
|
|
168
|
+
maintainabilityIndexEnabled: boolean;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Change Intelligence configuration (Untangle decision — spec.md §17.6 gives
|
|
173
|
+
* the default risk score weights and requires every component to be
|
|
174
|
+
* disclosed with its weight and contribution, but does not name a config
|
|
175
|
+
* type). Weights MUST sum to `1.0` within a `±0.001` tolerance, mirroring the
|
|
176
|
+
* validation rule portfolio.md §14.4 states for `blueprint.weights`.
|
|
177
|
+
*/
|
|
178
|
+
export interface ChangeConfig {
|
|
179
|
+
enabled: boolean;
|
|
180
|
+
riskWeights: {
|
|
181
|
+
churn: number;
|
|
182
|
+
positiveCognitiveDelta: number;
|
|
183
|
+
positiveCyclomaticDelta: number;
|
|
184
|
+
blastRadius: number;
|
|
185
|
+
graphCentrality: number;
|
|
186
|
+
semanticChange: number;
|
|
187
|
+
testDistance: number;
|
|
188
|
+
};
|
|
189
|
+
symbolMatching: {
|
|
190
|
+
/** Minimum token/MinHash similarity (spec.md §17.3 step 4) to accept a rename/move candidate. */
|
|
191
|
+
tokenSimilarityMin: number;
|
|
192
|
+
/** Whether step 6 (embedding cosine at equal profile) participates at all. */
|
|
193
|
+
enableEmbeddingSimilarity: boolean;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** spec.md §4.7. */
|
|
198
|
+
export interface QualityGateConfig {
|
|
199
|
+
mode: "off" | "warn" | "fail";
|
|
200
|
+
newCyclesAllowed: number;
|
|
201
|
+
cognitivePerSymbolMax?: number;
|
|
202
|
+
cognitiveDeltaPerSymbolMax?: number;
|
|
203
|
+
cyclomaticPerSymbolMax?: number;
|
|
204
|
+
cyclomaticDeltaPerSymbolMax?: number;
|
|
205
|
+
maxNestingPerSymbolMax?: number;
|
|
206
|
+
maintainabilityIndexMin?: number;
|
|
207
|
+
riskScoreMax?: number;
|
|
208
|
+
minimumParseQuality: "failed" | "partial" | "complete";
|
|
209
|
+
ignoreExistingViolations: boolean;
|
|
210
|
+
include?: string[];
|
|
211
|
+
exclude?: string[];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Telemetry (spec.md §24: "Telemetrie MUSS standardmäßig aus sein" — telemetry
|
|
216
|
+
* MUST default to off; §23.2/§23.3 name the log/metrics facilities this
|
|
217
|
+
* switches). Shape beyond `enabled` is an Untangle decision.
|
|
218
|
+
*/
|
|
219
|
+
export interface TelemetryConfig {
|
|
220
|
+
/** Default `false` — local-first, opt-in only (spec.md §24). */
|
|
221
|
+
enabled: boolean;
|
|
222
|
+
logLevel: "error" | "warn" | "info" | "debug" | "trace";
|
|
223
|
+
/** Whether internal counters (spec.md §23.3) are exported to an external system. */
|
|
224
|
+
metricsExport: "none" | "prometheus";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** portfolio.md §13. */
|
|
228
|
+
export interface PortfolioEnrichmentConfig {
|
|
229
|
+
/** Default `false` — no registry/network access without opt-in (portfolio.md §13). */
|
|
230
|
+
enabled: boolean;
|
|
231
|
+
registries: ("npm" | "pypi" | "crates" | "maven" | "nuget" | "go" | "composer")[];
|
|
232
|
+
registryUrls?: Record<string, string>;
|
|
233
|
+
/** Default `168` (one week). */
|
|
234
|
+
cacheTtlHours: number;
|
|
235
|
+
timeoutMs: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** portfolio.md §14.4 — normative shape. */
|
|
239
|
+
export interface PortfolioConfig {
|
|
240
|
+
/** Default `true`. */
|
|
241
|
+
enabled?: boolean;
|
|
242
|
+
/** Default `false`. */
|
|
243
|
+
includeTransitive?: boolean;
|
|
244
|
+
/** Default `2`. */
|
|
245
|
+
conventionMinRepos?: number;
|
|
246
|
+
conventions?: {
|
|
247
|
+
pinned?: string[];
|
|
248
|
+
suppressed?: string[];
|
|
249
|
+
};
|
|
250
|
+
radar?: {
|
|
251
|
+
/** Default `0.5`. */
|
|
252
|
+
adoptShareMin?: number;
|
|
253
|
+
/** Default `0.2`. */
|
|
254
|
+
maintainShareMin?: number;
|
|
255
|
+
/** Default `540`. */
|
|
256
|
+
retireAfterDays?: number;
|
|
257
|
+
};
|
|
258
|
+
blueprint?: {
|
|
259
|
+
weights?: {
|
|
260
|
+
/** Default `0.40`. */
|
|
261
|
+
adoption?: number;
|
|
262
|
+
/** Default `0.20`. */
|
|
263
|
+
recency?: number;
|
|
264
|
+
/** Default `0.25`. */
|
|
265
|
+
health?: number;
|
|
266
|
+
/** Default `0.15`. */
|
|
267
|
+
versionConsistency?: number;
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
enrichment?: PortfolioEnrichmentConfig;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Main configuration schema (spec.md §4.2), extended with `portfolio`
|
|
275
|
+
* (portfolio.md §14.4, folded into `UntangleConfigV1` per portfolio.md
|
|
276
|
+
* §18.1's planned spec.md §4.2 amendment).
|
|
277
|
+
*/
|
|
278
|
+
export interface UntangleConfigV1 {
|
|
279
|
+
schemaVersion: 1;
|
|
280
|
+
workspaceId?: string;
|
|
281
|
+
projectId?: string;
|
|
282
|
+
repositories?: RepositoryConfig[];
|
|
283
|
+
storage?: StorageConfig;
|
|
284
|
+
qdrant?: QdrantConfig;
|
|
285
|
+
embeddings?: EmbeddingConfig;
|
|
286
|
+
index?: IndexConfig;
|
|
287
|
+
search?: SearchConfig;
|
|
288
|
+
graph?: GraphConfig;
|
|
289
|
+
complexity?: ComplexityConfig;
|
|
290
|
+
changes?: ChangeConfig;
|
|
291
|
+
qualityGate?: QualityGateConfig;
|
|
292
|
+
contextArtifacts?: ContextArtifactConfig[];
|
|
293
|
+
telemetry?: TelemetryConfig;
|
|
294
|
+
portfolio?: PortfolioConfig;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The set of `UntangleConfigV1` top-level keys, used to detect unknown fields (spec.md §4.8). */
|
|
298
|
+
export const UNTANGLE_CONFIG_V1_KEYS = [
|
|
299
|
+
"schemaVersion",
|
|
300
|
+
"workspaceId",
|
|
301
|
+
"projectId",
|
|
302
|
+
"repositories",
|
|
303
|
+
"storage",
|
|
304
|
+
"qdrant",
|
|
305
|
+
"embeddings",
|
|
306
|
+
"index",
|
|
307
|
+
"search",
|
|
308
|
+
"graph",
|
|
309
|
+
"complexity",
|
|
310
|
+
"changes",
|
|
311
|
+
"qualityGate",
|
|
312
|
+
"contextArtifacts",
|
|
313
|
+
"telemetry",
|
|
314
|
+
"portfolio",
|
|
315
|
+
] as const satisfies readonly (keyof UntangleConfigV1)[];
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Recursively makes every field of `T` optional, including inside array
|
|
319
|
+
* elements. Used for {@link RawConfigLayer}: a single JSON file, an env-var
|
|
320
|
+
* layer, or an `explicit` argument object commonly sets only a handful of
|
|
321
|
+
* leaves inside a nested config section (e.g. just `qdrant.collectionPrefix`)
|
|
322
|
+
* — a shallow `Partial<T>` would still require every OTHER field of
|
|
323
|
+
* `QdrantConfig` once `qdrant` is present at all, which is not how any of
|
|
324
|
+
* the six layers in spec.md §4.1 are actually written. Runtime validation
|
|
325
|
+
* (`validate.ts`) is what enforces which fields are truly required once all
|
|
326
|
+
* layers are merged with the product defaults.
|
|
327
|
+
*/
|
|
328
|
+
export type DeepPartial<T> = T extends (infer U)[]
|
|
329
|
+
? DeepPartial<U>[]
|
|
330
|
+
: T extends object
|
|
331
|
+
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
332
|
+
: T;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* A raw configuration layer as read from JSON, environment mapping or passed
|
|
336
|
+
* explicitly: every field optional (deeply — see {@link DeepPartial}),
|
|
337
|
+
* `schemaVersion` not required (defaults are applied and validated later).
|
|
338
|
+
* `$schema` is accepted and ignored (spec.md §11's example config carries it).
|
|
339
|
+
*/
|
|
340
|
+
export type RawConfigLayer = {
|
|
341
|
+
$schema?: string;
|
|
342
|
+
schemaVersion?: number;
|
|
343
|
+
} & DeepPartial<Omit<UntangleConfigV1, "schemaVersion">>;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The fully merged, defaulted and validated configuration
|
|
347
|
+
* ({@link "./load.js"}'s `loadConfig` result). All fields the product
|
|
348
|
+
* defaults (spec.md §4.5, this package's `defaults.ts`) supply are guaranteed
|
|
349
|
+
* present; `repositories` and `contextArtifacts` stay optional because they
|
|
350
|
+
* have no universal default (spec.md §4.3: the current project path MAY
|
|
351
|
+
* serve as an implicit single repository instead).
|
|
352
|
+
*/
|
|
353
|
+
export interface EffectiveUntangleConfigV1 extends UntangleConfigV1 {
|
|
354
|
+
schemaVersion: 1;
|
|
355
|
+
storage: StorageConfig;
|
|
356
|
+
qdrant: QdrantConfig;
|
|
357
|
+
embeddings: EmbeddingConfig;
|
|
358
|
+
index: IndexConfig;
|
|
359
|
+
search: SearchConfig;
|
|
360
|
+
graph: GraphConfig;
|
|
361
|
+
complexity: ComplexityConfig;
|
|
362
|
+
changes: ChangeConfig;
|
|
363
|
+
qualityGate: QualityGateConfig;
|
|
364
|
+
telemetry: TelemetryConfig;
|
|
365
|
+
portfolio: PortfolioConfig;
|
|
366
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User configuration location (spec.md §4.1 layer 5: "Nutzerkonfiguration im
|
|
3
|
+
* OS-Konfigurationsverzeichnis"). The exact directory and file name are an
|
|
4
|
+
* Untangle decision — spec.md does not name them.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
export const USER_CONFIG_FILE_NAME = "config.json";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves the per-OS Untangle config directory:
|
|
14
|
+
* - Linux: `$XDG_CONFIG_HOME/untangle` or `~/.config/untangle`;
|
|
15
|
+
* - macOS: `~/Library/Application Support/untangle`;
|
|
16
|
+
* - Windows: `%APPDATA%\untangle` or `~/AppData/Roaming/untangle`.
|
|
17
|
+
*
|
|
18
|
+
* `env` and `platform` are injectable for deterministic tests.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveUserConfigDir(
|
|
21
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
22
|
+
platform: NodeJS.Platform = process.platform,
|
|
23
|
+
): string {
|
|
24
|
+
if (platform === "win32") {
|
|
25
|
+
const appData = env["APPDATA"];
|
|
26
|
+
return join(
|
|
27
|
+
appData && appData.length > 0 ? appData : join(homedir(), "AppData", "Roaming"),
|
|
28
|
+
"untangle",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (platform === "darwin") {
|
|
32
|
+
return join(homedir(), "Library", "Application Support", "untangle");
|
|
33
|
+
}
|
|
34
|
+
const xdgConfigHome = env["XDG_CONFIG_HOME"];
|
|
35
|
+
return join(
|
|
36
|
+
xdgConfigHome && xdgConfigHome.length > 0 ? xdgConfigHome : join(homedir(), ".config"),
|
|
37
|
+
"untangle",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Full path to the user config file, or `undefined` when it does not exist. */
|
|
42
|
+
export function resolveUserConfigPath(
|
|
43
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
44
|
+
platform: NodeJS.Platform = process.platform,
|
|
45
|
+
): string | undefined {
|
|
46
|
+
const candidate = join(resolveUserConfigDir(env, platform), USER_CONFIG_FILE_NAME);
|
|
47
|
+
return existsSync(candidate) ? candidate : undefined;
|
|
48
|
+
}
|