11// Copyright (c) Microsoft. All rights reserved.
22using System . Text . Json ;
3+ using System . Text . Json . Serialization . Metadata ;
34using KernelMemory . Core . Config . Cache ;
45using KernelMemory . Core . Config . ContentIndex ;
56using KernelMemory . Core . Config . SearchIndex ;
@@ -19,28 +20,55 @@ public static class ConfigParser
1920 /// - Case insensitive property names
2021 /// - Comments allowed
2122 /// - Trailing commas allowed
23+ /// - Supports polymorphic deserialization
2224 /// </summary>
2325 private static readonly JsonSerializerOptions s_jsonOptions = new ( )
2426 {
2527 PropertyNameCaseInsensitive = true ,
2628 ReadCommentHandling = JsonCommentHandling . Skip ,
2729 AllowTrailingCommas = true ,
28- PropertyNamingPolicy = JsonNamingPolicy . CamelCase
30+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase ,
31+ TypeInfoResolver = new DefaultJsonTypeInfoResolver ( )
2932 } ;
3033
3134 /// <summary>
32- /// Loads configuration from a file, or returns default config if file doesn't exist
35+ /// JSON serializer options for writing config files
36+ /// - Indented formatting
37+ /// - Camel case property names
38+ /// - Omit null values
39+ /// </summary>
40+ private static readonly JsonSerializerOptions s_writeJsonOptions = new ( )
41+ {
42+ WriteIndented = true ,
43+ PropertyNamingPolicy = JsonNamingPolicy . CamelCase ,
44+ DefaultIgnoreCondition = System . Text . Json . Serialization . JsonIgnoreCondition . WhenWritingNull
45+ } ;
46+
47+ /// <summary>
48+ /// Loads configuration from a file, or creates default config if file doesn't exist.
49+ /// The config file is always ensured to exist on disk after loading.
3350 /// Performs tilde expansion on paths (~/ → home directory)
3451 /// </summary>
3552 /// <param name="filePath">Path to configuration file</param>
3653 /// <returns>Validated AppConfig instance</returns>
3754 /// <exception cref="ConfigException">Thrown when file exists but parsing or validation fails</exception>
3855 public static AppConfig LoadFromFile ( string filePath )
3956 {
40- // If file doesn't exist, return default configuration
57+ AppConfig config ;
58+
59+ // If file doesn't exist, create default configuration relative to config file location
4160 if ( ! File . Exists ( filePath ) )
4261 {
43- return AppConfig . CreateDefault ( ) ;
62+ var configDir = Path . GetDirectoryName ( filePath ) ;
63+ var baseDir = string . IsNullOrEmpty ( configDir ) ? "." : configDir ;
64+
65+ // Create default config relative to config file location
66+ config = AppConfig . CreateDefault ( baseDir ) ;
67+
68+ // Write the config file
69+ WriteConfigFile ( filePath , config ) ;
70+
71+ return config ;
4472 }
4573
4674 try
@@ -49,11 +77,14 @@ public static AppConfig LoadFromFile(string filePath)
4977 var json = File . ReadAllText ( filePath ) ;
5078
5179 // Parse and validate
52- var config = ParseFromString ( json ) ;
80+ config = ParseFromString ( json ) ;
5381
5482 // Expand tilde paths
5583 ExpandTildePaths ( config ) ;
5684
85+ // Always ensure the config file exists (recreate if deleted between load and save)
86+ WriteConfigFileIfMissing ( filePath , config ) ;
87+
5788 return config ;
5889 }
5990 catch ( ConfigException )
@@ -251,4 +282,38 @@ private static string ExpandTilde(string path, string homeDir)
251282
252283 return path ;
253284 }
285+ /// <summary>
286+ /// Writes the config file to disk if it doesn't exist.
287+ /// Used to ensure config file is always present after any operation.
288+ /// </summary>
289+ /// <param name="filePath">Path to configuration file</param>
290+ /// <param name="config">Configuration to write</param>
291+ private static void WriteConfigFileIfMissing ( string filePath , AppConfig config )
292+ {
293+ if ( File . Exists ( filePath ) )
294+ {
295+ return ;
296+ }
297+
298+ WriteConfigFile ( filePath , config ) ;
299+ }
300+
301+ /// <summary>
302+ /// Writes the config file to disk, creating directories if needed.
303+ /// </summary>
304+ /// <param name="filePath">Path to configuration file</param>
305+ /// <param name="config">Configuration to write</param>
306+ private static void WriteConfigFile ( string filePath , AppConfig config )
307+ {
308+ // Create the directory if needed
309+ var configDir = Path . GetDirectoryName ( filePath ) ;
310+ if ( ! string . IsNullOrEmpty ( configDir ) && ! Directory . Exists ( configDir ) )
311+ {
312+ Directory . CreateDirectory ( configDir ) ;
313+ }
314+
315+ // Write the config file
316+ var json = System . Text . Json . JsonSerializer . Serialize ( config , s_writeJsonOptions ) ;
317+ File . WriteAllText ( filePath , json ) ;
318+ }
254319}
0 commit comments