chati-dev 4.3.1 → 4.4.1

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 (158) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +28 -4
  3. package/framework/agents/build/dev.md +5 -1
  4. package/framework/agents/discover/greenfield-wu.md +16 -1
  5. package/framework/agents/plan/tasks.md +31 -0
  6. package/framework/agents/plan/ux-brand-architect.md +21 -2
  7. package/framework/agents/plan/ux-component-engineer.md +10 -0
  8. package/framework/agents/quality/qa-implementation.md +1 -1
  9. package/framework/agents/quality/qa-planning.md +1 -1
  10. package/framework/agents/quality/qa-visual.md +24 -21
  11. package/framework/config.yaml +5 -4
  12. package/framework/constitution.md +60 -11
  13. package/framework/context/governance.md +12 -10
  14. package/framework/context/root.md +2 -2
  15. package/framework/data/entity-registry.yaml +12 -4
  16. package/framework/data/qa-rubrics.yaml +335 -0
  17. package/framework/domains/constitution.yaml +3 -3
  18. package/framework/executors/json-validate.js +80 -0
  19. package/framework/executors/npm-script.js +72 -0
  20. package/framework/executors/path-check.js +67 -0
  21. package/framework/executors/registry.yaml +36 -0
  22. package/framework/hooks/advance-trigger.js +47 -3
  23. package/framework/hooks/constitution-guard.js +9 -4
  24. package/framework/hooks/git-push-authority.js +113 -0
  25. package/framework/hooks/license-guard.js +59 -9
  26. package/framework/hooks/model-governance.js +40 -17
  27. package/framework/hooks/prism-engine.js +78 -11
  28. package/framework/hooks/session-digest.js +12 -3
  29. package/framework/hooks/settings.json +7 -3
  30. package/framework/hooks/style-guard.js +52 -3
  31. package/framework/hooks/team-quality-gate.js +43 -5
  32. package/framework/i18n/en.yaml +3 -3
  33. package/framework/i18n/es.yaml +3 -3
  34. package/framework/i18n/fr.yaml +3 -3
  35. package/framework/i18n/pt.yaml +3 -3
  36. package/framework/intelligence/confidence.yaml +85 -6
  37. package/framework/intelligence/context-engine.md +9 -5
  38. package/framework/intelligence/decision-engine.md +1 -1
  39. package/framework/manifest.json +1351 -0
  40. package/framework/manifest.sig +1 -0
  41. package/framework/orchestrator/chati-router.js +1 -1
  42. package/framework/orchestrator/chati-update.md +19 -3
  43. package/framework/orchestrator/chati.md +24 -3
  44. package/framework/quality-gates/planning-gate.md +2 -0
  45. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  46. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  47. package/framework/scaffold/saas-dashboard/README.md +58 -0
  48. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  49. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  50. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  51. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  52. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  54. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  55. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  56. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  57. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  58. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  59. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  61. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  62. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  67. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  68. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  75. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  76. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  77. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  78. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  79. package/framework/schemas/session.schema.json +5 -0
  80. package/framework/schemas/task.schema.json +9 -0
  81. package/framework/scripts/visual-qa.js +87 -1
  82. package/framework/tasks/orchestrator-health.md +4 -4
  83. package/framework/tasks/qa-impl-verdict.md +11 -5
  84. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  85. package/package.json +4 -4
  86. package/src/config/claude-settings-generator.js +9 -6
  87. package/src/config/context-file-generator.js +0 -6
  88. package/src/dashboard/renderer.js +0 -36
  89. package/src/executors/runner.js +204 -0
  90. package/src/installer/core.js +35 -4
  91. package/src/installer/manifest.js +30 -4
  92. package/src/installer/signing-public-key.pem +1 -1
  93. package/src/installer/templates.js +5 -6
  94. package/src/installer/validator.js +2 -1
  95. package/src/intelligence/registry-manager.js +3 -2
  96. package/src/license/client.js +9 -3
  97. package/src/license/commands.js +12 -2
  98. package/src/license/machine-id.js +42 -1
  99. package/src/memory/gotchas.js +58 -40
  100. package/src/memory/magic-docs.js +7 -2
  101. package/src/memory/session-digest.js +9 -4
  102. package/src/orchestrator/cli.js +195 -23
  103. package/src/orchestrator/deviation-handler.js +5 -3
  104. package/src/orchestrator/doctor.js +98 -16
  105. package/src/orchestrator/pipeline-manager.js +44 -22
  106. package/src/orchestrator/session-manager.js +56 -8
  107. package/src/telemetry/sender.js +7 -19
  108. package/src/terminal/run-parallel.js +14 -14
  109. package/src/terminal/run-team.js +3 -3
  110. package/src/upgrade/backup.js +22 -44
  111. package/src/upgrade/migrator.js +51 -12
  112. package/src/upgrade/tracked-files-detector.js +34 -16
  113. package/src/utils/constitution-meta.js +12 -0
  114. package/src/utils/feature-flags.js +26 -2
  115. package/src/utils/flatten-entities.js +4 -40
  116. package/src/utils/schema-validator.js +0 -14
  117. package/src/wizard/i18n.js +3 -3
  118. package/src/api/index.js +0 -120
  119. package/src/autonomy/autonomous-gate.js +0 -294
  120. package/src/autonomy/build-loop.js +0 -281
  121. package/src/autonomy/build-state.js +0 -286
  122. package/src/autonomy/cause-analyzer.js +0 -177
  123. package/src/autonomy/escalation.js +0 -214
  124. package/src/autonomy/index.js +0 -51
  125. package/src/autonomy/mode-manager.js +0 -225
  126. package/src/autonomy/mode-suggester.js +0 -283
  127. package/src/autonomy/progress-reporter.js +0 -275
  128. package/src/autonomy/safety-net.js +0 -370
  129. package/src/config/agent-customizer.js +0 -231
  130. package/src/decision/analyzer.js +0 -291
  131. package/src/decision/engine.js +0 -250
  132. package/src/decision/index.js +0 -38
  133. package/src/decision/registry-healer.js +0 -468
  134. package/src/decision/registry-updater.js +0 -339
  135. package/src/extensions/loader.js +0 -145
  136. package/src/extensions/registry.js +0 -134
  137. package/src/gates/circuit-breaker.js +0 -151
  138. package/src/gates/g1-planning-complete.js +0 -154
  139. package/src/gates/g2-qa-planning.js +0 -156
  140. package/src/gates/g3-implementation.js +0 -215
  141. package/src/gates/g4-qa-implementation.js +0 -240
  142. package/src/gates/g5-deploy-ready.js +0 -181
  143. package/src/gates/gate-base.js +0 -185
  144. package/src/gates/index.js +0 -46
  145. package/src/health/auto-fix.js +0 -216
  146. package/src/health/engine.js +0 -246
  147. package/src/merger/semantic-merger.js +0 -292
  148. package/src/preview/detector.js +0 -238
  149. package/src/preview/index.js +0 -20
  150. package/src/preview/launcher.js +0 -235
  151. package/src/preview/log-buffer.js +0 -103
  152. package/src/quality/metrics-collector.js +0 -281
  153. package/src/quality/test-runner.js +0 -366
  154. package/src/tasks/executor.js +0 -195
  155. package/src/tasks/index.js +0 -4
  156. package/src/tasks/loader.js +0 -210
  157. package/src/tasks/router.js +0 -182
  158. package/src/utils/event-bus.js +0 -126
@@ -1,468 +0,0 @@
1
- /**
2
- * Registry Healer - Self-healing for entity-registry.yaml
3
- * Detects and fixes 6 types of registry issues
4
- *
5
- * @module decision/registry-healer
6
- */
7
-
8
- import { readFileSync, writeFileSync, existsSync, statSync, copyFileSync, mkdirSync } from 'fs';
9
- import { join, isAbsolute } from 'path';
10
- import yaml from 'js-yaml';
11
- import { resolveFrameworkDir } from '../utils/framework-dir.js';
12
- import { detectNewEntities, detectRemovedEntities, generateEntityMeta } from './registry-updater.js';
13
-
14
- /**
15
- * Load and parse the entity registry YAML once.
16
- * @param {string} projectDir
17
- * @returns {{ registry: object|null, registryPath: string }}
18
- */
19
- function loadRegistry(projectDir) {
20
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
21
- if (!existsSync(registryPath)) {
22
- return { registry: null, registryPath };
23
- }
24
- const registry = yaml.load(readFileSync(registryPath, 'utf8'));
25
- return { registry, registryPath };
26
- }
27
-
28
- /**
29
- * Run all healing rules and return diagnosis.
30
- * @param {string} projectDir
31
- * @param {object} [options] - { autoFix: boolean }
32
- * @returns {{ healthy: boolean, issues: object[], fixes: object[] }}
33
- */
34
- export function healRegistry(projectDir, options = {}) {
35
- const { autoFix = false } = options;
36
-
37
- const issues = [];
38
- const fixes = [];
39
-
40
- // Load registry once for rules 3-6
41
- const { registry } = loadRegistry(projectDir);
42
-
43
- // Rule 1: Missing entities
44
- const missingIssues = detectMissingEntities(projectDir);
45
- issues.push(...missingIssues);
46
- if (autoFix) {
47
- missingIssues.forEach(issue => {
48
- fixes.push({
49
- rule: 'missing_entity',
50
- action: 'add',
51
- path: issue.path,
52
- entity: issue.entity
53
- });
54
- });
55
- }
56
-
57
- // Rule 2: Orphaned entries
58
- const orphanedIssues = detectOrphanedEntries(projectDir);
59
- issues.push(...orphanedIssues);
60
- if (autoFix) {
61
- orphanedIssues.forEach(issue => {
62
- fixes.push({
63
- rule: 'orphaned_entry',
64
- action: 'remove',
65
- path: issue.path
66
- });
67
- });
68
- }
69
-
70
- // Rule 3: Stale metadata
71
- const staleIssues = detectStaleMetadata(projectDir, registry);
72
- issues.push(...staleIssues);
73
- if (autoFix) {
74
- staleIssues.forEach(issue => {
75
- fixes.push({
76
- rule: 'stale_metadata',
77
- action: 'update',
78
- path: issue.path,
79
- newMeta: issue.newMeta
80
- });
81
- });
82
- }
83
-
84
- // Rule 4: Duplicates
85
- const duplicateIssues = detectDuplicates(projectDir, registry);
86
- issues.push(...duplicateIssues);
87
- if (autoFix) {
88
- duplicateIssues.forEach(issue => {
89
- fixes.push({
90
- rule: 'duplicate_entry',
91
- action: 'deduplicate',
92
- path: issue.path,
93
- count: issue.count
94
- });
95
- });
96
- }
97
-
98
- // Rule 5: Invalid paths
99
- const invalidPathIssues = detectInvalidPaths(projectDir, registry);
100
- issues.push(...invalidPathIssues);
101
- if (autoFix) {
102
- invalidPathIssues.forEach(issue => {
103
- fixes.push({
104
- rule: 'invalid_path',
105
- action: 'remove',
106
- path: issue.path,
107
- reason: issue.reason
108
- });
109
- });
110
- }
111
-
112
- // Rule 6: Count mismatch
113
- const countIssues = detectCountMismatch(projectDir, registry);
114
- issues.push(...countIssues);
115
- if (autoFix) {
116
- countIssues.forEach(issue => {
117
- fixes.push({
118
- rule: 'count_mismatch',
119
- action: 'fix_count',
120
- expected: issue.actual,
121
- current: issue.recorded
122
- });
123
- });
124
- }
125
-
126
- // Apply fixes if requested
127
- if (autoFix && fixes.length > 0) {
128
- const result = applyFixes(projectDir, fixes);
129
- return {
130
- healthy: issues.length === 0,
131
- issues,
132
- fixes,
133
- applied: result.applied,
134
- backup: result.backup
135
- };
136
- }
137
-
138
- return {
139
- healthy: issues.length === 0,
140
- issues,
141
- fixes
142
- };
143
- }
144
-
145
- /**
146
- * Rule 1: Missing entities (file exists but not in registry).
147
- * @param {string} projectDir
148
- * @returns {object[]} Issues found
149
- */
150
- export function detectMissingEntities(projectDir) {
151
- const newEntities = detectNewEntities(projectDir);
152
- return newEntities.map(entity => ({
153
- rule: 'missing_entity',
154
- severity: 'warning',
155
- path: entity.path,
156
- message: `File exists but not in registry: ${entity.path}`,
157
- entity
158
- }));
159
- }
160
-
161
- /**
162
- * Rule 2: Orphaned entries (in registry but file doesn't exist).
163
- * @param {string} projectDir
164
- * @returns {object[]} Issues found
165
- */
166
- export function detectOrphanedEntries(projectDir) {
167
- const removedEntities = detectRemovedEntities(projectDir);
168
- return removedEntities.map(entity => ({
169
- rule: 'orphaned_entry',
170
- severity: 'error',
171
- path: entity.path,
172
- message: `Registry entry exists but file not found: ${entity.path}`
173
- }));
174
- }
175
-
176
- /**
177
- * Rule 3: Stale metadata (file modified after registry entry).
178
- * @param {string} projectDir
179
- * @param {object} [preloaded] - Pre-loaded registry object (avoids redundant file read)
180
- * @returns {object[]} Issues found
181
- */
182
- export function detectStaleMetadata(projectDir, preloaded) {
183
- const registry = preloaded || loadRegistry(projectDir).registry;
184
- if (!registry) {
185
- return [];
186
- }
187
-
188
- const lastUpdated = registry.metadata?.last_updated
189
- ? new Date(registry.metadata.last_updated)
190
- : new Date(0);
191
-
192
- const issues = [];
193
-
194
- Object.entries(registry.entities || {}).forEach(([_type, entities]) => {
195
- if (Array.isArray(entities)) {
196
- entities.forEach(entity => {
197
- if (entity.path) {
198
- const fullPath = join(projectDir, entity.path);
199
- if (existsSync(fullPath)) {
200
- const stats = statSync(fullPath);
201
- if (stats.mtime > lastUpdated) {
202
- const newMeta = generateEntityMeta(projectDir, entity.path);
203
- issues.push({
204
- rule: 'stale_metadata',
205
- severity: 'info',
206
- path: entity.path,
207
- message: `File modified after registry update: ${entity.path}`,
208
- newMeta
209
- });
210
- }
211
- }
212
- }
213
- });
214
- }
215
- });
216
-
217
- return issues;
218
- }
219
-
220
- /**
221
- * Rule 4: Duplicate entries (same path appears twice).
222
- * @param {string} projectDir
223
- * @param {object} [preloaded] - Pre-loaded registry object (avoids redundant file read)
224
- * @returns {object[]} Issues found
225
- */
226
- export function detectDuplicates(projectDir, preloaded) {
227
- const registry = preloaded || loadRegistry(projectDir).registry;
228
- if (!registry) {
229
- return [];
230
- }
231
-
232
- const pathCounts = new Map();
233
- const issues = [];
234
-
235
- Object.values(registry.entities || {}).forEach(entities => {
236
- if (Array.isArray(entities)) {
237
- entities.forEach(entity => {
238
- if (entity.path) {
239
- pathCounts.set(entity.path, (pathCounts.get(entity.path) || 0) + 1);
240
- }
241
- });
242
- }
243
- });
244
-
245
- pathCounts.forEach((count, path) => {
246
- if (count > 1) {
247
- issues.push({
248
- rule: 'duplicate_entry',
249
- severity: 'error',
250
- path,
251
- count,
252
- message: `Duplicate registry entries for path: ${path} (${count} times)`
253
- });
254
- }
255
- });
256
-
257
- return issues;
258
- }
259
-
260
- /**
261
- * Rule 5: Invalid paths (malformed or absolute paths).
262
- * @param {string} projectDir
263
- * @param {object} [preloaded] - Pre-loaded registry object (avoids redundant file read)
264
- * @returns {object[]} Issues found
265
- */
266
- export function detectInvalidPaths(projectDir, preloaded) {
267
- const registry = preloaded || loadRegistry(projectDir).registry;
268
- if (!registry) {
269
- return [];
270
- }
271
-
272
- const issues = [];
273
-
274
- Object.values(registry.entities || {}).forEach(entities => {
275
- if (Array.isArray(entities)) {
276
- entities.forEach(entity => {
277
- if (!entity.path) {
278
- issues.push({
279
- rule: 'invalid_path',
280
- severity: 'error',
281
- path: '<missing>',
282
- reason: 'Missing path field',
283
- message: 'Entity missing path field'
284
- });
285
- } else if (isAbsolute(entity.path)) {
286
- issues.push({
287
- rule: 'invalid_path',
288
- severity: 'error',
289
- path: entity.path,
290
- reason: 'Absolute path',
291
- message: `Path should be relative: ${entity.path}`
292
- });
293
- } else if (entity.path.includes('..')) {
294
- issues.push({
295
- rule: 'invalid_path',
296
- severity: 'error',
297
- path: entity.path,
298
- reason: 'Contains parent directory reference',
299
- message: `Path contains ..: ${entity.path}`
300
- });
301
- }
302
- });
303
- }
304
- });
305
-
306
- return issues;
307
- }
308
-
309
- /**
310
- * Rule 6: Entity count mismatch (metadata.entity_count != actual).
311
- * @param {string} projectDir
312
- * @param {object} [preloaded] - Pre-loaded registry object (avoids redundant file read)
313
- * @returns {object[]} Issues found
314
- */
315
- export function detectCountMismatch(projectDir, preloaded) {
316
- const registry = preloaded || loadRegistry(projectDir).registry;
317
- if (!registry) {
318
- return [];
319
- }
320
-
321
- let actualCount = 0;
322
-
323
- Object.values(registry.entities || {}).forEach(entities => {
324
- if (Array.isArray(entities)) {
325
- actualCount += entities.length;
326
- }
327
- });
328
-
329
- const recordedCount = registry.metadata?.entity_count || 0;
330
-
331
- if (actualCount !== recordedCount) {
332
- return [{
333
- rule: 'count_mismatch',
334
- severity: 'warning',
335
- actual: actualCount,
336
- recorded: recordedCount,
337
- message: `Entity count mismatch: metadata says ${recordedCount}, actual is ${actualCount}`
338
- }];
339
- }
340
-
341
- return [];
342
- }
343
-
344
- /**
345
- * Apply fixes and create backup before modifying.
346
- * @param {string} projectDir
347
- * @param {object[]} fixes
348
- * @returns {{ applied: number, backup: string }}
349
- */
350
- export function applyFixes(projectDir, fixes) {
351
- const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
352
-
353
- if (!existsSync(registryPath)) {
354
- return { applied: 0, backup: null };
355
- }
356
-
357
- // Create backup
358
- const backupDir = join(projectDir, '.chati', 'backups');
359
- if (!existsSync(backupDir)) {
360
- mkdirSync(backupDir, { recursive: true });
361
- }
362
-
363
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
364
- const backupPath = join(backupDir, `entity-registry-${timestamp}.yaml`);
365
- copyFileSync(registryPath, backupPath);
366
-
367
- // Load registry (fresh read for modification — applyFixes is the write path)
368
- const registry = yaml.load(readFileSync(registryPath, 'utf8'));
369
-
370
- let applied = 0;
371
-
372
- fixes.forEach(fix => {
373
- try {
374
- switch (fix.action) {
375
- case 'add':
376
- // Add missing entity
377
- if (fix.entity) {
378
- const type = fix.entity.type;
379
- if (!registry.entities[type]) {
380
- registry.entities[type] = [];
381
- }
382
- registry.entities[type].push(fix.entity);
383
- applied++;
384
- }
385
- break;
386
-
387
- case 'remove':
388
- // Remove orphaned entry
389
- Object.keys(registry.entities).forEach(type => {
390
- if (Array.isArray(registry.entities[type])) {
391
- registry.entities[type] = registry.entities[type].filter(
392
- e => e.path !== fix.path
393
- );
394
- }
395
- });
396
- applied++;
397
- break;
398
-
399
- case 'update':
400
- // Update stale metadata
401
- Object.keys(registry.entities).forEach(type => {
402
- if (Array.isArray(registry.entities[type])) {
403
- const index = registry.entities[type].findIndex(
404
- e => e.path === fix.path
405
- );
406
- if (index !== -1) {
407
- registry.entities[type][index] = {
408
- ...registry.entities[type][index],
409
- ...fix.newMeta
410
- };
411
- }
412
- }
413
- });
414
- applied++;
415
- break;
416
-
417
- case 'deduplicate':
418
- // Remove duplicate entries (keep first occurrence)
419
- Object.keys(registry.entities).forEach(type => {
420
- if (Array.isArray(registry.entities[type])) {
421
- const seen = new Set();
422
- registry.entities[type] = registry.entities[type].filter(e => {
423
- if (e.path === fix.path) {
424
- if (seen.has(e.path)) {
425
- return false; // Remove duplicate
426
- }
427
- seen.add(e.path);
428
- }
429
- return true;
430
- });
431
- }
432
- });
433
- applied++;
434
- break;
435
-
436
- case 'fix_count':
437
- // Fix entity count
438
- registry.metadata = registry.metadata || {};
439
- registry.metadata.entity_count = fix.expected;
440
- applied++;
441
- break;
442
- }
443
- } catch { /* expected: operation may fail gracefully */
444
- // Skip failed fixes
445
- }
446
- });
447
-
448
- // Update metadata
449
- registry.metadata = registry.metadata || {};
450
- registry.metadata.last_updated = new Date().toISOString();
451
-
452
- // Recalculate entity count
453
- let totalCount = 0;
454
- Object.values(registry.entities).forEach(entities => {
455
- if (Array.isArray(entities)) {
456
- totalCount += entities.length;
457
- }
458
- });
459
- registry.metadata.entity_count = totalCount;
460
-
461
- // Write updated registry
462
- writeFileSync(registryPath, yaml.dump(registry), 'utf8');
463
-
464
- return {
465
- applied,
466
- backup: backupPath
467
- };
468
- }