lula2 0.0.5 → 0.0.6

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.
Files changed (108) hide show
  1. package/README.md +291 -8
  2. package/dist/_app/env.js +1 -0
  3. package/dist/_app/immutable/assets/0.DtiRW3lO.css +1 -0
  4. package/dist/_app/immutable/assets/DynamicControlEditor.BkVTzFZ-.css +1 -0
  5. package/dist/_app/immutable/chunks/7x_q-1ab.js +1 -0
  6. package/dist/_app/immutable/chunks/B19gt6-g.js +2 -0
  7. package/dist/_app/immutable/chunks/BR-0Dorr.js +1 -0
  8. package/dist/_app/immutable/chunks/B_3ksxz5.js +2 -0
  9. package/dist/_app/immutable/chunks/Bg_R1qWi.js +3 -0
  10. package/dist/_app/immutable/chunks/D3aNP_lg.js +1 -0
  11. package/dist/_app/immutable/chunks/D4Q_ObIy.js +1 -0
  12. package/dist/_app/immutable/chunks/DsnmJJEf.js +1 -0
  13. package/dist/_app/immutable/chunks/XY2j_owG.js +66 -0
  14. package/dist/_app/immutable/chunks/rzN25oDf.js +1 -0
  15. package/dist/_app/immutable/entry/app.r0uOd9qg.js +2 -0
  16. package/dist/_app/immutable/entry/start.DvoqR0rc.js +1 -0
  17. package/dist/_app/immutable/nodes/0.Ct6FAss_.js +1 -0
  18. package/dist/_app/immutable/nodes/1.DLoKuy8Q.js +1 -0
  19. package/dist/_app/immutable/nodes/2.IRkwSmiB.js +1 -0
  20. package/dist/_app/immutable/nodes/3.BrTg-ZHv.js +1 -0
  21. package/dist/_app/immutable/nodes/4.Blq-4WQS.js +9 -0
  22. package/dist/_app/version.json +1 -0
  23. package/dist/cli/commands/crawl.js +128 -0
  24. package/dist/cli/commands/ui.js +2769 -0
  25. package/dist/cli/commands/version.js +30 -0
  26. package/dist/cli/server/index.js +2713 -0
  27. package/dist/cli/server/server.js +2702 -0
  28. package/dist/cli/server/serverState.js +1199 -0
  29. package/dist/cli/server/spreadsheetRoutes.js +788 -0
  30. package/dist/cli/server/types.js +0 -0
  31. package/dist/cli/server/websocketServer.js +2625 -0
  32. package/dist/cli/utils/debug.js +24 -0
  33. package/dist/favicon.svg +1 -0
  34. package/dist/index.html +38 -0
  35. package/dist/index.js +2924 -37
  36. package/dist/lula.png +0 -0
  37. package/dist/lula2 +2 -0
  38. package/package.json +120 -72
  39. package/src/app.css +192 -0
  40. package/src/app.d.ts +13 -0
  41. package/src/app.html +13 -0
  42. package/src/lib/actions/fadeWhenScrollable.ts +39 -0
  43. package/src/lib/actions/modal.ts +230 -0
  44. package/src/lib/actions/tooltip.ts +82 -0
  45. package/src/lib/components/control-sets/ControlSetInfo.svelte +20 -0
  46. package/src/lib/components/control-sets/ControlSetSelector.svelte +46 -0
  47. package/src/lib/components/control-sets/index.ts +5 -0
  48. package/src/lib/components/controls/ControlDetailsPanel.svelte +235 -0
  49. package/src/lib/components/controls/ControlsList.svelte +608 -0
  50. package/src/lib/components/controls/DynamicControlEditor.svelte +298 -0
  51. package/src/lib/components/controls/MappingCard.svelte +105 -0
  52. package/src/lib/components/controls/MappingForm.svelte +188 -0
  53. package/src/lib/components/controls/index.ts +9 -0
  54. package/src/lib/components/controls/renderers/EditableFieldRenderer.svelte +103 -0
  55. package/src/lib/components/controls/renderers/FieldRenderer.svelte +49 -0
  56. package/src/lib/components/controls/renderers/index.ts +5 -0
  57. package/src/lib/components/controls/tabs/CustomFieldsTab.svelte +130 -0
  58. package/src/lib/components/controls/tabs/ImplementationTab.svelte +127 -0
  59. package/src/lib/components/controls/tabs/MappingsTab.svelte +182 -0
  60. package/src/lib/components/controls/tabs/OverviewTab.svelte +151 -0
  61. package/src/lib/components/controls/tabs/TimelineTab.svelte +41 -0
  62. package/src/lib/components/controls/tabs/index.ts +8 -0
  63. package/src/lib/components/controls/utils/ProcessedTextRenderer.svelte +63 -0
  64. package/src/lib/components/controls/utils/textProcessor.ts +164 -0
  65. package/src/lib/components/forms/DynamicControlForm.svelte +340 -0
  66. package/src/lib/components/forms/DynamicField.svelte +494 -0
  67. package/src/lib/components/forms/FormField.svelte +107 -0
  68. package/src/lib/components/forms/index.ts +6 -0
  69. package/src/lib/components/setup/ExistingControlSets.svelte +284 -0
  70. package/src/lib/components/setup/SpreadsheetImport.svelte +968 -0
  71. package/src/lib/components/setup/index.ts +5 -0
  72. package/src/lib/components/ui/Dropdown.svelte +107 -0
  73. package/src/lib/components/ui/EmptyState.svelte +80 -0
  74. package/src/lib/components/ui/FeatureToggle.svelte +50 -0
  75. package/src/lib/components/ui/SearchBar.svelte +73 -0
  76. package/src/lib/components/ui/StatusBadge.svelte +79 -0
  77. package/src/lib/components/ui/TabNavigation.svelte +48 -0
  78. package/src/lib/components/ui/Tooltip.svelte +120 -0
  79. package/src/lib/components/ui/index.ts +10 -0
  80. package/src/lib/components/version-control/DiffViewer.svelte +292 -0
  81. package/src/lib/components/version-control/TimelineItem.svelte +107 -0
  82. package/src/lib/components/version-control/YamlDiffViewer.svelte +428 -0
  83. package/src/lib/components/version-control/index.ts +6 -0
  84. package/src/lib/form-types.ts +57 -0
  85. package/src/lib/formatUtils.ts +17 -0
  86. package/src/lib/index.ts +5 -0
  87. package/src/lib/types.ts +180 -0
  88. package/src/lib/websocket.ts +359 -0
  89. package/src/routes/+layout.svelte +236 -0
  90. package/src/routes/+page.svelte +38 -0
  91. package/src/routes/control/[id]/+page.svelte +112 -0
  92. package/src/routes/setup/+page.svelte +241 -0
  93. package/src/stores/compliance.ts +95 -0
  94. package/src/styles/highlightjs.css +20 -0
  95. package/src/styles/modal.css +58 -0
  96. package/src/styles/tables.css +111 -0
  97. package/src/styles/tooltip.css +65 -0
  98. package/dist/controls/index.d.ts +0 -18
  99. package/dist/controls/index.d.ts.map +0 -1
  100. package/dist/controls/index.js +0 -18
  101. package/dist/crawl.d.ts +0 -62
  102. package/dist/crawl.d.ts.map +0 -1
  103. package/dist/crawl.js +0 -172
  104. package/dist/index.d.ts +0 -8
  105. package/dist/index.d.ts.map +0 -1
  106. package/src/controls/index.ts +0 -19
  107. package/src/crawl.ts +0 -227
  108. package/src/index.ts +0 -46
@@ -0,0 +1,1199 @@
1
+ // cli/server/serverState.ts
2
+ import { join as join3 } from "path";
3
+
4
+ // cli/utils/debug.ts
5
+ var debugEnabled = false;
6
+ function debug(...args) {
7
+ if (debugEnabled) {
8
+ console.log("[DEBUG]", ...args);
9
+ }
10
+ }
11
+
12
+ // cli/server/infrastructure/fileStore.ts
13
+ import {
14
+ existsSync as existsSync2,
15
+ promises as fs,
16
+ mkdirSync,
17
+ readdirSync,
18
+ readFileSync as readFileSync2,
19
+ statSync,
20
+ unlinkSync,
21
+ writeFileSync
22
+ } from "fs";
23
+ import * as yaml2 from "js-yaml";
24
+ import { join as join2 } from "path";
25
+
26
+ // cli/server/infrastructure/controlHelpers.ts
27
+ import { existsSync, readFileSync } from "fs";
28
+ import { join } from "path";
29
+ import * as yaml from "js-yaml";
30
+ var controlSetMetadata = null;
31
+ var metadataPath = null;
32
+ function loadControlSetMetadata(baseDir) {
33
+ const path = join(baseDir, "lula.yaml");
34
+ if (metadataPath !== path || !controlSetMetadata) {
35
+ if (existsSync(path)) {
36
+ try {
37
+ const content = readFileSync(path, "utf8");
38
+ controlSetMetadata = yaml.load(content);
39
+ metadataPath = path;
40
+ } catch (error) {
41
+ console.error(`Failed to parse lula.yaml at ${path}:`, error);
42
+ controlSetMetadata = {};
43
+ metadataPath = path;
44
+ }
45
+ } else {
46
+ controlSetMetadata = {};
47
+ metadataPath = path;
48
+ }
49
+ }
50
+ return controlSetMetadata;
51
+ }
52
+ function getControlIdField(baseDir) {
53
+ if (baseDir) {
54
+ loadControlSetMetadata(baseDir);
55
+ }
56
+ return controlSetMetadata?.control_id_field || "id";
57
+ }
58
+ function getControlId(control, baseDir) {
59
+ if (!control || typeof control !== "object") {
60
+ throw new Error("Invalid control object provided");
61
+ }
62
+ const idField = getControlIdField(baseDir);
63
+ const configuredId = control[idField];
64
+ if (configuredId && typeof configuredId === "string") {
65
+ return configuredId;
66
+ }
67
+ if (idField !== "id" && control.id) {
68
+ return control.id;
69
+ }
70
+ const possibleIdFields = [
71
+ "ap-acronym",
72
+ "control-id",
73
+ "control_id",
74
+ "controlId"
75
+ ];
76
+ for (const field of possibleIdFields) {
77
+ const value = control[field];
78
+ if (value && typeof value === "string") {
79
+ return value;
80
+ }
81
+ }
82
+ const availableFields = Object.keys(control).filter(
83
+ (key) => control[key] !== void 0
84
+ );
85
+ throw new Error(
86
+ `No control ID found in control object. Available fields: ${availableFields.join(", ")}`
87
+ );
88
+ }
89
+
90
+ // cli/server/infrastructure/fileStore.ts
91
+ var FileStore = class {
92
+ baseDir;
93
+ controlsDir;
94
+ mappingsDir;
95
+ // Simple cache - just control ID to filename mapping
96
+ controlMetadataCache = /* @__PURE__ */ new Map();
97
+ constructor(options) {
98
+ this.baseDir = options.baseDir;
99
+ this.controlsDir = join2(this.baseDir, "controls");
100
+ this.mappingsDir = join2(this.baseDir, "mappings");
101
+ if (existsSync2(this.controlsDir)) {
102
+ this.refreshControlsCache();
103
+ }
104
+ }
105
+ /**
106
+ * Get simple filename from control ID
107
+ */
108
+ getControlFilename(controlId) {
109
+ const sanitized = controlId.replace(/[^\w\-]/g, "_");
110
+ return `${sanitized}.yaml`;
111
+ }
112
+ /**
113
+ * Get family name from control ID
114
+ */
115
+ getControlFamily(controlId) {
116
+ return controlId.split("-")[0];
117
+ }
118
+ /**
119
+ * Ensure required directories exist
120
+ */
121
+ ensureDirectories() {
122
+ if (!this.baseDir || this.baseDir === "." || this.baseDir === process.cwd()) {
123
+ return;
124
+ }
125
+ const lulaConfigPath = join2(this.baseDir, "lula.yaml");
126
+ if (!existsSync2(lulaConfigPath)) {
127
+ return;
128
+ }
129
+ if (!existsSync2(this.controlsDir)) {
130
+ mkdirSync(this.controlsDir, { recursive: true });
131
+ }
132
+ if (!existsSync2(this.mappingsDir)) {
133
+ mkdirSync(this.mappingsDir, { recursive: true });
134
+ }
135
+ }
136
+ /**
137
+ * Get control metadata by ID
138
+ */
139
+ getControlMetadata(controlId) {
140
+ return this.controlMetadataCache.get(controlId);
141
+ }
142
+ /**
143
+ * Load a control by ID
144
+ */
145
+ async loadControl(controlId) {
146
+ const sanitizedId = controlId.replace(/[^\w\-]/g, "_");
147
+ const possibleFlatPaths = [
148
+ join2(this.controlsDir, `${controlId}.yaml`),
149
+ join2(this.controlsDir, `${sanitizedId}.yaml`)
150
+ ];
151
+ for (const flatFilePath of possibleFlatPaths) {
152
+ if (existsSync2(flatFilePath)) {
153
+ try {
154
+ const content = readFileSync2(flatFilePath, "utf8");
155
+ const parsed = yaml2.load(content);
156
+ if (!parsed.id) {
157
+ parsed.id = controlId.replace(/_(\d)/g, ".$1");
158
+ }
159
+ return parsed;
160
+ } catch (error) {
161
+ console.error(`Failed to load control ${controlId} from flat structure:`, error);
162
+ throw new Error(
163
+ `Failed to load control ${controlId}: ${error instanceof Error ? error.message : String(error)}`
164
+ );
165
+ }
166
+ }
167
+ }
168
+ const family = this.getControlFamily(controlId);
169
+ const familyDir = join2(this.controlsDir, family);
170
+ const possibleFamilyPaths = [
171
+ join2(familyDir, `${controlId}.yaml`),
172
+ join2(familyDir, `${sanitizedId}.yaml`)
173
+ ];
174
+ for (const filePath of possibleFamilyPaths) {
175
+ if (existsSync2(filePath)) {
176
+ try {
177
+ const content = readFileSync2(filePath, "utf8");
178
+ const parsed = yaml2.load(content);
179
+ if (!parsed.id) {
180
+ parsed.id = controlId.replace(/_(\d)/g, ".$1");
181
+ }
182
+ return parsed;
183
+ } catch (error) {
184
+ console.error(`Failed to load control ${controlId}:`, error);
185
+ throw new Error(
186
+ `Failed to load control ${controlId}: ${error instanceof Error ? error.message : String(error)}`
187
+ );
188
+ }
189
+ }
190
+ }
191
+ return null;
192
+ }
193
+ /**
194
+ * Save a control
195
+ */
196
+ async saveControl(control) {
197
+ this.ensureDirectories();
198
+ const controlId = getControlId(control, this.baseDir);
199
+ const family = this.getControlFamily(controlId);
200
+ const filename = this.getControlFilename(controlId);
201
+ const familyDir = join2(this.controlsDir, family);
202
+ const filePath = join2(familyDir, filename);
203
+ if (!existsSync2(familyDir)) {
204
+ mkdirSync(familyDir, { recursive: true });
205
+ }
206
+ try {
207
+ let yamlContent;
208
+ if (existsSync2(filePath)) {
209
+ const existingContent = readFileSync2(filePath, "utf8");
210
+ const existingControl = yaml2.load(existingContent);
211
+ const fieldsToUpdate = {};
212
+ for (const key in control) {
213
+ if (key === "timeline" || key === "unifiedHistory" || key === "_metadata" || key === "id") {
214
+ continue;
215
+ }
216
+ if (JSON.stringify(control[key]) !== JSON.stringify(existingControl[key])) {
217
+ fieldsToUpdate[key] = control[key];
218
+ }
219
+ }
220
+ if (Object.keys(fieldsToUpdate).length > 0) {
221
+ const updatedControl = { ...existingControl, ...fieldsToUpdate };
222
+ yamlContent = yaml2.dump(updatedControl, {
223
+ indent: 2,
224
+ lineWidth: 80,
225
+ noRefs: true,
226
+ sortKeys: false
227
+ });
228
+ } else {
229
+ yamlContent = existingContent;
230
+ }
231
+ } else {
232
+ const controlToSave = { ...control };
233
+ delete controlToSave.timeline;
234
+ delete controlToSave.unifiedHistory;
235
+ delete controlToSave._metadata;
236
+ delete controlToSave.id;
237
+ yamlContent = yaml2.dump(controlToSave, {
238
+ indent: 2,
239
+ lineWidth: 80,
240
+ noRefs: true,
241
+ sortKeys: false
242
+ });
243
+ }
244
+ writeFileSync(filePath, yamlContent, "utf8");
245
+ this.controlMetadataCache.set(controlId, {
246
+ controlId,
247
+ filename,
248
+ family
249
+ });
250
+ } catch (error) {
251
+ throw new Error(
252
+ `Failed to save control ${controlId}: ${error instanceof Error ? error.message : String(error)}`
253
+ );
254
+ }
255
+ }
256
+ /**
257
+ * Delete a control
258
+ */
259
+ async deleteControl(controlId) {
260
+ const family = this.getControlFamily(controlId);
261
+ const filename = this.getControlFilename(controlId);
262
+ const familyDir = join2(this.controlsDir, family);
263
+ const filePath = join2(familyDir, filename);
264
+ if (existsSync2(filePath)) {
265
+ unlinkSync(filePath);
266
+ this.controlMetadataCache.delete(controlId);
267
+ }
268
+ }
269
+ /**
270
+ * Load all controls
271
+ */
272
+ async loadAllControls() {
273
+ if (!existsSync2(this.controlsDir)) {
274
+ return [];
275
+ }
276
+ const entries = readdirSync(this.controlsDir);
277
+ const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
278
+ if (yamlFiles.length > 0) {
279
+ const promises = yamlFiles.map(async (file) => {
280
+ try {
281
+ const filePath = join2(this.controlsDir, file);
282
+ const content = await fs.readFile(filePath, "utf8");
283
+ const parsed = yaml2.load(content);
284
+ if (!parsed.id) {
285
+ parsed.id = getControlId(parsed, this.baseDir);
286
+ }
287
+ return parsed;
288
+ } catch (error) {
289
+ console.error(`Failed to load control from file ${file}:`, error);
290
+ return null;
291
+ }
292
+ });
293
+ const results2 = await Promise.all(promises);
294
+ return results2.filter((c) => c !== null);
295
+ }
296
+ const families = entries.filter((name) => {
297
+ const familyPath = join2(this.controlsDir, name);
298
+ return statSync(familyPath).isDirectory();
299
+ });
300
+ const allPromises = [];
301
+ for (const family of families) {
302
+ const familyPath = join2(this.controlsDir, family);
303
+ const files = readdirSync(familyPath).filter((file) => file.endsWith(".yaml"));
304
+ const familyPromises = files.map(async (file) => {
305
+ try {
306
+ const controlId = file.replace(".yaml", "");
307
+ const control = await this.loadControl(controlId);
308
+ return control;
309
+ } catch (error) {
310
+ console.error(`Failed to load control from file ${file}:`, error);
311
+ return null;
312
+ }
313
+ });
314
+ allPromises.push(...familyPromises);
315
+ }
316
+ const results = await Promise.all(allPromises);
317
+ return results.filter((c) => c !== null);
318
+ }
319
+ /**
320
+ * Load mappings from mappings directory
321
+ */
322
+ async loadMappings() {
323
+ const mappings = [];
324
+ if (!existsSync2(this.mappingsDir)) {
325
+ return mappings;
326
+ }
327
+ const families = readdirSync(this.mappingsDir).filter((name) => {
328
+ const familyPath = join2(this.mappingsDir, name);
329
+ return statSync(familyPath).isDirectory();
330
+ });
331
+ for (const family of families) {
332
+ const familyPath = join2(this.mappingsDir, family);
333
+ const files = readdirSync(familyPath).filter((file) => file.endsWith("-mappings.yaml"));
334
+ for (const file of files) {
335
+ const mappingFile = join2(familyPath, file);
336
+ try {
337
+ const content = readFileSync2(mappingFile, "utf8");
338
+ const parsed = yaml2.load(content);
339
+ if (Array.isArray(parsed)) {
340
+ mappings.push(...parsed);
341
+ }
342
+ } catch (error) {
343
+ console.error(`Failed to load mappings from ${family}/${file}:`, error);
344
+ }
345
+ }
346
+ }
347
+ return mappings;
348
+ }
349
+ /**
350
+ * Save a single mapping
351
+ */
352
+ async saveMapping(mapping) {
353
+ this.ensureDirectories();
354
+ const controlId = mapping.control_id;
355
+ const family = this.getControlFamily(controlId);
356
+ const familyDir = join2(this.mappingsDir, family);
357
+ const mappingFile = join2(familyDir, `${controlId}-mappings.yaml`);
358
+ if (!existsSync2(familyDir)) {
359
+ mkdirSync(familyDir, { recursive: true });
360
+ }
361
+ let existingMappings = [];
362
+ if (existsSync2(mappingFile)) {
363
+ try {
364
+ const content = readFileSync2(mappingFile, "utf8");
365
+ existingMappings = yaml2.load(content) || [];
366
+ } catch (error) {
367
+ console.error(`Failed to parse existing mappings file: ${mappingFile}`, error);
368
+ existingMappings = [];
369
+ }
370
+ }
371
+ const existingIndex = existingMappings.findIndex((m) => m.uuid === mapping.uuid);
372
+ if (existingIndex >= 0) {
373
+ existingMappings[existingIndex] = mapping;
374
+ } else {
375
+ existingMappings.push(mapping);
376
+ }
377
+ try {
378
+ const yamlContent = yaml2.dump(existingMappings, {
379
+ indent: 2,
380
+ lineWidth: -1,
381
+ noRefs: true
382
+ });
383
+ writeFileSync(mappingFile, yamlContent, "utf8");
384
+ } catch (error) {
385
+ throw new Error(
386
+ `Failed to save mapping for control ${controlId}: ${error instanceof Error ? error.message : String(error)}`
387
+ );
388
+ }
389
+ }
390
+ /**
391
+ * Delete a single mapping
392
+ */
393
+ async deleteMapping(uuid) {
394
+ const mappingFiles = this.getAllMappingFiles();
395
+ for (const file of mappingFiles) {
396
+ try {
397
+ const content = readFileSync2(file, "utf8");
398
+ let mappings = yaml2.load(content) || [];
399
+ const originalLength = mappings.length;
400
+ mappings = mappings.filter((m) => m.uuid !== uuid);
401
+ if (mappings.length < originalLength) {
402
+ if (mappings.length === 0) {
403
+ unlinkSync(file);
404
+ } else {
405
+ const yamlContent = yaml2.dump(mappings, {
406
+ indent: 2,
407
+ lineWidth: -1,
408
+ noRefs: true
409
+ });
410
+ writeFileSync(file, yamlContent, "utf8");
411
+ }
412
+ return;
413
+ }
414
+ } catch (error) {
415
+ console.error(`Error processing mapping file ${file}:`, error);
416
+ }
417
+ }
418
+ }
419
+ /**
420
+ * Get all mapping files
421
+ */
422
+ getAllMappingFiles() {
423
+ const files = [];
424
+ if (!existsSync2(this.mappingsDir)) {
425
+ return files;
426
+ }
427
+ const flatFiles = readdirSync(this.mappingsDir).filter((file) => file.endsWith("-mappings.yaml")).map((file) => join2(this.mappingsDir, file));
428
+ files.push(...flatFiles);
429
+ const entries = readdirSync(this.mappingsDir, { withFileTypes: true });
430
+ for (const entry of entries) {
431
+ if (entry.isDirectory()) {
432
+ const familyDir = join2(this.mappingsDir, entry.name);
433
+ const familyFiles = readdirSync(familyDir).filter((file) => file.endsWith("-mappings.yaml")).map((file) => join2(familyDir, file));
434
+ files.push(...familyFiles);
435
+ }
436
+ }
437
+ return files;
438
+ }
439
+ /**
440
+ * Save mappings to per-control files
441
+ */
442
+ async saveMappings(mappings) {
443
+ this.ensureDirectories();
444
+ const mappingsByControl = /* @__PURE__ */ new Map();
445
+ for (const mapping of mappings) {
446
+ const controlId = mapping.control_id;
447
+ if (!mappingsByControl.has(controlId)) {
448
+ mappingsByControl.set(controlId, []);
449
+ }
450
+ mappingsByControl.get(controlId).push(mapping);
451
+ }
452
+ for (const [controlId, controlMappings] of mappingsByControl) {
453
+ const family = this.getControlFamily(controlId);
454
+ const familyDir = join2(this.mappingsDir, family);
455
+ const mappingFile = join2(familyDir, `${controlId}-mappings.yaml`);
456
+ if (!existsSync2(familyDir)) {
457
+ mkdirSync(familyDir, { recursive: true });
458
+ }
459
+ try {
460
+ const yamlContent = yaml2.dump(controlMappings, {
461
+ indent: 2,
462
+ lineWidth: -1,
463
+ noRefs: true
464
+ });
465
+ writeFileSync(mappingFile, yamlContent, "utf8");
466
+ } catch (error) {
467
+ throw new Error(
468
+ `Failed to save mappings for control ${controlId}: ${error instanceof Error ? error.message : String(error)}`
469
+ );
470
+ }
471
+ }
472
+ }
473
+ /**
474
+ * Refresh controls cache
475
+ */
476
+ refreshControlsCache() {
477
+ this.controlMetadataCache.clear();
478
+ if (!existsSync2(this.controlsDir)) {
479
+ return;
480
+ }
481
+ const entries = readdirSync(this.controlsDir);
482
+ const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
483
+ if (yamlFiles.length > 0) {
484
+ for (const filename of yamlFiles) {
485
+ const controlId = filename.replace(".yaml", "");
486
+ const family = this.getControlFamily(controlId);
487
+ this.controlMetadataCache.set(controlId, {
488
+ controlId,
489
+ filename,
490
+ family
491
+ });
492
+ }
493
+ return;
494
+ }
495
+ const families = entries.filter((name) => {
496
+ const familyPath = join2(this.controlsDir, name);
497
+ return statSync(familyPath).isDirectory();
498
+ });
499
+ for (const family of families) {
500
+ const familyPath = join2(this.controlsDir, family);
501
+ const files = readdirSync(familyPath).filter((file) => file.endsWith(".yaml"));
502
+ for (const filename of files) {
503
+ try {
504
+ const filePath = join2(familyPath, filename);
505
+ const content = readFileSync2(filePath, "utf8");
506
+ const parsed = yaml2.load(content);
507
+ const controlId = getControlId(parsed, this.baseDir);
508
+ this.controlMetadataCache.set(controlId, {
509
+ controlId,
510
+ filename,
511
+ family
512
+ });
513
+ } catch (error) {
514
+ console.error(`Failed to read control metadata from ${family}/${filename}:`, error);
515
+ const controlId = filename.replace(".yaml", "").replace(/_/g, "/");
516
+ this.controlMetadataCache.set(controlId, {
517
+ controlId,
518
+ filename,
519
+ family
520
+ });
521
+ }
522
+ }
523
+ }
524
+ }
525
+ /**
526
+ * Get file store statistics
527
+ */
528
+ getStats() {
529
+ const controlCount = this.controlMetadataCache.size;
530
+ let mappingCount = 0;
531
+ if (existsSync2(this.mappingsDir)) {
532
+ const families = readdirSync(this.mappingsDir).filter((name) => {
533
+ const familyPath = join2(this.mappingsDir, name);
534
+ return statSync(familyPath).isDirectory();
535
+ });
536
+ mappingCount = families.length;
537
+ }
538
+ const familyCount = new Set(
539
+ Array.from(this.controlMetadataCache.values()).map((meta) => meta.family)
540
+ ).size;
541
+ return {
542
+ controlCount,
543
+ mappingCount,
544
+ familyCount
545
+ };
546
+ }
547
+ /**
548
+ * Clear all caches
549
+ */
550
+ clearCache() {
551
+ this.controlMetadataCache.clear();
552
+ this.refreshControlsCache();
553
+ }
554
+ };
555
+
556
+ // cli/server/infrastructure/gitHistory.ts
557
+ import * as fs2 from "fs";
558
+ import * as git from "isomorphic-git";
559
+ import { relative } from "path";
560
+
561
+ // cli/server/infrastructure/yamlDiff.ts
562
+ import * as yaml3 from "js-yaml";
563
+ function createYamlDiff(oldYaml, newYaml, isArrayFile = false) {
564
+ try {
565
+ const emptyDefault = isArrayFile ? "[]" : "{}";
566
+ const oldData = yaml3.load(oldYaml || emptyDefault);
567
+ const newData = yaml3.load(newYaml || emptyDefault);
568
+ const changes = compareValues(oldData, newData, "");
569
+ return {
570
+ hasChanges: changes.length > 0,
571
+ changes,
572
+ summary: generateSummary(changes)
573
+ };
574
+ } catch (error) {
575
+ console.error("Error parsing YAML for diff:", error);
576
+ return {
577
+ hasChanges: false,
578
+ changes: [],
579
+ summary: "Error parsing YAML content"
580
+ };
581
+ }
582
+ }
583
+ function compareValues(oldValue, newValue, basePath) {
584
+ const changes = [];
585
+ if (oldValue === null || oldValue === void 0) {
586
+ if (newValue !== null && newValue !== void 0) {
587
+ changes.push({
588
+ type: "added",
589
+ path: basePath || "root",
590
+ newValue,
591
+ description: `Added ${basePath || "content"}`
592
+ });
593
+ }
594
+ return changes;
595
+ }
596
+ if (newValue === null || newValue === void 0) {
597
+ changes.push({
598
+ type: "removed",
599
+ path: basePath || "root",
600
+ oldValue,
601
+ description: `Removed ${basePath || "content"}`
602
+ });
603
+ return changes;
604
+ }
605
+ if (Array.isArray(oldValue) || Array.isArray(newValue)) {
606
+ return compareArrays(oldValue, newValue, basePath);
607
+ }
608
+ if (typeof oldValue === "object" && typeof newValue === "object") {
609
+ return compareObjects(oldValue, newValue, basePath);
610
+ }
611
+ if (oldValue !== newValue) {
612
+ changes.push({
613
+ type: "modified",
614
+ path: basePath || "root",
615
+ oldValue,
616
+ newValue,
617
+ description: `Changed ${basePath || "value"}`
618
+ });
619
+ }
620
+ return changes;
621
+ }
622
+ function compareObjects(oldObj, newObj, basePath) {
623
+ const changes = [];
624
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
625
+ for (const key of allKeys) {
626
+ const currentPath = basePath ? `${basePath}.${key}` : key;
627
+ const oldValue = oldObj[key];
628
+ const newValue = newObj[key];
629
+ if (!(key in oldObj)) {
630
+ changes.push({
631
+ type: "added",
632
+ path: currentPath,
633
+ newValue,
634
+ description: `Added ${key}`
635
+ });
636
+ } else if (!(key in newObj)) {
637
+ changes.push({
638
+ type: "removed",
639
+ path: currentPath,
640
+ oldValue,
641
+ description: `Removed ${key}`
642
+ });
643
+ } else if (!deepEqual(oldValue, newValue)) {
644
+ if (typeof oldValue === "object" && typeof newValue === "object") {
645
+ changes.push(...compareValues(oldValue, newValue, currentPath));
646
+ } else {
647
+ changes.push({
648
+ type: "modified",
649
+ path: currentPath,
650
+ oldValue,
651
+ newValue,
652
+ description: `Changed ${key}`
653
+ });
654
+ }
655
+ }
656
+ }
657
+ return changes;
658
+ }
659
+ function compareArrays(oldArr, newArr, basePath) {
660
+ const changes = [];
661
+ if (!Array.isArray(oldArr) && Array.isArray(newArr)) {
662
+ changes.push({
663
+ type: "modified",
664
+ path: basePath || "root",
665
+ oldValue: oldArr,
666
+ newValue: newArr,
667
+ description: `Changed ${basePath || "value"} from non-array to array`
668
+ });
669
+ return changes;
670
+ }
671
+ if (Array.isArray(oldArr) && !Array.isArray(newArr)) {
672
+ changes.push({
673
+ type: "modified",
674
+ path: basePath || "root",
675
+ oldValue: oldArr,
676
+ newValue: newArr,
677
+ description: `Changed ${basePath || "value"} from array to non-array`
678
+ });
679
+ return changes;
680
+ }
681
+ const oldArray = oldArr;
682
+ const newArray = newArr;
683
+ if (isMappingArray(oldArray) || isMappingArray(newArray)) {
684
+ return compareMappingArrays(oldArray, newArray, basePath);
685
+ }
686
+ if (oldArray.length !== newArray.length) {
687
+ changes.push({
688
+ type: "modified",
689
+ path: basePath || "root",
690
+ oldValue: oldArray,
691
+ newValue: newArray,
692
+ description: `Array ${basePath || "items"} changed from ${oldArray.length} to ${newArray.length} items`
693
+ });
694
+ } else {
695
+ for (let i = 0; i < oldArray.length; i++) {
696
+ const elementPath = `${basePath}[${i}]`;
697
+ if (!deepEqual(oldArray[i], newArray[i])) {
698
+ changes.push(...compareValues(oldArray[i], newArray[i], elementPath));
699
+ }
700
+ }
701
+ }
702
+ return changes;
703
+ }
704
+ function isMappingArray(arr) {
705
+ if (!Array.isArray(arr) || arr.length === 0) return false;
706
+ const firstItem = arr[0];
707
+ return typeof firstItem === "object" && firstItem !== null && "control_id" in firstItem && "uuid" in firstItem;
708
+ }
709
+ function compareMappingArrays(oldArr, newArr, basePath) {
710
+ const changes = [];
711
+ const oldMappings = /* @__PURE__ */ new Map();
712
+ const newMappings = /* @__PURE__ */ new Map();
713
+ for (const item of oldArr) {
714
+ if (typeof item === "object" && item !== null && "uuid" in item) {
715
+ oldMappings.set(item.uuid, item);
716
+ }
717
+ }
718
+ for (const item of newArr) {
719
+ if (typeof item === "object" && item !== null && "uuid" in item) {
720
+ newMappings.set(item.uuid, item);
721
+ }
722
+ }
723
+ for (const [uuid, mapping] of newMappings) {
724
+ if (!oldMappings.has(uuid)) {
725
+ changes.push({
726
+ type: "added",
727
+ path: `mapping`,
728
+ newValue: mapping,
729
+ description: `Added mapping`
730
+ });
731
+ }
732
+ }
733
+ for (const [uuid, mapping] of oldMappings) {
734
+ if (!newMappings.has(uuid)) {
735
+ changes.push({
736
+ type: "removed",
737
+ path: `mapping`,
738
+ oldValue: mapping,
739
+ description: `Removed mapping`
740
+ });
741
+ }
742
+ }
743
+ for (const [uuid, oldMapping] of oldMappings) {
744
+ if (newMappings.has(uuid)) {
745
+ const newMapping = newMappings.get(uuid);
746
+ if (!deepEqual(oldMapping, newMapping)) {
747
+ changes.push({
748
+ type: "modified",
749
+ path: `mapping`,
750
+ oldValue: oldMapping,
751
+ newValue: newMapping,
752
+ description: `Modified mapping`
753
+ });
754
+ }
755
+ }
756
+ }
757
+ if (changes.length === 0 && oldArr.length !== newArr.length) {
758
+ changes.push({
759
+ type: "modified",
760
+ path: basePath || "mappings",
761
+ oldValue: oldArr,
762
+ newValue: newArr,
763
+ description: `Mappings changed from ${oldArr.length} to ${newArr.length} items`
764
+ });
765
+ }
766
+ return changes;
767
+ }
768
+ function deepEqual(a, b) {
769
+ if (a === b) return true;
770
+ if (a === null || b === null) return false;
771
+ if (typeof a !== typeof b) return false;
772
+ if (Array.isArray(a) && Array.isArray(b)) {
773
+ if (a.length !== b.length) return false;
774
+ for (let i = 0; i < a.length; i++) {
775
+ if (!deepEqual(a[i], b[i])) return false;
776
+ }
777
+ return true;
778
+ }
779
+ if (typeof a === "object" && typeof b === "object") {
780
+ const aObj = a;
781
+ const bObj = b;
782
+ const aKeys = Object.keys(aObj);
783
+ const bKeys = Object.keys(bObj);
784
+ if (aKeys.length !== bKeys.length) return false;
785
+ for (const key of aKeys) {
786
+ if (!bKeys.includes(key)) return false;
787
+ if (!deepEqual(aObj[key], bObj[key])) return false;
788
+ }
789
+ return true;
790
+ }
791
+ return false;
792
+ }
793
+ function generateSummary(changes) {
794
+ if (changes.length === 0) {
795
+ return "No changes detected";
796
+ }
797
+ const added = changes.filter((c) => c.type === "added").length;
798
+ const removed = changes.filter((c) => c.type === "removed").length;
799
+ const modified = changes.filter((c) => c.type === "modified").length;
800
+ const parts = [];
801
+ if (added > 0) parts.push(`${added} added`);
802
+ if (removed > 0) parts.push(`${removed} removed`);
803
+ if (modified > 0) parts.push(`${modified} modified`);
804
+ return parts.join(", ");
805
+ }
806
+
807
+ // cli/server/infrastructure/gitHistory.ts
808
+ var GitHistoryUtil = class {
809
+ baseDir;
810
+ constructor(baseDir) {
811
+ this.baseDir = baseDir;
812
+ }
813
+ /**
814
+ * Check if the directory is a git repository
815
+ */
816
+ async isGitRepository() {
817
+ try {
818
+ const gitDir = await git.findRoot({ fs: fs2, filepath: process.cwd() });
819
+ return !!gitDir;
820
+ } catch {
821
+ return false;
822
+ }
823
+ }
824
+ /**
825
+ * Get git history for a specific file
826
+ */
827
+ async getFileHistory(filePath, limit = 50) {
828
+ const isGitRepo = await this.isGitRepository();
829
+ if (!isGitRepo) {
830
+ return {
831
+ filePath,
832
+ commits: [],
833
+ totalCommits: 0,
834
+ firstCommit: null,
835
+ lastCommit: null
836
+ };
837
+ }
838
+ try {
839
+ const gitRoot = await git.findRoot({ fs: fs2, filepath: process.cwd() });
840
+ const relativePath = relative(gitRoot, filePath);
841
+ const commits = await git.log({
842
+ fs: fs2,
843
+ dir: gitRoot,
844
+ filepath: relativePath,
845
+ depth: limit
846
+ });
847
+ if (!commits || commits.length === 0) {
848
+ return {
849
+ filePath,
850
+ commits: [],
851
+ totalCommits: 0,
852
+ firstCommit: null,
853
+ lastCommit: null
854
+ };
855
+ }
856
+ const gitCommits = await this.convertIsomorphicCommits(commits, relativePath, gitRoot);
857
+ return {
858
+ filePath,
859
+ commits: gitCommits,
860
+ totalCommits: gitCommits.length,
861
+ firstCommit: gitCommits[gitCommits.length - 1] || null,
862
+ lastCommit: gitCommits[0] || null
863
+ };
864
+ } catch (error) {
865
+ const err = error;
866
+ if (err?.code === "NotFoundError" || err?.message?.includes("Could not find file")) {
867
+ return {
868
+ filePath,
869
+ commits: [],
870
+ totalCommits: 0,
871
+ firstCommit: null,
872
+ lastCommit: null
873
+ };
874
+ }
875
+ console.error(`Unexpected error getting git history for ${filePath}:`, error);
876
+ return {
877
+ filePath,
878
+ commits: [],
879
+ totalCommits: 0,
880
+ firstCommit: null,
881
+ lastCommit: null
882
+ };
883
+ }
884
+ }
885
+ /**
886
+ * Get the total number of commits for a file
887
+ */
888
+ async getFileCommitCount(filePath) {
889
+ const isGitRepo = await this.isGitRepository();
890
+ if (!isGitRepo) {
891
+ return 0;
892
+ }
893
+ try {
894
+ const gitRoot = await git.findRoot({ fs: fs2, filepath: process.cwd() });
895
+ const relativePath = relative(gitRoot, filePath);
896
+ const commits = await git.log({
897
+ fs: fs2,
898
+ dir: gitRoot,
899
+ filepath: relativePath
900
+ });
901
+ return commits.length;
902
+ } catch {
903
+ return 0;
904
+ }
905
+ }
906
+ /**
907
+ * Get the latest commit info for a file
908
+ */
909
+ async getLatestCommit(filePath) {
910
+ const history = await this.getFileHistory(filePath, 1);
911
+ return history.lastCommit;
912
+ }
913
+ /**
914
+ * Get file content at a specific commit (public method)
915
+ */
916
+ async getFileContentAtCommit(filePath, commitHash) {
917
+ const isGitRepo = await this.isGitRepository();
918
+ if (!isGitRepo) {
919
+ return null;
920
+ }
921
+ try {
922
+ const gitRoot = await git.findRoot({ fs: fs2, filepath: process.cwd() });
923
+ const relativePath = relative(gitRoot, filePath);
924
+ return await this.getFileAtCommit(commitHash, relativePath, gitRoot);
925
+ } catch (error) {
926
+ console.error(`Error getting file content at commit ${commitHash}:`, error);
927
+ return null;
928
+ }
929
+ }
930
+ /**
931
+ * Convert isomorphic-git commits to our GitCommit format
932
+ */
933
+ async convertIsomorphicCommits(commits, relativePath, gitRoot) {
934
+ const gitCommits = [];
935
+ for (let i = 0; i < commits.length; i++) {
936
+ const commit = commits[i];
937
+ const includeDiff = i < 5;
938
+ let changes = { insertions: 0, deletions: 0, files: 1 };
939
+ let diff;
940
+ let yamlDiff;
941
+ if (includeDiff) {
942
+ try {
943
+ const diffResult = await this.getCommitDiff(commit.oid, relativePath, gitRoot);
944
+ changes = diffResult.changes;
945
+ diff = diffResult.diff;
946
+ yamlDiff = diffResult.yamlDiff;
947
+ } catch (error) {
948
+ }
949
+ }
950
+ gitCommits.push({
951
+ hash: commit.oid,
952
+ shortHash: commit.oid.substring(0, 7),
953
+ author: commit.commit.author.name,
954
+ authorEmail: commit.commit.author.email,
955
+ date: new Date(commit.commit.author.timestamp * 1e3).toISOString(),
956
+ message: commit.commit.message,
957
+ changes,
958
+ ...diff && { diff },
959
+ ...yamlDiff && { yamlDiff }
960
+ });
961
+ }
962
+ return gitCommits;
963
+ }
964
+ /**
965
+ * Get diff for a specific commit and file
966
+ */
967
+ async getCommitDiff(commitOid, relativePath, gitRoot) {
968
+ try {
969
+ const commit = await git.readCommit({ fs: fs2, dir: gitRoot, oid: commitOid });
970
+ const parentOid = commit.commit.parent.length > 0 ? commit.commit.parent[0] : null;
971
+ if (!parentOid) {
972
+ const currentContent2 = await this.getFileAtCommit(commitOid, relativePath, gitRoot);
973
+ if (currentContent2) {
974
+ const lines = currentContent2.split("\n");
975
+ const isMappingFile2 = relativePath.includes("-mappings.yaml");
976
+ const yamlDiff2 = createYamlDiff("", currentContent2, isMappingFile2);
977
+ return {
978
+ changes: { insertions: lines.length, deletions: 0, files: 1 },
979
+ diff: `--- /dev/null
980
+ +++ b/${relativePath}
981
+ @@ -0,0 +1,${lines.length} @@
982
+ ` + lines.map((line) => "+" + line).join("\n"),
983
+ yamlDiff: yamlDiff2
984
+ };
985
+ }
986
+ return { changes: { insertions: 0, deletions: 0, files: 1 } };
987
+ }
988
+ const currentContent = await this.getFileAtCommit(commitOid, relativePath, gitRoot);
989
+ const parentContent = await this.getFileAtCommit(parentOid, relativePath, gitRoot);
990
+ if (!currentContent && !parentContent) {
991
+ return { changes: { insertions: 0, deletions: 0, files: 1 } };
992
+ }
993
+ const currentLines = currentContent ? currentContent.split("\n") : [];
994
+ const parentLines = parentContent ? parentContent.split("\n") : [];
995
+ const diff = this.createSimpleDiff(parentLines, currentLines, relativePath);
996
+ const { insertions, deletions } = this.countChanges(parentLines, currentLines);
997
+ const isMappingFile = relativePath.includes("-mappings.yaml");
998
+ const yamlDiff = createYamlDiff(parentContent || "", currentContent || "", isMappingFile);
999
+ return {
1000
+ changes: { insertions, deletions, files: 1 },
1001
+ diff,
1002
+ yamlDiff
1003
+ };
1004
+ } catch (error) {
1005
+ return { changes: { insertions: 0, deletions: 0, files: 1 } };
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Get file content at a specific commit
1010
+ */
1011
+ async getFileAtCommit(commitOid, filepath, gitRoot) {
1012
+ try {
1013
+ const { blob } = await git.readBlob({
1014
+ fs: fs2,
1015
+ dir: gitRoot,
1016
+ oid: commitOid,
1017
+ filepath
1018
+ });
1019
+ return new TextDecoder().decode(blob);
1020
+ } catch (_error) {
1021
+ return null;
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Create a simple unified diff between two file versions
1026
+ */
1027
+ createSimpleDiff(oldLines, newLines, filepath) {
1028
+ const diffLines = [];
1029
+ diffLines.push(`--- a/${filepath}`);
1030
+ diffLines.push(`+++ b/${filepath}`);
1031
+ const oldCount = oldLines.length;
1032
+ const newCount = newLines.length;
1033
+ diffLines.push(`@@ -1,${oldCount} +1,${newCount} @@`);
1034
+ let i = 0, j = 0;
1035
+ while (i < oldLines.length || j < newLines.length) {
1036
+ if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {
1037
+ diffLines.push(` ${oldLines[i]}`);
1038
+ i++;
1039
+ j++;
1040
+ } else if (i < oldLines.length && (j >= newLines.length || oldLines[i] !== newLines[j])) {
1041
+ diffLines.push(`-${oldLines[i]}`);
1042
+ i++;
1043
+ } else if (j < newLines.length) {
1044
+ diffLines.push(`+${newLines[j]}`);
1045
+ j++;
1046
+ }
1047
+ }
1048
+ return diffLines.join("\n");
1049
+ }
1050
+ /**
1051
+ * Count insertions and deletions between two file versions
1052
+ */
1053
+ countChanges(oldLines, newLines) {
1054
+ let insertions = 0;
1055
+ let deletions = 0;
1056
+ const maxLines = Math.max(oldLines.length, newLines.length);
1057
+ for (let i = 0; i < maxLines; i++) {
1058
+ const oldLine = i < oldLines.length ? oldLines[i] : null;
1059
+ const newLine = i < newLines.length ? newLines[i] : null;
1060
+ if (oldLine === null) {
1061
+ insertions++;
1062
+ } else if (newLine === null) {
1063
+ deletions++;
1064
+ } else if (oldLine !== newLine) {
1065
+ insertions++;
1066
+ deletions++;
1067
+ }
1068
+ }
1069
+ return { insertions, deletions };
1070
+ }
1071
+ /**
1072
+ * Get git stats for the entire repository
1073
+ */
1074
+ async getRepositoryStats() {
1075
+ const isGitRepo = await this.isGitRepository();
1076
+ if (!isGitRepo) {
1077
+ return {
1078
+ totalCommits: 0,
1079
+ contributors: 0,
1080
+ lastCommitDate: null,
1081
+ firstCommitDate: null
1082
+ };
1083
+ }
1084
+ try {
1085
+ const gitRoot = await git.findRoot({ fs: fs2, filepath: process.cwd() });
1086
+ const commits = await git.log({ fs: fs2, dir: gitRoot });
1087
+ const contributorEmails = /* @__PURE__ */ new Set();
1088
+ commits.forEach((commit) => {
1089
+ contributorEmails.add(commit.commit.author.email);
1090
+ });
1091
+ const firstCommit = commits[commits.length - 1];
1092
+ const lastCommit = commits[0];
1093
+ return {
1094
+ totalCommits: commits.length,
1095
+ contributors: contributorEmails.size,
1096
+ lastCommitDate: lastCommit ? new Date(lastCommit.commit.author.timestamp * 1e3).toISOString() : null,
1097
+ firstCommitDate: firstCommit ? new Date(firstCommit.commit.author.timestamp * 1e3).toISOString() : null
1098
+ };
1099
+ } catch (error) {
1100
+ console.error("Error getting repository stats:", error);
1101
+ return {
1102
+ totalCommits: 0,
1103
+ contributors: 0,
1104
+ lastCommitDate: null,
1105
+ firstCommitDate: null
1106
+ };
1107
+ }
1108
+ }
1109
+ };
1110
+
1111
+ // cli/server/serverState.ts
1112
+ var serverState = void 0;
1113
+ function initializeServerState(controlSetDir, subdir = ".") {
1114
+ const fullPath = subdir === "." ? controlSetDir : join3(controlSetDir, subdir);
1115
+ serverState = {
1116
+ CONTROL_SET_DIR: controlSetDir,
1117
+ currentSubdir: subdir,
1118
+ fileStore: new FileStore({ baseDir: fullPath }),
1119
+ gitHistory: new GitHistoryUtil(fullPath),
1120
+ controlsCache: /* @__PURE__ */ new Map(),
1121
+ mappingsCache: /* @__PURE__ */ new Map(),
1122
+ controlsByFamily: /* @__PURE__ */ new Map(),
1123
+ mappingsByFamily: /* @__PURE__ */ new Map(),
1124
+ mappingsByControl: /* @__PURE__ */ new Map()
1125
+ };
1126
+ return serverState;
1127
+ }
1128
+ function getServerState() {
1129
+ if (!serverState) {
1130
+ throw new Error("Server state not initialized. Call initializeServerState() first.");
1131
+ }
1132
+ return serverState;
1133
+ }
1134
+ function getCurrentControlSetPath() {
1135
+ const state = getServerState();
1136
+ return state.currentSubdir === "." ? state.CONTROL_SET_DIR : join3(state.CONTROL_SET_DIR, state.currentSubdir);
1137
+ }
1138
+ function addControlToIndexes(control) {
1139
+ const state = getServerState();
1140
+ const family = control.family;
1141
+ if (!family) {
1142
+ return;
1143
+ }
1144
+ if (!state.controlsByFamily.has(family)) {
1145
+ state.controlsByFamily.set(family, /* @__PURE__ */ new Set());
1146
+ }
1147
+ state.controlsByFamily.get(family).add(control.id);
1148
+ }
1149
+ function addMappingToIndexes(mapping) {
1150
+ const state = getServerState();
1151
+ const family = mapping.control_id.split("-")[0];
1152
+ if (!state.mappingsByFamily.has(family)) {
1153
+ state.mappingsByFamily.set(family, /* @__PURE__ */ new Set());
1154
+ }
1155
+ state.mappingsByFamily.get(family).add(mapping.uuid);
1156
+ if (!state.mappingsByControl.has(mapping.control_id)) {
1157
+ state.mappingsByControl.set(mapping.control_id, /* @__PURE__ */ new Set());
1158
+ }
1159
+ state.mappingsByControl.get(mapping.control_id).add(mapping.uuid);
1160
+ }
1161
+ async function loadAllData() {
1162
+ const state = getServerState();
1163
+ debug("Loading data into memory...");
1164
+ try {
1165
+ const controls = await state.fileStore.loadAllControls();
1166
+ for (const control of controls) {
1167
+ state.controlsCache.set(control.id, control);
1168
+ addControlToIndexes(control);
1169
+ }
1170
+ debug(`Loaded ${controls.length} controls from individual files`);
1171
+ const mappings = await state.fileStore.loadMappings();
1172
+ for (const mapping of mappings) {
1173
+ state.mappingsCache.set(mapping.uuid, mapping);
1174
+ addMappingToIndexes(mapping);
1175
+ }
1176
+ debug(`Loaded ${mappings.length} mappings`);
1177
+ } catch (error) {
1178
+ console.error("Error loading data:", error);
1179
+ }
1180
+ }
1181
+ async function saveMappingsToFile() {
1182
+ const state = getServerState();
1183
+ try {
1184
+ const allMappings = Array.from(state.mappingsCache.values());
1185
+ await state.fileStore.saveMappings(allMappings);
1186
+ debug(`Saved ${allMappings.length} mappings`);
1187
+ } catch (error) {
1188
+ console.error("Error saving mappings:", error);
1189
+ }
1190
+ }
1191
+ export {
1192
+ addControlToIndexes,
1193
+ addMappingToIndexes,
1194
+ getCurrentControlSetPath,
1195
+ getServerState,
1196
+ initializeServerState,
1197
+ loadAllData,
1198
+ saveMappingsToFile
1199
+ };