| 1 | using System.Text.Json; |
| 2 | using System.Text.Json.Serialization; |
| 3 | |
| 4 | namespace AgentNotes.Core; |
| 5 | |
| 6 | /// <summary>Значения по умолчанию для hot-context: бюджеты, списки секций, блоклист тяжёлых L1. |
| 7 | /// Списки L0 / compact suffix / core — из embedded JSON в сборке AgentNotes.Core (<see cref="BundledAgentNotesContent"/> + <c>Resources/hot-context-defaults.json</c>, как <c>BundledAppContent</c> в CascadeIDE). |
| 8 | /// Переопределение рабочей памяти через <c>memory-architecture-v1</c> JSON (см. <see cref="MemoryArchitectureManifestData"/>).</summary> |
| 9 | internal static class HotContextDefaults |
| 10 | { |
| 11 | /// <summary>Порог предупреждения <c>memory_health</c> (сумма символов hot-секций).</summary> |
| 12 | public const int HotContextBudgetWarningChars = 6000; |
| 13 | |
| 14 | /// <summary>Порог critical <c>memory_health</c>.</summary> |
| 15 | public const int HotContextBudgetCriticalChars = 12000; |
| 16 | |
| 17 | private static readonly Lazy<HotContextEmbeddedLists> Lists = new(LoadLists); |
| 18 | |
| 19 | public static string[] DefaultL0Ids => Lists.Value.DefaultL0Ids; |
| 20 | |
| 21 | public static string[] DefaultCompactOrderSuffix => Lists.Value.DefaultCompactOrderSuffix; |
| 22 | |
| 23 | public static string[] RequiredCoreSectionIds => Lists.Value.RequiredCoreSectionIds; |
| 24 | |
| 25 | /// <summary>L1 / тяжёлые секции: не включать в hot, даже если перечислены в L0 манифеста. |
| 26 | /// Дополнительные id — в JSON <c>hot_context_section_exclusions</c>.</summary> |
| 27 | public static bool IsBuiltInHotExclusion(string sectionId) |
| 28 | { |
| 29 | return sectionId.StartsWith("hpmor-", StringComparison.OrdinalIgnoreCase) |
| 30 | || sectionId.Equals("it-source-mini-index-v1", StringComparison.Ordinal) |
| 31 | || sectionId.Equals("knowledge-index-v1", StringComparison.Ordinal) |
| 32 | || sectionId.Equals("imc-ui-ux-vision-v1", StringComparison.Ordinal) |
| 33 | || sectionId.Equals("psychology-gender-studies-subdomain-v1", StringComparison.Ordinal) |
| 34 | || sectionId.Equals("world-human-system-v1", StringComparison.Ordinal) |
| 35 | || sectionId.Equals("world-human-system-playbook-v1", StringComparison.Ordinal); |
| 36 | } |
| 37 | |
| 38 | private static HotContextEmbeddedLists LoadLists() |
| 39 | { |
| 40 | if (!BundledAgentNotesContent.TryReadEmbeddedText("hot-context-defaults.json", out var text)) |
| 41 | return HardcodedFallbackLists.Instance; |
| 42 | |
| 43 | try |
| 44 | { |
| 45 | var dto = JsonSerializer.Deserialize<HotContextDefaultsDto>(text, JsonOptions); |
| 46 | if (dto is not null |
| 47 | && dto.DefaultL0Ids is { Length: > 0 } |
| 48 | && dto.DefaultCompactOrderSuffix is { Length: > 0 } |
| 49 | && dto.RequiredCoreSectionIds is { Length: > 0 }) |
| 50 | { |
| 51 | return new HotContextEmbeddedLists(dto.DefaultL0Ids, dto.DefaultCompactOrderSuffix, dto.RequiredCoreSectionIds); |
| 52 | } |
| 53 | } |
| 54 | catch |
| 55 | { |
| 56 | // fall through to hardcoded fallback |
| 57 | } |
| 58 | |
| 59 | return HardcodedFallbackLists.Instance; |
| 60 | } |
| 61 | |
| 62 | private static readonly JsonSerializerOptions JsonOptions = new() |
| 63 | { |
| 64 | PropertyNameCaseInsensitive = true, |
| 65 | ReadCommentHandling = JsonCommentHandling.Skip, |
| 66 | AllowTrailingCommas = true |
| 67 | }; |
| 68 | |
| 69 | private sealed class HotContextDefaultsDto |
| 70 | { |
| 71 | [JsonPropertyName("default_l0_ids")] |
| 72 | public string[]? DefaultL0Ids { get; set; } |
| 73 | |
| 74 | [JsonPropertyName("default_compact_order_suffix")] |
| 75 | public string[]? DefaultCompactOrderSuffix { get; set; } |
| 76 | |
| 77 | [JsonPropertyName("required_core_section_ids")] |
| 78 | public string[]? RequiredCoreSectionIds { get; set; } |
| 79 | } |
| 80 | |
| 81 | private sealed record HotContextEmbeddedLists( |
| 82 | string[] DefaultL0Ids, |
| 83 | string[] DefaultCompactOrderSuffix, |
| 84 | string[] RequiredCoreSectionIds); |
| 85 | |
| 86 | /// <summary>Последний резерв: поток embedded не открылся (сборка/имя ресурса) или JSON внутри сборки не парсится / пустые списки. Не дублирует полный объём списков из JSON в репо.</summary> |
| 87 | private static class HardcodedFallbackLists |
| 88 | { |
| 89 | internal static readonly HotContextEmbeddedLists Instance = new( |
| 90 | DefaultL0Ids: [ |
| 91 | "baseline-integrity-epistemic-v1", |
| 92 | "active-scope", |
| 93 | "current-task" |
| 94 | ], |
| 95 | DefaultCompactOrderSuffix: [ |
| 96 | "workspace-scope-map-v1", |
| 97 | "scope-door-to-singularity", |
| 98 | "memory-architecture-v1" |
| 99 | ], |
| 100 | RequiredCoreSectionIds: [ |
| 101 | "current-task" |
| 102 | ]); |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | /// <summary>Данные из JSON, на который указывает <c>l0_manifest:</c> в секции memory-architecture-v1.</summary> |
| 107 | internal sealed record MemoryArchitectureManifestData( |
| 108 | IReadOnlyList<string> L0, |
| 109 | IReadOnlyList<string>? CompactOrderSuffix, |
| 110 | int? HotBudgetWarningChars, |
| 111 | int? HotBudgetCriticalChars, |
| 112 | IReadOnlyList<string>? HotContextSectionExclusions); |
| 113 | |